# SPDX-License-Identifier: MIT """YS-EN-0004 ยท check Equation 2 by simulating the fleet event by event. Failures arrive one at a time across the working cores (a dead core's parts stop running, unlike the Poisson model). Each failure is fixed from stock if possible, otherwise by taking the part from a core that is already dead (it donates its good parts); failing that, the core is laid up. Prints the simulated fleet POS for the manifests quoted in the note. Usage: python check_cannibalisation.py (a few minutes; requires numpy) """ from __future__ import annotations import numpy as np from sparing import INPUTS, ITEMS N, T = INPUTS["fleet"], INPUTS["mission_yr"] RATE = np.array([INPUTS["rate_share"][i] for i in ITEMS]) / INPUTS["core_mtbf_yr"] def fleet_pos(spares, tolerate=0, reps=20000, seed=1) -> float: rng = np.random.default_rng(seed) ok = 0 for _ in range(reps): stock = np.array(spares, float) dead = [] # per dead core: which parts it is missing working, t = N, 0.0 while True: t += rng.exponential(1 / (working * RATE.sum())) if t > T: break j = rng.choice(len(ITEMS), p=RATE / RATE.sum()) if stock[j] > 0: stock[j] -= 1 continue donor = next((d for d in dead if not d[j]), None) if donor is not None: # strip a dead core for the part donor[j] = True continue miss = np.zeros(len(ITEMS), bool) miss[j] = True dead.append(miss) working -= 1 ok += (N - working) <= tolerate return ok / reps if __name__ == "__main__": print("modules 98/84/71/71/24, m=0 :", fleet_pos([98, 84, 71, 71, 24])) print("94 spare cores (kits), m=0 :", fleet_pos([94] * 5)) print("modules 88/74/61/61/14, m=10:", fleet_pos([88, 74, 61, 61, 14], tolerate=10, reps=40000, seed=7))