Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions raster/r.sim/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ build_library_in_subdir(
grass_datetime
grass_gis
grass_gmath
grass_parson
grass_raster
grass_vector
OPTIONAL_DEPENDS OpenMP::OpenMP_C
Expand Down
22 changes: 21 additions & 1 deletion raster/r.sim/r.sim.sediment/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
/********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <grass/config.h>
Expand Down Expand Up @@ -303,10 +304,24 @@ int main(int argc, char *argv[])
_("Number of threads which will be used for parallel computation.");
parm.threads->guisection = _("Parameters");

flag.print = G_define_flag();
flag.print->key = 'p';
flag.print->description = _("Print run summary to standard output");
flag.print->guisection = _("Print");

parm.format = G_define_standard_option(G_OPT_F_FORMAT);
parm.format->guisection = _("Print");

G_option_collective(parm.dxin, parm.dyin, NULL);
if (G_parser(argc, argv))
exit(EXIT_FAILURE);

SummaryFormat summary_format = SUMMARY_NONE;
if (flag.print->answer)
summary_format = strcmp(parm.format->answer, "json") == 0
? SUMMARY_JSON
: SUMMARY_PLAIN;

if (flag.generateSeed->answer) {
seed_value = G_srand48_auto();
G_verbose_message(_("Generated random seed (-s): %ld"), seed_value);
Expand Down Expand Up @@ -334,6 +349,7 @@ int main(int argc, char *argv[])
Inputs inputs = {0};
Outputs outputs = {0};
Grids grids = {0};
Summary summary = {0};

geometry.conv = G_database_units_to_meters_factor();

Expand Down Expand Up @@ -385,6 +401,7 @@ int main(int argc, char *argv[])
threads = 1;
#endif
G_message(_("Number of threads: %d"), threads);
summary.threads = threads;

/* sscanf(parm.nwalk->answer, "%d", &wp.maxwa); */
sscanf(parm.niter->answer, "%d", &settings.timesec);
Expand Down Expand Up @@ -435,7 +452,10 @@ int main(int argc, char *argv[])
init_grids_sediment(&setup, &geometry, &outputs, &grids);
/* treba dat output pre topoerdep */
main_loop(&setup, &geometry, &settings, &sim, &points, &inputs, &outputs,
&grids);
&grids, &summary);
print_summary(summary_format, &setup, &settings, &sim, &inputs, &outputs,
&summary);
free_summary(&summary);
free_walkers(&sim, outputs.outwalk);

/* Exit with Success */
Expand Down
70 changes: 70 additions & 0 deletions raster/r.sim/r.sim.sediment/r.sim.sediment.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,76 @@ <h2>DESCRIPTION</h2>
Increasing the number of threads with <b>nprocs</b> does not really
speed up the simulation.

<h2>NOTES</h2>

<h3>Run summary</h3>

With the <b>-p</b> flag, a summary of the run is printed to standard output
after the maps are written. The <b>format</b> option selects plain text
(one <code>key: value</code> pair per line) or JSON. Without <b>-p</b>, nothing is
printed to standard output regardless of <b>format</b>. The values are also
stored in the history of the <em>sediment_flux</em> raster map under the same
keys (see <a href="r.info.html">r.info</a>).

<p>
The keys are the same as for <a href="r.sim.water.html">r.sim.water</a> with these
differences:

<table border="1">
<tr><th>Key</th><th>Meaning</th><th>Unit</th></tr>
<tr><td><code>time_step_sediment</code></td><td>Time step limit derived from the sediment transport parameters, <code>null</code> when no cell exceeds the critical shear stress</td><td>s</td></tr>
<tr><td><code>velocity_max</code></td><td>Maximum flow velocity over the defined cells</td><td>m/s</td></tr>
<tr><td><code>sigma_max</code></td><td>Maximum first order reaction coefficient (detachment to transport capacity ratio) over the defined cells</td><td>1/m</td></tr>
<tr><td><code>mean_source_rate</code></td><td>Mean sediment source (detachment) rate</td><td>kg/m^2s</td></tr>
<tr><td><code>mean_infiltration</code></td><td>Not reported</td><td></td></tr>
<tr><td><code>transport_capacity</code>, <code>tlimit_erosion_deposition</code></td><td>Names of these maps, which are written once before the simulation starts, or <code>null</code> when not requested</td><td></td></tr>
<tr><td><code>outputs</code></td><td>A single entry with the <code>simulated_time</code> (s), <code>timestamp</code> and <code>walkers_remaining</code> at the time of writing, and the names of the <code>sediment_concentration</code>, <code>sediment_flux</code>, <code>erosion_deposition</code> and <code>walkers</code> maps, or <code>null</code> for maps which were not requested</td><td></td></tr>
</table>

<p>
Summary of a run in JSON:

<div class="code"><pre>
r.sim.sediment elevation=elevation water_depth=water_depth detachment_coeff=detachment \
transport_coeff=transport shear_stress=shear_stress man_value=1 \
sediment_flux=flux erosion_deposition=erdep transport_capacity=tc \
duration=1 random_seed=1 -p format=json
</pre></div>

