RunSimulator: Mass Point Model

This example shows how to run multiple MassPointModel simulations in serial and parallelly using RunSimulator.

First, we import the class MassPointModel and define a simulator based on MassPointModel.run() method.

from psimpy.simulator import MassPointModel

mpm = MassPointModel()
mpm_simulator = mpm.run

This simulator takes required input data, solves the mass point model, and returns time history of the mass point’s location and velocity (see MassPointModel.run()).

Required inputs for mpm_simulator include:
  1. topographic data: digital elevation model (in ESRI ascii format)

  2. friction coefficients: coulomb friction and turbulent friction coefficients

  3. initial state: initial location and initial velocity of the masspoint

  4. computational parameters: such as time step, end time, etc.

The synthetic topography synthetic_topo.asc is used here for illustration. It is located at the /tests/data/ folder.

import os

dir_data = os.path.abspath('../../../tests/data/')
elevation = os.path.join(dir_data, 'synthetic_topo.asc')

Note

You may need to modify dir_data according to where you save synthetic_topo.asc on your local machine.

For this example, we are going to run multiple simulations at different values of coulomb friction coefficient (coulomb_friction) and turbulent friction coefficient (turbulent_friction) while keep other inputs fixed. Namely, coulomb_friction and turbulent_friction are variable input parameters. All other inputs are fixed input parameters.

import numpy as np
import itertools

# Variable iput parameters are defined as a list of strings. Their values will be
# passed as a numpy array (var_samples) when run simulations.
var_inp_parameter = ['coulomb_friction', 'turbulent_friction']
coulomb_friction = np.arange(0.1, 0.31, 0.1)
turbulent_friction = np.arange(500, 2001, 400)
var_samples = np.array(
    [x for x in itertools.product(coulomb_friction, turbulent_friction)])
print("Number of variable input samples are: ", len(var_samples))

# Fixed input parameters are defined as a dictionary. Their values are given.
fix_inp = {'elevation': elevation, 'x0': 200, 'y0': 2000, 'tend': 50}
Number of variable input samples are:  12

Note

Parameters of mpm_simulator which are not included in var_inp_parameter and fix_inp, such as ux0, uy0 and curvature, will automatically take their default values.

We may want to save outputs returned by mpm_simulator at each simulation for later inspection or processing. In that case, we need to define dir_out and set save_out as True.

import os

# Here we create a folder called `temp_run_MassPointModel_example` to save outputs
# returned at each simulation.
cwd = os.getcwd()
if not os.path.exists('temp_run_MassPointModel_example'):
    os.mkdir('temp_run_MassPointModel_example')
dir_out = os.path.join(cwd, 'temp_run_MassPointModel_example')

Now we can define an object of RunSimulator by

from psimpy.simulator import RunSimulator

run_mpm_simulator = RunSimulator(mpm_simulator, var_inp_parameter, fix_inp,
    dir_out=dir_out, save_out=True)

Note

Since outputs of each simulation are saved in the same folder dir_out, we need to give each file a unique name in order to avoid conflict. This is realized by defining prefixes.

serial_prefixes = ["serial"+str(i) for i in range(len(var_samples))]
parallel_prefixes = ["parallel"+str(i) for i in range(len(var_samples))]

Using RunSimulator.serial_run() method or RunSimulator.parallel_run() method, we can run the simulations in serial or parallelly.

import time

start = time.time()
run_mpm_simulator.serial_run(var_samples=var_samples, prefixes=serial_prefixes)
serial_time = time.time() - start
serial_output = run_mpm_simulator.outputs

start = time.time()
# max_workers controls maximum number of tasks running in parallel
run_mpm_simulator.parallel_run(var_samples, prefixes=parallel_prefixes, max_workers=4)
parallel_time = time.time() - start
parallel_output = run_mpm_simulator.outputs

print("Serial run time: ", serial_time)
print("Parallel run time: ", parallel_time)
Serial run time:  1.1941184997558594
Parallel run time:  0.4859800338745117

Once a simulation is done, its output is saved to dir_out. All output files generated by above simulations are as follows:

os.listdir(dir_out)
['serial0_output.npy', 'serial9_output.npy', 'serial11_output.npy', 'parallel10_output.npy', 'parallel0_output.npy', 'parallel5_output.npy', 'parallel11_output.npy', 'serial1_output.npy', 'serial4_output.npy', 'parallel7_output.npy', 'serial5_output.npy', 'serial8_output.npy', 'parallel6_output.npy', 'parallel4_output.npy', 'parallel2_output.npy', 'serial3_output.npy', 'serial6_output.npy', 'parallel3_output.npy', 'serial2_output.npy', 'parallel9_output.npy', 'serial7_output.npy', 'parallel1_output.npy', 'parallel8_output.npy', 'serial10_output.npy']

Once all simulations are done, their outputs can also be accessed by RunSimulator.outputs attribute, which is a list.

# Here we print the maximum velocity of each simulation in the parallel run.
max_v = [np.max(output[:,5]) for output in parallel_output]
print(
    f"Maximum velocity of {parallel_prefixes[0]} to {parallel_prefixes[-1]} are: ",
    '\n',
    max_v)
Maximum velocity of parallel0 to parallel11 are:
 [16.55505557211892, 22.014114896121015, 26.245546886730782, 29.78487303612985, 15.34198274528081, 20.371864007499866, 24.249957243412815, 27.485487855011687, 14.031665649204944, 18.589324449452967, 22.092803891476187, 24.99727320816709]

Warning

If one simulation failed due to whatever reason, the error massage will be printed to the screen but other simulations will continue. In that case, the output file of failed simulation will not be writted to dir_out. Also, the element of RunSimulator.outputs corresponding to that simulation will be a string representing the error message, instead of a numpy array.

Here we delete the folder temp_run_MassPointModel_example and all files therein.

import shutil

shutil.rmtree(dir_out)

Total running time of the script: ( 0 minutes 1.687 seconds)

Gallery generated by Sphinx-Gallery