Skip to content

fix(runtime): subsetting feature without a value holds values of the subsetted feature - #1512

Merged
HuiJun merged 7 commits into
developfrom
fix/subsetter-values-from-subsetted
Oct 26, 2026
Merged

HuiJun merged 7 commits into
developfrom
fix/subsetter-values-from-subsetted

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

What and why

A subsetting feature that states no value of its own — no =, no default, no binding determining it whole — now holds values of the feature it subsets, as many as its lower bound asks, instead of objects materializeIntrinsicValue made up for the bound that the subsetted feature never held.

In ShapeItems::Cuboid, item tfe [2] :> edges; held two StructuredCurve objects of its own, none a value of edges; every binding [1] bind [0..1] tfe = [0..1] tf.edges of Box was then violated (the ends shared no value), vertices held 18 objects made up for tflv, tfrv, … where the faces hold 24, and the library's asserted size(vertices) == size(edges) was violated. On the same reading item two [2] :> edges; beside edges [6] = faces.es held two objects outside edges.

  • materializeFromSubsetted (internal/exec/runtime/subsetting.go) runs before a required or binding-constrained subsetter is filled: subsettedPool gathers the values the subsetted features hold by their own declaration (a value expression, a write or a binding-derived value), narrowed to the subsetter's types with canClassify; bindingWitnesses takes, per binding on the subsetter — its own or one an owner declares through the feature's path, walking the owner chain as resolveBindings does; an owner's binding counts only where its other end states a value of its own, since one that would itself draw from what it subsets (cf.edges = be in ConeOrCylinder, with be :> edges = faces.edges) has none to give yet — as many values as the binding links (bindingLinks, shared with CheckBindingOn); pickFromPool fills the rest of the lower bound with pooled values no sibling subsetter holds yet (siblingSubsetterValues).
  • A subsetted feature populated by its own subsetters (faces [6] under tf : Rectangle :> faces) or holding only a default is no pool, so its subsetters keep making their own values and it keeps rolling them up; a feature with no declared subsetted feature is untouched.
  • Context.drawingSubsetters keeps a subsetter that is drawing its values out of subsettingContributions of the feature it draws from, so the read it triggers does not recurse into it; the subsetter is listed as a dependent of every source it draws from (listRead), so a write to one unmaterializes it.
  • A subsetted feature holding fewer values than the bound asks leaves the subsetter short: the read reports the multiplicity violation it already reports, no new diagnostic.

Specification basis

  • KerML 1.0 §7.3.4.4 Subsetting: for each instance of the subsetting feature's domain, its values are values of the subsetted feature. A feature holding objects the subsetted feature never held violates this; a feature holding fewer values than its lower bound violates the multiplicity (§7.3.3.2), and that is the only thing the spec implies for an under-populated subsetted feature.
  • KerML 1.0 §7.4.6 Binding connectors: the related features have the same values; a binding stating a link between a subsetter and another collection fixes which of the subsetted feature's values the subsetter holds, where the other end holds a value.
  • Where neither a binding nor a value expression fixes which values the subsetter holds, the choice is the model's degree of freedom; the implementation takes them in the subsetted feature's order, skipping values a sibling subsetter already holds, so Box's twelve edge subsetters hold twelve distinct edges and its eight vertex subsetters hold the faces' vertices the bindings name.

