routing.core.bundle_optimization#

Bundle optimization: phenomena measured on a bundle, aggregated into a GA problem, recorded pass by pass.

Three layers. A Phenomenon measures one physical effect on the bundle as a vector (one entry per line, collision pair, turn or rule violation). Cost and Constraint are the two aggregation policies over a phenomenon — soft F term versus hard G block — picked per OptimizationTerm, derived from the bundle’s own rules (OptimizationTerm.from_bundle()) or handed to the orchestrator as a runtime override. The GASolver (pymoo GA with swappable operator configs) minimizes the assembled BundleProblem; the routing.core.harness.HarnessOptimizer orchestrates the passes, recorded in the HarnessOptimizationHistory dashboard.

class routing.core.bundle_optimization.AnnealedSurvivalConfig(*, name: str = '', temp0: float = 0.05)[source]#

Bases: Model

Survival lever: annealed tolerance to infeasibility, cooling over the generation budget.

build(problem: BundleProblem, config: GAConfig) → Survival[source]#

Build the configured AnnealedViolationSurvival cooling over the termination’s generation budget.

temp0: float#
class routing.core.bundle_optimization.AnnealedViolationSurvival(temp0: float, n_gen_total: int)[source]#

Bases: Survival

Feasibility-first survival whose infeasible ranking is softened by a cooling temperature.

Feasible individuals (CV<=0) always survive over infeasible ones and rank among themselves by objective. Infeasible ones rank by their violation plus a non-negative noise scaled by a temperature decaying linearly to zero over n_gen_total generations: early on a worse-violation individual can survive (the annealing escape), and as the temperature reaches zero the ranking becomes exact violation descent (feasibility by the end). The objective never enters the infeasible comparison (unlike stochastic ranking), so it cannot pull the search toward more violation. Generation counted internally (robust to pymoo’s copy_algorithm deepcopy).

class routing.core.bundle_optimization.BackBend(bundle: Bundle, interactions: BundleInteractions)[source]#

Bases: Phenomenon

Per turn dot(outgoing_a, outgoing_b) at the shared point; positive = acute fold-back of one route.

property carriers: list[Turn]#

The turn measured by each entry.

measure() → ndarray[source]#

Per-turn outgoing alignments (positive = back-bend).

name: str = 'BackBend'#
property size: int#

One entry per successive line pair.

class routing.core.bundle_optimization.BlockCrossoverConfig(*, name: str = '', granularity: str = 'chain', prob: float = 0.9)[source]#

Bases: Model

Domain crossover lever: groups inherited whole from one parent (see GroupBlockCrossover).

build(problem: BundleProblem, config: GAConfig) → Crossover[source]#

Build the configured GroupBlockCrossover at point or chain granularity.

granularity: str#
prob: float#
class routing.core.bundle_optimization.BundleEncoding(slices: list[tuple[BundlePoint, slice, type[BundleVariable]]], lower: ndarray, upper: ndarray, n_var: int)[source]#

Bases: object

Maps a Bundle’s updatable BundleVariables to and from a flat numpy vector.

Bounds are read straight off each BundlePoint (lower/upper, accumulated by BundleLine.clip_bounds_in_cadmap() over incident lines): a 3-entry box for a PointVariable, a scalar range for a PortVariable.

classmethod from_bundle(bundle: Bundle) → BundleEncoding[source]#

Build the vector layout from the bundle’s updatable points and their accumulated bounds.

Raises when an updatable point carries no bounds: the bundle was not prepared — call Bundle.attach_local_cadmaps() first.

pack() → ndarray[source]#

Read the current Bundle state into a flat vector matching the encoding layout.

unpack(x: ndarray) → None[source]#

Write a flat vector back into the Bundle (mutates BundleVariables in place).

class routing.core.bundle_optimization.BundleProblem(bundle: ~routing.core.bundling.Bundle, encoding: ~routing.core.bundle_optimization.BundleEncoding, costs: list[~routing.core.bundle_optimization.Cost], constraints: list[~routing.core.bundle_optimization.Constraint] = <factory>)[source]#

Bases: object

Wires bundle + encoding + costs + constraints. Solver-facing.

