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,81 @@
1
+ # hfdibdem_dict_writer.py
2
+
3
+ from ..foam_writer import FoamWriter
4
+
5
+ class HFDIBDEMDictWriter(FoamWriter):
6
+ """
7
+ A class to write an OpenFOAM HFDIBDEMDict file.
8
+
9
+ Handles complex, nested structures and dynamic body definitions
10
+ by recursively processing a complete property dictionary.
11
+ """
12
+
13
+ def __init__(self, file_path, properties):
14
+ """
15
+ Initializes the HFDIBDEMDictWriter.
16
+
17
+ Args:
18
+ file_path (str): The full path to the output file 'constant/HFDIBDEMDict'.
19
+ properties (dict): A dictionary containing the full content of the file.
20
+ """
21
+ # Note: location is "constant" for this file type.
22
+ super().__init__(file_path, foam_class="dictionary", foam_object="HFDIBDEMDict")
23
+ self.properties = properties
24
+
25
+ def _write_recursively(self, data, indent_level):
26
+ """
27
+ Recursively writes dictionary contents, handling nested dictionaries,
28
+ lists, and tuples (vectors/tensors).
29
+ """
30
+ indent = " " * indent_level
31
+
32
+ # Pre-calculate padding for neat alignment
33
+ keys_to_pad = [k for k, v in data.items() if not isinstance(v, dict)]
34
+ max_key_len = max((len(k) for k in keys_to_pad), default=0)
35
+
36
+ for key, value in data.items():
37
+ if isinstance(value, dict):
38
+ # Write a sub-dictionary
39
+ self.file_handle.write(f"\n{indent}{key}\n")
40
+ self.file_handle.write(f"{indent}{{\n")
41
+ self._write_recursively(value, indent_level + 1)
42
+ self.file_handle.write(f"{indent}}}\n")
43
+ elif isinstance(value, (list, tuple)):
44
+ # Write a list or vector: (item1 item2 ...)
45
+ # Check if it's a list of strings (like bodyNames) to add quotes
46
+ if isinstance(value, list) and value and isinstance(value[0], str):
47
+ # Quote string items: ("body1" "body2")
48
+ val_str = ' '.join([f'"{v}"' for v in value])
49
+ else:
50
+ # Standard numbers/vectors: (1 0 0)
51
+ val_str = ' '.join(map(str, value))
52
+
53
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} ({val_str});\n")
54
+ elif isinstance(value, bool):
55
+ # Write booleans as 'true' or 'false'
56
+ val_str = "true" if value else "false"
57
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} {val_str};\n")
58
+ else:
59
+ # Write simple key-value pairs (numbers, strings with units)
60
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} {value};\n")
61
+
62
+ def _write_properties(self):
63
+ """Writes the main content of the HFDIBDEMDict file."""
64
+ self._write_recursively(self.properties, indent_level=0)
65
+
66
+ def write(self):
67
+ """Writes the complete file."""
68
+ print(f"Writing HFDIBDEMDict to: {self.file_path}")
69
+ with open(self.file_path, 'w') as f:
70
+ self.file_handle = f
71
+ # Note: This file type often has 'location "constant";' in header.
72
+ # The base FoamWriter handles class/object.
73
+ self._write_header()
74
+ self._write_foamfile_dict()
75
+ self._write_separator()
76
+
77
+ self._write_properties()
78
+
79
+ self._write_footer()
80
+ self.file_handle = None
81
+ print("...Done")
@@ -0,0 +1,202 @@
1
+ # transport_properties_writer.py
2
+
3
+ """
4
+ Transport Properties Writer for OpenFOAM cases.
5
+
6
+ This writer handles the creation of transportProperties files with support for
7
+ Newtonian and non-Newtonian fluids, thermal properties, and species transport.
8
+ """
9
+
10
+ from ..foam_writer import FoamWriter
11
+
12
+
13
+ class TransportPropertiesWriter(FoamWriter):
14
+ """
15
+ A class to write an OpenFOAM transportProperties file.
16
+
17
+ This writer supports various transport models including Newtonian and
18
+ non-Newtonian fluids, thermal properties, and species transport.
19
+ """
20
+
21
+ def __init__(self, file_path, transport_model, model_properties,
22
+ thermal_properties=None, species_properties=None,
23
+ advanced_properties=None):
24
+ """
25
+ Initialize the TransportPropertiesWriter.
26
+
27
+ Args:
28
+ file_path (str): The full path to the output file 'transportProperties'.
29
+ transport_model (str): The transport model ('Newtonian', 'NonNewtonian', etc.).
30
+ model_properties (dict): Properties for the transport model.
31
+ thermal_properties (dict, optional): Thermal transport properties.
32
+ species_properties (dict, optional): Species transport properties.
33
+ advanced_properties (dict, optional): Advanced transport properties.
34
+ """
35
+ super().__init__(file_path, foam_class="dictionary", foam_object="transportProperties")
36
+ self.transport_model = transport_model
37
+ self.model_properties = model_properties
38
+ self.thermal_properties = thermal_properties or {}
39
+ self.species_properties = species_properties or {}
40
+ self.advanced_properties = advanced_properties or {}
41
+
42
+ def validate_transport_model(self):
43
+ """
44
+ Validate the transport model.
45
+
46
+ Returns:
47
+ bool: True if validation passes, False otherwise.
48
+ """
49
+ valid_models = [
50
+ 'Newtonian', 'NonNewtonian', 'BirdCarreau', 'CrossPowerLaw',
51
+ 'HerschelBulkley', 'PowerLaw', 'Casson', 'GeneralizedNewtonian'
52
+ ]
53
+
54
+ if self.transport_model not in valid_models:
55
+ print(f"Warning: Invalid transport model '{self.transport_model}'. Valid models: {valid_models}")
56
+ return False
57
+
58
+ return True
59
+
60
+ def validate_model_properties(self):
61
+ """
62
+ Validate the model properties based on transport model.
63
+
64
+ Returns:
65
+ bool: True if validation passes, False otherwise.
66
+ """
67
+ if self.transport_model == 'Newtonian':
68
+ if 'nu' not in self.model_properties:
69
+ print("Warning: Newtonian model requires 'nu' (kinematic viscosity)")
70
+ return False
71
+
72
+ elif self.transport_model in ['NonNewtonian', 'BirdCarreau', 'CrossPowerLaw',
73
+ 'HerschelBulkley', 'PowerLaw', 'Casson']:
74
+ if 'nu' not in self.model_properties:
75
+ print("Warning: Non-Newtonian models require 'nu' (reference viscosity)")
76
+ return False
77
+
78
+ if 'modelCoeffs' not in self.model_properties:
79
+ print("Warning: Non-Newtonian models require 'modelCoeffs'")
80
+ return False
81
+
82
+ return True
83
+
84
+ def _write_dict_recursively(self, data_dict, indent_level):
85
+ """
86
+ Recursively writes a dictionary's contents, handling nested dictionaries.
87
+
88
+ Args:
89
+ data_dict (dict): The dictionary to write.
90
+ indent_level (int): The current level of indentation.
91
+ """
92
+ indent = " " * indent_level
93
+ # Calculate padding for alignment within the current dictionary level
94
+ max_key_len = max((len(k) for k in data_dict.keys()), default=0)
95
+
96
+ for key, value in data_dict.items():
97
+ if isinstance(value, dict):
98
+ # If the value is another dictionary, start a new block
99
+ self.file_handle.write(f"{indent}{key}\n")
100
+ self.file_handle.write(f"{indent}{{\n")
101
+ self._write_dict_recursively(value, indent_level + 1)
102
+ self.file_handle.write(f"{indent}}}\n\n")
103
+ elif isinstance(value, (list, tuple)):
104
+ # Write lists and tuples
105
+ if isinstance(value, list) and value and isinstance(value[0], str):
106
+ # Quote string items
107
+ val_str = ' '.join([f'"{v}"' for v in value])
108
+ else:
109
+ # Standard numbers
110
+ val_str = ' '.join(map(str, value))
111
+
112
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} ({val_str});\n")
113
+ else:
114
+ # Otherwise, it's a simple key-value pair
115
+ padded_key = f"{key:<{max_key_len}}"
116
+ self.file_handle.write(f"{indent}{padded_key} {value};\n")
117
+
118
+ def _write_transport_model(self):
119
+ """Write the transport model configuration."""
120
+ self.file_handle.write(f"transportModel {self.transport_model};\n\n")
121
+
122
+ # Write model-specific properties
123
+ if self.transport_model == 'Newtonian':
124
+ # Write simple Newtonian properties
125
+ for key, value in self.model_properties.items():
126
+ self.file_handle.write(f"{key:<20} {value};\n")
127
+
128
+ elif self.transport_model in ['NonNewtonian', 'BirdCarreau', 'CrossPowerLaw',
129
+ 'HerschelBulkley', 'PowerLaw', 'Casson']:
130
+ # Write non-Newtonian properties
131
+ for key, value in self.model_properties.items():
132
+ if key != 'modelCoeffs':
133
+ self.file_handle.write(f"{key:<20} {value};\n")
134
+ else:
135
+ # Write model coefficients as a sub-dictionary
136
+ self.file_handle.write(f"\n{self.transport_model}Coeffs\n")
137
+ self.file_handle.write("{\n")
138
+ self._write_dict_recursively(value, indent_level=1)
139
+ self.file_handle.write("}\n")
140
+
141
+ self.file_handle.write("\n")
142
+
143
+ def _write_thermal_properties(self):
144
+ """Write thermal properties if enabled."""
145
+ if not self.thermal_properties.get('enableThermal', False):
146
+ return
147
+
148
+ self.file_handle.write("// Thermal properties\n")
149
+ for key, value in self.thermal_properties.items():
150
+ if key != 'enableThermal':
151
+ self.file_handle.write(f"{key:<20} {value};\n")
152
+ self.file_handle.write("\n")
153
+
154
+ def _write_species_properties(self):
155
+ """Write species properties if enabled."""
156
+ if not self.species_properties.get('enableSpecies', False):
157
+ return
158
+
159
+ self.file_handle.write("// Species transport properties\n")
160
+ for key, value in self.species_properties.items():
161
+ if key != 'enableSpecies':
162
+ if isinstance(value, list):
163
+ val_str = ' '.join(map(str, value))
164
+ self.file_handle.write(f"{key:<20} ({val_str});\n")
165
+ else:
166
+ self.file_handle.write(f"{key:<20} {value};\n")
167
+ self.file_handle.write("\n")
168
+
169
+ def _write_advanced_properties(self):
170
+ """Write advanced properties if enabled."""
171
+ if not self.advanced_properties.get('enableAdvanced', False):
172
+ return
173
+
174
+ self.file_handle.write("// Advanced properties\n")
175
+ for key, value in self.advanced_properties.items():
176
+ if key != 'enableAdvanced':
177
+ self.file_handle.write(f"{key:<20} {value};\n")
178
+ self.file_handle.write("\n")
179
+
180
+ def _write_properties(self):
181
+ """Writes the main content of the transportProperties file."""
182
+ self._write_transport_model()
183
+ self._write_thermal_properties()
184
+ self._write_species_properties()
185
+ self._write_advanced_properties()
186
+
187
+ def write(self):
188
+ """
189
+ Writes the complete transportProperties file.
190
+ """
191
+ print(f"Writing transportProperties to: {self.file_path}")
192
+ with open(self.file_path, 'w') as f:
193
+ self.file_handle = f
194
+ self._write_header()
195
+ self._write_foamfile_dict()
196
+ self._write_separator()
197
+
198
+ self._write_properties()
199
+
200
+ self._write_footer()
201
+ self.file_handle = None
202
+ print("...Done")
@@ -1,6 +1,6 @@
1
1
  # turbulence_properties_writer.py
