1use crate::solver::SolverSnapshot;
10
11pub trait Serializable {
16 fn to_json(&self) -> String;
17 fn write_csv_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()>;
18 fn write_table_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()>;
19}
20
21#[derive(Debug, Clone)]
28#[must_use]
29pub struct SolverResult {
30 pub snapshot: SolverSnapshot,
32 pub x_grid: Vec<f64>,
34 pub step_count: usize,
36 pub diag_newton_exhausted: usize,
38 pub warnings: Vec<String>,
42}
43
44impl SolverResult {
45 pub fn to_json(&self) -> String {
50 let s = &self.snapshot;
51 let mut out = String::with_capacity(self.x_grid.len() * 30 + 256);
52 out.push_str("{\"results\":[{");
53 write_json_kv(&mut out, "pde_mu", s.mu);
54 out.push(',');
55 write_json_kv(&mut out, "pde_y", s.y);
56 out.push(',');
57 write_json_kv(&mut out, "drho", s.delta_rho_over_rho);
58 out.push(',');
59 write_json_kv(&mut out, "accumulated_delta_t", s.accumulated_delta_t);
60 out.push(',');
61 write_json_kv(&mut out, "z", s.z);
62 out.push(',');
63 write_json_kv(&mut out, "rho_e", s.rho_e);
64 out.push(',');
65 write_json_kv(&mut out, "step_count", self.step_count as f64);
66 out.push(',');
67 write_json_array(&mut out, "x", &self.x_grid);
68 out.push(',');
69 write_json_array(&mut out, "delta_n", &s.delta_n);
70 out.push_str("}],");
71 write_json_kv(
72 &mut out,
73 "diag_newton_exhausted",
74 self.diag_newton_exhausted as f64,
75 );
76 if !self.warnings.is_empty() {
77 out.push(',');
78 write_json_string_array(&mut out, "warnings", &self.warnings);
79 }
80 out.push('}');
81 out
82 }
83
84 pub fn write_csv<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
86 let s = &self.snapshot;
87 writeln!(
88 w,
89 "# mu={:.6e} y={:.6e} delta_rho_over_rho={:.6e} z={:.1} steps={} newton_exhausted={}",
90 s.mu, s.y, s.delta_rho_over_rho, s.z, self.step_count, self.diag_newton_exhausted
91 )?;
92 for warning in &self.warnings {
93 writeln!(w, "# WARNING: {warning}")?;
94 }
95 writeln!(w, "x,delta_n")?;
96 for (i, &x) in self.x_grid.iter().enumerate() {
97 writeln!(w, "{:.8e},{:.8e}", x, s.delta_n[i])?;
98 }
99 Ok(())
100 }
101
102 pub fn write_table<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
104 let s = &self.snapshot;
105 writeln!(w, "Solver Result")?;
106 writeln!(w, "=============")?;
107 writeln!(w, " z_final = {:.1}", s.z)?;
108 writeln!(w, " mu = {:.6e}", s.mu)?;
109 writeln!(w, " y = {:.6e}", s.y)?;
110 writeln!(w, " delta_rho/rho = {:.6e}", s.delta_rho_over_rho)?;
111 writeln!(w, " rho_e = {:.8}", s.rho_e)?;
112 writeln!(w, " accum_delta_T = {:.6e}", s.accumulated_delta_t)?;
113 writeln!(w, " steps = {}", self.step_count)?;
114 writeln!(w, " grid points = {}", self.x_grid.len())?;
115 writeln!(w, " newton_exhausted = {}", self.diag_newton_exhausted)?;
116 if !self.warnings.is_empty() {
117 writeln!(w, " warnings = {} (see below)", self.warnings.len())?;
118 writeln!(w, "Warnings:")?;
119 for warning in &self.warnings {
120 writeln!(w, " - {warning}")?;
121 }
122 }
123 Ok(())
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct SweepRow {
130 pub z_h: f64,
131 pub snapshot: SolverSnapshot,
132 pub gf_mu: f64,
133 pub gf_y: f64,
134 pub gf_delta_n: Vec<f64>,
135 pub x_grid: Vec<f64>,
136 pub step_count: usize,
137}
138
139#[derive(Debug, Clone)]
141#[must_use]
142pub struct SweepResult {
143 pub delta_rho: f64,
144 pub rows: Vec<SweepRow>,
145 pub warnings: Vec<String>,
147}
148
149impl SweepResult {
150 pub fn to_json(&self) -> String {
152 let per_row = self.rows.first().map_or(4096, |r| {
153 (r.x_grid.len() + r.snapshot.delta_n.len()) * 24 + 256
154 });
155 let mut out = String::with_capacity(self.rows.len() * per_row + 128);
156 out.push('{');
157 write_json_kv(&mut out, "delta_rho_inj", self.delta_rho);
158 out.push_str(",\"results\":[");
159 for (i, row) in self.rows.iter().enumerate() {
160 if i > 0 {
161 out.push(',');
162 }
163 out.push('{');
164 write_json_kv(&mut out, "z_h", row.z_h);
165 out.push(',');
166 write_json_kv(&mut out, "pde_mu", row.snapshot.mu);
167 out.push(',');
168 write_json_kv(&mut out, "gf_mu", row.gf_mu);
169 out.push(',');
170 write_json_kv(&mut out, "pde_y", row.snapshot.y);
171 out.push(',');
172 write_json_kv(&mut out, "gf_y", row.gf_y);
173 out.push(',');
174 write_json_kv(&mut out, "drho", row.snapshot.delta_rho_over_rho);
175 out.push(',');
176 write_json_kv(
177 &mut out,
178 "accumulated_delta_t",
179 row.snapshot.accumulated_delta_t,
180 );
181 out.push(',');
182 write_json_array(&mut out, "x", &row.x_grid);
183 out.push(',');
184 write_json_array(&mut out, "delta_n", &row.snapshot.delta_n);
185 out.push(',');
186 write_json_array(&mut out, "delta_n_gf", &row.gf_delta_n);
187 out.push('}');
188 }
189 out.push(']');
190 if !self.warnings.is_empty() {
191 out.push(',');
192 write_json_string_array(&mut out, "warnings", &self.warnings);
193 }
194 out.push('}');
195 out
196 }
197
198 pub fn write_csv<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
200 for warning in &self.warnings {
201 writeln!(w, "# WARNING: {warning}")?;
202 }
203 writeln!(w, "z_h,pde_mu,gf_mu,pde_y,gf_y,drho,steps")?;
204 for row in &self.rows {
205 writeln!(
206 w,
207 "{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{}",
208 row.z_h,
209 row.snapshot.mu,
210 row.gf_mu,
211 row.snapshot.y,
212 row.gf_y,
213 row.snapshot.delta_rho_over_rho,
214 row.step_count,
215 )?;
216 }
217 Ok(())
218 }
219
220 pub fn write_table<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
222 writeln!(
223 w,
224 "{:<10} {:>10} {:>10} {:>10} {:>10} {:>10} {:>10}",
225 "z_inj", "PDE_mu", "GF_mu", "PDE_y", "GF_y", "PDE_drho", "steps"
226 )?;
227 writeln!(w, "{}", "-".repeat(75))?;
228 for row in &self.rows {
229 writeln!(
230 w,
231 "{:<10.0e} {:>10.3e} {:>10.3e} {:>10.3e} {:>10.3e} {:>10.3e} {:>10}",
232 row.z_h,
233 row.snapshot.mu,
234 row.gf_mu,
235 row.snapshot.y,
236 row.gf_y,
237 row.snapshot.delta_rho_over_rho,
238 row.step_count,
239 )?;
240 }
241 if !self.warnings.is_empty() {
242 writeln!(w, "Warnings ({}):", self.warnings.len())?;
243 for warning in &self.warnings {
244 writeln!(w, " - {warning}")?;
245 }
246 }
247 Ok(())
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct PhotonSweepRow {
254 pub z_h: f64,
255 pub snapshot: SolverSnapshot,
256 pub x_grid: Vec<f64>,
257 pub step_count: usize,
258}
259
260#[derive(Debug, Clone)]
262#[must_use]
263pub struct PhotonSweepResult {
264 pub x_inj: f64,
265 pub delta_n_over_n: f64,
266 pub rows: Vec<PhotonSweepRow>,
267 pub warnings: Vec<String>,
269}
270
271impl PhotonSweepResult {
272 pub fn to_json(&self) -> String {
274 let per_row = self.rows.first().map_or(4096, |r| {
275 (r.x_grid.len() + r.snapshot.delta_n.len()) * 24 + 256
276 });
277 let mut out = String::with_capacity(self.rows.len() * per_row + 128);
278 out.push('{');
279 write_json_kv(&mut out, "x_inj", self.x_inj);
280 out.push(',');
281 write_json_kv(&mut out, "delta_n_over_n", self.delta_n_over_n);
282 out.push_str(",\"results\":[");
283 for (i, row) in self.rows.iter().enumerate() {
284 if i > 0 {
285 out.push(',');
286 }
287 out.push('{');
288 write_json_kv(&mut out, "z_h", row.z_h);
289 out.push(',');
290 write_json_kv(&mut out, "pde_mu", row.snapshot.mu);
291 out.push(',');
292 write_json_kv(&mut out, "pde_y", row.snapshot.y);
293 out.push(',');
294 write_json_kv(&mut out, "drho", row.snapshot.delta_rho_over_rho);
295 out.push(',');
296 write_json_kv(
297 &mut out,
298 "accumulated_delta_t",
299 row.snapshot.accumulated_delta_t,
300 );
301 out.push(',');
302 write_json_array(&mut out, "x", &row.x_grid);
303 out.push(',');
304 write_json_array(&mut out, "delta_n", &row.snapshot.delta_n);
305 out.push('}');
306 }
307 out.push(']');
308 if !self.warnings.is_empty() {
309 out.push(',');
310 write_json_string_array(&mut out, "warnings", &self.warnings);
311 }
312 out.push('}');
313 out
314 }
315
316 pub fn write_csv<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
318 for warning in &self.warnings {
319 writeln!(w, "# WARNING: {warning}")?;
320 }
321 writeln!(w, "z_h,pde_mu,pde_y,drho,steps")?;
322 for row in &self.rows {
323 writeln!(
324 w,
325 "{:.6e},{:.6e},{:.6e},{:.6e},{}",
326 row.z_h,
327 row.snapshot.mu,
328 row.snapshot.y,
329 row.snapshot.delta_rho_over_rho,
330 row.step_count,
331 )?;
332 }
333 Ok(())
334 }
335
336 pub fn write_table<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
338 writeln!(
339 w,
340 "Photon sweep: x_inj={:.4e}, delta_n_over_n={:.4e}",
341 self.x_inj, self.delta_n_over_n
342 )?;
343 writeln!(
344 w,
345 "{:<10} {:>10} {:>10} {:>10} {:>10}",
346 "z_inj", "PDE_mu", "PDE_y", "PDE_drho", "steps"
347 )?;
348 writeln!(w, "{}", "-".repeat(55))?;
349 for row in &self.rows {
350 writeln!(
351 w,
352 "{:<10.0e} {:>10.3e} {:>10.3e} {:>10.3e} {:>10}",
353 row.z_h,
354 row.snapshot.mu,
355 row.snapshot.y,
356 row.snapshot.delta_rho_over_rho,
357 row.step_count,
358 )?;
359 }
360 if !self.warnings.is_empty() {
361 writeln!(w, "Warnings ({}):", self.warnings.len())?;
362 for warning in &self.warnings {
363 writeln!(w, " - {warning}")?;
364 }
365 }
366 Ok(())
367 }
368}
369
370#[derive(Debug, Clone)]
374#[must_use]
375pub struct PhotonSweepBatchResult {
376 pub results: Vec<PhotonSweepResult>,
377 pub warnings: Vec<String>,
379}
380
381impl PhotonSweepBatchResult {
382 pub fn to_json(&self) -> String {
387 let mut out = String::with_capacity(self.results.len() * 4096);
388 out.push_str("{\"results\":[");
389 for (i, r) in self.results.iter().enumerate() {
390 if i > 0 {
391 out.push(',');
392 }
393 out.push_str(&r.to_json());
394 }
395 out.push(']');
396 if !self.warnings.is_empty() {
397 out.push(',');
398 write_json_string_array(&mut out, "warnings", &self.warnings);
399 }
400 out.push('}');
401 out
402 }
403
404 pub fn write_csv<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
406 for warning in &self.warnings {
407 writeln!(w, "# WARNING: {warning}")?;
408 }
409 writeln!(w, "x_inj,z_h,pde_mu,pde_y,drho,steps")?;
410 for r in &self.results {
411 for row in &r.rows {
412 writeln!(
413 w,
414 "{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{}",
415 r.x_inj,
416 row.z_h,
417 row.snapshot.mu,
418 row.snapshot.y,
419 row.snapshot.delta_rho_over_rho,
420 row.step_count,
421 )?;
422 }
423 }
424 Ok(())
425 }
426
427 pub fn write_table<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
429 writeln!(w, "Photon sweep batch: {} x_inj values", self.results.len())?;
430 for r in &self.results {
431 r.write_table(w)?;
432 writeln!(w)?;
433 }
434 if !self.warnings.is_empty() {
435 writeln!(w, "Aggregated warnings ({}):", self.warnings.len())?;
436 for warning in &self.warnings {
437 writeln!(w, " - {warning}")?;
438 }
439 }
440 Ok(())
441 }
442}
443
444#[derive(Debug, Clone)]
446#[must_use]
447pub struct GreensResult {
448 pub z_h: f64,
449 pub mu: f64,
450 pub y: f64,
451 pub x_grid: Vec<f64>,
452 pub delta_n: Vec<f64>,
453 pub warnings: Vec<String>,
455}
456
457impl GreensResult {
458 pub fn to_json(&self) -> String {
460 let mut out = String::with_capacity(self.x_grid.len() * 30 + 256);
461 use std::fmt::Write;
462 write!(out, "{{\"results\":[{{").unwrap();
463 write_json_kv(&mut out, "z_h", self.z_h);
464 out.push(',');
465 write_json_kv(&mut out, "gf_mu", self.mu);
466 out.push(',');
467 write_json_kv(&mut out, "gf_y", self.y);
468 out.push(',');
469 write_json_array(&mut out, "x", &self.x_grid);
470 out.push(',');
471 write_json_array(&mut out, "delta_n_gf", &self.delta_n);
472 out.push_str("}]");
475 if !self.warnings.is_empty() {
476 out.push(',');
477 write_json_string_array(&mut out, "warnings", &self.warnings);
478 }
479 out.push('}');
480 out
481 }
482
483 pub fn write_csv<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
485 writeln!(
486 w,
487 "# mu={:.6e} y={:.6e} z_h={:.2e}",
488 self.mu, self.y, self.z_h
489 )?;
490 for warning in &self.warnings {
491 writeln!(w, "# WARNING: {warning}")?;
492 }
493 writeln!(w, "x,delta_n")?;
494 for (i, &x) in self.x_grid.iter().enumerate() {
495 writeln!(w, "{:.8e},{:.8e}", x, self.delta_n[i])?;
496 }
497 Ok(())
498 }
499
500 pub fn write_table<W: std::io::Write + ?Sized>(&self, w: &mut W) -> std::io::Result<()> {
502 writeln!(w, "Green's function result at z_h = {:.2e}:", self.z_h)?;
503 writeln!(w, " mu = {:.6e}", self.mu)?;
504 writeln!(w, " y = {:.6e}", self.y)?;
505 if !self.warnings.is_empty() {
506 writeln!(w, "Warnings ({}):", self.warnings.len())?;
507 for warning in &self.warnings {
508 writeln!(w, " - {warning}")?;
509 }
510 }
511 Ok(())
512 }
513}
514
515impl Serializable for SolverResult {
518 fn to_json(&self) -> String {
519 self.to_json()
520 }
521 fn write_csv_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
522 self.write_csv(w)
523 }
524 fn write_table_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
525 self.write_table(w)
526 }
527}
528
529impl Serializable for SweepResult {
530 fn to_json(&self) -> String {
531 self.to_json()
532 }
533 fn write_csv_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
534 self.write_csv(w)
535 }
536 fn write_table_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
537 self.write_table(w)
538 }
539}
540
541impl Serializable for PhotonSweepResult {
542 fn to_json(&self) -> String {
543 self.to_json()
544 }
545 fn write_csv_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
546 self.write_csv(w)
547 }
548 fn write_table_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
549 self.write_table(w)
550 }
551}
552
553impl Serializable for PhotonSweepBatchResult {
554 fn to_json(&self) -> String {
555 self.to_json()
556 }
557 fn write_csv_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
558 self.write_csv(w)
559 }
560 fn write_table_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
561 self.write_table(w)
562 }
563}
564
565impl Serializable for GreensResult {
566 fn to_json(&self) -> String {
567 self.to_json()
568 }
569 fn write_csv_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
570 self.write_csv(w)
571 }
572 fn write_table_dyn(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
573 self.write_table(w)
574 }
575}
576
577fn write_json_float(out: &mut String, val: f64, precision: usize) {
579 use std::fmt::Write;
580 if val.is_finite() {
581 write!(out, "{val:.prec$e}", prec = precision).unwrap();
582 } else {
583 out.push_str("null");
584 }
585}
586
587fn write_json_kv(out: &mut String, key: &str, val: f64) {
588 use std::fmt::Write;
589 write!(out, "\"{key}\":").unwrap();
590 write_json_float(out, val, 15);
591}
592
593fn write_json_array(out: &mut String, key: &str, arr: &[f64]) {
594 use std::fmt::Write;
595 write!(out, "\"{key}\":[").unwrap();
596 for (i, &v) in arr.iter().enumerate() {
597 if i > 0 {
598 out.push(',');
599 }
600 write_json_float(out, v, 15);
601 }
602 out.push(']');
603}
604
605fn write_json_string_array(out: &mut String, key: &str, arr: &[String]) {
607 use std::fmt::Write;
608 write!(out, "\"{key}\":[").unwrap();
609 for (i, s) in arr.iter().enumerate() {
610 if i > 0 {
611 out.push(',');
612 }
613 out.push('"');
614 for c in s.chars() {
615 match c {
616 '"' => out.push_str("\\\""),
617 '\\' => out.push_str("\\\\"),
618 '\n' => out.push_str("\\n"),
619 '\r' => out.push_str("\\r"),
620 '\t' => out.push_str("\\t"),
621 c if (c as u32) < 0x20 => {
622 write!(out, "\\u{:04x}", c as u32).unwrap();
623 }
624 c => out.push(c),
625 }
626 }
627 out.push('"');
628 }
629 out.push(']');
630}
631
632#[derive(Debug, Clone, Copy, PartialEq, Eq)]
634pub enum OutputFormat {
635 Json,
636 Csv,
637 Table,
638}
639
640impl OutputFormat {
641 pub fn from_str(s: &str) -> Result<Self, String> {
642 match s {
643 "json" => Ok(OutputFormat::Json),
644 "csv" => Ok(OutputFormat::Csv),
645 "table" => Ok(OutputFormat::Table),
646 _ => Err(format!(
647 "Unknown output format: '{s}'. Valid: json, csv, table"
648 )),
649 }
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 fn sample_result() -> SolverResult {
658 SolverResult {
659 snapshot: SolverSnapshot {
660 z: 100.0,
661 delta_n: vec![1e-6, 2e-6, -3e-6],
662 rho_e: 1.000_01,
663 mu: 5.0e-7,
664 y: 1.0e-7,
665 delta_rho_over_rho: 1.0e-5,
666 accumulated_delta_t: 2.0e-10,
667 },
668 x_grid: vec![0.1, 1.0, 10.0],
669 step_count: 42,
670 diag_newton_exhausted: 0,
671 warnings: Vec::new(),
672 }
673 }
674
675 #[test]
676 fn test_output_format_from_str() {
677 assert_eq!(OutputFormat::from_str("json").unwrap(), OutputFormat::Json);
678 assert_eq!(OutputFormat::from_str("csv").unwrap(), OutputFormat::Csv);
679 assert_eq!(
680 OutputFormat::from_str("table").unwrap(),
681 OutputFormat::Table
682 );
683 assert!(OutputFormat::from_str("xml").is_err());
684 assert!(OutputFormat::from_str("").is_err());
685 }
686
687 fn sample_sweep_result() -> SweepResult {
688 SweepResult {
689 delta_rho: 1e-5,
690 warnings: Vec::new(),
691 rows: vec![
692 SweepRow {
693 z_h: 1e4,
694 snapshot: SolverSnapshot {
695 z: 500.0,
696 delta_n: vec![1e-8, 2e-8],
697 rho_e: 1.0,
698 mu: 1e-10,
699 y: 2.5e-6,
700 delta_rho_over_rho: 1e-5,
701 accumulated_delta_t: 0.0,
702 },
703 gf_mu: 1.1e-10,
704 gf_y: 2.4e-6,
705 gf_delta_n: vec![1.1e-8, 1.9e-8],
706 x_grid: vec![1.0, 10.0],
707 step_count: 100,
708 },
709 SweepRow {
710 z_h: 2e5,
711 snapshot: SolverSnapshot {
712 z: 500.0,
713 delta_n: vec![3e-6, -1e-6],
714 rho_e: 1.000_01,
715 mu: 1.4e-5,
716 y: 1e-8,
717 delta_rho_over_rho: 1e-5,
718 accumulated_delta_t: 1e-11,
719 },
720 gf_mu: 1.38e-5,
721 gf_y: 1.1e-8,
722 gf_delta_n: vec![2.9e-6, -0.9e-6],
723 x_grid: vec![1.0, 10.0],
724 step_count: 500,
725 },
726 ],
727 }
728 }
729
730 fn json_is_balanced(s: &str) -> bool {
735 let mut in_str = false;
736 let mut escape = false;
737 let mut curly: i32 = 0;
738 let mut square: i32 = 0;
739 for c in s.chars() {
740 if escape {
741 escape = false;
742 continue;
743 }
744 if in_str {
745 match c {
746 '\\' => escape = true,
747 '"' => in_str = false,
748 _ => {}
749 }
750 continue;
751 }
752 match c {
753 '"' => in_str = true,
754 '{' => curly += 1,
755 '}' => curly -= 1,
756 '[' => square += 1,
757 ']' => square -= 1,
758 _ => {}
759 }
760 if curly < 0 || square < 0 {
761 return false;
762 }
763 }
764 curly == 0 && square == 0 && !in_str
765 }
766
767 #[test]
768 fn test_json_bracket_balance_all_variants() {
769 assert!(
771 json_is_balanced(&sample_result().to_json()),
772 "SolverResult JSON has unbalanced brackets"
773 );
774 assert!(
776 json_is_balanced(&sample_sweep_result().to_json()),
777 "SweepResult JSON has unbalanced brackets"
778 );
779 let g = GreensResult {
781 z_h: 2e5,
782 mu: 1.4e-5,
783 y: 1e-8,
784 x_grid: vec![1.0, 10.0],
785 delta_n: vec![3e-6, -1e-6],
786 warnings: Vec::new(),
787 };
788 assert!(
789 json_is_balanced(&g.to_json()),
790 "GreensResult JSON has unbalanced brackets"
791 );
792 }
793}