Skip to content

Release 0.14 - #353

Merged
derrynknife merged 107 commits into
masterfrom
dev
Oct 16, 2026
Merged

derrynknife merged 107 commits into
masterfrom
dev

Conversation

@derrynknife

Copy link
Copy Markdown
Owner

Summary

Merge dev into master for release 0.14: everything merged into dev since 0.13 (#262–#352, and #343, the release commit), as the CHANGELOG's [0.14] - 2026-10-16 section lists. Its summary:

Fitted models in, every kind of model brought up to the diagram, and maintenance questions answered in model time. A component's life may be what surpyval fits to a repairable unit's history, and a simulation starts such a unit from its virtual age (#269); a fitted degradation process is a life, and replacement on condition may go by its measured level (#271); a component may operate part of the time (#276); a regression node's covariates are levers (#272). The alpha-factor common-cause model (#311) and the joint importance with common-cause groups (#317). A FaultTree, a Network and a PhasedMission have the analyses a diagram has (#314, #315, #316). Over a window: the failure frequency, MUT and MDT with common-cause groups whose tests take time (#293), criticality and Fussell-Vesely with repair crews around a nested RBD (#312), minimal repair whose repairs take time (#318), and spares_stock for a block-replaced unit dead on arrival (#322). The maintenance module (#319): a part's failures over a window, whether to skip the next planned replacement (#350), the optimal replacement policy over a calendar of shuts (#349) and when to replace a part in service next (#348). Fixes #267, #268, #294, #295 and the 0.14 persona review's (#351). RepairableRBD's code is in modules by analysis; the README shows what each model can do; surpyval 0.24 is required; what 0.13 deprecated is gone, and NodeState.phase is deprecated for since_scheduled (#350).

The behaviour changes open the CHANGELOG section and the release notes.

Release

This pull request runs the full suite (actions.yml), which the release workflow requires to have passed on these files. After the merge, the release is cut from master with release.yml (version 0.14, a dry run first).

Merge with a merge commit, as the earlier releases were (#259, #261): the release check matches the merge commit's tree against the dev commit the suite ran on.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ


Generated by Claude Code

derrynknife and others added 30 commits October 9, 2026 21:16
…ster (#262)

* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
#263)

* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: run the suite on every core, and measure coverage on Python 3.14 alone

Every test job runs pytest -n auto (pytest-xdist), using all the runner's
cores where it used one. Coverage is measured once, on Python 3.14
through sys.monitoring, collected from each worker by pytest-cov: Python
3.11 has no sys.monitoring, and under coverage's default tracer its job
took an hour. The coverage gate and report are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: run the suite on every core, and measure coverage on Python 3.14 alone

Every test job runs pytest -n auto (pytest-xdist), using all the runner's
cores where it used one. Coverage is measured once, on Python 3.14
through sys.monitoring, collected from each worker by pytest-cov: Python
3.11 has no sys.monitoring, and under coverage's default tracer its job
took an hour. The coverage gate and report are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Require surpyval 0.24

0.24 gives a mixture a survival function precise in the far tail
(SurPyval#671) and has conditional_gaps read a limited failure
population's lfp_p, not a mixture's p (SurPyval#626), so the mixture
test's survival is the mixture's own sf and MixtureLife only supplies the
quantile. 0.24's MixtureModel.qf inverts F in the upper tail (relative
error ~1e-4 for long lives) and takes ~200 times as long as
mixture_quantile (SurPyval#821), so the simulations keep RePyability's.
Seeded results are unchanged. pyproject's comment, CLAUDE.md's
workaround list and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…qf (#265)

* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: run the suite on every core, and measure coverage on Python 3.14 alone

Every test job runs pytest -n auto (pytest-xdist), using all the runner's
cores where it used one. Coverage is measured once, on Python 3.14
through sys.monitoring, collected from each worker by pytest-cov: Python
3.11 has no sys.monitoring, and under coverage's default tracer its job
took an hour. The coverage gate and report are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Require surpyval 0.24

0.24 gives a mixture a survival function precise in the far tail
(SurPyval#671) and has conditional_gaps read a limited failure
population's lfp_p, not a mixture's p (SurPyval#626), so the mixture
test's survival is the mixture's own sf and MixtureLife only supplies the
quantile. 0.24's MixtureModel.qf inverts F in the upper tail (relative
error ~1e-4 for long lives) and takes ~200 times as long as
mixture_quantile (SurPyval#821), so the simulations keep RePyability's.
Seeded results are unchanged. pyproject's comment, CLAUDE.md's
workaround list and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: exact mean and draws

mean() integrated the survival curve tabulated on 4,096 points and
stopped where it fell to 1e-4, so it came out low (8e-5 for a LogNormal
AFT), and draws were read off that grid, none past its end. The mean is
now mean_lifetime's integral (relative 1e-10, pieces at the model's
quantiles or the schedule's change points; inf where lives never end),
and a draw is the model's qf(u, Z) at fixed covariates (surpyval 0.24)
or, along a schedule, the time at which Hf_tvc reaches -log(1 - u), by
bisection. surpyval's mean(Z) is not used: on AH models with real-line
baselines it integrates an sf above 1 (SurPyval#828). Seeded draws with a
regression node change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: a CovariatePath schedule is integrated and drawn; saving it is refused by name

A CovariatePath has no step edges, so _kinks takes a schedule's edges
only where it has them (its linear moves only bend the hazard, which
the integral follows), and to_dict refuses it with a NotImplementedError
naming it, where both raised AttributeError. Its mean matches surpyval's
mean_tvc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…irable models as components (#273)

* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: run the suite on every core, and measure coverage on Python 3.14 alone

Every test job runs pytest -n auto (pytest-xdist), using all the runner's
cores where it used one. Coverage is measured once, on Python 3.14
through sys.monitoring, collected from each worker by pytest-cov: Python
3.11 has no sys.monitoring, and under coverage's default tracer its job
took an hour. The coverage gate and report are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Require surpyval 0.24

0.24 gives a mixture a survival function precise in the far tail
(SurPyval#671) and has conditional_gaps read a limited failure
population's lfp_p, not a mixture's p (SurPyval#626), so the mixture
test's survival is the mixture's own sf and MixtureLife only supplies the
quantile. 0.24's MixtureModel.qf inverts F in the upper tail (relative
error ~1e-4 for long lives) and takes ~200 times as long as
mixture_quantile (SurPyval#821), so the simulations keep RePyability's.
Seeded results are unchanged. pyproject's comment, CLAUDE.md's
workaround list and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: exact mean and draws

mean() integrated the survival curve tabulated on 4,096 points and
stopped where it fell to 1e-4, so it came out low (8e-5 for a LogNormal
AFT), and draws were read off that grid, none past its end. The mean is
now mean_lifetime's integral (relative 1e-10, pieces at the model's
quantiles or the schedule's change points; inf where lives never end),
and a draw is the model's qf(u, Z) at fixed covariates (surpyval 0.24)
or, along a schedule, the time at which Hf_tvc reaches -log(1 - u), by
bisection. surpyval's mean(Z) is not used: on AH models with real-line
baselines it integrates an sf above 1 (SurPyval#828). Seeded draws with a
regression node change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: a CovariatePath schedule is integrated and drawn; saving it is refused by name

A CovariatePath has no step edges, so _kinks takes a schedule's edges
only where it has them (its linear moves only bend the hazard, which
the integral follows), and to_dict refuses it with a NotImplementedError
naming it, where both raised AttributeError. Its mean matches surpyval's
mean_tvc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Parameter uncertainty draws a fit's lfp_p and f0 from covariance() (#267)

"fit" drew only a distribution's own parameters (hess_inv) and kept a
limited failure population's lfp_p and a zero-inflated fit's f0 at their
fitted values, so the spread of the reliability long after the failing
units have failed came out as nothing. It now draws every parameter the
fit estimated from surpyval's covariance(), the shares on the logit
scale. An offset, which the covariance leaves out (SurPyval#830), is
held where it was fitted, with a warning.

parametric_spec lists the shares after a distribution's parameters, in
covariance()'s order, and builds a model from any such vector, so
parameter_sensitivity, levers()/with_levers, both classes' delta methods
and {parameter: distribution} uncertainties take them as parameters.
The catalogue gains a fit of both shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional survival from the cumulative hazard, precise at old ages (#268)

conditional_survival took R(X + x) / R(X), which is 0 once both are too
small for a float. Where the model has a cumulative hazard it is now
exp(-(H(X + x) - H(X))): a Weibull(100, 3) unit 1000 hours old survives
the next 10 hours with probability 6.9e-14, where it gave 0. It serves
NonRepairableRBD.cs and a node's age in sf_given_state; models without
an Hf keep the ratio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Fitted repairable-unit models as RepairableRBD components (#269)

A spec's "reliability" may be a surpyval CrowAMSAA, Duane or HPP fit,
minimal repair of the life whose cumulative hazard is the process's
cumulative intensity (a Weibull or exponential life), or a
GeneralizedRenewal fit, its life distribution with the "repair" of its
Kijima model and restoration factor. The constructor writes such a spec
as that life and repair (repyability/rbd/_processes.py), so every
analysis, both engines and saving take it as they take the spec written
out. ARA, ARI and G1 fits are refused, their repairs not being Kijima's
(SurPyval#833); a fit given with its own "repair" is refused.

The catalogue gains a component given a fitted Crow-AMSAA process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: run the suite on every core, and measure coverage on Python 3.14 alone

Every test job runs pytest -n auto (pytest-xdist), using all the runner's
cores where it used one. Coverage is measured once, on Python 3.14
through sys.monitoring, collected from each worker by pytest-cov: Python
3.11 has no sys.monitoring, and under coverage's default tracer its job
took an hour. The coverage gate and report are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Require surpyval 0.24

0.24 gives a mixture a survival function precise in the far tail
(SurPyval#671) and has conditional_gaps read a limited failure
population's lfp_p, not a mixture's p (SurPyval#626), so the mixture
test's survival is the mixture's own sf and MixtureLife only supplies the
quantile. 0.24's MixtureModel.qf inverts F in the upper tail (relative
error ~1e-4 for long lives) and takes ~200 times as long as
mixture_quantile (SurPyval#821), so the simulations keep RePyability's.
Seeded results are unchanged. pyproject's comment, CLAUDE.md's
workaround list and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: exact mean and draws

mean() integrated the survival curve tabulated on 4,096 points and
stopped where it fell to 1e-4, so it came out low (8e-5 for a LogNormal
AFT), and draws were read off that grid, none past its end. The mean is
now mean_lifetime's integral (relative 1e-10, pieces at the model's
quantiles or the schedule's change points; inf where lives never end),
and a draw is the model's qf(u, Z) at fixed covariates (surpyval 0.24)
or, along a schedule, the time at which Hf_tvc reaches -log(1 - u), by
bisection. surpyval's mean(Z) is not used: on AH models with real-line
baselines it integrates an sf above 1 (SurPyval#828). Seeded draws with a
regression node change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: a CovariatePath schedule is integrated and drawn; saving it is refused by name

A CovariatePath has no step edges, so _kinks takes a schedule's edges
only where it has them (its linear moves only bend the hazard, which
the integral follows), and to_dict refuses it with a NotImplementedError
naming it, where both raised AttributeError. Its mean matches surpyval's
mean_tvc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Parameter uncertainty draws a fit's lfp_p and f0 from covariance() (#267)

"fit" drew only a distribution's own parameters (hess_inv) and kept a
limited failure population's lfp_p and a zero-inflated fit's f0 at their
fitted values, so the spread of the reliability long after the failing
units have failed came out as nothing. It now draws every parameter the
fit estimated from surpyval's covariance(), the shares on the logit
scale. An offset, which the covariance leaves out (SurPyval#830), is
held where it was fitted, with a warning.

parametric_spec lists the shares after a distribution's parameters, in
covariance()'s order, and builds a model from any such vector, so
parameter_sensitivity, levers()/with_levers, both classes' delta methods
and {parameter: distribution} uncertainties take them as parameters.
The catalogue gains a fit of both shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional survival from the cumulative hazard, precise at old ages (#268)

conditional_survival took R(X + x) / R(X), which is 0 once both are too
small for a float. Where the model has a cumulative hazard it is now
exp(-(H(X + x) - H(X))): a Weibull(100, 3) unit 1000 hours old survives
the next 10 hours with probability 6.9e-14, where it gave 0. It serves
NonRepairableRBD.cs and a node's age in sf_given_state; models without
an Hf keep the ratio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Fitted repairable-unit models as RepairableRBD components (#269)

A spec's "reliability" may be a surpyval CrowAMSAA, Duane or HPP fit,
minimal repair of the life whose cumulative hazard is the process's
cumulative intensity (a Weibull or exponential life), or a
GeneralizedRenewal fit, its life distribution with the "repair" of its
Kijima model and restoration factor. The constructor writes such a spec
as that life and repair (repyability/rbd/_processes.py), so every
analysis, both engines and saving take it as they take the spec written
out. ARA, ARI and G1 fits are refused, their repairs not being Kijima's
(SurPyval#833); a fit given with its own "repair" is refused.

The catalogue gains a component given a fitted Crow-AMSAA process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* A simulation starts an imperfectly repaired unit from its virtual age (#269)

NodeState(age=a, virtual_age=v): an imperfectly repaired component's
virtual age at its last repair and its operating time since. The Python
event loop (the one that simulates imperfect repair) draws its life left
from virtual age v + a, from the component's start stream, and its next
repair takes the virtual age on by the whole time since the last repair;
one down is at virtual age v once its repair is over. A fitted
GeneralizedRenewal's unit_states() gives both.

The exact methods still refuse such a state, now saying the simulations
take it, as do the simulations for a component also replaced after some
failures, maintained or tested (that needs its history since renewal); a
virtual age on a component not repaired imperfectly is refused. Tests
check Kijima I and II against a reference written from the definition,
minimal repair against its closed form, and a unit started down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Start-from-state test: an imperfectly repaired unit's state is taken by the simulations (#269)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: run the suite on every core, and measure coverage on Python 3.14 alone

Every test job runs pytest -n auto (pytest-xdist), using all the runner's
cores where it used one. Coverage is measured once, on Python 3.14
through sys.monitoring, collected from each worker by pytest-cov: Python
3.11 has no sys.monitoring, and under coverage's default tracer its job
took an hour. The coverage gate and report are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Require surpyval 0.24

0.24 gives a mixture a survival function precise in the far tail
(SurPyval#671) and has conditional_gaps read a limited failure
population's lfp_p, not a mixture's p (SurPyval#626), so the mixture
test's survival is the mixture's own sf and MixtureLife only supplies the
quantile. 0.24's MixtureModel.qf inverts F in the upper tail (relative
error ~1e-4 for long lives) and takes ~200 times as long as
mixture_quantile (SurPyval#821), so the simulations keep RePyability's.
Seeded results are unchanged. pyproject's comment, CLAUDE.md's
workaround list and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: exact mean and draws

mean() integrated the survival curve tabulated on 4,096 points and
stopped where it fell to 1e-4, so it came out low (8e-5 for a LogNormal
AFT), and draws were read off that grid, none past its end. The mean is
now mean_lifetime's integral (relative 1e-10, pieces at the model's
quantiles or the schedule's change points; inf where lives never end),
and a draw is the model's qf(u, Z) at fixed covariates (surpyval 0.24)
or, along a schedule, the time at which Hf_tvc reaches -log(1 - u), by
bisection. surpyval's mean(Z) is not used: on AH models with real-line
baselines it integrates an sf above 1 (SurPyval#828). Seeded draws with a
regression node change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: a CovariatePath schedule is integrated and drawn; saving it is refused by name

A CovariatePath has no step edges, so _kinks takes a schedule's edges
only where it has them (its linear moves only bend the hazard, which
the integral follows), and to_dict refuses it with a NotImplementedError
naming it, where both raised AttributeError. Its mean matches surpyval's
mean_tvc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Parameter uncertainty draws a fit's lfp_p and f0 from covariance() (#267)

"fit" drew only a distribution's own parameters (hess_inv) and kept a
limited failure population's lfp_p and a zero-inflated fit's f0 at their
fitted values, so the spread of the reliability long after the failing
units have failed came out as nothing. It now draws every parameter the
fit estimated from surpyval's covariance(), the shares on the logit
scale. An offset, which the covariance leaves out (SurPyval#830), is
held where it was fitted, with a warning.

parametric_spec lists the shares after a distribution's parameters, in
covariance()'s order, and builds a model from any such vector, so
parameter_sensitivity, levers()/with_levers, both classes' delta methods
and {parameter: distribution} uncertainties take them as parameters.
The catalogue gains a fit of both shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional survival from the cumulative hazard, precise at old ages (#268)

conditional_survival took R(X + x) / R(X), which is 0 once both are too
small for a float. Where the model has a cumulative hazard it is now
exp(-(H(X + x) - H(X))): a Weibull(100, 3) unit 1000 hours old survives
the next 10 hours with probability 6.9e-14, where it gave 0. It serves
NonRepairableRBD.cs and a node's age in sf_given_state; models without
an Hf keep the ratio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Fitted repairable-unit models as RepairableRBD components (#269)

A spec's "reliability" may be a surpyval CrowAMSAA, Duane or HPP fit,
minimal repair of the life whose cumulative hazard is the process's
cumulative intensity (a Weibull or exponential life), or a
GeneralizedRenewal fit, its life distribution with the "repair" of its
Kijima model and restoration factor. The constructor writes such a spec
as that life and repair (repyability/rbd/_processes.py), so every
analysis, both engines and saving take it as they take the spec written
out. ARA, ARI and G1 fits are refused, their repairs not being Kijima's
(SurPyval#833); a fit given with its own "repair" is refused.

The catalogue gains a component given a fitted Crow-AMSAA process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* A simulation starts an imperfectly repaired unit from its virtual age (#269)

NodeState(age=a, virtual_age=v): an imperfectly repaired component's
virtual age at its last repair and its operating time since. The Python
event loop (the one that simulates imperfect repair) draws its life left
from virtual age v + a, from the component's start stream, and its next
repair takes the virtual age on by the whole time since the last repair;
one down is at virtual age v once its repair is over. A fitted
GeneralizedRenewal's unit_states() gives both.

The exact methods still refuse such a state, now saying the simulations
take it, as do the simulations for a component also replaced after some
failures, maintained or tested (that needs its history since renewal); a
virtual age on a component not repaired imperfectly is refused. Tests
check Kijima I and II against a reference written from the definition,
minimal repair against its closed form, and a unit started down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Start-from-state test: an imperfectly repaired unit's state is taken by the simulations (#269)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* A regression node's covariates are levers (#272)

A RegressionNode at fixed covariates lists each covariate in levers() as
"covariate.<name>" (the model's feature name, or its place);
parameter_sensitivity reports the system's derivative in it and
with_levers builds the diagram with the component run at another value.
A CovariateSpec, with ParametricSpec's interface, carries them, and
lever_spec (parametric_spec or covariate_spec) feeds the levers, the
sensitivities and a common-cause group's sensitivities; parameter
uncertainty keeps to parametric_spec, as covariates are operating
conditions, not estimates. A covariate is unbounded: running outside the
fitted conditions is extrapolation, as an ALT's use level is, and is not
refused or warned about. A node along a schedule has no levers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…); unit fixes and duty cycles (#276)

* Refuse node_availability with crews and common-cause groups (#251)

With limited repair crews, every long-run value but node_availability
already refused common-cause groups. node_availability gave each
component's value in the crews' chain, which has no common causes,
although a shared failure leaves one member waiting for the crew. The
crews' chain now refuses groups itself (_require_crew_chain), so neither
it nor its route can be read without them. The refusal says to simulate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Keep every level's structure up to date; no truth tables (#255)

The compiled engine built a table of whether the system works in each of
the 2^n states of its components (n <= 20), and one for each nested RBD,
on every run: 0.11 s for 20 components, over half of a 1,000-simulation
run. The incremental structure it used above 20 components is as fast as
the table at every size measured, and faster at 20, so it is now the one
way: _compiled._kept lays out every level's structure as one (each path
set's level, each level's root and whether it always works), and the
kernel keeps it up to date at every level (_keep, _level_works). The
tables, truth_table, _works and MAX_TABLED are gone, and with them the
refusal of a nested RBD of more than 20 components.

Results are the same, bit for bit. A 1,000-simulation run of two lines
of ten components takes 0.036 s rather than 0.119 s; 20,000, 0.62 s
rather than 0.70 s. engines.API is 3, as the run's arrays changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Work out each stream's key once a run (#254)

A run's Plan keeps each stream's Philox key, worked out once, and hands
it to the blocks: 40 SeedSequences rather than 800 for 20,000
simulations of 20 components (about 0.035 s of 0.65 s).

Laying the draws out more tightly was measured and left out: a limit for
each simulation, extended alone, with first chunks nearer the expected
draws, worked out a third fewer values but ran the simulations that ran
out again, for no net gain on any of five benchmarks. The quantile
functions' overhead is raised in surpyval (SurPyval#769).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: add up equal levels with a sorted reduction (#246)

merged() summed the probabilities of equal levels with np.add.at, one
row at a time. A stable sort of the rows by level and np.add.reduceat
make the same sums in the same order (each level from its first row,
and 0.0 + x is x), so results are the same, bit for bit. tidy() is
cached: a run tidies the same few levels for every state it meets.

25 redundant pairs in series (50 components with capacities), 2,000 h,
numba: 500 simulations 0.39 s -> 0.31 s, 2,000 simulations 1.04 s ->
0.96 s; 70 pairs on the Python loop, 100 simulations, 3.83 s -> 3.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Timelines: work out each change's position once (#247)

A _Data's changes' histories and positions in them are a cached
property, worked out once with a repeat of the offsets (no gather
through the histories' numbers), where every measure (uptime, failures,
the curve, the merges) worked them out again. A unit's own histories no
longer store their changes' index among their cause's changes, which is
their position (index None; _Data.indices gives either).

Results are the same, bit for bit. The 12-component bridge feeding a
vote, 20,000 simulations, numba: simulate_timelines 0.76 s -> 0.69 s,
the first measures over its result 0.79 s -> 0.68 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional runs: order the modules' changes in one sort (#248)

paths() put every module's changes in order with a four-key lexsort, and
the stretches with another. The changes now take one sort of a key that
grows with the simulation and the time (each simulation's times shifted
past the one before's by a power of two beyond the window, so rounding
never puts two out of order), and each run of equal keys (changes at one
instant, or within rounding) is put in order exactly: by simulation,
time, rank, then as given. The stretches need no sort: a simulation's
start goes before its first change, the changes already in order.
values() and capacity_values() find each joint state's stretches, and
the changes down from and into it, from one stable sort, rather than
comparing every stretch with every state.

Results are the same, bit for bit. Also: the simulation guide said the
compiled engine takes nested RBDs of up to 20 components, which #255
lifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: faster capacity runs, timelines and conditional runs (#246, #247, #248)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: black (#246)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Capacity: sum no levels when none is given; equal to np.add.at to rounding (#246)

The sorted reduction marked a first row even with no rows, so a run
whose capacity had no level left failed in add.reduceat. Its sums also
differ from np.add.at's in the last bits where a level has three or more
rows (reduceat adds a level's later rows up before its first): the test
now checks the sums to rounding and the levels exactly, and the
CHANGELOG says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Release 0.13: remove what 0.12 deprecated; version, CHANGELOG, saving guide

What 0.12 deprecated goes: calling SparesDemand.mean() or std(),
properties since 0.12 (#184), raises TypeError, as do StandbyModel's and
LoadSharingModel's mc_samples, lower and seed, which set a fit to
simulated lifetimes that 0.12 removed (#149). StandbyModel's
switching_probability and dormancy_factor came after them, so they are
given by name, and a call that gave them by position is refused rather
than misread. test_removed_in_0_13 keeps them gone.

deprecation.NEXT_REMOVAL is now 0.14, what 0.13 deprecates (offsets=,
CapacityDistribution.mean() called, an exact mean's simulation options),
and REMOVAL_AFTER_NEXT goes. The CHANGELOG's [Unreleased] becomes
[0.13] - 2026-10-09, with its summary and behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: one automated test run, the full suite on the pull request from dev into master

actions.yml runs on pull requests into master alone, not on pushes or on
pull requests into dev, and upstream.yml (the suite against surpyval's
develop, on pushes, pull requests and nightly) goes: the maintainer wants
one automated test run. release.yml takes that run, which is recorded
against dev's commit, for master's merge commit when the two hold the same
files. CLAUDE.md and pyproject.toml's comments say so, and that surpyval's
develop is tested by hand before surpyval releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CI: run the suite on every core, and measure coverage on Python 3.14 alone

Every test job runs pytest -n auto (pytest-xdist), using all the runner's
cores where it used one. Coverage is measured once, on Python 3.14
through sys.monitoring, collected from each worker by pytest-cov: Python
3.11 has no sys.monitoring, and under coverage's default tracer its job
took an hour. The coverage gate and report are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Require surpyval 0.24

0.24 gives a mixture a survival function precise in the far tail
(SurPyval#671) and has conditional_gaps read a limited failure
population's lfp_p, not a mixture's p (SurPyval#626), so the mixture
test's survival is the mixture's own sf and MixtureLife only supplies the
quantile. 0.24's MixtureModel.qf inverts F in the upper tail (relative
error ~1e-4 for long lives) and takes ~200 times as long as
mixture_quantile (SurPyval#821), so the simulations keep RePyability's.
Seeded results are unchanged. pyproject's comment, CLAUDE.md's
workaround list and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: exact mean and draws

mean() integrated the survival curve tabulated on 4,096 points and
stopped where it fell to 1e-4, so it came out low (8e-5 for a LogNormal
AFT), and draws were read off that grid, none past its end. The mean is
now mean_lifetime's integral (relative 1e-10, pieces at the model's
quantiles or the schedule's change points; inf where lives never end),
and a draw is the model's qf(u, Z) at fixed covariates (surpyval 0.24)
or, along a schedule, the time at which Hf_tvc reaches -log(1 - u), by
bisection. surpyval's mean(Z) is not used: on AH models with real-line
baselines it integrates an sf above 1 (SurPyval#828). Seeded draws with a
regression node change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* RegressionNode: a CovariatePath schedule is integrated and drawn; saving it is refused by name

A CovariatePath has no step edges, so _kinks takes a schedule's edges
only where it has them (its linear moves only bend the hazard, which
the integral follows), and to_dict refuses it with a NotImplementedError
naming it, where both raised AttributeError. Its mean matches surpyval's
mean_tvc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Parameter uncertainty draws a fit's lfp_p and f0 from covariance() (#267)

"fit" drew only a distribution's own parameters (hess_inv) and kept a
limited failure population's lfp_p and a zero-inflated fit's f0 at their
fitted values, so the spread of the reliability long after the failing
units have failed came out as nothing. It now draws every parameter the
fit estimated from surpyval's covariance(), the shares on the logit
scale. An offset, which the covariance leaves out (SurPyval#830), is
held where it was fitted, with a warning.

parametric_spec lists the shares after a distribution's parameters, in
covariance()'s order, and builds a model from any such vector, so
parameter_sensitivity, levers()/with_levers, both classes' delta methods
and {parameter: distribution} uncertainties take them as parameters.
The catalogue gains a fit of both shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Conditional survival from the cumulative hazard, precise at old ages (#268)

conditional_survival took R(X + x) / R(X), which is 0 once both are too
small for a float. Where the model has a cumulative hazard it is now
exp(-(H(X + x) - H(X))): a Weibull(100, 3) unit 1000 hours old survives
the next 10 hours with probability 6.9e-14, where it gave 0. It serves
NonRepairableRBD.cs and a node's age in sf_given_state; models without
an Hf keep the ratio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Fitted repairable-unit models as RepairableRBD components (#269)

A spec's "reliability" may be a surpyval CrowAMSAA, Duane or HPP fit,
minimal repair of the life whose cumulative hazard is the process's
cumulative intensity (a Weibull or exponential life), or a
GeneralizedRenewal fit, its life distribution with the "repair" of its
Kijima model and restoration factor. The constructor writes such a spec
as that life and repair (repyability/rbd/_processes.py), so every
analysis, both engines and saving take it as they take the spec written
out. ARA, ARI and G1 fits are refused, their repairs not being Kijima's
(SurPyval#833); a fit given with its own "repair" is refused.

The catalogue gains a component given a fitted Crow-AMSAA process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* A simulation starts an imperfectly repaired unit from its virtual age (#269)

NodeState(age=a, virtual_age=v): an imperfectly repaired component's
virtual age at its last repair and its operating time since. The Python
event loop (the one that simulates imperfect repair) draws its life left
from virtual age v + a, from the component's start stream, and its next
repair takes the virtual age on by the whole time since the last repair;
one down is at virtual age v once its repair is over. A fitted
GeneralizedRenewal's unit_states() gives both.

The exact methods still refuse such a state, now saying the simulations
take it, as do the simulations for a component also replaced after some
failures, maintained or tested (that needs its history since renewal); a
virtual age on a component not repaired imperfectly is refused. Tests
check Kijima I and II against a reference written from the definition,
minimal repair against its closed form, and a unit started down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Start-from-state test: an imperfectly repaired unit's state is taken by the simulations (#269)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* A regression node's covariates are levers (#272)

A RegressionNode at fixed covariates lists each covariate in levers() as
"covariate.<name>" (the model's feature name, or its place);
parameter_sensitivity reports the system's derivative in it and
with_levers builds the diagram with the component run at another value.
A CovariateSpec, with ParametricSpec's interface, carries them, and
lever_spec (parametric_spec or covariate_spec) feeds the levers, the
sensitivities and a common-cause group's sensitivities; parameter
uncertainty keeps to parametric_spec, as covariates are operating
conditions, not estimates. A covariate is unbounded: running outside the
fitted conditions is extrapolation, as an ALT's use level is, and is not
refused or warned about. A node along a schedule has no levers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Fitted degradation processes as lives; replacement on condition by level (#271)

A surpyval WienerProcess or GammaProcess fit is a node's life in both
diagram classes: its first-passage time to the threshold, exact in every
method and simulated on both engines, and saved. One fitted with stress
covariates is refused.

"preventive": {"policy": "condition", "level": x} replaces a unit whose
inspection finds its level at or past x. The level an inspection finds is
drawn, from a new LEVEL stream, given that the unit has not failed since
the last: a gamma increment truncated at the threshold, or a Wiener path
killed there (the method of images), a workaround for SurPyval#836. Only
the Python loop simulates it; the exact methods and a start state refuse
it. Tests check the levels against brute force and the simulations
against the process stepped on a fine grid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Units: each node's unit checked to agree, a component's duty, four unit bugs

- units= on both diagram classes: each node's unit as text, refused when
  they differ (a nested diagram's taking part, a phased mission's phases
  too), saved, and the diagram's units property.
- A RepairableRBD spec's "duty": the share of the time a component
  operates, putting a life in operating time on the calendar, R(d t), by
  each surpyval distribution's rule.
- A FixedEventProbability life no longer has a long run (it was read as a
  mean life); RegressionNode takes accelerated-life models (it counted
  their life model's parameters as covariates); the cold-standby grid and
  the density's steps are relative, so no result depends on the unit;
  proportional DIM moves a log-scale parameter by the change itself and
  refuses a covariate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Leave a model's unit to surpyval: drop per-node units=

A model's unit belongs to the data it was fitted to, so surpyval should
carry it (SurPyval#845); a per-node units= here would have needed a
deprecation once it does. The guide's Units section keeps the convention
and its traps. The duty's rescaling rules are listed as a workaround for
the same issue. The long-run refusal of a probability checks only that,
so a simulated standby group's message is its own again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ide (#277)

- Lint: black-format three tests, wrap two long docstring lines, and
  fix the four mypy errors on dev, so black, isort, flake8 and mypy pass.
- scripts/check.sh runs those four as CI runs them, then any tests given.
  Pre-commit's isort, flake8 and mypy match the pinned dev versions and
  check the package as CI does; pyupgrade goes.
- CONTRIBUTING.md rewritten: setup, the one automated run, the checks,
  what must agree, surpyval workarounds, deprecations, releases.
- README: a plain opening, Install before the simulation table (with
  Python 3.11+), Testing and Pre-commit replaced by a pointer to
  CONTRIBUTING.md. The PR template's checklist follows.
- Docstrings and guide: degradation processes and mixtures listed as
  lives, the condition policy's "level", the decomposition named
  correctly, CapacityDistribution.mean as a property, and smaller fixes.


Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

Co-authored-by: Claude <noreply@anthropic.com>
…CI floors (#278)

* Review, second findings: dead code, narrower catches, shared checks, CI floors

- Dead code out: _continued_rows, curve_se, draw_rows, _POSITIVE,
  GateSpec and the _as_float/_number_or_nan aliases. The timeline
  arithmetic only tests used moves to tests/timeline_reference.py.
- The test catalogue fits each model once and hands out copies: the
  mixture's EM refit made each repairable_kinds() call take about 6 s.
- A model is probed with _model_utils.MODEL_ERRORS (and saved with
  SAVE_ERRORS), not Exception, so a fault in a model is raised.
  analysis_routes() reports only refusals (NotImplementedError, or the
  new routes.Refused, a ValueError) and raises anything else.
- One dispatcher for the Birnbaum, improvement, RAW and RRW measures;
  checks.one_of for 17 option checks; ccf.checked_groups for the three
  common-cause group checks; needless function-level imports moved up.
- mypy checks untyped function bodies (21 fixes).
- CI: the minimum job installs every dependency at its floor (uv
  lowest-direct), jobs have timeouts, pytest-timeout names a hung test
  and --durations reports the slowest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Assert the availability target where it is used: none is given when the cost alone is minimised

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…gs, a maintenance guide page (#279)

* API reference: a page per section, without inlined source, with inherited members

The one page was 5.7 MB, most of it inlined source; the largest page is
now 0.9 MB. A test checks the pages document every public name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Public docstrings drop bare issue numbers, which help() and the web pages showed without meaning

Comments and private docstrings keep them for maintainers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Guide: system maintenance gets its own page, out of Costs

Preventive maintenance in a diagram, replacement on condition,
opportunistic maintenance and hidden failures move from costs.md (8.7k
words) to system-maintenance.md, beside Maintenance policies; links
follow them. Docstring links to the guide resolve from the API pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* CHANGELOG: the documentation's new layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…too (#280)

A RepairableRBD component whose life is FixedEventProbability at 0 was
refused by the exact methods (and before that read as a mean life of 0).
It is now a junction, as a life of PerfectReliability is: the shared
check moves to _model_utils.always_works. test_bdd_core's diagram, which
draws a vote point that way, passes again.


Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

Co-authored-by: Claude <noreply@anthropic.com>
…me order (#281)

Pure moves, no change to any computation:
- _time_order.py: a run's changes put in time order (_net_by_time, ...);
- _events.py: the event loop's records and state (Event, _EventQueue,
  _Crews, _StandbyGroup, _Cause, the schedules);
- _tally.py: what a run's simulations add up to (_Tally, _ModuleRun,
  the capacity recorders);
- checks.simulation_window and checks.nonnegative_times, the input checks
  _check_window and _check_times were.

Every user imports the names from their new homes; CLAUDE.md names them.


Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

Co-authored-by: Claude <noreply@anthropic.com>
…ec (#282)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…cation (#285)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…vals (#286)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
#288)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ups (#289)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…able_importance (#290)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…291)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…n _windows (#292)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 12: values over time from the curves, as functions of _windows

point_availability, point_unavailability, mission_availability,
mission_unavailability, expected_failures, expected_events and
expected_cost, with the window integrals and counts they rest on, move
to a new module as functions taking the diagram; the public methods
stay on the class, calling them. _settling and _MISSION_POINTS stay in
repairable_rbd for now (a test patches _settling there), imported where
they are used, as the other modules do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…296)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 12: values over time from the curves, as functions of _windows

point_availability, point_unavailability, mission_availability,
mission_unavailability, expected_failures, expected_events and
expected_cost, with the window integrals and counts they rest on, move
to a new module as functions taking the diagram; the public methods
stay on the class, calling them. _settling and _MISSION_POINTS stay in
repairable_rbd for now (a test patches _settling there), imported where
they are used, as the other modules do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 13: the components' curves, as functions of _curves

Each node's availability curve (plain units, block replacement, tested
units, standby groups, nested RBDs, and the crews' and groups' chains),
the states a run or an analysis starts from, and when the curves settle
(_settling, with _MISSION_POINTS and _POINT_STEPS) move to a new module;
the modules that imported _settling and _MISSION_POINTS from
repairable_rbd take them from _curves, and the tests patch them there.
_sharing_curves stays a method (a context manager over the diagram's
cache of curves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
… _crews and _requirements (#297)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 12: values over time from the curves, as functions of _windows

point_availability, point_unavailability, mission_availability,
mission_unavailability, expected_failures, expected_events and
expected_cost, with the window integrals and counts they rest on, move
to a new module as functions taking the diagram; the public methods
stay on the class, calling them. _settling and _MISSION_POINTS stay in
repairable_rbd for now (a test patches _settling there), imported where
they are used, as the other modules do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 13: the components' curves, as functions of _curves

Each node's availability curve (plain units, block replacement, tested
units, standby groups, nested RBDs, and the crews' and groups' chains),
the states a run or an analysis starts from, and when the curves settle
(_settling, with _MISSION_POINTS and _POINT_STEPS) move to a new module;
the modules that imported _settling and _MISSION_POINTS from
repairable_rbd take them from _curves, and the tests patch them there.
_sharing_curves stays a method (a context manager over the diagram's
cache of curves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 14: the crews' chain and the checks, as functions of _crews and _requirements

The repair crews' Markov chain in the exact methods (whether crews limit
the components, the chain, its rates, probabilities and outage terms)
moves to _crews; the checks the analyses and analysis_routes refuse
through (_require_*) and the tested units' terms to _requirements, as
functions taking the diagram. _crews_couple stays a method (a test
overrides it on one diagram). _compiled imports _crews where it uses it,
as _crews imports the modules above _compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…heir modules (#298)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 12: values over time from the curves, as functions of _windows

point_availability, point_unavailability, mission_availability,
mission_unavailability, expected_failures, expected_events and
expected_cost, with the window integrals and counts they rest on, move
to a new module as functions taking the diagram; the public methods
stay on the class, calling them. _settling and _MISSION_POINTS stay in
repairable_rbd for now (a test patches _settling there), imported where
they are used, as the other modules do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 13: the components' curves, as functions of _curves

Each node's availability curve (plain units, block replacement, tested
units, standby groups, nested RBDs, and the crews' and groups' chains),
the states a run or an analysis starts from, and when the curves settle
(_settling, with _MISSION_POINTS and _POINT_STEPS) move to a new module;
the modules that imported _settling and _MISSION_POINTS from
repairable_rbd take them from _curves, and the tests patch them there.
_sharing_curves stays a method (a context manager over the diagram's
cache of curves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 14: the crews' chain and the checks, as functions of _crews and _requirements

The repair crews' Markov chain in the exact methods (whether crews limit
the components, the chain, its rates, probabilities and outage terms)
moves to _crews; the checks the analyses and analysis_routes refuse
through (_require_*) and the tested units' terms to _requirements, as
functions taking the diagram. _crews_couple stays a method (a test
overrides it on one diagram). _compiled imports _crews where it uses it,
as _crews imports the modules above _compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 15: uncertainty and sensitivity, as functions of their modules

The parameter-uncertainty methods (mean, point and mission
availability, the cost rate, uncertainty importance) join
_repairable_uncertainty, and parameter_sensitivity joins _sensitivity,
as functions taking the diagram; the public methods stay on the class,
calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 12: values over time from the curves, as functions of _windows

point_availability, point_unavailability, mission_availability,
mission_unavailability, expected_failures, expected_events and
expected_cost, with the window integrals and counts they rest on, move
to a new module as functions taking the diagram; the public methods
stay on the class, calling them. _settling and _MISSION_POINTS stay in
repairable_rbd for now (a test patches _settling there), imported where
they are used, as the other modules do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 13: the components' curves, as functions of _curves

Each node's availability curve (plain units, block replacement, tested
units, standby groups, nested RBDs, and the crews' and groups' chains),
the states a run or an analysis starts from, and when the curves settle
(_settling, with _MISSION_POINTS and _POINT_STEPS) move to a new module;
the modules that imported _settling and _MISSION_POINTS from
repairable_rbd take them from _curves, and the tests patch them there.
_sharing_curves stays a method (a context manager over the diagram's
cache of curves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 14: the crews' chain and the checks, as functions of _crews and _requirements

The repair crews' Markov chain in the exact methods (whether crews limit
the components, the chain, its rates, probabilities and outage terms)
moves to _crews; the checks the analyses and analysis_routes refuse
through (_require_*) and the tested units' terms to _requirements, as
functions taking the diagram. _crews_couple stays a method (a test
overrides it on one diagram). _compiled imports _crews where it uses it,
as _crews imports the modules above _compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 15: uncertainty and sensitivity, as functions of their modules

The parameter-uncertainty methods (mean, point and mission
availability, the cost rate, uncertainty importance) join
_repairable_uncertainty, and parameter_sensitivity joins _sensitivity,
as functions taking the diagram; the public methods stay on the class,
calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 16: the runs, as functions of _runs

availability and cost with their result and engine, the exact twin as a
control variate, conditional runs, chunks, shards and their merge,
simulate_timelines and compare move to a new module as functions taking
the diagram; the public methods stay on the class, calling them. The
event loop (_run, _replicate) stays with the diagram, and
_conditional_modules stays a method (a test overrides it on one
diagram). The criticality indices of a run's counts stay importable
from repairable_rbd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 12: values over time from the curves, as functions of _windows

point_availability, point_unavailability, mission_availability,
mission_unavailability, expected_failures, expected_events and
expected_cost, with the window integrals and counts they rest on, move
to a new module as functions taking the diagram; the public methods
stay on the class, calling them. _settling and _MISSION_POINTS stay in
repairable_rbd for now (a test patches _settling there), imported where
they are used, as the other modules do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 13: the components' curves, as functions of _curves

Each node's availability curve (plain units, block replacement, tested
units, standby groups, nested RBDs, and the crews' and groups' chains),
the states a run or an analysis starts from, and when the curves settle
(_settling, with _MISSION_POINTS and _POINT_STEPS) move to a new module;
the modules that imported _settling and _MISSION_POINTS from
repairable_rbd take them from _curves, and the tests patch them there.
_sharing_curves stays a method (a context manager over the diagram's
cache of curves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 14: the crews' chain and the checks, as functions of _crews and _requirements

The repair crews' Markov chain in the exact methods (whether crews limit
the components, the chain, its rates, probabilities and outage terms)
moves to _crews; the checks the analyses and analysis_routes refuse
through (_require_*) and the tested units' terms to _requirements, as
functions taking the diagram. _crews_couple stays a method (a test
overrides it on one diagram). _compiled imports _crews where it uses it,
as _crews imports the modules above _compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 15: uncertainty and sensitivity, as functions of their modules

The parameter-uncertainty methods (mean, point and mission
availability, the cost rate, uncertainty importance) join
_repairable_uncertainty, and parameter_sensitivity joins _sensitivity,
as functions taking the diagram; the public methods stay on the class,
calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 16: the runs, as functions of _runs

availability and cost with their result and engine, the exact twin as a
control variate, conditional runs, chunks, shards and their merge,
simulate_timelines and compare move to a new module as functions taking
the diagram; the public methods stay on the class, calling them. The
event loop (_run, _replicate) stays with the diagram, and
_conditional_modules stays a method (a test overrides it on one
diagram). The criticality indices of a run's counts stay importable
from repairable_rbd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 17: the event loop, as functions of _event_loop

The Python event loop moves to a new module as functions taking the
diagram: the components' streams and stand-ins, a run's context, the
queue and what follows each event, one simulation (_replicate) and a
run of them (_run, holding SIMULATIONS) in this process, in workers or
in shards; with every module-level helper repairable_rbd had, so that
it now holds the class alone. The public initialize_event_queue and
next_event stay on the class, calling them. Annotations on the run
state's attributes (rbd.x: T = ...), which a function cannot declare,
go until the run state is an object of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Event's doctest and an engine test take the moved names from their modules

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
…es; CLAUDE.md follows the split (#301)

* Split repairable_rbd.py, step 2: the spec checks, as functions of _spec

The constructor's checks of its inputs (_validate_* and _unknown_component)
become functions of rbd/_spec.py, with the keys a spec may carry, which
stay the class's attributes (RepairableRBD.PREVENTIVE_KEYS, ...) as
aliases. Moved unchanged but for the calls between them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* _spec: import RepairableRBD where it is used at run time, not only for type checking

The common-cause check tells a nested RBD by its class; imported only
under TYPE_CHECKING, the name was undefined when it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Move the RepairableRBD modules' shared helpers into _common (group 3)

The horizon and discount checks, a model's mean, survival and cumulative
distribution as arrays, the constant rate, the common period of
intervals and the shapes results take, moved unchanged out of
repairable_rbd.py, so the method groups that follow can import them
without importing the class's module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 4: spares_demand and spares_stock, as functions of _spares

The spares methods and their helpers move, unchanged, to _spares as
functions taking the diagram; the public methods stay on the class,
calling them. The crews' messages move with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 5: the allocations, as functions of _repairable_allocation

allocate_redundancy, availability_allocation and mttf_mttr_allocation,
their helpers and the trains' types move, unchanged, to a new module as
functions taking the diagram; the public methods stay on the class,
calling them. The hooks RBD's allocations call (_allocation_probability,
_log_odds) stay methods, as they defer to RBD's through super().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 6: the maintenance intervals, as functions of _intervals

optimal_replacement_intervals, optimal_inspection_intervals and
with_intervals, their helpers and the search over the allowed calendar
move, unchanged, to a new module as functions taking the diagram; the
public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 7: the long-run costs, as functions of _costs

expected_cost_rate, acquisition_cost and total_cost, with each node's
cost rate and actions, move, unchanged, to a new module as functions
taking the diagram; the public methods stay on the class, calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 8: the capacity, as functions of _repairable_capacity

capacity_distribution, point_capacity and mission_capacity, their rows
and curves, the crews' capacity over nested RBDs (_CrewCapacity) and the
capacity's refusals move, unchanged, to a new module as functions taking
the diagram; the public methods stay on the class, calling them, and the
hook _capacity_models stays a method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 9: the common-cause groups, as functions of _ccf_groups

The groups' rates and timings, their chains in the long run and over
time, the system worked out with them and their checks move, unchanged,
to a new module as functions taking the diagram. _ccf_plan and
_ccf_measure stay methods: NonRepairableRBD has its own of each, which
callers reach on either kind of diagram.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 10: the importance measures, as functions of _repairable_importance

Birnbaum's measure and those built on it, Fussell-Vesely, and the
measures of change (differential, joint, availability rate,
Barlow-Proschan) move to a new module as functions taking the diagram;
the public methods stay on the class, calling them. Their calls of
RBD's static parts go through the diagram (rbd._birnbaum_importance,
...) instead of super(), which a function has not: RepairableRBD does
not override them, so it is the same method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 11: the long-run values, as functions of _long_run

Each component's long-run availability and failure frequency (renewal
cycles, block replacement's calendar grid, tested units' cycles) and
the system's mean availability, failure frequency, MTBF, mean up and
down times and node availability move to a new module as functions
taking the diagram; the public methods stay on the class, calling them.
_outage_terms and _chain_outage_terms call rbd._birnbaum_importance,
the same method as super()'s (RepairableRBD does not override it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 12: values over time from the curves, as functions of _windows

point_availability, point_unavailability, mission_availability,
mission_unavailability, expected_failures, expected_events and
expected_cost, with the window integrals and counts they rest on, move
to a new module as functions taking the diagram; the public methods
stay on the class, calling them. _settling and _MISSION_POINTS stay in
repairable_rbd for now (a test patches _settling there), imported where
they are used, as the other modules do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 13: the components' curves, as functions of _curves

Each node's availability curve (plain units, block replacement, tested
units, standby groups, nested RBDs, and the crews' and groups' chains),
the states a run or an analysis starts from, and when the curves settle
(_settling, with _MISSION_POINTS and _POINT_STEPS) move to a new module;
the modules that imported _settling and _MISSION_POINTS from
repairable_rbd take them from _curves, and the tests patch them there.
_sharing_curves stays a method (a context manager over the diagram's
cache of curves).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 14: the crews' chain and the checks, as functions of _crews and _requirements

The repair crews' Markov chain in the exact methods (whether crews limit
the components, the chain, its rates, probabilities and outage terms)
moves to _crews; the checks the analyses and analysis_routes refuse
through (_require_*) and the tested units' terms to _requirements, as
functions taking the diagram. _crews_couple stays a method (a test
overrides it on one diagram). _compiled imports _crews where it uses it,
as _crews imports the modules above _compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 15: uncertainty and sensitivity, as functions of their modules

The parameter-uncertainty methods (mean, point and mission
availability, the cost rate, uncertainty importance) join
_repairable_uncertainty, and parameter_sensitivity joins _sensitivity,
as functions taking the diagram; the public methods stay on the class,
calling them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 16: the runs, as functions of _runs

availability and cost with their result and engine, the exact twin as a
control variate, conditional runs, chunks, shards and their merge,
simulate_timelines and compare move to a new module as functions taking
the diagram; the public methods stay on the class, calling them. The
event loop (_run, _replicate) stays with the diagram, and
_conditional_modules stays a method (a test overrides it on one
diagram). The criticality indices of a run's counts stay importable
from repairable_rbd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 17: the event loop, as functions of _event_loop

The Python event loop moves to a new module as functions taking the
diagram: the components' streams and stand-ins, a run's context, the
queue and what follows each event, one simulation (_replicate) and a
run of them (_run, holding SIMULATIONS) in this process, in workers or
in shards; with every module-level helper repairable_rbd had, so that
it now holds the class alone. The public initialize_event_queue and
next_event stay on the class, calling them. Annotations on the run
state's attributes (rbd.x: T = ...), which a function cannot declare,
go until the run state is an object of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Event's doctest and an engine test take the moved names from their modules

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

* Split repairable_rbd.py, step 18: analysis_routes in _repairable_routes; CLAUDE.md follows the split

analysis_routes moves to a new module as a function taking the diagram;
the public method stays on the class, calling it. CLAUDE.md names the
moved functions by their modules, and says where RepairableRBD's code
lives and how a new analysis fits in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUGaeQXKxjjiQ1ULT1ApXS

---------

Co-authored-by: Claude <noreply@anthropic.com>
claude and others added 13 commits October 16, 2026 15:13
…its period (#319)

The grid's step is sized from the periods (and a plan's repeat), with
the offsets and dates on it only where that keeps it near the life's,
so an offset of a few hours no longer loses a calendar its period and
sends it to sampling. From the 0.14 persona review besides: "auto" warns
when it samples, a calendar allowing no planned replacement is refused,
a policy carries the life it was worked out for and a component with
another is refused, a unit past the oldest age is replaced at the next
opportunity, a planned replacement at once is replacing now at
now_cost, the resetting skip warns of a rate that discounts everything
away and gives replace and skip one meaning, and the single-unit
policies are result mappings.
…nd docs

A junction is no component of a phased mission, and an irrelevant node
behind one is found; a network refuses a distribution class, takes a
fixed element among lives, lists minimal sets and times of any shape;
a tree takes kind=, fv_type=, mean(method="simulate") and random; the
classes refuse simulation options on exact calls and bad counts alike;
analysis_routes() prints one method a line; and the messages, the
docs' numbers and the quickstart are put right.
… offsets as levers, and the review's smaller fixes

A group's members' tests that take time are the system's planned outages over a window, from the chain just before and at each test, and the chain's bends (with exponential transients) are the integrals' pieces; a group's members keep their own test offsets through with_levers, listed as their own levers; a test at the end of the group's period is not rounded past it; expected_events counts a test a rounding short of the extension's period once; BetaFactor, MGL and CCFGroup check their inputs; differential_importance gives a share per window; the measures' convention at a test instant and a shared cause's attribution in timelines are documented; the README's revealed-failures group row shows the group's own routes; a model's constant rate is kept while it lives.
…ffsets as levers, and the review's smaller fixes
A tested component repaired minimally refuses the values over time as the
routes say, where it was computed as renewed at every repair, and a run's
means fall back to the simulations' own; the cost analyses refuse a diagram
with nothing priced (routes.Refused), as the routes say. A nested RBD's
costs are warned of, its node counts follow the simulation's, its spares
refuse by default, its fits are said not to be drawn; one nested object for
two nodes is refused when built; the sensitivities over time leave out a
minimal repair's q; a run's delivered fraction is exact or conditional as
its mean availability is; node_availability lists the components alone; a
state's age is on the diagram's clock; spares convolutions keep their
transforms, and a lever's rebuilt diagram reuses the cycles it shares.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JHdSR3ZhskmRmRo8vuCT7V
Two reconciliations between the review's fix batches: cost() now refuses a
diagram with nothing priced before it checks its window, and the routes
mapping is a new export.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JHdSR3ZhskmRmRo8vuCT7V
The small and medium issues, and the 0.14 persona review's fixes
GitHub closes an issue only when the pull request saying "Closes #N"
merges into the default branch, master, which here happens at a release.
A workflow now closes them as the work reaches dev, with a comment naming
the pull request; CLAUDE.md and CONTRIBUTING.md say so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JHdSR3ZhskmRmRo8vuCT7V
Close the issues a pull request into dev finishes, when it merges
… guide (#343)

What 0.13 deprecated goes: optimal_inspection_intervals(offsets=) and
calling CapacityDistribution.mean() raise TypeError, mc_samples or seed
given to the exact mean of a StandbyModel, LoadSharingModel or
DegradingNode raise ValueError (the refusal every exact call gives a
simulation option, #351), and rbd._sensitivity's levers and Lever are
gone. test_removed_in_0_14 keeps them gone. deprecation.NEXT_REMOVAL is
0.15, for NodeState's phase, renamed since_scheduled (#350).

The CHANGELOG's [Unreleased] becomes [0.14] - 2026-10-16, with its
summary, behaviour changes, Deprecated and Removed sections; the version
and the saving guide say 0.14; CLAUDE.md and CONTRIBUTING name the next
removal. Type-only mypy fixes, and ReplacementPlan listed as no node.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
The joint importance's docstring in NonRepairableRBD linked the
common-cause guide from the API page's own directory (RepairableRBD's
links it from the parent, which is where it is), and the Learn lesson on
dependence linked the guide's "The two models" heading, "The three
models" since the alpha-factor model (#311). mkdocs build --strict
failed on both on the release pull request (#353).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
MinimalRepairCurve keeps its grid's values (up, failures) as optional
beside its times, set together. mypy 2.3.0, which CI pins, does not
narrow them with the times at np.interp, and failed the release pull
request (#353); mypy 2.3.1 does. Each read asserts the invariant.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
derrynknife and others added 3 commits October 17, 2026 06:19
…nges (#356)

The window counts ask whatever chain they are given (_atom_groups,
_before_period_end) for the times its states jump down, a common-cause
group's members' tests that take time (#220): the groups' system has
them, and the repair crews' (CrewSystem) did not, so every count over a
window with limited crews around a nested RBD failed on the release pull
request (#353). Its chain jumps nowhere at exact times: none.

Two tests follow what dev changed. The cost analyses refuse a diagram
with nothing priced (#351), so the condition test that compares a
threshold no age reaches with running to failure prices the repairs. And
the ample-crews test compared the chain's counts with the independent
nodes' to 1e-6, where each path's quadrature is within about 2e-6 of the
closed form over the shortest window (the chain's now the closer, since
the chains' bends are the integrals' pieces): 5e-6.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
ROADMAP.md holds the larger ideas and their designs (first among them a
net present value simulator for maintenance plans), and stories/ the
questions users and agents should be able to answer, with how we will
know they are answered; the README links both. Statuses are as of 0.13
and what is merged on dev for 0.14. Scope as CLAUDE.md says: RePyability
works in model time, and dates and calendars belong to the application.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
…sion to rounding (#358)

The suite at every dependency's floor (numpy 2.0.0, new this cycle: the
job tested only the oldest surpyval before) failed four tests on the
release pull request (#353). Three refused a component maintained for an
exactly known time: the duration is told fixed from its variance, a
difference of two numbers about its square, against a tolerance of a
billionth of it squared, a few units of rounding wide, and numpy 2.0
sums the moments to a variance of 1e-15 at a duration of 5. The
tolerance is a millionth of the duration, which rounding cannot cross
and no spread duration meets. The fourth asserted that an always-up
unit's mission availability is exactly 1.0, where the integral of a
constant sums the quadrature's weights: to rounding now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
…nding (#359)

With the fixed-duration tolerance rounding cannot cross (#358), a time
known exactly is told fixed on every numpy, but was given its mean,
which numpy 2.0 sums to 1.9999999999999996 for 2: it is now the time of
its one atom. The planned-outage test that compares a run's costs, the
exact means integrated by quadrature, with whole numbers compares them
to rounding: at the floors the integrals come out at 19.999999999999996
and 20.000000000000057 for 20. The full suite at the floors passes
locally (6605 passed).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
test_importance_is_averaged_over_the_interval asserted that the mean
availability with the critical node held working is exactly 1.0, where
it is worked out numerically: CI's minimum-dependencies job (numpy 2.0.0
on its runner) summed it to 0.9999999999999999 on the release pull
request (#353), while the same suite passed here at the floors. To
rounding.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Ng6hwUY8zznhm1jRfZJqZ
@derrynknife
derrynknife merged commit 86853f3 into master Oct 16, 2026
9 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.

Parameter uncertainty never varies lfp_p, f0 or an offset's gamma

2 participants