phyloom 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. phyloom-0.0.0/LICENSE.txt +22 -0
  2. phyloom-0.0.0/PKG-INFO +15 -0
  3. phyloom-0.0.0/README.md +0 -0
  4. phyloom-0.0.0/pyproject.toml +52 -0
  5. phyloom-0.0.0/setup.cfg +4 -0
  6. phyloom-0.0.0/src/phyloom/__init__.py +0 -0
  7. phyloom-0.0.0/src/phyloom/configs/__init__.py +15 -0
  8. phyloom-0.0.0/src/phyloom/configs/events.py +49 -0
  9. phyloom-0.0.0/src/phyloom/configs/generator.py +143 -0
  10. phyloom-0.0.0/src/phyloom/configs/hazards.py +88 -0
  11. phyloom-0.0.0/src/phyloom/configs/models/__init__.py +0 -0
  12. phyloom-0.0.0/src/phyloom/configs/models/bdsh.py +141 -0
  13. phyloom-0.0.0/src/phyloom/configs/models/coalescent.py +62 -0
  14. phyloom-0.0.0/src/phyloom/configs/models/epidemic.py +102 -0
  15. phyloom-0.0.0/src/phyloom/configs/roots/__init__.py +32 -0
  16. phyloom-0.0.0/src/phyloom/configs/roots/context.py +39 -0
  17. phyloom-0.0.0/src/phyloom/configs/roots/float.py +28 -0
  18. phyloom-0.0.0/src/phyloom/configs/roots/int.py +19 -0
  19. phyloom-0.0.0/src/phyloom/configs/roots/source.py +111 -0
  20. phyloom-0.0.0/src/phyloom/configs/roots/str.py +25 -0
  21. phyloom-0.0.0/src/phyloom/configs/roots/typeguards.py +43 -0
  22. phyloom-0.0.0/src/phyloom/configs/roots/utils.py +38 -0
  23. phyloom-0.0.0/src/phyloom/core/__init__.py +8 -0
  24. phyloom-0.0.0/src/phyloom/core/_algo/__init__.py +5 -0
  25. phyloom-0.0.0/src/phyloom/core/_algo/iter.py +38 -0
  26. phyloom-0.0.0/src/phyloom/core/_algo/pprint.py +62 -0
  27. phyloom-0.0.0/src/phyloom/core/_algo/utils.py +15 -0
  28. phyloom-0.0.0/src/phyloom/core/edge.py +32 -0
  29. phyloom-0.0.0/src/phyloom/core/msa.py +81 -0
  30. phyloom-0.0.0/src/phyloom/core/net.py +127 -0
  31. phyloom-0.0.0/src/phyloom/core/node.py +25 -0
  32. phyloom-0.0.0/src/phyloom/core/sequence.py +40 -0
  33. phyloom-0.0.0/src/phyloom/core/tree.py +78 -0
  34. phyloom-0.0.0/src/phyloom/io/__init__.py +11 -0
  35. phyloom-0.0.0/src/phyloom/io/_utils.py +15 -0
  36. phyloom-0.0.0/src/phyloom/io/fasta.py +72 -0
  37. phyloom-0.0.0/src/phyloom/io/newick.py +221 -0
  38. phyloom-0.0.0/src/phyloom/main.py +85 -0
  39. phyloom-0.0.0/src/phyloom/py.typed +0 -0
  40. phyloom-0.0.0/src/phyloom/simulator/__init__.py +17 -0
  41. phyloom-0.0.0/src/phyloom/simulator/bdsh/__init__.py +12 -0
  42. phyloom-0.0.0/src/phyloom/simulator/bdsh/events.py +115 -0
  43. phyloom-0.0.0/src/phyloom/simulator/bdsh/model.py +111 -0
  44. phyloom-0.0.0/src/phyloom/simulator/coalescent/__init__.py +4 -0
  45. phyloom-0.0.0/src/phyloom/simulator/coalescent/events.py +34 -0
  46. phyloom-0.0.0/src/phyloom/simulator/coalescent/model.py +82 -0
  47. phyloom-0.0.0/src/phyloom/simulator/core.py +164 -0
  48. phyloom-0.0.0/src/phyloom/simulator/epidemic/__init__.py +4 -0
  49. phyloom-0.0.0/src/phyloom/simulator/epidemic/events.py +69 -0
  50. phyloom-0.0.0/src/phyloom/simulator/epidemic/model.py +120 -0
  51. phyloom-0.0.0/src/phyloom/simulator/errors.py +10 -0
  52. phyloom-0.0.0/src/phyloom/simulator/hazards/__init__.py +15 -0
  53. phyloom-0.0.0/src/phyloom/simulator/hazards/constant.py +11 -0
  54. phyloom-0.0.0/src/phyloom/simulator/hazards/delta.py +6 -0
  55. phyloom-0.0.0/src/phyloom/simulator/hazards/exponential.py +28 -0
  56. phyloom-0.0.0/src/phyloom/simulator/hazards/hazard.py +15 -0
  57. phyloom-0.0.0/src/phyloom/simulator/hazards/numeric.py +57 -0
  58. phyloom-0.0.0/src/phyloom/simulator/hazards/skyline.py +55 -0
  59. phyloom-0.0.0/src/phyloom/simulator/net/__init__.py +4 -0
  60. phyloom-0.0.0/src/phyloom/simulator/net/events.py +50 -0
  61. phyloom-0.0.0/src/phyloom/simulator/net/model.py +157 -0
  62. phyloom-0.0.0/src/phyloom/simulator/utils.py +34 -0
  63. phyloom-0.0.0/src/phyloom/types.py +29 -0
  64. phyloom-0.0.0/src/phyloom/utils/__init__.py +6 -0
  65. phyloom-0.0.0/src/phyloom/utils/indexed_set.py +53 -0
  66. phyloom-0.0.0/src/phyloom/utils/priority_buckets.py +47 -0
  67. phyloom-0.0.0/src/phyloom/utils/registry.py +83 -0
  68. phyloom-0.0.0/src/phyloom/utils/set_view.py +23 -0
  69. phyloom-0.0.0/src/phyloom.egg-info/PKG-INFO +15 -0
  70. phyloom-0.0.0/src/phyloom.egg-info/SOURCES.txt +72 -0
  71. phyloom-0.0.0/src/phyloom.egg-info/dependency_links.txt +1 -0
  72. phyloom-0.0.0/src/phyloom.egg-info/entry_points.txt +7 -0
  73. phyloom-0.0.0/src/phyloom.egg-info/requires.txt +7 -0
  74. phyloom-0.0.0/src/phyloom.egg-info/top_level.txt +1 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 GABRIELE MARINO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
