Biodata Generator 1.0.0
Procedural human physical characteristics generation for C++23
Loading...
Searching...
No Matches
biodatagen.hpp
Go to the documentation of this file.
1#ifndef DASMIG_BIODATAGEN_HPP
2#define DASMIG_BIODATAGEN_HPP
3
4#include "random.hpp"
5#include <algorithm>
6#include <array>
7#include <charconv>
8#include <cmath>
9#include <cstddef>
10#include <cstdint>
11#include <filesystem>
12#include <format>
13#include <fstream>
14#include <ostream>
15#include <random>
16#include <ranges>
17#include <stdexcept>
18#include <string>
19#include <string_view>
20#include <system_error>
21#include <unordered_map>
22#include <utility>
23#include <vector>
24
25/// @file biodatagen.hpp
26/// @brief Biodata generator library — procedural human physical
27/// characteristics generation for C++23.
28/// @author Diego Dasso Migotto (diegomigotto at hotmail dot com)
29/// @see See doc/usage.md for the narrative tutorial.
30
31namespace dasmig
32{
33
34/// @brief Dataset size tier for resource loading.
35#ifndef DASMIG_DATASET_DEFINED
36#define DASMIG_DATASET_DEFINED
37enum class dataset : std::uint8_t
38{
39 lite, ///< ~111 countries with best data coverage.
40 full ///< ~197 countries (gaps filled with regional defaults).
41};
42#endif
43
44/// @brief Biological sex for biodata generation.
45#ifndef DASMIG_SEX_DEFINED
46#define DASMIG_SEX_DEFINED
47enum class sex : std::uint8_t
48{
49 male,
50 female
51};
52#endif
53
54/// @brief Eye colour categories.
55enum class eye_color : std::uint8_t
56{
57 blue,
58 intermediate, ///< Green, hazel, amber.
59 brown
60};
61
62/// @brief Hair colour categories.
63enum class hair_color : std::uint8_t
64{
65 black,
66 brown,
67 blond,
68 red
69};
70
71/// @brief Fitzpatrick skin-type scale (I–VI).
72enum class skin_type : std::uint8_t
73{
74 I = 1,
75 II,
76 III,
77 IV,
78 V,
79 VI
80};
81
82/// @brief ABO/Rh blood-type groups.
83enum class blood_type : std::uint8_t
84{
85 O_pos,
86 A_pos,
87 B_pos,
88 AB_pos,
89 O_neg,
90 A_neg,
91 B_neg,
92 AB_neg
93};
94
95/// @brief Handedness.
96enum class handedness : std::uint8_t
97{
98 right,
99 left
100};
101
102/// @brief Return type for biodata generation, holding all physical
103/// characteristics.
104///
105/// Supports implicit conversion to std::string (summary line) and
106/// streaming via operator<<.
108{
109 public:
110 std::string country_code; ///< ISO 3166-1 alpha-2 code.
111 sex bio_sex{sex::male}; ///< Biological sex.
112 double height_cm{0.0}; ///< Height in centimetres.
113 double weight_kg{0.0}; ///< Weight in kilograms.
114 double bmi{0.0}; ///< Body mass index (kg/m²).
115 eye_color eyes{eye_color::brown}; ///< Eye colour.
116 hair_color hair{hair_color::black}; ///< Hair colour.
117 skin_type skin{skin_type::III}; ///< Fitzpatrick skin type.
118 blood_type blood{blood_type::O_pos}; ///< Blood type (ABO/Rh).
119 handedness hand{handedness::right}; ///< Handedness.
120
121 /// @brief Retrieve the random seed used to generate this biodata.
122 [[nodiscard]] std::uint64_t seed() const { return _seed; }
123
124 /// @brief Human-readable summary string.
125 [[nodiscard]] std::string to_string() const
126 {
127 return std::format("{} {} {:.1f}cm {:.1f}kg BMI={:.1f} "
128 "eyes={} hair={} skin={} blood={} {}",
136 }
137
138 /// @brief Implicit conversion to std::string.
139 /// @return Summary string.
140 operator std::string() const // NOLINT(hicpp-explicit-conversions)
141 {
142 return to_string();
143 }
144
145 /// @brief Stream the summary to an output stream.
146 friend std::ostream& operator<<(std::ostream& os, const biodata& b)
147 {
148 os << b.to_string();
149 return os;
150 }
151
152 // -- Enum to string helpers -------------------------------------------
153
154 /// @brief Eye colour label.
155 [[nodiscard]] static std::string_view eye_color_str(eye_color c)
156 {
157 switch (c)
158 {
159 case eye_color::blue: return "blue";
160 case eye_color::intermediate: return "intermediate";
161 case eye_color::brown: return "brown";
162 }
163 return "unknown";
164 }
165
166 /// @brief Hair colour label.
167 [[nodiscard]] static std::string_view hair_color_str(hair_color c)
168 {
169 switch (c)
170 {
171 case hair_color::black: return "black";
172 case hair_color::brown: return "brown";
173 case hair_color::blond: return "blond";
174 case hair_color::red: return "red";
175 }
176 return "unknown";
177 }
178
179 /// @brief Skin type label.
180 [[nodiscard]] static std::string_view skin_type_str(skin_type t)
181 {
182 switch (t)
183 {
184 case skin_type::I: return "I";
185 case skin_type::II: return "II";
186 case skin_type::III: return "III";
187 case skin_type::IV: return "IV";
188 case skin_type::V: return "V";
189 case skin_type::VI: return "VI";
190 }
191 return "unknown";
192 }
193
194 /// @brief Blood type label.
195 [[nodiscard]] static std::string_view blood_type_str(blood_type t)
196 {
197 switch (t)
198 {
199 case blood_type::O_pos: return "O+";
200 case blood_type::A_pos: return "A+";
201 case blood_type::B_pos: return "B+";
202 case blood_type::AB_pos: return "AB+";
203 case blood_type::O_neg: return "O-";
204 case blood_type::A_neg: return "A-";
205 case blood_type::B_neg: return "B-";
206 case blood_type::AB_neg: return "AB-";
207 }
208 return "unknown";
209 }
210
211 /// @brief Handedness label.
212 [[nodiscard]] static std::string_view handedness_str(handedness h)
213 {
214 switch (h)
215 {
216 case handedness::right: return "right";
217 case handedness::left: return "left";
218 }
219 return "unknown";
220 }
221
222 /// @brief Biological sex label.
223 [[nodiscard]] static std::string_view sex_str(sex s)
224 {
225 switch (s)
226 {
227 case sex::male: return "male";
228 case sex::female: return "female";
229 }
230 return "unknown";
231 }
232
233 private:
234 std::uint64_t _seed{0};
235 friend class bdg;
236};
237
238/// @brief Biodata generator that produces demographically plausible
239/// human physical characteristics using country-level
240/// population statistics.
241///
242/// The generation pipeline:
243/// 1. Select biological sex (50/50 unless forced).
244/// 2. Sample height from a Gaussian (country/sex mean and SD).
245/// 3. Sample BMI from a log-normal distribution.
246/// 4. Derive weight from height and BMI.
247/// 5. Sample eye colour, hair colour, skin type from categorical
248/// distributions.
249/// 6. Sample blood type from ABO/Rh distribution.
250/// 7. Sample handedness from country-level left-handedness rate.
251///
252/// Can be used as a singleton via instance() or constructed
253/// independently.
254///
255/// @par Thread safety
256/// Each instance is independent. Concurrent calls to get_biodata()
257/// on the **same** instance require external synchronisation.
258class bdg
259{
260 public:
261 /// @brief Default constructor — creates an empty generator with no
262 /// data.
263 bdg() = default;
264
265 bdg(const bdg&) = delete;
266 bdg& operator=(const bdg&) = delete;
267 bdg(bdg&&) noexcept = default;
268 bdg& operator=(bdg&&) noexcept = default;
269 ~bdg() = default;
270
271 /// @brief Access the global singleton instance.
272 ///
273 /// Auto-probes common resource paths on first access.
274 static bdg& instance()
275 {
276 static bdg inst{auto_probe_tag{}};
277 return inst;
278 }
279
280 // -- Generation -------------------------------------------------------
281
282 /// @brief Generate random biodata for a specific country.
283 /// @param cca2 ISO 3166-1 alpha-2 country code (e.g. "US", "BR").
284 /// @throws std::runtime_error If no data has been loaded.
285 /// @throws std::invalid_argument If the country code is unknown.
286 [[nodiscard]] biodata get_biodata(std::string_view cca2)
287 {
288 return generate_(lookup_entry_(cca2), draw_seed_(), {});
289 }
290
291 /// @brief Generate deterministic biodata for a specific country.
292 [[nodiscard]] biodata get_biodata(std::string_view cca2,
293 std::uint64_t call_seed) const
294 {
295 return generate_(lookup_entry_(cca2), call_seed, {});
296 }
297
298 /// @brief Generate random biodata from a random country.
299 /// @throws std::runtime_error If no data has been loaded.
300 [[nodiscard]] biodata get_biodata()
301 {
302 return get_biodata(draw_seed_());
303 }
304
305 /// @brief Generate deterministic biodata from a random country.
306 [[nodiscard]] biodata get_biodata(std::uint64_t call_seed) const
307 {
308 return get_biodata(pick_random_cca2_(call_seed), call_seed);
309 }
310
311 // -- Sex-specific generation ------------------------------------------
312
313 /// @brief Generate random biodata with a predetermined sex.
314 [[nodiscard]] biodata get_biodata(std::string_view cca2, sex bio_sex)
315 {
316 return generate_(lookup_entry_(cca2), draw_seed_(),
317 {.fix_sex = true, .forced_sex = bio_sex});
318 }
319
320 /// @brief Generate deterministic biodata with a predetermined sex.
321 [[nodiscard]] biodata get_biodata(std::string_view cca2, sex bio_sex,
322 std::uint64_t call_seed) const
323 {
324 return generate_(lookup_entry_(cca2), call_seed,
325 {.fix_sex = true, .forced_sex = bio_sex});
326 }
327
328 /// @brief Generate random biodata from a random country with a
329 /// predetermined sex.
330 [[nodiscard]] biodata get_biodata(sex bio_sex)
331 {
332 auto seed = draw_seed_();
333 return generate_(lookup_entry_(pick_random_cca2_(seed)), seed,
334 {.fix_sex = true, .forced_sex = bio_sex});
335 }
336
337 /// @brief Generate deterministic biodata from a random country with
338 /// a predetermined sex.
339 [[nodiscard]] biodata get_biodata(sex bio_sex,
340 std::uint64_t call_seed) const
341 {
342 return get_biodata(pick_random_cca2_(call_seed), bio_sex,
343 call_seed);
344 }
345
346 // -- Seeding ----------------------------------------------------------
347
348 /// @brief Seed the internal random engine for deterministic
349 /// sequences.
350 bdg& seed(std::uint64_t seed_value)
351 {
352 _engine.seed(seed_value);
353 return *this;
354 }
355
356 /// @brief Reseed the engine with a non-deterministic source.
358 {
359 _engine.seed(std::random_device{}());
360 return *this;
361 }
362
363 // -- Data management --------------------------------------------------
364
365 /// @brief Check whether any data has been loaded.
366 [[nodiscard]] bool has_data() const { return !_entries.empty(); }
367
368 /// @brief Return the number of loaded countries.
369 [[nodiscard]] std::size_t country_count() const
370 {
371 return _entries.size();
372 }
373
374 /// @brief Load biodata from a resource directory.
375 ///
376 /// Expects the directory to contain: biodata.tsv.
377 ///
378 /// Replaces any previously loaded data.
379 void load(const std::filesystem::path& dir)
380 {
381 if (!std::filesystem::is_directory(dir))
382 {
383 return;
384 }
385
386 _entries.clear();
387 load_biodata_(dir / "biodata.tsv");
388 rebuild_indices_();
389 }
390
391 /// @brief Load a specific dataset tier from auto-probed paths.
392 [[nodiscard]] bool load(dataset tier)
393 {
394 std::string_view sub = (tier == dataset::full) ? "full" : "lite";
395 auto found = std::ranges::find_if(
396 probe_bases_, [&](std::string_view base) {
397 auto d = std::filesystem::path{base} / sub;
398 return std::filesystem::is_regular_file(
399 d / "biodata.tsv");
400 });
401 if (found != probe_bases_.end())
402 {
403 load(std::filesystem::path{*found} / sub);
404 return true;
405 }
406 return false;
407 }
408
409 private:
410 // -- Internal data structures -----------------------------------------
411
412 struct entry
413 {
414 std::string cca2;
415 std::string name;
416
417 // Height Gaussian parameters.
418 double male_height_mean{170.0};
419 double male_height_sd{7.0};
420 double female_height_mean{157.0};
421 double female_height_sd{6.5};
422
423 // BMI parameters.
424 double male_bmi_mean{24.5};
425 double female_bmi_mean{25.0};
426 double bmi_sd{4.5};
427
428 // Eye colour distribution (blue, intermediate, brown).
429 std::array<double, 3> eye_weights{0.0, 5.0, 95.0};
430
431 // Hair colour distribution (black, brown, blond, red).
432 std::array<double, 4> hair_weights{90.0, 8.0, 1.0, 1.0};
433
434 // Skin type distribution (I..VI).
435 std::array<double, 6> skin_weights{2.0, 8.0, 18.0, 30.0,
436 28.0, 14.0};
437
438 // Blood type distribution (O+ A+ B+ AB+ O- A- B- AB-).
439 std::array<double, 8> blood_weights{37.0, 28.0, 20.0, 5.0,
440 4.0, 3.0, 2.0, 1.0};
441
442 // Handedness.
443 double left_handed_pct{10.6};
444 };
445
446 std::unordered_map<std::string, entry> _entries;
447
448 // Ordered cca2 list for random-country selection.
449 std::vector<std::string> _cca2_order;
450 mutable std::uniform_int_distribution<std::size_t> _country_uniform;
451
452 static constexpr unsigned seed_shift_{32U};
453
454 static constexpr std::array<std::string_view, 3> probe_bases_{
455 "resources", "../resources", "biodata-generator/resources"};
456
457 std::mt19937_64 _engine{std::random_device{}()};
458
459 struct auto_probe_tag {};
460
461 explicit bdg(auto_probe_tag /*tag*/)
462 {
463 auto found = std::ranges::find_if(
464 probe_bases_, [](std::string_view p) {
465 return std::filesystem::exists(p) &&
466 std::filesystem::is_directory(p);
467 });
468 if (found != probe_bases_.end())
469 {
470 const std::filesystem::path base{*found};
471 auto lite = base / "lite";
472 auto full = base / "full";
473 if (std::filesystem::is_regular_file(
474 lite / "biodata.tsv"))
475 {
476 load(lite);
477 }
478 else if (std::filesystem::is_regular_file(
479 full / "biodata.tsv"))
480 {
481 load(full);
482 }
483 }
484 }
485
486 // -- Helpers ----------------------------------------------------------
487
488 std::uint64_t draw_seed_()
489 {
490 return static_cast<std::uint64_t>(_engine());
491 }
492
493 [[nodiscard]] const entry& lookup_entry_(
494 std::string_view cca2) const
495 {
496 if (_entries.empty())
497 {
498 throw std::runtime_error(
499 "No biodata loaded. Call load() first.");
500 }
501 auto it = _entries.find(std::string{cca2});
502 if (it == _entries.end())
503 {
504 throw std::invalid_argument(
505 std::format("Unknown country code: {}", cca2));
506 }
507 return it->second;
508 }
509
510 /// Pick a random country code deterministically from the seed.
511 [[nodiscard]] const std::string& pick_random_cca2_(
512 std::uint64_t call_seed) const
513 {
514 if (_entries.empty())
515 {
516 throw std::runtime_error(
517 "No biodata loaded. Call load() first.");
518 }
519 effolkronium::random_local rng;
520 rng.seed(static_cast<std::mt19937::result_type>(
521 call_seed ^ (call_seed >> seed_shift_)));
522 return _cca2_order[_country_uniform(rng.engine())]; // NOLINT
523 }
524
525 // Optional constraints passed to generate_().
526 struct gen_opts_
527 {
528 bool fix_sex{false};
529 sex forced_sex{sex::male};
530 };
531
532 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
533 [[nodiscard]] biodata generate_(const entry& e,
534 std::uint64_t call_seed,
535 const gen_opts_& opts) const
536 {
537 effolkronium::random_local rng;
538 rng.seed(static_cast<std::mt19937::result_type>(
539 call_seed ^ (call_seed >> seed_shift_)));
540
541 biodata b;
542 b._seed = call_seed;
543 b.country_code = e.cca2;
544
545 // 1. Sex — fixed or 50/50.
546 // Note: when fix_sex is true the Bernoulli draw is skipped,
547 // so the RNG sequence diverges from an unfixed call with the
548 // same seed that happened to land on the same sex.
549 if (opts.fix_sex)
550 {
551 b.bio_sex = opts.forced_sex;
552 }
553 else
554 {
555 std::bernoulli_distribution sex_dist(0.5);
556 b.bio_sex = sex_dist(rng.engine()) ? sex::female
557 : sex::male;
558 }
559
560 // 2. Height — Gaussian from country/sex mean and SD.
561 const double h_mean = (b.bio_sex == sex::male)
562 ? e.male_height_mean
563 : e.female_height_mean;
564 const double h_sd = (b.bio_sex == sex::male)
565 ? e.male_height_sd
566 : e.female_height_sd;
567 std::normal_distribution<double> height_dist(h_mean, h_sd);
568 b.height_cm = std::clamp(height_dist(rng.engine()),
569 h_mean - 4.0 * h_sd,
570 h_mean + 4.0 * h_sd);
571
572 // 3. BMI — log-normal to model right-skewed distribution.
573 const double bmi_mean = (b.bio_sex == sex::male)
574 ? e.male_bmi_mean
575 : e.female_bmi_mean;
576 const double bmi_sd = e.bmi_sd;
577 // Convert mean/SD to log-normal parameters.
578 const double bmi_var = bmi_sd * bmi_sd;
579 const double ln_sigma2 =
580 std::log1p(bmi_var / (bmi_mean * bmi_mean));
581 const double ln_mu =
582 std::log(bmi_mean) - 0.5 * ln_sigma2;
583 const double ln_sigma = std::sqrt(ln_sigma2);
584 std::lognormal_distribution<double> bmi_dist(ln_mu, ln_sigma);
585 b.bmi = std::clamp(bmi_dist(rng.engine()), 14.0, 55.0);
586
587 // 4. Weight = BMI × (height_m)².
588 const double height_m = b.height_cm / 100.0;
589 b.weight_kg = b.bmi * height_m * height_m;
590
591 // 5. Eye colour — categorical.
592 std::discrete_distribution<unsigned> eye_dist(
593 e.eye_weights.begin(), e.eye_weights.end());
594 b.eyes = static_cast<eye_color>(eye_dist(rng.engine()));
595
596 // 6. Hair colour — categorical.
597 std::discrete_distribution<unsigned> hair_dist(
598 e.hair_weights.begin(), e.hair_weights.end());
599 b.hair = static_cast<hair_color>(hair_dist(rng.engine()));
600
601 // 7. Skin type — categorical (Fitzpatrick I–VI).
602 std::discrete_distribution<unsigned> skin_dist(
603 e.skin_weights.begin(), e.skin_weights.end());
604 b.skin = static_cast<skin_type>(
605 skin_dist(rng.engine()) + 1); // enum starts at 1
606
607 // 8. Blood type — categorical (ABO/Rh).
608 std::discrete_distribution<unsigned> blood_dist(
609 e.blood_weights.begin(), e.blood_weights.end());
610 b.blood = static_cast<blood_type>(blood_dist(rng.engine()));
611
612 // 9. Handedness — Bernoulli.
613 std::bernoulli_distribution hand_dist(
614 e.left_handed_pct / 100.0);
615 b.hand = hand_dist(rng.engine()) ? handedness::left
616 : handedness::right;
617
618 return b;
619 }
620
621 // -- Loading ----------------------------------------------------------
622
623 // Locale-independent double parser via std::from_chars.
624 static double parse_double_(std::string_view str,
625 double fallback = 0.0)
626 {
627 if (str.empty()) { return fallback; }
628 double val{};
629 auto [ptr, ec] =
630 std::from_chars(str.data(),
631 str.data() + str.size(), val); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
632 return ec == std::errc{} ? val : fallback;
633 }
634
635 static std::vector<std::string> split_tab_(std::string_view line)
636 {
637 std::vector<std::string> fields;
638 fields.reserve(32);
639 for (auto part : line | std::views::split('\t'))
640 {
641 fields.emplace_back(std::ranges::begin(part),
642 std::ranges::end(part));
643 }
644 return fields;
645 }
646
647 void load_biodata_(const std::filesystem::path& path)
648 {
649 if (!std::filesystem::is_regular_file(path)) return;
650
651 std::ifstream file{path};
652 if (!file.is_open()) return;
653
654 std::string line;
655 if (!std::getline(file, line)) return; // skip header
656
657 // Header (31 columns):
658 // country_code country_name
659 // male_height_cm male_height_sd female_height_cm female_height_sd
660 // male_bmi_mean female_bmi_mean bmi_sd
661 // eye_blue eye_intermediate eye_brown
662 // hair_black hair_brown hair_blond hair_red
663 // skin_I skin_II skin_III skin_IV skin_V skin_VI
664 // blood_O_pos blood_A_pos blood_B_pos blood_AB_pos
665 // blood_O_neg blood_A_neg blood_B_neg blood_AB_neg
666 // left_handed_pct
667 static constexpr std::size_t min_fields{31};
668
669 while (std::getline(file, line))
670 {
671 if (line.empty()) continue;
672 if (line.back() == '\r') line.pop_back();
673
674 auto f = split_tab_(line);
675 if (f.size() < min_fields) continue;
676
677 entry e;
678 e.cca2 = std::move(f[0]);
679 e.name = std::move(f[1]);
680
681 // Height.
682 e.male_height_mean = parse_double_(f[2], 170.0);
683 e.male_height_sd = parse_double_(f[3], 7.0);
684 e.female_height_mean = parse_double_(f[4], 157.0);
685 e.female_height_sd = parse_double_(f[5], 6.5);
686
687 // BMI.
688 e.male_bmi_mean = parse_double_(f[6], 24.5);
689 e.female_bmi_mean = parse_double_(f[7], 25.0);
690 e.bmi_sd = parse_double_(f[8], 4.5);
691
692 // Eye colour.
693 e.eye_weights = {
694 parse_double_(f[9]), // blue
695 parse_double_(f[10]), // intermediate
696 parse_double_(f[11]) // brown
697 };
698
699 // Hair colour.
700 e.hair_weights = {
701 parse_double_(f[12]), // black
702 parse_double_(f[13]), // brown
703 parse_double_(f[14]), // blond
704 parse_double_(f[15]) // red
705 };
706
707 // Skin type (I–VI).
708 e.skin_weights = {
709 parse_double_(f[16]), // I
710 parse_double_(f[17]), // II
711 parse_double_(f[18]), // III
712 parse_double_(f[19]), // IV
713 parse_double_(f[20]), // V
714 parse_double_(f[21]) // VI
715 };
716
717 // Blood type.
718 e.blood_weights = {
719 parse_double_(f[22]), // O+
720 parse_double_(f[23]), // A+
721 parse_double_(f[24]), // B+
722 parse_double_(f[25]), // AB+
723 parse_double_(f[26]), // O-
724 parse_double_(f[27]), // A-
725 parse_double_(f[28]), // B-
726 parse_double_(f[29]) // AB-
727 };
728
729 // Handedness.
730 e.left_handed_pct = parse_double_(f[30], 10.6);
731
732 std::string key{e.cca2};
733 _entries.insert_or_assign(std::move(key), std::move(e));
734 }
735 }
736
737 void rebuild_indices_()
738 {
739 _cca2_order.clear();
740 _cca2_order.reserve(_entries.size());
741
742 for (const auto& key : _entries | std::views::keys)
743 {
744 _cca2_order.push_back(key);
745 }
746 std::ranges::sort(_cca2_order);
747
748 if (!_cca2_order.empty())
749 {
750 _country_uniform =
751 std::uniform_int_distribution<std::size_t>(
752 0, _cca2_order.size() - 1);
753 }
754 }
755};
756
757} // namespace dasmig
758
759#endif // DASMIG_BIODATAGEN_HPP
eye_color
Eye colour categories.
@ intermediate
Green, hazel, amber.
hair_color
Hair colour categories.
@ full
~197 countries (gaps filled with regional defaults).
@ lite
~111 countries with best data coverage.
handedness
Handedness.
blood_type
ABO/Rh blood-type groups.
skin_type
Fitzpatrick skin-type scale (I–VI).
Biodata generator that produces demographically plausible human physical characteristics using countr...
bdg & seed(std::uint64_t seed_value)
Seed the internal random engine for deterministic sequences.
bdg & unseed()
Reseed the engine with a non-deterministic source.
biodata get_biodata(std::string_view cca2, sex bio_sex, std::uint64_t call_seed) const
Generate deterministic biodata with a predetermined sex.
bool load(dataset tier)
Load a specific dataset tier from auto-probed paths.
void load(const std::filesystem::path &dir)
Load biodata from a resource directory.
biodata get_biodata(sex bio_sex, std::uint64_t call_seed) const
Generate deterministic biodata from a random country with a predetermined sex.
biodata get_biodata(std::string_view cca2, sex bio_sex)
Generate random biodata with a predetermined sex.
biodata get_biodata(sex bio_sex)
Generate random biodata from a random country with a predetermined sex.
biodata get_biodata(std::string_view cca2)
Generate random biodata for a specific country.
bdg()=default
Default constructor — creates an empty generator with no data.
bool has_data() const
Check whether any data has been loaded.
std::size_t country_count() const
Return the number of loaded countries.
biodata get_biodata(std::uint64_t call_seed) const
Generate deterministic biodata from a random country.
biodata get_biodata()
Generate random biodata from a random country.
static bdg & instance()
Access the global singleton instance.
biodata get_biodata(std::string_view cca2, std::uint64_t call_seed) const
Generate deterministic biodata for a specific country.
Return type for biodata generation, holding all physical characteristics.
static std::string_view blood_type_str(blood_type t)
Blood type label.
std::string to_string() const
Human-readable summary string.
std::string country_code
ISO 3166-1 alpha-2 code.
double bmi
Body mass index (kg/m²).
static std::string_view eye_color_str(eye_color c)
Eye colour label.
blood_type blood
Blood type (ABO/Rh).
double height_cm
Height in centimetres.
static std::string_view hair_color_str(hair_color c)
Hair colour label.
static std::string_view sex_str(sex s)
Biological sex label.
sex bio_sex
Biological sex.
static std::string_view handedness_str(handedness h)
Handedness label.
skin_type skin
Fitzpatrick skin type.
static std::string_view skin_type_str(skin_type t)
Skin type label.
hair_color hair
Hair colour.
friend std::ostream & operator<<(std::ostream &os, const biodata &b)
Stream the summary to an output stream.
std::uint64_t seed() const
Retrieve the random seed used to generate this biodata.
eye_color eyes
Eye colour.
handedness hand
Handedness.
double weight_kg
Weight in kilograms.