nx_stress.nx source
↩ module page · 171 lines · 7088 B
1// nx_stress.nx -- mechanical stress primitive (σ = F/A in Pascals).
2//
3// THREE-WAY cross-domain composition demo per
4// [[feedback-engineering-sciences-bits-up-3d-print-first]]:
5// nx_force (force input, Layer 1 physics)
6// <area-source> (polygon/mesh-derived, Layer 3 geometry)
7// material-strength (polymer literature, Layer 2 chemistry-derived)
8// → stress classification (Layer 3 engineering output)
9//
10// Unit convention: integer Pascals (N/m²). Engineering yield
11// strengths are MPa range (10^6 Pa); ultimate tensile up to GPa
12// (10^9 Pa). i64 Pa range = ±9.2 × 10^18 Pa = ±9.2 × 10^12 GPa,
13// vastly exceeding any engineering analysis.
14//
15// Algebra: σ_Pa = F_N / A_m². Both Q14 inputs scale identically,
16// so σ_Pa = F_q14 / A_q14 (Q14 scaling cancels in the ratio).
17// Integer result, no rescaling.
18//
19// Material yield strengths (MPa) verified against polymer / FDM
20// literature consensus:
21// PLA: yield 50 / ultimate 65
22// PETG: yield 45 / ultimate 50
23// ABS: yield 35 / ultimate 45
24// ASA: yield 35 / ultimate 45
25// PC: yield 65 / ultimate 70
26// TPU: yield 30 / ultimate 40 (highly elastic; values for TPU 95A)
27// NYLON: yield 60 / ultimate 80
28// PA-CF: yield 80 / ultimate 120 (carbon-fibre reinforced)
29// PEEK: yield 95 / ultimate 100
30//
31// Per cardinal feedback-nishilang-multi-line-binary-op-statement-break:
32// every multi-line arithmetic uses named temporaries throughout.
33//
34// license_tier: ORIGINAL
35
36import "nx_syscalls.nx"
37import "nx_material_profile.nx"
38import "nx_abs.nx"
39const NX_MAGIC_1000000: i64 = 1000000
40const NX_MAGIC_50000000: i64 = 50000000
41const NX_MAGIC_45000000: i64 = 45000000
42const NX_MAGIC_35000000: i64 = 35000000
43const NX_MAGIC_65000000: i64 = 65000000
44const NX_MAGIC_30000000: i64 = 30000000
45const NX_MAGIC_60000000: i64 = 60000000
46const NX_MAGIC_80000000: i64 = 80000000
47const NX_MAGIC_95000000: i64 = 95000000
48const NX_MAGIC_70000000: i64 = 70000000
49const NX_MAGIC_40000000: i64 = 40000000
50const NX_MAGIC_120000000: i64 = 120000000
51const NX_MAGIC_100000000: i64 = 100000000
52
53// ===== sealed-enum verdicts ========================================
54
55const NX_STRESS_SAFE: i64 = 0 // < yield / 2 -- ample margin
56const NX_STRESS_WORKING: i64 = 1 // [yield/2, yield) -- design range
57const NX_STRESS_YIELDED: i64 = 2 // [yield, ultimate) -- permanent deformation
58const NX_STRESS_FRACTURED: i64 = 3 // >= ultimate -- catastrophic failure
59const NX_STRESS_UNKNOWN_MAT: i64 = 4 // material class not in registry
60const NX_STRESS_N: i64 = 5
61
62func nx_stress_verdict_name(v: i64) -> *u8 {
63 if v == NX_STRESS_SAFE { return "SAFE" }
64 if v == NX_STRESS_WORKING { return "WORKING" }
65 if v == NX_STRESS_YIELDED { return "YIELDED" }
66 if v == NX_STRESS_FRACTURED { return "FRACTURED" }
67 if v == NX_STRESS_UNKNOWN_MAT { return "UNKNOWN_MAT" }
68 return "UNKNOWN"
69}
70
71// ===== struct ======================================================
72
73struct NxStress {
74 pa: i64, // Pascals (N/m²)
75}
76
77const NX_STRESS_BYTES: i64 = 8
78
79// ===== constructors ================================================
80
81func nx_stress_new(pa: i64) -> *NxStress {
82 let s: *NxStress = (sys_mmap(NX_STRESS_BYTES)) as *NxStress
83 s.pa = pa
84 return s
85}
86
87func nx_stress_zero() -> *NxStress {
88 return nx_stress_new(0)
89}
90
91// ===== composition: F/A ============================================
92//
93// Pure formula: σ = F/A. Both inputs Q14 (force in Q14 N, area in
94// Q14 m²); Q14 scaling cancels in the ratio so result is plain Pa.
95//
96// Defensive: zero area returns NULL (operator must check) -- this
97// matches the engineering reality that "stress on zero area" is
98// undefined (would be infinite).
99
100func nx_stress_from_force_area(force_n_q14: i64, area_m2_q14: i64) -> *NxStress {
101 if area_m2_q14 <= 0 { return 0 as *NxStress }
102 let pa: i64 = force_n_q14 / area_m2_q14
103 return nx_stress_new(pa)
104}
105
106// ===== MPa conversion for human display ============================
107
108func nx_stress_to_mpa(s: *NxStress) -> i64 {
109 return s.pa / NX_MAGIC_1000000
110}
111
112func nx_stress_to_kpa(s: *NxStress) -> i64 {
113 return s.pa / 1000
114}
115
116// ===== material strength lookup ====================================
117//
118// Yield strength in Pa for a given polymer class. Returns 0 for
119// unknown class. Caller can detect unknown via the 0 return.
120
121func nx_stress_yield_pa(material_class: i64) -> i64 {
122 if material_class == NX_MAT_PLA { return NX_MAGIC_50000000 }
123 if material_class == NX_MAT_PETG { return NX_MAGIC_45000000 }
124 if material_class == NX_MAT_ABS { return NX_MAGIC_35000000 }
125 if material_class == NX_MAT_ASA { return NX_MAGIC_35000000 }
126 if material_class == NX_MAT_PC { return NX_MAGIC_65000000 }
127 if material_class == NX_MAT_TPU { return NX_MAGIC_30000000 }
128 if material_class == NX_MAT_NYLON { return NX_MAGIC_60000000 }
129 if material_class == NX_MAT_PA_CF { return NX_MAGIC_80000000 }
130 if material_class == NX_MAT_PEEK { return NX_MAGIC_95000000 }
131 return 0
132}
133
134func nx_stress_ultimate_pa(material_class: i64) -> i64 {
135 if material_class == NX_MAT_PLA { return NX_MAGIC_65000000 }
136 if material_class == NX_MAT_PETG { return NX_MAGIC_50000000 }
137 if material_class == NX_MAT_ABS { return NX_MAGIC_45000000 }
138 if material_class == NX_MAT_ASA { return NX_MAGIC_45000000 }
139 if material_class == NX_MAT_PC { return NX_MAGIC_70000000 }
140 if material_class == NX_MAT_TPU { return NX_MAGIC_40000000 }
141 if material_class == NX_MAT_NYLON { return NX_MAGIC_80000000 }
142 if material_class == NX_MAT_PA_CF { return NX_MAGIC_120000000 }
143 if material_class == NX_MAT_PEEK { return NX_MAGIC_100000000 }
144 return 0
145}
146
147// ===== classifier ==================================================
148//
149// Maps a stress value against the material's yield/ultimate strengths
150// to a verdict:
151// SAFE : stress < yield / 2 (engineering safety factor of 2)
152// WORKING : yield/2 <= stress < yield
153// YIELDED : yield <= stress < ultimate (permanent deformation)
154// FRACTURED : stress >= ultimate (catastrophic failure)
155// UNKNOWN_MAT: material class not in strength registry
156
157func nx_stress_classify(s: *NxStress, material_class: i64) -> i64 {
158 let yield_pa: i64 = nx_stress_yield_pa(material_class)
159 let ult_pa: i64 = nx_stress_ultimate_pa(material_class)
160 if yield_pa <= 0 { return NX_STRESS_UNKNOWN_MAT }
161 if ult_pa <= 0 { return NX_STRESS_UNKNOWN_MAT }
162 // Compose nx_abs (already shipped) instead of inline `if x < 0`
163 // pattern. Per the four-pillar analysis: let-immutability is
164 // intentional design (Rust/Swift pattern), so the right path is
165 // composing existing pure helpers, NOT reaching for var on reflex.
166 let abs_pa: i64 = nx_abs(s.pa)
167 if abs_pa >= ult_pa { return NX_STRESS_FRACTURED }
168 if abs_pa >= yield_pa { return NX_STRESS_YIELDED }
169 if abs_pa >= (yield_pa / 2) { return NX_STRESS_WORKING }
170 return NX_STRESS_SAFE
171}