Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.26.0] - 2026-10-29

### Fixed

- Preparation and runs no longer repeat exact-fraction precision checks for every failure case and MSD probe: scenarios with many ECMP `combine` sources prepare and run 15 to 30 times faster than in 0.25.0, and placement uses its `workers` again.

### Changed

- **BREAKING**: preparation chooses `q` once from a split count (nodes, links, equal-split demands placed together, tunnels) and the smallest amounts, without re-preparing candidates; analyses no longer check splits or report precision misses. `units_report` replaces `binding_constraints`, `nominal_split_count`, `nominal_splits`, `misses` and the input-rounding samples and maxima with `limited_by`, `achieved_precision`, `input_bound`, `split_bound` and `range_limit`. `q` is finer on equal-split workloads and can change on others; the documented precision promise is in Precision and the choice of q. `units.precision` must now be at most 0.5.

## [0.25.0] - 2026-10-27

### Added
Expand Down
4 changes: 2 additions & 2 deletions dev/explicit_results_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3099,7 +3099,7 @@
}
},
"units_report": {
"description": "The units report of the preparation: q, its reasons, rounding and misses.",
"description": "The units report of the preparation: q, its input/count bounds and range sums.",
"$ref": "#/$defs/UnitsReportJSON"
}
},
Expand Down Expand Up @@ -3131,7 +3131,7 @@
]
},
"units_report": {
"description": "The units report of the preparation: q, its reasons, rounding and misses.",
"description": "The units report of the preparation: q, its input/count bounds and range sums.",
"$ref": "#/$defs/UnitsReportJSON"
}
},
Expand Down
3 changes: 1 addition & 2 deletions dev/generate_results_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
PAGE = ROOT / "docs/reference/results.md"
EXPLICIT = ROOT / "dev/explicit_results_schema.json"
ALIASES = (
"UnitConstraint",
"CostReport",
"CostUnavailableReason",
"MSDStatus",
Expand Down Expand Up @@ -554,7 +553,7 @@ def page(document: dict) -> str:
" units of the run's resolution `q` (the units report holds `q`); units × `q` is the",
" physical amount.",
"- **Physical amounts:** inputs, question offers and designs, `NetworkStats`",
" capacities and the units report's input samples, as declared.",
" capacities and `split_bound.capacity` in the units report, as declared.",
"- **Ratios:** delivery ratios, alpha, shares and caps, with no unit.",
"- **Routing costs:** path costs, tunnel costs and their sums, which never take `q`.",
" `total_cost` is cost × bandwidth units, so `total_cost` × `q` is cost × physical",
Expand Down
77 changes: 38 additions & 39 deletions docs/concepts.md

Large diffs are not rendered by default.

135 changes: 25 additions & 110 deletions docs/reference/api-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ The answer of ``capacity``: the question asked and one result per failure case.

- `kind` (ClassVar[Literal['capacity']]) = capacity
- `prepared` (PreparedScenario)
- `misses` (tuple[PrecisionMiss, ...])
- `metadata` (AnalysisMetadata)
- `outputs` (OutputSelection)
- `question` (Literal['lossless_bound', 'fixed_offer_delivery', 'first_hop_experiment'])
Expand Down Expand Up @@ -584,20 +583,23 @@ whether q meets it.
- `q_upper_bound` (Fraction)
- `met` (bool)

### ngraph.InputQuantization
### ngraph.SplitCountConstraint

One physical input converted to units: its binary64 value, its units and the
rounding error.
The prepared split-count bound, not a certificate for arbitrary answers.

**Attributes:**

- `label` (str)
- `kind` (Literal['capacity', 'demand', 'offer', 'design', 'min_unreserved'])
- `binary64_value` (float)
- `binary64_hex` (str)
- `units` (int)
- `error` (Fraction)
- `relative_error` (Fraction | None): None when the input is zero
- `kind` (ClassVar[Literal['split_count']]) = split_count
- `capacity_label` (str)
- `capacity` (float)
- `nodes` (int)
- `directed_links` (int)
- `group` (str | None): None when the floor sets the count
- `demands` (int)
- `tunnels` (int)
- `count` (Fraction)
- `q_upper_bound` (Fraction)
- `met` (bool)

### ngraph.JSONValue

Expand Down Expand Up @@ -656,7 +658,6 @@ under ``MSDAnalysisJSON`` in the results reference.

- `kind` (ClassVar[Literal['msd']]) = msd
- `prepared` (PreparedScenario)
- `misses` (tuple[PrecisionMiss, ...])
- `metadata` (AnalysisMetadata)
- `outputs` (OutputSelection)
- `alpha_feasible` (ExactAlpha | None)
Expand Down Expand Up @@ -895,7 +896,6 @@ The answer of ``paths``: one row per endpoint pair.

- `kind` (ClassVar[Literal['paths']]) = paths
- `prepared` (PreparedScenario)
- `misses` (tuple[PrecisionMiss, ...])
- `metadata` (AnalysisMetadata)
- `algorithm` (Literal['spf', 'ksp'])
- `mode` (Literal['combine', 'pairwise'])
Expand Down Expand Up @@ -970,7 +970,6 @@ accounting.

- `kind` (ClassVar[Literal['placement']]) = placement
- `prepared` (PreparedScenario)
- `misses` (tuple[PrecisionMiss, ...])
- `metadata` (AnalysisMetadata)
- `outputs` (OutputSelection)
- `alpha` (ExactAlpha)
Expand Down Expand Up @@ -1047,19 +1046,6 @@ A TE ingress pool; ``offer_limit_units`` is its prepared lossy offer budget.
- `offer_limit_units` (int) = 0
- `synthetic_ingress_cost` (ClassVar[Literal[1]]) = 1

### ngraph.PrecisionMiss

One place an analysis missed the precision target: its location, the constraints
it missed and its input rounding.

**Attributes:**

- `location` (ReportLocation)
- `constraints` (tuple[InputConstraint | SplitConstraint | TunnelResolutionConstraint | UnverifiedConstraint, ...])
- `inexact_inputs` (int)
- `zeroed_inputs` (int)
- `examples` (tuple[InputQuantization, ...])

### ngraph.PreparationScope

What a preparation covered: its workflow steps, and the demand sets bound for the
Expand All @@ -1075,8 +1061,7 @@ workflow and for library callers.

One graph, unit and demand binding over a read-only view of a scenario.

``units_report`` is the preparation's report; each analysis returns its
own precision misses beside it.
``units_report`` records the fixed input/count bounds for every analysis.

**Attributes:**

Expand Down Expand Up @@ -1128,17 +1113,6 @@ Why an MSD probe was out of range.
- `message` (str)
- `binary64_hex` (str | None): None when the refused amount is not a binary64 value

### ngraph.ReportLocation

Where a precision miss happened: the step, the failure case and the MSD probe,
where they apply.

**Attributes:**

- `step` (str | None): None when the report is outside a workflow
- `case_id` (str | None): None when the report is outside failure cases
- `probe_index` (int | None): None when the report is outside MSD

### ngraph.ResolvedDemand

One demand bound to the prepared graph: its identity, endpoints, priority, units
Expand Down Expand Up @@ -1229,7 +1203,7 @@ version and the seed.

### ngraph.RunResult

One run; ``units_report`` adds every step's precision misses, by step.
One run with the preparation's units report.

**Attributes:**

Expand Down Expand Up @@ -1329,7 +1303,6 @@ directed edge's removal causes.

- `kind` (ClassVar[Literal['sensitivity']]) = sensitivity
- `prepared` (PreparedScenario)
- `misses` (tuple[PrecisionMiss, ...])
- `metadata` (AnalysisMetadata)
- `outputs` (OutputSelection)
- `endpoints` (Endpoints)
Expand All @@ -1355,21 +1328,6 @@ delivery.
- `outputs` (ngc.OutputOptions)
- `export_details` (bool) = False

### ngraph.SplitConstraint

The split bound of a positive amount v over m members at precision p (q at most
pv/(m - 1)), and whether q meets it.

**Attributes:**

- `kind` (Literal['expansion', 'source', 'router', 'tunnel'])
- `label` (str)
- `incoming_physical` (Fraction)
- `basis` (Literal['declared', 'observed'])
- `members` (int)
- `q_upper_bound` (Fraction)
- `met` (bool)

### ngraph.StepContext

What a custom step's ``run`` receives: the prepared scenario, earlier steps'
Expand Down Expand Up @@ -1454,7 +1412,6 @@ The answer of ``tunnel_counts``: one row per demand and count.

- `kind` (ClassVar[Literal['tunnel_counts']]) = tunnel_counts
- `prepared` (PreparedScenario)
- `misses` (tuple[PrecisionMiss, ...])
- `metadata` (AnalysisMetadata)
- `outputs` (OutputSelection)
- `rows` (tuple[TunnelCountRow, ...])
Expand Down Expand Up @@ -1500,34 +1457,9 @@ offer).
- `lossless_at_design_units` (int)
- `physical_tunnel_cost` (int)