Behaviour changes

  • ShapeItems::Box (package G { private import ShapeItems::*; item b : Box; }, develop → branch): b.tfe two objects of its own → two values of edges (b.tf.e1, b.ff.e1); b.tflv three objects of its own → three values of b.tf.vertices; b.vertices->size() 18 → 24; b.edges->size() 24 → 24; b.faces->size() 6 → 6; b.subitems 66 → 54 (develop: the 6 faces, their 24 Rectangle edges, 18 made-up StructuredCurve and 18 made-up StructuredPoint; branch: the same faces and edges plus their 24 actual vertices); -validate=G::b binding verdicts 38 reported, 0 held / 38 violated (six bindings with an empty end reached no verdict) → 44 reported, 44 held / 0 violated; the four asserted constraints of Box (size(vertices) == size(edges) and the three face-pair constraints) all hold (before: size(vertices) == size(edges) violated). On develop before fix(runtime): read a binding linking some value of each end as a constraint #1508 the same reads were a binding-end error.
  • ShapeItems::Cylinder: cyl.edges->size() 2 → 2, cyl.be two StructuredCurve objects of its own → the two values of edges (cyl.edges->includes(cyl.be#(1)) false → true); cyl.ae [] → []. ShapeItems::Cone: cone.apex [] → [].
  • User model part def Q { item coll : Node [4] = (x, y, z, w); item two [2] :> coll; }: q.coll->includes(q.two#(1)) false → true, q.two#(1) == q.x false → true. The variant with coll : Node [4] holding no declared value is unchanged: coll has no values of its own, two makes two and coll rolls them up.
  • binding_partial_collections_split conformance: left [1] :> nodes and right [1] :> nodes bound [0..1] left = [0..1] right now share the node the binding links (left.1 is right.1, verdict holds); before they held two objects of their own and the verdict was violated. The robustness case for ends sharing no value (TestRuntimeRobustnessPartialBinding) now uses two features that subset nothing, which is the model that case describes.
  • A binding stating more than one link (binding [2] bind [0..2] picked = [0..2] late on picked [3] :> coll, late = (c, d)) gives the subsetter as many of the other end's values as it links (picked holds c, d and then a; the verdict holds).
  • A binding an owner declares through a path (binding [1] bind [0..1] child.picked = [0..1] chosen beside part child : Child with picked [2] :> coll) guides the nested subsetter as its own bindings do: child.picked holds chosen's value and then another of coll; the verdict holds.
  • A write to the subsetted feature unmaterializes the subsetter, which then holds values of what the feature now holds (coll := (z, y) after pick [1] :> coll held x makes pick hold z), as a reference subsetter follows its source.
  • Tim's model: OccurrencesTest::p1.v 2 → 2, new OccurrencesTest::P1().v 2 → 2.

Fixture, golden and sweep movement

  • internal/exec/runtime/testdata/conformance/instance_library_geometry_box.expected.json: box.vertices 18 → 24 instances; box.tfe.1 is box.tf.e1 and box.tfe.2 is box.ff.e1 (the two edges tfe's bindings name). box.subitems 66 → 54 objects: the 18 StructuredCurve and 18 StructuredPoint the edge and vertex subsetters made up are gone and the faces' 24 actual vertices are in; subitems.7 is tf.e1 and now also tfe.1, subitems.11 is urle.2, vertices.24 is subitems.54.
  • internal/exec/runtime/testdata/conformance/binding_partial_collections_split.expected.json: left.1/right.1 distinct → identical, first verdict violated → holds (above).
  • internal/exec/runtime/shape_items_limits_test.go: box.vertices 18 → 24; the box.tfe.length error case is gone because tfe now holds Rectangle edges, which have a length.
  • docs/project/library-and-prefix-sweep-outcomes.txt (regenerated with -update-library-prefix-sweep), 16 rows: Probe::box.subitems 66 → 54 objects as above; the twelve Probe::box.*e edge subsetters (bfe, bre, bsle, bsre, tfe, tre, tsle, tsre, ufle, ufre, urle, urre) StructuredCurve#1, #2 of their own → two Rectangle edges (ShapeItems::Rectangle::e<n> objects) of edges, those their bindings name; box.tfrv, box.trrv empty → three StructuredPoints (the faces' vertices their bindings name); box.vertices collection(18) → collection(24). No other row moved.
  • tests/corpus/testdata/training_examples_expected.txt: untouched.

New pins: conformance/subsetting_draws_from_subsetted.sysml + .expected.json, conformance/subsetting_binding_links.sysml + .expected.json, conformance/subsetting_binding_from_owner.sysml + .expected.json, robustness_subsetted_pool_test.go:TestRuntimeRobustnessSubsettedPool (under-populated collection; write to the collection), a row in docs/project/spec-compliance.md, changes/unreleased/subsetter-values-from-subsetted.fixed.md.

Cost

Wall time of sysml, three runs each, develop → branch: -instantiate=G::b on Box 0.08/0.08/0.08 s → 0.08/0.10/0.10 s; -action L::count over a 10 000-iteration while loop 0.07/0.08/0.09 s → 0.08/0.07/0.08 s.

How it was verified

  • go build ./... clean; go vet ./... clean; gofmt -l . empty; make lint ✓ (staticcheck + gosec).
  • go test ./... with the four corpora downloaded and OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 OPENSYSML_REQUIRE_SPEC_EXAMPLES=1 OPENSYSML_REQUIRE_PILOT_LIBRARY_XMI=1: every package ok on the merged head except cmd/sysml, which hit its 10 min package timeout under the whole suite's load (as in earlier runs) and passes alone: ok cmd/sysml 237.009s, ok tests/codegen 267.350s (go test -count=1 -p 1).
  • go test -count=1 -run 'TestExecutionConformance|TestHeldImage|TestKernelLibraryMembersReadableWhileRunning|TestRuntimeRobustness|TestShapeItems' ./internal/exec/runtime: ok 36.346s.
  • go test -count=1 -run 'TestExecutionConformance|TestLibraryAndPrefixSweep|TestKernelLibraryMembersReadable|TestRuntimeRobustness|TestHeldImage' ./internal/exec/runtime on the merged head: ok 40.791s.
  • python3 scripts/changelog.py check ✓; python3 scripts/check-doc-ids.py ✓ ("No internal work-item labels in reader-facing documentation").

Dry-merges: origin/develop (cf6a6e2ba, which carries #1492, #1508 and #1515) is an ancestor of this branch (merged in as a merge commit; the one conflict, instance_library_geometry_box.expected.json box.subitems, is re-recorded with this tree's reading, 54 objects, together with the sweep row Probe::box.subitems).

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface
  • Changelog entry added as changes/unreleased/<slug>.<section>.md
  • baselines regenerated and make docs-counts run if a gate moved
  • No internal work-item labels (waves, slices, F4, K5) in anything a user reads

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/51a37fedd9774deb9642040ed4e5a7f4
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/51a37fedd9774deb9642040ed4e5a7f4?variant=devin
Requested by: @HuiJun

devin-ai-integration Bot and others added 2 commits October 25, 2026 10:49
…subsetted feature

A subsetting feature stating no value of its own holds values of the
feature it subsets where that feature holds values by its own
declaration, as many as its lower bound asks (KerML 1.0 §7.3.4.4); a
binding constraint on it picks the values its other end holds (§7.4.6).
A subsetted feature holding fewer values than the bound asks is the
multiplicity violation the read already reports, not objects made up.
In ShapeItems::Box every binding of the faces' edges and vertices holds,
vertices holds the faces' 24 vertices and size(vertices) == size(edges)
holds.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 25, 2026 12:04
devin-ai-integration[bot]

This comment was marked as resolved.

…s; a subsetter follows writes

A binding stating more than one link gives the subsetting feature as
many of its other end's values as it links (bindingLinks, shared with
CheckBindingOn). A binding an owner declares through the feature's path
guides the nested subsetter as its own do, walking the owner chain as
resolveBindings does, where its other end states a value of its own.
The subsetter is listed as a dependent of every feature it draws from,
so a write to one unmaterializes it and the next read draws from what
the feature then holds.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Second push, three movements in bindingWitnesses and none in the readings of the body: a binding stating more than one link now gives the subsetter as many of its other end's values as it links (bindingLinks, factored out of CheckBindingOn), pinned by the subsetting_binding_links fixture; a binding an owner declares through a path (bind child.picked = chosen) is consulted as the feature's own are, by walking the owner chain as resolveBindings does, pinned by the subsetting_binding_from_owner fixture; and the subsetter is listed as a dependent of every feature it draws from — the subsetted features and the other ends of its bindings — so a write to one unmaterializes it and the next read draws from what the feature then holds, pinned by a second TestRuntimeRobustnessSubsettedPool case. No golden or sweep row moved; the Box, Cylinder, Cone and user-model readings are unchanged; gates re-run in the body.

devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Third push, comments only: the four comments this branch added that ran to three to five lines (materializeFromSubsetted, subsettedPool, bindingWitnesses, TestRuntimeRobustnessSubsettedPool) are two lines each. No code, fixture or golden moved.

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/spec-compliance.md
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Fourth push: a merge of #1508's current head c1b19c049 (a binding whose end cannot be resolved is an undecided verdict; an end reaching no object outranks the other end's resolution error), as a merge commit; the one conflict, in docs/project/spec-compliance.md, keeps #1508's updated binding row followed by this branch's subsetting row. No code of this branch moved. With the undecided case in place the readings hold: Box 44 binding verdicts held, none violated or undecided, the four asserted constraints hold, vertices 24, tfe = (tf.e1, ff.e1); Cylinder.be the two values of edges; Cone.apex empty; the user models and both occurrence probes as before. Gates re-run in the body.

…es-from-subsetted

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	internal/exec/runtime/testdata/conformance/instance_library_geometry_box.expected.json
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Fifth push: a merge of develop cf6a6e2ba (#1492, #1508, #1509, #1511, #1514, #1515 and the rest since 111a59a2a), as a merge commit. The one conflict was instance_library_geometry_box.expected.json, where #1515 recorded box.subitems as 66 objects on a tree without this branch; it is re-recorded with this tree's reading, 54, and the sweep row Probe::box.subitems likewise. Per row, develop 66 → branch 54: develop's 66 are the 6 faces (tf, bf, ff, rf, slf, srf), the 24 Rectangle edges (e1–e4 of each face), 18 StructuredCurve objects the twelve edge subsetters (tfe, tre, …, urre) made up for their [2] bounds, none a value of edges, and 18 StructuredPoint objects the vertex subsetters (tflv, tfrv, …) made up the same way, none a value of the faces' vertices; the branch's 54 are the same 6 faces, the same 24 edges and the faces' 24 actual vertices, which vertices now holds through the subsetters. So the 36 made-up objects go and 6 of the faces' vertices appear (the 18 made-up points stood in for 18 of them): −36 + 24 = −12. #1515's identity rows move with it: subitems.1 is tf, .6 is srf, .7 is tf.e1 and now also tfe.1, .30 is srf.e4, .31 is vertices.1, vertices.18 at .48 becomes vertices.24 at .54, tfe.1 at .49 and urle.2 at .66 were made-up objects and are now .7 and .11, Rectangle edges. No code moved; gates re-run in the body.

@HuiJun
HuiJun merged commit 71a21ba into develop Oct 26, 2026
30 checks passed
@HuiJun
HuiJun deleted the fix/subsetter-values-from-subsetted branch October 26, 2026 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant