-
Notifications
You must be signed in to change notification settings - Fork 142
Expand file tree
/
Copy pathcase_validator.py
More file actions
2225 lines (1856 loc) · 111 KB
/
case_validator.py
File metadata and controls
2225 lines (1856 loc) · 111 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
MFC Case Parameter Constraint Validator
Validates inter-parameter dependencies and constraints before running
the Fortran codes. This catches configuration errors early and provides
user-friendly error messages.
Based on the constraints enforced in:
- src/common/m_checker_common.fpp
- src/pre_process/m_checker.fpp
- src/simulation/m_checker.fpp
- src/post_process/m_checker.fpp
"""
# Justification: Comprehensive validator covering all MFC parameter constraints
import re
from functools import lru_cache
from typing import Any, Dict, List, Set
from .common import MFCException
from .params.definitions import CONSTRAINTS
from .params.namelist_parser import get_fortran_constants
from .state import CFG
# Physics documentation for check methods.
# Each entry maps a check method name to metadata used by gen_physics_docs.py
# to auto-generate docs/documentation/physics_constraints.md.
# See the contributing guide for how to add entries.
PHYSICS_DOCS = {
# Thermodynamic Constraints
"check_stiffened_eos": {
"title": "Stiffened EOS Positivity",
"category": "Thermodynamic Constraints",
"math": r"\Gamma > 0, \quad \Pi_\infty \geq 0, \quad c_v \geq 0",
"explanation": "The equation-of-state parameters must satisfy basic positivity requirements for thermodynamic stability.",
"references": ["Wilfong26"],
},
"check_eos_parameter_sanity": {
"title": "EOS Parameter Sanity (Transformed Gamma)",
"category": "Thermodynamic Constraints",
"math": r"\Gamma = \frac{1}{\gamma - 1}",
"explanation": ("MFC uses the transformed stiffened gas parameter. A common mistake is entering the physical gamma (e.g., 1.4 for air) instead of the transformed value 1/(gamma-1) = 2.5."),
"references": ["Wilfong26", "Allaire02"],
},
"check_patch_physics": {
"title": "Patch Initial Condition Constraints",
"category": "Thermodynamic Constraints",
"math": r"p > 0, \quad \alpha_i \rho_i \geq 0, \quad 0 \leq \alpha_i \leq 1",
"explanation": ("All initial patch pressures must be strictly positive. Partial densities must be non-negative. Volume fractions must be in [0,1]."),
},
# Mixture Constraints
"check_volume_fraction_sum": {
"title": "Volume Fraction Sum",
"category": "Mixture Constraints",
"math": r"\sum_{i=1}^{N_f} \alpha_i = 1",
"explanation": "For multi-component models, volume fractions must satisfy the mixture constraint.",
"exceptions": [
"Single-fluid Euler-Euler bubble models (alpha represents void fraction)",
"Lagrangian bubble models (Lagrangian phase not tracked on Euler grid)",
"IBM cases (alpha acts as level-set indicator)",
"Alter patches and hard-coded IC patches (values computed at runtime)",
"Analytical expressions (cannot validate statically)",
],
},
"check_alpha_rho_consistency": {
"title": "Alpha-Rho Consistency",
"category": "Mixture Constraints",
"math": r"\alpha_j = 0 \Rightarrow \alpha_j \rho_j = 0, \quad \alpha_j > 0 \Rightarrow \alpha_j \rho_j > 0",
"explanation": ("Warns about physically inconsistent combinations: density assigned to an absent phase, or a present phase with zero density."),
},
# Domain and Geometry
"check_domain_bounds": {
"title": "Domain Bounds",
"category": "Domain and Geometry",
"math": r"x_{\mathrm{end}} > x_{\mathrm{beg}}, \quad y_{\mathrm{end}} > y_{\mathrm{beg}}, \quad z_{\mathrm{end}} > z_{\mathrm{beg}}",
"explanation": "Each active spatial dimension must have positive extent.",
},
"check_simulation_domain": {
"title": "Dimensionality",
"category": "Domain and Geometry",
"math": r"m > 0, \quad n \geq 0, \quad p \geq 0",
"explanation": ("The x-direction must have cells. Cannot have z without y. Cylindrical coordinates require odd p."),
},
"check_patch_within_domain": {
"title": "Patch Within Domain",
"category": "Domain and Geometry",
"explanation": ("For patches with centroid + length geometry, the bounding box must not be entirely outside the computational domain. Skipped when grid stretching is active."),
},
# Velocity and Dimensional Consistency
"check_velocity_components": {
"title": "Velocity Components in Inactive Dimensions",
"category": "Velocity and Dimensional Consistency",
"math": r"n = 0 \Rightarrow v_2 = 0, \quad p = 0 \Rightarrow v_3 = 0",
"explanation": "Setting velocity components in dimensions that do not exist is almost certainly a mistake.",
"exceptions": ["MHD simulations (transverse velocity couples to magnetic field in 1D)"],
},
# Model Equations
"check_model_eqns_and_num_fluids": {
"title": "Model Equation Selection",
"category": "Model Equations",
"explanation": ("Model 1: gamma-law single-fluid. Model 2: five-equation (Allaire). Model 3: six-equation (Saurel). Model 4: four-equation (single-component with bubbles)."),
"references": ["Wilfong26", "Allaire02", "Saurel09"],
},
# Boundary Conditions
"check_boundary_conditions": {
"title": "Boundary Condition Compatibility",
"category": "Boundary Conditions",
"explanation": ("Periodicity must match on both ends. Valid BC values range from -1 to -17. Cylindrical coordinates have specific BC requirements at the axis."),
},
# Bubble Physics
"check_bubbles_euler": {
"title": "Euler-Euler Bubble Model",
"category": "Bubble Physics",
"explanation": ("Requires nb >= 1, positive reference quantities. Polydisperse requires odd nb > 1 and poly_sigma > 0. QBMM requires nnode = 4."),
"references": ["Bryngelson21"],
},
"check_bubbles_euler_simulation": {
"title": "Bubble Simulation Constraints",
"category": "Bubble Physics",
"explanation": ("Requires HLLC Riemann solver and arithmetic average. Five-equation model does not support Gilmore bubble_model."),
},
"check_bubbles_lagrange": {
"title": "Euler-Lagrange Bubble Model",
"category": "Bubble Physics",
"explanation": "2D/3D only. Requires polytropic = F and thermal = 3. Not compatible with model_eqns = 3.",
},
# Numerical Schemes
"check_weno": {
"title": "WENO Reconstruction",
"category": "Numerical Schemes",
"explanation": ("weno_order must be 1, 3, 5, or 7. Grid must have enough cells. Only one of mapped_weno, wenoz, teno can be active."),
},
"check_muscl": {
"title": "MUSCL Reconstruction",
"category": "Numerical Schemes",
"explanation": "muscl_order must be 1 or 2. Second order requires muscl_lim in {0,1,2,3,4,5}.",
},
"check_time_stepping": {
"title": "Time Stepping",
"category": "Numerical Schemes",
"explanation": ("time_stepper in {1,2,3}. Fixed dt must be positive. CFL-based modes require cfl_target in (0,1]."),
},
"check_viscosity": {
"title": "Viscosity",
"category": "Numerical Schemes",
"math": r"\mathrm{Re}_1 > 0, \quad \mathrm{Re}_2 > 0",
"explanation": "Reynolds numbers must be positive. Not supported with model_eqns = 1.",
},
# Feature Compatibility
"check_mhd": {
"title": "Magnetohydrodynamics (MHD)",
"category": "Feature Compatibility",
"explanation": ("Requires model_eqns = 2, num_fluids = 1, HLL or HLLD Riemann solver. No relativity with HLLD."),
},
"check_surface_tension": {
"title": "Surface Tension",
"category": "Feature Compatibility",
"explanation": "Requires model_eqns 2 or 3, num_fluids = 2.",
},
"check_hypoelasticity": {
"title": "Hypoelasticity",
"category": "Feature Compatibility",
"explanation": "Requires model_eqns = 2, HLL Riemann solver.",
},
"check_phase_change": {
"title": "Phase Change",
"category": "Feature Compatibility",
"explanation": "Model 2: relax_model 5 or 6. Model 3: relax_model 1, 4, 5, or 6.",
},
"check_alt_soundspeed": {
"title": "Alternative Sound Speed",
"category": "Feature Compatibility",
"explanation": "Requires model_eqns = 2, num_fluids 2 or 3, HLLC solver. Incompatible with bubbles.",
},
"check_igr": {
"title": "Iterative Generalized Riemann (IGR)",
"category": "Feature Compatibility",
"explanation": ("Requires model_eqns = 2. Incompatible with characteristic BCs, bubbles, MHD, and elastic models."),
},
# Acoustic Sources
"check_acoustic_source": {
"title": "Acoustic Sources",
"category": "Acoustic Sources",
"explanation": ("Dimension-specific support types. Pulse type in {1,2,3,4}. Non-planar sources require foc_length and aperture."),
},
# Post-Processing
"check_vorticity": {
"title": "Vorticity Output",
"category": "Post-Processing",
"explanation": "omega_wrt requires at least 2D (n > 0). 3D components require p > 0. Requires fd_order.",
},
"check_fft": {
"title": "FFT Output",
"category": "Post-Processing",
"explanation": ("Requires 3D with all periodic boundaries. Global dimensions must be even. Incompatible with cylindrical coordinates."),
},
}
@lru_cache(maxsize=1)
def _get_logical_params_from_registry() -> Set[str]:
"""
Get all LOG-type parameter names from the registry.
This replaces the hardcoded logical_params list with a dynamic lookup,
ensuring all LOG parameters are validated without manual maintenance.
Returns:
Set of parameter names that have LOG type.
"""
from .params import REGISTRY
from .params.schema import ParamType
return {name for name, param in REGISTRY.all_params.items() if param.param_type == ParamType.LOG}
class CaseConstraintError(MFCException):
"""Exception raised when case parameters violate constraints"""
class CaseValidator:
"""Validates MFC case parameter constraints"""
def __init__(self, params: Dict[str, Any]):
self.params = params
self.errors: List[str] = []
self.warnings: List[str] = []
def get(self, key: str, default=None):
"""Get parameter value with default"""
return self.params.get(key, default)
def is_set(self, key: str) -> bool:
"""Check if parameter is set (not None and present)"""
return key in self.params and self.params[key] is not None
def prohibit(self, condition: bool, message: str):
"""Assert that condition is False, otherwise add error"""
if condition:
self.errors.append(message)
def warn(self, condition: bool, message: str):
"""Add a non-fatal warning if condition is True"""
if condition:
self.warnings.append(message)
def _validate_logical(self, key: str):
"""Validate that a parameter is a valid Fortran logical ('T' or 'F')."""
val = self.get(key)
if val is not None and val not in ("T", "F"):
self.errors.append(f"{key} must be 'T' or 'F', got '{val}'")
def _check_order_fits_grid(self, order: int, param_name: str) -> None:
"""Prohibit reconstruction order that exceeds grid cell count in any active dimension."""
m = self.get("m", 0)
n = self.get("n", 0) or 0
p = self.get("p", 0) or 0
self.prohibit(m + 1 < order, f"m must be at least {param_name} - 1 (= {order - 1})")
self.prohibit(n > 0 and n + 1 < order, f"For 2D: n must be at least {param_name} - 1 (= {order - 1})")
self.prohibit(p > 0 and p + 1 < order, f"For 3D: p must be at least {param_name} - 1 (= {order - 1})")
def _get_recon_type(self) -> int:
return self.get("recon_type", 1)
def check_parameter_types(self):
"""Validate parameter types before other checks.
This catches invalid values early with clear error messages,
rather than letting them cause confusing failures later.
LOG parameters are discovered dynamically from the registry,
eliminating the need to maintain a hardcoded list.
"""
# Validate all LOG-type parameters from registry
logical_params = _get_logical_params_from_registry()
for param in logical_params:
if param in self.params: # Only validate params that are set
self._validate_logical(param)
self.prohibit(self.get("recon_type", 1) not in [1, 2], "recon_type must be 1 (WENO) or 2 (MUSCL)")
# Required domain parameters when m > 0
m = self.get("m")
if m is not None and m > 0:
self.prohibit(not self.is_set("x_domain%beg"), "x_domain%beg must be set when m > 0")
self.prohibit(not self.is_set("x_domain%end"), "x_domain%end must be set when m > 0")
# Common Checks (All Stages)
def check_simulation_domain(self):
"""Checks constraints on dimensionality and number of cells"""
m = self.get("m")
n = self.get("n", 0)
p = self.get("p", 0)
cyl_coord = self.get("cyl_coord", "F") == "T"
self.prohibit(m is None, "m must be set")
self.prohibit(m is not None and m <= 0, "m must be positive")
self.prohibit(n is not None and n < 0, "n must be non-negative")
self.prohibit(p is not None and p < 0, "p must be non-negative")
self.prohibit(cyl_coord and p is not None and p > 0 and p % 2 == 0, "p must be odd for cylindrical coordinates")
self.prohibit(n is not None and p is not None and n == 0 and p > 0, "p must be 0 if n = 0")
def check_model_eqns_and_num_fluids(self):
"""Checks constraints on model equations and number of fluids"""
model_eqns = self.get("model_eqns")
num_fluids = self.get("num_fluids")
mpp_lim = self.get("mpp_lim", "F") == "T"
cyl_coord = self.get("cyl_coord", "F") == "T"
p = self.get("p", 0)
self.prohibit(model_eqns is not None and model_eqns not in [1, 2, 3, 4], "model_eqns must be 1, 2, 3, or 4")
self.prohibit(num_fluids is not None and num_fluids < 1, "num_fluids must be positive")
self.prohibit(model_eqns == 1 and num_fluids is not None, "num_fluids is not supported for model_eqns = 1")
self.prohibit(model_eqns == 2 and num_fluids is None, "5-equation model (model_eqns = 2) requires num_fluids to be set")
self.prohibit(model_eqns == 3 and num_fluids is None, "6-equation model (model_eqns = 3) requires num_fluids to be set")
self.prohibit(model_eqns == 4 and num_fluids is None, "4-equation model (model_eqns = 4) requires num_fluids to be set")
self.prohibit(model_eqns == 1 and mpp_lim, "model_eqns = 1 does not support mpp_lim")
self.prohibit(num_fluids == 1 and mpp_lim, "num_fluids = 1 does not support mpp_lim")
self.prohibit(model_eqns == 3 and cyl_coord and p != 0, "6-equation model (model_eqns = 3) does not support cylindrical coordinates (cyl_coord = T and p != 0)")
def check_igr(self):
"""Checks constraints regarding IGR order"""
igr = self.get("igr", "F") == "T"
igr_pres_lim = self.get("igr_pres_lim", "F") == "T"
self.prohibit(igr_pres_lim and not igr, "igr_pres_lim requires igr to be enabled")
if not igr:
return
igr_order = self.get("igr_order")
self.prohibit(igr_order not in [None, 3, 5], "igr_order must be 3 or 5")
if igr_order:
self._check_order_fits_grid(igr_order, "igr_order")
def check_weno(self):
"""Checks constraints regarding WENO order"""
if self._get_recon_type() != 1:
return
for param in ["muscl_order", "muscl_lim"]:
self.prohibit(self.is_set(param), f"recon_type = 1 (WENO) is not compatible with {param}")
weno_order = self.get("weno_order")
if weno_order is None:
return
self.prohibit(weno_order not in [1, 3, 5, 7], "weno_order must be 1, 3, 5, or 7")
self._check_order_fits_grid(weno_order, "weno_order")
def check_muscl(self):
"""Check constraints regarding MUSCL order"""
if self._get_recon_type() != 2:
return
for param in ["mapped_weno", "wenoz", "teno", "mp_weno", "weno_avg", "null_weights", "weno_Re_flux"]:
self.prohibit(self.get(param) == "T", f"recon_type = 2 (MUSCL) is not compatible with {param} = T")
for param in ["wenoz_q", "teno_CT", "weno_eps"]:
self.prohibit(self.is_set(param), f"recon_type = 2 (MUSCL) is not compatible with {param}")
weno_order = self.get("weno_order")
self.prohibit(weno_order is not None and weno_order != 0, f"recon_type = 2 (MUSCL) requires weno_order unset or 0, but got {weno_order}")
muscl_order = self.get("muscl_order")
if muscl_order is None:
return
self.prohibit(muscl_order not in [1, 2], "muscl_order must be 1 or 2")
self._check_order_fits_grid(muscl_order, "muscl_order")
def check_interface_compression(self):
"""Check constraints regarding interface compression"""
int_comp = self.get("int_comp", 0)
n = self.get("n", 0)
num_fluids = self.get("num_fluids", 0)
model_eqns = self.get("model_eqns", 2)
self.prohibit(int_comp not in [0, 1, 2], "int_comp must be 0 (off), 1 (THINC), or 2 (MTHINC)")
self.prohibit(int_comp == 2 and n == 0, "int_comp = 2 (MTHINC) requires at least 2D (n > 0)")
self.prohibit(int_comp != 0 and num_fluids != 2, "int_comp > 0 requires num_fluids = 2")
self.prohibit(
int_comp != 0 and model_eqns == 3,
"int_comp > 0 is not supported with model_eqns = 3: THINC does not update per-fluid internal energies, leaving thermodynamically inconsistent face states",
)
recon_type = self.get("recon_type", 1)
if recon_type == 1: # WENO
weno_order = self.get("weno_order")
self.prohibit(weno_order == 1 and int_comp != 0, "int_comp must be 0 (off) when weno_order = 1")
elif recon_type == 2: # MUSCL
muscl_order = self.get("muscl_order")
self.prohibit(muscl_order == 1 and int_comp != 0, "int_comp must be 0 (off) when muscl_order = 1")
def check_boundary_conditions(self):
"""Checks constraints on boundary conditions"""
cyl_coord = self.get("cyl_coord", "F") == "T"
m = self.get("m", 0)
n = self.get("n", 0)
p = self.get("p", 0)
for dir, var in [("x", "m"), ("y", "n"), ("z", "p")]:
var_val = {"m": m, "n": n, "p": p}[var]
for bound in ["beg", "end"]:
bc_key = f"bc_{dir}%{bound}"
bc_val = self.get(bc_key)
self.prohibit(var_val is not None and var_val == 0 and bc_val is not None, f"{bc_key} is not supported for {var} = 0")
self.prohibit(var_val is not None and var_val > 0 and bc_val is None, f"{var} != 0 but {bc_key} is not set")
# Check periodicity matches
beg_bc = self.get(f"bc_{dir}%beg")
end_bc = self.get(f"bc_{dir}%end")
if beg_bc is not None and end_bc is not None:
self.prohibit((beg_bc == -1 and end_bc != -1) or (end_bc == -1 and beg_bc != -1), f"bc_{dir}%beg and bc_{dir}%end must be both periodic (= -1) or both non-periodic")
# Range check (skip for cylindrical y/z)
skip_check = cyl_coord and dir in ["y", "z"]
for bound in ["beg", "end"]:
bc_key = f"bc_{dir}%{bound}"
bc_val = self.get(bc_key)
if not skip_check and bc_val is not None:
self.prohibit(bc_val > -1 or bc_val < -17, f"{bc_key} must be between -1 and -17")
self.prohibit(bc_val == -14 and not cyl_coord, f"{bc_key} must not be -14 (BC_AXIS) for non-cylindrical coordinates")
# Check BC_NULL is not used
for dir in ["x", "y", "z"]:
for bound in ["beg", "end"]:
bc_val = self.get(f"bc_{dir}%{bound}")
self.prohibit(bc_val == -13, "Boundary condition -13 (BC_NULL) is not supported")
# Cylindrical specific checks
if cyl_coord:
self.prohibit(n is not None and n == 0, "n must be positive (2D or 3D) for cylindrical coordinates")
bc_y_beg = self.get("bc_y%beg")
bc_y_end = self.get("bc_y%end")
bc_z_beg = self.get("bc_z%beg")
bc_z_end = self.get("bc_z%end")
self.prohibit(p is not None and p == 0 and bc_y_beg != -2, "bc_y%beg must be -2 (BC_REFLECTIVE) for 2D cylindrical coordinates (p = 0)")
self.prohibit(p is not None and p > 0 and bc_y_beg != -14, "bc_y%beg must be -14 (BC_AXIS) for 3D cylindrical coordinates (p > 0)")
if bc_y_end is not None:
self.prohibit(bc_y_end > -1 or bc_y_end < -17, "bc_y%end must be between -1 and -17")
self.prohibit(bc_y_end == -14, "bc_y%end must not be -14 (BC_AXIS)")
# 3D cylindrical
if p is not None and p > 0:
self.prohibit(bc_z_beg is not None and bc_z_beg not in [-1, -2], "bc_z%beg must be -1 (periodic) or -2 (reflective) for 3D cylindrical coordinates")
self.prohibit(bc_z_end is not None and bc_z_end not in [-1, -2], "bc_z%end must be -1 (periodic) or -2 (reflective) for 3D cylindrical coordinates")
def check_bubbles_euler(self):
"""Checks constraints on bubble parameters"""
bubbles_euler = self.get("bubbles_euler", "F") == "T"
if not bubbles_euler:
return
nb = self.get("nb")
polydisperse = self.get("polydisperse", "F") == "T"
thermal = self.get("thermal")
model_eqns = self.get("model_eqns")
cyl_coord = self.get("cyl_coord", "F") == "T"
rhoref = self.get("rhoref")
pref = self.get("pref")
num_fluids = self.get("num_fluids")
self.prohibit(nb is None or nb < 1, "The Ensemble-Averaged Bubble Model requires nb >= 1")
self.prohibit(polydisperse and nb == 1, "Polydisperse bubble dynamics requires nb > 1")
self.prohibit(polydisperse and nb is not None and nb % 2 == 0, "nb must be odd for polydisperse bubbles")
self.prohibit(thermal is not None and thermal > 3, "thermal must be <= 3")
self.prohibit(model_eqns == 3, "Bubble models untested with 6-equation model (model_eqns = 3)")
self.prohibit(model_eqns == 1, "Bubble models untested with pi-gamma model (model_eqns = 1)")
self.prohibit(model_eqns == 4 and rhoref is None, "rhoref must be set if using bubbles_euler with model_eqns = 4")
self.prohibit(rhoref is not None and rhoref <= 0, "rhoref (reference density) must be positive")
self.prohibit(model_eqns == 4 and pref is None, "pref must be set if using bubbles_euler with model_eqns = 4")
self.prohibit(pref is not None and pref <= 0, "pref (reference pressure) must be positive")
self.prohibit(model_eqns == 4 and num_fluids != 1, "4-equation model (model_eqns = 4) is single-component and requires num_fluids = 1")
self.prohibit(cyl_coord, "Bubble models untested in cylindrical coordinates")
# BUBBLE PHYSICS PARAMETERS
# Validate bubble reference parameters (bub_pp%)
R0ref = self.get("bub_pp%R0ref")
p0ref = self.get("bub_pp%p0ref")
rho0ref = self.get("bub_pp%rho0ref")
T0ref = self.get("bub_pp%T0ref")
if R0ref is not None:
self.prohibit(R0ref <= 0, "bub_pp%R0ref (reference bubble radius) must be positive")
if p0ref is not None:
self.prohibit(p0ref <= 0, "bub_pp%p0ref (reference pressure) must be positive")
if rho0ref is not None:
self.prohibit(rho0ref <= 0, "bub_pp%rho0ref (reference density) must be positive")
if T0ref is not None:
self.prohibit(T0ref <= 0, "bub_pp%T0ref (reference temperature) must be positive")
# Viscosities must be non-negative
mu_l = self.get("bub_pp%mu_l")
mu_g = self.get("bub_pp%mu_g")
mu_v = self.get("bub_pp%mu_v")
if mu_l is not None:
self.prohibit(mu_l < 0, "bub_pp%mu_l (liquid viscosity) must be non-negative")
if mu_g is not None:
self.prohibit(mu_g < 0, "bub_pp%mu_g (gas viscosity) must be non-negative")
if mu_v is not None:
self.prohibit(mu_v < 0, "bub_pp%mu_v (vapor viscosity) must be non-negative")
# Surface tension must be non-negative
ss = self.get("bub_pp%ss")
if ss is not None:
self.prohibit(ss < 0, "bub_pp%ss (surface tension) must be non-negative")
def check_qbmm_and_polydisperse(self):
"""Checks constraints on QBMM and polydisperse bubble parameters"""
polydisperse = self.get("polydisperse", "F") == "T"
bubbles_euler = self.get("bubbles_euler", "F") == "T"
poly_sigma = self.get("poly_sigma")
qbmm = self.get("qbmm", "F") == "T"
nnode = self.get("nnode")
self.prohibit(polydisperse and not bubbles_euler, "Polydisperse bubble modeling requires the bubbles_euler flag to be set")
self.prohibit(polydisperse and poly_sigma is None, "Polydisperse bubble modeling requires poly_sigma to be set")
self.prohibit(polydisperse and poly_sigma is not None and poly_sigma <= 0, "poly_sigma must be positive")
self.prohibit(qbmm and not bubbles_euler, "QBMM requires the bubbles_euler flag to be set")
self.prohibit(qbmm and nnode is not None and nnode != 4, "QBMM requires nnode = 4")
def check_adv_n(self):
"""Checks constraints on adv_n flag"""
adv_n = self.get("adv_n", "F") == "T"
bubbles_euler = self.get("bubbles_euler", "F") == "T"
num_fluids = self.get("num_fluids")
qbmm = self.get("qbmm", "F") == "T"
if not adv_n:
return
self.prohibit(not bubbles_euler, "adv_n requires bubbles_euler to be enabled")
self.prohibit(num_fluids != 1, "adv_n requires num_fluids = 1")
self.prohibit(qbmm, "adv_n is not compatible with qbmm")
def check_hypoelasticity(self):
"""Checks constraints on hypoelasticity parameters"""
hypoelasticity = self.get("hypoelasticity", "F") == "T"
model_eqns = self.get("model_eqns")
riemann_solver = self.get("riemann_solver")
if not hypoelasticity:
return
self.prohibit(model_eqns is not None and model_eqns != 2, "hypoelasticity requires model_eqns = 2")
self.prohibit(riemann_solver is not None and riemann_solver != 1, "hypoelasticity requires HLL Riemann solver (riemann_solver = 1)")
def check_phase_change(self):
"""Checks constraints on phase change parameters"""
relax = self.get("relax", "F") == "T"
relax_model = self.get("relax_model")
model_eqns = self.get("model_eqns")
palpha_eps = self.get("palpha_eps")
ptgalpha_eps = self.get("ptgalpha_eps")
if not relax:
return
self.prohibit(
(model_eqns not in (2, 3) or (model_eqns == 2 and relax_model not in (5, 6)) or (model_eqns == 3 and relax_model not in (1, 4, 5, 6))),
"phase change requires model_eqns==2 with relax_model in [5,6] or model_eqns==3 with relax_model in [1,4,5,6]",
)
self.prohibit(palpha_eps is not None and palpha_eps <= 0, "palpha_eps must be positive")
self.prohibit(palpha_eps is not None and palpha_eps >= 1, "palpha_eps must be less than 1")
self.prohibit(ptgalpha_eps is not None and ptgalpha_eps <= 0, "ptgalpha_eps must be positive")
self.prohibit(ptgalpha_eps is not None and ptgalpha_eps >= 1, "ptgalpha_eps must be less than 1")
def check_ibm(self):
"""Checks constraints on Immersed Boundaries parameters"""
ib = self.get("ib", "F") == "T"
n = self.get("n", 0)
num_ibs = self.get("num_ibs", 0)
num_particle_beds = self.get("num_particle_beds", 0) or 0
ib_state_wrt = self.get("ib_state_wrt", "F") == "T"
self.prohibit(ib and n <= 0, "Immersed Boundaries do not work in 1D (requires n > 0)")
has_particle_beds = num_particle_beds > 0 and any((self.get(f"particle_bed({i})%num_particles", 0) or 0) > 0 for i in range(1, num_particle_beds + 1))
self.prohibit(
ib and num_ibs <= 0 and not has_particle_beds,
"num_ibs must be >= 1 when ib is enabled (or specify at least one particle_bed with num_particles > 0)",
)
num_ib_patches_max = get_fortran_constants().get("num_ib_patches_max_namelist", 50000)
self.prohibit(
ib and num_ibs > num_ib_patches_max,
f"num_ibs must be <= {num_ib_patches_max} (num_ib_patches_max_namelist in m_constants.fpp)",
)
self.prohibit(not ib and num_ibs > 0, "num_ibs is set, but ib is not enabled")
self.prohibit(ib_state_wrt and not ib, "ib_state_wrt requires ib to be enabled")
def check_stiffened_eos(self):
"""Checks constraints on stiffened equation of state fluids parameters"""
num_fluids = self.get("num_fluids")
model_eqns = self.get("model_eqns")
bubbles_euler = self.get("bubbles_euler", "F") == "T"
if num_fluids is None:
return
# Allow one extra fluid property slot when using bubbles_euler
bub_fac = 1 if (bubbles_euler) else 0
for i in range(1, num_fluids + 1 + bub_fac):
gamma = self.get(f"fluid_pp({i})%gamma")
pi_inf = self.get(f"fluid_pp({i})%pi_inf")
cv = self.get(f"fluid_pp({i})%cv")
# Positivity checks
if gamma is not None:
self.prohibit(gamma <= 0, f"fluid_pp({i})%gamma must be positive")
if pi_inf is not None:
self.prohibit(pi_inf < 0, f"fluid_pp({i})%pi_inf must be non-negative")
if cv is not None:
self.prohibit(cv < 0, f"fluid_pp({i})%cv must be positive")
# Model-specific support
if model_eqns == 1:
self.prohibit(gamma is not None, f"model_eqns = 1 does not support fluid_pp({i})%gamma")
self.prohibit(pi_inf is not None, f"model_eqns = 1 does not support fluid_pp({i})%pi_inf")
def check_surface_tension(self):
"""Checks constraints on surface tension"""
surface_tension = self.get("surface_tension", "F") == "T"
sigma = self.get("sigma")
model_eqns = self.get("model_eqns")
num_fluids = self.get("num_fluids")
if not surface_tension and sigma is None:
return
self.prohibit(surface_tension and sigma is None, "sigma must be set if surface_tension is enabled")
self.prohibit(surface_tension and sigma is not None and sigma < 0, "sigma must be greater than or equal to zero")
self.prohibit(sigma is not None and not surface_tension, "sigma is set but surface_tension is not enabled")
self.prohibit(surface_tension and model_eqns not in [2, 3], "The surface tension model requires model_eqns = 2 or model_eqns = 3")
self.prohibit(surface_tension and num_fluids != 2, "The surface tension model requires num_fluids = 2")
def check_mhd(self):
"""Checks constraints on MHD parameters"""
mhd = self.get("mhd", "F") == "T"
num_fluids = self.get("num_fluids")
model_eqns = self.get("model_eqns")
relativity = self.get("relativity", "F") == "T"
Bx0 = self.get("Bx0")
n = self.get("n", 0)
self.prohibit(mhd and num_fluids != 1, "MHD is only available for single-component flows (num_fluids = 1)")
self.prohibit(mhd and model_eqns != 2, "MHD is only available for the 5-equation model (model_eqns = 2)")
self.prohibit(relativity and not mhd, "relativity requires mhd to be enabled")
self.prohibit(Bx0 is not None and not mhd, "Bx0 must not be set if MHD is not enabled")
self.prohibit(mhd and n is not None and n == 0 and Bx0 is None, "Bx0 must be set in 1D MHD simulations")
self.prohibit(mhd and n is not None and n > 0 and Bx0 is not None, "Bx0 must not be set in 2D/3D MHD simulations")
# Simulation-Specific Checks
def check_riemann_solver(self):
"""Checks constraints on Riemann solver (simulation only)"""
riemann_solver = self.get("riemann_solver")
model_eqns = self.get("model_eqns")
wave_speeds = self.get("wave_speeds")
avg_state = self.get("avg_state")
low_Mach = self.get("low_Mach", 0)
cyl_coord = self.get("cyl_coord", "F") == "T"
viscous = self.get("viscous", "F") == "T"
igr = self.get("igr", "F") == "T"
if igr:
return
self.prohibit(riemann_solver is None, "riemann_solver must be specified (1=HLL, 2=HLLC, 4=HLLD, 5=Lax-Friedrichs)")
if riemann_solver is None:
return
self.prohibit(riemann_solver not in [1, 2, 4, 5], "riemann_solver must be 1 (HLL), 2 (HLLC), 4 (HLLD), or 5 (Lax-Friedrichs)")
self.prohibit(riemann_solver != 2 and model_eqns == 3, "6-equation model (model_eqns = 3) requires riemann_solver = 2 (HLLC)")
self.prohibit(wave_speeds is not None and wave_speeds not in [1, 2], "wave_speeds must be 1 or 2")
self.prohibit(avg_state is not None and avg_state not in [1, 2], "avg_state must be 1 or 2")
self.prohibit(riemann_solver != 5 and wave_speeds is None, "wave_speeds must be set for riemann_solver 1, 2, or 4")
self.prohibit(riemann_solver != 5 and avg_state is None, "avg_state must be set for riemann_solver 1, 2, or 4")
self.prohibit(low_Mach not in [0, 1, 2], "low_Mach must be 0, 1, or 2")
self.prohibit(riemann_solver != 2 and low_Mach == 2, "low_Mach = 2 requires riemann_solver = 2")
self.prohibit(low_Mach != 0 and model_eqns not in [2, 3], "low_Mach = 1 or 2 requires model_eqns = 2 or 3")
self.prohibit(riemann_solver == 5 and cyl_coord and viscous, "Lax Friedrichs with cylindrical viscous flux not supported")
def check_time_stepping(self):
"""Checks time stepping parameters (simulation/post-process)"""
cfl_dt = self.get("cfl_dt", "F") == "T"
cfl_adap_dt = self.get("cfl_adap_dt", "F") == "T"
adap_dt = self.get("adap_dt", "F") == "T"
time_stepper = self.get("time_stepper")
# Check time_stepper bounds
self.prohibit(time_stepper is not None and (time_stepper < 1 or time_stepper > 3), "time_stepper must be 1, 2, or 3")
# CFL-based variable dt modes (use t_stop/t_save for termination)
# Note: adap_dt is NOT included here - it uses t_step_* for termination
variable_dt = cfl_dt or cfl_adap_dt
# dt validation (applies to all modes if dt is set)
dt = self.get("dt")
self.prohibit(dt is not None and dt <= 0, "dt must be positive")
if variable_dt:
cfl_target = self.get("cfl_target")
t_stop = self.get("t_stop")
t_save = self.get("t_save")
n_start = self.get("n_start")
self.prohibit(cfl_target is not None and (cfl_target <= 0 or cfl_target > 1), "cfl_target must be in (0, 1]")
self.prohibit(t_stop is not None and t_stop <= 0, "t_stop must be positive")
self.prohibit(t_save is not None and t_save <= 0, "t_save must be positive")
self.prohibit(t_save is not None and t_stop is not None and t_save > t_stop, "t_save must be <= t_stop")
self.prohibit(n_start is not None and n_start < 0, "n_start must be non-negative")
# t_step_* validation (applies to fixed and adap_dt modes)
t_step_start = self.get("t_step_start")
t_step_stop = self.get("t_step_stop")
t_step_save = self.get("t_step_save")
self.prohibit(t_step_start is not None and t_step_start < 0, "t_step_start must be non-negative")
self.prohibit(t_step_stop is not None and t_step_stop < 0, "t_step_stop must be non-negative")
self.prohibit(t_step_stop is not None and t_step_start is not None and t_step_stop <= t_step_start, "t_step_stop must be > t_step_start")
self.prohibit(t_step_save is not None and t_step_save <= 0, "t_step_save must be positive")
self.prohibit(
t_step_save is not None and t_step_stop is not None and t_step_start is not None and t_step_save > t_step_stop - t_step_start, "t_step_save must be <= (t_step_stop - t_step_start)"
)
if not variable_dt:
# dt is required in pure fixed dt mode (not cfl_dt, not cfl_adap_dt)
# adap_dt mode uses dt as initial value, so it's optional
uses_fixed_stepping = self.is_set("t_step_start") or self.is_set("t_step_stop")
self.prohibit(uses_fixed_stepping and not adap_dt and not self.is_set("dt"), "dt must be set when using fixed time stepping (t_step_start/t_step_stop)")
def check_finite_difference(self):
"""Checks constraints on finite difference parameters"""
fd_order = self.get("fd_order")
if fd_order is None:
return
self.prohibit(fd_order not in [1, 2, 4], "fd_order must be 1, 2, or 4")
def check_weno_simulation(self):
"""Checks WENO-specific constraints for simulation"""
if self._get_recon_type() != 1:
return
weno_order = self.get("weno_order")
weno_eps = self.get("weno_eps")
wenoz = self.get("wenoz", "F") == "T"
wenoz_q = self.get("wenoz_q")
teno = self.get("teno", "F") == "T"
teno_CT = self.get("teno_CT")
mapped_weno = self.get("mapped_weno", "F") == "T"
mp_weno = self.get("mp_weno", "F") == "T"
weno_avg = self.get("weno_avg", "F") == "T"
model_eqns = self.get("model_eqns")
# Check for multiple WENO schemes (regardless of weno_order being set)
num_schemes = sum([mapped_weno, wenoz, teno])
self.prohibit(num_schemes >= 2, "Only one of mapped_weno, wenoz, or teno can be set to true")
# Early return if weno_order not set (other checks need it)
if weno_order is None:
return
self.prohibit(weno_order != 1 and weno_eps is None, "weno_order != 1 requires weno_eps to be set. A typical value is 1e-6")
self.prohibit(weno_eps is not None and weno_eps <= 0, "weno_eps must be positive. A typical value is 1e-6")
self.prohibit(wenoz and weno_order == 7 and wenoz_q is None, "wenoz at 7th order requires wenoz_q to be set (should be 2, 3, or 4)")
self.prohibit(wenoz and weno_order == 7 and wenoz_q is not None and wenoz_q not in [2, 3, 4], "wenoz_q must be either 2, 3, or 4)")
self.prohibit(teno and teno_CT is None, "teno requires teno_CT to be set. A typical value is 1e-6")
self.prohibit(teno and teno_CT is not None and teno_CT <= 0, "teno_CT must be positive. A typical value is 1e-6")
self.prohibit(weno_order == 1 and mapped_weno, "mapped_weno is not compatible with weno_order = 1")
self.prohibit(weno_order == 1 and wenoz, "wenoz is not compatible with weno_order = 1")
self.prohibit(weno_order in [1, 3] and teno, "teno requires weno_order = 5 or 7")
self.prohibit(weno_order != 5 and mp_weno, "mp_weno requires weno_order = 5")
self.prohibit(model_eqns == 1 and weno_avg, "weno_avg is not compatible with model_eqns = 1")
def check_muscl_simulation(self):
"""Checks MUSCL-specific constraints for simulation"""
if self._get_recon_type() != 2:
return
muscl_order = self.get("muscl_order")
muscl_lim = self.get("muscl_lim")
muscl_eps = self.get("muscl_eps")
if muscl_order is None:
return
self.prohibit(muscl_order == 2 and muscl_lim is None, "muscl_lim must be defined if using muscl_order = 2")
self.prohibit(muscl_lim is not None and (muscl_lim < 0 or muscl_lim > 5), "muscl_lim must be 0 (unlimited), 1, 2, 3, 4, or 5")
if muscl_eps is not None:
self.prohibit(muscl_eps < 0, "muscl_eps must be >= 0 (use 0 for textbook limiter behavior)")
def check_model_eqns_simulation(self):
"""Checks model equation constraints specific to simulation"""
model_eqns = self.get("model_eqns")
avg_state = self.get("avg_state")
wave_speeds = self.get("wave_speeds")
if model_eqns != 3:
return
self.prohibit(avg_state is not None and avg_state != 2, "6-equation model (model_eqns = 3) requires avg_state = 2")
self.prohibit(wave_speeds is not None and wave_speeds != 1, "6-equation model (model_eqns = 3) requires wave_speeds = 1")
def check_bubbles_euler_simulation(self):
"""Checks bubble constraints specific to simulation"""
bubbles_euler = self.get("bubbles_euler", "F") == "T"
bubbles_lagrange = self.get("bubbles_lagrange", "F") == "T"
riemann_solver = self.get("riemann_solver")
avg_state = self.get("avg_state")
model_eqns = self.get("model_eqns")
bubble_model = self.get("bubble_model")
self.prohibit(bubbles_euler and bubbles_lagrange, "Activate only one of the bubble subgrid models (bubbles_euler or bubbles_lagrange)")
if not bubbles_euler:
return
self.prohibit(riemann_solver is not None and riemann_solver != 2, "Bubble modeling requires HLLC Riemann solver (riemann_solver = 2)")
self.prohibit(avg_state is not None and avg_state != 2, "Bubble modeling requires arithmetic average (avg_state = 2)")
self.prohibit(model_eqns == 2 and bubble_model == 1, "The 5-equation bubbly flow model does not support bubble_model = 1 (Gilmore)")
def check_body_forces(self):
"""Checks constraints on body forces parameters"""
for dir in ["x", "y", "z"]:
bf = self.get(f"bf_{dir}", "F") == "T"
if not bf:
continue
self.prohibit(self.get(f"k_{dir}") is None, f"k_{dir} must be specified if bf_{dir} is true")
self.prohibit(self.get(f"w_{dir}") is None, f"w_{dir} must be specified if bf_{dir} is true")
self.prohibit(self.get(f"p_{dir}") is None, f"p_{dir} must be specified if bf_{dir} is true")
self.prohibit(self.get(f"g_{dir}") is None, f"g_{dir} must be specified if bf_{dir} is true")
def check_viscosity(self):
"""Checks constraints on viscosity parameters"""
viscous = self.get("viscous", "F") == "T"
num_fluids = self.get("num_fluids")
model_eqns = self.get("model_eqns")
weno_order = self.get("weno_order")
weno_avg = self.get("weno_avg", "F") == "T"
igr = self.get("igr", "F") == "T"
# If num_fluids is not set, check at least fluid 1 (for model_eqns=1)
if num_fluids is None:
num_fluids = 1
for i in range(1, num_fluids + 1):
Re1 = self.get(f"fluid_pp({i})%Re(1)")
Re2 = self.get(f"fluid_pp({i})%Re(2)")
for j, Re_val in [(1, Re1), (2, Re2)]:
if Re_val is not None:
self.prohibit(Re_val <= 0, f"fluid_pp({i})%Re({j}) must be positive")
self.prohibit(model_eqns == 1, f"model_eqns = 1 does not support fluid_pp({i})%Re({j})")
if not igr:
self.prohibit(weno_order == 1 and not weno_avg, f"weno_order = 1 without weno_avg does not support fluid_pp({i})%Re({j})")
self.prohibit(not viscous, f"Re({j}) is specified, but viscous is not set to true")
# Check Re(1) requirement
self.prohibit(Re1 is None and viscous, f"viscous is set to true, but fluid_pp({i})%Re(1) is not specified")
# weno_Re_flux requires viscous
weno_Re_flux = self.get("weno_Re_flux", "F") == "T"
self.prohibit(weno_Re_flux and not viscous, "weno_Re_flux requires viscous to be enabled")
def check_mhd_simulation(self):
"""Checks MHD constraints specific to simulation"""
mhd = self.get("mhd", "F") == "T"
riemann_solver = self.get("riemann_solver")
relativity = self.get("relativity", "F") == "T"
hyper_cleaning = self.get("hyper_cleaning", "F") == "T"
wave_speeds = self.get("wave_speeds")
n = self.get("n", 0)
self.prohibit(mhd and riemann_solver is not None and riemann_solver not in [1, 4], "MHD simulations require riemann_solver = 1 (HLL) or riemann_solver = 4 (HLLD)")
self.prohibit(mhd and wave_speeds is not None and wave_speeds == 2, "MHD requires wave_speeds = 1")
self.prohibit(riemann_solver == 4 and not mhd, "HLLD (riemann_solver = 4) is only available for MHD simulations")
self.prohibit(riemann_solver == 4 and relativity, "HLLD is not available for RMHD (relativity)")
self.prohibit(hyper_cleaning and not mhd, "Hyperbolic cleaning requires mhd to be enabled")
self.prohibit(hyper_cleaning and n is not None and n == 0, "Hyperbolic cleaning is not supported for 1D simulations")
def check_igr_simulation(self):
"""Checks IGR constraints specific to simulation"""
igr = self.get("igr", "F") == "T"
if not igr:
return
num_igr_iters = self.get("num_igr_iters")
num_igr_warm_start_iters = self.get("num_igr_warm_start_iters")
igr_iter_solver = self.get("igr_iter_solver")
alf_factor = self.get("alf_factor")
model_eqns = self.get("model_eqns")
ib = self.get("ib", "F") == "T"
bubbles_euler = self.get("bubbles_euler", "F") == "T"
bubbles_lagrange = self.get("bubbles_lagrange", "F") == "T"
alt_soundspeed = self.get("alt_soundspeed", "F") == "T"
surface_tension = self.get("surface_tension", "F") == "T"
hypoelasticity = self.get("hypoelasticity", "F") == "T"
acoustic_source = self.get("acoustic_source", "F") == "T"
relax = self.get("relax", "F") == "T"
mhd = self.get("mhd", "F") == "T"
hyperelasticity = self.get("hyperelasticity", "F") == "T"
cyl_coord = self.get("cyl_coord", "F") == "T"
probe_wrt = self.get("probe_wrt", "F") == "T"
int_comp = self.get("int_comp", 0)
self.prohibit(num_igr_iters is not None and num_igr_iters < 0, "num_igr_iters must be greater than or equal to 0")
self.prohibit(num_igr_warm_start_iters is not None and num_igr_warm_start_iters < 0, "num_igr_warm_start_iters must be greater than or equal to 0")
self.prohibit(igr_iter_solver is not None and igr_iter_solver not in [1, 2], "igr_iter_solver must be 1 or 2")
self.prohibit(alf_factor is not None and alf_factor < 0, "alf_factor must be non-negative")
self.prohibit(model_eqns is not None and model_eqns != 2, "IGR only supports model_eqns = 2")
self.prohibit(ib, "IGR does not support the immersed boundary method")
self.prohibit(bubbles_euler, "IGR does not support Euler-Euler bubble models")
self.prohibit(bubbles_lagrange, "IGR does not support Euler-Lagrange bubble models")
self.prohibit(alt_soundspeed, "IGR does not support alt_soundspeed = T")
self.prohibit(surface_tension, "IGR does not support surface tension")
self.prohibit(hypoelasticity, "IGR does not support hypoelasticity")
self.prohibit(acoustic_source, "IGR does not support acoustic sources")
self.prohibit(relax, "IGR does not support phase change")
self.prohibit(mhd, "IGR does not support magnetohydrodynamics")
self.prohibit(hyperelasticity, "IGR does not support hyperelasticity")
self.prohibit(cyl_coord, "IGR does not support cylindrical or axisymmetric coordinates")
self.prohibit(probe_wrt, "IGR does not support probe writes")
self.prohibit(int_comp > 0, "IGR does not support int_comp > 0")
# Check BCs - IGR does not support characteristic BCs
# Characteristic BCs are BC_CHAR_SLIP_WALL (-5) through BC_CHAR_SUP_OUTFLOW (-12)
for dir in ["x", "y", "z"]:
for bound in ["beg", "end"]:
bc = self.get(f"bc_{dir}%{bound}")
if bc is not None:
self.prohibit(-12 <= bc <= -5, f"Characteristic boundary condition bc_{dir}%{bound} is not compatible with IGR")
def check_acoustic_source(self):
"""Checks acoustic source parameters (simulation)"""
acoustic_source = self.get("acoustic_source", "F") == "T"
if not acoustic_source:
return
num_source = self.get("num_source")
n = self.get("n", 0)
p = self.get("p", 0)
cyl_coord = self.get("cyl_coord", "F") == "T"
# Determine dimensionality
if n is not None and n == 0:
dim = 1
elif p is not None and p == 0:
dim = 2
else:
dim = 3
self.prohibit(num_source is None, "num_source must be specified for acoustic_source")
self.prohibit(num_source is not None and num_source < 0, "num_source must be non-negative")
if num_source is None or num_source <= 0:
return
# Check each acoustic source
for j in range(1, num_source + 1):
jstr = str(j)
support = self.get(f"acoustic({j})%support")
loc = [self.get(f"acoustic({j})%loc({i})") for i in range(1, 4)]
mag = self.get(f"acoustic({j})%mag")
pulse = self.get(f"acoustic({j})%pulse")
frequency = self.get(f"acoustic({j})%frequency")
wavelength = self.get(f"acoustic({j})%wavelength")
gauss_sigma_time = self.get(f"acoustic({j})%gauss_sigma_time")
gauss_sigma_dist = self.get(f"acoustic({j})%gauss_sigma_dist")
bb_num_freq = self.get(f"acoustic({j})%bb_num_freq")
bb_bandwidth = self.get(f"acoustic({j})%bb_bandwidth")
bb_lowest_freq = self.get(f"acoustic({j})%bb_lowest_freq")
npulse = self.get(f"acoustic({j})%npulse")
dipole = self.get(f"acoustic({j})%dipole", "F") == "T"
dir_val = self.get(f"acoustic({j})%dir")
delay = self.get(f"acoustic({j})%delay")
length = self.get(f"acoustic({j})%length")
height = self.get(f"acoustic({j})%height")
foc_length = self.get(f"acoustic({j})%foc_length")
aperture = self.get(f"acoustic({j})%aperture")
num_elements = self.get(f"acoustic({j})%num_elements")
element_on = self.get(f"acoustic({j})%element_on")
element_spacing_angle = self.get(f"acoustic({j})%element_spacing_angle")
element_polygon_ratio = self.get(f"acoustic({j})%element_polygon_ratio")
self.prohibit(support is None, f"acoustic({jstr})%support must be specified for acoustic_source")