### ngraph.TunnelResolutionConstraint

A native tunnel plan whose live heads cannot receive a whole unit per tunnel.

**Attributes:**

- `kind` (ClassVar[Literal['tunnel_resolution']]) = tunnel_resolution
- `label` (str)
- `q` (float)
- `heads` (tuple[str | PoolHead, ...])
- `met` (ClassVar[Literal[False]]) = False

### ngraph.UnitConstraint

```python
UnitConstraint = (InputConstraint
| SplitConstraint
| TunnelResolutionConstraint
| RangeConstraint
| UnverifiedConstraint)
```

One precision or range constraint of a units report.

### ngraph.UnitsReport

How preparation chose q: the mode and target, the bounds that set q, input
rounding, the range sums and every precision miss.
The prepared unit, its input/count bounds and augmented graph range sums.

**Attributes:**

Expand All @@ -1539,25 +1471,18 @@ rounding, the range sums and every precision miss.
- `input_convention` (ClassVar[Literal['promoted_binary64_half_up']]) = promoted_binary64_half_up
- `display_convention` (ClassVar[Literal['units_authoritative_binary64_display']]) = units_authoritative_binary64_display
- `scope` (PreparationScope)
- `binding_constraints` (tuple[UnitConstraint, ...])
- `input_count` (int)
- `inexact_inputs` (int)
- `zeroed_inputs` (int)
- `inexact_examples` (tuple[InputQuantization, ...])
- `zeroed_examples` (tuple[InputQuantization, ...])
- `max_absolute_input_error` (Fraction)
- `max_relative_input_error` (Fraction)
- `limited_by` (Literal['input', 'split_count', 'range', 'resolution_floor', 'declared', 'unbounded'])
- `achieved_precision` (Fraction | None): None when there is no positive input or capacity
- `input_bound` (InputConstraint | None): None when there is no positive prepared bandwidth input
- `split_bound` (SplitCountConstraint | None): None when there is no positive link capacity
- `range_limit` (RangeConstraint | None): None when limited_by is not range
- `augmented_capacity_sum_units` (int)
- `augmented_cost_sum` (int)
- `nominal_split_count` (int)
- `nominal_splits` (tuple[SplitConstraint, ...])
- `misses` (tuple[PrecisionMiss, ...])
- `weight_approximations` (tuple[WeightApproximation, ...]) = ()

