Repository navigation
Release 0.14 - #353
Merged
Merged
Release 0.14#353
Conversation
…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>
…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.
… missions and docs
…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
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
…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
This was referenced Oct 16, 2026
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Merge dev into master for release 0.14: everything merged into
devsince 0.13 (#262–#352, and #343, the release commit), as the CHANGELOG's[0.14] - 2026-10-16section 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, aNetworkand aPhasedMissionhave 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), andspares_stockfor 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, andNodeState.phaseis deprecated forsince_scheduled(#350).The behaviour changes open the CHANGELOG section and the release notes.
Release
This pull request runs the full suite (
actions.yml), which thereleaseworkflow requires to have passed on these files. After the merge, the release is cut from master withrelease.yml(version0.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