<div class="code"><pre>
{
"walkers_requested": 60,
"walkers_generated": 78,
"walkers_remaining": 43,
"duration": 60,
"simulated_time": 54.891343113611583,
"time_step": 5.4891343113611581,
"time_step_sediment": 70.517234241515013,
"iterations_planned": 10,
"iterations_completed": 10,
"stopped_early": false,
"mean_velocity": 0.1821780891624834,
"velocity_max": 0.21544346900318839,
"sigma_max": 0.052657639041437901,
"mean_mannings_n": 1,
"mean_source_rate": 0.56025284041612944,
"threads": 1,
"transport_capacity": "tc",
"tlimit_erosion_deposition": null,
"outputs": [
{
"simulated_time": 54.891343113611583,
"timestamp": "1 minutes",
"walkers_remaining": 43,
"sediment_concentration": null,
"sediment_flux": "flux",
"erosion_deposition": "erdep",
"walkers": null
}
]
}
</pre></div>

<!--
<h2>NOTES</h2>

Expand Down
67 changes: 67 additions & 0 deletions raster/r.sim/r.sim.sediment/r.sim.sediment.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,73 @@ erosion/deposition map is noisy, higher number of walkers, given by
Increasing the number of threads with **nprocs** does not really speed
up the simulation.

## NOTES

### Run summary

With the **-p** flag, a summary of the run is printed to standard output
after the maps are written. The **format** option selects plain text
(one `key: value` pair per line) or JSON. Without **-p**, nothing is
printed to standard output regardless of **format**. The values are also
stored in the history of the *sediment_flux* raster map under the same
keys (see [r.info](r.info.md)).

The keys are the same as for [r.sim.water](r.sim.water.md) with these
differences:

| Key | Meaning | Unit |
| --- | --- | --- |
| `time_step_sediment` | Time step limit derived from the sediment transport parameters, `null` when no cell exceeds the critical shear stress | s |
| `velocity_max` | Maximum flow velocity over the defined cells | m/s |
| `sigma_max` | Maximum first order reaction coefficient (detachment to transport capacity ratio) over the defined cells | 1/m |
| `mean_source_rate` | Mean sediment source (detachment) rate | kg/m^2s |
| `mean_infiltration` | Not reported | |
| `transport_capacity`, `tlimit_erosion_deposition` | Names of these maps, which are written once before the simulation starts, or `null` when not requested | |
| `outputs` | A single entry with the `simulated_time` (s), `timestamp` and `walkers_remaining` at the time of writing, and the names of the `sediment_concentration`, `sediment_flux`, `erosion_deposition` and `walkers` maps, or `null` for maps which were not requested | |

Summary of a run in JSON:

```sh
r.sim.sediment elevation=elevation water_depth=water_depth detachment_coeff=detachment \
transport_coeff=transport shear_stress=shear_stress man_value=1 \
sediment_flux=flux erosion_deposition=erdep transport_capacity=tc \
duration=1 random_seed=1 -p format=json
```

```json
{
"walkers_requested": 60,
"walkers_generated": 78,
"walkers_remaining": 43,
"duration": 60,
"simulated_time": 54.891343113611583,
"time_step": 5.4891343113611581,
"time_step_sediment": 70.517234241515013,
"iterations_planned": 10,
"iterations_completed": 10,
"stopped_early": false,
"mean_velocity": 0.1821780891624834,
"velocity_max": 0.21544346900318839,
"sigma_max": 0.052657639041437901,
"mean_mannings_n": 1,
"mean_source_rate": 0.56025284041612944,
"threads": 1,
"transport_capacity": "tc",
"tlimit_erosion_deposition": null,
"outputs": [
{
"simulated_time": 54.891343113611583,
"timestamp": "1 minutes",
"walkers_remaining": 43,
"sediment_concentration": null,
"sediment_flux": "flux",
"erosion_deposition": "erdep",
"walkers": null
}
]
}
```

## REFERENCES

