Coverage for bim2sim/plugins/PluginOpenFOAM/test/regression/test_openfoam.py: 0%
167 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 10:24 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 10:24 +0000
1import os
2import sys
3import filecmp
4import difflib
5import unittest
6import logging
7from pathlib import Path
9import bim2sim
10from bim2sim.tasks import common, bps
11from bim2sim.plugins.PluginComfort.bim2sim_comfort import task as comfort_tasks
12from bim2sim.plugins.PluginEnergyPlus.bim2sim_energyplus import task as ep_tasks
13from bim2sim.plugins.PluginOpenFOAM.bim2sim_openfoam import task as of_tasks
14from bim2sim.utilities.types import LOD, IFCDomain
15from bim2sim.kernel.decision.decisionhandler import DebugDecisionHandler
16from bim2sim.utilities.test import RegressionTestBase
18logger = logging.getLogger(__name__)
21class RegressionTestOpenFOAM(RegressionTestBase):
22 """Class to set up and run CFD regression tests."""
24 def setUp(self):
25 self.old_stderr = sys.stderr
26 self.working_dir = os.getcwd()
27 self.ref_results_src_path = None
28 self.results_src_dir = None
29 self.results_dst_dir = None
30 self.tester = None
31 super().setUp()
33 def tearDown(self):
34 os.chdir(self.working_dir)
35 sys.stderr = self.old_stderr
36 super().tearDown()
38 @staticmethod
39 def generate_html_diff_report(new_dir: Path, ref_dir: Path,
40 output_html: str,
41 context_lines: int = 5):
42 """
43 Recursively compare reference vs generated directories and produce an HTML report.
44 Returns a tuple (has_diffs: bool, html_path: str).
45 """
46 html_sections = []
47 diffs_found = False
48 final_diffs_found = 0
49 differ = difflib.HtmlDiff(tabsize=4, wrapcolumn=80)
50 for root, _, files in os.walk(ref_dir):
51 rel_root = os.path.relpath(root, ref_dir)
52 if 'temp' in rel_root.lower():
53 # exclude temp file directories from further diff checks as
54 # they are obsolete
55 break
56 gen_root = new_dir / rel_root
58 if not gen_root.exists():
59 html_sections.append(
60 f"<h3>Missing directory in generated: {rel_root}</h3>")
61 diffs_found = True
62 continue
64 for f in files:
65 ref_file = Path(root) / f
66 gen_file = gen_root / f
67 if not gen_file.exists():
68 diffs_found = True
69 html_sections.append(
70 f"<h4>Missing file in generated: "
71 f"{os.path.join(rel_root, f)}</h4>"
72 )
73 final_diffs_found += 1
74 continue
75 if filecmp.cmp(ref_file, gen_file, shallow=False):
76 continue # identical, skip
77 diffs_found = True
78 with open(ref_file, "r", encoding="utf-8",
79 errors="replace") as rf:
80 ref_lines = rf.read().splitlines()
81 with open(gen_file, "r", encoding="utf-8",
82 errors="replace") as gf:
83 gen_lines = gf.read().splitlines()
85 desc_from = f"Reference: {os.path.join(rel_root, f)}"
86 desc_to = f"Generated: {os.path.join(rel_root, f)}"
87 table_html = differ.make_table(ref_lines, gen_lines,
88 fromdesc=desc_from,
89 todesc=desc_to,
90 context=True,
91 numlines=context_lines)
92 if "No Differences Found".lower() in table_html.lower():
93 continue
94 html_sections.append(
95 f"<h2>{os.path.join(rel_root, f)}</h2>\n{table_html}")
96 final_diffs_found += 1
98 # Check for unexpected extra files in generated_dir
99 extra_files = []
100 for root, _, files in os.walk(new_dir):
101 rel_root = os.path.relpath(root, new_dir)
102 if 'temp' in rel_root.lower():
103 # exclude temp file directories from further diff checks as
104 # they are obsolete
105 break
106 for f in files:
107 gen_path = Path(root) / f
108 ref_path = ref_dir / rel_root / f
109 if not ref_path.exists():
110 extra_files.append(os.path.join(rel_root, f))
111 print(
112 f"Extra file in generated: "
113 f"{os.path.join(rel_root, f)}") # progress print, todo
114 final_diffs_found += 1
116 if extra_files:
117 diffs_found = True
118 extras_html = \
119 "<h3>Unexpected extra files in generated:</h3>\n<ul>\n"
120 for ef in extra_files:
121 extras_html += f"<li>{ef}</li>\n"
122 extras_html += "</ul>\n"
123 html_sections.insert(0, extras_html) # show extras near top
125 # Build full HTML page
126 html_body = "\n<hr/>\n".join(
127 html_sections) if html_sections else "<p>No differences found.</p>"
128 html_page = f"""<!doctype html>
129 <html lang="en">
130 <head>
131 <meta charset="utf-8">
132 <title>Regression Test Diff Report</title>
133 <style>
134 body {{ font-family: Arial, sans-serif; padding: 1rem; }}
135 h1 {{ margin-bottom: .5rem; }}
136 table.diff {{ width: 100%; border-collapse: collapse; }}
137 table.diff td, table.diff th {{ padding: 4px; vertical-align: top; font-family: monospace; }}
138 /* difflib default classes: diff_header, diff_next, diff_add, diff_chg, diff_sub */
139 .diff_add {{ background-color: #99ffb6; }} /* added in generated */
140 .diff_chg {{ background-color: #ffe74d; }} /* changed */
141 .diff_sub {{ background-color: #ff808e; }} /* removed from reference */
142 .diff_header {{ background-color: #f0f0f0; font-weight: bold; }}
143 .center {{ text-align:center; }}
144 </style>
145 </head>
146 <body>
147 <h1>Regression Test Diff Report</h1>
148 <p><b>Reference dir:</b> {ref_dir}</p>
149 <p><b>Generated dir:</b> {new_dir}</p>
150 <hr/>
151 {html_body}
152 </body>
153 </html>
154 """
155 # Ensure parent exists
156 out_path = Path(output_html)
157 out_path.parent.mkdir(parents=True, exist_ok=True)
158 out_path.write_text(html_page, encoding="utf-8")
159 print(f"HTML diff report written to: {out_path}. Differences were "
160 f"found in {final_diffs_found} files.")
161 if final_diffs_found == 0:
162 diffs_found = False
163 return diffs_found, str(out_path)
165 def create_regression_setup(self):
166 passed_regression_test = True
167 ref_results_dir = Path(bim2sim.__file__).parent.parent \
168 / "test/resources/arch/regression_results" \
169 / self.project.name / 'OpenFOAM'
170 sim_output_dir = self.project.paths.export / "OpenFOAM"
171 regression_results_dir = (self.project.paths.root /
172 "regression_results" / "cfd" /
173 self.project.name / "OpenFOAM")
174 regression_results_dir.mkdir(parents=True, exist_ok=True)
175 html_report_path = regression_results_dir / "diff_report.html"
176 print(f"Generating HTML diff report: {html_report_path}")
177 has_diffs, report_path = self.generate_html_diff_report(sim_output_dir,
178 ref_results_dir,
179 html_report_path)
180 if has_diffs:
181 passed_regression_test = False
182 logger.error(
183 f"Regression test failed. Results are written to {report_path}.")
184 return passed_regression_test
186 def run_regression_test(self):
187 return self.create_regression_setup()
190class TestRegressionOpenFOAMCase(RegressionTestOpenFOAM, unittest.TestCase):
191 """Regression tests for PluginOpenFOAM."""
192 def test_regression_AC20_FZK_Haus(self):
193 """Run PluginOpenFOAM regression test with AC20-FZK-Haus.ifc."""
194 ifc_path = {IFCDomain.arch: 'AC20-FZK-Haus.ifc'}
195 project = self.create_project(ifc_path, "openfoam")
197 project.plugin_cls.default_tasks = [
198 common.LoadIFC,
199 # common.CheckIfc,
200 common.CreateElementsOnIfcTypes,
201 bps.CreateSpaceBoundaries,
202 bps.AddSpaceBoundaries2B,
203 bps.CorrectSpaceBoundaries,
204 common.CreateRelations,
205 bps.DisaggregationCreationAndTypeCheck,
206 bps.EnrichMaterial,
207 bps.EnrichUseConditions,
208 common.Weather,
209 ep_tasks.CreateIdf,
210 comfort_tasks.ComfortSettings,
211 # ep_tasks.ExportIdfForCfd,
212 # common.SerializeElements,
213 ep_tasks.RunEnergyPlusSimulation,
214 of_tasks.InitializeOpenFOAMSetup,
215 of_tasks.CreateOpenFOAMGeometry,
216 of_tasks.AddOpenFOAMComfort,
217 of_tasks.CreateOpenFOAMMeshing,
218 of_tasks.SetOpenFOAMBoundaryConditions,
219 of_tasks.RunOpenFOAMMeshing,
220 of_tasks.RunOpenFOAMSimulation
221 ]
223 project.sim_settings.weather_file_path = \
224 (self.test_resources_path() /
225 'weather_files/DEU_NW_Aachen.105010_TMYx.epw')
226 # project.sim_settings.ep_install_path = 'C://EnergyPlusV9-4-0/'
227 project.sim_settings.cfd_export = True
228 project.sim_settings.select_space_guid = '2RSCzLOBz4FAK$_wE8VckM'
229 project.sim_settings.simulation_time = 12
230 project.sim_settings.simulation_date = "01/14"
231 project.sim_settings.add_heating = True
232 project.sim_settings.heater_radiation = 0.6
233 project.sim_settings.radiation_model = 'P1'
234 project.sim_settings.add_airterminals = True
235 project.sim_settings.inlet_type = 'SimpleStlDiffusor'
236 project.sim_settings.outlet_type = 'SimpleStlDiffusor'
237 project.sim_settings.mesh_size = 0.15
238 project.sim_settings.cluster_max_runtime_simulation = "02:59:00"
239 project.sim_settings.cluster_max_runtime_meshing = "00:20:00"
240 project.sim_settings.cluster_jobname = "RegressionTest"
241 project.sim_settings.cluster_compute_account = "test1234"
242 project.sim_settings.cluster_cpu_per_node = 48
243 project.sim_settings.n_procs = 72
244 project.sim_settings.total_iterations = 5000
246 handler = DebugDecisionHandler(())
247 handler.handle(project.run())
249 reg_test_res = self.run_regression_test()
250 self.assertTrue(reg_test_res,
251 "OpenFOAM Regression test did not finish successfully "
252 "or created deviations.")
254 def test_regression_DigitalHub_SB89(self):
255 """Run PluginOpenFOAM regression test with DigitalHub."""
256 ifc_paths = {
257 IFCDomain.arch:
258 Path(bim2sim.__file__).parent.parent /
259 'test/resources/arch/ifc/FM_ARC_DigitalHub_with_SB89.ifc',
260 IFCDomain.ventilation:
261 Path(bim2sim.__file__).parent.parent /
262 'test/resources/hydraulic/ifc/DigitalHub_Gebaeudetechnik'
263 '-LUEFTUNG_v2.ifc',
264 IFCDomain.hydraulic:
265 Path(bim2sim.__file__).parent.parent /
266 'test/resources/hydraulic/ifc/DigitalHub_Gebaeudetechnik-HEIZUNG_v2'
267 '.ifc',
268 }
269 project = self.create_project(ifc_paths, "openfoam")
270 project.plugin_cls.default_tasks = [
271 common.LoadIFC,
272 # common.CheckIfc,
273 common.CreateElementsOnIfcTypes,
274 bps.CreateSpaceBoundaries,
275 bps.AddSpaceBoundaries2B,
276 bps.CorrectSpaceBoundaries,
277 common.CreateRelations,
278 bps.DisaggregationCreationAndTypeCheck,
279 bps.EnrichMaterial,
280 bps.EnrichUseConditions,
281 common.Weather,
282 ep_tasks.CreateIdf,
283 comfort_tasks.ComfortSettings,
284 # ep_tasks.ExportIdfForCfd,
285 # common.SerializeElements,
286 ep_tasks.RunEnergyPlusSimulation,
287 of_tasks.InitializeOpenFOAMSetup,
288 of_tasks.CreateOpenFOAMGeometry,
289 of_tasks.AddOpenFOAMComfort,
290 of_tasks.CreateOpenFOAMMeshing,
291 of_tasks.SetOpenFOAMBoundaryConditions,
292 of_tasks.RunOpenFOAMMeshing,
293 of_tasks.RunOpenFOAMSimulation
294 ]
296 project.sim_settings.weather_file_path = \
297 (self.test_resources_path() /
298 'weather_files/DEU_NW_Aachen.105010_TMYx.epw')
299 project.sim_settings.prj_custom_usages = (Path(
300 bim2sim.__file__).parent.parent / "test/resources/arch/custom_usages/"
301 "customUsagesFM_ARC_DigitalHub_with_SB89.json")
302 # project.sim_settings.ep_install_path = 'C://EnergyPlusV9-4-0/'
303 project.sim_settings.cfd_export = True
304 project.sim_settings.select_space_guid = '3hiy47ppf5B8MyZqbpTfpc'
305 project.sim_settings.inlet_type = 'Original'
306 project.sim_settings.outlet_type = 'Original'
307 project.sim_settings.add_heating = True
308 project.sim_settings.add_people = True
309 project.sim_settings.add_floorheating = False
310 project.sim_settings.add_airterminals = True
311 project.sim_settings.add_comfort = True
312 project.sim_settings.add_furniture = True
313 project.sim_settings.add_people = True
314 project.sim_settings.add_comfort = True
315 project.sim_settings.furniture_setting = 'Office'
316 project.sim_settings.furniture_amount = 8
317 project.sim_settings.people_amount = 4
318 project.sim_settings.people_setting = 'Seated'
319 project.sim_settings.radiation_precondition_time = 4000
320 project.sim_settings.radiation_model = 'preconditioned_fvDOM'
321 project.sim_settings.output_keys = ['output_outdoor_conditions',
322 'output_zone_temperature',
323 'output_zone',
324 'output_infiltration',
325 'output_meters',
326 'output_internal_gains']
328 answers = ('Autodesk Revit', 'Autodesk Revit', *(None,) * 13,
329 *('HVAC-AirTerminal',) * 3, *(None,) * 2, 2015)
330 handler = DebugDecisionHandler(answers)
331 handler.handle(project.run())
333 reg_test_res = self.run_regression_test()
334 self.assertTrue(reg_test_res,
335 "OpenFOAM Regression test did not finish successfully "
336 "or created deviations.")