layers/craters/generation/population.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/layers/craters/generation/population.h" | ||
| 2 | |||
| 3 | #include "terraingen/layers/craters/generation/spacing.h" | ||
| 4 | #include "terraingen/random/philox.h" | ||
| 5 | #include "terraingen/random/samplers.h" | ||
| 6 | #include "terraingen/util/checks.h" | ||
| 7 | #include "terraingen/util/log.h" | ||
| 8 | |||
| 9 | #include <algorithm> | ||
| 10 | #include <cmath> | ||
| 11 | #include <memory> | ||
| 12 | #include <stdexcept> | ||
| 13 | #include <string> | ||
| 14 | #include <utility> | ||
| 15 | |||
| 16 | namespace terraingen { | ||
| 17 | |||
| 18 | namespace { | ||
| 19 | |||
| 20 | constexpr std::uint32_t kCraterStream = 0x43524154u; | ||
| 21 | constexpr std::uint32_t kCraterAgeStream = 0x43524147u; | ||
| 22 | constexpr std::uint32_t kCraterIdStream = 0x43524944u; | ||
| 23 | constexpr std::uint32_t kCraterKeepStream = 0x43524B50u; | ||
| 24 | |||
| 25 | 3092554 | std::uint64_t CraterId(std::uint32_t seed, const CraterBand& band, const CellIndex& cell, | |
| 26 | std::uint32_t index) { | ||
| 27 | const U32x4 block = | ||
| 28 | 3092554 | Philox4x32({static_cast<std::uint32_t>(cell.i), static_cast<std::uint32_t>(cell.j), | |
| 29 | 3092554 | static_cast<std::uint32_t>(band.exponent), index}, | |
| 30 | {seed, kCraterIdStream}); | ||
| 31 | 3092554 | return (static_cast<std::uint64_t>(block.x) << 32) | block.y; | |
| 32 | } | ||
| 33 | |||
| 34 | 364680 | void CheckOptions(const CraterGenerationOptions& options) { | |
| 35 | 364680 | ValidateCraterSpacing(options.spacing); | |
| 36 | 364679 | RequirePositive(options.dMin, "dMin"); | |
| 37 | 364677 | if (!(options.dMax > options.dMin)) { | |
| 38 | 1 | throw std::invalid_argument("dMax must exceed dMin"); | |
| 39 | } | ||
| 40 | 364676 | RequireNonNegative(options.marginRadii, "marginRadii"); | |
| 41 | 364675 | RequirePositive(options.cellSizeFactor, "cellSizeFactor"); | |
| 42 | 364674 | if (!(options.maxExpectedCraters >= 0.0)) { | |
| 43 | 1 | throw std::invalid_argument("maxExpectedCraters must not be negative"); | |
| 44 | } | ||
| 45 | 364673 | RequireNonNegative(options.minResolvedDiameter, "minResolvedDiameter"); | |
| 46 | 364672 | if (!(options.maxBandDiameter > 0.0)) { | |
| 47 | 1 | throw std::invalid_argument("maxBandDiameter must be positive"); | |
| 48 | } | ||
| 49 | 364671 | } | |
| 50 | |||
| 51 | /// One band of a generation: the cells drawn and the craters they are expected to hold. | ||
| 52 | struct BandPlan { | ||
| 53 | CraterBand band; | ||
| 54 | std::vector<CellIndex> cells; | ||
| 55 | double expected = 0.0; | ||
| 56 | }; | ||
| 57 | |||
| 58 | /// What a generation keeps and drops, for its log line. | ||
| 59 | struct GenerationCounts { | ||
| 60 | std::size_t bands = 0; | ||
| 61 | double expected = 0.0; | ||
| 62 | std::size_t removed = 0; ///< By spacing. | ||
| 63 | std::size_t capped = 0; ///< Over `maxCraters`. | ||
| 64 | }; | ||
| 65 | |||
| 66 | /// One call of `GenerateCraters`, in steps. | ||
| 67 | class Generation { | ||
| 68 | public: | ||
| 69 | 364671 | Generation(const SurfaceCells& cells, const Bounds2d& region, | |
| 70 | const SizeFrequencyDistribution& density, const CraterAgeModel& ages, | ||
| 71 | std::uint32_t seed, const CraterGenerationOptions& options) | ||
| 72 | 364671 | : cells_(cells), region_(region), density_(density), ages_(ages), seed_(seed), | |
| 73 | 364671 | options_(options), surface_(options.surface) { | |
| 74 | 364671 | if (density.Kind() != SfdKind::Density) { | |
| 75 | 1 | throw std::invalid_argument("Craters are generated from a Density distribution"); | |
| 76 | } | ||
| 77 | 364670 | dMax_ = ResolveDMax(options.dMax, density); | |
| 78 | 364670 | if (std::isinf(dMax_)) { | |
| 79 | 1 | throw std::invalid_argument("An unbounded density needs a finite dMax"); | |
| 80 | } | ||
| 81 | 364669 | if (!(dMax_ > options.dMin)) { | |
| 82 | 1 | throw std::invalid_argument("dMax must exceed dMin"); | |
| 83 | } | ||
| 84 | 364668 | } | |
| 85 | |||
| 86 | 364668 | CraterList Run(GenerationCounts& counts) const { | |
| 87 | 364668 | const std::vector<BandPlan> plan = PlanBands(); | |
| 88 | 364666 | counts.bands = plan.size(); | |
| 89 | 1507635 | for (const BandPlan& entry : plan) { | |
| 90 | 1142969 | counts.expected += entry.expected; | |
| 91 | } | ||
| 92 | 364666 | if (counts.expected > options_.maxExpectedCraters) { | |
| 93 | 2 | throw std::invalid_argument("Expected crater count " + std::to_string(counts.expected) + | |
| 94 | 3 | " exceeds maxExpectedCraters"); | |
| 95 | } | ||
| 96 | 364665 | CraterList craters = DrawPlannedCells(plan, counts.expected); | |
| 97 | 364665 | const std::size_t generated = craters.Size(); | |
| 98 | 364668 | AppendManualCraters(options_.manual, craters); | |
| 99 | 364662 | if (options_.spacing.Enabled()) { | |
| 100 | 37 | craters = RemoveSpacingConflicts(craters, counts.removed); | |
| 101 | 364625 | } else if (options_.manual.Size() > 0) { | |
| 102 | 2950 | craters = CropToRegion(craters, generated); | |
| 103 | } | ||
| 104 | 729324 | return KeepLargest(craters, counts.capped); | |
| 105 | 364669 | } | |
| 106 | |||
| 107 | 364662 | void Log(std::size_t kept, const GenerationCounts& counts, const Stopwatch& stopwatch) const { | |
| 108 | 364662 | const bool spaced = options_.spacing.Enabled(); | |
| 109 | 364662 | const std::size_t manual = options_.manual.Size(); | |
| 110 | 364662 | LogDebug(options_.logName, "Generated ", kept, " ", options_.logName, " (", options_.dMin, | |
| 111 | 364662 | "-", dMax_, " m, ", counts.bands, " bands, ", | |
| 112 | 364662 | static_cast<long long>(std::llround(counts.expected)), " expected", | |
| 113 | 1458574 | spaced ? ", " + std::to_string(counts.removed) + " removed by spacing" : "", | |
| 114 | 1452718 | manual > 0 ? ", " + std::to_string(manual) + " manual supplied" : "", | |
| 115 | 364662 | counts.capped > 0 | |
| 116 | 1458636 | ? ", " + std::to_string(counts.capped) + " over maxCraters dropped" | |
| 117 | : "", | ||
| 118 | 729324 | ") in ", FormatDuration(stopwatch.Milliseconds())); | |
| 119 | 364662 | } | |
| 120 | |||
| 121 | private: | ||
| 122 | /// How far beyond the region a crater `diameter` across is drawn: its margin, and with spacing | ||
| 123 | /// as far again as the craters it can conflict with reach. | ||
| 124 | 4075650 | double CandidateMargin(double diameter) const { | |
| 125 | 4075650 | const CraterSpacing& spacing = options_.spacing; | |
| 126 | 4075650 | if (!spacing.Enabled()) { | |
| 127 | 4070811 | return 0.5 * options_.marginRadii * diameter; | |
| 128 | } | ||
| 129 | 4839 | const double neighbour = std::fmin(spacing.diameterRatio * diameter, dMax_); | |
| 130 | 4839 | return SpacingReach(diameter, dMax_, spacing) + 0.5 * options_.marginRadii * neighbour; | |
| 131 | } | ||
| 132 | |||
| 133 | /// Whether `crater` is centred in the region or within its margin of it. | ||
| 134 | 679625 | bool InRegion(const Crater& crater, double margin) const { | |
| 135 | 679625 | return surface_.Contains(surface_.Expand(region_, margin), {crater.x, crater.y}); | |
| 136 | } | ||
| 137 | |||
| 138 | 364668 | std::vector<BandPlan> PlanBands() const { | |
| 139 | 364668 | const CraterSpacing& spacing = options_.spacing; | |
| 140 | const double skipBelow = | ||
| 141 | 364668 | options_.minResolvedDiameter / (spacing.Enabled() ? spacing.diameterRatio : 1.0); | |
| 142 | 364668 | std::vector<BandPlan> plan; | |
| 143 | 1555331 | for (const CraterBand& band : CraterBands(options_.dMin, dMax_, options_.cellSizeFactor)) { | |
| 144 | 2381124 | const bool tooLarge = !spacing.Enabled() && options_.maxCraters == 0 && | |
| 145 | 1190459 | band.low > options_.maxBandDiameter; | |
| 146 | 1190665 | if (band.high <= skipBelow || tooLarge) { | |
| 147 | 47690 | continue; | |
| 148 | } | ||
| 149 | 1142975 | const double margin = CandidateMargin(band.high); | |
| 150 | 1142975 | BandPlan entry{band, | |
| 151 | 1142975 | cells_.CellsCovering(surface_.Expand(region_, margin), band.cellSize)}; | |
| 152 | 1142974 | const double countPerArea = density_.ExpectedCount(1.0, band.low, band.high); | |
| 153 | 4223353 | for (const CellIndex& cell : entry.cells) { | |
| 154 | 3080380 | entry.expected += countPerArea * cells_.CellArea(cell, band.cellSize); | |
| 155 | } | ||
| 156 | 1142973 | LogDebug(options_.logName, "Band [", band.low, ", ", band.high, | |
| 157 | 1142973 | ") m: ", entry.cells.size(), " cells of ", band.cellSize, " m, ", | |
| 158 | 1142973 | entry.expected, " ", options_.logName, " expected"); | |
| 159 | 1142973 | plan.push_back(std::move(entry)); | |
| 160 | 1507642 | } | |
| 161 | 364666 | return plan; | |
| 162 | 2 | } | |
| 163 | |||
| 164 | 364665 | CraterList DrawPlannedCells(const std::vector<BandPlan>& plan, double expected) const { | |
| 165 | 364665 | CraterList craters; | |
| 166 | 364665 | craters.Reserve(static_cast<std::size_t>(expected)); | |
| 167 | 1507623 | for (const BandPlan& entry : plan) { | |
| 168 | 1142958 | const BandRegion region = BandRegionOf(entry.band); | |
| 169 | 4009951 | for (const CellIndex& cell : entry.cells) { | |
| 170 | 2866993 | const std::shared_ptr<const CraterList> drawn = DrawCell(entry.band, cell); | |
| 171 | 114991639 | for (std::size_t n = 0; n < drawn->Size(); ++n) { | |
| 172 | 112124646 | const Crater crater = drawn->At(n); | |
| 173 | 112124646 | if (InBandRegion(region, crater) && Kept(crater)) { | |
| 174 | 8897595 | craters.Append(crater); | |
| 175 | } | ||
| 176 | } | ||
| 177 | 2866993 | } | |
| 178 | } | ||
| 179 | 364665 | return craters; | |
| 180 | } | ||
| 181 | |||
| 182 | /// The region expanded by the candidate margins of a band's smallest and largest craters, | ||
| 183 | /// which bound every margin in between since `Expand` and `CandidateMargin` only grow. | ||
| 184 | struct BandRegion { | ||
| 185 | CraterBand band; | ||
| 186 | Bounds2d inner; | ||
| 187 | Bounds2d outer; | ||
| 188 | }; | ||
| 189 | |||
| 190 | 1142958 | BandRegion BandRegionOf(const CraterBand& band) const { | |
| 191 | 1142958 | return {band, surface_.Expand(region_, CandidateMargin(band.low)), | |
| 192 | 2285916 | surface_.Expand(region_, CandidateMargin(band.high))}; | |
| 193 | } | ||
| 194 | |||
| 195 | /// `InRegion` with the crater's candidate margin, expanding the region only for craters of the | ||
| 196 | /// band between its inner and outer regions. | ||
| 197 | 112124646 | bool InBandRegion(const BandRegion& region, const Crater& crater) const { | |
| 198 | 112124646 | if (!(crater.diameter >= region.band.low && crater.diameter <= region.band.high)) { | |
| 199 | 40 | return InRegion(crater, CandidateMargin(crater.diameter)); | |
| 200 | } | ||
| 201 | 112124606 | const Vec2d centre{crater.x, crater.y}; | |
| 202 | 112124606 | if (!surface_.Contains(region.outer, centre)) { | |
| 203 | 102751342 | return false; | |
| 204 | } | ||
| 205 | 10019983 | return surface_.Contains(region.inner, centre) || | |
| 206 | 10019983 | InRegion(crater, CandidateMargin(crater.diameter)); | |
| 207 | } | ||
| 208 | |||
| 209 | /// Whether a generated crater stays: below the catalog's completeness limit at its centre, and | ||
| 210 | /// within the keep share there by a uniform keyed to its ID. | ||
| 211 | 8932640 | bool Kept(const Crater& crater) const { | |
| 212 | 8969671 | if (options_.completeness && | |
| 213 | 8969671 | crater.diameter >= options_.completeness->ValueAt(crater.x, crater.y)) { | |
| 214 | 1000 | return false; | |
| 215 | } | ||
| 216 | 8931640 | if (!options_.keepShare) { | |
| 217 | 8384135 | return true; | |
| 218 | } | ||
| 219 | 547505 | const IdWords id = SplitId(crater.id); | |
| 220 | 547505 | RandomStream random(seed_, kCraterKeepStream, id.low, id.high); | |
| 221 | 547505 | return random.NextDouble() < options_.keepShare(crater); | |
| 222 | } | ||
| 223 | |||
| 224 | 2866993 | std::shared_ptr<const CraterList> DrawCell(const CraterBand& band, | |
| 225 | const CellIndex& cell) const { | ||
| 226 | 2866993 | if (options_.cellCraters) { | |
| 227 | 2764900 | return options_.cellCraters(band, cell); | |
| 228 | } | ||
| 229 | return std::make_shared<const CraterList>( | ||
| 230 | 102093 | GenerateCellCraters(cells_, band, cell, density_, ages_, seed_)); | |
| 231 | } | ||
| 232 | |||
| 233 | /// The craters within their margin of the region, the first `generated` of which already are. | ||
| 234 | 2950 | CraterList CropToRegion(const CraterList& craters, std::size_t generated) const { | |
| 235 | 2950 | return FilterCraters(craters, [&](std::size_t n, const Crater& crater) { | |
| 236 | 7283297 | return n < generated || InRegion(crater, 0.5 * options_.marginRadii * crater.diameter); | |
| 237 | 5900 | }); | |
| 238 | } | ||
| 239 | |||
| 240 | /// The craters within their margin of the region that spacing keeps, counting those removed. | ||
| 241 | 37 | CraterList RemoveSpacingConflicts(const CraterList& craters, std::size_t& removed) const { | |
| 242 | 37 | const std::vector<bool> conflicts = SpacingConflicts(craters, options_.spacing, surface_); | |
| 243 | ✗ | return FilterCraters(craters, [&](std::size_t n, const Crater& crater) { | |
| 244 | 29901 | if (!InRegion(crater, 0.5 * options_.marginRadii * crater.diameter)) { | |
| 245 | 4764 | return false; | |
| 246 | } | ||
| 247 | 25137 | if (conflicts[n]) { | |
| 248 | 8848 | ++removed; | |
| 249 | 8848 | return false; | |
| 250 | } | ||
| 251 | 16289 | return true; | |
| 252 | 74 | }); | |
| 253 | 37 | } | |
| 254 | |||
| 255 | /// The manual craters and the `maxCraters` largest generated ones (ties to the smaller ID), | ||
| 256 | /// counting the generated craters dropped. | ||
| 257 | 364662 | CraterList KeepLargest(const CraterList& craters, std::size_t& capped) const { | |
| 258 | 364662 | const std::size_t limit = options_.maxCraters; | |
| 259 | 364662 | std::vector<std::size_t> generated; | |
| 260 | 9249538 | for (std::size_t n = 0; n < craters.Size(); ++n) { | |
| 261 | 8884876 | if (craters.Manual()[n] == 0) { | |
| 262 | 8883962 | generated.push_back(n); | |
| 263 | } | ||
| 264 | } | ||
| 265 | 364662 | if (limit == 0 || generated.size() <= limit) { | |
| 266 | 364656 | return craters; | |
| 267 | } | ||
| 268 | 6 | const auto& diameters = craters.Diameters(); | |
| 269 | 6 | const auto& ids = craters.Ids(); | |
| 270 | 6 | std::nth_element(generated.begin(), generated.begin() + static_cast<std::ptrdiff_t>(limit), | |
| 271 | 32818 | generated.end(), [&](std::size_t a, std::size_t b) { | |
| 272 | 32818 | if (diameters[a] != diameters[b]) { | |
| 273 | 32818 | return diameters[a] > diameters[b]; | |
| 274 | } | ||
| 275 | ✗ | return ids[a] < ids[b]; | |
| 276 | }); | ||
| 277 | 6 | std::vector<bool> keep(craters.Size(), false); | |
| 278 | 666 | for (std::size_t k = 0; k < limit; ++k) { | |
| 279 | 660 | keep[generated[k]] = true; | |
| 280 | } | ||
| 281 | 6 | capped = generated.size() - limit; | |
| 282 | return FilterCraters( | ||
| 283 | 14552 | craters, [&](std::size_t n, const Crater& crater) { return keep[n] || crater.manual; }); | |
| 284 | 364662 | } | |
| 285 | |||
| 286 | const SurfaceCells& cells_; | ||
| 287 | const Bounds2d& region_; | ||
| 288 | const SizeFrequencyDistribution& density_; | ||
| 289 | const CraterAgeModel& ages_; | ||
| 290 | std::uint32_t seed_; | ||
| 291 | const CraterGenerationOptions& options_; | ||
| 292 | const Surface& surface_; | ||
| 293 | double dMax_ = 0.0; | ||
| 294 | }; | ||
| 295 | |||
| 296 | } // namespace | ||
| 297 | |||
| 298 | 364670 | std::vector<CraterBand> CraterBands(double dMin, double dMax, double cellSizeFactor) { | |
| 299 | 364670 | std::vector<CraterBand> bands; | |
| 300 | 1555344 | for (int k = std::ilogb(dMin); std::ldexp(1.0, k) < dMax; ++k) { | |
| 301 | 1190674 | const double low = std::fmax(dMin, std::ldexp(1.0, k)); | |
| 302 | 1190674 | const double high = std::fmin(dMax, std::ldexp(1.0, k + 1)); | |
| 303 | 1190674 | bands.push_back({k, low, high, cellSizeFactor * std::ldexp(1.0, k + 1)}); | |
| 304 | } | ||
| 305 | 364670 | return bands; | |
| 306 | } | ||
| 307 | |||
| 308 | 208356 | CraterList GenerateCellCraters(const SurfaceCells& cells, const CraterBand& band, | |
| 309 | const CellIndex& cell, const SizeFrequencyDistribution& density, | ||
| 310 | const CraterAgeModel& ages, std::uint32_t seed) { | ||
| 311 | 208356 | const auto k = static_cast<std::uint32_t>(band.exponent); | |
| 312 | 208356 | const auto i = static_cast<std::uint32_t>(cell.i); | |
| 313 | 208356 | const auto j = static_cast<std::uint32_t>(cell.j); | |
| 314 | 208356 | RandomStream geometry(seed, kCraterStream, i, j, k); | |
| 315 | 208356 | RandomStream ageStream(seed, kCraterAgeStream, i, j, k); | |
| 316 | 208356 | const double countPerArea = density.ExpectedCount(1.0, band.low, band.high); | |
| 317 | 208356 | const SurfaceCells::Sampling sampling = cells.CellSampling(cell, band.cellSize); | |
| 318 | 208356 | const std::uint64_t count = SamplePoisson(geometry, countPerArea * sampling.area); | |
| 319 | 208356 | CraterList craters; | |
| 320 | 208356 | craters.Reserve(static_cast<std::size_t>(count)); | |
| 321 | 3388294 | for (std::uint64_t index = 0; index < count; ++index) { | |
| 322 | 3179938 | const double diameter = density.SampleDiameter(geometry, band.low, band.high); | |
| 323 | 3179938 | const double u = geometry.NextDouble(); | |
| 324 | 3179938 | const double v = geometry.NextDouble(); | |
| 325 | 3179938 | const Vec2d centre = cells.SamplePoint(u, v, cell, band.cellSize); | |
| 326 | 3340140 | if (sampling.bound > 0.0 && | |
| 327 | 160202 | geometry.NextDouble() * sampling.bound >= cells.Weight(centre)) { | |
| 328 | 87384 | continue; | |
| 329 | } | ||
| 330 | 3092554 | const double ageGyr = ages.SampleAgeGyr(ageStream, diameter); | |
| 331 | 3092554 | craters.Append({centre.x, centre.y, diameter, ageGyr, | |
| 332 | 3092554 | CraterId(seed, band, cell, static_cast<std::uint32_t>(index))}); | |
| 333 | } | ||
| 334 | 416712 | return craters; | |
| 335 | } | ||
| 336 | |||
| 337 | 583992 | double ResolveDMax(double dMax, const SizeFrequencyDistribution& density) { | |
| 338 | 583992 | return std::isinf(dMax) ? density.ValidRange().max : dMax; | |
| 339 | } | ||
| 340 | |||
| 341 | 364680 | CraterList GenerateCraters(const SurfaceCells& cells, const Bounds2d& region, | |
| 342 | const SizeFrequencyDistribution& density, const CraterAgeModel& ages, | ||
| 343 | std::uint32_t seed, const CraterGenerationOptions& options) { | ||
| 344 | 364680 | CheckOptions(options); | |
| 345 | 364671 | const Stopwatch stopwatch; | |
| 346 | 364671 | const Generation generation(cells, region, density, ages, seed, options); | |
| 347 | 364668 | GenerationCounts counts; | |
| 348 | 364668 | CraterList craters = generation.Run(counts); | |
| 349 | 364662 | generation.Log(craters.Size(), counts, stopwatch); | |
| 350 | 729324 | return craters; | |
| 351 | } | ||
| 352 | |||
| 353 | 6 | double MinDiameterForCount(const SizeFrequencyDistribution& density, double area, double count, | |
| 354 | double dMin, double dMax) { | ||
| 355 | 6 | if (!(IsPositive(count) && IsPositive(area))) { | |
| 356 | 2 | throw std::invalid_argument("Count and area must be positive and finite"); | |
| 357 | } | ||
| 358 | 4 | if (!(IsPositive(dMin) && dMax > dMin && std::isfinite(dMax))) { | |
| 359 | 1 | throw std::invalid_argument("Diameter interval must satisfy 0 < dMin < dMax"); | |
| 360 | } | ||
| 361 | 3 | if (density.ExpectedCount(area, dMin, dMax) <= count) { | |
| 362 | 1 | return dMin; | |
| 363 | } | ||
| 364 | 2 | double low = std::log(dMin); | |
| 365 | 2 | double high = std::log(dMax); | |
| 366 | 88 | for (int i = 0; i < 200 && high - low > 1e-12; ++i) { | |
| 367 | 86 | const double middle = 0.5 * (low + high); | |
| 368 | 86 | if (density.ExpectedCount(area, std::exp(middle), dMax) > count) { | |
| 369 | 40 | low = middle; | |
| 370 | } else { | ||
| 371 | 46 | high = middle; | |
| 372 | } | ||
| 373 | } | ||
| 374 | 2 | return std::exp(high); | |
| 375 | } | ||
| 376 | |||
| 377 | } // namespace terraingen | ||
| 378 |