phyloom-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: phyloom
3
+ Version: 0.0.0
4
+ Summary: Generate phylogenetic datasets with minimal setup effort
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE.txt
8
+ Requires-Dist: joblib==1.5.2
9
+ Requires-Dist: matplotlib==3.10.7
10
+ Requires-Dist: pandas==2.3.3
11
+ Requires-Dist: pydantic==2.12.3
12
+ Requires-Dist: pyyaml==6.0.3
13
+ Requires-Dist: scipy>=1.15.3
14
+ Requires-Dist: tqdm==4.67.1
15
+ Dynamic: license-file
File without changes
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "phyloom"
3
+ version = "0.0.0"
4
+ description = "Generate phylogenetic datasets with minimal setup effort"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "joblib==1.5.2",
9
+ "matplotlib==3.10.7",
10
+ "pandas==2.3.3",
11
+ "pydantic==2.12.3",
12
+ "pyyaml==6.0.3",
13
+ "scipy>=1.15.3",
14
+ "tqdm==4.67.1",
15
+ ]
16
+
17
+ [dependency-groups]
18
+ dev = [
19
+ "joblib-stubs==1.5.2.0.20250831",
20
+ "pandas-stubs==2.3.3.260113",
21
+ "pylint>=4.0.6",
22
+ "pyright==1.1.411",
23
+ "pytest==8.4.2",
24
+ "ruff>=0.16.0",
25
+ "scipy-stubs>=1.15.3.0",
26
+ ]
27
+ docs = [
28
+ "myst-parser==4.0.1",
29
+ "poethepoet==0.46.0",
30
+ "sphinx==8.1.3",
31
+ "sphinx-copybutton==0.5.2",
32
+ "sphinx-rtd-theme==3.1.0",
33
+ ]
34
+
35
+ [tool.pyright]
36
+ extraPaths = ["docs/_ext"]
37
+ typeCheckingMode = "strict"
38
+
39
+ [tool.poe.tasks]
40
+ docs = "sphinx-build -b html docs docs/_build/html"
41
+
42
+ [project.scripts]
43
+ phyloom = "phyloom.main:main"
44
+
45
+ [project.entry-points."phyloom.plugins"]
46
+ bdsh = "phyloom.configs.models.bdsh"
47
+ coalescent = "phyloom.configs.models.coalescent"
48
+ epidemic = "phyloom.configs.models.epidemic"
49
+
50
+ [build-system]
51
+ requires = ["setuptools>=42"]
52
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,15 @@
1
+ from phyloom.configs.events import EventCfg, EventRuleCfgBase, ScheduledEventCfg
2
+ from phyloom.configs.generator import GENERATOR_REGISTRY, GeneratorBase, GeneratorCfg
3
+ from phyloom.configs.hazards import HAZARD_REGISTRY, HazardCfg, HazardCfgBase
4
+
5
+ __all__ = [
6
+ "GENERATOR_REGISTRY",
7
+ "HAZARD_REGISTRY",
8
+ "EventCfg",
9
+ "EventRuleCfgBase",
10
+ "GeneratorBase",
11
+ "GeneratorCfg",
12
+ "HazardCfg",
13
+ "HazardCfgBase",
14
+ "ScheduledEventCfg",
15
+ ]
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
5
+
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+ from phyloom.configs.hazards import HazardCfg
9
+ from phyloom.configs.roots import FloatListCfg, ModelActionCfg
10
+ from phyloom.simulator.core import Event, ModelT_contra, ScheduledEvent
11
+
12
+ if TYPE_CHECKING:
13
+ from phyloom.simulator.core import EventRule, ModelT
14
+ from phyloom.types import Context
15
+
16
+ RuleCfgT_co = TypeVar("RuleCfgT_co", bound="EventRuleCfgBase[Any]", covariant=True)
17
+
18
+
19
+ class EventRuleCfgBase(ABC, BaseModel, Generic[ModelT_contra]):
20
+ model_config = ConfigDict(extra="forbid")
21
+
22
+ @abstractmethod
23
+ def contextualize(self, context: Context) -> EventRule[ModelT_contra]: ...
24
+
25
+
26
+ class EventCfg(BaseModel, Generic[RuleCfgT_co]):
27
+ model_config = ConfigDict(extra="forbid", frozen=True)
28
+
29
+ rule: RuleCfgT_co
30
+ hazard: HazardCfg
31
+
32
+ def contextualize(
33
+ self: EventCfg[EventRuleCfgBase[ModelT]], context: Context
34
+ ) -> Event[ModelT]:
35
+ return Event(
36
+ self.hazard.contextualize(context), self.rule.contextualize(context)
37
+ )
38
+
39
+
40
+ class ScheduledEventCfg(BaseModel):
41
+ model_config = ConfigDict(extra="forbid")
42
+
43
+ times: FloatListCfg
44
+ action: ModelActionCfg
45
+
46
+ def contextualize(self, context: Context) -> ScheduledEvent[Any]:
47
+ return ScheduledEvent(
48
+ self.times.contextualize(context), self.action.contextualize(context)
49
+ )
@@ -0,0 +1,143 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from functools import partial
5
+ from pathlib import Path
6
+ from random import Random
7
+ from typing import TYPE_CHECKING, Annotated
8
+
9
+ import pandas as pd
10
+ from numpy.random import default_rng
11
+ from pydantic import BaseModel, ConfigDict
12
+
13
+ from phyloom.configs.roots import (
14
+ ContextCfg,
15
+ FloatCfg,
16
+ ModelExpressionCfg,
17
+ ModelPredicateCfg,
18
+ OutputHookCfg,
19
+ contextualize_optional,
20
+ )
21
+ from phyloom.simulator import (
22
+ MaxRejectionsExceededError,
23
+ SimulationTimeoutError,
24
+ run_simulations,
25
+ )
26
+ from phyloom.simulator.core import Model
27
+ from phyloom.utils import Registry
28
+
29
+ if TYPE_CHECKING:
30
+ from collections.abc import Callable
31
+ from typing import Any, TypeAlias
32
+
33
+ from phyloom.types import Context, Metadata, SampleID, Seed
34
+
35
+
36
+ DATA_DIRNAME = "data"
37
+ METADATA_FILENAME = "metadata.csv"
38
+
39
+
40
+ class GeneratorBase(ABC, BaseModel):
41
+ model_config = ConfigDict(extra="forbid")
42
+
43
+ output_dir: str = "phyloom-outputs"
44
+ n_samples: int | dict[str, int] = 1
45
+ n_jobs: int = -1
46
+ seed: int | None = None
47
+ context: ContextCfg = ContextCfg()
48
+
49
+ max_time: FloatCfg | None = None
50
+ stop_criterion: ModelPredicateCfg | None = None
51
+ acceptance_criterion: ModelPredicateCfg | None = None
52
+ max_rejections: int | None = None
53
+ timeout: float | None = None
54
+ logs: dict[str, ModelExpressionCfg] = {}
55
+
56
+ output_hooks: tuple[OutputHookCfg, ...]
57
+
58
+ @abstractmethod
59
+ def contextualize(self, context: Context) -> Model: ...
60
+
61
+ def _contextualize_logs(
62
+ self, context: Context
63
+ ) -> Callable[[Any], dict[str, object]]:
64
+ return lambda model: {
65
+ key: value.contextualize(context)(model) for key, value in self.logs.items()
66
+ }
67
+
68
+ def _generate_sample(
69
+ self, sample_id: SampleID, seed: Seed, *, data_dir: Path
70
+ ) -> Metadata:
71
+ timed_out_contexts: list[Context] = []
72
+ max_rejections_exceeded_contexts: list[Context] = []
73
+ while True:
74
+ context = self.context.materialize(default_rng(seed))
75
+ model = self.contextualize(context)
76
+
77
+ try:
78
+ metadata = model.run(
79
+ max_time=contextualize_optional(self.max_time, context),
80
+ stop_criterion=contextualize_optional(self.stop_criterion, context),
81
+ acceptance_criterion=contextualize_optional(
82
+ self.acceptance_criterion, context
83
+ ),
84
+ logs=self._contextualize_logs(context),
85
+ timeout=self.timeout,
86
+ max_rejections=self.max_rejections,
87
+ seed=seed,
88
+ )
89
+ output_dir = data_dir / str(sample_id)
90
+ output_dir.mkdir(parents=True)
91
+ for hook in self.output_hooks:
92
+ hook.contextualize(context)(model, output_dir)
93
+ except SimulationTimeoutError:
94
+ timed_out_contexts.append(context)
95
+ seed += 1
96
+ continue
97
+ except MaxRejectionsExceededError:
98
+ max_rejections_exceeded_contexts.append(context)
99
+ seed += 1
100
+ continue
101
+
102
+ return {
103
+ **context,
104
+ **metadata,
105
+ "timed_out_contexts": timed_out_contexts,
106
+ "max_rejections_exceeded_contexts": max_rejections_exceeded_contexts,
107
+ }
108
+
109
+ def _generate_split(self, n_samples: int, output_dir: Path, seed: Seed):
110
+ if output_dir.exists():
111
+ print(f"Output directory {output_dir} already exists. Skipping.")
112
+ return
113
+
114
+ data_dir = output_dir / DATA_DIRNAME
115
+ df = run_simulations(
116
+ n_samples,
117
+ partial(self._generate_sample, data_dir=data_dir),
118
+ seed=seed,
119
+ n_jobs=self.n_jobs,
120
+ )
121
+
122
+ for name in ["timed_out_contexts", "max_rejections_exceeded_contexts"]:
123
+ meta_df = pd.DataFrame([d for row in df[name] for d in row])
124
+ if not meta_df.empty:
125
+ meta_df.to_csv(output_dir / f"{name}.csv", index=False)
126
+ df = df.drop(columns=[name])
127
+
128
+ df.to_csv(output_dir / METADATA_FILENAME, index=False)
129
+
130
+ def generate(self):
131
+ rng = Random(self.seed)
132
+ output_dir = Path(self.output_dir)
133
+ if isinstance(self.n_samples, dict):
134
+ for split, n_samples in self.n_samples.items():
135
+ self._generate_split(n_samples, output_dir / split, rng.getrandbits(32))
136
+ else:
137
+ self._generate_split(self.n_samples, output_dir, rng.getrandbits(32))
138
+
139
+
140
+ GENERATOR_REGISTRY: Registry[GeneratorBase] = Registry(
141
+ "Generator", GeneratorBase, discriminator="model"
142
+ )
143
+ GeneratorCfg: TypeAlias = Annotated[GeneratorBase, GENERATOR_REGISTRY.validator]
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Annotated
5
+
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+ from phyloom.configs.roots import (
9
+ FloatCfg,
10
+ FloatListCfg,
11
+ IntCfg,
12
+ TimeFnCfg,
13
+ )
14
+ from phyloom.simulator.hazards import (
15
+ ConstantHazard,
16
+ DeltaHazard,
17
+ ExponentialHazard,
18
+ NumericHazard,
19
+ SkylineHazard,
20
+ )
21
+ from phyloom.utils import Registry
22
+
23
+ if TYPE_CHECKING:
24
+ from typing import TypeAlias
25
+
26
+ from phyloom.simulator.hazards import Hazard
27
+ from phyloom.types import Context
28
+
29
+
30
+ class HazardCfgBase(ABC, BaseModel):
31
+ model_config = ConfigDict(extra="forbid")
32
+
33
+ @abstractmethod
34
+ def contextualize(self, context: Context) -> Hazard: ...
35
+
36
+
37
+ HAZARD_REGISTRY: Registry[HazardCfgBase] = Registry("Hazard", HazardCfgBase)
38
+ HazardCfg: TypeAlias = Annotated[HazardCfgBase, HAZARD_REGISTRY.validator]
39
+
40
+
41
+ @HAZARD_REGISTRY.register("constant")
42
+ class ConstantHazardCfg(HazardCfgBase):
43
+ rate: FloatCfg
44
+
45
+ def contextualize(self, context: Context) -> ConstantHazard:
46
+ return ConstantHazard(self.rate.contextualize(context))
47
+
48
+
49
+ @HAZARD_REGISTRY.register("delta")
50
+ class DeltaHazardCfg(HazardCfgBase):
51
+ def contextualize(self, context: Context) -> DeltaHazard:
52
+ return DeltaHazard()
53
+
54
+
55
+ @HAZARD_REGISTRY.register("exponential")
56
+ class ExponentialHazardCfg(HazardCfgBase):
57
+ rate: FloatCfg
58
+ decay: FloatCfg
59
+
60
+ def contextualize(self, context: Context) -> ExponentialHazard:
61
+ return ExponentialHazard(
62
+ self.rate.contextualize(context), self.decay.contextualize(context)
63
+ )
64
+
65
+
66
+ @HAZARD_REGISTRY.register("skyline")
67
+ class SkylineHazardCfg(HazardCfgBase):
68
+ rates: FloatListCfg
69
+ break_times: FloatListCfg
70
+
71
+ def contextualize(self, context: Context) -> SkylineHazard:
72
+ return SkylineHazard(
73
+ self.rates.contextualize(context), self.break_times.contextualize(context)
74
+ )
75
+
76
+
77
+ @HAZARD_REGISTRY.register("numeric")
78
+ class NumericHazardCfg(HazardCfgBase):
79
+ rate_fn: TimeFnCfg
80
+ max_bracketing_steps: IntCfg = IntCfg.model_validate(16)
81
+ initial_dt: FloatCfg = FloatCfg.model_validate(1.0)
82
+
83
+ def contextualize(self, context: Context) -> NumericHazard:
84
+ return NumericHazard(
85
+ self.rate_fn.contextualize(context),
86
+ max_bracketing_steps=self.max_bracketing_steps.contextualize(context),
87
+ initial_dt=self.initial_dt.contextualize(context),
88
+ )
File without changes
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Annotated
4
+
5
+ from phyloom.configs.events import EventCfg, EventRuleCfgBase, ScheduledEventCfg
6
+ from phyloom.configs.generator import GENERATOR_REGISTRY, GeneratorBase
7
+ from phyloom.configs.roots import StrCfg, StrPairCfg, contextualize_optional
8
+ from phyloom.simulator.bdsh import (
9
+ LDH,
10
+ LGH,
11
+ LNH,
12
+ BDSHModel,
13
+ Birth,
14
+ Death,
15
+ Migration,
16
+ Sampling,
17
+ )
18
+ from phyloom.utils import Registry
19
+
20
+ if TYPE_CHECKING:
21
+ from typing import TypeAlias
22
+
23
+ from phyloom.types import Context
24
+
25
+ BDSH_EVENT_RULE_REGISTRY: Registry[EventRuleCfgBase[BDSHModel]] = Registry(
26
+ "BDSHEventRule", EventRuleCfgBase
27
+ )
28
+ BDSHRuleCfg: TypeAlias = Annotated[
29
+ EventRuleCfgBase[BDSHModel], BDSH_EVENT_RULE_REGISTRY.validator
30
+ ]
31
+
32
+
33
+ @BDSH_EVENT_RULE_REGISTRY.register("birth")
34
+ class BirthCfg(EventRuleCfgBase[BDSHModel]):
35
+ parent_state: StrCfg | None = None
36
+ child_states: tuple[StrCfg | None, StrCfg | None] = (None, None)
37
+
38
+ def contextualize(self, context: Context) -> Birth:
39
+ child_state1, child_state2 = self.child_states
40
+ return Birth(
41
+ parent_state=contextualize_optional(self.parent_state, context),
42
+ child_states=(
43
+ contextualize_optional(child_state1, context),
44
+ contextualize_optional(child_state2, context),
45
+ ),
46
+ )
47
+
48
+
49
+ @BDSH_EVENT_RULE_REGISTRY.register("death")
50
+ class DeathCfg(EventRuleCfgBase[BDSHModel]):
51
+ state: StrCfg | None = None
52
+
53
+ def contextualize(self, context: Context) -> Death:
54
+ return Death(state=contextualize_optional(self.state, context))
55
+
56
+
57
+ @BDSH_EVENT_RULE_REGISTRY.register("migration")
58
+ class MigrationCfg(EventRuleCfgBase[BDSHModel]):
59
+ target_state: StrCfg
60
+ source_state: StrCfg | None = None
61
+
62
+ def contextualize(self, context: Context) -> Migration:
63
+ return Migration(
64
+ target_state=self.target_state.contextualize(context),
65
+ source_state=contextualize_optional(self.source_state, context),
66
+ )
67
+
68
+
69
+ @BDSH_EVENT_RULE_REGISTRY.register("sampling")
70
+ class SamplingCfg(EventRuleCfgBase[BDSHModel]):
71
+ removal: bool
72
+ state: StrCfg | None = None
73
+
74
+ def contextualize(self, context: Context) -> Sampling:
75
+ return Sampling(
76
+ removal=self.removal, state=contextualize_optional(self.state, context)
77
+ )
78
+
79
+
80
+ @BDSH_EVENT_RULE_REGISTRY.register("ldh")
81
+ class LDHCfg(EventRuleCfgBase[BDSHModel]):
82
+ parent_states: StrPairCfg | None = None
83
+ hybrid_state: StrCfg | None = None
84
+
85
+ def contextualize(self, context: Context) -> LDH:
86
+ return LDH(
87
+ parent_states=contextualize_optional(self.parent_states, context),
88
+ hybrid_state=contextualize_optional(self.hybrid_state, context),
89
+ )
90
+
91
+
92
+ @BDSH_EVENT_RULE_REGISTRY.register("lnh")
93
+ class LNHCfg(EventRuleCfgBase[BDSHModel]):
94
+ continuing_parent_state: StrCfg | None = None
95
+ terminating_parent_state: StrCfg | None = None
96
+ new_parent_state: StrCfg | None = None
97
+ hybrid_state: StrCfg | None = None
98
+
99
+ def contextualize(self, context: Context) -> LNH:
100
+ return LNH(
101
+ continuing_parent_state=contextualize_optional(
102
+ self.continuing_parent_state, context
103
+ ),
104
+ terminating_parent_state=contextualize_optional(
105
+ self.terminating_parent_state, context
106
+ ),
107
+ new_parent_state=contextualize_optional(self.new_parent_state, context),
108
+ hybrid_state=contextualize_optional(self.hybrid_state, context),
109
+ )
110
+
111
+
112
+ @BDSH_EVENT_RULE_REGISTRY.register("lgh")
113
+ class LGHCfg(EventRuleCfgBase[BDSHModel]):
114
+ parent_states: StrPairCfg | None = None
115
+ hybrid_state: StrCfg | None = None
116
+ new_parent_states: tuple[StrCfg | None, StrCfg | None] = (None, None)
117
+
118
+ def contextualize(self, context: Context) -> LGH:
119
+ new_parent_state1, new_parent_state2 = self.new_parent_states
120
+ return LGH(
121
+ parent_states=contextualize_optional(self.parent_states, context),
122
+ hybrid_state=contextualize_optional(self.hybrid_state, context),
123
+ new_parent_states=(
124
+ contextualize_optional(new_parent_state1, context),
125
+ contextualize_optional(new_parent_state2, context),
126
+ ),
127
+ )
128
+
129
+
130
+ @GENERATOR_REGISTRY.register("bdsh")
131
+ class BDSHGenerator(GeneratorBase):
132
+ init_state: StrCfg
133
+ events: tuple[EventCfg[BDSHRuleCfg], ...] = ()
134
+ schedule: tuple[ScheduledEventCfg, ...] = ()
135
+
136
+ def contextualize(self, context: Context) -> BDSHModel:
137
+ return BDSHModel(
138
+ init_state=self.init_state.contextualize(context),
139
+ events=[e.contextualize(context) for e in self.events],
140
+ schedule=[e.contextualize(context) for e in self.schedule],
141
+ )
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Annotated
4
+
5
+ from phyloom.configs.events import EventCfg, EventRuleCfgBase, ScheduledEventCfg
6
+ from phyloom.configs.generator import GENERATOR_REGISTRY, GeneratorBase
7
+ from phyloom.configs.roots import IntCfg, StrCfg, StrPairCfg, contextualize_optional
8
+ from phyloom.simulator.coalescent import Coalesce, CoalescentModel, Retrace
9
+ from phyloom.utils import Registry
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import TypeAlias
13
+
14
+ from phyloom.types import Context
15
+
16
+ COALESCENT_EVENT_RULE_REGISTRY: Registry[EventRuleCfgBase[CoalescentModel]] = Registry(
17
+ "CoalescentEventRule", EventRuleCfgBase
18
+ )
19
+ CoalescentRuleCfg: TypeAlias = Annotated[
20
+ EventRuleCfgBase[CoalescentModel], COALESCENT_EVENT_RULE_REGISTRY.validator
21
+ ]
22
+
23
+
24
+ @COALESCENT_EVENT_RULE_REGISTRY.register("retrace")
25
+ class RetraceCfg(EventRuleCfgBase[CoalescentModel]):
26
+ ancestral_state: StrCfg
27
+ current_state: StrCfg | None = None
28
+
29
+ def contextualize(self, context: Context) -> Retrace:
30
+ return Retrace(
31
+ ancestral_state=self.ancestral_state.contextualize(context),
32
+ current_state=contextualize_optional(self.current_state, context),
33
+ )
34
+
35
+
36
+ @COALESCENT_EVENT_RULE_REGISTRY.register("coalesce")
37
+ class CoalesceCfg(EventRuleCfgBase[CoalescentModel]):
38
+ current_states: StrPairCfg | None = None
39
+ ancestral_state: StrCfg | None = None
40
+
41
+ def contextualize(self, context: Context) -> Coalesce:
42
+ return Coalesce(
43
+ current_states=contextualize_optional(self.current_states, context),
44
+ ancestral_state=contextualize_optional(self.ancestral_state, context),
45
+ )
46
+
47
+
48
+ @GENERATOR_REGISTRY.register("coalescent")
49
+ class CoalescentGenerator(GeneratorBase):
50
+ init_population: dict[str, IntCfg]
51
+ events: tuple[EventCfg[CoalescentRuleCfg], ...] = ()
52
+ schedule: tuple[ScheduledEventCfg, ...] = ()
53
+
54
+ def contextualize(self, context: Context) -> CoalescentModel:
55
+ return CoalescentModel(
56
+ init_population={
57
+ state: size.contextualize(context)
58
+ for state, size in self.init_population.items()
59
+ },
60
+ events=[e.contextualize(context) for e in self.events],
61
+ schedule=[e.contextualize(context) for e in self.schedule],
62
+ )