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

1import os 

2import sys 

3import filecmp 

4import difflib 

5import unittest 

6import logging 

7from pathlib import Path 

8 

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 

17 

18logger = logging.getLogger(__name__) 

19 

20 

21class RegressionTestOpenFOAM(RegressionTestBase): 

22 """Class to set up and run CFD regression tests.""" 

23 

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() 

32 

33 def tearDown(self): 

34 os.chdir(self.working_dir) 

35 sys.stderr = self.old_stderr 

36 super().tearDown() 

37 

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 

57 

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 

63 

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() 

84 

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 

97 

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 

115 

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 

124 

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) 

164 

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 

185 

186 def run_regression_test(self): 

187 return self.create_regression_setup() 

188 

189 

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") 

196 

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 ] 

222 

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 

245 

246 handler = DebugDecisionHandler(()) 

247 handler.handle(project.run()) 

248 

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.") 

253 

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 ] 

295 

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'] 

327 

328 answers = ('Autodesk Revit', 'Autodesk Revit', *(None,) * 13, 

329 *('HVAC-AirTerminal',) * 3, *(None,) * 2, 2015) 

330 handler = DebugDecisionHandler(answers) 

331 handler.handle(project.run()) 

332 

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.")