pycphy 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. pycphy/__init__.py +11 -0
  2. pycphy/cli.py +145 -0
  3. pycphy/config_manager.py +373 -0
  4. pycphy/foamCaseDeveloper/__init__.py +60 -41
  5. pycphy/foamCaseDeveloper/config/__init__.py +69 -26
  6. pycphy/foamCaseDeveloper/config/cad_mesh_config.py +62 -0
  7. pycphy/foamCaseDeveloper/config/config_hfdibdem.py +193 -0
  8. pycphy/foamCaseDeveloper/config/constant/__init__.py +23 -0
  9. pycphy/foamCaseDeveloper/config/constant/dynamic_mesh_config.py +208 -0
  10. pycphy/foamCaseDeveloper/config/constant/gravity_field_config.py +379 -0
  11. pycphy/foamCaseDeveloper/config/constant/transport_properties_config.py +225 -0
  12. pycphy/foamCaseDeveloper/config/constant/turbulence_config.py +617 -0
  13. pycphy/foamCaseDeveloper/config/csv_boundary_reader.py +219 -0
  14. pycphy/foamCaseDeveloper/config/system/__init__.py +31 -0
  15. pycphy/foamCaseDeveloper/config/system/block_mesh_config.py +184 -0
  16. pycphy/foamCaseDeveloper/config/{control_config.py → system/control_config.py} +113 -1
  17. pycphy/foamCaseDeveloper/config/system/decompose_par_config.py +525 -0
  18. pycphy/foamCaseDeveloper/config/system/fv_options_config.py +575 -0
  19. pycphy/foamCaseDeveloper/config/system/fv_schemes_config.py +363 -0
  20. pycphy/foamCaseDeveloper/config/system/set_fields_config.py +640 -0
  21. pycphy/foamCaseDeveloper/config/system/snappy_hex_mesh_config.py +241 -0
  22. pycphy/foamCaseDeveloper/config/zero/U_config.py +135 -0
  23. pycphy/foamCaseDeveloper/config/zero/__init__.py +22 -0
  24. pycphy/foamCaseDeveloper/config/zero/f_config.py +140 -0
  25. pycphy/foamCaseDeveloper/config/zero/lambda_config.py +157 -0
  26. pycphy/foamCaseDeveloper/config/zero/p_config.py +97 -0
  27. pycphy/foamCaseDeveloper/core/__init__.py +30 -18
  28. pycphy/foamCaseDeveloper/core/block_mesh_developer.py +1 -1
  29. pycphy/foamCaseDeveloper/core/cad_block_mesh_developer.py +463 -0
  30. pycphy/foamCaseDeveloper/core/case_builder.py +1217 -0
  31. pycphy/foamCaseDeveloper/core/foam_case_manager.py +370 -111
  32. pycphy/foamCaseDeveloper/develop_case.py +640 -0
  33. pycphy/foamCaseDeveloper/main.py +260 -260
  34. pycphy/foamCaseDeveloper/utils/myAutoCAD.py +418 -0
  35. pycphy/foamCaseDeveloper/writers/__init__.py +37 -4
  36. pycphy/foamCaseDeveloper/writers/constant/__init__.py +25 -0
  37. pycphy/foamCaseDeveloper/writers/constant/dynamic_mesh_dict_writer.py +75 -0
  38. pycphy/foamCaseDeveloper/writers/constant/gravity_field_writer.py +88 -0
  39. pycphy/foamCaseDeveloper/writers/constant/hfdibdem_dict_writer.py +81 -0
  40. pycphy/foamCaseDeveloper/writers/constant/transport_properties_writer.py +202 -0
  41. pycphy/foamCaseDeveloper/writers/{turbulence_properties_writer.py → constant/turbulence_properties_writer.py} +49 -1
  42. pycphy/foamCaseDeveloper/writers/system/__init__.py +31 -0
  43. pycphy/foamCaseDeveloper/writers/{block_mesh_writer.py → system/block_mesh_writer.py} +1 -1
  44. pycphy/foamCaseDeveloper/writers/{control_dict_writer.py → system/control_dict_writer.py} +37 -1
  45. pycphy/foamCaseDeveloper/writers/system/decompose_par_writer.py +228 -0
  46. pycphy/foamCaseDeveloper/writers/system/fv_options_writer.py +188 -0
  47. pycphy/foamCaseDeveloper/writers/system/fv_schemes_writer.py +155 -0
  48. pycphy/foamCaseDeveloper/writers/system/set_fields_writer.py +191 -0
  49. pycphy/foamCaseDeveloper/writers/system/snappy_hex_mesh_writer.py +123 -0
  50. pycphy/foamCaseDeveloper/writers/zero/__init__.py +24 -0
  51. pycphy/foamCaseDeveloper/writers/zero/f_field_writer.py +89 -0
  52. pycphy/foamCaseDeveloper/writers/zero/lambda_field_writer.py +84 -0
  53. pycphy/foamCaseDeveloper/writers/zero/p_field_writer.py +89 -0
  54. pycphy/foamCaseDeveloper/writers/zero/u_field_writer.py +96 -0
  55. pycphy/foamCaseDeveloper/writers/zero/zero_field_factory.py +388 -0
  56. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/METADATA +154 -6
  57. pycphy-0.2.0.dist-info/RECORD +63 -0
  58. pycphy-0.2.0.dist-info/entry_points.txt +3 -0
  59. pycphy/foamCaseDeveloper/config/block_mesh_config.py +0 -90
  60. pycphy/foamCaseDeveloper/config/turbulence_config.py +0 -187
  61. pycphy/foamCaseDeveloper/core/control_dict_writer.py +0 -55
  62. pycphy/foamCaseDeveloper/core/turbulence_properties_writer.py +0 -68
  63. pycphy-0.1.0.dist-info/RECORD +0 -24
  64. pycphy-0.1.0.dist-info/entry_points.txt +0 -2
  65. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/WHEEL +0 -0
  66. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/licenses/LICENSE +0 -0
  67. {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1217 @@
1
+ # case_builder.py
2
+
3
+ """
4
+ Case builder module for OpenFOAM case creation.
5
+
6
+ This module provides a more modular approach to building OpenFOAM cases
7
+ with clear separation of concerns and better error handling.
8
+ """
9
+
10
+ import os
11
+ from typing import Dict, Any
12
+ from abc import ABC, abstractmethod
13
+
14
+ from ..writers.system.block_mesh_writer import BlockMeshWriter
15
+ from ..writers.system.control_dict_writer import ControlDictWriter
16
+ from ..writers.system.fv_schemes_writer import FvSchemesWriter
17
+ from ..writers.system.fv_options_writer import FvOptionsWriter
18
+ from ..writers.system.set_fields_writer import SetFieldsWriter
19
+ from ..writers.system.decompose_par_writer import DecomposeParWriter
20
+ from ..writers.system.snappy_hex_mesh_writer import SnappyHexMeshWriter
21
+
22
+ from ..writers.constant.turbulence_properties_writer import TurbulencePropertiesWriter
23
+ from ..writers.constant.dynamic_mesh_dict_writer import DynamicMeshDictWriter
24
+ from ..writers.constant.hfdibdem_dict_writer import HFDIBDEMDictWriter
25
+ from ..writers.constant.transport_properties_writer import TransportPropertiesWriter
26
+ from ..writers.constant.gravity_field_writer import GravityFieldWriter
27
+
28
+ from ..writers.zero.p_field_writer import PFieldWriter
29
+ from ..writers.zero.u_field_writer import UFieldWriter
30
+ from ..writers.zero.f_field_writer import FFieldWriter
31
+ from ..writers.zero.lambda_field_writer import LambdaFieldWriter
32
+ from .block_mesh_developer import BlockMeshDeveloper
33
+
34
+
35
+ class CaseComponent(ABC):
36
+ """
37
+ Abstract base class for OpenFOAM case components.
38
+
39
+ Each component represents a specific part of an OpenFOAM case
40
+ (e.g., mesh, control, turbulence, etc.).
41
+ """
42
+
43
+ def __init__(self, name: str, description: str = ""):
44
+ """
45
+ Initialize the case component.
46
+
47
+ Args:
48
+ name (str): Name of the component.
49
+ description (str): Description of the component.
50
+ """
51
+ self.name = name
52
+ self.description = description
53
+ self.is_configured = False
54
+ self.is_required = True
55
+
56
+ @abstractmethod
57
+ def configure(self, **kwargs) -> bool:
58
+ """
59
+ Configure the component with the provided parameters.
60
+
61
+ Args:
62
+ **kwargs: Configuration parameters.
63
+
64
+ Returns:
65
+ bool: True if configuration successful, False otherwise.
66
+ """
67
+ pass
68
+
69
+ @abstractmethod
70
+ def build(self, case_dir: str) -> bool:
71
+ """
72
+ Build the component (create files, etc.).
73
+
74
+ Args:
75
+ case_dir (str): Path to the OpenFOAM case directory.
76
+
77
+ Returns:
78
+ bool: True if build successful, False otherwise.
79
+ """
80
+ pass
81
+
82
+ @abstractmethod
83
+ def validate(self) -> bool:
84
+ """
85
+ Validate the component configuration.
86
+
87
+ Returns:
88
+ bool: True if validation passes, False otherwise.
89
+ """
90
+ pass
91
+
92
+ def get_status(self) -> Dict[str, Any]:
93
+ """
94
+ Get the current status of the component.
95
+
96
+ Returns:
97
+ Dict containing status information.
98
+ """
99
+ return {
100
+ "name": self.name,
101
+ "description": self.description,
102
+ "is_configured": self.is_configured,
103
+ "is_required": self.is_required,
104
+ "is_valid": self.validate() if self.is_configured else False
105
+ }
106
+
107
+
108
+ class GeometryComponent(CaseComponent):
109
+ """Component for handling geometry and mesh configuration."""
110
+
111
+ def __init__(self):
112
+ super().__init__("Geometry", "Mesh geometry and blockMeshDict configuration")
113
+ self.geometry_config = {}
114
+ self.developer = None
115
+
116
+ def configure(self, p0: tuple, p1: tuple, cells: tuple,
117
+ patch_names: Dict[str, str], scale: float = 1.0) -> bool:
118
+ """
119
+ Configure the geometry component.
120
+
121
+ Args:
122
+ p0 (tuple): Minimum corner coordinates (x0, y0, z0).
123
+ p1 (tuple): Maximum corner coordinates (x1, y1, z1).
124
+ cells (tuple): Number of cells in each direction (nx, ny, nz).
125
+ patch_names (Dict): Mapping of face identifiers to custom names.
126
+ scale (float): Scaling factor for the mesh.
127
+
128
+ Returns:
129
+ bool: True if configuration successful, False otherwise.
130
+ """
131
+ try:
132
+ self.geometry_config = {
133
+ 'p0': p0,
134
+ 'p1': p1,
135
+ 'cells': cells,
136
+ 'patch_names': patch_names,
137
+ 'scale': scale
138
+ }
139
+
140
+ self.developer = BlockMeshDeveloper(
141
+ p0=p0, p1=p1, cells=cells,
142
+ patch_names=patch_names, scale=scale
143
+ )
144
+
145
+ self.is_configured = True
146
+ return True
147
+
148
+ except Exception as e:
149
+ print(f"Error configuring geometry: {e}")
150
+ return False
151
+
152
+ def build(self, case_dir: str) -> bool:
153
+ """Build the blockMeshDict file."""
154
+ if not self.is_configured:
155
+ print("Error: Geometry not configured")
156
+ return False
157
+
158
+ try:
159
+ system_dir = os.path.join(case_dir, "system")
160
+ os.makedirs(system_dir, exist_ok=True)
161
+
162
+ bmd_path = os.path.join(system_dir, "blockMeshDict")
163
+
164
+ # Check if this is CAD-based geometry (developer is None but blockMeshDict exists)
165
+ if self.developer is None:
166
+ # For CAD-based generation, the blockMeshDict should already exist
167
+ if os.path.exists(bmd_path):
168
+ print(f"Using existing blockMeshDict from CAD generation: {bmd_path}")
169
+ return True
170
+ else:
171
+ print(f"Error: CAD-based geometry configured but blockMeshDict not found at {bmd_path}")
172
+ return False
173
+ else:
174
+ # Traditional geometry generation
175
+ self.developer.create_blockmesh_dict(file_path=bmd_path)
176
+ return True
177
+
178
+ except Exception as e:
179
+ print(f"Error building geometry: {e}")
180
+ return False
181
+
182
+ def validate(self) -> bool:
183
+ """Validate the geometry configuration."""
184
+ if not self.is_configured:
185
+ return False
186
+
187
+ # Check if this is CAD-based geometry (has CAD mesh summary)
188
+ if 'total_blocks' in self.geometry_config and 'total_patches' in self.geometry_config:
189
+ # CAD-based geometry validation
190
+ total_blocks = self.geometry_config.get('total_blocks', 0)
191
+ total_patches = self.geometry_config.get('total_patches', 0)
192
+ total_vertices = self.geometry_config.get('total_vertices', 0)
193
+
194
+ if total_blocks <= 0:
195
+ print(f"Invalid CAD geometry: {total_blocks} blocks")
196
+ return False
197
+
198
+ if total_patches <= 0:
199
+ print(f"Invalid CAD geometry: {total_patches} patches")
200
+ return False
201
+
202
+ if total_vertices <= 0:
203
+ print(f"Invalid CAD geometry: {total_vertices} vertices")
204
+ return False
205
+
206
+ return True
207
+
208
+ # Traditional geometry validation
209
+ required_keys = ['p0', 'p1', 'cells', 'patch_names']
210
+ for key in required_keys:
211
+ if key not in self.geometry_config:
212
+ print(f"Missing required geometry parameter: {key}")
213
+ return False
214
+
215
+ # Check coordinate validity
216
+ p0, p1, cells = self.geometry_config['p0'], self.geometry_config['p1'], self.geometry_config['cells']
217
+
218
+ for i, (coord0, coord1) in enumerate(zip(p0, p1)):
219
+ if coord1 <= coord0:
220
+ print(f"Invalid geometry: p1[{i}] ({coord1}) must be > p0[{i}] ({coord0})")
221
+ return False
222
+
223
+ for i, cell_count in enumerate(cells):
224
+ if not isinstance(cell_count, int) or cell_count <= 0:
225
+ print(f"Invalid cell count in dimension {i}: {cell_count}")
226
+ return False
227
+
228
+ return True
229
+
230
+
231
+ class ControlComponent(CaseComponent):
232
+ """Component for handling control dictionary configuration."""
233
+
234
+ def __init__(self):
235
+ super().__init__("Control", "Solver control and time stepping configuration")
236
+ self.control_params = {}
237
+ self.writer = None
238
+
239
+ def configure(self, control_params: Dict[str, Any]) -> bool:
240
+ """
241
+ Configure the control component.
242
+
243
+ Args:
244
+ control_params (Dict): Dictionary of control parameters.
245
+
246
+ Returns:
247
+ bool: True if configuration successful, False otherwise.
248
+ """
249
+ try:
250
+ self.control_params = control_params.copy()
251
+ self.is_configured = True
252
+ return True
253
+
254
+ except Exception as e:
255
+ print(f"Error configuring control: {e}")
256
+ return False
257
+
258
+ def build(self, case_dir: str) -> bool:
259
+ """Build the controlDict file."""
260
+ if not self.is_configured:
261
+ print("Error: Control not configured")
262
+ return False
263
+
264
+ try:
265
+ system_dir = os.path.join(case_dir, "system")
266
+ os.makedirs(system_dir, exist_ok=True)
267
+
268
+ cd_path = os.path.join(system_dir, "controlDict")
269
+ self.writer = ControlDictWriter(file_path=cd_path, params=self.control_params)
270
+
271
+ # Validate parameters before writing
272
+ if not self.writer.validate_params():
273
+ print("Warning: Control parameters validation failed, but proceeding anyway.")
274
+
275
+ self.writer.write()
276
+ return True
277
+
278
+ except Exception as e:
279
+ print(f"Error building control: {e}")
280
+ return False
281
+
282
+ def validate(self) -> bool:
283
+ """Validate the control configuration."""
284
+ if not self.is_configured:
285
+ return False
286
+
287
+ required_params = ['application', 'startFrom', 'stopAt']
288
+ for param in required_params:
289
+ if param not in self.control_params:
290
+ print(f"Missing required control parameter: {param}")
291
+ return False
292
+
293
+ return True
294
+
295
+
296
+ class TurbulenceComponent(CaseComponent):
297
+ """Component for handling turbulence model configuration."""
298
+
299
+ def __init__(self):
300
+ super().__init__("Turbulence", "Turbulence model and properties configuration")
301
+ self.simulation_type = ""
302
+ self.model_properties = {}
303
+ self.writer = None
304
+
305
+ def configure(self, simulation_type: str, model_properties: Dict[str, Any]) -> bool:
306
+ """
307
+ Configure the turbulence component.
308
+
309
+ Args:
310
+ simulation_type (str): Type of turbulence simulation.
311
+ model_properties (Dict): Properties for the turbulence model.
312
+
313
+ Returns:
314
+ bool: True if configuration successful, False otherwise.
315
+ """
316
+ try:
317
+ self.simulation_type = simulation_type
318
+ self.model_properties = model_properties.copy()
319
+ self.is_configured = True
320
+ return True
321
+
322
+ except Exception as e:
323
+ print(f"Error configuring turbulence: {e}")
324
+ return False
325
+
326
+ def build(self, case_dir: str) -> bool:
327
+ """Build the turbulenceProperties file."""
328
+ if not self.is_configured:
329
+ print("Error: Turbulence not configured")
330
+ return False
331
+
332
+ try:
333
+ constant_dir = os.path.join(case_dir, "constant")
334
+ os.makedirs(constant_dir, exist_ok=True)
335
+
336
+ tp_path = os.path.join(constant_dir, "turbulenceProperties")
337
+ self.writer = TurbulencePropertiesWriter(
338
+ file_path=tp_path,
339
+ simulation_type=self.simulation_type,
340
+ model_properties=self.model_properties
341
+ )
342
+
343
+ # Validate configuration before writing
344
+ if not self.writer.validate_simulation_type():
345
+ print("Warning: Simulation type validation failed, but proceeding anyway.")
346
+
347
+ if not self.writer.validate_model_properties():
348
+ print("Warning: Model properties validation failed, but proceeding anyway.")
349
+
350
+ self.writer.write()
351
+ return True
352
+
353
+ except Exception as e:
354
+ print(f"Error building turbulence: {e}")
355
+ return False
356
+
357
+ def validate(self) -> bool:
358
+ """Validate the turbulence configuration."""
359
+ if not self.is_configured:
360
+ return False
361
+
362
+ valid_types = ['RAS', 'LES', 'laminar']
363
+ if self.simulation_type not in valid_types:
364
+ print(f"Invalid simulation type: {self.simulation_type}")
365
+ return False
366
+
367
+ return True
368
+
369
+
370
+ class DynamicMeshComponent(CaseComponent):
371
+ """Component for handling dynamic mesh configuration."""
372
+
373
+ def __init__(self):
374
+ super().__init__("DynamicMesh", "Dynamic mesh configuration")
375
+ self.is_required = False # Optional component
376
+ self.write_dynamic_mesh_dict = False
377
+ self.mesh_type = ""
378
+ self.mesh_properties = {}
379
+ self.writer = None
380
+
381
+ def configure(self, write_dynamic_mesh_dict: bool, mesh_type: str,
382
+ mesh_properties: Dict[str, Any]) -> bool:
383
+ """
384
+ Configure the dynamic mesh component.
385
+
386
+ Args:
387
+ write_dynamic_mesh_dict (bool): Whether to write dynamicMeshDict.
388
+ mesh_type (str): Type of dynamic mesh.
389
+ mesh_properties (Dict): Properties for the dynamic mesh.
390
+
391
+ Returns:
392
+ bool: True if configuration successful, False otherwise.
393
+ """
394
+ try:
395
+ self.write_dynamic_mesh_dict = write_dynamic_mesh_dict
396
+ self.mesh_type = mesh_type
397
+ self.mesh_properties = mesh_properties.copy()
398
+ self.is_configured = True
399
+ return True
400
+
401
+ except Exception as e:
402
+ print(f"Error configuring dynamic mesh: {e}")
403
+ return False
404
+
405
+ def build(self, case_dir: str) -> bool:
406
+ """Build the dynamicMeshDict file (if enabled)."""
407
+ if not self.is_configured:
408
+ print("Error: Dynamic mesh not configured")
409
+ return False
410
+
411
+ if not self.write_dynamic_mesh_dict:
412
+ print("Dynamic mesh dictionary creation skipped (write_dynamic_mesh_dict is False).")
413
+ return True
414
+
415
+ try:
416
+ constant_dir = os.path.join(case_dir, "constant")
417
+ os.makedirs(constant_dir, exist_ok=True)
418
+
419
+ dmd_path = os.path.join(constant_dir, "dynamicMeshDict")
420
+ self.writer = DynamicMeshDictWriter(
421
+ file_path=dmd_path,
422
+ properties=self.mesh_properties
423
+ )
424
+ self.writer.write()
425
+ return True
426
+
427
+ except Exception as e:
428
+ print(f"Error building dynamic mesh: {e}")
429
+ return False
430
+
431
+ def validate(self) -> bool:
432
+ """Validate the dynamic mesh configuration."""
433
+ if not self.is_configured:
434
+ return False
435
+
436
+ if not self.write_dynamic_mesh_dict:
437
+ return True # Skip validation if not enabled
438
+
439
+ valid_types = ['solidBodyMotion', 'multiBodyOverset', 'adaptiveRefinement', 'morphingMesh']
440
+ if self.mesh_type not in valid_types:
441
+ print(f"Invalid mesh type: {self.mesh_type}")
442
+ return False
443
+
444
+ return True
445
+
446
+
447
+ class HFDIBDEMComponent(CaseComponent):
448
+ """Component for handling HFDIBDEM configuration."""
449
+
450
+ def __init__(self):
451
+ super().__init__("HFDIBDEM", "Immersed Boundary DEM configuration")
452
+ self.is_required = False # Optional component
453
+ self.write_hfdibdem_dict = False
454
+ self.hfdibdem_properties = {}
455
+ self.writer = None
456
+
457
+ def configure(self, write_hfdibdem_dict: bool, hfdibdem_properties: Dict[str, Any]) -> bool:
458
+ """
459
+ Configure the HFDIBDEM component.
460
+
461
+ Args:
462
+ write_hfdibdem_dict (bool): Whether to write HFDIBDEMDict.
463
+ hfdibdem_properties (Dict): Properties for the HFDIBDEM configuration.
464
+
465
+ Returns:
466
+ bool: True if configuration successful, False otherwise.
467
+ """
468
+ try:
469
+ self.write_hfdibdem_dict = write_hfdibdem_dict
470
+ self.hfdibdem_properties = hfdibdem_properties.copy()
471
+ self.is_configured = True
472
+ return True
473
+
474
+ except Exception as e:
475
+ print(f"Error configuring HFDIBDEM: {e}")
476
+ return False
477
+
478
+ def build(self, case_dir: str) -> bool:
479
+ """Build the HFDIBDEMDict file (if enabled)."""
480
+ if not self.is_configured:
481
+ print("Error: HFDIBDEM not configured")
482
+ return False
483
+
484
+ if not self.write_hfdibdem_dict:
485
+ print("HFDIBDEM dictionary creation skipped (write_hfdibdem_dict is False).")
486
+ return True
487
+
488
+ try:
489
+ constant_dir = os.path.join(case_dir, "constant")
490
+ os.makedirs(constant_dir, exist_ok=True)
491
+
492
+ hfdibdem_path = os.path.join(constant_dir, "HFDIBDEMDict")
493
+ self.writer = HFDIBDEMDictWriter(
494
+ file_path=hfdibdem_path,
495
+ properties=self.hfdibdem_properties
496
+ )
497
+ self.writer.write()
498
+ return True
499
+
500
+ except Exception as e:
501
+ print(f"Error building HFDIBDEM: {e}")
502
+ return False
503
+
504
+ def validate(self) -> bool:
505
+ """Validate the HFDIBDEM configuration."""
506
+ if not self.is_configured:
507
+ return False
508
+
509
+ if not self.write_hfdibdem_dict:
510
+ return True # Skip validation if not enabled
511
+
512
+ # Basic validation for HFDIBDEM properties
513
+ required_keys = ['bodyNames', 'DEM', 'virtualMesh']
514
+ for key in required_keys:
515
+ if key not in self.hfdibdem_properties:
516
+ print(f"Missing required HFDIBDEM parameter: {key}")
517
+ return False
518
+
519
+ return True
520
+
521
+
522
+ class TransportPropertiesComponent(CaseComponent):
523
+ """Component for handling transport properties configuration."""
524
+
525
+ def __init__(self):
526
+ super().__init__("TransportProperties", "Fluid transport properties configuration")
527
+ self.is_required = False # Optional component
528
+ self.write_transport_properties = False
529
+ self.transport_model = ""
530
+ self.model_properties = {}
531
+ self.thermal_properties = {}
532
+ self.species_properties = {}
533
+ self.advanced_properties = {}
534
+ self.writer = None
535
+
536
+ def configure(self, write_transport_properties: bool, transport_model: str,
537
+ model_properties: Dict[str, Any], thermal_properties: Dict[str, Any] = None,
538
+ species_properties: Dict[str, Any] = None, advanced_properties: Dict[str, Any] = None) -> bool:
539
+ """Configure the transport properties component."""
540
+ try:
541
+ self.write_transport_properties = write_transport_properties
542
+ self.transport_model = transport_model
543
+ self.model_properties = model_properties.copy()
544
+ self.thermal_properties = thermal_properties or {}
545
+ self.species_properties = species_properties or {}
546
+ self.advanced_properties = advanced_properties or {}
547
+ self.is_configured = True
548
+ return True
549
+ except Exception as e:
550
+ print(f"Error configuring transport properties: {e}")
551
+ return False
552
+
553
+ def build(self, case_dir: str) -> bool:
554
+ """Build the transportProperties file (if enabled)."""
555
+ if not self.is_configured or not self.write_transport_properties:
556
+ return True
557
+
558
+ try:
559
+ constant_dir = os.path.join(case_dir, "constant")
560
+ os.makedirs(constant_dir, exist_ok=True)
561
+
562
+ tp_path = os.path.join(constant_dir, "transportProperties")
563
+ self.writer = TransportPropertiesWriter(
564
+ file_path=tp_path,
565
+ transport_model=self.transport_model,
566
+ model_properties=self.model_properties,
567
+ thermal_properties=self.thermal_properties,
568
+ species_properties=self.species_properties,
569
+ advanced_properties=self.advanced_properties
570
+ )
571
+ self.writer.write()
572
+ return True
573
+ except Exception as e:
574
+ print(f"Error building transport properties: {e}")
575
+ return False
576
+
577
+ def validate(self) -> bool:
578
+ """Validate the transport properties configuration."""
579
+ if not self.is_configured or not self.write_transport_properties:
580
+ return True
581
+ valid_models = ['Newtonian', 'NonNewtonian', 'BirdCarreau', 'CrossPowerLaw',
582
+ 'HerschelBulkley', 'PowerLaw', 'Casson', 'GeneralizedNewtonian']
583
+ if self.transport_model not in valid_models:
584
+ print(f"Invalid transport model: {self.transport_model}")
585
+ return False
586
+ return True
587
+
588
+
589
+ class FvSchemesComponent(CaseComponent):
590
+ """Component for handling finite volume schemes configuration."""
591
+
592
+ def __init__(self):
593
+ super().__init__("FvSchemes", "Finite volume discretization schemes configuration")
594
+ self.is_required = False
595
+ self.write_fv_schemes = False
596
+ self.ddt_schemes = {}
597
+ self.grad_schemes = {}
598
+ self.div_schemes = {}
599
+ self.laplacian_schemes = {}
600
+ self.interpolation_schemes = {}
601
+ self.sn_grad_schemes = {}
602
+ self.flux_required = {}
603
+ self.writer = None
604
+
605
+ def configure(self, write_fv_schemes: bool, ddt_schemes: Dict[str, Any],
606
+ grad_schemes: Dict[str, Any], div_schemes: Dict[str, Any],
607
+ laplacian_schemes: Dict[str, Any], interpolation_schemes: Dict[str, Any],
608
+ sn_grad_schemes: Dict[str, Any], flux_required: Dict[str, Any] = None) -> bool:
609
+ """Configure the fvSchemes component."""
610
+ try:
611
+ self.write_fv_schemes = write_fv_schemes
612
+ self.ddt_schemes = ddt_schemes.copy()
613
+ self.grad_schemes = grad_schemes.copy()
614
+ self.div_schemes = div_schemes.copy()
615
+ self.laplacian_schemes = laplacian_schemes.copy()
616
+ self.interpolation_schemes = interpolation_schemes.copy()
617
+ self.sn_grad_schemes = sn_grad_schemes.copy()
618
+ self.flux_required = flux_required or {}
619
+ self.is_configured = True
620
+ return True
621
+ except Exception as e:
622
+ print(f"Error configuring fvSchemes: {e}")
623
+ return False
624
+
625
+ def build(self, case_dir: str) -> bool:
626
+ """Build the fvSchemes file (if enabled)."""
627
+ if not self.is_configured or not self.write_fv_schemes:
628
+ return True
629
+
630
+ try:
631
+ system_dir = os.path.join(case_dir, "system")
632
+ os.makedirs(system_dir, exist_ok=True)
633
+
634
+ fvs_path = os.path.join(system_dir, "fvSchemes")
635
+ self.writer = FvSchemesWriter(
636
+ file_path=fvs_path,
637
+ ddt_schemes=self.ddt_schemes,
638
+ grad_schemes=self.grad_schemes,
639
+ div_schemes=self.div_schemes,
640
+ laplacian_schemes=self.laplacian_schemes,
641
+ interpolation_schemes=self.interpolation_schemes,
642
+ sn_grad_schemes=self.sn_grad_schemes,
643
+ flux_required=self.flux_required
644
+ )
645
+ self.writer.write()
646
+ return True
647
+ except Exception as e:
648
+ print(f"Error building fvSchemes: {e}")
649
+ return False
650
+
651
+ def validate(self) -> bool:
652
+ """Validate the fvSchemes configuration."""
653
+ return True
654
+
655
+
656
+ class FvOptionsComponent(CaseComponent):
657
+ """Component for handling finite volume options configuration."""
658
+
659
+ def __init__(self):
660
+ super().__init__("FvOptions", "Finite volume source terms and constraints configuration")
661
+ self.is_required = False
662
+ self.write_fv_options = False
663
+ self.momentum_sources = {}
664
+ self.thermal_sources = {}
665
+ self.species_sources = {}
666
+ self.turbulence_sources = {}
667
+ self.pressure_sources = {}
668
+ self.volume_fraction_sources = {}
669
+ self.advanced_sources = {}
670
+ self.writer = None
671
+
672
+ def configure(self, write_fv_options: bool, momentum_sources: Dict[str, Any] = None,
673
+ thermal_sources: Dict[str, Any] = None, species_sources: Dict[str, Any] = None,
674
+ turbulence_sources: Dict[str, Any] = None, pressure_sources: Dict[str, Any] = None,
675
+ volume_fraction_sources: Dict[str, Any] = None, advanced_sources: Dict[str, Any] = None) -> bool:
676
+ """Configure the fvOptions component."""
677
+ try:
678
+ self.write_fv_options = write_fv_options
679
+ self.momentum_sources = momentum_sources or {}
680
+ self.thermal_sources = thermal_sources or {}
681
+ self.species_sources = species_sources or {}
682
+ self.turbulence_sources = turbulence_sources or {}
683
+ self.pressure_sources = pressure_sources or {}
684
+ self.volume_fraction_sources = volume_fraction_sources or {}
685
+ self.advanced_sources = advanced_sources or {}
686
+ self.is_configured = True
687
+ return True
688
+ except Exception as e:
689
+ print(f"Error configuring fvOptions: {e}")
690
+ return False
691
+
692
+ def build(self, case_dir: str) -> bool:
693
+ """Build the fvOptions file (if enabled)."""
694
+ if not self.is_configured or not self.write_fv_options:
695
+ return True
696
+
697
+ try:
698
+ system_dir = os.path.join(case_dir, "system")
699
+ os.makedirs(system_dir, exist_ok=True)
700
+
701
+ fvo_path = os.path.join(system_dir, "fvOptions")
702
+ self.writer = FvOptionsWriter(
703
+ file_path=fvo_path,
704
+ momentum_sources=self.momentum_sources,
705
+ thermal_sources=self.thermal_sources,
706
+ species_sources=self.species_sources,
707
+ turbulence_sources=self.turbulence_sources,
708
+ pressure_sources=self.pressure_sources,
709
+ volume_fraction_sources=self.volume_fraction_sources,
710
+ advanced_sources=self.advanced_sources
711
+ )
712
+ self.writer.write()
713
+ return True
714
+ except Exception as e:
715
+ print(f"Error building fvOptions: {e}")
716
+ return False
717
+
718
+ def validate(self) -> bool:
719
+ """Validate the fvOptions configuration."""
720
+ return True
721
+
722
+
723
+ class GravityFieldComponent(CaseComponent):
724
+ """Component for handling gravity field configuration."""
725
+
726
+ def __init__(self):
727
+ super().__init__("GravityField", "Gravity field configuration")
728
+ self.is_required = False
729
+ self.write_gravity_field = False
730
+ self.gravity_value = (0, 0, -9.81)
731
+ self.dimensions = [0, 1, -2, 0, 0, 0, 0]
732
+ self.writer = None
733
+
734
+ def configure(self, write_gravity_field: bool, gravity_value: tuple,
735
+ dimensions: list = None) -> bool:
736
+ """Configure the gravity field component."""
737
+ try:
738
+ self.write_gravity_field = write_gravity_field
739
+ self.gravity_value = gravity_value
740
+ self.dimensions = dimensions or [0, 1, -2, 0, 0, 0, 0]
741
+ self.is_configured = True
742
+ return True
743
+ except Exception as e:
744
+ print(f"Error configuring gravity field: {e}")
745
+ return False
746
+
747
+ def build(self, case_dir: str) -> bool:
748
+ """Build the gravity field file (if enabled)."""
749
+ if not self.is_configured or not self.write_gravity_field:
750
+ return True
751
+
752
+ try:
753
+ constant_dir = os.path.join(case_dir, "constant")
754
+ os.makedirs(constant_dir, exist_ok=True)
755
+
756
+ g_path = os.path.join(constant_dir, "g")
757
+ self.writer = GravityFieldWriter(
758
+ file_path=g_path,
759
+ gravity_value=self.gravity_value,
760
+ dimensions=self.dimensions
761
+ )
762
+ self.writer.write()
763
+ return True
764
+ except Exception as e:
765
+ print(f"Error building gravity field: {e}")
766
+ return False
767
+
768
+ def validate(self) -> bool:
769
+ """Validate the gravity field configuration."""
770
+ if not self.is_configured or not self.write_gravity_field:
771
+ return True
772
+ if not isinstance(self.gravity_value, (tuple, list)) or len(self.gravity_value) != 3:
773
+ print("Gravity value must be a tuple/list with 3 components")
774
+ return False
775
+ return True
776
+
777
+
778
+ class SetFieldsComponent(CaseComponent):
779
+ """Component for handling setFields configuration."""
780
+
781
+ def __init__(self):
782
+ super().__init__("SetFields", "Field initialization configuration")
783
+ self.is_required = False
784
+ self.write_set_fields_dict = False
785
+ self.default_field_values = {}
786
+ self.regions = {}
787
+ self.writer = None
788
+
789
+ def configure(self, write_set_fields_dict: bool, default_field_values: Dict[str, Any] = None,
790
+ regions: Dict[str, Any] = None) -> bool:
791
+ """Configure the setFields component."""
792
+ try:
793
+ self.write_set_fields_dict = write_set_fields_dict
794
+ self.default_field_values = default_field_values or {}
795
+ self.regions = regions or {}
796
+ self.is_configured = True
797
+ return True
798
+ except Exception as e:
799
+ print(f"Error configuring setFields: {e}")
800
+ return False
801
+
802
+ def build(self, case_dir: str) -> bool:
803
+ """Build the setFieldsDict file (if enabled)."""
804
+ if not self.is_configured or not self.write_set_fields_dict:
805
+ return True
806
+
807
+ try:
808
+ system_dir = os.path.join(case_dir, "system")
809
+ os.makedirs(system_dir, exist_ok=True)
810
+
811
+ sf_path = os.path.join(system_dir, "setFieldsDict")
812
+ self.writer = SetFieldsWriter(
813
+ file_path=sf_path,
814
+ default_field_values=self.default_field_values,
815
+ regions=self.regions
816
+ )
817
+ self.writer.write()
818
+ return True
819
+ except Exception as e:
820
+ print(f"Error building setFields: {e}")
821
+ return False
822
+
823
+ def validate(self) -> bool:
824
+ """Validate the setFields configuration."""
825
+ return True
826
+
827
+
828
+ class DecomposeParComponent(CaseComponent):
829
+ """Component for handling decomposePar configuration."""
830
+
831
+ def __init__(self):
832
+ super().__init__("DecomposePar", "Parallel decomposition configuration")
833
+ self.is_required = False
834
+ self.write_decompose_par_dict = False
835
+ self.number_of_subdomains = 1
836
+ self.method = "scotch"
837
+ self.coeffs = {}
838
+ self.options = {}
839
+ self.fields = []
840
+ self.preserve_patches = []
841
+ self.preserve_cell_zones = []
842
+ self.preserve_face_zones = []
843
+ self.preserve_point_zones = []
844
+ self.writer = None
845
+
846
+ def configure(self, write_decompose_par_dict: bool, number_of_subdomains: int,
847
+ method: str, coeffs: Dict[str, Any] = None, options: Dict[str, Any] = None,
848
+ fields: list = None, preserve_patches: list = None, preserve_cell_zones: list = None,
849
+ preserve_face_zones: list = None, preserve_point_zones: list = None) -> bool:
850
+ """Configure the decomposePar component."""
851
+ try:
852
+ self.write_decompose_par_dict = write_decompose_par_dict
853
+ self.number_of_subdomains = number_of_subdomains
854
+ self.method = method
855
+ self.coeffs = coeffs or {}
856
+ self.options = options or {}
857
+ self.fields = fields or []
858
+ self.preserve_patches = preserve_patches or []
859
+ self.preserve_cell_zones = preserve_cell_zones or []
860
+ self.preserve_face_zones = preserve_face_zones or []
861
+ self.preserve_point_zones = preserve_point_zones or []
862
+ self.is_configured = True
863
+ return True
864
+ except Exception as e:
865
+ print(f"Error configuring decomposePar: {e}")
866
+ return False
867
+
868
+ def build(self, case_dir: str) -> bool:
869
+ """Build the decomposeParDict file (if enabled)."""
870
+ if not self.is_configured or not self.write_decompose_par_dict:
871
+ return True
872
+
873
+ try:
874
+ system_dir = os.path.join(case_dir, "system")
875
+ os.makedirs(system_dir, exist_ok=True)
876
+
877
+ dp_path = os.path.join(system_dir, "decomposeParDict")
878
+ self.writer = DecomposeParWriter(
879
+ file_path=dp_path,
880
+ number_of_subdomains=self.number_of_subdomains,
881
+ method=self.method,
882
+ coeffs=self.coeffs,
883
+ options=self.options,
884
+ fields=self.fields,
885
+ preserve_patches=self.preserve_patches,
886
+ preserve_cell_zones=self.preserve_cell_zones,
887
+ preserve_face_zones=self.preserve_face_zones,
888
+ preserve_point_zones=self.preserve_point_zones
889
+ )
890
+ self.writer.write()
891
+ return True
892
+ except Exception as e:
893
+ print(f"Error building decomposePar: {e}")
894
+ return False
895
+
896
+ def validate(self) -> bool:
897
+ """Validate the decomposePar configuration."""
898
+ if not self.is_configured or not self.write_decompose_par_dict:
899
+ return True
900
+ valid_methods = ['simple', 'hierarchical', 'scotch', 'metis', 'manual',
901
+ 'multiLevel', 'structured', 'kahip', 'ptscotch']
902
+ if self.method not in valid_methods:
903
+ print(f"Invalid decomposition method: {self.method}")
904
+ return False
905
+ if not isinstance(self.number_of_subdomains, int) or self.number_of_subdomains < 1:
906
+ print("numberOfSubdomains must be a positive integer")
907
+ return False
908
+ return True
909
+
910
+
911
+ class SnappyHexMeshComponent(CaseComponent):
912
+ """Component for handling snappyHexMesh configuration."""
913
+
914
+ def __init__(self):
915
+ super().__init__("SnappyHexMesh", "Complex geometry mesh generation configuration")
916
+ self.is_required = False
917
+ self.write_snappy_hex_mesh_dict = False
918
+ self.geometry = {}
919
+ self.castellated_mesh_controls = {}
920
+ self.snap_controls = {}
921
+ self.add_layers_controls = {}
922
+ self.mesh_quality_controls = {}
923
+ self.merged_patches = []
924
+ self.write_flags = {}
925
+ self.writer = None
926
+
927
+ def configure(self, write_snappy_hex_mesh_dict: bool, geometry: Dict[str, Any],
928
+ castellated_mesh_controls: Dict[str, Any], snap_controls: Dict[str, Any],
929
+ add_layers_controls: Dict[str, Any] = None, mesh_quality_controls: Dict[str, Any] = None,
930
+ merged_patches: list = None, write_flags: Dict[str, Any] = None) -> bool:
931
+ """Configure the snappyHexMesh component."""
932
+ try:
933
+ self.write_snappy_hex_mesh_dict = write_snappy_hex_mesh_dict
934
+ self.geometry = geometry.copy()
935
+ self.castellated_mesh_controls = castellated_mesh_controls.copy()
936
+ self.snap_controls = snap_controls.copy()
937
+ self.add_layers_controls = add_layers_controls or {}
938
+ self.mesh_quality_controls = mesh_quality_controls or {}
939
+ self.merged_patches = merged_patches or []
940
+ self.write_flags = write_flags or {}
941
+ self.is_configured = True
942
+ return True
943
+ except Exception as e:
944
+ print(f"Error configuring snappyHexMesh: {e}")
945
+ return False
946
+
947
+ def build(self, case_dir: str) -> bool:
948
+ """Build the snappyHexMeshDict file (if enabled)."""
949
+ if not self.is_configured or not self.write_snappy_hex_mesh_dict:
950
+ return True
951
+
952
+ try:
953
+ system_dir = os.path.join(case_dir, "system")
954
+ os.makedirs(system_dir, exist_ok=True)
955
+
956
+ shm_path = os.path.join(system_dir, "snappyHexMeshDict")
957
+ self.writer = SnappyHexMeshWriter(
958
+ file_path=shm_path,
959
+ geometry=self.geometry,
960
+ castellated_mesh_controls=self.castellated_mesh_controls,
961
+ snap_controls=self.snap_controls,
962
+ add_layers_controls=self.add_layers_controls,
963
+ mesh_quality_controls=self.mesh_quality_controls,
964
+ merged_patches=self.merged_patches,
965
+ write_flags=self.write_flags
966
+ )
967
+ self.writer.write()
968
+ return True
969
+ except Exception as e:
970
+ print(f"Error building snappyHexMesh: {e}")
971
+ return False
972
+
973
+ def validate(self) -> bool:
974
+ """Validate the snappyHexMesh configuration."""
975
+ if not self.is_configured or not self.write_snappy_hex_mesh_dict:
976
+ return True
977
+ if not self.geometry:
978
+ print("Geometry dictionary is required for snappyHexMesh")
979
+ return False
980
+ if not self.castellated_mesh_controls:
981
+ print("Castellated mesh controls are required for snappyHexMesh")
982
+ return False
983
+ if not self.snap_controls:
984
+ print("Snap controls are required for snappyHexMesh")
985
+ return False
986
+ return True
987
+
988
+
989
+ class PFieldComponent(CaseComponent):
990
+ """Component for handling pressure field (p) configuration."""
991
+
992
+ def __init__(self):
993
+ super().__init__("PField", "Pressure field initialization")
994
+ self.is_required = False
995
+ self.write_p_field = False
996
+ self.internal_pressure = 0.0
997
+ self.boundary_conditions = {}
998
+ self.ref_pressure_cell = 0
999
+ self.ref_pressure_value = 0.0
1000
+ self.pressure_dimensions = [0, 2, -2, 0, 0, 0, 0]
1001
+ self.writer = None
1002
+
1003
+ def configure(self, write_p_field: bool, internal_pressure: float,
1004
+ boundary_conditions: Dict[str, Any], ref_pressure_cell: int = 0,
1005
+ ref_pressure_value: float = 0.0, pressure_dimensions: list = None) -> bool:
1006
+ """Configure the pressure field component."""
1007
+ try:
1008
+ self.write_p_field = write_p_field
1009
+ self.internal_pressure = internal_pressure
1010
+ self.boundary_conditions = boundary_conditions.copy()
1011
+ self.ref_pressure_cell = ref_pressure_cell
1012
+ self.ref_pressure_value = ref_pressure_value
1013
+ if pressure_dimensions:
1014
+ self.pressure_dimensions = pressure_dimensions
1015
+ self.is_configured = True
1016
+ return True
1017
+ except Exception as e:
1018
+ print(f"Error configuring pressure field: {e}")
1019
+ return False
1020
+
1021
+ def build(self, case_dir: str) -> bool:
1022
+ """Build the pressure field file (if enabled)."""
1023
+ if not self.is_configured or not self.write_p_field:
1024
+ return True
1025
+
1026
+ try:
1027
+ zero_dir = os.path.join(case_dir, "0")
1028
+ os.makedirs(zero_dir, exist_ok=True)
1029
+
1030
+ p_path = os.path.join(zero_dir, "p")
1031
+ self.writer = PFieldWriter(
1032
+ file_path=p_path,
1033
+ internal_pressure=self.internal_pressure,
1034
+ boundary_conditions=self.boundary_conditions,
1035
+ ref_pressure_cell=self.ref_pressure_cell,
1036
+ ref_pressure_value=self.ref_pressure_value,
1037
+ pressure_dimensions=self.pressure_dimensions
1038
+ )
1039
+ self.writer.write()
1040
+ return True
1041
+ except Exception as e:
1042
+ print(f"Error building pressure field: {e}")
1043
+ return False
1044
+
1045
+ def validate(self) -> bool:
1046
+ """Validate the pressure field configuration."""
1047
+ if not self.is_configured or not self.write_p_field:
1048
+ return True
1049
+ return True
1050
+
1051
+
1052
+ class UFieldComponent(CaseComponent):
1053
+ """Component for handling velocity field (U) configuration."""
1054
+
1055
+ def __init__(self):
1056
+ super().__init__("UField", "Velocity field initialization")
1057
+ self.is_required = False
1058
+ self.write_u_field = False
1059
+ self.internal_velocity = (0.0, 0.0, 0.0)
1060
+ self.boundary_conditions = {}
1061
+ self.velocity_dimensions = [0, 1, -1, 0, 0, 0, 0]
1062
+ self.writer = None
1063
+
1064
+ def configure(self, write_u_field: bool, internal_velocity: tuple,
1065
+ boundary_conditions: Dict[str, Any], velocity_dimensions: list = None) -> bool:
1066
+ """Configure the velocity field component."""
1067
+ try:
1068
+ self.write_u_field = write_u_field
1069
+ self.internal_velocity = internal_velocity
1070
+ self.boundary_conditions = boundary_conditions.copy()
1071
+ if velocity_dimensions:
1072
+ self.velocity_dimensions = velocity_dimensions
1073
+ self.is_configured = True
1074
+ return True
1075
+ except Exception as e:
1076
+ print(f"Error configuring velocity field: {e}")
1077
+ return False
1078
+
1079
+ def build(self, case_dir: str) -> bool:
1080
+ """Build the velocity field file (if enabled)."""
1081
+ if not self.is_configured or not self.write_u_field:
1082
+ return True
1083
+
1084
+ try:
1085
+ zero_dir = os.path.join(case_dir, "0")
1086
+ os.makedirs(zero_dir, exist_ok=True)
1087
+
1088
+ u_path = os.path.join(zero_dir, "U")
1089
+ self.writer = UFieldWriter(
1090
+ file_path=u_path,
1091
+ internal_velocity=self.internal_velocity,
1092
+ boundary_conditions=self.boundary_conditions,
1093
+ velocity_dimensions=self.velocity_dimensions
1094
+ )
1095
+ self.writer.write()
1096
+ return True
1097
+ except Exception as e:
1098
+ print(f"Error building velocity field: {e}")
1099
+ return False
1100
+
1101
+ def validate(self) -> bool:
1102
+ """Validate the velocity field configuration."""
1103
+ if not self.is_configured or not self.write_u_field:
1104
+ return True
1105
+ return True
1106
+
1107
+
1108
+ class FFieldComponent(CaseComponent):
1109
+ """Component for handling force field (f) configuration."""
1110
+
1111
+ def __init__(self):
1112
+ super().__init__("FField", "Force field initialization")
1113
+ self.is_required = False
1114
+ self.write_f_field = False
1115
+ self.internal_force = (0.0, 0.0, 0.0)
1116
+ self.boundary_conditions = {}
1117
+ self.force_dimensions = [0, 1, -2, 0, 0, 0, 0]
1118
+ self.writer = None
1119
+
1120
+ def configure(self, write_f_field: bool, internal_force: tuple,
1121
+ boundary_conditions: Dict[str, Any], force_dimensions: list = None) -> bool:
1122
+ """Configure the force field component."""
1123
+ try:
1124
+ self.write_f_field = write_f_field
1125
+ self.internal_force = internal_force
1126
+ self.boundary_conditions = boundary_conditions.copy()
1127
+ if force_dimensions:
1128
+ self.force_dimensions = force_dimensions
1129
+ self.is_configured = True
1130
+ return True
1131
+ except Exception as e:
1132
+ print(f"Error configuring force field: {e}")
1133
+ return False
1134
+
1135
+ def build(self, case_dir: str) -> bool:
1136
+ """Build the force field file (if enabled)."""
1137
+ if not self.is_configured or not self.write_f_field:
1138
+ return True
1139
+
1140
+ try:
1141
+ zero_dir = os.path.join(case_dir, "0")
1142
+ os.makedirs(zero_dir, exist_ok=True)
1143
+
1144
+ f_path = os.path.join(zero_dir, "f")
1145
+ self.writer = FFieldWriter(
1146
+ file_path=f_path,
1147
+ internal_force=self.internal_force,
1148
+ boundary_conditions=self.boundary_conditions,
1149
+ force_dimensions=self.force_dimensions
1150
+ )
1151
+ self.writer.write()
1152
+ return True
1153
+ except Exception as e:
1154
+ print(f"Error building force field: {e}")
1155
+ return False
1156
+
1157
+ def validate(self) -> bool:
1158
+ """Validate the force field configuration."""
1159
+ if not self.is_configured or not self.write_f_field:
1160
+ return True
1161
+ return True
1162
+
1163
+
1164
+ class LambdaFieldComponent(CaseComponent):
1165
+ """Component for handling lambda field (λ) configuration."""
1166
+
1167
+ def __init__(self):
1168
+ super().__init__("LambdaField", "Lambda field initialization")
1169
+ self.is_required = False
1170
+ self.write_lambda_field = False
1171
+ self.internal_lambda = 0.0
1172
+ self.boundary_conditions = {}
1173
+ self.lambda_dimensions = [0, 0, 0, 0, 0, 0, 0]
1174
+ self.writer = None
1175
+
1176
+ def configure(self, write_lambda_field: bool, internal_lambda: float,
1177
+ boundary_conditions: Dict[str, Any], lambda_dimensions: list = None) -> bool:
1178
+ """Configure the lambda field component."""
1179
+ try:
1180
+ self.write_lambda_field = write_lambda_field
1181
+ self.internal_lambda = internal_lambda
1182
+ self.boundary_conditions = boundary_conditions.copy()
1183
+ if lambda_dimensions:
1184
+ self.lambda_dimensions = lambda_dimensions
1185
+ self.is_configured = True
1186
+ return True
1187
+ except Exception as e:
1188
+ print(f"Error configuring lambda field: {e}")
1189
+ return False
1190
+
1191
+ def build(self, case_dir: str) -> bool:
1192
+ """Build the lambda field file (if enabled)."""
1193
+ if not self.is_configured or not self.write_lambda_field:
1194
+ return True
1195
+
1196
+ try:
1197
+ zero_dir = os.path.join(case_dir, "0")
1198
+ os.makedirs(zero_dir, exist_ok=True)
1199
+
1200
+ lambda_path = os.path.join(zero_dir, "lambda")
1201
+ self.writer = LambdaFieldWriter(
1202
+ file_path=lambda_path,
1203
+ internal_lambda=self.internal_lambda,
1204
+ boundary_conditions=self.boundary_conditions,
1205
+ lambda_dimensions=self.lambda_dimensions
1206
+ )
1207
+ self.writer.write()
1208
+ return True
1209
+ except Exception as e:
1210
+ print(f"Error building lambda field: {e}")
1211
+ return False
1212
+
1213
+ def validate(self) -> bool:
1214
+ """Validate the lambda field configuration."""
1215
+ if not self.is_configured or not self.write_lambda_field:
1216
+ return True
1217
+ return True