Muk3D does have some basic tools for creating storage-elevation curves for a TSF (accounting for the beach slope) however they are really only limited to simple situations (simple dam configurations, simple deposition schemes, simple pond models).
Scripting in Muk3D with Python is a way we can create storage-elevation curves for more complicated TSF configurations. This tutorial will show how this can be done by recording macros and then wrapping them with some simple Python code.
Assumptions
This tutorial assumes that a suitable dam template has been created and that some tailings properties are already defined.
The dam template used in this model is an upstream constructed dam.
Record the basic macro
The basic macro that we will record will be to:
- Clear graphics (to get rid of any old geometry);
- Raise a dam template; and
- Pour tailings from a fixed elevation with a fixed pond volume.
To start macro recording run Scripts/Record macro and call the macro Create_storage_curve.py.
Raise the dam using Dam templates/Dams/Raise dam. Use values that are appropriate for your dataset. The actual values entered will get replaced with variables later on, so the specific values don't matter too much.
Make sure that you check Generate discharge points upstream since we'll be using these in the deposition model.
Running Ooze/Single stream deposition/Fixed pond volume/As drawn, we fill out the basic information required. The base grid and discharge points are in the dam raise directory.
The resulting script file is below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# running command Clear graphics
# Parameters from dialog
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': False})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': 110.0,
'generate_discharge_points': True,
'grid': u'../../../grid-10.mgrid',
'save': True,
'upstream_toe_extension': 0})
# running command Single stream fixed pond volume, as drawn
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, as drawn')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, as drawn',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': 200000.0,
'maxPondElevation': 109.0,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': 105.0,
'pondTolerance': 1.0,
'pond_location': ( 2872.7224207067416,
-2040.756505049976,
99.35871404895046),
'seCurveIncrement': 0.1,
'tailings': u'Sand'})
|
Running a series of deposition runs
The next step is to modify the script to run through a series of dam raises and deposition runs, each building on the last. To do this, we need to do 2 things:
- Replace the hardwired values that we want to change for each run and replace them with variables.
- Encapsulate the dam raising and deposition functions in a loop.
- Get relevant output data from the dam raise and deposition run:
- dam fill volume;
- deposited tailings volume; and
- pond elevation.
The variables that will change for each run will be:
- Base grid for the dam raise. For the first run it will be the bare earth. For subsequent runs it will be the output of the discharge model (output_grid.mgrid).
- Crest elevation for the dam raise.
- Pond elevation guess for the deposition run.
- Maximum pond elevation (set to 1m below dam crest elevation)
For now, we'll leave the pond volume hardwired in the script.
The variables are created and added to the top of the script file (lines 4 - 7).
Dam fill volume is stored in the result variable that is returned when the Raise dam command is called. This result value is a Python dictionary and the fill volume can be accessed using the notation (line 26)
dam_fill_volume = results['fill_volume']
Finally we get the deposition results after the deposition run (lines 47 - 57). The code to get the deposition results is taken from this tutorial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# running command Clear graphics
# Parameters from dialog
base_grid = u'../../../grid-10.mgrid'
dam_crest_elevation = 110.0
pond_elevation_guess = 105.0
max_pond_elevation = dam_crest_elevation - 1.0
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': True})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': dam_crest_elevation,
'generate_discharge_points': True,
'grid': base_grid,
'save': True,
'upstream_toe_extension': 0})
dam_fill_volume = result['fill_volume']
# running command Single stream fixed pond volume, as drawn
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, as drawn')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, as drawn',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': 200000.0,
'maxPondElevation': max_pond_elevation,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': pond_elevation_guess,
'pondTolerance': 1.0,
'pond_location': ( 2872.7224207067416,
-2040.756505049976,
99.35871404895046),
'seCurveIncrement': 0.1,
'tailings': u'Sand'})
vars = {}
script_text = open('results.py','rt').read()
exec script_text in None, vars
result_dict = vars['results']
pipelines = result_dict['pipelines']
pipeline = pipelines[0]
tailings_volume = pipeline['total volume']
pond_summary = result_dict['pond_summary']
pond_elevation = pond_summary['pond elevation']
|
If we run this script we should get the same result as when the values were hardwired.
Running a deposition sequence
To run a sequence of deposition runs we need to encapsulate this code in a loop. We'll use a for loop and run through a range of elevations. The elevation range will be specified by a start elevation, an elevation interval, and a maximum elevation.
We can start by adding a couple of new variables at the top of the script (lines 9 and 10 in the script below):
- max_crest_elevation; and
- elevation_increment.
We then use a while loop to run the deposition until the dam_crest_elevation is greater than the max_crest_elevation.
In Python we use indentation to specify which code gets executed inside a control statement or loop. In this case, if we want the Clear graphics, Raise dam, and deposition functions to run in the loop we need to indent them. The Python standard is to indent code blocks by 4 spaces. If you're using Notepad++ as your text editor then its a matter of selecting the appropriate code, pressing tab and it will be indented with spaces. (See this article about setting Notepad++ up correctly - if it inserts Tabs then errors might occur when its time to run the script).
In this case, lines 15 - 70 are indented and will be run in each iteration of the while loop.
In lines 66 - 70, the variables are updated for the next iteration of the loop:
- dam_crest_elevation is increased by the elevation_increment.
- base_grid is changed to output_grid.mgrid which is the output of the previous deposition run. This way, each run is giving the incremental deposition volume, and since its upstream raises, the actual dam fill volume for the raise.
- max_pond_elevation is changed to be 1m below the new dam_crest_elevation
- pond_elevation_guess is changed to pond_elevation + elevation_increment. This might be a reasonable way to guess the new pond elevation, but we'll see for sure then the deposition model runs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# running command Clear graphics
# Parameters from dialog
base_grid = u'../../../grid-10.mgrid'
dam_crest_elevation = 110.0
pond_elevation_guess = 105.0
max_pond_elevation = dam_crest_elevation - 1.0
max_crest_elevation = 115.0
elevation_increment = 1.0
while dam_crest_elevation <= max_crest_elevation:
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': True})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': dam_crest_elevation,
'generate_discharge_points': True,
'grid': base_grid,
'save': True,
'upstream_toe_extension': 0})
dam_fill_volume = result['fill_volume']
# running command Single stream fixed pond volume, as drawn
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, as drawn')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, as drawn',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': 200000.0,
'maxPondElevation': max_pond_elevation,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': pond_elevation_guess,
'pondTolerance': 1.0,
'pond_location': ( 2872.7224207067416,
-2040.756505049976,
99.35871404895046),
'seCurveIncrement': 0.1,
'tailings': u'Sand'})
vars = {}
script_text = open('results.py','rt').read()
exec script_text in None, vars
result_dict = vars['results']
pipelines = result_dict['pipelines']
pipeline = pipelines[0]
tailings_volume = pipeline['total volume']
pond_summary = result_dict['pond_summary']
pond_elevation = pond_summary['pond elevation']
# update variables for next running
dam_crest_elevation += elevation_increment
base_grid = 'output_grid.mgrid'
max_pond_elevation = dam_crest_elevation - 1
pond_elevation_guess = pond_elevation + elevation_increment
|
If we run this script now we should see that it loops through a series of deposition runs (6 in total). The elevation of the final dam in the 3D window should be elevation 115m.
Saving run results
For each iteration of the loop, the result values are recovered from the output files, but are not save, so the next step is to save the results and write them to a CSV. To do this, we're going to use module from the Python Standard Library called csv. The standard library documentation can be found here.
This library simplifies the task of reading and writing CSV files. To write a CSV file we can simply store the output data from the deposition runs as an array, and this module will format it as a CSV text file.
In line 1 the csv module is imported.
In line 2 the Python standard library os is loaded. This is because at the end of the deposition run we want to load the newly created CSV file and there is a function in the library to do this.
A variable called csv_file_name is added in line 13 since it is referenced a couple of times in the script.
In line 15, a try...except block is added. This tests to see if the csv_file_name can be opened for writing. If it can't (i.e. its open in another program), then an IOError exception is raised. Without the try...except statement the script would halt with an error. By using try...except, the exception is caught and the script can exit gracefully, after writing some meaningful feedback for the user.
In line 22, the function end is called. This is not a standard Python keyword, but a function that is only available in Python scripts run within Muk3D. It causes the script file to exit.
If the open command in line 16 is successful, the csv_file variable is a file handle. This is a reference to the file opened on disk and is needed by the csv.writer function that creates a csv writer object in line 25.
The csv writer object has a method called .writerow which takes an array of values. The values are written to the csv file using the default separator (comma) and each time this is called, the data is written on a new line.
In line 26, the header row is written.
In line 82, the output data for the run is written to the csv file. Note that you need to do this before some of the values are updated (e.g. dam crest elevation is incremented in line 85).
After all crest elevations have been modelled, in line 92 the csv_file filehandle is explicitly closed using the .close method.
Finally, in line 94, the command os.startfile is used to open the CSV file in the default windows viewer (usually Excel).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
import csv
import os
base_grid = u'../../../grid-10.mgrid'
dam_crest_elevation = 110.0
pond_elevation_guess = 105.0
max_pond_elevation = dam_crest_elevation - 1.0
max_crest_elevation = 115.0
elevation_increment = 1.0
csv_file_name = 'summary_results.csv'
try:
csv_file = open(csv_file_name, 'wb')
except IOError as e:
# this is raised when the file is already open. In that case, ask user to close file
# and run the command again.
print "The CSV file '{}' is already open in another program. Please close it and run this script again.".format(csv_file_name)
end()
csv_writer = csv.writer(csv_file)
csv_writer.writerow(['Dam elevation', 'Dam fill volume', 'Tailings volume', 'Pond elevation'])
while dam_crest_elevation <= max_crest_elevation:
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': True})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': dam_crest_elevation,
'generate_discharge_points': True,
'grid': base_grid,
'save': True,
'upstream_toe_extension': 0})
dam_fill_volume = result['fill_volume']
# running command Single stream fixed pond volume, as drawn
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, as drawn')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, as drawn',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': 200000.0,
'maxPondElevation': max_pond_elevation,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': pond_elevation_guess,
'pondTolerance': 1.0,
'pond_location': ( 2872.7224207067416,
-2040.756505049976,
99.35871404895046),
'seCurveIncrement': 0.1,
'tailings': u'Sand'})
vars = {}
script_text = open('results.py','rt').read()
exec script_text in None, vars
result_dict = vars['results']
pipelines = result_dict['pipelines']
pipeline = pipelines[0]
tailings_volume = pipeline['total volume']
pond_summary = result_dict['pond_summary']
pond_elevation = pond_summary['pond elevation']
# write the output values to the CSV
csv_writer.writerow([dam_crest_elevation, dam_fill_volume, tailings_volume, pond_elevation])
# update variables for next running
dam_crest_elevation += elevation_increment
base_grid = 'output_grid.mgrid'
max_pond_elevation = dam_crest_elevation - 1
pond_elevation_guess = pond_elevation + elevation_increment
csv_file.close()
os.startfile(csv_file_name)
|
The final script shown here is available for download at the bottom of this article.
Further work
This script could be enhanced in a few ways. A basic user interface could be added that allows the user to enter start/end elevations, interval, pond volume, and initial grid. The incorporation of a basic UI is covered in another tutorial.
Each deposition run will overwrite the previous run, so the final output_grid.mgrid in the directory will be the output grid from the last run. If we wanted to preserve each run we'd either need to do each run in a separate directory or rename the output files from each run after they are generated so there's a copy.
We could also take a screencapture for each run so that we could have a slideshow showing each stage. This will be covered in another article.