feat(runtime): set values for unordered collections and tensor quantities of any rank - #121
Merged
Conversation
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
devin-ai-integration
Bot
force-pushed
the
feature/set-and-tensor-values
branch
from
September 8, 2026 04:31
fec37be to
e2a04ef
Compare
devin-ai-integration
Bot
force-pushed
the
feature/set-and-tensor-values
branch
from
September 8, 2026 05:06
e2a04ef to
9bee284
Compare
devin-ai-integration
Bot
force-pushed
the
feature/set-and-tensor-values
branch
from
September 8, 2026 12:52
5869152 to
a17f385
Compare
devin-ai-integration
Bot
force-pushed
the
feature/set-and-tensor-values
branch
from
September 8, 2026 14:15
84aefdc to
d2003bb
Compare
devin-ai-integration
Bot
force-pushed
the
feature/set-and-tensor-values
branch
from
September 8, 2026 19:58
0df8e54 to
f0088ad
Compare
…nsors of any rank A Collection whose elements the library declares unique and not ordered (Set, UniqueCollection, Map) holds them as a set: equal regardless of order, deduplicated, enumerating in a canonical order whenever an ordered operation consumes it, and converted to that sequence when read into an ordered or nonunique feature. Collections the library declares ordered (OrderedSet, OrderedMap, List, Array) or nonunique (Bag) keep their sequence. CollectionFunctions read a Collection object through its elements. Tensor quantities of rank above two construct, index with one index per dimension, and keep their shape through the existing arithmetic; a wrong number of indexes or an index outside a dimension is a typed error. Co-Authored-By: jason.han <[email protected]>
…ire and every client Value gains dedicated set and tensor_quantity arms, advertised as the set_values and tensor_values capabilities; the Go, Python, Node, Rust and Java clients decode, send and refuse them by capability. Sets have no RDF literal form and neither value compiles natively; both are documented, and the spec-compliance rows cite the library declarations. Co-Authored-By: jason.han <[email protected]>
Co-Authored-By: jason.han <[email protected]>
Co-Authored-By: jason.han <[email protected]>
…, clients refuse repeated set members canonicalLess is now a three-way total order: same-rendering values fall back to declaration identity (enumeration literals, variants) or to their elements (sequences, sets, arrays), so equal sets enumerate alike whatever order their members were inserted in. A Set and a Sequence that valueEqual accepts as equal now receive the same valueKeyFunc key, so they share a bucket and deduplicate. The Go, Python and Node clients validate an incoming set's uniqueness by their own value equality — nested sets, sequences and quantities included — as the Rust and Java clients already did. Co-Authored-By: jason.han <[email protected]>
canonicalCompare falls through to the contents of every structured kind whose trace text can coincide — array shape and elements, vector and tensor components, quantity unit, measurement reference, frame and transformation keys, expression span — so two unequal values never share a position and equal sets enumerate alike whatever their insertion order. Co-Authored-By: jason.han <[email protected]>
…numbers exactly, refuse overflowing tensor shapes Co-Authored-By: jason.han <[email protected]>
…able units A set listing a member twice, by each client's value equality, is now refused before it is sent (Go, Node) or on construction and decoding (Python, Rust, Java), so a malformed set never compares equal to a valid one. Client quantity equality converts through the unit's reduction to base units — exactly while the magnitude is an integer and the scale a whole ratio — so 1 m and 100 cm are one member, km/h and m/s are commensurable, and metres never equal seconds; a quantity without a reduction is still compared in its unit as written. The Java membership test expecting 2.0 outside a set holding 2 is corrected to the numeric equality the other clients already implement. Co-Authored-By: jason.han <[email protected]>
…terals by id Client value equality now matches the engine's: a MeasurementRef is one reduction at one scale however it is spelt or which declaration names it (SI::'m/s' is m/s, km/m is m/mm), except that a named unit of dimension one reduces to nothing and so is only its own declaration (rad is not sr); an EnumLiteral is its literal id alone, whatever enumeration id or display name accompanies it. Applies to Go, Python, Node, Rust and Java, with the set-membership consequences: equivalent references are one member and a set listing both is refused. The Python duplicate-set test asserts the ValueError raised at construction, where the check now lives. Co-Authored-By: jason.han <[email protected]>
…instance refs and arrays An unresolved instance reference decoded to a plain int, so a set holding instance 1 and Integer 1 was rejected as a duplicate; Array equality compared elements with Python ==, so nested true and 1 collided. Unresolved references are now an InstanceRef (an int subclass sent back as instance_id), and Array compares its elements as same_value does. Co-Authored-By: jason.han <[email protected]>
InstanceRef is no longer an int subclass, so Vector, Quantity, Array and TensorQuantity dimensions and typed.as_int/as_float/as_complex refuse it instead of encoding it as int_value; Connection still sends it as instance_id. Co-Authored-By: jason.han <[email protected]>
Co-Authored-By: jason.han <[email protected]>
Co-Authored-By: jason.han <[email protected]>
…ore it is sent Co-Authored-By: jason.han <[email protected]>
Co-Authored-By: jason.han <[email protected]>
…time Co-Authored-By: jason.han <[email protected]>
…et apart from its sequence as a member canonicalCompare falls through to the calc, bound object and enclosing run when two function values render alike, so equal sets of functions enumerate and cross the wire in one order however they were written. Set membership no longer equates a set with the sequence of its members: valueEqual and valueKeyFunc keep the two kinds apart, matching every client, while the == operator and binding agreement still read a set flowing into an ordered context as its canonical sequence (equalValues). Co-Authored-By: jason.han <[email protected]>
…sendable members canonicalCompare orders each value by the representative valueEqual reads it as, so a real-axis complex number sits among the numbers and an empty collection with null, and equal sets holding different representatives enumerate alike; a for loop over a set visits that same canonical order rather than a rendering sort. A set holding a member with no wire form, or one the service withholds, crosses as one unsupported null naming the set instead of nulls in the members' places, which would read back as a repeated member. Co-Authored-By: jason.han <[email protected]>
…refuse a quantity without a magnitude Set membership in the Go, Python, Node, Java and Rust clients now judges a null, an empty sequence and an empty set as one value, as the engine does, so a set spelling the absent value twice is refused before it is sent or when it is decoded. The Go client refuses a Quantity carrying no magnitude with CodeInvalidArgument wherever it is sent (scalar, vector or tensor component) instead of putting an empty quantity on the wire. Co-Authored-By: jason.han <[email protected]>
devin-ai-integration
Bot
force-pushed
the
feature/set-and-tensor-values
branch
from
September 8, 2026 21:19
c52bc13 to
f00ea98
Compare
A null, an empty Sequence and an empty SetValue compare equal under sameValue, so valueHash now hashes them to one code: sets that differ only in how their absent member is spelt hash and key alike. Co-Authored-By: jason.han <[email protected]>
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
The Kernel Data Type Library declares some collections'
elementsunique and unordered, but the runtime read every multi-valued feature as an ordered sequence: aSet { :>> elements = (1, 2, 2, 3); }answered[1, 2, 2, 3],CollectionFunctions::size(s)answered 1 (it counted the object) and two sets written in different orders were unequal. Tensor quantities of rank three and above constructed, but nothing pinned their indexing, shape checks, wire form or native/RDF behaviour.Set values.
Set::elements,UniqueCollection::elementsandMap::elements— the featuresCollections.kermldeclaresuniquewithoutordered— now hold aValSet(internal/core/runtime/set_feature.go). Classification follows the library declaration through specialization and redefinition (Context.holdsSet), so a userattribute def MySet :> Setholds a set and aBag,List,Array,OrderedSetorOrderedMap, or any feature declaredordered/nonunique, stays a sequence. A set keeps each member once,sizecounts members,==/!=/sameignore the order written,contains/containsAllare membership, andisEmptyreflects the members.Every operation whose library signature is
ordered nonuniqueis unchanged; a set reaching one is read as its members in a canonical order. That order is total and documented (set_order.go,docs/reference/wire-contract.md,spec-compliance.md): nulls, thenfalsebeforetrue, numbers ascending, complex by real then imaginary, strings lexicographic, quantities by dimension then magnitude, enumeration literals by declaration (qualified name, then document and position, so same-named literals of different enumerations still order alike), objects by identity, everything else by trace text and then by its elements — onlyvalueEqualvalues compare as neither before nor after.collect,select,head,tail,#, comparison against a sequence, trace rendering and a write into anorderedornonuniquefeature all consume that enumeration.FormatTraceValuealready renderedSet{…}; it now renders the canonical enumeration rather than insertion order, which is the only change to existing expectations:internal/core/runtime/value_test.goTestSetElementsPreserveInsertionOrder→TestSetElementsEnumerateInCanonicalOrder:{2, 1, 2, 3}enumerates[1, 2, 3], not[2, 1, 3].internal/repl/runtime_commands_test.goTestFormatValue/set:Set{"a", "z"}, notSet{"z", "a"}.Both follow from
Collections.kerml'sSet("unique and unordered"): an unordered value has no insertion order to preserve, and a deterministic enumeration is what makes equal sets print and index alike. No existing conformance golden or trace changed; every set fixture is new.There is no
distinctin the bundled library, andSequenceFunctions::union/intersection/including/includingAt/excludingare all declaredordered nonunique(SequenceFunctions.kerml), so they remain sequence-valued even over set operands;library_set_sequence_functions.sysmlpins that.Tensors of any rank.
TensorQuantityis rank-agnostic: aTensorMeasurementReferencewith:>> dimensions = (2, 2, 2)builds a rank-three tensor,#takes one index per dimension, and each shape failure is a typed error — too few/too many indexes and a component count offflattenedSizeareErrMultiplicityViolation, an index outside1..dimensionisErrIndexOutOfRange, a non-Integer index isErrTypeMismatch, and+/-between different shapes isErrMultiplicityViolationnaming both. Shape survives+,-and the scalar products; formatting isTensor(2, 2, 2)[…] [m].gRPC.
api/proto/sysml.protogains twoValuearms:A set is sent in canonical order and accepted in any order; a repeated member is
INVALID_ARGUMENT. A tensor is its positive dimensions and one scalarQuantityper row-major component; a non-positive dimension, a component count off the product or a component without a magnitude isINVALID_ARGUMENT. A rank-one tensor stays a tensor, distinct fromVectorQuantity. The service advertisesset_valuesandtensor_values; a client that omits either receives the unsupported null it always did and an inbound set/tensor isUNIMPLEMENTED— nested values are inspected recursively. Stubs were regenerated withmake proto proto-buf python-proto proto-ts proto-rust; the Python gRPC header pin (GRPC_GENERATED_VERSION = '1.83.0') matches CI'sgrpcio-tools. The Go, Python, Node, Rust and Java clients each gain a native set type (unique, order-insensitive equality, nestable) and tensor type (positive dimensions, exact shape, row-majorget, wrong-rank and out-of-range errors, shared unit when there is one), decode both arms, send both as calc arguments, and refuse them locally to a service without the capability.internal/grpc/convert_structured_test.goTestTensorQuantityCrossesAsUnsupportedis removed: it asserted the arm did not exist, andTestTensorQuantityRoundTrip/TestSetAndTensorCapabilitiesreplace it with the arm present and the arm withheld.RDF. The mapping represents models, not evaluated values, so neither value has a literal form; a set-valued or tensor-valued feature exports as the expression tree that values it and reads back verbatim.
rdf_expr.gonow spells a multi-index ascube#(2, 1, 2)rather thancube#((2, 1, 2)). Documented indocs/reference/rdf-mapping.md.Native compilation. A calc with a set or tensor parameter, result, attribute or local is refused with
codegen.UnsupportedError(Unwrap() == ErrUnsupported) naming the type; documented indocs/project/native-compilation.mdandspec-compliance.md.Specification basis
KerML 1.1 Kernel Data Type Library,
Collections.kerml:Collection::elements(nonunique),OrderedCollection::elements(ordered nonunique),UniqueCollection::elements(unique, "not necessarily ordered"),Bag("unordered and nonunique"),Set("unique and unordered"),OrderedSet("unique and ordered"),Map/OrderedMap;SequenceFunctions.kermlunion,intersection,including,includingAt,excluding(ordered nonunique);CollectionFunctions.kerml==,size,isEmpty,notEmpty,contains,containsAll. SysML v2 1.1Quantities.kermlTensorQuantityValueandMeasurementReferences.kermlTensorMeasurementReference::dimensions/flattenedSize.docs/project/spec-compliance.mdgains set and tensor rows (canonical iteration, ordered-boundary conversion, wire encoding, capability behaviour, RDF, native) and its gRPC Values row now coversset/tensor_quantity. Known limitation, recorded there asOrderedSet::elements,OrderedMap::elements, any multi-valued feature not declarednonunique) is still not enforced at runtime —OrderedSet { :>> elements = (1, 1, 2); }reads[1, 1, 2]with size 3, as before this change. The set kind only covers what the library declares unordered.How it was verified
New coverage:
internal/core/runtime/testdata/conformance/):library_set_elements,library_set_elements_already_distinct,library_set_elements_empty,library_unique_collection_elements,library_map_elements,library_bag_elements,library_ordered_set_elements,library_set_operations(equality regardless of order, membership, size, conversion into ordered/nonunique/plain features),library_set_sequence_functions(everySequenceFunctionsresult stays a sequence over set operands),calc_set_consumed_by_ordered_operations(+ trace golden),instance_tensor_rank_three,instance_tensor_rank_three_failures.set_feature_test.go,collections_test.goTestCollectionOperationsOverSets,tensor_test.go(rank-three construction, indexing, arithmetic, equality, formatting).robustness_test.go): typed errors for each tensor shape/index failure and higher-rank identity.internal/grpc/convert_set_tensor_test.go(round trips, malformed input, nested values on everyValuesurface, capabilities);client/opensysml/set_tensor_test.goover in-process, Connect protobuf and Connect JSON.internal/core/export/set_tensor_rdf_test.go.internal/repl/compile_test.gorefusalsSetParam,SetElements,SetLocal,TensorParam,TensorBuilt.conformance/fixtures/set_tensor.sysml, scenarios in01-server-info,04-evaluate,10-evaluate-calc—94 scenarios: 91 passed, 0 failed, 3 skippedagainst the Go package client.sysml-grpc: Python747 passed(test_set_tensor.pyall 54 service-backed and unit cases pass); Node108 pass; Rust49 + 5 + 14 passed. The Java sources compile against the regenerated stubs and their tests (PublicTypesTest,ProtosTest,ApiIntegrationTest) are written, but Maven Central rate-limited this machine (HTTP 429 resolvingjacoco-maven-plugin) so the Java suite could not be run locally; CI's Java job covers it.Checklist
make testandmake lintpass locallychanges/unreleased/<slug>.<section>.md, not as an edit toCHANGELOG.mdmake docs-countsrun if a gate count moved (compliance rows need nothing: the census is counted at docs build)F4,K5) in the body, docs, or changelog