Requires a prepared bundle (local CadMaps attached, bounds clipped): BundleEncoding.from_bundle() enforces that contract at encoding time.

bundle: Bundle#
constraints: list[Constraint]#
costs: list[Cost]#
diagnose(x: ndarray | None = None) → dict[source]#

Per-cost contribution and per-constraint violation summary at state x (current state if None).

encoding: BundleEncoding#
evaluate(x: ndarray) → tuple[float, ndarray][source]#

Set the Bundle to state x, return (F, G). Mutates the Bundle in place.

classmethod from_terms(bundle: Bundle, interactions: BundleInteractions, terms: list[OptimizationTerm], stage: str) → BundleProblem[source]#

Bind the terms active in stage on the prepared bundle and assemble the problem.

Each term declares the stages it joins (OptimizationTerm.active_in). Binding is pure measurement wiring: the bundle must arrive prepared and posed — HarnessOptimizer._run_pass() owns that sequence.

property lower: ndarray#

Per-variable lower bounds.

property n_constr: int#

Total number of entries in the concatenated G vector.

property n_var: int#

Number of optimization variables in the flat vector.

property upper: ndarray#

Per-variable upper bounds.

violated_entries() → list[ViolatedEntry][source]#

Every G entry above its tolerance, mapped to the carrier entity its phenomenon measures.

The per-entity identity diagnose() aggregates away: one ViolatedEntry per violated entry (residual > 0), carrying the owning constraint’s name, the carrier (a BundleLine, CollisionPair, Turn, or None for entity-less rule entries) and the residual.

class routing.core.bundle_optimization.BundleSnapshot(*, name: str = '', label: str, bundle: ~routing.core.bundling.Bundle, values: list[list[float] | float], locks: list[~volmdlr.primitives3d.Cylinder] = <factory>, solutions: list[~routing.core.core.Solution] = <factory>, passes_before: int = 0, signature: list[int] | None = None)[source]#

Bases: Model

One labeled bundle state outside the pass cycle — a (re)generation — with the locks that led to it.

solutions is the complete routed set of that generation (references, not copies): the replayable side of the mutable cadmap — CadMap.restore_solutions() rebuilds the solution/pack state from it, CadMap.withdraw_zones() strips the later locks. passes_before is the chronology key: how many optimization passes the history held when the snapshot was taken, so rendering interleaves snapshots and passes in event order.

bundle: Bundle#
label: str#
locks: list[Cylinder]#
passes_before: int#
signature: list[int] | None#
solutions: list[Solution]#
values: list[list[float] | float]#
volmdlr_primitives() → list[Mesh3D | Cylinder][source]#

Return the snapshot’s bundle mesh under its label, plus its lock cylinders (red, translucent).

class routing.core.bundle_optimization.CadInterference(bundle: Bundle, interactions: BundleInteractions, *, alpha: float = 0.0)[source]#

Bases: Phenomenon

Per-line deepest pipe-surface penetration into CAD along the scored span (world units, >= 0).

alpha is the safety margin in voxel units, padding the packed radius. Measure only: the satisfiability precondition — posing every port-leader base at the first clear abscissa of its axis (Bundle.clear_port_cad_margin(), meaningful at alpha = 0 too) — belongs to the pass preparation, before the interactions snapshot and the encoding read the posed state.

property carriers: list[BundleLine]#

The scored line measured by each entry.

measure() → ndarray[source]#

Per-line CAD penetrations.

name: str = 'CadInterference'#
property size: int#

One entry per scored line.

class routing.core.bundle_optimization.CauchyMutation(scale: float = 0.1, gene_prob: float | None = None)[source]#

Bases: Mutation

Heavy-tailed (Cauchy) mutation: large jumps to cross constraint-violation ridges in a single move.

Each gene is perturbed with probability prob by a Cauchy step scaled by scale * (xu - xl); the heavy tail produces occasional large jumps a Gaussian/polynomial mutation never would, then the result is clipped to the bounds. Pairs with Deb selection: the jump explores, Deb keeps what lands at lower violation.

class routing.core.bundle_optimization.CauchyMutationConfig(*, name: str = '', scale: float = 0.1, gene_prob: float | None = None)[source]#

Bases: Model

Heavy-tailed mutation lever: Cauchy jumps across violation ridges (see CauchyMutation).

