nx_rot_health.nx source
↩ module page · 140 lines · 5356 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 -------------------------------------------
38import "nx_vecmath.nx"
39const NX_ROT_INSUFFICIENT_DATA: i64 = 0
40const NX_ROT_HEALTHY: i64 = 1
41const NX_ROT_ROUGH_WARNING: i64 = 2 // ISO zone B/C -- elevated vibration
42const NX_ROT_ROUGH_ALARM: i64 = 3 // ISO zone C/D -- unacceptable (imbalance/misalign/loose)
43const NX_ROT_BEARING_WEAR: i64 = 4 // high crest factor -- bearing impacts (early fault)
44const NX_ROT_OVERHEAT: i64 = 5 // bearing temp-rise over ceiling
45const NX_ROT_IDLE: i64 = 6 // not running (valid, not a fault)
46const NX_ROT_BAD_ARG: i64 = 7
47const NX_ROT_N: i64 = 8
48
49func nx_rot_verdict_is_valid(v: i64) -> i64 {
50 if v < 0 { return 0 }
51 if v >= NX_ROT_N { return 0 }
52 return 1
53}
54
55// ---- Data-driven spec = the healthy envelope (per machine class, #11) ----
56struct RotSpec {
57 iso_warn_um_s: i64, // ISO zone B/C boundary (RMS velocity, micro-mm/s)
58 iso_alarm_um_s: i64, // ISO zone C/D boundary
59 crest_warn_x100: i64, // crest ceiling x100 (sine ~141; bearing faults rise past ~300)
60 temp_rise_warn_mC: i64, // bearing temp-rise ceiling (milli-degC)
61}
62
63struct RotEff {
64 rms_um_s: i64,
65 peak_um_s: i64,
66 crest_x100: i64,
67 temp_rise_mC: i64,
68 verdict: i64,
69}
70
71// Integer square root (Newton). Pure; no float.
72func nx_rot_isqrt(n: i64) -> i64 { return vm_isqrt(n) }
73
74// ---- The analysis --------------------------------------------------
75// vib[i] = instantaneous vibration velocity samples (signed, micro-mm/s).
76// bearing_mC / ambient_mC = bearing + ambient temperature (milli-degC).
77// running = 1 if the machine ran during the window, 0 if stopped.
78func nx_rot_analyze(vib: *i64, n: i64, bearing_mC: i64, ambient_mC: i64,
79 running: i64, spec: *RotSpec, out: *RotEff) -> i64 {
80 out.rms_um_s = 0
81 out.peak_um_s = 0
82 out.crest_x100 = 0
83 out.temp_rise_mC = 0
84 out.verdict = NX_ROT_INSUFFICIENT_DATA
85
86 if n < 1 {
87 out.verdict = NX_ROT_INSUFFICIENT_DATA
88 return out.verdict
89 }
90 if running != 0 {
91 if running != 1 {
92 out.verdict = NX_ROT_BAD_ARG
93 return out.verdict
94 }
95 }
96
97 var ss: i64 = 0
98 var peak: i64 = 0
99 var i: i64 = 0
100 while i < n {
101 var v: i64 = vib[i]
102 if v < 0 { v = 0 - v }
103 if v > peak { peak = v }
104 ss = ss + v * v
105 i = i + 1
106 }
107 let meansq: i64 = ss / n
108 let rms: i64 = nx_rot_isqrt(meansq)
109 out.rms_um_s = rms
110 out.peak_um_s = peak
111 var crest: i64 = 100
112 if rms > 0 { crest = (peak * 100) / rms }
113 out.crest_x100 = crest
114 out.temp_rise_mC = bearing_mC - ambient_mC
115
116 if running == 0 {
117 out.verdict = NX_ROT_IDLE
118 return out.verdict
119 }
120 // severity-ordered: safety (overheat) > specific bearing fault (crest) >
121 // overall roughness (RMS zones).
122 if out.temp_rise_mC > spec.temp_rise_warn_mC {
123 out.verdict = NX_ROT_OVERHEAT
124 return out.verdict
125 }
126 if out.crest_x100 > spec.crest_warn_x100 {
127 out.verdict = NX_ROT_BEARING_WEAR
128 return out.verdict
129 }
130 if out.rms_um_s >= spec.iso_alarm_um_s {
131 out.verdict = NX_ROT_ROUGH_ALARM
132 return out.verdict
133 }
134 if out.rms_um_s >= spec.iso_warn_um_s {
135 out.verdict = NX_ROT_ROUGH_WARNING
136 return out.verdict
137 }
138 out.verdict = NX_ROT_HEALTHY
139 return out.verdict
140}