Repository navigation
fix(solve): read a feature's multiplicity as the model computes it, assuming one value only where the default 0..* governs - #1553
Merged
Conversation
…ssuming one value only where the default 0..* governs A feature the solver reads as one value is held to one by the multiplicity the model computes (GoverningMultiplicityOf: declared, inherited by redefinition, or the default). A stated or implicit multiplicity admitting several values refuses the read as before; a feature that declares and inherits none, which the usual default 0..* governs (KerML 1.1 §7.4.12, §7.4.5.2) — a KerML feature, a package-owned attribute, a bare in parameter that SysML v2 §7.6.3 leaves out of the implicit [1..1] — is read as one value under an assumption the query records (Query.AssumedOne) and the REPL's solver reports print, never asserted as a count. A check of a part walks a defaulted object-holding feature through its one representative object, as validation does, under the same reported assumption. The solve differential runs over the .kerml conformance cases too. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Contributor
Author
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
… line in %explain A chain read through a stated collection still refuses as "it may hold more than one value"; the %explain reports over a bare in parameter now carry the one-value assumption line the solver reports print. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…oup the query states The walk records the assumption on each held group's path, and AssertedOn keeps it only for a group it translates, so a part whose assertions the translation leaves out (Uncovered) assumes nothing. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Two holders of one type hold two objects of the same defaulted part; the walked assumptions are told apart by path, while a value read through the feature stays covered by any object walked under it. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
6 tasks done
Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # docs/project/spec-compliance.md
This was referenced Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Follow-up to #1542. The model's default multiplicity is
0..*everywhere it is judged, but the constraint solver still read a feature that states no multiplicity as exactly one value (StatedMultiplicityOfreported nothing for it, so no refusal and no note). The solver now takes the multiplicity from the model alone (Model.GoverningMultiplicityOf: declared, inherited by redefinition, orDefaultMultiplicityOf;StatedMultiplicityOftells a stated bound from the default) and derives no rule of its own:c.wheels.radiusthroughwheels : Wheel[4]; a redefinition inheriting[0..*]).0..*governs, is read as one value under an assumption the query records (Query.AssumedOne) and the REPL's solver reports print:Assuming test::Tank::level holds one value: it declares no multiplicity, so the default [0..*] governs it. No count is asserted, so no[1..1]is smuggled in. The verdict is about the case where the feature holds one value, which is also the only case the evaluator answers about: over none or two values,level >= 0.0is a type-mismatch refusal, not a verdict (the new_none/_twofixtures pin that).%check <part>walks a defaulted object-holding feature through its one representative object, as%validatedoes, and reports the same assumption once (Assuming spare holds one object: …), merged with the value read through it; the assumption is carried by the held group and reported only when that group is translated, so a part left inUncoveredassumes nothing.Why not a plain refusal: the default
0..*governs more than KerML features. A barein a : Integerparameter of a constraint or calc def is a ReferenceUsage, which SysML v2 §7.6.3 leaves out of the implicit[1..1], andEffectiveParameterRangeanswers0..*for it too (probed:test::C::agoverning0..*, stated none). Refusing it rejects everyconstraint def { in x : Real; … }: 65 tests ininternal/exec/solveand the gRPC verify tests fail, and%check,%solve,%configure,%optimizeand the gRPC verify questions become unusable on ordinary models. The runtime holds such a parameter to0..*as well (two bound values are held, not refused). Reading it as one value and saying so keeps the solver sound about the model and useful.Audit of the other count-dependent paths:
quantify.goalready readsGoverningMultiplicityOffor the variation point (unchanged); noAtMostOne/sizeread exists ininternal/exec/solve; counterexample and witness rendering is per variable and unchanged; the gRPC verify verdict has no field for notes (as forQuery.Uncovered), so the assumption reaches gRPC only through the sharedQueryfor now;%check,%solve,%explain,%configureand%optimizeall print throughsolveReports, which appends the assumption lines.The solve differential now also runs over the
.kermlconformance cases (it skipped them by extension, andmentionsConditionsdid not know KerMLinv).Specification basis
[1..1]for an attribute, item, part or port usage owned by a definition or usage with no owned subsetting or redefinition; a reference usage (a barein x) and a package-owned attribute are left at0..*.docs/project/spec-compliance.md: new row in the Constraint Solving table (the multiplicity rule,Query.AssumedOne), the translatable-subset text and the collections limitation reworded, and the #1542 default-multiplicity row's solver mention.docs/reference/repl-commands.md%checkdescribes theAssuming … holds one valueline.How it was verified
New fixtures under
internal/exec/runtime/testdata/conformance/(runtime conformance and the solve differential):solve_default_multiplicity_kerml_none.kerml(feature level : Real = ())null)levelassumed one; differential: without valuessolve_default_multiplicity_kerml_one.kerml(= 2.0)solve_default_multiplicity_kerml_two.kerml(= (2.0, 3.5))sequence)solve_implicit_multiplicity_usage.sysml(§7.6.3 attribute)solve_redefinition_inherits_multiplicity.sysmllevelNonNegativesatisfied:>> levelunder[1]: translated, nothing assumed, agreed;:>> readingsunder[0..*]: refusedit may hold more than one valueUnit tests:
internal/exec/solve/default_multiplicity_test.go(the five fixtures plus a barein a : Integerparameter assumed once),internal/frontend/repl/check_default_multiplicity_test.go(%checkprints the assumption for a KerML feature, nothing for an implicit[1..1]usage, and the one-object assumption once forpart spare : Tank :> main).Solve differential movement (z3 4.8.12), conformance corpus, before → after: 246 → 251 files, 550 → 556 elements, 342 → 347 translated, 208 → 209 skipped-by-refusal (+1:
readings), 171 → 174 agreed (+3:kerml_one,implicit_multiplicity_usage,levelNonNegative), 147 → 149 without values (+2:kerml_none,kerml_two), 0 disagreed; the standard-library, training-corpus and randomized gates are byte-identical to develop. No oracle baseline (pilot-diff, pilot-exec, xpect, reject, training expectations) moved.Gates (all with
OPENSYSML_REQUIRE_SMT=1 OPENSYSML_SMT=/usr/bin/z3and the training, pilot, spec-example, pilot-library-XMI and PSSM corpora required):go build ./...ok,go vet ./...ok,gofmt -l .empty,python3 scripts/changelog.py checkok,mkdocs build --strictok;go test -count=1 ./...ok (every package;cmd/sysmlandtests/codegenre-run with-timeout 40mafter hitting the local 10-minute default, 257 s and 290 s).Merged
origin/developat 48f60b4 (after #1550 landed): the only conflict was the default-multiplicity row ofspec-compliance.md, resolved by keeping #1550's closing sentence and representative-object row and appending this PR's solver clause; doc gates re-run on the merged tree (mkdocs build --strict,changelog.py check), solve/REPL/runtime suites green. Against #1529 (chore/pilot-pin-2026-09, head 9ca600a) the merge conflicts indocs/project/spec-compliance.mdanddocs/project/validation-constraints.md, but every conflict hunk is between #1529 and develop's own later commits (the pilot2026-08→2026-09prose and the validation census counts); #1529 conflicts with bareorigin/developin the same two files without this branch. This branch's rows merge cleanly outside those hunks. The only file both touch isspec-compliance.md; #1529 touches nothing underinternal/exec/solve,internal/frontend/replor the conformance corpus.Checklist
make testandmake lintpass locallychanges/unreleased/<slug>.<section>.md, not as an edit toCHANGELOG.mdmake docs-countsrun if a gate count moved (no gate count moved)F4,K5) in the body, docs, or changelogLink to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/d98944611f3940a3aed28854fad06251
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/d98944611f3940a3aed28854fad06251?variant=devin
Requested by: @HuiJun