build(problem: BundleProblem, config: GAConfig) → Mutation[source]#

Build the configured CauchyMutation.

gene_prob: float | None#
scale: float#
class routing.core.bundle_optimization.ChainTranslationConfig(*, name: str = '', scale: float = 0.15, chain_prob: float | None = None)[source]#

Bases: Model

Domain mutation lever: whole-chain Cauchy translations (see ChainTranslationMutation).

build(problem: BundleProblem, config: GAConfig) → Mutation[source]#

Build the configured ChainTranslationMutation over the problem’s chain groups.

chain_prob: float | None#
scale: float#
class routing.core.bundle_optimization.ChainTranslationMutation(groups: VariableGroups, scale: float = 0.15, chain_prob: float | None = None)[source]#

Bases: Mutation

Translate whole chains: one shared Cauchy step per mutated chain, ports stepped along their own axis.

Independent per-gene mutation kinks a chain — every point moves its own way, raising length and back-bend before any constraint gain. Moving the chain as one rigid piece is the move the harness physically allows; the Cauchy tail still produces the occasional large escape jump.

class routing.core.bundle_optimization.Constraint(phenomenon: Phenomenon, tolerance: float = 0.0)[source]#

Bases: object

Hard form of a phenomenon: G block measure - tolerance, feasible when <= 0 (pymoo convention).

property name: str#

The measured phenomenon’s name.

property size: int#

Number of entries contributed to the global G vector.

class routing.core.bundle_optimization.Cost(phenomenon: Phenomenon, weight: float = 1.0, power: int = 1)[source]#

Bases: object

Soft form of a phenomenon: weight * sum(max(0, measure)^power) added to F.

property name: str#

The measured phenomenon’s name.

class routing.core.bundle_optimization.CriteriaTermination(criteria: TerminationCriteria)[source]#

Bases: Termination

pymoo Termination racing every active criterion of a TerminationCriteria — first to trip stops the run.

Progress is the largest consumed budget fraction (generations, wall-clock); it jumps to 1.0 when the current regime’s stall window trips or when stop_when_feasible fires on a feasible best. Switching regime clears the other regime’s window, so each regime is judged on a fresh stall clock. Windows count evaluations (not generations), staying meaningful at any population size — 20 generations mean 1000 evaluations at pop 50 but only 40 at pop 2.

class routing.core.bundle_optimization.GAConfig(*, name: str = '', pop_size: int = 2, seed: int = 1, eliminate_duplicates: bool = True, x0: list[float] | None = None, jitter_sigma: float | None = None, jitter_distribution: str = 'normal', termination: TerminationCriteria = TerminationCriteria(name='', n_gen=12500, max_seconds=None, feasible_tol=1e-06, feasible_stall=StallCriterion(name='', ftol=0.001, evals=1000), violation_stall=None, stop_when_feasible=False), verbose: bool = False, mutation: PolynomialMutationConfig | CauchyMutationConfig | ChainTranslationConfig | None = PolynomialMutationConfig(name='', genes_per_child=3.0), crossover: BlockCrossoverConfig | None = None, survival: AnnealedSurvivalConfig | None = None, term_record_period: int | None = None)[source]#

Bases: Model

Every GA option in one place; defaults = the 2026-07 campaign verdict (pop 2, ~3 mutated genes per child).

At pop_size=2 always set jitter_sigma (production: max_shift, normal) — two full-domain random individuals are a dead start. termination declares the stopping policy (TerminationCriteria): every active criterion races and the first to trip stops the run. term_record_period records the per-term F/G breakdown at the Deb-best every that many generations (one extra bundle evaluation each — significant at tiny populations).

crossover: BlockCrossoverConfig | None#
eliminate_duplicates: bool#
initial_sampling(x0: ndarray) → FloatRandomSampling[source]#

JitteredSeedSampling around x0 when jitter_sigma is set, full-domain random otherwise.

jitter_distribution: str#
jitter_sigma: float | None#
mutation: PolynomialMutationConfig | CauchyMutationConfig | ChainTranslationConfig | None#
pop_size: int#
seed: int#
survival: AnnealedSurvivalConfig | None#
term_record_period: int | None#
termination: TerminationCriteria#
verbose: bool#
x0: list[float] | None#
class routing.core.bundle_optimization.GASolver(config: GAConfig | None = None)[source]#