2
2
 
3
- from .foam_writer import FoamWriter
3
+ from ..foam_writer import FoamWriter
4
4
 
5
5
  class TurbulencePropertiesWriter(FoamWriter):
6
6
  """
@@ -23,6 +23,54 @@ class TurbulencePropertiesWriter(FoamWriter):
23
23
  super().__init__(file_path, foam_class="dictionary", foam_object="turbulenceProperties")
24
24
  self.simulation_type = simulation_type
25
25
  self.model_properties = model_properties
26
+
27
+ def validate_simulation_type(self):
28
+ """
29
+ Validates the simulation type.
30
+
31
+ Returns:
32
+ bool: True if validation passes, False otherwise.
33
+ """
34
+ valid_types = ['RAS', 'LES', 'laminar']
35
+
36
+ if self.simulation_type not in valid_types:
37
+ print(f"Warning: Invalid simulation type '{self.simulation_type}'. Valid types: {valid_types}")
38
+ return False
39
+
40
+ return True
41
+
42
+ def validate_model_properties(self):
43
+ """
44
+ Validates the model properties based on simulation type.
45
+
46
+ Returns:
47
+ bool: True if validation passes, False otherwise.
48
+ """
49
+ if self.simulation_type == 'RAS':
50
+ required_keys = ['RASModel', 'turbulence']
51
+ for key in required_keys:
52
+ if key not in self.model_properties:
53
+ print(f"Warning: Required RAS parameter '{key}' is missing.")
54
+ return False
55
+
56
+ # Validate RAS model
57
+ valid_ras_models = ['kEpsilon', 'realizableKE', 'kOmegaSST', 'SpalartAllmaras']
58
+ if self.model_properties.get('RASModel') not in valid_ras_models:
59
+ print(f"Warning: Unknown RAS model '{self.model_properties.get('RASModel')}'.")
60
+
61
+ elif self.simulation_type == 'LES':
62
+ required_keys = ['LESModel', 'turbulence']
63
+ for key in required_keys:
64
+ if key not in self.model_properties:
65
+ print(f"Warning: Required LES parameter '{key}' is missing.")
66
+ return False
67
+
68
+ # Validate LES model
69
+ valid_les_models = ['Smagorinsky', 'kEqn', 'WALE', 'dynamicKEqn']
70
+ if self.model_properties.get('LESModel') not in valid_les_models:
71
+ print(f"Warning: Unknown LES model '{self.model_properties.get('LESModel')}'.")
72
+
73
+ return True
26
74
 
27
75
  def _write_dict_recursively(self, data_dict, indent_level):
28
76
  """
