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.
- pycphy/__init__.py +11 -0
- pycphy/cli.py +145 -0
- pycphy/config_manager.py +373 -0
- pycphy/foamCaseDeveloper/__init__.py +60 -41
- pycphy/foamCaseDeveloper/config/__init__.py +69 -26
- pycphy/foamCaseDeveloper/config/cad_mesh_config.py +62 -0
- pycphy/foamCaseDeveloper/config/config_hfdibdem.py +193 -0
- pycphy/foamCaseDeveloper/config/constant/__init__.py +23 -0
- pycphy/foamCaseDeveloper/config/constant/dynamic_mesh_config.py +208 -0
- pycphy/foamCaseDeveloper/config/constant/gravity_field_config.py +379 -0
- pycphy/foamCaseDeveloper/config/constant/transport_properties_config.py +225 -0
- pycphy/foamCaseDeveloper/config/constant/turbulence_config.py +617 -0
- pycphy/foamCaseDeveloper/config/csv_boundary_reader.py +219 -0
- pycphy/foamCaseDeveloper/config/system/__init__.py +31 -0
- pycphy/foamCaseDeveloper/config/system/block_mesh_config.py +184 -0
- pycphy/foamCaseDeveloper/config/{control_config.py → system/control_config.py} +113 -1
- pycphy/foamCaseDeveloper/config/system/decompose_par_config.py +525 -0
- pycphy/foamCaseDeveloper/config/system/fv_options_config.py +575 -0
- pycphy/foamCaseDeveloper/config/system/fv_schemes_config.py +363 -0
- pycphy/foamCaseDeveloper/config/system/set_fields_config.py +640 -0
- pycphy/foamCaseDeveloper/config/system/snappy_hex_mesh_config.py +241 -0
- pycphy/foamCaseDeveloper/config/zero/U_config.py +135 -0
- pycphy/foamCaseDeveloper/config/zero/__init__.py +22 -0
- pycphy/foamCaseDeveloper/config/zero/f_config.py +140 -0
- pycphy/foamCaseDeveloper/config/zero/lambda_config.py +157 -0
- pycphy/foamCaseDeveloper/config/zero/p_config.py +97 -0
- pycphy/foamCaseDeveloper/core/__init__.py +30 -18
- pycphy/foamCaseDeveloper/core/block_mesh_developer.py +1 -1
- pycphy/foamCaseDeveloper/core/cad_block_mesh_developer.py +463 -0
- pycphy/foamCaseDeveloper/core/case_builder.py +1217 -0
- pycphy/foamCaseDeveloper/core/foam_case_manager.py +370 -111
- pycphy/foamCaseDeveloper/develop_case.py +640 -0
- pycphy/foamCaseDeveloper/main.py +260 -260
- pycphy/foamCaseDeveloper/utils/myAutoCAD.py +418 -0
- pycphy/foamCaseDeveloper/writers/__init__.py +37 -4
- pycphy/foamCaseDeveloper/writers/constant/__init__.py +25 -0
- pycphy/foamCaseDeveloper/writers/constant/dynamic_mesh_dict_writer.py +75 -0
- pycphy/foamCaseDeveloper/writers/constant/gravity_field_writer.py +88 -0
- pycphy/foamCaseDeveloper/writers/constant/hfdibdem_dict_writer.py +81 -0
- pycphy/foamCaseDeveloper/writers/constant/transport_properties_writer.py +202 -0
- pycphy/foamCaseDeveloper/writers/{turbulence_properties_writer.py → constant/turbulence_properties_writer.py} +49 -1
- pycphy/foamCaseDeveloper/writers/system/__init__.py +31 -0
- pycphy/foamCaseDeveloper/writers/{block_mesh_writer.py → system/block_mesh_writer.py} +1 -1
- pycphy/foamCaseDeveloper/writers/{control_dict_writer.py → system/control_dict_writer.py} +37 -1
- pycphy/foamCaseDeveloper/writers/system/decompose_par_writer.py +228 -0
- pycphy/foamCaseDeveloper/writers/system/fv_options_writer.py +188 -0
- pycphy/foamCaseDeveloper/writers/system/fv_schemes_writer.py +155 -0
- pycphy/foamCaseDeveloper/writers/system/set_fields_writer.py +191 -0
- pycphy/foamCaseDeveloper/writers/system/snappy_hex_mesh_writer.py +123 -0
- pycphy/foamCaseDeveloper/writers/zero/__init__.py +24 -0
- pycphy/foamCaseDeveloper/writers/zero/f_field_writer.py +89 -0
- pycphy/foamCaseDeveloper/writers/zero/lambda_field_writer.py +84 -0
- pycphy/foamCaseDeveloper/writers/zero/p_field_writer.py +89 -0
- pycphy/foamCaseDeveloper/writers/zero/u_field_writer.py +96 -0
- pycphy/foamCaseDeveloper/writers/zero/zero_field_factory.py +388 -0
- {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/METADATA +154 -6
- pycphy-0.2.0.dist-info/RECORD +63 -0
- pycphy-0.2.0.dist-info/entry_points.txt +3 -0
- pycphy/foamCaseDeveloper/config/block_mesh_config.py +0 -90
- pycphy/foamCaseDeveloper/config/turbulence_config.py +0 -187
- pycphy/foamCaseDeveloper/core/control_dict_writer.py +0 -55
- pycphy/foamCaseDeveloper/core/turbulence_properties_writer.py +0 -68
- pycphy-0.1.0.dist-info/RECORD +0 -24
- pycphy-0.1.0.dist-info/entry_points.txt +0 -2
- {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/WHEEL +0 -0
- {pycphy-0.1.0.dist-info → pycphy-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
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
|
# control_dict_writer.py
|
2
2
|
|
3
|
-
from
|
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")
|