diff --git a/demes/demes.py b/demes/demes.py index 97bfd666..c5259f21 100644 --- a/demes/demes.py +++ b/demes/demes.py @@ -2373,6 +2373,8 @@ def add_deme( if proportions is not None: deme["proportions"] = proportions if start_time is not None: + if start_time == "Infinity": + start_time = math.inf deme["start_time"] = start_time if epochs is not None: deme["epochs"] = epochs @@ -2427,6 +2429,8 @@ def add_migration( if dest is not NO_DEFAULT: migration["dest"] = dest if start_time is not None: + if start_time == "Infinity": + start_time = math.inf migration["start_time"] = start_time if end_time is not None: migration["end_time"] = end_time diff --git a/demes/load_dump.py b/demes/load_dump.py index d0a18405..b6bf5813 100644 --- a/demes/load_dump.py +++ b/demes/load_dump.py @@ -104,6 +104,11 @@ def _unstringify_infinities(data: MutableMapping[str, Any]) -> None: start_time = migration.get("start_time") if start_time == _INFINITY_STR: migration["start_time"] = float(start_time) + for default in data.get("defaults", []): + if default in ["migration", "deme"]: + start_time = data["defaults"][default].get("start_time") + if start_time == _INFINITY_STR: + data["defaults"][default]["start_time"] = float(start_time) def loads_asdict(string, *, format="yaml") -> MutableMapping[str, Any]: diff --git a/tests/test_demes.py b/tests/test_demes.py index 1ae98493..035ae7d6 100644 --- a/tests/test_demes.py +++ b/tests/test_demes.py @@ -4160,3 +4160,76 @@ def test_back_and_forth(self, graph): b = Builder.fromdict(graph.asdict_simplified()) g = b.resolve() assert g.isclose(graph) + + def test_infinite_start_time(self): + # deme start time + for start_time in (math.inf, "Infinity", None): + b = Builder(defaults=dict(epoch=dict(start_size=1))) + b.add_deme("a", start_time=start_time) + g = b.resolve() + assert g.demes[0].start_time == math.inf + for start_time in ("infinity", "inf"): + with pytest.raises(TypeError, match="must be real number, not str"): + b = Builder(defaults=dict(epoch=dict(start_size=1))) + b.add_deme("a", start_time=start_time) + g = b.resolve() + # migration start time + for start_time in (math.inf, "Infinity", None): + b = Builder(defaults=dict(epoch=dict(start_size=1))) + b.add_deme("a", start_time=start_time) + b.add_deme("b", start_time=start_time) + b.add_migration(demes=["a", "b"], rate=0.01, start_time=start_time) + g = b.resolve() + assert len(g.migrations) == 2 + assert g.migrations[0].start_time == math.inf + assert g.migrations[1].start_time == math.inf + for start_time in ("infinity", "inf"): + with pytest.raises(TypeError, match="must be real number, not str"): + b = Builder(defaults=dict(epoch=dict(start_size=1))) + b.add_deme("a", start_time=start_time) + b.add_deme("b", start_time=start_time) + b.add_migration(demes=["a", "b"], rate=0.01, start_time=start_time) + g = b.resolve() + + def test_infinite_start_time_yaml(self): + model = """time_units: generations +demes: +- name: a + start_time: Infinity + epochs: + - {end_time: 0, start_size: 1} +""" + g = demes.loads(model) + assert g.demes[0].start_time == math.inf + + model_bad = """time_units: generations +demes: +- name: a + start_time: infinity + epochs: + - {end_time: 0, start_size: 1} +""" + with pytest.raises(TypeError, match="must be real number, not str"): + g = demes.loads(model_bad) + + def test_infinities_in_defaults(self): + model = """time_units: generations +defaults: + migration: + demes: [a, b] + start_time: Infinity + deme: + start_time: Infinity +demes: +- name: a + epochs: + - {start_size: 1} +- name: b + epochs: + - {start_size: 1} +migrations: +- rate: 0.01""" + g = demes.loads(model) + assert g.demes[0].start_time == math.inf + assert g.demes[1].start_time == math.inf + assert g.migrations[0].start_time == math.inf