nx_rot_health.nx source
↩ module page · 148 lines · 5468 B
1// nx_rot_health.nx -- rotating-machine health kernel (#2 of the 4 physics
2// kernels). Independent vibration + thermal signature -> health verdict for
3// motors, pumps, fans, compressors, bearings.
4//
5// Operator (2026-06-23): the maintenance platform's computed top-build to cross
6// into market LEADER (beats Augury, the strongest incumbent) -- and the 2nd
7// physics kernel, which unlocks the whole rotating-equipment domain.
8//
9// FFT-FREE first cut = real entry-level predictive-maintenance practice,
10// integer/no-float:
11// - RMS overall severity (ISO-10816-style zones) -> imbalance/misalign/loose
12// - CREST factor (peak/RMS) -> bearing impacts; RISES BEFORE RMS does, so it
13// catches an incipient bearing fault an overall-amplitude reading MISSES
14// (the measured exceed)
15// - bearing temp-rise -> friction/overheat (safety)
16// (Frequency-resolved diagnosis -- 1x imbalance vs 2x misalignment vs bearing
17// pass-frequencies -- is a later FFT rung; this overall+crest+thermal cut is
18// what a handheld vibration pen / ISO-10816 overall measurement already uses.)
19//
20// NEVER-BRICK (#26): reads samples, emits a verdict; writes nothing to any
21// machine. Read-only by construction; no firmware path exists here.
22//
23// genealogy_id: project-maintenance-platform-sclass-2026-06-23 (kernel #2; CIQ top-build)
24// license_tier: ORIGINAL
25//
26// nx_capability_claims:
27// needs: [pointer_arithmetic]
28// provides: [rotating_machine_health_verdict, iso10816_overall_severity,
29// crest_factor_bearing_early_warning, bearing_overheat_detect]
30// safety: [no_floating_point, no_syscall, bounded_iteration,
31// read_only_no_device_write, no_firmware_write_by_construction,
32// sealed_enum_verdict]
33// verdict: [sealed_enum_rot, no_silent_failure]
34// license: ORIGINAL
35// kind: maintenance_runtime_primitive
36
37// ---- Sealed verdict enum -------------------------------------------
38const NX_ROT_INSUFFICIENT_DATA: i64 = 0
39const NX_ROT_HEALTHY: i64 = 1
40const NX_ROT_ROUGH_WARNING: i64 = 2 // ISO zone B/C -- elevated vibration
41const NX_ROT_ROUGH_ALARM: i64 = 3 // ISO zone C/D -- unacceptable (imbalance/misalign/loose)
42const NX_ROT_BEARING_WEAR: i64 = 4 // high crest factor -- bearing impacts (early fault)
43const NX_ROT_OVERHEAT: i64 = 5 // bearing temp-rise over ceiling
44const NX_ROT_IDLE: i64 = 6 // not running (valid, not a fault)
45const NX_ROT_BAD_ARG: i64 = 7
46const NX_ROT_N: i64 = 8
47
48func nx_rot_verdict_is_valid(v: i64) -> i64 {
49 if v < 0 { return 0 }
50 if v >= NX_ROT_N { return 0 }
51 return 1
52}
53
54// ---- Data-driven spec = the healthy envelope (per machine class, #11) ----
55struct RotSpec {
56 iso_warn_um_s: i64, // ISO zone B/C boundary (RMS velocity, micro-mm/s)
57 iso_alarm_um_s: i64, // ISO zone C/D boundary
58 crest_warn_x100: i64, // crest ceiling x100 (sine ~141; bearing faults rise past ~300)
59 temp_rise_warn_mC: i64, // bearing temp-rise ceiling (milli-degC)
60}
61
62struct RotEff {
63 rms_um_s: i64,
64 peak_um_s: i64,
65 crest_x100: i64,
66 temp_rise_mC: i64,
67 verdict: i64,
68}
69
70// Integer square root (Newton). Pure; no float.
71func nx_rot_isqrt(n: i64) -> i64 {
72 if n <= 0 { return 0 }
73 var x: i64 = n
74 var y: i64 = (x + 1) / 2
75 while y < x {
76 x = y
77 y = (x + n / x) / 2
78 }
79 return x
80}
81
82// ---- The analysis --------------------------------------------------
83// vib[i] = instantaneous vibration velocity samples (signed, micro-mm/s).
84// bearing_mC / ambient_mC = bearing + ambient temperature (milli-degC).
85// running = 1 if the machine ran during the window, 0 if stopped.
86func nx_rot_analyze(vib: *i64, n: i64, bearing_mC: i64, ambient_mC: i64,
87 running: i64, spec: *RotSpec, out: *RotEff) -> i64 {
88 out.rms_um_s = 0
89 out.peak_um_s = 0
90 out.crest_x100 = 0
91 out.temp_rise_mC = 0
92 out.verdict = NX_ROT_INSUFFICIENT_DATA
93
94 if n < 1 {
95 out.verdict = NX_ROT_INSUFFICIENT_DATA
96 return out.verdict
97 }
98 if running != 0 {
99 if running != 1 {
100 out.verdict = NX_ROT_BAD_ARG
101 return out.verdict
102 }
103 }
104
105 var ss: i64 = 0
106 var peak: i64 = 0
107 var i: i64 = 0
108 while i < n {
109 var v: i64 = vib[i]
110 if v < 0 { v = 0 - v }
111 if v > peak { peak = v }
112 ss = ss + v * v
113 i = i + 1
114 }
115 let meansq: i64 = ss / n
116 let rms: i64 = nx_rot_isqrt(meansq)
117 out.rms_um_s = rms
118 out.peak_um_s = peak
119 var crest: i64 = 100
120 if rms > 0 { crest = (peak * 100) / rms }
121 out.crest_x100 = crest
122 out.temp_rise_mC = bearing_mC - ambient_mC
123
124 if running == 0 {
125 out.verdict = NX_ROT_IDLE
126 return out.verdict
127 }
128 // severity-ordered: safety (overheat) > specific bearing fault (crest) >
129 // overall roughness (RMS zones).
130 if out.temp_rise_mC > spec.temp_rise_warn_mC {
131 out.verdict = NX_ROT_OVERHEAT
132 return out.verdict
133 }
134 if out.crest_x100 > spec.crest_warn_x100 {
135 out.verdict = NX_ROT_BEARING_WEAR
136 return out.verdict
137 }
138 if out.rms_um_s >= spec.iso_alarm_um_s {
139 out.verdict = NX_ROT_ROUGH_ALARM
140 return out.verdict
141 }
142 if out.rms_um_s >= spec.iso_warn_um_s {
143 out.verdict = NX_ROT_ROUGH_WARNING
144 return out.verdict
145 }
146 out.verdict = NX_ROT_HEALTHY
147 return out.verdict
148}