TerrainGen coverage


terraingen/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
161 / 173
93.1%
Branches:
86 / 99
86.9%
Functions:
26 / 31
83.9%

python/src/crater_models.cpp
Line Branch Exec Source
1 #include "bindings.h"
2 #include "common.h"
3 #include "trampolines.h"
4
5 #include "terraingen/layers/craters/ages/age_model.h"
6 #include "terraingen/layers/craters/ages/steady_state.h"
7 #include "terraingen/layers/craters/crater_layer.h"
8 #include "terraingen/layers/craters/degradation/degradation_model.h"
9 #include "terraingen/layers/craters/degradation/diffusion.h"
10 #include "terraingen/layers/craters/degradation/diffusivities.h"
11 #include "terraingen/layers/craters/degradation/freshness.h"
12 #include "terraingen/layers/craters/profiles/complex.h"
13 #include "terraingen/layers/craters/profiles/fassett_thomson.h"
14 #include "terraingen/layers/craters/profiles/martin_parkes_dunstan.h"
15 #include "terraingen/layers/craters/shaping/morphometry.h"
16 #include "terraingen/random/stream.h"
17
18 #include <optional>
19 #include <utility>
20
21 namespace terraingen::python {
22
23 namespace {
24
25 /// @brief An age uniform over a crater's lifetime, as Python age models default to.
26 4521 double UniformAgeGyr(RandomStream& random, double lifetimeGyr) {
27 4521 return lifetimeGyr * random.NextDouble();
28 }
29
30 /// @brief A crater age model written in Python: `lifetime_at(diameter)` is required; without
31 /// `sample_age_at(random, diameter, lifetime_gyr)`, ages are uniform over the lifetime.
32 class PyCraterAgeModel : public CraterAgeModel {
33 public:
34 NB_TRAMPOLINE(CraterAgeModel);
35
36 protected:
37 4522 double LifetimeAt(double diameter) const override {
38
2/2
✓ Branch 2 taken 4522 times.
✓ Branch 5 taken 4522 times.
27132 NB_OVERRIDE_PURE_NAME("lifetime_at", LifetimeAt, diameter);
39 }
40
41 4521 double SampleAgeAt(RandomStream& random, double diameter, double lifetimeGyr) const override {
42 9042 return CallOverride<double>(nb_trampoline, "sample_age_at",
43
1/1
✓ Branch 1 taken 4521 times.
4521 ByReference<RandomStream>{random}, diameter, lifetimeGyr)
44
1/1
✓ Branch 1 taken 4521 times.
4521 .value_or(UniformAgeGyr(random, lifetimeGyr));
45 }
46 };
47
48 /// @brief A degradation model written in Python: `state(crater)` returns a `CraterState`.
49 class PyDegradationModel : public DegradationModel {
50 public:
51 NB_TRAMPOLINE(DegradationModel);
52
53 465 CraterState State(const Crater& crater) const override {
54
2/2
✓ Branch 2 taken 465 times.
✓ Branch 5 taken 465 times.
2790 NB_OVERRIDE_PURE_NAME("state", State, crater);
55 }
56 };
57
58 /// @brief A crater profile written in Python: `height(distance, diameter, state)` and
59 /// `reach_radii()` are required; `is_complex(diameter)` is False unless overridden.
60 class PyCraterProfile : public CraterProfile {
61 public:
62 NB_TRAMPOLINE(CraterProfile);
63
64 142282 double Height(double distance, double diameter, const CraterState& state) const override {
65
2/2
✓ Branch 2 taken 142282 times.
✓ Branch 5 taken 142282 times.
853692 NB_OVERRIDE_PURE_NAME("height", Height, distance, diameter, state);
66 }
67
68 233994 double ReachRadii() const override {
69
2/2
✓ Branch 2 taken 233994 times.
✓ Branch 5 taken 233994 times.
1403964 NB_OVERRIDE_PURE_NAME("reach_radii", ReachRadii);
70 }
71
72 264 bool IsComplex(double diameter) const override {
73
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 264 times.
✗ Branch 4 not taken.
✗ Branch 7 not taken.
528 NB_OVERRIDE_NAME("is_complex", IsComplex, diameter);
74 }
75 };
76
77 44 void BindAges(nb::module_& m) {
78 88 nb::class_<CraterAgeModel, PyCraterAgeModel>(
79 m, "CraterAgeModel",
80 "How old craters are: each has a lifetime by size and an age drawn within it. Subclass "
81 "it and implement `lifetime_at(diameter)`, and optionally `sample_age_at(random, "
82 "diameter, lifetime_gyr)` (uniform over the lifetime otherwise).")
83 44 .def(nb::init<>())
84
1/1
✓ Branch 1 taken 44 times.
44 .def("lifetime_at", &CraterAgeModel::LifetimeGyr, "diameter"_a,
85 "The lifetime (Gyr) of a crater `diameter` across; override it.")
86 44 .def(
87 "sample_age_at",
88 ✗ [](const CraterAgeModel& /*model*/, RandomStream& random, double /*diameter*/,
89 ✗ double lifetimeGyr) { return UniformAgeGyr(random, lifetimeGyr); },
90
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 "random"_a, "diameter"_a, "lifetime_gyr"_a,
91 "An age (Gyr) within the lifetime; uniform unless overridden.")
92
1/1
✓ Branch 1 taken 44 times.
44 .def("lifetime_gyr", &CraterAgeModel::LifetimeGyr, "diameter"_a)
93
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("sample_age_gyr", &CraterAgeModel::SampleAgeGyr, "random"_a, "diameter"_a)
94
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
88 .def("remaining_lifetime", &CraterAgeModel::RemainingLifetime, "diameter"_a, "age_gyr"_a,
95 "The share of a crater's lifetime left: 1 for a new crater, falling to 0 at the end "
96 "of its lifetime.");
97 88 nb::class_<FreshAges, CraterAgeModel>(m, "FreshAges", "Every crater is new (age 0).")
98 44 .def(nb::init<>());
99 44 nb::class_<UniformAges, CraterAgeModel>(
100 m, "UniformAges", "Ages uniform over a lifetime of `lifetime_gyr`, whatever the size.")
101
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<double>(), "lifetime_gyr"_a);
102 ✗ nb::class_<SteadyStateAges, CraterAgeModel>(
103 m, "SteadyStateAges",
104 "Ages of a surface `age_gyr` old in steady state: a crater lasts until the "
105 "`equilibrium` density is replaced by `production_rate`, or the surface's age, and ages "
106 "follow the formation rate of `chronology`.")
107 ✗ .def(nb::init<std::shared_ptr<SizeFrequencyDistribution>,
108 std::shared_ptr<SizeFrequencyDistribution>, double, Chronology>(),
109
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
88 "production_rate"_a, "equilibrium"_a, "age_gyr"_a, "chronology"_a)
110 44 .def_prop_ro("surface_age_gyr", &SteadyStateAges::SurfaceAgeGyr)
111
1/1
✓ Branch 1 taken 44 times.
88 .def("equilibrium_lifetime_gyr", &SteadyStateAges::EquilibriumLifetimeGyr, "diameter"_a);
112 44 }
113
114 44 void BindCraterState(nb::module_& m) {
115 44 nb::class_<CraterMorphometry>(m, "CraterMorphometry",
116 "A crater's depth and rim height, each over its diameter.")
117 44 .def(
118 "__init__",
119 511 [](CraterMorphometry* self, double depthRatio, double rimHeightRatio) {
120 467 new (self) CraterMorphometry{depthRatio, rimHeightRatio};
121 467 },
122
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 "depth_ratio"_a = 0.0, "rim_height_ratio"_a = 0.0)
123 44 .def_rw("depth_ratio", &CraterMorphometry::depthRatio)
124 .def_rw("rim_height_ratio", &CraterMorphometry::rimHeightRatio);
125 ✗ nb::class_<CraterState>(m, "CraterState",
126 "A crater's shape when fresh and now, and how far diffusion has "
127 "smoothed it.")
128 44 .def(
129 "__init__",
130 514 [](CraterState* self, CraterMorphometry fresh, CraterMorphometry current,
131 470 double diffusion) { new (self) CraterState{fresh, current, diffusion}; },
132
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
176 "fresh"_a = CraterMorphometry{}, "current"_a = CraterMorphometry{}, "diffusion"_a = 0.0)
133 44 .def_rw("fresh", &CraterState::fresh)
134 44 .def_rw("current", &CraterState::current)
135 44 .def_rw("diffusion", &CraterState::diffusion)
136 88 .def_prop_ro("freshness", &CraterState::Freshness,
137 "The depth now over the depth when fresh; 1 for a fresh crater.");
138 44 }
139
140 44 void BindDegradationModels(nb::module_& m) {
141 88 nb::class_<DegradationModel, PyDegradationModel>(
142 m, "DegradationModel",
143 "How a crater's shape changes with age. Subclass it and implement `state(crater)`, "
144 "returning a CraterState, for a model of your own.")
145 44 .def(nb::init<>())
146
1/1
✓ Branch 1 taken 44 times.
44 .def("state", &DegradationModel::State, "crater"_a)
147
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
88 .def("worn_state", &DegradationModel::WornState, "crater"_a, "rate"_a,
148 "The state had the crater worn `rate` times as fast as on level ground.");
149 88 nb::class_<NoDegradation, DegradationModel>(
150 m, "NoDegradation",
151 "Craters keep their fresh shape (`fresh`, a function of diameter giving a "
152 "CraterMorphometry, such as `lunar_fresh_crater_morphometry`), or a fixed `depth_ratio`.")
153
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<FreshMorphometry, std::optional<double>>(), "fresh"_a,
154
1/1
✓ Branch 1 taken 44 times.
88 "depth_ratio"_a = nb::none());
155 ✗ nb::class_<FreshnessDegradation, DegradationModel>(
156 m, "FreshnessDegradation",
157 "Craters shallow from their fresh shape with their freshness under `ages`, down to "
158 "`cutoff_depth_ratio`.")
159
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<std::shared_ptr<CraterAgeModel>, FreshMorphometry, double>(), "ages"_a,
160
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 "fresh"_a, "cutoff_depth_ratio"_a = 0.04);
161 44 }
162
163 44 void BindDiffusion(nb::module_& m) {
164 auto lawClass = nb::class_<PowerLawDiffusivity>(
165 m, "PowerLawDiffusivity",
166 "Topographic diffusivity coefficient * D^exponent (m^2 per Myr, D in km), no less than "
167 "at `min_diameter` metres; called with (diameter, age_gyr) it gives the diffusion "
168 44 "length squared, in m^2.");
169
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<PowerLawDiffusivity>(lawClass);
170 44 lawClass.def_rw("coefficient", &PowerLawDiffusivity::coefficient)
171 44 .def_rw("exponent", &PowerLawDiffusivity::exponent)
172 44 .def_rw("min_diameter", &PowerLawDiffusivity::minDiameter)
173
1/1
✓ Branch 1 taken 44 times.
44 .def("diffusivity", &PowerLawDiffusivity::Diffusivity, "diameter"_a)
174
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 .def("__call__", &PowerLawDiffusivity::operator(), "diameter"_a, "age_gyr"_a);
175
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_power_law_diffusivity", &ValidatePowerLawDiffusivity, "law"_a, kValidateDoc);
176 ✗ nb::class_<DiffusionDegradation, DegradationModel>(
177 m, "DiffusionDegradation",
178 "Craters smoothed by topographic diffusion over their age (Fassett and Thomson 2014): "
179 "`diffusivity` gives each crater's diffusion length squared (m^2) from its diameter and "
180 "age, as PowerLawDiffusivity does, and `profile` the diffused shape.")
181 44 .def(
182 "__init__",
183 ✗ [](DiffusionDegradation* self, FreshMorphometry fresh, DiffusionAgeModel diffusivity,
184 std::shared_ptr<DiffusedProfile> profile) {
185
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
12 new (self) DiffusionDegradation(std::move(fresh), std::move(diffusivity),
186
1/1
✓ Branch 6 taken 6 times.
18 std::move(profile));
187 6 },
188
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 "fresh"_a, "diffusivity"_a, "profile"_a)
189 .def(
190 "__init__",
191 44 [](DiffusionDegradation* self, FreshMorphometry fresh,
192 const PowerLawDiffusivity& diffusivity, std::shared_ptr<DiffusedProfile> profile) {
193
0/4
✗ Branch 4 not taken.
✗ Branch 9 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ new (self) DiffusionDegradation(std::move(fresh), diffusivity, std::move(profile));
194 ✗ },
195
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
88 "fresh"_a, "diffusivity"_a, "profile"_a);
196 44 }
197
198 44 void BindSimpleProfiles(nb::module_& m) {
199 88 nb::class_<CraterProfile, PyCraterProfile>(
200 m, "CraterProfile",
201 "A crater's height relative to the surrounding surface at `distance` metres from its "
202 "centre. Subclass it and implement `height(distance, diameter, state)` and "
203 "`reach_radii()` (how far, in radii, it changes the terrain), and optionally "
204 "`is_complex(diameter)`. A Python profile is called for every sample near every crater, "
205 "from every evaluation thread, and Python calls take turns on the GIL, so evaluate "
206 "terrains with one on one thread (`threads=1`).")
207 44 .def(nb::init<>())
208
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
44 .def("height", &CraterProfile::Height, "distance"_a, "diameter"_a, "state"_a)
209 44 .def("reach_radii", &CraterProfile::ReachRadii)
210
1/1
✓ Branch 1 taken 44 times.
88 .def("is_complex", &CraterProfile::IsComplex, "diameter"_a);
211 88 nb::enum_<FassettThomsonRim>(m, "FassettThomsonRim", "How the Fassett and Thomson rim is fit.")
212 44 .value("ORIGINAL", FassettThomsonRim::Original)
213 44 .value("AT_ONE_RADIUS", FassettThomsonRim::AtOneRadius)
214 .value("PLATEAU", FassettThomsonRim::Plateau);
215 44 nb::class_<FassettThomsonProfile, CraterProfile>(
216 m, "FassettThomsonProfile",
217 "The fresh simple-crater profile of Fassett and Thomson (2014).")
218
1/1
✓ Branch 1 taken 44 times.
88 .def(nb::init<FassettThomsonRim>(), "rim"_a = FassettThomsonRim::Original);
219 44 nb::class_<MartinParkesDunstanProfile, CraterProfile>(
220 m, "MartinParkesDunstanProfile",
221 "Martin, Parkes and Dunstan's profile, whose rounded rim lowers as the crater degrades.")
222 44 .def(nb::init<>());
223 44 }
224
225 44 void BindComplexShape(nb::module_& m) {
226 44 nb::class_<KilometrePowerLaw>(m, "KilometrePowerLaw",
227 "coefficient * D^exponent with D in kilometres.")
228 44 .def(
229 "__init__",
230 45 [](KilometrePowerLaw* self, double coefficient, double exponent) {
231 1 new (self) KilometrePowerLaw{coefficient, exponent};
232 1 },
233
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
132 "coefficient"_a = 0.0, "exponent"_a = 0.0)
234 44 .def_rw("coefficient", &KilometrePowerLaw::coefficient)
235 .def_rw("exponent", &KilometrePowerLaw::exponent);
236 auto shape = nb::class_<ComplexCraterShape>(
237 m, "ComplexCraterShape",
238 44 "Where craters turn complex and how their floors and central peaks scale.");
239
1/1
✓ Branch 1 taken 44 times.
44 BindKwargsInit<ComplexCraterShape>(shape);
240 44 shape.def_rw("transition_diameter", &ComplexCraterShape::transitionDiameter)
241 44 .def_rw("floor_diameter", &ComplexCraterShape::floorDiameter)
242 44 .def_rw("peak_height", &ComplexCraterShape::peakHeight)
243 44 .def_rw("peak_volume", &ComplexCraterShape::peakVolume)
244 44 .def_rw("peak_diameter", &ComplexCraterShape::peakDiameter)
245 44 .def_rw("peak_fit_max_diameter", &ComplexCraterShape::peakFitMaxDiameter)
246 44 .def_rw("ejecta_exponent", &ComplexCraterShape::ejectaExponent)
247 .def_rw("reach_radii", &ComplexCraterShape::reachRadii);
248
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("validate_complex_crater_shape", &ValidateComplexCraterShape, "shape"_a, kValidateDoc);
249 44 }
250
251 44 void BindCompositeProfiles(nb::module_& m) {
252 44 nb::class_<ComplexCraterProfile, CraterProfile>(
253 m, "ComplexCraterProfile",
254 "`simple` below `shape.transition_diameter`; above it a flat floor and a central peak.")
255 44 .def(
256 "__init__",
257 45 [](ComplexCraterProfile* self, std::shared_ptr<CraterProfile> simple,
258 ComplexCraterShape shape) {
259
1/3
✓ Branch 5 taken 1 time.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
1 new (self) ComplexCraterProfile(std::move(simple), std::move(shape));
260 1 },
261
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 "simple"_a, "shape"_a)
262 88 .def_prop_ro("shape", &ComplexCraterProfile::Shape);
263 ✗ nb::class_<DiffusedProfile, CraterProfile>(
264 m, "DiffusedProfile",
265 "`fresh` smoothed by topographic diffusion, tabulated by the diffusion time over the "
266 "diameter squared; used with DiffusionDegradation.")
267 .def(
268 "__init__",
269 50 [](DiffusedProfile* self, std::shared_ptr<CraterProfile> fresh, double reachRadii,
270 std::size_t cells) {
271
1/3
✓ Branch 4 taken 6 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
6 new (self) DiffusedProfile(std::move(fresh), reachRadii, cells);
272 6 },
273
3/3
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
176 "fresh"_a, "reach_radii"_a = 3.0, "cells"_a = 600);
274 44 }
275
276 44 void BindCraterModelSet(nb::module_& m) {
277 ✗ nb::class_<CraterModels>(m, "CraterModels",
278 "The models a crater layer draws and shapes craters with.")
279 44 .def(
280 "__init__",
281 133 [](CraterModels* self, std::shared_ptr<SizeFrequencyDistribution> density,
282 std::shared_ptr<CraterAgeModel> ages, std::shared_ptr<DegradationModel> degradation,
283 std::shared_ptr<CraterProfile> profile) {
284 445 new (self) CraterModels{std::move(density), std::move(ages), std::move(degradation),
285 89 std::move(profile)};
286 89 },
287
4/4
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
✓ Branch 7 taken 44 times.
✓ Branch 10 taken 44 times.
44 "density"_a, "ages"_a, "degradation"_a, "profile"_a)
288 44 .def_prop_ro("density", [](const CraterModels& c) { return Mutable(c.density); })
289 44 .def_prop_ro("ages", [](const CraterModels& c) { return Mutable(c.ages); })
290 44 .def_prop_ro("degradation", [](const CraterModels& c) { return Mutable(c.degradation); })
291 45 .def_prop_ro("profile", [](const CraterModels& c) { return Mutable(c.profile); });
292 44 }
293
294 44 void BindFreshCraterFits(nb::module_& m) {
295
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("lunar_fresh_crater_morphometry", &LunarFreshCraterMorphometry, "diameter"_a,
296 "A fresh lunar crater's depth and rim height ratios.");
297
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("lunar_fresh_crater_depth_ratio", &LunarFreshCraterDepthRatio, "diameter"_a,
298 "A fresh lunar crater's depth over diameter.");
299
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("mars_fresh_crater_morphometry", &MarsFreshCraterMorphometry, "diameter"_a,
300 "A fresh Martian crater's depth and rim height ratios.");
301
1/1
✓ Branch 1 taken 44 times.
44 m.def("lunar_complex_crater_shape", &LunarComplexCraterShape,
302 "Lunar complex craters, from 15 km.");
303
1/1
✓ Branch 1 taken 44 times.
44 m.def("mars_complex_crater_shape", &MarsComplexCraterShape,
304 "Martian complex craters, from 7 km.");
305 44 }
306
307 44 void BindDiffusivities(nb::module_& m) {
308
2/2
✓ Branch 1 taken 44 times.
✓ Branch 4 taken 44 times.
44 m.def("fassett_thomson_2014_diffusion_age", &FassettThomson2014DiffusionAge, "age_gyr"_a,
309 "Fassett and Thomson's (2014) effective diffusion age for a crater `age_gyr` old.");
310
1/1
✓ Branch 1 taken 44 times.
44 m.def("fassett_thomson_2014_diffusivity", &FassettThomson2014Diffusivity,
311 "Fassett and Thomson's (2014) constant diffusivity, 5.5 m^2/Myr.");
312
1/1
✓ Branch 1 taken 44 times.
44 m.def("fassett_2022_hartmann_diffusivity", &Fassett2022HartmannDiffusivity,
313 "Fassett et al.'s (2022) diffusivity fit with Hartmann's equilibrium.");
314
1/1
✓ Branch 1 taken 44 times.
44 m.def("fassett_2022_trask_diffusivity", &Fassett2022TraskDiffusivity,
315 "Fassett et al.'s (2022) diffusivity fit with Trask's equilibrium.");
316 44 }
317
318 } // namespace
319
320 44 void BindCraterModels(nb::module_& m) {
321 44 BindAges(m);
322 44 BindCraterState(m);
323 44 BindDegradationModels(m);
324 44 BindSimpleProfiles(m);
325 44 BindComplexShape(m);
326 44 BindCompositeProfiles(m);
327 44 BindDiffusion(m);
328 44 BindFreshCraterFits(m);
329 44 BindDiffusivities(m);
330 44 BindCraterModelSet(m);
331 44 }
332
333 } // namespace terraingen::python
334