Coverage for steam_pysigma\comsol\BuildComsolModel.py: 88%
187 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-12-16 17:09 +0100
« prev ^ index » next coverage.py v7.4.3, created at 2024-12-16 17:09 +0100
1# STEAM PySigma is a python wrapper of STEAM-SIGMA written in Java.
2# Copyright (C) 2023, CERN, Switzerland. All rights reserved.
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, version 3 of the License.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <https://www.gnu.org/licenses/>.
16import os
17from pathlib import Path
19import numpy as np
21from steam_pysigma.comsol.BuildGlobalVariables import BuildGlobalVariables
22from steam_pysigma.sigma import pysigma as ps
23from steam_pysigma.domain_generator.GeometryMultipole import GeometryMultipole
26class BuildComsolModel:
27 """
28 Class take in domains and a configuration and builds a functioning comsol model saved as java.
29 """
31 def __init__(self, model_data=None, input_conductor_params=None, output_path=None, settings=None,
32 path_to_results=None, input_coordinates_path=None, roxie_data=None, bh_curve_database=None):
33 """
34 Class builds a comsol model.
35 :param model_data: data dict from magnet_name.yaml
36 :param input_conductor_params: magnet_name.set
37 :param output_path: Output path of java files and mph model.
38 :param settings: settings dict
39 :param path_to_results: location of comsol-generated results
40 :param input_coordinates_path: path to file with coordinates to evaluate B_field
41 :param roxie_data: roxie data
42 :param bh_curve_database: path to bh curve file
43 """
44 self.g = ps.GatewaySIGMA()
45 self.a = ps.ArraysSIGMA
46 self.model_data = model_data
47 self.output_path = output_path
48 self.roxie_data = roxie_data.Roxie_Data
49 self.settings = settings
50 self.input_conductor_params = input_conductor_params
51 self.funlib_map = ps.FunLibCSVReader().read_csv_funclib_create_hashmap(self.g)
52 self.COMSOL_exe_path = Path(self.settings.comsolexe_path).parent
53 self.java_jdk_path = self.settings.JAVA_jdk_path
54 self.COMSOL_compile_path = os.path.join(self.COMSOL_exe_path, 'comsolcompile.exe') # Change os.join()
55 self.COMSOL_batch_path = os.path.join(self.COMSOL_exe_path, 'comsolbatch.exe')
56 self.COMSOL_version = os.path.basename(os.path.dirname(self.COMSOL_exe_path.parent.parent)).replace("COMSOL",
57 "")
58 self.path_to_results = path_to_results if path_to_results != None else self.output_path
59 self.bh_curve_database = bh_curve_database
60 if self.COMSOL_version != "60" and self.COMSOL_version != "53a":
61 raise Exception("Not supporting any other versions than 6.0 and 5.3a")
63 self.model_name = f"{self.model_data.GeneralParameters.magnet_name}_Model"
65 self.model_java_file_path = f"{os.path.join(self.output_path, self.model_name)}.java"
66 self.compile_batch_file_path = f"{os.path.join(self.output_path, self.model_name)}_Compile_and_Open.bat"
67 self.model_class_file_path = f"{os.path.join(self.output_path, self.model_name)}.class"
68 self.split_java_file_path = []
69 self.study_type = self.model_data.Options_SIGMA.simulation.study_type
70 self.num_qh = self.model_data.Quench_Protection.Quench_Heaters.N_strips
71 self.make_batch_mode_executable = self.model_data.Options_SIGMA.simulation.make_batch_mode_executable
72 self.generate_study = self.model_data.Options_SIGMA.simulation.generate_study
73 self.input_coordinates_path = input_coordinates_path
75 self.variables2DConverted = self.model_data.Options_SIGMA.postprocessing.out_2D_at_points.variables
76 self.time2DConverted = self.model_data.Options_SIGMA.postprocessing.out_2D_at_points.time # List of all exported time
77 self.variables1DvsTime = self.model_data.Options_SIGMA.postprocessing.out_1D_vs_times.variables
78 self.time1DConverted = self.model_data.Options_SIGMA.postprocessing.out_1D_vs_times.time
79 self.variables1DvsTimeVector = self.model_data.Options_SIGMA.postprocessing.out_1D_vs_times.variables
80 self.timeRange = ", ".join(["range(" + ", ".join(map(str, lst)) + ")" for lst in
81 self.model_data.Options_SIGMA.time_vector_solution.time_step])
83 print(f"Comsol compile path {self.COMSOL_compile_path}")
84 print(f"Comsol version {self.COMSOL_version}")
85 bgv = BuildGlobalVariables(self.g, self.model_data)
86 bgv.validate_sigma_model_data()
87 self.cfg = self.setup_config_sigma()
88 self.srv = self.g.TxtSigmaServer(self.cfg.getOutputModelPath(), self.cfg.getComsolBatchPath(),
89 self.cfg.getComsolCompilePath())
92 geom_multipole = GeometryMultipole(self.g, self.roxie_data, self.model_data, self.bh_curve_database, self.input_conductor_params,
93 self.settings, self.COMSOL_version)
95 self.domains = geom_multipole.build_magnet()
96 self.wedge_areas = geom_multipole.wedge_areas
97 self.iron_yoke_areas = geom_multipole.iron_yoke_areas
98 #self.plot_magnet(self.iron_yoke_areas , self.wedge_areas, self.roxie_data)
99 self.connect_create_MPH_model(self.domains)
100 self.save_files_java()
101 self.save_compile_and_open_bat()
103 def setup_config_sigma(self):
104 cfg = self.g.ConfigSigma()
105 cfg.setComsolVersion(self.COMSOL_version) # for sigma_60
106 cfg.setOutputModelPath(self.model_java_file_path)
107 cfg.setComsolBatchPath(self.COMSOL_batch_path)
108 cfg.setComsolCompilePath(self.COMSOL_compile_path)
109 cfg.setExternalCFunLibPath(self.settings.CFunLibPath)
110 cfg.setRunStudy(self.make_batch_mode_executable)
111 cfg.setNumOfQHs(self.num_qh)
112 cfg.setStudyType(self.study_type)
113 cfg.setSymFactor(1)
114 if self.model_data.Options_SIGMA.quench_initialization.quench_init_HT is not None:
115 array = self.a.convert_list_to_string_array(self.g.gateway,
116 self.model_data.Options_SIGMA.quench_initialization.quench_init_HT)
117 cfg.setQuenchInitHT(array)
118 if self.model_data.Options_SIGMA.quench_initialization.quench_init_heat is not None:
119 cfg.setQuenchInitHeat(float(self.model_data.Options_SIGMA.quench_initialization.quench_init_heat))
120 qh_positions = self.g.gateway.jvm.java.util.ArrayList()
121 if self.model_data.Options_SIGMA.quench_heaters.quench_heater_positions is not None:
122 for qh in self.model_data.Options_SIGMA.quench_heaters.quench_heater_positions:
123 temp = self.g.gateway.jvm.java.util.ArrayList()
124 for pos in qh:
125 temp.add(pos)
126 qh_positions.add(temp)
127 cfg.setQHPositions(qh_positions)
128 return cfg
130 def build_study(self):
131 """
132 If valid study time defined function creates a COMSOL study
133 :return: None
134 """
135 if self.study_type is not None:
136 if self.study_type == self.g.MPHC.LABEL_STATIONARY:
137 # Add code to create and run study
138 self.g.StudyAPI.setNewBasicStationaryStudy(self.srv, self.cfg, "sol1")
139 elif self.study_type == self.g.MPHC.LABEL_TRANSIENT:
140 self.g.StudyAPI.setNewMonolithicStudy(self.srv, self.cfg, "Default_study", self.timeRange)
141 else:
142 ValueError("Invaid study_type input")
144 def create_results(self, input_coordinates_path, path_to_results):
145 """
146 Function creates SIGMA result objects to export
147 :param input_coordinates_path: path to coordinate file to evaluate 2D variables in.
148 :param path_to_results: path to result folder
149 :return:
150 """
151 if self.make_batch_mode_executable:
152 for i in range(len(self.variables2DConverted)):
153 time_vector_2D = ', '.join(str(x) for x in self.time2DConverted[i])
155 self.g.ResultsAPI.create2DResultNode(self.srv, self.cfg, self.variables2DConverted[i], time_vector_2D,
156 f"data {i}",
157 input_coordinates_path, str(path_to_results))
158 if self.study_type == self.g.MPHC.LABEL_TRANSIENT:
159 for j in range(len(self.variables1DvsTime)):
160 self.g.ResultsAPI.create1DResultNodeAllTimes(self.srv, self.cfg, self.variables1DvsTime[j],
161 f"1DExport_{j}", path_to_results)
163 for k in range(len(self.variables1DvsTimeVector)):
164 time_vector_1D = ', '.join(str(x) for x in self.time1DConverted[k])
165 self.g.ResultsAPI.create1DResultNodeTimeVector(self.srv, self.cfg, self.variables1DvsTimeVector[k],
166 time_vector_1D,
167 f"data {i + 1 + k}", path_to_results)
168 else:
169 pass
170 # print("No study run, no results to be exported.")
172 def connect_create_MPH_model(self, domains):
173 """
174 This function connects to the COMSOL server and creates an MPH model by using the MagnetMPHBuilder.
175 It then builds the MPH model with the specified domains and global variables.
176 If the study type is "Transient" and there are Quench Heaters present, it also builds a Quench Heater MPH model.
178 :param domains: a tuple of domains to be included in the MPH model.
179 :return: None
180 """
182 self.srv.connect(self.cfg.getComsolBatchPath())
183 self.model = self.g.MagnetMPHBuilder(self.cfg, self.srv)
184 # Create map with I0.
185 global_map = BuildGlobalVariables(self.g, self.model_data).build_global_variables()
186 global_map.put(self.g.MPHC.LABEL_CLIQ_CURRENT_EXT_INITIAL,
187 f"{self.model_data.Power_Supply.I_initial}")
189 self.model = self.g.MagnetMPHBuilder(self.cfg, self.srv)
190 self.model.buildMPH(self.a.create_domain_array(self.g.gateway, tuple(domains)), global_map, 4, 1,
191 self.funlib_map)
193 if self.study_type == self.g.MPHC.LABEL_TRANSIENT and self.cfg.getNumOfQHs() > 0:
194 builder_qh = self.g.QuenchHeaterMPHBuilder(self.cfg, self.srv)
195 self.model.connectToServer()
196 builder_qh.buildQuenchHeaterMPH()
197 if self.generate_study:
198 self.build_study()
199 # Define result nodes
201 self.create_results(self.input_coordinates_path, self.path_to_results)
202 self.srv.build(self.cfg.getOutputModelPath())
203 self.model.save()
205 def save_files_java(self):
206 with open(self.model_java_file_path) as java_file:
208 print("Java file splitting started.")
209 max_no_lines = 6e4
210 public_indexes = []
211 files = []
212 content = java_file.readlines()
213 for i, line in enumerate(content):
214 if "public static" in line:
215 public_indexes += [i]
217 no_lines = public_indexes[-1] - public_indexes[0]
218 no_files = int(np.ceil(no_lines / max_no_lines))
219 max_no_lines = round(no_lines / no_files)
220 real_indexes = [public_indexes[i] - public_indexes[0] for i in range(len(public_indexes))]
221 closest = [min(real_indexes, key=lambda x: abs(x - max_no_lines * (i + 1))) + public_indexes[0]
222 for i in range(no_files)]
223 no_run = [int(content[i][content[i].index('run') + 3:content[i].index('(')]) for i in closest[0:-1]]
224 no_run += [int(content[public_indexes[-2]][content[public_indexes[-2]].index('run')
225 + 3:content[public_indexes[-2]].index('(')]) + 1]
226 additional_lines = {}
227 for i in range(no_files):
228 file_path = os.path.join(self.output_path, self.model_name)
229 files += [open(file_path + '_%d.java' % i, 'w')]
230 name = self.model_name + '_%d' % i
231 self.split_java_file_path += [f"{os.path.join(self.output_path, self.model_name + '_%d' % i)}"]
232 files[i].writelines(content[0:public_indexes[0] - 2] + ['public class ' + name + ' {\n', '\n'])
233 if i == 0:
234 files[i].writelines('\tpublic static Model run1(Model mph) {\n')
235 files[i].writelines(content[public_indexes[0] + 2:closest[i]] + ['}\n'])
236 additional_lines[name] = {'start': 2, 'end': no_run[i] - 1}
237 elif i + 1 == no_files:
238 files[i].writelines(content[closest[i - 1]:public_indexes[-1]] + ['}\n'])
239 additional_lines[name] = {'start': no_run[i - 1], 'end': len(public_indexes) - 1}
240 else:
241 files[i].writelines(content[closest[i - 1]:closest[i]] + ['}\n'])
242 additional_lines[name] = {'start': no_run[i - 1], 'end': no_run[i] - 1}
243 files[i].close()
245 with open(self.model_java_file_path, 'w') as java_file:
246 content = content[0:public_indexes[0] + 2] + ['\n'] + \
247 content[public_indexes[-1]:public_indexes[-1] + 2] + ['\t}\n'] + ['}\n']
248 content.insert(public_indexes[0] + 2, '\t\tmph = ' + self.model_name + '_0.run1(mph);\n')
249 ll = 1
250 for name, item in additional_lines.items():
251 for j in range(item['end'] - item['start'] + 1):
252 content.insert(public_indexes[0] + 2 + ll + j,
253 '\t\tmph = ' + name + '.run' + str(item['start'] + j) + '(mph);\n')
254 ll += j + 1
255 content.insert(public_indexes[0] + 2 + ll, '\t\treturn mph;\n')
256 content.insert(public_indexes[0] + 3 + ll, '\t}\n')
257 java_file.writelines(content)
259 if len(self.split_java_file_path) == 1:
260 print(f"BuilderSIGMA successfully saved file: {self.split_java_file_path[0]}")
261 else:
262 print("BuilderSIGMA successfully saved files:")
263 for file in self.split_java_file_path:
264 print(file)
266 def save_compile_and_open_bat(self):
267 """
268 Function writes a .bat file which has to be run to create a COMSOL model.
269 :return: None
270 """
271 script_lines = []
272 class_paths = []
273 network_drive = False
274 for file in self.split_java_file_path:
275 if file.startswith("\\\\"): # Check if the path is a UNC path
276 network_drive = True
277 print("Network drive identified: Configuring .bat file to support network paths.")
278 script_lines.append(f'pushd "{self.output_path}"')
279 script_lines.append(
280 f'"{self.COMSOL_compile_path}" -jdkroot "{self.java_jdk_path}" "{os.path.basename(file)}.java"')
281 script_lines.append(
282 f'"{self.java_jdk_path}\\bin\\jar.exe" cf "{os.path.basename(file)}.jar" "{os.path.basename(file)}.class"')
283 class_paths.append(f'"{os.path.basename(file)}.jar"')
284 else:
285 script_lines.append(f'"{self.COMSOL_compile_path}" -jdkroot "{self.java_jdk_path}" "{file}.java"')
286 script_lines.append(f'"{self.java_jdk_path}\\bin\\jar.exe" cf "{file}.jar" "{file}.class"')
287 class_paths.append(f'"{os.path.join(self.output_path, file)}.jar"')
289 script_lines.append(
290 f'"{self.COMSOL_compile_path}" -jdkroot "{self.java_jdk_path}" -classpathadd {" ".join(class_paths)} "{self.model_java_file_path}"')
291 script_lines.append(f'"{self.COMSOL_batch_path}" -inputfile "{self.model_class_file_path}" '
292 f'-outputfile "{os.path.join(self.output_path, self.model_data.GeneralParameters.magnet_name)}.mph"')
293 if network_drive:
294 script_lines.append('popd')
296 with open(self.compile_batch_file_path, "w") as outfile:
297 outfile.write("\n".join(str(line) for line in script_lines))
299 print(f'BuilderSIGMA successfully saved: {self.compile_batch_file_path}')
300 os.chdir(self.output_path)