RelationSystem¶
RelationSystem orchestrates the solve process across a set of relations and variables, handling inference, constraints, and diagnostics.
RelationSystem solves a set of Relation objects against Variable objects using a dict-based bipartite graph.
Profile integration is explicit in relation functions; RelationSystem does not
auto-integrate profile outputs for scalar variables.
Core inputs (dataclass fields)
- relations: list of Relation
- variables: list of Variable
- mode: "overwrite" or "check"
- verbose: bool
- n_max: max block size (default 4)
- max_passes: max overwrite passes
- default_rel_tol: fallback tolerance for variables without overrides
Example
from fusdb import RelationSystem, Variable, Relation
rel = Relation(
name="Aspect ratio",
output="A",
func=lambda R, a: R / a,
inputs=["R", "a"],
tags=("geometry",),
)
system = RelationSystem(
relations=[rel],
variables=[
Variable(name="R", values=[3.0], input_source="explicit"),
Variable(name="a", values=[1.0], input_source="explicit"),
],
mode="overwrite",
)
system.solve()
Internal maps (examples)
- _vars = {"R": Variable(...), "a": Variable(...), "P_fus": None}
- _vars_to_rels = {"R": {relA, relB}, "a": {relC}, ...}
- _rels_to_vars = {relA: ("R", "a", "B0"), ...}
- _var_order = ["R", "a", "B0", ...]
- _var_constraints_map = {"R": ("R > 0",), ...}
- _rel_constraints_map = {relA: ("a > 0",), ...}
Methods (concise, all)
- __post_init__() builds maps, constraints, logger, and pending relations.
- variables_dict property returns {name: Variable}.
- _get_value(name) returns the effective value considering pass/override logic.
- _values_dict() returns the current values dict.
- _accept_candidate_values(...) validates and commits candidate values.
- _set_value(...) writes a value and updates pending relations/metadata.
- _expected(rel, values) computes expected output (or None).
- _residual(rel, values, scaled=False) computes actual - expected.
- _residual_derivative(rel, name, values, current=None) finite-difference derivative.
- _candidate_better(key, best_key, tol=...) compares candidate scores.
- _constraints_violated(values, rel=None, names=None) checks constraints.
- _solve_for_value(rel, name, values_map, prefer_eval_output=False) solves a single var.
- _apply_relation(rel, rel_values, missing_inputs) forward/backward apply.
- _infer_var_bounds(name) infers simple numeric bounds from constraints.
- _constraint_residuals(constraints, values, penalty) returns constraint penalties.
- _least_squares_block_compact(relations, unknowns, values_map) nxn LSQ solver.
- _solve_block(relations, unknowns, values_map) picks 1×1 vs nxn path.
- _build_unknown_map(rel_nodes, values) groups relations by unknown set.
- _solve_unknown_blocks(unknown_map, values) solves blocks up to n_max.
- _enforce_pending_relations(rel_index) processes the pending queue.
- _start_pass() seeds overrides and resets pending for a new pass.
- _select_culprit(rels, values, rel_nodes) chooses an override candidate.
- solve() runs the main loop.
- _violated_relations(values, rels=None) returns violated relations.
- _relation_status(rel, values) returns (status, residual).
- _culprit_for_relation(rel, values) finds a likely culprit variable.
- diagnose(values_override=None) returns consolidated diagnostics.
- export_relation_graph(path="relation_graph.html") writes an HTML graph.
Solve flow (detailed)
1. If mode == "check", exit early (no mutation).
2. Seed the pending queue with all relations.
3. Loop passes:
4. Enforce pending relations via _enforce_pending_relations (forward eval; backward solve if exactly one input is missing).
5. Update violated relations based on current values.
6. Build unknown blocks and try to solve 1×1..n_max with _solve_unknown_blocks (1×1 uses solve_for_value; nxn uses compact LSQ with bounds and penalties).
7. If progress was made, continue; if not and violations exist in overwrite mode, pick a culprit and override, then start a new pass.
8. Exit when no progress remains (or after max_passes).
Outputs and diagnostics
- variables_dict → {name: Variable} (built from _vars + _var_order)
- diagnose() → dictionary with:
- relation_status: list of (relation, status, residual)
- violated_relations: list of relation names
- likely_culprits: mapping relation_name -> (variable, rel_change, target_value)
- variable_issues: list of (variable, status, rank)
- soft_constraint_violations: list of (kind, name, constraint)
Relation Interactions¶
This section summarizes how coupled relations interact during RelationSystem.solve().
Interaction Pattern¶
At runtime, RelationSystem builds a bipartite graph:
- variable nodes (
R,a,P_fus,n_e, ...) - relation nodes (each equation with one output)
Edges connect relation inputs/output names to variables.
Solve Mechanics¶
In overwrite mode, the solver iterates by:
- forward evaluation when all inputs are known;
- backward single-unknown solve when one input is missing;
- block solve for coupled unknown sets (
1x1ton_max x n_max); - culprit-based override if unresolved violations remain.
In check mode, no variable mutation is performed and only diagnostics are returned.
Typical Cross-Domain Couplings¶
- Geometry -> volume/shape -> profile integrals -> fusion and radiation power.
- Composition -> density partition and species fractions -> reactivity and pressure.
- Confinement (
tau_E) <-> power balance (P_loss) through implicit loops. - Operational limits consume solved state and report feasibility boundaries.
Constraint Roles¶
- Hard constraints enforce mathematical/physical admissibility and can block candidate values.
- Soft constraints are recorded as warnings for design-space awareness.
Both relation-level and variable-level constraints influence candidate ranking.
Profile-Aware Flows¶
Profiles are explicit variables and are not automatically integrated for scalar outputs. If a scalar quantity depends on a profile, the relation function must integrate it directly. This keeps coupling visible in the relation graph and avoids hidden aggregation behavior.