Skip to content

Commit 9cef636

Browse files
committed
Added append logic to h5 file
1 parent 175a025 commit 9cef636

File tree

1 file changed

+100
-51
lines changed

1 file changed

+100
-51
lines changed

rocketpy/simulation/monte_carlo.py

+100-51
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
from time import process_time, time
66

77
import h5py
8-
import hdf5plugin
98
import numpy as np
109
import simplekml
11-
from multiprocess import Lock, Process, JoinableQueue
10+
from multiprocess import JoinableQueue, Lock, Process
1211
from multiprocess.managers import BaseManager
1312

1413
from rocketpy import Function
@@ -170,7 +169,7 @@ def simulate(
170169
If True, the results will be appended to the existing files. If
171170
False, the files will be overwritten. Default is False.
172171
light_mode : bool, optional
173-
If True, tonly variables from the export_list will be saved to
172+
If True, only variables from the export_list will be saved to
174173
the output file as a .txt file. If False, all variables will be
175174
saved to the output file as a .h5 file. Default is False.
176175
parallel : bool, optional
@@ -180,9 +179,6 @@ def simulate(
180179
-------
181180
None
182181
"""
183-
if append and light_mode is False:
184-
raise ValueError("Append mode is not available when light_mode is False.")
185-
186182
# initialize counters
187183
self.number_of_simulations = number_of_simulations
188184
self.iteration_count = self.num_of_loaded_sims if append else 0
@@ -194,22 +190,27 @@ def simulate(
194190

195191
# Run simulations
196192
if parallel:
197-
self._run_in_parallel(append, light_mode=light_mode)
193+
self.__run_in_parallel(append, light_mode=light_mode)
198194
else:
199-
self._run_in_serial(append, light_mode=light_mode)
195+
self.__run_in_serial(append, light_mode=light_mode)
200196

201-
def _run_in_serial(self, append, light_mode):
197+
def __run_in_serial(self, append, light_mode):
202198
"""
203199
Runs the monte carlo simulation in serial mode.
204200
205-
Args:
201+
Parameters
202+
----------
206203
append: bool
207204
If True, the results will be appended to the existing files. If
208205
False, the files will be overwritten.
209206
light_mode: bool
210207
If True, only variables from the export_list will be saved to
211208
the output file as a .txt file. If False, all variables will be
212209
saved to the output file as a .h5 file.
210+
211+
Returns
212+
-------
213+
None
213214
"""
214215

215216
# Create data files for inputs, outputs and error logging
@@ -224,12 +225,18 @@ def _run_in_serial(self, append, light_mode):
224225
input_file = h5py.File(self._input_file.with_suffix(".h5"), open_mode)
225226
output_file = h5py.File(self._output_file.with_suffix(".h5"), open_mode)
226227
error_file = open(self._error_file, open_mode, encoding="utf-8")
228+
229+
idx_i = self.__get_initial_sim_idx(input_file)
230+
idx_o = self.__get_initial_sim_idx(output_file)
231+
232+
if idx_i != idx_o:
233+
raise ValueError("Input and output files are not synchronized. Append mode is not available.")
227234

228235
# Run simulations
229236
try:
230237
while self.iteration_count < self.number_of_simulations:
231238
self.__run_single_simulation(
232-
self.iteration_count, input_file, output_file, light_mode=light_mode
239+
self.iteration_count + idx_i, input_file, output_file, light_mode=light_mode
233240
)
234241

235242
except KeyboardInterrupt:
@@ -247,11 +254,12 @@ def _run_in_serial(self, append, light_mode):
247254
input_file, output_file, error_file, light_mode=light_mode
248255
)
249256

250-
def _run_in_parallel(self, append, light_mode, n_workers=None):
257+
def __run_in_parallel(self, append, light_mode, n_workers=None):
251258
"""
252259
Runs the monte carlo simulation in parallel.
253-
254-
Args:
260+
261+
Parameters
262+
----------
255263
append: bool
256264
If True, the results will be appended to the existing files. If
257265
False, the files will be overwritten.
@@ -275,13 +283,12 @@ def _run_in_parallel(self, append, light_mode, n_workers=None):
275283
inputs_lock = manager.Lock()
276284
outputs_lock = manager.Lock()
277285
errors_lock = manager.Lock()
278-
sim_counter = manager.SimCounter()
279286
queue = manager.JoinableQueue()
280-
287+
281288
# Initialize queue
282289
for _ in range(self.number_of_simulations):
283290
queue.put("RUN")
284-
291+
285292
for _ in range(n_workers):
286293
queue.put("STOP")
287294

@@ -303,27 +310,34 @@ def _run_in_parallel(self, append, light_mode, n_workers=None):
303310
pass # initialize file
304311

305312
else:
306-
with h5py.File(self._input_file.with_suffix(".h5"), 'w') as _:
307-
pass # initialize file
308-
with h5py.File(self._output_file.with_suffix(".h5"), 'w') as _:
309-
pass # initialize file
310-
313+
# Change file extensions to .h5
311314
file_paths["input_file"] = file_paths["input_file"].with_suffix(".h5")
312315
file_paths["output_file"] = file_paths["output_file"].with_suffix(".h5")
313316
file_paths["error_file"] = file_paths["error_file"].with_suffix(".h5")
314317

318+
# Initialize files and get initial simulation index
319+
with h5py.File(file_paths["input_file"], open_mode) as f:
320+
idx_i = self.__get_initial_sim_idx(f)
321+
with h5py.File(file_paths["output_file"], open_mode) as f:
322+
idx_o = self.__get_initial_sim_idx(f)
323+
324+
if idx_i != idx_o:
325+
raise ValueError("Input and output files are not synchronized. Append mode is not available.")
326+
315327
# Initialize error file - always a .txt file
316328
with open(self._error_file, mode=open_mode) as _:
317329
pass # initialize file
330+
331+
# Initialize simulation counter
332+
sim_counter = manager.SimCounter(idx_i)
318333

319-
320-
print("Starting monte carlo analysis", end="\r")
334+
print("\nStarting monte carlo analysis", end="\r")
321335
print(f"Number of simulations: {self.number_of_simulations}")
322336

323337
# Creates n_workers processes then starts them
324338
for _ in range(n_workers):
325339
p = Process(
326-
target=self._run_simulation_worker,
340+
target=self.__run_simulation_worker,
327341
args=(
328342
self.environment,
329343
self.rocket,
@@ -352,11 +366,11 @@ def _run_in_parallel(self, append, light_mode, n_workers=None):
352366
parallel_end = time()
353367

354368
print("-" * 80 + "\nAll workers joined, simulation complete.")
355-
print(f"In total, {sim_counter.get_count()} simulations were performed.")
369+
print(f"In total, {sim_counter.get_count() - idx_i} simulations were performed.")
356370
print("Simulation took", parallel_end - parallel_start, "seconds to run.")
357371

358372
@staticmethod
359-
def _run_simulation_worker(
373+
def __run_simulation_worker(
360374
sto_env,
361375
sto_rocket,
362376
sto_flight,
@@ -370,8 +384,9 @@ def _run_simulation_worker(
370384
):
371385
"""
372386
Runs a simulation from a queue.
373-
374-
Args:
387+
388+
Parameters
389+
----------
375390
worker_no: int
376391
Worker number.
377392
n_sim: int
@@ -400,7 +415,7 @@ def _run_simulation_worker(
400415
while True:
401416
if queue.get() == "STOP":
402417
break
403-
418+
404419
sim_idx = sim_counter.increment()
405420
sim_start = time()
406421

@@ -442,12 +457,16 @@ def _run_simulation_worker(
442457

443458
# Write flight setting and results to file
444459
inputs_lock.acquire()
445-
with open(file_paths["input_file"], mode='a', encoding="utf-8") as f:
460+
with open(
461+
file_paths["input_file"], mode='a', encoding="utf-8"
462+
) as f:
446463
f.write(json.dumps(inputs_dict, cls=RocketPyEncoder) + "\n")
447464
inputs_lock.release()
448-
465+
449466
outputs_lock.acquire()
450-
with open(file_paths["output_file"], mode='a', encoding="utf-8") as f:
467+
with open(
468+
file_paths["output_file"], mode='a', encoding="utf-8"
469+
) as f:
451470
f.write(json.dumps(results, cls=RocketPyEncoder) + "\n")
452471
outputs_lock.release()
453472

@@ -468,12 +487,12 @@ def _run_simulation_worker(
468487

469488
inputs_lock.acquire()
470489
with h5py.File(file_paths["input_file"], 'a') as h5_file:
471-
MonteCarlo.dict_to_h5(h5_file, '/', export_inputs)
490+
MonteCarlo.__dict_to_h5(h5_file, '/', export_inputs)
472491
inputs_lock.release()
473492

474493
outputs_lock.acquire()
475494
with h5py.File(file_paths["output_file"], 'a') as h5_file:
476-
MonteCarlo.dict_to_h5(h5_file, '/', export_outputs)
495+
MonteCarlo.__dict_to_h5(h5_file, '/', export_outputs)
477496
outputs_lock.release()
478497

479498
sim_end = time()
@@ -482,15 +501,15 @@ def _run_simulation_worker(
482501
"-" * 80
483502
+ f"\nSimulation {sim_idx} took {sim_end - sim_start} seconds to run."
484503
)
485-
504+
486505
except Exception as error:
487506
print(f"Error on iteration {sim_idx}: {error}")
488507
errors_lock.acquire()
489508
with open(file_paths["error_file"], mode='a', encoding="utf-8") as f:
490509
f.write(json.dumps(inputs_dict, cls=RocketPyEncoder) + "\n")
491510
errors_lock.release()
492511
raise error
493-
512+
494513
finally:
495514
print("Worker stopped.")
496515

@@ -540,8 +559,8 @@ def __run_single_simulation(
540559
export_inputs = {str(sim_idx): input_parameters}
541560
export_outputs = {str(sim_idx): flight_results}
542561

543-
self.dict_to_h5(input_file, '/', export_inputs)
544-
self.dict_to_h5(output_file, '/', export_outputs)
562+
self.__dict_to_h5(input_file, '/', export_inputs)
563+
self.__dict_to_h5(output_file, '/', export_outputs)
545564

546565
average_time = (process_time() - self.start_cpu_time) / self.iteration_count
547566
estimated_time = int(
@@ -1016,28 +1035,58 @@ def inspect_object_attributes(obj):
10161035
return result
10171036

10181037
@staticmethod
1019-
def dict_to_h5(h5_file, path, dic):
1038+
def __get_initial_sim_idx(file):
1039+
"""
1040+
Get the initial simulation index from the filename.
1041+
1042+
Parameters
1043+
----------
1044+
filename : str
1045+
Name of the file to be analyzed.
1046+
1047+
Returns
1048+
-------
1049+
int
1050+
Initial simulation index.
1051+
"""
1052+
if len(file.keys()) == 0:
1053+
return 0
1054+
1055+
keys = [int(key) for key in file.keys()]
1056+
return max(keys) + 1
1057+
1058+
1059+
@staticmethod
1060+
def __dict_to_h5(h5_file, path, dic):
10201061
"""
10211062
....
10221063
"""
10231064
for key, item in dic.items():
1024-
if isinstance(
1025-
item, (np.int64, np.float64, int, float)
1026-
):
1065+
if isinstance(item, (np.int64, np.float64, int, float)):
10271066
data = np.array([[item]])
1028-
h5_file.create_dataset(path + key, data=data, shape=data.shape, dtype=data.dtype)
1067+
h5_file.create_dataset(
1068+
path + key, data=data, shape=data.shape, dtype=data.dtype
1069+
)
10291070
elif isinstance(item, np.ndarray):
10301071
if len(item.shape) < 2:
1031-
item = item.reshape(-1, 1) # Ensure it is a column vector
1032-
h5_file.create_dataset(path + key, data=item, shape=item.shape, dtype=item.dtype)
1072+
item = item.reshape(-1, 1) # Ensure it is a column vector
1073+
h5_file.create_dataset(
1074+
path + key,
1075+
data=item,
1076+
shape=item.shape,
1077+
dtype=item.dtype,
1078+
)
10331079
elif isinstance(item, (str, bytes)):
1034-
h5_file.create_dataset(path + key, data=item)
1080+
h5_file.create_dataset(
1081+
path + key,
1082+
data=item,
1083+
)
10351084
elif isinstance(item, Function):
10361085
raise TypeError(
10371086
"Function objects should be preprocessed before saving."
10381087
)
10391088
elif isinstance(item, dict):
1040-
MonteCarlo.dict_to_h5(h5_file, path + key + '/', item)
1089+
MonteCarlo.__dict_to_h5(h5_file, path + key + '/', item)
10411090
else:
10421091
pass # Implement other types as needed
10431092

@@ -1054,12 +1103,12 @@ def __init__(self):
10541103

10551104

10561105
class SimCounter:
1057-
def __init__(self):
1058-
self.count = 0
1106+
def __init__(self, initial_count):
1107+
self.count = initial_count
10591108

10601109
def increment(self) -> int:
10611110
self.count += 1
1062-
return self.count
1111+
return self.count - 1
10631112

10641113
def get_count(self) -> int:
10651114
return self.count

0 commit comments

Comments
 (0)