Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/pysagas/optimisation/cart3d/utilities.py: 0%

163 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-30 04:27 +0000

1import os 

2import time 

3import shutil 

4from random import random 

5from typing import Optional 

6from hypervehicle.utilities import append_sensitivities_to_tri 

7 

8 

9class C3DPrep: 

10 def __init__( 

11 self, 

12 logfile, 

13 jitter_denom: float = 1000, 

14 rotation_attempts: int = 6, 

15 info_file: str = None, 

16 write_config: bool = True, 

17 ) -> None: 

18 self._logfile = logfile 

19 self._info = info_file if info_file is not None else self._logfile 

20 self._jitter_denom = jitter_denom # for 1000; Max of 0.0001, min of 0 

21 self._rotation_attempts = rotation_attempts 

22 self._write_config = write_config 

23 

24 def _run_stl2tri(self, stl_files: list): 

25 tri_files = [] 

26 for file in stl_files: 

27 prefix = file.split(".")[0] 

28 tri_file = prefix + ".tri" 

29 os.system(f"stl2tri.pl {file} {tri_file} >> {self._logfile} 2>&1") 

30 tri_files.append(tri_file) 

31 

32 os.system(f"rm *.comp.tri *.off >> {self._logfile} 2>&1") 

33 return tri_files 

34 

35 def _jitter_tri_files(self, tri_files): 

36 for file in tri_files: 

37 prefix = file.split(".")[0] 

38 x_pert = random() / self._jitter_denom 

39 y_pert = random() / self._jitter_denom 

40 z_pert = random() / self._jitter_denom 

41 os.system( 

42 f"trix -x {x_pert} -y {y_pert} -z {z_pert} -o {prefix} {file} >> {self._logfile} 2>&1" 

43 ) 

44 

45 def _shift_all( 

46 self, 

47 tri_files, 

48 x_shift, 

49 y_shift, 

50 z_shift, 

51 component: str = None, 

52 reverse: bool = False, 

53 ): 

54 if component is None: 

55 transform_files = tri_files 

56 else: 

57 transform_files = [component] 

58 

59 for file in transform_files: 

60 prefix = ".".join(file.split(".")[:-1]) 

61 if reverse: 

62 os.system( 

63 f"trix -x {-x_shift} -y {-y_shift} -z {-z_shift} -o {prefix} {file} >> {self._logfile} 2>&1" 

64 ) 

65 else: 

66 os.system( 

67 f"trix -x {x_shift} -y {y_shift} -z {z_shift} -o {prefix} {file} >> {self._logfile} 2>&1" 

68 ) 

69 

70 def _rotate_all( 

71 self, 

72 tri_files, 

73 x_rot, 

74 y_rot, 

75 z_rot, 

76 component: str = None, 

77 reverse: bool = False, 

78 ): 

79 # Define rotations dict 

80 rotations = { 

81 "x": x_rot, 

82 "y": y_rot, 

83 "z": z_rot, 

84 } 

85 

86 # Determine files to be transformed 

87 if component is None: 

88 # Rotate all .tri files (as provided) 

89 transform_files = tri_files 

90 else: 

91 # Rotate the single component 

92 transform_files = [component] 

93 

94 for file in transform_files: 

95 prefix = ".".join(file.split(".")[:-1]) 

96 

97 # Check order of operations 

98 if reverse: 

99 order = ["z", "y", "x"] 

100 else: 

101 order = ["x", "y", "z"] 

102 

103 # Apply rotations 

104 for axis in order: 

105 rotation = rotations[axis] 

106 os.system( 

107 f"trix -r{axis} {rotation} -o {prefix} {file} >> {self._logfile} 2>&1" 

108 ) 

109 

110 def _run_comp2tri(self, tri_files): 

111 tri_files_str = " ".join(tri_files) 

112 

113 if os.path.exists("Config.xml"): 

114 # Remove old config file 

115 os.remove("Config.xml") 

116 

117 # Run command 

118 conf_str = "-config" if self._write_config else "" 

