Skip to content

Combining systems compares the variables, and a loaded system holds the live ones - #486

Merged
ofloveandhate merged 1 commit into
chore/housekeeping-for-4.0from
fix/combining-systems-compares-the-variables
Oct 3, 2026
Merged

ofloveandhate merged 1 commit into
chore/housekeeping-for-4.0from
fix/combining-systems-compares-the-variables

Conversation

@ofloveandhate

Copy link
Copy Markdown
Contributor

Stacked on #483 (chore/housekeeping-for-4.0), so the diff shows only this work; it retargets to develop when #483 merges.

A loaded system holds the live variables. Variable::Make interns variables by name, and the whole symbolic engine relies on that: making x here and x there gives one node. Boost deserialization bypassed Make, so a System or Slice that came through an archive, which is what copy.deepcopy, copy.copy and pickle do, held variables equal by name but distinct as objects. A System and a Slice now re-intern their nodes as the last step of a load (the loader for records already did this through ReinternNodes), so a copied or unpickled system combines with its original. ADR-0068 records the decision; a new archivable class that holds nodes needs the same step.

Combining systems makes one check. Concatenate compared variable orderings by object and refused the un-interned copies. MakeHomotopy and MakeMovingHomotopy compared counts only and accepted anything with the right number of variables, then blended block operands by position: a target over (x, y) and a start over (y, x) produced a homotopy that evaluated y - 3 at (7, 11) to 4. All four combining operations (Concatenate, MakeHomotopy, MakeMovingHomotopy, operator+=) now go through CheckVariableStructuresMatch, which compares the ordering, the affine and projective groups, the ungrouped variables and the homogenizing variables, and says which kind of difference it found: the same variables in a different order, a different grouping, different variables, or the same names as different objects. A system that declares no variables adopts the other's structure, so building a system from functions alone and combining it still works.

Python nodes compare by identity of the node. __eq__, __ne__ and __hash__ on the node base compare the underlying pointer, so Variable('x') == Variable('x') holds and nodes can be dictionary keys; comparing with a non-node returns NotImplemented.

A latent bug the work exposed. System::swap omitted the auxiliary variable groups and auxiliary coordinates. It surfaced when MakeHomotopy gained a second return value and the move stopped being elided. Fixed, with a test.

Tests: core/test/classes/system_identity_test.cpp, core/test/classes/auxiliary_coordinates_test.cpp, python/test/classes/clone_concatenate_test.py. The concatenate tutorial and the changelog describe the rule. Verified: all C++ suites, the Python suite (1160), the tutorial doctests (198), and the three lints. The cellular decomposition port, the most intensive user of the library, combines systems at dozens of sites and was refused nowhere.

🤖 Generated with Claude Code

…he live ones

Variables are canonical by name: Variable::Make interns, so the x made here and the x made there are one object.  Two things contradicted that.

An archive handed back variables that were not interned.  Boost deserialization builds a node through its default constructor, never through Make, so copy.deepcopy, copy.copy, pickle and the MPI broadcast each returned a system content-equal to the original and sharing none of its nodes, which could not be concatenated with the system it was copied from.  System and Slice now re-intern what they hold as the last step of loading.  The repair existed (ReinternNodes, used by the records loader); it is in the load itself now, so no caller has to remember it.

The operations that combine systems asked two different questions.  Concatenate compared the variable orderings; MakeHomotopy, MakeMovingHomotopy and += compared counts, and evaluate their operands by position.  Measured: moving rows over (y, x) blended with a fixed system over (x, y) were accepted, and y - 3 at (x, y) = (7, 11) evaluated to 4.  All four now make one check, CheckVariableStructuresMatch, variable by variable, group by group, in order, and the refusal names both structures and says whether the order, the grouping or the variables differ.  A system that declares no variables takes the variable structure of the one it is combined with (AdoptVariableStructure).

In Python a node compares by identity: __eq__, __ne__ and __hash__ on the node wrappers compare the node.  Each call across the binding makes a new wrapper, and the default == said two handles on one variable were different.

Found on the way: System swap left out the auxiliary declarations, so a moved or assigned System lost them.  It did not show while MakeHomotopy had a single return statement and the compiler elided the move; the adoption added a second.

straight_line_homotopy accepts deep-copied operands as a result; it had refused what the builder beneath it accepted.  Left alone on purpose: a function that mentions a variable no group holds, which waits for parameters.  ADR-0068.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ofloveandhate

Copy link
Copy Markdown
Contributor Author

this PR brings an imporatnt ability in Python -- x made twice, is the same x. and, systems that are loaded from pickel etc are combineable with those already in memory, when they use the same symbols. this is the hashconsing work i did in June and July coming back in winning form. I used Claude Fable to do that work, and it has made some UI issues in this library ... disappear. Verschwinden.

@ofloveandhate ofloveandhate left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interning work is admittedly the part i least understand of this work. I am accepting it -- it clearly works correctly. I am doing my best to be diligent, and I accept responsibility for the correctness of this code. If it is incorrect in any way, I will do my best to promptly fix it. My dreams are big. I am reaching for them.

@ofloveandhate
ofloveandhate merged commit e638995 into develop Oct 3, 2026
20 checks passed
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