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.
- phyloom-0.0.0/LICENSE.txt +22 -0
- phyloom-0.0.0/PKG-INFO +15 -0
- phyloom-0.0.0/README.md +0 -0
- phyloom-0.0.0/pyproject.toml +52 -0
- phyloom-0.0.0/setup.cfg +4 -0
- phyloom-0.0.0/src/phyloom/__init__.py +0 -0
- phyloom-0.0.0/src/phyloom/configs/__init__.py +15 -0
- phyloom-0.0.0/src/phyloom/configs/events.py +49 -0
- phyloom-0.0.0/src/phyloom/configs/generator.py +143 -0
- phyloom-0.0.0/src/phyloom/configs/hazards.py +88 -0
- phyloom-0.0.0/src/phyloom/configs/models/__init__.py +0 -0
- phyloom-0.0.0/src/phyloom/configs/models/bdsh.py +141 -0
- phyloom-0.0.0/src/phyloom/configs/models/coalescent.py +62 -0
- phyloom-0.0.0/src/phyloom/configs/models/epidemic.py +102 -0
- phyloom-0.0.0/src/phyloom/configs/roots/__init__.py +32 -0
- phyloom-0.0.0/src/phyloom/configs/roots/context.py +39 -0
- phyloom-0.0.0/src/phyloom/configs/roots/float.py +28 -0
- phyloom-0.0.0/src/phyloom/configs/roots/int.py +19 -0
- phyloom-0.0.0/src/phyloom/configs/roots/source.py +111 -0
- phyloom-0.0.0/src/phyloom/configs/roots/str.py +25 -0
- phyloom-0.0.0/src/phyloom/configs/roots/typeguards.py +43 -0
- phyloom-0.0.0/src/phyloom/configs/roots/utils.py +38 -0
- phyloom-0.0.0/src/phyloom/core/__init__.py +8 -0
- phyloom-0.0.0/src/phyloom/core/_algo/__init__.py +5 -0
- phyloom-0.0.0/src/phyloom/core/_algo/iter.py +38 -0
- phyloom-0.0.0/src/phyloom/core/_algo/pprint.py +62 -0
- phyloom-0.0.0/src/phyloom/core/_algo/utils.py +15 -0
- phyloom-0.0.0/src/phyloom/core/edge.py +32 -0
- phyloom-0.0.0/src/phyloom/core/msa.py +81 -0
- phyloom-0.0.0/src/phyloom/core/net.py +127 -0
- phyloom-0.0.0/src/phyloom/core/node.py +25 -0
- phyloom-0.0.0/src/phyloom/core/sequence.py +40 -0
- phyloom-0.0.0/src/phyloom/core/tree.py +78 -0
- phyloom-0.0.0/src/phyloom/io/__init__.py +11 -0
- phyloom-0.0.0/src/phyloom/io/_utils.py +15 -0
- phyloom-0.0.0/src/phyloom/io/fasta.py +72 -0
- phyloom-0.0.0/src/phyloom/io/newick.py +221 -0
- phyloom-0.0.0/src/phyloom/main.py +85 -0
- phyloom-0.0.0/src/phyloom/py.typed +0 -0
- phyloom-0.0.0/src/phyloom/simulator/__init__.py +17 -0
- phyloom-0.0.0/src/phyloom/simulator/bdsh/__init__.py +12 -0
- phyloom-0.0.0/src/phyloom/simulator/bdsh/events.py +115 -0
- phyloom-0.0.0/src/phyloom/simulator/bdsh/model.py +111 -0
- phyloom-0.0.0/src/phyloom/simulator/coalescent/__init__.py +4 -0
- phyloom-0.0.0/src/phyloom/simulator/coalescent/events.py +34 -0
- phyloom-0.0.0/src/phyloom/simulator/coalescent/model.py +82 -0
- phyloom-0.0.0/src/phyloom/simulator/core.py +164 -0
- phyloom-0.0.0/src/phyloom/simulator/epidemic/__init__.py +4 -0
- phyloom-0.0.0/src/phyloom/simulator/epidemic/events.py +69 -0
- phyloom-0.0.0/src/phyloom/simulator/epidemic/model.py +120 -0
- phyloom-0.0.0/src/phyloom/simulator/errors.py +10 -0
- phyloom-0.0.0/src/phyloom/simulator/hazards/__init__.py +15 -0
- phyloom-0.0.0/src/phyloom/simulator/hazards/constant.py +11 -0
- phyloom-0.0.0/src/phyloom/simulator/hazards/delta.py +6 -0
- phyloom-0.0.0/src/phyloom/simulator/hazards/exponential.py +28 -0
- phyloom-0.0.0/src/phyloom/simulator/hazards/hazard.py +15 -0
- phyloom-0.0.0/src/phyloom/simulator/hazards/numeric.py +57 -0
- phyloom-0.0.0/src/phyloom/simulator/hazards/skyline.py +55 -0
- phyloom-0.0.0/src/phyloom/simulator/net/__init__.py +4 -0
- phyloom-0.0.0/src/phyloom/simulator/net/events.py +50 -0
- phyloom-0.0.0/src/phyloom/simulator/net/model.py +157 -0
- phyloom-0.0.0/src/phyloom/simulator/utils.py +34 -0
- phyloom-0.0.0/src/phyloom/types.py +29 -0
- phyloom-0.0.0/src/phyloom/utils/__init__.py +6 -0
- phyloom-0.0.0/src/phyloom/utils/indexed_set.py +53 -0
- phyloom-0.0.0/src/phyloom/utils/priority_buckets.py +47 -0
- phyloom-0.0.0/src/phyloom/utils/registry.py +83 -0
- phyloom-0.0.0/src/phyloom/utils/set_view.py +23 -0
- phyloom-0.0.0/src/phyloom.egg-info/PKG-INFO +15 -0
- phyloom-0.0.0/src/phyloom.egg-info/SOURCES.txt +72 -0
- phyloom-0.0.0/src/phyloom.egg-info/dependency_links.txt +1 -0
- phyloom-0.0.0/src/phyloom.egg-info/entry_points.txt +7 -0
- phyloom-0.0.0/src/phyloom.egg-info/requires.txt +7 -0
- 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
|
phyloom-0.0.0/README.md
ADDED
|
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"
|
phyloom-0.0.0/setup.cfg
ADDED
|
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
|
+
)
|