119 os.system( 

120 f"comp2tri -makeGMPtags {tri_files_str} {conf_str} >> {self._logfile} 2>&1" 

121 ) 

122 

123 if self._write_config: 

124 # Await Config.xml 

125 while not os.path.exists("Config.xml"): 

126 time.sleep(0.2) 

127 

128 # Overwrite Config.xml Component names using tri files 

129 original = os.path.join("Config.xml") 

130 new = os.path.join("temp_config.xml") 

131 with open(new, "w+") as new_file: 

132 with open(original, "r") as original_file: 

133 for line in original_file: 

134 if "Component Name" in line: 

135 # Get component number 

136 name_prefix = "Component_" 

137 name_start = line.index(name_prefix) 

138 comp_no = line.split('"')[1].split("_")[-1] 

139 tri_prefix = tri_files[int(comp_no) - 1].split(".")[0] 

140 

141 # Update line 

142 line = ( 

143 line[:name_start] 

144 + tri_prefix 

145 + line[name_start + len(name_prefix) + 1 :] 

146 ) 

147 

148 # Write line to new file 

149 new_file.write(line) 

150 

151 # # Replace original aero.csh file with updated file 

152 shutil.copymode(original, new) 

153 os.remove(original) 

154 os.rename(new, original) 

155 

156 else: 

157 # Await Components.tri 

158 while not os.path.exists("Components.tri"): 

159 time.sleep(0.2) 

160 

161 def _run_intersect(self): 

162 os.system(f"intersect >> {self._logfile} 2>&1") 

163 

164 @staticmethod 

165 def _get_stl_files() -> list[str]: 

166 path = os.getcwd() 

167 all_files = [ 

168 f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) 

169 ] 

170 stl_files = [] 

171 for file in all_files: 

172 if file.split(".")[-1] == "stl": 

173 stl_files.append(file) 

174 

175 # Sort files 

176 stl_files.sort() 

177 

178 return stl_files 

179 

180 @staticmethod 

181 def _check_for_success(): 

182 success = False 

183 if os.path.isfile("Components.i.tri"): 

184 success = True 

185 return success 

186 

187 def intersect_stls(self, stl_files: Optional[list[str]] = None) -> bool: 

188 """Create Components.i.tri by intersecting all STL files.""" 

189 # Check for existing intersected file 

190 if self._check_for_success(): 

191 self._log("Intersected components file already present.") 

192 return True 

193 

194 # Continue 

195 if not stl_files: 

196 stl_files = self._get_stl_files() 

197 tri_files = self._run_stl2tri(stl_files) 

198 

199 # First try intersect original files 

200 self._log("Making first attempt to intersect components with no perturbations.") 

201 self._run_comp2tri(tri_files) 

202 self._run_intersect() 

203 successful = self._check_for_success() 

204 if successful: 

205 self._log("Success.") 

206 return True 

207 

208 # That failed, try jittering components 

209 self._log("Attempt failed, now attempting to intersect jittered components.") 

210 self._jitter_tri_files(tri_files) 

211 self._run_comp2tri(tri_files) 

212 self._run_intersect() 

213 successful = self._check_for_success() 

214 if successful: 

215 self._log("Success.") 

216 return True 

217 

218 # That failed, try arbitrary shifts away 

219 self._log("Attempt failed, now attempting random perturbations.") 

220 for attempt in range(self._rotation_attempts): 

221 # Define shifts 

222 x_shift = random() * 10 # Max of 10, min of 0 

223 y_shift = random() * 10 # Max of 10, min of 0 

224 z_shift = random() * 10 # Max of 10, min of 0 

225 

226 # Define rotations 

227 x_rot = random() * 10 # Max of 10, min of 0 

228 y_rot = random() * 10 # Max of 10, min of 0 

229 z_rot = random() * 10 # Max of 10, min of 0 

230 

231 # Apply transformations 

232 self._shift_all( 

233 tri_files=tri_files, x_shift=x_shift, y_shift=y_shift, z_shift=z_shift 

234 ) 