Bases: object

Constrained single-objective GA (pymoo, Deb dominance) driven by a GAConfig.

The production solver. Builds the pymoo algorithm from the config’s operator choices, minimizes with per-generation tracing and Ctrl+C recovery (returning the Deb-best seen), and packages a SolverResult. Handles constrained and unconstrained problems alike — an empty G reduces the Deb ranking to plain F descent.

solve(problem: BundleProblem) → SolverResult[source]#

Run the GA on problem; KeyboardInterrupt returns the best-so-far. Leaves the bundle at x_best.

class routing.core.bundle_optimization.GroupBlockCrossover(var_to_group: ndarray, prob: float = 0.9)[source]#

Bases: Crossover

Uniform crossover at group granularity: each group inherited whole from one parent.

Per-gene mixing (SBX/UX) can hand a point’s x to one parent and its y to the other, or tear a chain in two — geometrically incoherent children the Deb ranking then discards. Inheriting each group whole recombines the bundle at the level where the physics is separable.

class routing.core.bundle_optimization.HarnessOptimizationHistory(passes: list[OptimizationPass] | None = None, snapshots: list[BundleSnapshot] | None = None, name: str = '')[source]#

Bases: DessiaObject

Dashboard of a harness optimization run: pass records plus (re)generation snapshots.

One OptimizationPass per pass, plus the BundleSnapshot states recorded outside the pass cycle (each (re)generation of the robust pipeline, with its locks). volume_models() renders snapshots and every pass’s three states in event order; plot_data() returns the convergence dashboard (objective and violation per pass, per-term curves when the GAConfig’s term_record_period recorded them). The records serialize with the platform object: the passes share one bundle by reference (pointer machinery) and each line’s local cadmap stays runtime-only — the curves and states are carried, not the gigabytes.

best() → OptimizationPass[source]#

Return the pass with the lowest feasible objective (violation-penalized tie-break).

final() → OptimizationPass[source]#

Return the last pass — the refined result (the shaping pass after a full HarnessOptimizer.optimize()).

plot_data(reference_path: str = '#', **kwargs) → Graph2D#

Specific display tag for PlotData. Add a picture argument.

record_pass(bundle: Bundle, raw_values: list[list[float] | float], relaxed_values: list[list[float] | float], optimized_values: list[list[float] | float], result: SolverResult, stage: str) → OptimizationPass[source]#

Append one pass’s three captured states (raw → relaxed → optimized) and its solver outcome.

record_state(label: str, bundle: Bundle, locks: list[Cylinder] | None = None, solutions: list[Solution] | None = None) → BundleSnapshot[source]#

Capture the bundle’s current state under label — a (re)generation event outside the pass cycle.

solutions is that generation’s complete routed set, kept so the mutable cadmap can be put back to this snapshot (CadMap.restore_solutions()) without rerouting from scratch.

volume_models() → VolumeModel[source]#

Return the environment plus, in event order, each snapshot mesh and each pass’s three state meshes.

class routing.core.bundle_optimization.JitteredSeedSampling(x_seed: ndarray, sigma: float, seed: int = 0, distribution: str = 'uniform')[source]#

Bases: FloatRandomSampling

pymoo initial sampling: x_seed perturbed within ±sigma (clipped to bounds).

The unperturbed seed is preserved as individual 0.

class routing.core.bundle_optimization.LineInterpenetration(bundle: Bundle, interactions: BundleInteractions)[source]#

Bases: Phenomenon

Per collision pair (radius_a + radius_b) - segment_distance; positive = the two lines interpenetrate.

The pair set is the interactions snapshot (see Bundle.collision_candidate_pairs() for the candidate semantics: leader pairs never candidates, kinship exemptions). The radii sums are precomputed once — only the distances move with the geometry.

property carriers: list[CollisionPair]#

The collision pair measured by each entry.

measure() → ndarray[source]#

Per-pair interpenetrations (may be negative: clearance).

name: str = 'LineInterpenetration'#
property size: int#

One entry per collision pair.