@@ -0,0 +1,31 @@
1
+ # system/__init__.py
2
+ """
3
+ System directory writer modules.
4
+
5
+ This package contains writer modules for OpenFOAM system directory files:
6
+ - blockMeshDict
7
+ - controlDict
8
+ - fvSchemes
9
+ - fvOptions
10
+ - setFieldsDict
11
+ - decomposeParDict
12
+ - snappyHexMeshDict
13
+ """
14
+
15
+ from . import block_mesh_writer
16
+ from . import control_dict_writer
17
+ from . import fv_schemes_writer
18
+ from . import fv_options_writer
19
+ from . import set_fields_writer
20
+ from . import decompose_par_writer
21
+ from . import snappy_hex_mesh_writer
22
+
23
+ __all__ = [
24
+ 'block_mesh_writer',
25
+ 'control_dict_writer',
26
+ 'fv_schemes_writer',
27
+ 'fv_options_writer',
28
+ 'set_fields_writer',
29
+ 'decompose_par_writer',
30
+ 'snappy_hex_mesh_writer'
31
+ ]
@@ -1,6 +1,6 @@
1
1
  # block_mesh_writer.py
2
2
 
3
- from .foam_writer import FoamWriter
3
+ from ..foam_writer import FoamWriter
4
4
 
5
5
  class BlockMeshWriter(FoamWriter):