**Methods:**

- `with_approximations(self, entries: 'tuple[WeightApproximation, ...]') -> 'UnitsReport'` - This report with the controller's weight approximations, each once.
- `with_misses(self, misses: 'tuple[PrecisionMiss, ...]') -> 'UnitsReport'` - This report with further precision misses appended.

### ngraph.UnitsSettings

Expand All @@ -1569,16 +1494,6 @@ relative precision target.
- `resolution` (float | Literal['auto']) = auto
- `precision` (float) = 0.001

### ngraph.UnverifiedConstraint

A supplied program whose splits cannot be checked against the precision target.

**Attributes:**

- `kind` (ClassVar[Literal['unverified']]) = unverified
- `label` (str)
- `reason` (ClassVar[Literal['opaque_program']]) = opaque_program

### ngraph.UpperCertificate

A proved bracket around the largest feasible alpha: a feasible probe and the
Expand Down Expand Up @@ -1747,13 +1662,13 @@ What preparation must know of one workflow step's question.
``numerical`` steps are evaluated at the prepared resolution. A step may
name a demand set to bind, a failure policy, and an earlier step whose
feasible alpha it uses (one that ``provides_alpha``). ``capacity`` builds
the step's capacity question, which preparation evaluates once per
candidate resolution; ``inputs`` are that question's physical amounts, by
the step's capacity question, which preparation validates once after
final compilation; ``inputs`` are that question's physical amounts, by
field name. ``pool_endpoints`` are the source and target of a question
whose combined sources are a pool: preparation gives them a private head.
``offer_budget`` is a fixed lossy offer every pool leaf of ``demand_set``
receives; ``counts`` builds a tunnel-count question, whose split targets
preparation observes once per candidate resolution. ``nodes`` and ``links``
receives; ``counts`` builds a tunnel-count question, which preparation
validates once after final binding. ``nodes`` and ``links``
are names the step reads that must exist in the network.