[Mitasova, H., Thaxton, C., Hofierka, J., McLaughlin, R., Moore, A.,
Expand Down
171 changes: 171 additions & 0 deletions raster/r.sim/r.sim.sediment/tests/r_sim_sediment_summary_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# SPDX-License-Identifier: GPL-2.0-or-later

"""Tests of the r.sim.sediment run summary (-p flag, format option)"""
Comment thread
petrasovaa marked this conversation as resolved.
Resolved

import re

import pytest

# rows_raster from the shared fixture is a plane rising 1 m per row, 5 rows
# by 6 columns at 1 m resolution. See the r.sim.water tests for why the flow
# is slowed down with Manning's n and a small diffusion coefficient.
ELEVATION = "rows_raster"

SEDIMENT_KEYS = {
"walkers_requested",
"walkers_generated",
"walkers_remaining",
"duration",
"simulated_time",
"time_step",
"time_step_sediment",
"iterations_planned",
"iterations_completed",
"stopped_early",
"mean_velocity",
"velocity_max",
"sigma_max",
"mean_mannings_n",
"mean_source_rate",
"threads",
"transport_capacity",
"tlimit_erosion_deposition",
"outputs",
}
OUTPUT_KEYS = {
"simulated_time",
"timestamp",
"walkers_remaining",
"sediment_concentration",
"sediment_flux",
"erosion_deposition",
"walkers",
}
MAP_KEYS = OUTPUT_KEYS - {"simulated_time", "timestamp", "walkers_remaining"}


def timestamp_for(simulated_time):
"""Timestamp text the tool writes for a simulated time in seconds"""
return f"{int(simulated_time / 60 + 0.5)} minutes"


def history_value(history, key):
"""Value of a key=value pair in the history text as float"""
return float(re.search(rf"{key}=([-+.\deE]+)", history).group(1))


@pytest.fixture
def sediment_inputs(session_tools):
"""Constant water depth and soil parameter maps for a run on rows_raster"""
session_tools.r_mapcalc(expression="water_depth = 0.1")
session_tools.r_mapcalc(expression="detachment = 0.001")
session_tools.r_mapcalc(expression="transport = 0.001")
session_tools.r_mapcalc(expression="shear_stress = 0.01")
return {
"elevation": ELEVATION,
"water_depth": "water_depth",
"detachment_coeff": "detachment",
"transport_coeff": "transport",
"shear_stress": "shear_stress",
"man_value": 1,
"diffusion_coeff": 0.05,
"random_seed": 1,
}


def test_json_summary(session_tools, sediment_inputs):
"""JSON summary has the sediment keys and names every written map"""
summary = session_tools.r_sim_sediment(
**sediment_inputs,
transport_capacity="tc",
tlimit_erosion_deposition="et",
sediment_concentration="conc",
sediment_flux="flux",
erosion_deposition="erdep",
nwalkers=100,
duration=1,
flags="p",
format="json",
).json
assert set(summary) == SEDIMENT_KEYS
assert summary["walkers_requested"] == 100
assert summary["walkers_generated"] > 0
assert summary["duration"] == 60
assert summary["time_step"] > 0
assert summary["iterations_planned"] > 1
assert summary["iterations_completed"] <= summary["iterations_planned"]
assert summary["time_step_sediment"] > 0
assert summary["velocity_max"] >= summary["mean_velocity"] > 0
assert summary["sigma_max"] > 0
assert summary["mean_mannings_n"] == pytest.approx(1)
assert summary["mean_source_rate"] > 0
assert summary["threads"] == 1
assert summary["transport_capacity"] == "tc"
assert summary["tlimit_erosion_deposition"] == "et"

assert len(summary["outputs"]) == 1
output = summary["outputs"][0]
assert set(output) == OUTPUT_KEYS
assert output["sediment_concentration"] == "conc"
assert output["sediment_flux"] == "flux"
assert output["erosion_deposition"] == "erdep"
assert output["walkers"] is None
assert output["simulated_time"] == summary["simulated_time"]
assert output["timestamp"] == timestamp_for(output["simulated_time"])
for name in ("tc", "et", "conc", "flux", "erdep"):
assert session_tools.r_info(map=name, format="json")["rows"] == 5


def test_json_summary_optional_outputs(session_tools, sediment_inputs):
"""Maps which were not requested are reported as null"""
summary = session_tools.r_sim_sediment(
**sediment_inputs, sediment_flux="flux", duration=1, flags="p", format="json"
).json
assert summary["transport_capacity"] is None
assert summary["tlimit_erosion_deposition"] is None
output = summary["outputs"][0]
assert output["sediment_flux"] == "flux"
for key in MAP_KEYS - {"sediment_flux"}:
assert output[key] is None


def test_plain_summary(session_tools, sediment_inputs):
"""Plain summary prints one key per line"""
lines = session_tools.r_sim_sediment(
**sediment_inputs, sediment_flux="flux", nwalkers=100, duration=1, flags="p"
).text.splitlines()
assert "walkers_requested: 100" in lines
assert any(line.startswith("time_step_sediment: ") for line in lines)
assert " sediment_flux: flux" in lines


def test_no_output_without_print_flag(session_tools, sediment_inputs):
"""Standard output stays empty unless -p is given, whatever the format"""
for kwargs in ({}, {"format": "json"}):
result = session_tools.r_sim_sediment(
**sediment_inputs,
sediment_flux="flux",
duration=1,
overwrite=True,
**kwargs,
)
# Tools returns None when the tool produced no standard output.
assert result is None


def test_history(session_tools, sediment_inputs):
"""Sediment flux history carries the summary values under the same keys"""
summary = session_tools.r_sim_sediment(
**sediment_inputs,
sediment_flux="flux",
nwalkers=100,
duration=1,
flags="p",
format="json",
).json
history = session_tools.r_info(map="flux", flags="h").text
for key in ("walkers_requested", "walkers_generated"):
assert history_value(history, key) == summary[key]
for key in ("simulated_time", "time_step", "mean_velocity", "mean_mannings_n"):
assert history_value(history, key) == pytest.approx(summary[key], abs=1e-6)
assert "mean_infiltration=" not in history
Loading
Loading