|
Relationship Generator 1.2.2
Believable families around a person: parents, siblings, partners and children, with ages and names for C++23
|
This guide covers every feature of the relationship-generator library. For a quick overview, see the README. For the data and the model behind it, see Data and Method. For the full API reference, run doxygen Doxyfile from the repository root and open doc/api/html/index.html.
A family is built around one person, the anchor (members[0]): the anchor's parents and grandparents, brothers and sisters (and half-siblings), partners (current and former), stepchildren, children and grandchildren, each with a date of birth, whether they are alive, a date of death for the dead, and a name.
Copy dasmig/relgen.hpp, dasmig/core.hpp and the dasmig/core/ directory into your include path, keeping them together. The library composes birth-generator (the anchor, the country's age pyramid and life expectancy) and name-generator (names, 4.5.0 or later), whose headers must be on the include path too. Supply resources/ next to the program, or define DASMIG_RELATIONSHIP_EMBED to compile the lite tier in (and DASMIG_BIRTH_EMBED and DASMIG_NAME_EMBED for the others).
instance() loads the lite tier on first use (or full if there is no lite), from the compiled-in copy or from the first of resources/, ../resources, relationship-generator/resources and ../relationship-generator/resources that has it. It draws from the birth-generator and name-generator singletons.
Each load replaces the data and every layer loaded before. To draw from other instances of the libraries it composes, construct the generator with them (they must outlive it):
family_request{} asks for the defaults: a random anchor, parents, siblings, partners, children and names.
birth wins over age. A fixed birth with a country and a sex does not consult birth-generator, so such an anchor is whoever the caller says.age outside 0 to 122, a birth after the reference date or one that makes the anchor older than 122, or an unknown country code throws std::invalid_argument.reference_date defaults to 1 July 2023, the date of the UN estimates the data comes from. The age pyramid and life expectancy are those of 2023 whatever the date, so a date far from it gives families that are valid in themselves but whose demography is today's.partners = false the partners are left out but the children remain (their other parent is not listed). Stepchildren need partners = true and down >= 1.stepparents and home are off by default; with them off a call gives exactly the family it gave before they existed. Turned on, they add to the family and change little else: stepparents and their children take the last member ids, so every other member keeps its id; a half-sibling born to a parent and a stepparent takes the stepparent as the other parent, and the stepparent's name. The household is drawn after everything else from a stream of its own.Every person has an id (its index in family::members), role (relation), bio_sex, given, patronymic, family and name, birth, alive, age (in completed years at the reference date, or at death), death for the dead and twin.
full is true when both parents are shared. A parent who is not a member (the other parent of a half-sibling, a grandchild's other parent) is left out of the edges.stepparents = true, roles stepfather and stepmother, family::stepparents()) are a parent's partner who is not the anchor's other parent: a partner_of edge from the parent to the stepparent (with its start year, end year and how it ended) and a step_parent_of edge from the stepparent to the anchor. The union began a year or more after the last child the parents had together (the anchor or a younger full sibling) and before the anchor turned 18, so a stepparent joined a child's life; it may have ended by separation or a death. With siblings, the family also holds:half_siblings with both as parents, born during the union. Half-siblings already drawn for that parent who were born during the union become the stepparent's children, and more are drawn up to the union's own fertility, as a later union's. They take ids after the stepparents, so half_siblings() sorts by birth;stepsiblings (family::stepsiblings()), drawn like a partner's stepchildren, born before the union began, with the stepparent as their one listed parent. They are linked to the stepparent's children with the parent as half-siblings, not to the anchor.request.home = true sets family::home, a household with a type (one_person, couple, nuclear, single_parent, with_parents, multi_generation or other) and the members who live with the anchor at the reference date: ids from the family, the anchor first, all alive. It is drawn from the members the family holds, so a family made with up = 0 leaves a child with no parent to live with (type other), and one made with partners = false or down = 0 has no partner or children to live with.
single_parent) draws a lone parent, the mother with mother_custody_share. Younger brothers and sisters of those parents come along (and a stepparent's own children under 18, if that stepparent kept them, by mother_custody_share), and, with the country's share of three-generation households (multi_generation), a grandparent. With no parent alive the type is other.mother_custody_share (or its complement for a father). With neither, an adult under 30 may live with their parents, and others live alone by the country's share of one-person households scaled to people (one_person_scale), or with people the family does not hold (other). With the multi-generation share, a living parent joins a partnered or lone-parent household.households.tsv (UN DESA), falling back to the subregion and then the world; the constants are in parameters.tsv and are curated.twin set.family::country_code, reference_date, fertility_source and union_source say where the figures came from (the country's own row, its subregion's median or the world's).Names come from name-generator, by the culture of the anchor's country: a parent is named after the child, a sibling, child, stepchild or grandchild after both parents, a partner is drawn and a wife may take the anchor's family name, each by the culture's convention (two surnames in Spain and Portugal, patronymics in Russia and Iceland, a family name first in China, Japan and Korea). A country whose culture has no lists in the loaded tier gets no names, and names = false gives none.
name_culture makes the whole family follow another culture than the anchor's country's, for a family of immigrants or a country without a culture of its own.
The anchor can be a person you already have. Give the name and the family is built to match:
person_name::family is the family name; two words are two surnames in a culture with two (Spain, Portugal). Without parents' names, the parents follow the anchor's; with them, the anchor follows the parents.
You can also give the relatives you know. A relative that is given is used as it is, and a list that is given (even an empty one) is the whole list:
| Field | Meaning |
|---|---|
name | The anchor's name |
father, mother | A parent; the other is drawn at the usual gap. known_person::parents holds that person's own parents (the grandparents) |
siblings | known_sibling (person, full, and for a half-sibling the shared parent's sex through); none are drawn when set |
unions | known_union (partner, start, end, children, stepchildren); none are drawn when set. A union without a partner is known by its children alone |
A known person has bio_sex, birth, an optional death (before the reference date; later is read as alive) and name. A known parent born after the anchor throws std::invalid_argument.
Any member of a family can have a family of their own that agrees with the first:
The member keeps their birth, sex, name and country, and the relatives base lists: parents (and grandparents), siblings and half-siblings, partners, stepchildren and children. The rest is drawn. The request's anchor and reference_date are ignored (the member and the family's date are used). A member whose partners or children base did not list (a family made with partners = false or down = 0) has them drawn again. See Data and Method for what is kept.
expand(base, member, request) without a seed draws one from the instance engine.
get_family(request, seed) gives the same result for the same request, seed and data, on every platform, without advancing the instance engine.get_family(request) draws a fresh seed from the instance engine, initially seeded from system entropy. There are no generator-level seed() or unseed() controls; for deterministic sequences, keep a dasmig::core::rng in the caller and pass its next value as the seed.v.seed() is the seed a result was generated from: get_family(request, v.seed()) replays it with the same request and data."parents", "siblings", "unions", "children", "names", ...), so asking for no siblings or no names leaves the other draws as they were.The shipped data is a set of layered tables: regions.tsv, fertility.tsv, unions.tsv, households.tsv, defaults.tsv and parameters.tsv (see Data and Method). Files with the same columns can be loaded on top of them to add rows, change them or remove them. Rows are keyed by cca2, subregion or key.
dasmig::core::tsv_error with the file, line and column, and the data stays as it was.fertility.tsv falls back to its subregion's row in defaults.tsv, and the family says so (fertility_source).load(...) replaces the base and drops every layer.Every result records the seed and the data it came from:
to_json() gives the result as JSON, the form the WebAssembly layer and the game consume. Seeds are strings because JavaScript numbers cannot hold every 64-bit value. to_json(writer) writes into an existing dasmig::core::json_writer, for composing larger documents.
A member that is a twin has "twin":true; the dead have death_age and death in place of age.
Define DASMIG_RELATIONSHIP_EMBED before including the header to compile the lite tier into the program. load(dataset::lite) and instance() then use it and need no files. The compiled-in data has the same data_hash() as the file, so seeds replay the same either way.
dasmig/relgen_data.hpp is generated from resources/lite by make embed; CI checks it is up to date.
get_family(request, seed), expand(base, member, request, seed) and the batch getters are const and may run concurrently on one instance. Calls that draw from the instance engine (get_family(request), expand(base, member, request)), load and extend need external synchronisation (e.g. a std::mutex) on the same instance.
Every seeded getter has batch forms: get_family_batch returns a std::vector, and for_each_family streams the items to a sink in index order without holding the batch.
i is exactly get_family(request, core::item_seed(batch_seed, first + i)). A range can be generated directly (first = 1000 starts at item 1000), two batches that follow each other equal one, and the output is the same for any threads (0 means the hardware count). Without a batch_seed one is drawn like the single getter draws its own.core::batch_options has count, first, threads and unique. The request describes one item; the options describe the batch..unique = core::unique_rule::by<family>(key) regenerates an item whose key already appeared in the batch, with a deterministic retry seed, and throws std::runtime_error naming the item when the attempts run out.core::jsonl_writer writes one to_json() per line, core::csv_writer flattens each result's JSON into dotted columns. Any callable (std::uint64_t index, family&) works.| Exception | When |
|---|---|
std::runtime_error | Any generation call before data is loaded (this library's or birth-generator's); names requested while name-generator has no data |
std::invalid_argument | up or down outside 0 to 2; an anchor age outside 0 to 122; a birth after the reference date or making the anchor older than 122; an unknown country code; a known person with an empty given name, a birth that is not a date, a death before the birth, or a parent not born before the child; a union known to end before it begins; expand with a member that is not in the family |
dasmig::core::tsv_error | A malformed table or layer (message gives file, line and column); the data is unchanged |