class routing.core.bundle_optimization.OptimizationPass(*, name: str = '', bundle: Bundle, raw_values: list[list[float] | float], relaxed_values: list[list[float] | float], optimized_values: list[list[float] | float], result: SolverResult | None = None, stage: str = '', signature: list[int] | None = None)[source]#

Bases: Model

One optimization pass: three line-states captured over a shared topology, plus its solver outcome.

raw_values is the pass input (pre-relaxation), relaxed_values is post-SeedRelaxer, optimized_values is post-GA — each a snapshot of the points’ variable values (Bundle.capture_values(), plain floats/lists; topology and cadmap shared by reference, no deep copy). stage names the pass’s stage — the terms whose active_in declares it entered this problem. volmdlr_primitives() materializes each state in turn to build one mesh per state, with a progressive alpha (optimized opaque) under a caller-driven color and name suffix.

bundle: Bundle#
materialize() → Bundle[source]#

Write the pass’s optimized state onto its bundle and return it (mutates in place).

optimized_values: list[list[float] | float]#
raw_values: list[list[float] | float]#
relaxed_values: list[list[float] | float]#
result: SolverResult | None#
signature: list[int] | None#
stage: str#
volmdlr_primitives(color: tuple[float, ...], name_suffix: str) → list[Mesh3D][source]#

One mesh per state (raw→relaxed→optimized): shared color, progressive alpha, optimized opaque.

class routing.core.bundle_optimization.OptimizationTerm(phenomenon: Callable[[Bundle, BundleInteractions], Phenomenon | list[Phenomenon]], hard: bool = True, soft: bool = False, weight: float = 1.0, power: int = 2, tolerance: float = 0.0, active_in: tuple[str, ...] = ('unlock', 'consolidate', 'shape'))[source]#

Bases: object

How one phenomenon enters the optimization: hard G block, soft F cost, or both.

phenomenon is the binding factory — a Phenomenon subclass or any callable (bundle, interactions) -> Phenomenon | list[Phenomenon]. weight/power shape the soft form, tolerance offsets the hard form. active_in declares the stages whose problems the term joins — every stage by default; the design-rule terms declare ("shape",), being antagonistic to consolidation.

active_in: tuple[str, ...] = ('unlock', 'consolidate', 'shape')#
bind(bundle: Bundle, interactions: BundleInteractions) → tuple[list[Cost], list[Constraint]][source]#

Instantiate the phenomenon(s) on this pass’s bundle and wrap them in the requested forms.

A hard+soft term shares one phenomenon instance between its Cost and its Constraint.

classmethod from_bundle(bundle: Bundle, alpha: float = 0.0, out_of_grid_penalty: float = 1000.0) → list[OptimizationTerm][source]#

Terms of bundle’s optimization problem: the intrinsic physics plus one term per bundle-active rule.

The intrinsic trio is the problem definition of any harness — weighted length as the objective (out-of-grid samples charged out_of_grid_penalty), CAD interference under the alpha voxel margin and line interpenetration as hard constraints. Every bundle-active rule (non-empty DesignRule.evaluate_on_bundle()) joins with the form its RuleEnforcement declares — hard constraint on the shaping stage when the rule carries none. A custom cost is a Phenomenon subclass plus one term, handed to the optimizer as a runtime override.

hard: bool = True#
phenomenon: Callable[[Bundle, BundleInteractions], Phenomenon | list[Phenomenon]]#
power: int = 2#
soft: bool = False#
tolerance: float = 0.0#
weight: float = 1.0#
class routing.core.bundle_optimization.Phenomenon[source]#

Bases: ABC

One physical effect measured on the bundle: a vector with one entry per carrier entity.

Bound to a prepared bundle for one optimization pass; measure() reads the bundle’s current geometry. The entry count is fixed by the seed topology (the stable-G-shape contract). Subclasses keep the uniform (bundle, interactions, **params) construction signature, so the class itself is an OptimizationTerm factory — specialize parameters with functools.partial.

property carriers: list[BundleLine | CollisionPair | Turn | None]#

Carrier entity per measured entry, index-aligned with measure(); None when an entry has none.

abstractmethod measure() → ndarray[source]#

Per-entity measures at the bundle’s current geometry; positive = violating.

name: str#
abstract property size: int#

