nx_maint_simloop.nx source
↩ module page · 98 lines · 4411 B
1// nx_maint_simloop.nx -- the CLOSED-LOOP DIGITAL-TWIN SIMULATOR + learning loop.
2// Proves the whole maintenance platform IN SOFTWARE, reproducibly, with NO IRL
3// spend -- the operator's real goal: simulate first, prove via modeling, then
4// (optionally) validate on hardware.
5//
6// Operator (2026-06-23): "use software to simulate these things ... most systems
7// have an emission loop ... we want a full learning loop ... a good example is
8// OBD [which] just tells you errors but then you need 3rd parties to make the
9// data meaningful, actionable, reproducible." Grounded in the sovereign research
10// (knowledge/fetched/maint_*: digital_twin, control_loop, feedback, predictive_
11// maintenance, prognostics, on-board_diagnostics): S-class CLOSES the loop with a
12// digital twin + feedback control; OBD is OPEN-loop.
13//
14// THE LOOP (per cycle): twin wears -> generate a sensor reading -> run the REAL
15// pipeline (nx_hvac_analyze_core -> nx_maint_severity -> nx_plan_for) -> CLOSE
16// THE LOOP: a fix/replace action feeds back and RESETS the wear -> the next
17// cycle SELF-VERIFIES the asset recovered. Open-loop (no feedback) just degrades
18// and stays broken (OBD); the closed loop self-maintains = the measured EXCEED.
19//
20// NEVER-BRICK (#26): pure simulation; no syscalls, no device writes.
21//
22// genealogy_id: project-maintenance-platform-sclass-2026-06-23 (closed-loop sim; researcher-grounded)
23// license_tier: ORIGINAL
24
25import "nx_maint_action.nx" // the real pipeline: kernels + nx_maint_severity + nx_plan_for + enums
26const K_MAGIC_1800: i64 = 1800
27const K_MAGIC_19000: i64 = 19000
28const K_MAGIC_5000: i64 = 5000
29
30// A digital twin of a thermal asset (furnace): a fouling/wear level that grows
31// each cycle and drops the effective response rate; a fix resets it.
32struct TwinThermal {
33 fouling: i64, // 0..1000 wear (grows with use; >625 -> response under floor)
34 wear_per_cycle: i64, // fouling growth per cycle
35 base_resp: i64, // healthy response rate (mC/min)
36 cycles_run: i64,
37 fixes_applied: i64,
38 failures_seen: i64, // cycles that reached DEGRADED or worse
39}
40
41func nx_twin_init(tw: *TwinThermal, base_resp: i64, wear_per_cycle: i64) -> i64 {
42 tw.fouling = 0
43 tw.wear_per_cycle = wear_per_cycle
44 tw.base_resp = base_resp
45 tw.cycles_run = 0
46 tw.fixes_applied = 0
47 tw.failures_seen = 0
48 return 0
49}
50
51// Run n_cycles of the closed (apply_fix=1) or open (apply_fix=0) loop. The
52// caller supplies scratch buffers for the trace + outputs. setpoint is set high
53// so the wear signal (declining response rate) is isolated -- no overshoot/
54// short-cycle artifacts (those axes are gated separately in the kernel test).
55func nx_sim_run(tw: *TwinThermal, apply_fix: i64, n_cycles: i64,
56 t: *i64, indoor: *i64, outdoor: *i64, on: *i64,
57 out: *HvacEff, plan: *ActionPlan,
58 setpoint: i64, min_resp: i64) -> i64 {
59 var c: i64 = 0
60 while c < n_cycles {
61 var eff: i64 = (tw.base_resp * (1000 - tw.fouling)) / 1000
62 if eff < 0 { eff = 0 }
63
64 // generate the simulated heating run (resp == eff)
65 t[0] = 0
66 t[1] = K_MAGIC_1800
67 indoor[0] = K_MAGIC_19000
68 indoor[1] = K_MAGIC_19000 + eff * 30
69 outdoor[0] = K_MAGIC_5000
70 outdoor[1] = K_MAGIC_5000
71 on[0] = 1
72 on[1] = 1
73 nx_hvac_analyze_core(t, indoor, outdoor, on, 2, setpoint, NX_HVAC_MODE_HEAT, 35, min_resp, 600, out)
74 let verdict: i64 = out.verdict
75 let sev: i64 = nx_maint_severity(NX_MK_THERMAL, verdict)
76 nx_plan_for(NX_MK_THERMAL, verdict, NX_TREND_STABLE, plan)
77
78 tw.cycles_run = tw.cycles_run + 1
79 if sev >= NX_HEALTH_DEGRADED { tw.failures_seen = tw.failures_seen + 1 }
80
81 // CLOSE THE LOOP: a fix/replace action feeds back and resets the wear.
82 var fixed: i64 = 0
83 if apply_fix == 1 {
84 if plan.action == NX_ACT_FIX_DIY { fixed = 1 }
85 if plan.action == NX_ACT_FIX_PRO { fixed = 1 }
86 if plan.action == NX_ACT_REPLACE_CONSUMABLE { fixed = 1 }
87 if plan.action == NX_ACT_REPLACE_COMPONENT { fixed = 1 }
88 }
89 if fixed == 1 {
90 tw.fouling = 0
91 tw.fixes_applied = tw.fixes_applied + 1
92 } else {
93 tw.fouling = tw.fouling + tw.wear_per_cycle
94 }
95 c = c + 1
96 }
97 return tw.fixes_applied
98}