**Attributes:**
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ assert prepared.topology.q == 1

[`Network`](api-full.md#ngraphnetwork) keeps editable dicts of immutable records. [`prepare`](api-full.md#ngraphprepare) resolves the selected demand sets, builds one native graph and returns [`PreparedScenario`](api-full.md#ngraphpreparedscenario); it does not change its input. Library callers name needed demand sets with `prepare(scenario, demand_sets=("traffic",))`; workflow-referenced sets are included automatically.

`PreparedScenario` holds the read-only scenario view, its `topology`, the bound `demand_sets` and the preparation's `units_report`. [`PreparedTopology`](api-full.md#ngraphpreparedtopology) holds the graph and its labels: `graph`, physical node names, read-only node-name and physical-link mappings, `edge_refs` (each engine edge's directed link or pool attachment, in edge order), `disabled` (the engine's eligibility mask of what the scenario disables), `q` (the bandwidth unit; [Units and the resolution q](../concepts.md#units-and-the-resolution-q)) and `pool_heads` (each finite TE ingress pool's synthetic head node; [Tunnels and TE ingress pools](../concepts.md#tunnels-and-te-ingress-pools)). Every analysis takes the prepared scenario, holds it as `prepared` and returns its own precision `misses`; its `units_report` is the preparation's report with those misses appended. A physical link has two native directions; [`DirectedLink(link, direction)`](api-full.md#ngraphdirectedlink) identifies one. Returned results stay readable after the scenario and the prepared scenario are deleted. The prepared view is read-only, and later edits need a new preparation ([Scenario, preparation and run](../concepts.md#scenario-preparation-and-run)). The examples on this page fix `q` to 1, so that units equal physical bandwidth; for amounts this small that misses the default precision target, and their units reports say so ([Precision and the choice of q](../concepts.md#precision-and-the-choice-of-q)).
`PreparedScenario` holds the read-only scenario view, its `topology`, the bound `demand_sets` and the preparation's `units_report`. [`PreparedTopology`](api-full.md#ngraphpreparedtopology) holds the graph and its labels: `graph`, physical node names, read-only node-name and physical-link mappings, `edge_refs` (each engine edge's directed link or pool attachment, in edge order), `disabled` (the engine's eligibility mask of what the scenario disables), `q` (the bandwidth unit; [Units and the resolution q](../concepts.md#units-and-the-resolution-q)) and `pool_heads` (each finite TE ingress pool's synthetic head node; [Tunnels and TE ingress pools](../concepts.md#tunnels-and-te-ingress-pools)). Every analysis takes the prepared scenario, holds it as `prepared` and uses the preparation's `units_report`, with controller weight approximations added for a later-bound demand set. A physical link has two native directions; [`DirectedLink(link, direction)`](api-full.md#ngraphdirectedlink) identifies one. Returned results stay readable after the scenario and the prepared scenario are deleted. The prepared view is read-only, and later edits need a new preparation ([Scenario, preparation and run](../concepts.md#scenario-preparation-and-run)). The examples on this page fix `q` to 1, so that units equal physical bandwidth; for amounts this small that misses the default precision target, and their units reports say so ([Precision and the choice of q](../concepts.md#precision-and-the-choice-of-q)).

## Native models and capacity

Expand Down Expand Up @@ -130,7 +130,7 @@ For K shortest paths, pass `options=ngc.KspOptions(k, cost_ceiling=...)`. In `co

## Results and export

[`run(prepare(scenario))`](api-full.md#ngraphrun) returns [`RunResult`](api-full.md#ngraphrunresult) with an ordered tuple of steps, one units report (the preparation's report with each step's misses, labelled by step) and producer metadata. Every analysis records its own typed query ([`CapacityQuery`](api-full.md#ngraphcapacityquery), [`PlacementQuery`](api-full.md#ngraphplacementquery), [`SensitivityQuery`](api-full.md#ngraphsensitivityquery), [`PathQuery`](api-full.md#ngraphpathquery), [`MSDQuery`](api-full.md#ngraphmsdquery)) as `query`, and a step records it as its `question`; a built-in domain step records itself. The JSON export writes each question in normalized form (selectors, the effective native model, failures, alpha and its source). A capacity case has `states={"converged": ...}`; a placement case has one entry per requested failure state (`PlacementQuery.states`, default `("converged",)`, `"stale"` and `"repair"` on request), and `PlacementQuery.interaction` selects `"ordered"` or `"simultaneous"` forwarding (workflow reference: TrafficMatrixPlacement). Each row holds the engine's result, with its endpoints, its reference request and any requested reports.
[`run(prepare(scenario))`](api-full.md#ngraphrun) returns [`RunResult`](api-full.md#ngraphrunresult) with an ordered tuple of steps, the preparation's units report and producer metadata. Every analysis records its own typed query ([`CapacityQuery`](api-full.md#ngraphcapacityquery), [`PlacementQuery`](api-full.md#ngraphplacementquery), [`SensitivityQuery`](api-full.md#ngraphsensitivityquery), [`PathQuery`](api-full.md#ngraphpathquery), [`MSDQuery`](api-full.md#ngraphmsdquery)) as `query`, and a step records it as its `question`; a built-in domain step records itself. The JSON export writes each question in normalized form (selectors, the effective native model, failures, alpha and its source). A capacity case has `states={"converged": ...}`; a placement case has one entry per requested failure state (`PlacementQuery.states`, default `("converged",)`, `"stale"` and `"repair"` on request), and `PlacementQuery.interaction` selects `"ordered"` or `"simultaneous"` forwarding (workflow reference: TrafficMatrixPlacement). Each row holds the engine's result, with its endpoints, its reference request and any requested reports.

[`write_json(result, file)`](api-full.md#ngraphwrite_json) is the export path. It writes each plain record as its declared constants and fields, in declaration order; `ngraph/schemas/results.json` is generated from the same declarations. The file's format is in [Output Format](cli.md#output-format), and what its numbers mean in [Results and their units](../concepts.md#results-and-their-units).

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ units:
| Key | Default | Meaning |
|-----|---------|---------|
| `resolution` | `auto` | The bandwidth unit `q`: `auto`, or one power of two from 2^-32 through 2^32 |
| `precision` | `0.001` | The relative precision target; the units report states whether it is met |
| `precision` | `0.001` | The relative precision target, greater than 0 and at most 0.5, from which preparation chooses `q`; the units report states the input/count budget and whether its bounds hold |

Capacities, demand volumes, finite offers and designs and minimum unreserved bandwidth are physical amounts, converted to whole units of `q` at preparation ([Units and the resolution q](../concepts.md#units-and-the-resolution-q)); [Precision and the choice of q](../concepts.md#precision-and-the-choice-of-q) says how `q` is chosen and what the units report records. Costs are positive integers ([link keys](#direct-node-and-link-definitions)); counts and weights are integers.

Expand Down
Loading
Loading