Number of measured entries (stable for a fixed bundle topology).

class routing.core.bundle_optimization.PolynomialMutationConfig(*, name: str = '', genes_per_child: float | None = 3.0)[source]#

Bases: Model

pymoo polynomial mutation sized in expected mutated genes per child (the campaign’s pm3n lever).

build(problem: BundleProblem, config: GAConfig) → Mutation[source]#

PM with prob_var = genes_per_child / n_var (pymoo default when unset).

genes_per_child: float | None#
class routing.core.bundle_optimization.RelaxationConfig(*, name: str = '', enabled: bool = True, max_search: int = 2, max_passes: int = 8)[source]#

Bases: Model

Seed-repair policy: whether the orchestrator runs the SeedRelaxer before each pass, and its budget.

enabled: bool#
max_passes: int#
class routing.core.bundle_optimization.RelaxationSite(point: BundlePoint, lines: list[BundleLine], pairs: list[CollisionPair])[source]#

Bases: object

A movable BundlePoint with its local interference scope.

The lines it carries and the collision pairs touching them. The seed-repair descent relaxes each site in turn.

interference(alpha: float | None) → float[source]#

CAD penetration of the incident lines (skipped when alpha is None) + positive interpenetration of the touching pairs.

relax(voxel_size: float, max_search: int, alpha: float | None) → None[source]#

Move the point to the neighbor minimizing its local interference; ties break on smallest shift.

Scoring every incident line and pair (clean ones included) keeps the move safe at a shared junction: a position is taken only if the local total drops, so a clean line is never sacrificed to rescue an interfering one. Candidate positions are voxel-aligned, so the chosen neighbor may still graze sub-voxel; the optimizer then resolves the remainder.

classmethod sites_for(points: list[BundlePoint], lines_by_point: dict[BundlePoint, list[BundleLine]], pairs: list[CollisionPair]) → list[RelaxationSite][source]#

One site per updatable point: its incident lines and the collision pairs touching them.

Each pair is attached to the four endpoints of its two lines (which never coincide, adjacency being excluded from the candidate set). points order is preserved for a deterministic descent.

class routing.core.bundle_optimization.RuleViolations(bundle: Bundle, interactions: BundleInteractions, *, rule: DesignRule)[source]#

Bases: Phenomenon

One design rule’s violation vector at the bundle’s current along-path measures.

Thin bridge: the distance logic lives in rule.evaluate_on_bundle (already the pymoo g <= 0 convention); aggregation into F or G belongs to the optimizer’s forms, like any other phenomenon. The entry count is fixed at the seed topology (the hook’s stable-shape contract).

measure() → ndarray[source]#

Rule violations at the bundle’s current along-path measures.

property size: int#

Entries contributed by the rule (fixed by bundle topology).

class routing.core.bundle_optimization.SeedRelaxer(max_search: int = 2, max_passes: int = 8)[source]#

Bases: object

Coordinate-descent seed repair driving a bundle to a feasible optimizer seed.

A policy object (descent budget only), owned by the orchestrator; relax() receives the problem it repairs. Each shared, movable BundlePoint is nudged (within its optimizer bounds, by up to max_search voxels) to the position minimizing the summed BundleLine.cad_penetration() of its incident lines plus the positive BundleLine.interpenetration() of the touching collision pairs — co-scored, so repairing one interference cannot silently create the other.

The scope and margins are read off the problem’s own CadInterference / LineInterpenetration phenomena (hard or soft): a repaired seed is feasible for exactly the interference terms the solver will read, by construction. With neither phenomenon in the problem the relaxer is a no-op. Back-bend and design rules are deliberately not relaxed — a one-step descent on those is riskier than letting the GA move. Run after Bundle.attach_local_cadmaps(); mutates the bundle in place.

relax(problem: BundleProblem, show_diff: bool = False) → None[source]#

Run the descent until the problem’s interference seed is clean or a sweep stops improving.

Every accepted move strictly lowers the global interference, so the sweep terminates; a move-free pass stops it early. At TRACE verbosity, traces a per-line penetration/shift table; show_diff overlays the interfering seed (red) over the repaired one (green).

