3.9. Modifying simulation parameters

Up to now, we always have used the predefined parameter values which where set in the constructor of the problem class. As an example, we have set values for mass, spring_constant and initial_displacement in the problem class of the harmonic oscillator in the previous section. These were then passed to the equation class and used as initial condition, respectively. Each change of the problem parameters would naively require to edit these values in the constructor of this class. The idea of a complete problem class is, however, that only reasonable default parameters are set and all parameters can be modified easily. In fact for complicated problems, it is a good practice to even put the problem class in another Python file than the script actually used to start the simulation.

3.9.1. Inside Python

The direct way of modifying parameters before running the simulation is directly in Python. As described, we can just import the previously discussed Python file where the problem is defined.

#Import the script where the problem is defined (here, the file dimensional_oscillator_with_units.py is in the same directory)
from dimensional_oscillator_with_units import *

if __name__=="__main__":
     with DimensionalOscillatorProblem() as problem:

             #Modify the parameters
             problem.initial_displacement=-10*centi*meter
             problem.mass=1*kilogram
             problem.spring_constant=1*newton/meter

             problem.run(endtime=10*second,numouts=1000)

Since the default output directory will have the same name as the script name without the .py extension, i.e. dim_osci_run here, the output directories are normally different by default. You can create multiple run scripts of this type with different parameters and each will write to its own output folder. To change the folder, you still can use set_output_directory().

Note that after the first call of run() (or also several other methods like output() and so on), one cannot change the output directory or any parameters that easily. The reason is that all these settings are required for the code generation, e.g. for the nondimensionalization and so on. If it is really necessary, one can invoke a recompilation by redefine_problem(), but this is not discussed here. Therefore, it is important to set all parameters before the first run() statement.

Sometimes, it is not beneficial to have an individual run script for each simulation indented to be performed. If you want to run e.g. hundreds or thousands of simulations to perform a parameter scan, it would require to write a lot of script file. Of course, you could loop through it in python, e.g.

from dimensional_oscillator_with_units import *

if __name__=="__main__":
     for k_in_N_per_m in [0.1,0.2,0.5,1,2,5]: #Scan the spring constant
             with DimensionalOscillatorProblem() as problem:

                     #Modify the parameters
                     problem.initial_displacement=-10*centi*meter
                     problem.mass=1*kilogram
                     problem.spring_constant=k_in_N_per_m*newton/meter

                     problem.set_output_directory("dim_osci_seq_run_k_"+str(k_in_N_per_m))

                     problem.run(endtime=10*second,numouts=1000)

However, this would run all simulations sequentially. For this simple problem, it is not an issue, but if each simulation would take several hours or days, it is not an option.

3.9.2. Via the command line

You can always override simulation parameters from the command line. For this, you can add the command line arguments –outdir so change the output directory and -P to modify parameters, e.g.

python dimensional_oscillator_with_units.py --outdir dim_osci_run_modified_params -P spring_constant='1.5*newton/meter' initial_displacement='0.25*meter'

invokes the script dimensional_oscillator_with_units.py, output will be written to the directory dim_osci_run_modified_params and we set the spring constant \(k=1.5 \:\mathrm{N}/\mathrm{m}\) and the initial displacement to \(x_0=0.25\:\mathrm{m}\). Note that the parameters passed via the command line will be set after the parameters set in dimensional_oscillator_with_units.py before the run() statement. Hence, the command line will override the parameters set in the script.

This allows to use e.g. bash in Linux or batch script in Windows to call multiple simulations in a loop.

3.9.3. Parameter scans via Python

Finally, you can also scan through parameters in parallel in Python. Again, we want to run the script dimensional_oscillator_with_units.py with multiple parameter settings. pyoomph comes with a class that can take care of looping over parameters and invoke a call of the simulation script with each particular parameter combination as follows:

#Import the parallel parameter scanner
from pyoomph.utils.paramscan import *
from pyoomph.expressions.units import * #Import the units (meter etc)

if __name__=="__main__":

     #Create a parameter scanner, give the script to run and the max number of processes to run simultaneously
     scanner=ParallelParameterScan("dimensional_oscillator_with_units.py",max_procs=4)

     for k_in_N_per_m in [0.1,0.2,0.5,1,2,5]: #Scan the spring constant
             sim=scanner.new_sim("dim_osci_seq_run_k_"+str(k_in_N_per_m))

             #Modify the parameters
             sim.initial_displacement=-10*centi*meter
             sim.mass=1*kilogram
             sim.spring_constant=k_in_N_per_m*newton/meter


     #Run all (and rerun also already finished sims)
     scanner.run_all(skip_done=False)

First, a ParallelParameterScan object is created, passing the script that should be started in parallel and an optional argument of how many processes to be used. If the latter is omitted, it will default to the number of CPUs on the system. The script to run must be either in the same directory or the corresponding full or relative directory has to be passed. Then, for each parameter combination, we add a new simulation to the list using new_sim() with the output directory of this particular script. Note that these output directories will be sub-directories of the directory of the parameter scan, which defaults to dim_osci_para_run here, but can be set via the keyword argument output_dir in the constructor of the ParallelParameterScan object. We can then modify any parameter we like to adjust for the simulation by setting them to the object obtained by new_sim().

Finally, if all simulations to be started are added, we can invoke the run_all() method to start them in parallel. The moment one simulation finishes, the next one is started, but the maximum number of parallel processes is never exceeded. The optional argument skip_done let you control whether you want to rerun already completed simulations or not. If you make e.g. relevant changes in the script dimensional_oscillator_with_units.py, it obviously has to be set to False, since the finished results may differ. Otherwise, you can easily continue a previously interrupted scanning process by setting skip_done=True, which will skip all simulations that were already completed previously.