235 self._rotate_all(tri_files=tri_files, x_rot=x_rot, y_rot=y_rot, z_rot=z_rot) 

236 

237 if attempt > 0: 

238 # Also jitter 

239 self._log(f"On attempt {attempt+1}, also applying random jitter.") 

240 self._jitter_tri_files(tri_files) 

241 

242 # Make intersect attempt 

243 self._run_comp2tri(tri_files) 

244 self._run_intersect() 

245 successful = self._check_for_success() 

246 

247 if successful: 

248 # Move configuration back to original location 

249 self._log( 

250 "Success. Now moving Components.i.tri back to original position." 

251 ) 

252 self._rotate_all( 

253 tri_files=tri_files, 

254 x_rot=-x_rot, 

255 y_rot=-y_rot, 

256 z_rot=-z_rot, 

257 component="Components.i.tri", 

258 reverse=True, # Perform in reverse order 

259 ) 

260 self._shift_all( 

261 tri_files=tri_files, 

262 x_shift=x_shift, 

263 y_shift=y_shift, 

264 z_shift=z_shift, 

265 component="Components.i.tri", 

266 reverse=True, 

267 ) 

268 return True 

269 

270 else: 

271 # Need to reset tri files 

272 self._log( 

273 f"Random perturbation attempt {attempt+1} failed. Resetting " 

274 + ".tri files and attempting again." 

275 ) 

276 tri_files = self._run_stl2tri(stl_files) 

277 

278 # Finish log 

279 self._log("Unsuccessful.") 

280 

281 return False 

282 

283 def run_autoinputs(self, r: int = 2, *args): 

284 """Runs autoInputs to create input.c3d. 

285 

286 Parameters 

287 ----------- 

288 r : int, optional 

289 The distance to farfield normalized by max geometry size. The default is 2. 

290 args : str, optional 

291 Any extra arguments to provide to autoInputs, as strings. For arguments with 

292 values, just include the value in the string. 

293 """ 

294 extra_args = "-" + " -".join(args) if args else "" 

295 os.system(f"autoInputs -r 2 {extra_args} >> {self._logfile} 2>&1") 

296 

297 def run_aero(self): 

298 """Runs aero.csh.""" 

299 os.system("./aero.csh restart") 

300 

301 def _log(self, msg: str): 

302 with open(self._info, "a") as f: 

303 f.write("\nC3DPrep: " + msg) 

304 

305 

306def combine_sense_data( 

307 components_filepath: str, 

308 sensitivity_files: list[str], 

309 match_target: float = 0.9, 

310 tol_0: float = 1e-5, 

311 max_tol: float = 1e-1, 

312 outdir: Optional[str] = None, 

313 verbosity: Optional[int] = 1, 

314): 

315 """Combine the individual component sensitivity data with the 

316 intersected geometry file (eg. Components.i.tri). 

317 """ 

318 match_frac = 0 

319 tol = tol_0 

320 while match_frac < match_target: 

321 # Check tolerance 

322 if tol > max_tol: 

323 raise Exception( 

324 "Cannot combine sensitivity data (match fraction: " 

325 + f"{match_frac}, tolerance: {tol}, max tolerance: {max_tol})." 

326 ) 

327 

328 # Run matching algorithm 

329 match_frac = append_sensitivities_to_tri( 

330 dp_filenames=sensitivity_files, 

331 components_filepath=components_filepath, 

332 match_tolerance=tol, 

333 verbosity=verbosity, 

334 outdir=outdir, 

335 ) 

336 

337 if match_frac < match_target and verbosity > 0: 

338 print( 

339 "Failed to combine sensitivity data " 

340 f"({100*match_frac:.02f}% match rate)." 

341 ) 

342 print(f" Increasing matching tolerance to {tol*10} and trying again.") 

343 elif verbosity > 0: 

344 print( 

345 "Component sensitivity data matched to intersected geometry " 

346 + f"with {100*match_frac:.02f}% match rate." 

347 ) 

348 

349 # Increase matching tolerance 

350 tol *= 10