class routing.core.bundle_optimization.SolverResult(*, name: str = '', x_best: list[float], f_best: float, g_best: list[float], max_violation: float, n_eval: int, elapsed_seconds: float, converged: bool, interrupted: bool, pymoo_best_objective: float | None = None, objective_history: list[list[float]] = <factory>, violation_history: list[list[float]] = <factory>, time_history: list[list[float]] = <factory>, term_history: list[list[float | dict[str, float]]] = <factory>)[source]#

Bases: Model

Outcome and per-generation traces of one GA run on a BundleProblem.

x_best/g_best are encoding-space vectors stored as plain float lists (JSON-portable); consumers re-wrap with np.asarray before unpacking. f_best re-evaluates the selected best, whereas pymoo_best_objective is what pymoo itself reported — the two diverge when interrupted cut the run short.

converged: bool#
elapsed_seconds: float#
f_best: float#
g_best: list[float]#
interrupted: bool#
max_violation: float#
n_eval: int#
objective_history: list[list[float]]#
pymoo_best_objective: float | None#
term_history: list[list[float | dict[str, float]]]#
time_history: list[list[float]]#
violation_history: list[list[float]]#
x_best: list[float]#
class routing.core.bundle_optimization.StallCriterion(*, name: str = '', ftol: float, evals: int = 1000)[source]#

Bases: Model

Stagnation window: trip when the tracked best improves by less than ftol over evals consecutive evaluations.

evals: int#
ftol: float#
class routing.core.bundle_optimization.TerminationCriteria(*, name: str = '', n_gen: int = 12500, max_seconds: float | None = None, feasible_tol: float = 1e-06, feasible_stall: StallCriterion | None = StallCriterion(name='', ftol=0.001, evals=1000), violation_stall: StallCriterion | None = None, stop_when_feasible: bool = False)[source]#

Bases: Model

Declarative stopping policy of one GA run — budgets, per-regime stalls, and the unlock cut-off.

n_gen always caps and max_seconds races against it when set. feasible_stall watches the best objective while feasible; violation_stall watches the best constraint violation while infeasible (None keeps the escape on its full budget — the historical policy). stop_when_feasible ends the run the moment the best individual is feasible: the unlock stage’s criterion, where reaching feasibility is the whole objective and any further polishing belongs to the next stage.

feasible_stall: StallCriterion | None#
feasible_tol: float#
max_seconds: float | None#
n_gen: int#
stop_when_feasible: bool#
violation_stall: StallCriterion | None#
class routing.core.bundle_optimization.VariableGroups(point_slices: list[list[slice]], port_slices: list[list[slice]], var_to_group: ndarray)[source]#

Bases: object

Encoding variables partitioned into physically-cohesive groups (single points, or whole chains).

A chain is a maximal run of updatable points threaded through degree-≤2 points — the span of harness between two junctions, which physically slides as one piece. Junctions (degree ≥ 3) stay singletons. Consumed by the domain operators (block crossover, chain translation).

classmethod from_encoding(encoding: BundleEncoding, bundle: Bundle, chains: bool) → VariableGroups[source]#

Group encoding variables by point, or by chain (union-find over degree-≤2 lines) when chains.

point_slices: list[list[slice]]#
port_slices: list[list[slice]]#
var_to_group: ndarray#
class routing.core.bundle_optimization.ViolatedEntry(constraint: str, carrier: BundleLine | CollisionPair | Turn | None, residual: float)[source]#

Bases: object

One violated G entry: its constraint, the carrier entity it measures, and the residual (measure - tolerance).

carrier: BundleLine | CollisionPair | Turn | None#
constraint: str#
residual: float#
class routing.core.bundle_optimization.WeightedLength(bundle: Bundle, interactions: BundleInteractions, *, out_of_grid_penalty: float = 1000.0)[source]#

Bases: Phenomenon

Per-line weighted length (mean traversed weight times length).

A NaN BundleLine.weighted_length() (scored span traversing only blocked voxels) reads as out_of_grid_penalty: a NaN F silently corrupts the solver’s ranking.

property carriers: list[BundleLine]#

The scored line measured by each entry.

measure() → ndarray[source]#

Per-line weighted lengths, NaN entries replaced by the penalty.

name: str = 'WeightedLength'#
property size: int#

One entry per scored line.