6
6
  """
@@ -1,6 +1,6 @@
1
1
  # control_dict_writer.py
2
2
 
3
- from .foam_writer import FoamWriter
3
+ from ..foam_writer import FoamWriter
4
4
 
5
5
  class ControlDictWriter(FoamWriter):
6
6
  """
@@ -21,6 +21,42 @@ class ControlDictWriter(FoamWriter):
21
21
  """
22
22
  super().__init__(file_path, foam_class="dictionary", foam_object="controlDict")
23
23
  self.params = params
24
+
25
+ def validate_params(self):
26
+ """
27
+ Validates the control parameters for common issues.
28
+
29
+ Returns:
30
+ bool: True if validation passes, False otherwise.
31
+ """
32
+ required_params = ['application', 'startFrom', 'stopAt']
33
+
34
+ for param in required_params:
35
+ if param not in self.params:
36
+ print(f"Warning: Required parameter '{param}' is missing from control parameters.")
37
+ return False
38
+
39
+ # Validate application
40
+ valid_applications = [
41
+ 'icoFoam', 'simpleFoam', 'pimpleFoam', 'interFoam',
42
+ 'rhoSimpleFoam', 'rhoPimpleFoam', 'buoyantFoam'
43
+ ]
44
+
45
+ if self.params.get('application') not in valid_applications:
46
+ print(f"Warning: Application '{self.params.get('application')}' may not be a valid OpenFOAM solver.")
47
+
48
+ # Validate time step
49
+ if 'deltaT' in self.params:
50
+ try:
51
+ delta_t = float(self.params['deltaT'])
52
+ if delta_t <= 0:
53
+ print("Warning: deltaT should be positive.")
54
+ return False
55
+ except (ValueError, TypeError):
56
+ print("Warning: deltaT should be a valid number.")
57
+ return False
58
+
59
+ return True
24
60
 
25
61
  def _write_parameters(self):
26
62
  """Writes the control parameters from the dictionary."""
@@ -0,0 +1,228 @@
1
+ # decompose_par_writer.py
2
+
3
+ """
4
+ Decompose Par Writer for OpenFOAM cases.
5
+
6
+ This writer handles the creation of decomposeParDict files with support for
7
+ various decomposition methods and load balancing strategies for parallel processing.
8
+ """
9
+
10
+ from ..foam_writer import FoamWriter
11
+
12
+
13
+ class DecomposeParWriter(FoamWriter):
14
+ """
15
+ A class to write an OpenFOAM decomposeParDict file.
16
+
17
+ This writer supports comprehensive decomposition configurations for various
18
+ simulation types and parallel processing scenarios.
19
+ """
20
+
21
+ def __init__(self, file_path, number_of_subdomains, method, coeffs=None,
22
+ options=None, fields=None, preserve_patches=None,
23
+ preserve_cell_zones=None, preserve_face_zones=None,
24
+ preserve_point_zones=None):
25
+ """
26
+ Initialize the DecomposeParWriter.
27
+
28
+ Args:
29
+ file_path (str): The full path to the output file 'decomposeParDict'.
30
+ number_of_subdomains (int): Number of domains to decompose into.
31
+ method (str): Decomposition method.
32
+ coeffs (dict, optional): Decomposition coefficients.
33
+ options (dict, optional): Additional decomposition options.
34
+ fields (list, optional): Fields to decompose.
35
+ preserve_patches (list, optional): Patches to preserve.
36
+ preserve_cell_zones (list, optional): Cell zones to preserve.
37
+ preserve_face_zones (list, optional): Face zones to preserve.
38
+ preserve_point_zones (list, optional): Point zones to preserve.
39
+ """
40
+ super().__init__(file_path, foam_class="dictionary", foam_object="decomposeParDict")
41
+ self.number_of_subdomains = number_of_subdomains
42
+ self.method = method
43
+ self.coeffs = coeffs or {}
44
+ self.options = options or {}
45
+ self.fields = fields or []
46
+ self.preserve_patches = preserve_patches or []
47
+ self.preserve_cell_zones = preserve_cell_zones or []
48
+ self.preserve_face_zones = preserve_face_zones or []
49
+ self.preserve_point_zones = preserve_point_zones or []
50
+
51
+ def validate_decomposition(self):
52
+ """
53
+ Validate the decomposition configuration.
54
+
55
+ Returns:
56
+ bool: True if validation passes, False otherwise.
57
+ """
58
+ valid = True
59
+
60
+ # Validate number of subdomains
61
+ if not isinstance(self.number_of_subdomains, int) or self.number_of_subdomains < 1:
62
+ print("Warning: numberOfSubdomains must be a positive integer")
63
+ valid = False
64
+
65
+ # Validate method
66
+ valid_methods = [
67
+ 'simple', 'hierarchical', 'scotch', 'metis', 'manual',
68
+ 'multiLevel', 'structured', 'kahip', 'ptscotch'
69
+ ]
70
+ if self.method not in valid_methods:
71
+ print(f"Warning: Invalid decomposition method '{self.method}'. Valid methods: {valid_methods}")
72
+ valid = False
73
+
74
+ # Validate coefficients based on method
75
+ if self.method == 'simple' and 'simpleCoeffs' not in self.coeffs:
76
+ print("Warning: Simple method requires simpleCoeffs")
77
+ valid = False
78
+
79
+ if self.method == 'scotch' and 'scotchCoeffs' not in self.coeffs:
80
+ print("Warning: Scotch method requires scotchCoeffs")
81
+ valid = False
82
+
83
+ return valid
84
+
85
+ def _write_dict_recursively(self, data_dict, indent_level):
86
+ """
87
+ Recursively writes a dictionary's contents, handling nested dictionaries.
88
+
89
+ Args:
90
+ data_dict (dict): The dictionary to write.
91
+ indent_level (int): The current level of indentation.
92
+ """
93
+ indent = " " * indent_level
94
+ # Calculate padding for alignment within the current dictionary level
95
+ max_key_len = max((len(k) for k in data_dict.keys()), default=0)
96
+
97
+ for key, value in data_dict.items():
98
+ if isinstance(value, dict):
99
+ # If the value is another dictionary, start a new block
100
+ self.file_handle.write(f"{indent}{key}\n")
101
+ self.file_handle.write(f"{indent}{{\n")
102
+ self._write_dict_recursively(value, indent_level + 1)
103
+ self.file_handle.write(f"{indent}}}\n\n")
104
+ elif isinstance(value, (list, tuple)):
105
+ # Write lists and tuples
106
+ if isinstance(value, list) and value and isinstance(value[0], str):
107
+ # Quote string items
108
+ val_str = ' '.join([f'"{v}"' for v in value])
109
+ else:
110
+ # Standard numbers
111
+ val_str = ' '.join(map(str, value))
112
+
113
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} ({val_str});\n")
114
+ elif isinstance(value, bool):
115
+ # Write booleans as 'true' or 'false'
116
+ val_str = "true" if value else "false"
117
+ self.file_handle.write(f"{indent}{key:<{max_key_len}} {val_str};\n")
118
+ else:
119
+ # Otherwise, it's a simple key-value pair
120
+ padded_key = f"{key:<{max_key_len}}"
121
+ self.file_handle.write(f"{indent}{padded_key} {value};\n")
122
+
123
+ def _write_coeffs(self):
124
+ """Write decomposition coefficients section."""
125
+ if not self.coeffs:
126
+ return
127
+
128
+ # Write method-specific coefficients
129
+ method_coeffs = self.coeffs.get(f"{self.method}Coeffs", {})
130
+ if method_coeffs:
131
+ self.file_handle.write(f"{self.method}Coeffs\n")
132
+ self.file_handle.write("{\n")
133
+ self._write_dict_recursively(method_coeffs, indent_level=1)
134
+ self.file_handle.write("}\n\n")
135
+
136
+ def _write_options(self):
137
+ """Write decomposition options section."""
138
+ if not self.options:
139
+ return
140
+
141
+ self.file_handle.write("options\n")
142
+ self.file_handle.write("{\n")
143
+ self._write_dict_recursively(self.options, indent_level=1)
144
+ self.file_handle.write("}\n\n")
145
+
146
+ def _write_fields(self):
147
+ """Write fields section."""
148
+ if not self.fields:
149
+ return
150
+
151
+ self.file_handle.write("fields\n")
152
+ self.file_handle.write("(\n")
153
+
154
+ for field in self.fields:
155
+ self.file_handle.write(f' "{field}"\n')
156
+
157
+ self.file_handle.write(")\n\n")
158
+
159
+ def _write_preserve_sections(self):
160
+ """Write preserve sections."""
161
+ # Write preserve patches
162
+ if self.preserve_patches:
163
+ self.file_handle.write("preservePatches\n")
164
+ self.file_handle.write("(\n")
165
+ for patch in self.preserve_patches:
166
+ self.file_handle.write(f' "{patch}"\n')
167
+ self.file_handle.write(")\n\n")
168
+
169
+ # Write preserve cell zones
170
+ if self.preserve_cell_zones:
171
+ self.file_handle.write("preserveCellZones\n")
172
+ self.file_handle.write("(\n")
173
+ for zone in self.preserve_cell_zones:
174
+ self.file_handle.write(f' "{zone}"\n')
175
+ self.file_handle.write(")\n\n")
176
+
177
+ # Write preserve face zones
178
+ if self.preserve_face_zones:
179
+ self.file_handle.write("preserveFaceZones\n")
180
+ self.file_handle.write("(\n")
181
+ for zone in self.preserve_face_zones:
182
+ self.file_handle.write(f' "{zone}"\n')
183
+ self.file_handle.write(")\n\n")
184
+
185
+ # Write preserve point zones
186
+ if self.preserve_point_zones:
187
+ self.file_handle.write("preservePointZones\n")
188
+ self.file_handle.write("(\n")
189
+ for zone in self.preserve_point_zones:
190
+ self.file_handle.write(f' "{zone}"\n')
191
+ self.file_handle.write(")\n\n")
192
+
193
+ def _write_properties(self):
194
+ """Writes the main content of the decomposeParDict file."""
195
+ # Write number of subdomains
196
+ self.file_handle.write(f"numberOfSubdomains {self.number_of_subdomains};\n\n")
197
+
198
+ # Write method
199
+ self.file_handle.write(f"method {self.method};\n\n")
200
+
201
+ # Write coefficients
202
+ self._write_coeffs()
203
+
204
+ # Write options
205
+ self._write_options()
206
+
207
+ # Write fields
208
+ self._write_fields()
209
+
210
+ # Write preserve sections
211
+ self._write_preserve_sections()
212
+
213
+ def write(self):
214
+ """
215
+ Writes the complete decomposeParDict file.
216
+ """
217
+ print(f"Writing decomposeParDict to: {self.file_path}")
218
+ with open(self.file_path, 'w') as f:
219
+ self.file_handle = f
220
+ self._write_header()
221
+ self._write_foamfile_dict()
222
+ self._write_separator()
223
+
224
+ self._write_properties()
225
+
226
+ self._write_footer()
227
+ self.file_handle = None
228
+ print("...Done")