3
3
import json
4
4
import os
5
5
import pickle
6
+ from copy import deepcopy
6
7
from pathlib import Path
7
8
from time import process_time , time
8
9
18
19
from rocketpy .plots .monte_carlo_plots import _MonteCarloPlots
19
20
from rocketpy .prints .monte_carlo_prints import _MonteCarloPrints
20
21
from rocketpy .simulation .flight import Flight
21
- from rocketpy .simulation .sim_config .flight2serializer import flightv1_serializer
22
- from rocketpy .simulation .sim_config .serializer import function_serializer
23
22
from rocketpy .stochastic import (
24
23
StochasticEnvironment ,
25
24
StochasticFlight ,
@@ -189,6 +188,9 @@ def simulate(
189
188
saved to the output file as a .h5 file. Default is False.
190
189
parallel : bool, optional
191
190
If True, the simulations will be run in parallel. Default is False.
191
+ n_workers : int, optional
192
+ Number of workers to be used. If None, the number of workers
193
+ will be equal to the number of CPUs available. Default is None.
192
194
193
195
Returns
194
196
-------
@@ -336,8 +338,6 @@ def __run_in_parallel(self, append, light_mode, n_workers=None):
336
338
337
339
with MonteCarloManager () as manager :
338
340
# initialize queue
339
- inputs_lock = manager .Lock ()
340
- outputs_lock = manager .Lock ()
341
341
errors_lock = manager .Lock ()
342
342
343
343
go_write_inputs = [manager .Semaphore (value = 1 ) for _ in range (n_sim_memory )]
@@ -411,6 +411,8 @@ def __run_in_parallel(self, append, light_mode, n_workers=None):
411
411
args = (
412
412
i ,
413
413
n_workers ,
414
+ light_mode ,
415
+ file_paths ,
414
416
self .environment ,
415
417
self .rocket ,
416
418
self .flight ,
@@ -420,13 +422,12 @@ def __run_in_parallel(self, append, light_mode, n_workers=None):
420
422
go_write_results ,
421
423
go_read_inputs ,
422
424
go_read_results ,
423
- light_mode ,
424
- file_paths ,
425
425
shared_inputs_buffer .name ,
426
426
shared_results_buffer .name ,
427
427
inputs_size ,
428
428
results_size ,
429
429
n_sim_memory ,
430
+ self .export_sample_time ,
430
431
),
431
432
)
432
433
processes .append (p )
@@ -504,6 +505,8 @@ def __run_in_parallel(self, append, light_mode, n_workers=None):
504
505
def __run_simulation_worker (
505
506
worker_no ,
506
507
n_workers ,
508
+ light_mode ,
509
+ file_paths ,
507
510
sto_env ,
508
511
sto_rocket ,
509
512
sto_flight ,
@@ -513,13 +516,12 @@ def __run_simulation_worker(
513
516
go_write_results ,
514
517
go_read_inputs ,
515
518
go_read_results ,
516
- light_mode ,
517
- file_paths ,
518
519
shared_inputs_name ,
519
520
shared_results_name ,
520
521
inputs_size ,
521
522
results_size ,
522
523
n_sim_memory ,
524
+ export_sample_time ,
523
525
):
524
526
"""
525
527
Runs a single simulation worker.
@@ -528,6 +530,14 @@ def __run_simulation_worker(
528
530
----------
529
531
worker_no : int
530
532
Worker number.
533
+ n_workers : int
534
+ Number of workers.
535
+ light_mode : bool
536
+ If True, only variables from the export_list will be saved to
537
+ the output file as a .txt file. If False, all variables will be
538
+ saved to the output file as a .h5 file.
539
+ file_paths : dict
540
+ Dictionary with the file paths.
531
541
sto_env : StochasticEnvironment
532
542
Stochastic environment object.
533
543
sto_rocket : StochasticRocket
@@ -536,28 +546,26 @@ def __run_simulation_worker(
536
546
Stochastic flight object.
537
547
sim_counter : SimCounter
538
548
Simulation counter object.
539
- inputs_lock : Lock
540
- Lock object for inputs file.
541
- outputs_lock : Lock
542
- Lock object for outputs file.
543
549
errors_lock : Lock
544
- Lock object for errors file.
545
- buffer_lock : Lock
546
- Lock object for shared memory buffer .
547
- light_mode : bool
548
- If True, only variables from the export_list will be saved to
549
- the output file as a .txt file. If False, all variables will be
550
- saved to the output file as a .h5 file .
551
- file_paths : dict
552
- Dictionary with the file paths .
553
- shared_inputs_buffer : Array
554
- Shared memory buffer for inputs.
555
- shared_results_buffer : Array
556
- Shared memory buffer for results.
550
+ Lock to write errors to the error file.
551
+ go_write_inputs : list
552
+ List of semaphores to write the inputs .
553
+ go_write_results : list
554
+ List of semaphores to write the results.
555
+ go_read_inputs : list
556
+ List of semaphores to read the inputs .
557
+ go_read_results : list
558
+ List of semaphores to read the results .
559
+ shared_inputs_name : str
560
+ Name of the shared memory buffer for the inputs.
561
+ shared_results_name : str
562
+ Name of the shared memory buffer for the results.
557
563
inputs_size : int
558
- Size of the serialized dictionary .
564
+ Size of the inputs to be written .
559
565
results_size : int
560
- Size of the serialized dictionary.
566
+ Size of the results to be written.
567
+ n_sim_memory : int
568
+ Number of simulations that can be stored in memory.
561
569
562
570
Returns
563
571
-------
@@ -588,7 +596,7 @@ def __run_simulation_worker(
588
596
initial_solution = sto_flight .initial_solution
589
597
terminate_on_apogee = sto_flight .terminate_on_apogee
590
598
591
- flight = Flight (
599
+ monte_carlo_flight = Flight (
592
600
rocket = rocket ,
593
601
environment = env ,
594
602
rail_length = rail_length ,
@@ -609,48 +617,45 @@ def __run_simulation_worker(
609
617
]
610
618
for item in d .items ()
611
619
)
612
-
613
620
# Construct the dict with the results from the flight
614
621
results = {
615
- export_item : getattr (flight , export_item )
622
+ export_item : getattr (monte_carlo_flight , export_item )
616
623
for export_item in file_paths ["export_list" ]
617
624
}
618
625
619
- export_inputs_ready = json .dumps (inputs_dict , cls = RocketPyEncoder )
620
- export_outputs_ready = json .dumps (results , cls = RocketPyEncoder )
626
+ export_inputs = json .dumps (inputs_dict , cls = RocketPyEncoder )
627
+ export_outputs = json .dumps (results , cls = RocketPyEncoder )
621
628
622
629
else :
623
630
# serialize data
624
- input_parameters = flightv1_serializer (
625
- flight , f"Simulation_ { sim_idx } " , return_dict = True
631
+ flight_results = MonteCarlo . inspect_object_attributes (
632
+ monte_carlo_flight , sample_time = export_sample_time
626
633
)
627
634
628
- flight_results = MonteCarlo .inspect_object_attributes (flight )
629
-
630
- # place in dictionary as it will be found in output file
635
+ # place data in dictionary as it will be found in output file
631
636
export_inputs = {
632
- str (sim_idx ): input_parameters ,
637
+ str (
638
+ sim_idx
639
+ ): "Currently no addition data is exported. Use the results file." ,
633
640
}
634
641
635
642
export_outputs = {
636
643
str (sim_idx ): flight_results ,
637
644
}
638
645
639
- # downsample the arrays, ensuring the maximum size won't be exceeded
640
- export_inputs_ready = MonteCarlo .__downsample_recursive (
641
- data_dict = export_inputs ,
642
- max_time = flight .max_time ,
643
- sample_time = 0.1 ,
644
- )
645
- export_outputs_ready = MonteCarlo .__downsample_recursive (
646
- data_dict = export_outputs ,
647
- max_time = flight .max_time ,
648
- sample_time = 0.1 ,
646
+ # convert to bytes
647
+ export_inputs_bytes = pickle .dumps (export_inputs )
648
+ export_outputs_bytes = pickle .dumps (export_outputs )
649
+
650
+ if len (export_inputs_bytes ) > inputs_size :
651
+ raise ValueError (
652
+ "Input data is too large to fit in the shared memory buffer."
649
653
)
650
654
651
- # convert to bytes
652
- export_inputs_bytes = pickle .dumps (export_inputs_ready )
653
- export_outputs_bytes = pickle .dumps (export_outputs_ready )
655
+ if len (export_outputs_bytes ) > results_size :
656
+ raise ValueError (
657
+ "Output data is too large to fit in the shared memory buffer."
658
+ )
654
659
655
660
# add padding to make sure the byte stream fits in the allocated space
656
661
export_inputs_bytes = export_inputs_bytes .ljust (inputs_size , b'\0 ' )
@@ -754,14 +759,21 @@ def __run_single_simulation(
754
759
output_file = output_file ,
755
760
)
756
761
else :
757
- input_parameters = flightv1_serializer (
758
- monte_carlo_flight , f"Simulation_{ sim_idx } " , return_dict = True
762
+ # serialize data
763
+ flight_results = MonteCarlo .inspect_object_attributes (
764
+ monte_carlo_flight , sample_time = self .export_sample_time
759
765
)
760
766
761
- flight_results = self .inspect_object_attributes (monte_carlo_flight )
767
+ # place data in dictionary as it will be found in output file
768
+ export_inputs = {
769
+ str (
770
+ sim_idx
771
+ ): "Currently no addition data is exported. Use the results file." ,
772
+ }
762
773
763
- export_inputs = {str (sim_idx ): input_parameters }
764
- export_outputs = {str (sim_idx ): flight_results }
774
+ export_outputs = {
775
+ str (sim_idx ): flight_results ,
776
+ }
765
777
766
778
self .__dict_to_h5 (input_file , '/' , export_inputs )
767
779
self .__dict_to_h5 (output_file , '/' , export_outputs )
@@ -913,7 +925,9 @@ def __get_export_size(self, light_mode):
913
925
monte_carlo_flight = self .flight .create_object ()
914
926
915
927
if monte_carlo_flight .max_time is None or monte_carlo_flight .max_time <= 0 :
916
- raise ValueError ("The max_time attribute must be greater than zero." )
928
+ raise ValueError (
929
+ "The max_time attribute must be greater than zero. To use parallel mode."
930
+ )
917
931
918
932
# Export inputs and outputs to file
919
933
if light_mode :
@@ -931,13 +945,13 @@ def __get_export_size(self, light_mode):
931
945
for export_item in self .export_list
932
946
}
933
947
else :
934
- input_parameters = flightv1_serializer (
935
- monte_carlo_flight , f"probe_simulation" , return_dict = True
948
+ flight_results = self . inspect_object_attributes (
949
+ monte_carlo_flight , self . export_sample_time
936
950
)
937
951
938
- flight_results = self . inspect_object_attributes ( monte_carlo_flight )
939
-
940
- export_inputs = { "probe_flight" : input_parameters }
952
+ export_inputs = {
953
+ "probe_flight" : "Currently no addition data is exported. Use the results file." ,
954
+ }
941
955
results = {"probe_flight" : flight_results }
942
956
943
957
# downsample the arrays, filling them up to the max time
@@ -1408,7 +1422,49 @@ def all_info(self):
1408
1422
self .plots .all ()
1409
1423
1410
1424
@staticmethod
1411
- def inspect_object_attributes (obj ):
1425
+ def time_function_serializer (function_object , t_range = None , sample_time = None ):
1426
+ """
1427
+ Method to serialize a Function object into a numpy array. If the function is
1428
+ callable, it will be discretized. If the downsample_time is specified, the
1429
+ function will be downsampled. This serializer should not be used for
1430
+ function that are not time dependent.
1431
+
1432
+ Parameters
1433
+ ----------
1434
+ function_object : Function
1435
+ Function object to be serialized.
1436
+ t_range : tuple, optional
1437
+ Tuple with the initial and final time of the function. Default is None.
1438
+ sample_time : float, optional
1439
+ Time interval between samples. Default is None.
1440
+
1441
+ Returns
1442
+ -------
1443
+ np.ndarray
1444
+ Serialized function as a numpy array.
1445
+ """
1446
+ func = deepcopy (function_object )
1447
+
1448
+ # Discretize the function if it is callable
1449
+ if callable (function_object .source ):
1450
+ if t_range is not None :
1451
+ func .set_discrete (* t_range , (t_range [1 ] - t_range [0 ]) / sample_time )
1452
+ else :
1453
+ raise ValueError ("t_range must be specified for callable functions" )
1454
+
1455
+ source = func .get_source ()
1456
+
1457
+ # Ensure the downsampling is applied
1458
+ if sample_time is not None :
1459
+ t0 = source [0 , 0 ]
1460
+ tf = source [- 1 , 0 ]
1461
+ t = np .arange (t0 , tf , sample_time )
1462
+ source = func (t )
1463
+
1464
+ return source
1465
+
1466
+ @staticmethod
1467
+ def inspect_object_attributes (obj , sample_time = 0.1 ):
1412
1468
"""
1413
1469
Inspects the attributes of an object and returns a dictionary of its
1414
1470
attributes.
@@ -1434,15 +1490,13 @@ def inspect_object_attributes(obj):
1434
1490
1435
1491
# Check if the attribute is of a type we are interested in and not a private attribute
1436
1492
if isinstance (
1437
- attr_value , (int , float , dict , Function )
1493
+ attr_value , (int , float , Function )
1438
1494
) and not attr_name .startswith ('_' ):
1439
1495
if isinstance (attr_value , Function ):
1440
1496
# Serialize the Functions
1441
- result [attr_name ] = function_serializer (attr_value )
1442
-
1443
- elif isinstance (attr_value , dict ):
1444
- # Recursively inspect the dictionary attributes
1445
- result [attr_name ] = MonteCarlo .inspect_object_attributes (attr_value )
1497
+ result [attr_name ] = MonteCarlo .time_function_serializer (
1498
+ attr_value , None , sample_time
1499
+ )
1446
1500
1447
1501
elif isinstance (attr_value , (int , float )):
1448
1502
result [attr_name ] = attr_value
0 commit comments