1#![allow(dead_code)]
29
30use std::time::{Duration, Instant};
31
32use crate::simd_paths::{
33 SoaMaterialPoints, elastic_stress_batch, elastic_stress_batch_x4, miner_damage_batch,
34 neo_hookean_stress_batch, return_mapping_batch, thermal_expansion_stress_batch,
35 viscoplastic_rate_batch, von_mises_yield_batch,
36};
37
38#[derive(Debug, Clone)]
42pub struct MatBenchReport {
43 pub name: String,
45 pub iterations: u32,
47 pub n: usize,
49 pub total: Duration,
51 pub mean: Duration,
53 pub mflops: Option<f64>,
55 pub mpoints_per_sec: f64,
57}
58
59impl std::fmt::Display for MatBenchReport {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(
62 f,
63 "[{:<28}] n={:>7} mean={:.2}µs {:.1} Mpts/s",
64 self.name,
65 self.n,
66 self.mean.as_secs_f64() * 1e6,
67 self.mpoints_per_sec
68 )?;
69 if let Some(mf) = self.mflops {
70 write!(f, " ({:.1} MFLOPs)", mf)?;
71 }
72 Ok(())
73 }
74}
75
76pub struct MatBenchHarness {
80 pub warmup: u32,
82 pub iterations: u32,
84 pub reports: Vec<MatBenchReport>,
86}
87
88impl MatBenchHarness {
89 pub fn new() -> Self {
91 Self {
92 warmup: 2,
93 iterations: 5,
94 reports: Vec::new(),
95 }
96 }
97
98 pub fn fast() -> Self {
100 Self {
101 warmup: 1,
102 iterations: 3,
103 reports: Vec::new(),
104 }
105 }
106
107 pub fn run<F>(&mut self, name: &str, mut f: F) -> MatBenchReport
109 where
110 F: FnMut() -> MatBenchResult,
111 {
112 let mut last = MatBenchResult::default();
113 for _ in 0..self.warmup {
114 last = f();
115 }
116
117 let t0 = Instant::now();
118 for _ in 0..self.iterations {
119 last = f();
120 }
121 let total = t0.elapsed();
122 let mean = total / self.iterations;
123
124 let sec = mean.as_secs_f64().max(1e-12);
125 let mpoints = last.n as f64 / sec / 1e6;
126
127 let r = MatBenchReport {
128 name: name.to_string(),
129 iterations: self.iterations,
130 n: last.n,
131 total,
132 mean,
133 mflops: last.mflops,
134 mpoints_per_sec: mpoints,
135 };
136 self.reports.push(r.clone());
137 r
138 }
139
140 pub fn print_summary(&self) {
142 println!("\n{:=<80}", "");
143 println!(
144 "{:<28} {:>8} {:>12} {:>12} {:>10}",
145 "Kernel", "N", "Mean (µs)", "Mpts/s", "MFLOPs"
146 );
147 println!("{:=<80}", "");
148 for r in &self.reports {
149 let mf = r.mflops.map_or("—".to_string(), |m| format!("{:.1}", m));
150 println!(
151 "{:<28} {:>8} {:>12.3} {:>12.1} {:>10}",
152 r.name,
153 r.n,
154 r.mean.as_secs_f64() * 1e6,
155 r.mpoints_per_sec,
156 mf
157 );
158 }
159 println!("{:=<80}", "");
160 }
161}
162
163impl Default for MatBenchHarness {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169#[derive(Debug, Default, Clone)]
173pub struct MatBenchResult {
174 pub n: usize,
176 pub mflops: Option<f64>,
178}
179
180pub fn bench_elastic(n: usize) -> MatBenchResult {
186 let mut pts = SoaMaterialPoints::new(n);
187 let e = 210e9_f64;
188 let nu = 0.3_f64;
189 pts.strain_xx.fill(1e-3);
191 elastic_stress_batch(&mut pts, e, nu);
192 let flops = 18.0 * n as f64;
193 MatBenchResult {
194 n,
195 mflops: Some(flops / 1e6),
196 }
197}
198
199pub fn bench_elastic_x4(n: usize) -> MatBenchResult {
203 let mut pts = SoaMaterialPoints::new(n);
204 let e = 210e9_f64;
205 let nu = 0.3_f64;
206 pts.strain_xx.fill(1e-3);
207 elastic_stress_batch_x4(&mut pts, e, nu);
208 let flops = 18.0 * n as f64;
209 MatBenchResult {
210 n,
211 mflops: Some(flops / 1e6),
212 }
213}
214
215pub fn bench_von_mises(n: usize) -> MatBenchResult {
219 let mut pts = SoaMaterialPoints::new(n);
220 pts.stress_xx.fill(300e6);
222 pts.stress_yy.fill(-50e6);
223 let _f = von_mises_yield_batch(&pts);
224 let flops = 10.0 * n as f64;
225 MatBenchResult {
226 n,
227 mflops: Some(flops / 1e6),
228 }
229}
230
231pub fn bench_neo_hookean(n: usize) -> MatBenchResult {
235 let mut pts = SoaMaterialPoints::new(n);
236 let e = 210e9_f64;
237 let nu = 0.3_f64;
238 for i in 0..n {
240 pts.f11[i] = 1.001;
241 pts.f22[i] = 0.999;
242 }
243 neo_hookean_stress_batch(&mut pts, e, nu);
244 let flops = 50.0 * n as f64;
245 MatBenchResult {
246 n,
247 mflops: Some(flops / 1e6),
248 }
249}
250
251pub fn bench_viscoplastic(n: usize) -> MatBenchResult {
255 let mut pts = SoaMaterialPoints::new(n);
256 pts.stress_xx.fill(400e6); let yield_vals = von_mises_yield_batch(&pts);
259 let _rates = viscoplastic_rate_batch(&pts, &yield_vals, 1.0, 1e6);
260 let flops = 12.0 * n as f64;
261 MatBenchResult {
262 n,
263 mflops: Some(flops / 1e6),
264 }
265}
266
267pub fn bench_fatigue(n: usize) -> MatBenchResult {
271 let mut pts = SoaMaterialPoints::new(n);
272 pts.stress_xx.fill(300e6);
273 miner_damage_batch(&mut pts, 500e6, -0.1, 1000.0);
275 let flops = 5.0 * n as f64;
276 MatBenchResult {
277 n,
278 mflops: Some(flops / 1e6),
279 }
280}
281
282pub fn bench_thermal_expansion(n: usize) -> MatBenchResult {
286 let mut pts = SoaMaterialPoints::new(n);
287 for i in 0..n {
288 pts.temperature[i] = 400.0;
289 } thermal_expansion_stress_batch(&mut pts, 210e9, 0.3, 12e-6, 293.15);
291 let flops = 8.0 * n as f64;
292 MatBenchResult {
293 n,
294 mflops: Some(flops / 1e6),
295 }
296}
297
298pub fn bench_return_mapping(n: usize) -> MatBenchResult {
302 let mut pts = SoaMaterialPoints::new(n);
303 pts.stress_xx.fill(500e6); return_mapping_batch(&mut pts, 210e9, 0.3, 1e9);
306 let flops = 30.0 * n as f64;
307 MatBenchResult {
308 n,
309 mflops: Some(flops / 1e6),
310 }
311}
312
313pub fn run_mat_bench_suite(verbose: bool) -> String {
323 let mut h = MatBenchHarness::fast();
324
325 let sizes = [1_000usize, 10_000, 100_000];
326
327 for &n in &sizes {
328 h.run(&format!("elastic_n{}", n), || bench_elastic(n));
329 h.run(&format!("elastic_x4_n{}", n), || bench_elastic_x4(n));
330 h.run(&format!("von_mises_n{}", n), || bench_von_mises(n));
331 h.run(&format!("neo_hookean_n{}", n), || bench_neo_hookean(n));
332 h.run(&format!("viscoplastic_n{}", n), || bench_viscoplastic(n));
333 h.run(&format!("fatigue_n{}", n), || bench_fatigue(n));
334 h.run(&format!("thermal_exp_n{}", n), || {
335 bench_thermal_expansion(n)
336 });
337 h.run(&format!("return_map_n{}", n), || bench_return_mapping(n));
338 }
339
340 let mut out = String::new();
341 if verbose {
342 for r in &h.reports {
343 out.push_str(&format!("{}\n", r));
344 }
345 } else {
346 out = format!("{} material benchmarks completed", h.reports.len());
347 }
348 out
349}
350
351#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_bench_elastic_runs() {
359 let r = bench_elastic(100);
360 assert_eq!(r.n, 100);
361 assert!(r.mflops.is_some());
362 }
363
364 #[test]
365 fn test_bench_elastic_x4_runs() {
366 let r = bench_elastic_x4(100);
367 assert_eq!(r.n, 100);
368 }
369
370 #[test]
371 fn test_bench_von_mises_runs() {
372 let r = bench_von_mises(100);
373 assert_eq!(r.n, 100);
374 }
375
376 #[test]
377 fn test_bench_neo_hookean_runs() {
378 let r = bench_neo_hookean(100);
379 assert_eq!(r.n, 100);
380 }
381
382 #[test]
383 fn test_bench_viscoplastic_runs() {
384 let r = bench_viscoplastic(100);
385 assert_eq!(r.n, 100);
386 }
387
388 #[test]
389 fn test_bench_fatigue_runs() {
390 let r = bench_fatigue(100);
391 assert_eq!(r.n, 100);
392 }
393
394 #[test]
395 fn test_bench_thermal_expansion_runs() {
396 let r = bench_thermal_expansion(100);
397 assert_eq!(r.n, 100);
398 }
399
400 #[test]
401 fn test_bench_return_mapping_runs() {
402 let r = bench_return_mapping(100);
403 assert_eq!(r.n, 100);
404 }
405
406 #[test]
407 fn test_harness_collects_reports() {
408 let mut h = MatBenchHarness {
409 warmup: 0,
410 iterations: 2,
411 reports: Vec::new(),
412 };
413 h.run("el_100", || bench_elastic(100));
414 h.run("vm_100", || bench_von_mises(100));
415 assert_eq!(h.reports.len(), 2);
416 }
417
418 #[test]
419 fn test_run_mat_bench_suite_compact() {
420 let s = run_mat_bench_suite(false);
421 assert!(s.contains("material benchmarks"), "output: {}", s);
422 }
423
424 #[test]
425 fn test_run_mat_bench_suite_verbose() {
426 let s = run_mat_bench_suite(true);
427 assert!(s.contains("elastic"), "output: {}", s);
428 }
429}