phyloom 0.0.0__py3-none-any.whl
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/__init__.py +0 -0
- phyloom/configs/__init__.py +15 -0
- phyloom/configs/events.py +49 -0
- phyloom/configs/generator.py +143 -0
- phyloom/configs/hazards.py +88 -0
- phyloom/configs/models/__init__.py +0 -0
- phyloom/configs/models/bdsh.py +141 -0
- phyloom/configs/models/coalescent.py +62 -0
- phyloom/configs/models/epidemic.py +102 -0
- phyloom/configs/roots/__init__.py +32 -0
- phyloom/configs/roots/context.py +39 -0
- phyloom/configs/roots/float.py +28 -0
- phyloom/configs/roots/int.py +19 -0
- phyloom/configs/roots/source.py +111 -0
- phyloom/configs/roots/str.py +25 -0
- phyloom/configs/roots/typeguards.py +43 -0
- phyloom/configs/roots/utils.py +38 -0
- phyloom/core/__init__.py +8 -0
- phyloom/core/_algo/__init__.py +5 -0
- phyloom/core/_algo/iter.py +38 -0
- phyloom/core/_algo/pprint.py +62 -0
- phyloom/core/_algo/utils.py +15 -0
- phyloom/core/edge.py +32 -0
- phyloom/core/msa.py +81 -0
- phyloom/core/net.py +127 -0
- phyloom/core/node.py +25 -0
- phyloom/core/sequence.py +40 -0
- phyloom/core/tree.py +78 -0
- phyloom/io/__init__.py +11 -0
- phyloom/io/_utils.py +15 -0
- phyloom/io/fasta.py +72 -0
- phyloom/io/newick.py +221 -0
- phyloom/main.py +85 -0
- phyloom/py.typed +0 -0
- phyloom/simulator/__init__.py +17 -0
- phyloom/simulator/bdsh/__init__.py +12 -0
- phyloom/simulator/bdsh/events.py +115 -0
- phyloom/simulator/bdsh/model.py +111 -0
- phyloom/simulator/coalescent/__init__.py +4 -0
- phyloom/simulator/coalescent/events.py +34 -0
- phyloom/simulator/coalescent/model.py +82 -0
- phyloom/simulator/core.py +164 -0
- phyloom/simulator/epidemic/__init__.py +4 -0
- phyloom/simulator/epidemic/events.py +69 -0
- phyloom/simulator/epidemic/model.py +120 -0
- phyloom/simulator/errors.py +10 -0
- phyloom/simulator/hazards/__init__.py +15 -0
- phyloom/simulator/hazards/constant.py +11 -0
- phyloom/simulator/hazards/delta.py +6 -0
- phyloom/simulator/hazards/exponential.py +28 -0
- phyloom/simulator/hazards/hazard.py +15 -0
- phyloom/simulator/hazards/numeric.py +57 -0
- phyloom/simulator/hazards/skyline.py +55 -0
- phyloom/simulator/net/__init__.py +4 -0
- phyloom/simulator/net/events.py +50 -0
- phyloom/simulator/net/model.py +157 -0
- phyloom/simulator/utils.py +34 -0
- phyloom/types.py +29 -0
- phyloom/utils/__init__.py +6 -0
- phyloom/utils/indexed_set.py +53 -0
- phyloom/utils/priority_buckets.py +47 -0
- phyloom/utils/registry.py +83 -0
- phyloom/utils/set_view.py +23 -0
- phyloom-0.0.0.dist-info/METADATA +15 -0
- phyloom-0.0.0.dist-info/RECORD +69 -0
- phyloom-0.0.0.dist-info/WHEEL +5 -0
- phyloom-0.0.0.dist-info/entry_points.txt +7 -0
- phyloom-0.0.0.dist-info/licenses/LICENSE.txt +22 -0
- phyloom-0.0.0.dist-info/top_level.txt +1 -0
phyloom/__init__.py
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
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
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, contextualize_optional
|
|
8
|
+
from phyloom.simulator.epidemic import (
|
|
9
|
+
EpidemicModel,
|
|
10
|
+
Flow,
|
|
11
|
+
Infection,
|
|
12
|
+
Migration,
|
|
13
|
+
Recovery,
|
|
14
|
+
)
|
|
15
|
+
from phyloom.utils import Registry
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from typing import TypeAlias
|
|
19
|
+
|
|
20
|
+
from phyloom.types import Context
|
|
21
|
+
|
|
22
|
+
EPIDEMIC_EVENT_RULE_REGISTRY: Registry[EventRuleCfgBase[EpidemicModel]] = Registry(
|
|
23
|
+
"EpidemicEventRule", EventRuleCfgBase
|
|
24
|
+
)
|
|
25
|
+
EpidemicRuleCfg: TypeAlias = Annotated[
|
|
26
|
+
EventRuleCfgBase[EpidemicModel], EPIDEMIC_EVENT_RULE_REGISTRY.validator
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@EPIDEMIC_EVENT_RULE_REGISTRY.register("infection")
|
|
31
|
+
class InfectionCfg(EventRuleCfgBase[EpidemicModel]):
|
|
32
|
+
recipient_pool: StrCfg
|
|
33
|
+
source_state: StrCfg | None = None
|
|
34
|
+
child_states: tuple[StrCfg | None, StrCfg | None] = (None, None)
|
|
35
|
+
|
|
36
|
+
def contextualize(self, context: Context) -> Infection:
|
|
37
|
+
child_state1, child_state2 = self.child_states
|
|
38
|
+
return Infection(
|
|
39
|
+
recipient_pool=self.recipient_pool.contextualize(context),
|
|
40
|
+
source_state=contextualize_optional(self.source_state, context),
|
|
41
|
+
child_states=(
|
|
42
|
+
contextualize_optional(child_state1, context),
|
|
43
|
+
contextualize_optional(child_state2, context),
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@EPIDEMIC_EVENT_RULE_REGISTRY.register("recovery")
|
|
49
|
+
class RecoveryCfg(EventRuleCfgBase[EpidemicModel]):
|
|
50
|
+
destination_pool: StrCfg
|
|
51
|
+
source_state: StrCfg | None = None
|
|
52
|
+
sample: bool = False
|
|
53
|
+
|
|
54
|
+
def contextualize(self, context: Context) -> Recovery:
|
|
55
|
+
return Recovery(
|
|
56
|
+
destination_pool=self.destination_pool.contextualize(context),
|
|
57
|
+
source_state=contextualize_optional(self.source_state, context),
|
|
58
|
+
sample=self.sample,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@EPIDEMIC_EVENT_RULE_REGISTRY.register("migration")
|
|
63
|
+
class MigrationCfg(EventRuleCfgBase[EpidemicModel]):
|
|
64
|
+
target_state: StrCfg
|
|
65
|
+
source_state: StrCfg | None = None
|
|
66
|
+
|
|
67
|
+
def contextualize(self, context: Context) -> Migration:
|
|
68
|
+
return Migration(
|
|
69
|
+
target_state=self.target_state.contextualize(context),
|
|
70
|
+
source_state=contextualize_optional(self.source_state, context),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@EPIDEMIC_EVENT_RULE_REGISTRY.register("flow")
|
|
75
|
+
class FlowCfg(EventRuleCfgBase[EpidemicModel]):
|
|
76
|
+
source_pool: StrCfg
|
|
77
|
+
destination_pool: StrCfg
|
|
78
|
+
|
|
79
|
+
def contextualize(self, context: Context) -> Flow:
|
|
80
|
+
return Flow(
|
|
81
|
+
source_pool=self.source_pool.contextualize(context),
|
|
82
|
+
destination_pool=self.destination_pool.contextualize(context),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@GENERATOR_REGISTRY.register("epidemic")
|
|
87
|
+
class EpidemicGenerator(GeneratorBase):
|
|
88
|
+
init_state: StrCfg
|
|
89
|
+
init_pools: dict[str, IntCfg]
|
|
90
|
+
events: tuple[EventCfg[EpidemicRuleCfg], ...] = ()
|
|
91
|
+
schedule: tuple[ScheduledEventCfg, ...] = ()
|
|
92
|
+
|
|
93
|
+
def contextualize(self, context: Context) -> EpidemicModel:
|
|
94
|
+
return EpidemicModel(
|
|
95
|
+
init_state=self.init_state.contextualize(context),
|
|
96
|
+
init_pools={
|
|
97
|
+
pool: count.contextualize(context)
|
|
98
|
+
for pool, count in self.init_pools.items()
|
|
99
|
+
},
|
|
100
|
+
events=[e.contextualize(context) for e in self.events],
|
|
101
|
+
schedule=[e.contextualize(context) for e in self.schedule],
|
|
102
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from phyloom.configs.roots.context import ContextCfg, DistributionCfg
|
|
2
|
+
from phyloom.configs.roots.float import FloatCfg, FloatListCfg
|
|
3
|
+
from phyloom.configs.roots.int import IntCfg
|
|
4
|
+
from phyloom.configs.roots.source import (
|
|
5
|
+
ModelActionCfg,
|
|
6
|
+
ModelExpressionCfg,
|
|
7
|
+
ModelPredicateCfg,
|
|
8
|
+
NumPyExpressionCfg,
|
|
9
|
+
OutputHookCfg,
|
|
10
|
+
SourceCfg,
|
|
11
|
+
TimeFnCfg,
|
|
12
|
+
)
|
|
13
|
+
from phyloom.configs.roots.str import StrCfg, StrPairCfg
|
|
14
|
+
from phyloom.configs.roots.utils import contextualize_optional
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ContextCfg",
|
|
18
|
+
"DistributionCfg",
|
|
19
|
+
"FloatCfg",
|
|
20
|
+
"FloatListCfg",
|
|
21
|
+
"IntCfg",
|
|
22
|
+
"ModelActionCfg",
|
|
23
|
+
"ModelExpressionCfg",
|
|
24
|
+
"ModelPredicateCfg",
|
|
25
|
+
"NumPyExpressionCfg",
|
|
26
|
+
"OutputHookCfg",
|
|
27
|
+
"SourceCfg",
|
|
28
|
+
"StrCfg",
|
|
29
|
+
"StrPairCfg",
|
|
30
|
+
"TimeFnCfg",
|
|
31
|
+
"contextualize_optional",
|
|
32
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, RootModel
|
|
6
|
+
|
|
7
|
+
from phyloom.configs.roots.source import NumPyExpressionCfg
|
|
8
|
+
from phyloom.configs.roots.str import StrCfg
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from numpy.random import Generator
|
|
12
|
+
|
|
13
|
+
from phyloom.types import Context
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DistributionCfg(BaseModel):
|
|
17
|
+
model_config = ConfigDict(extra="allow")
|
|
18
|
+
type: StrCfg
|
|
19
|
+
__pydantic_extra__: dict[str, NumPyExpressionCfg] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
|
|
20
|
+
|
|
21
|
+
def draw(self, context: Context, rng: Generator) -> object:
|
|
22
|
+
args = {
|
|
23
|
+
name: value.contextualize(context)
|
|
24
|
+
for name, value in self.__pydantic_extra__.items()
|
|
25
|
+
}
|
|
26
|
+
return getattr(rng, self.type.contextualize(context))(**args)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ContextCfg(RootModel[dict[str, DistributionCfg | NumPyExpressionCfg]]):
|
|
30
|
+
root: dict[str, DistributionCfg | NumPyExpressionCfg] = {}
|
|
31
|
+
|
|
32
|
+
def materialize(self, rng: Generator) -> Context:
|
|
33
|
+
values: Context = {}
|
|
34
|
+
for key, value in self.root.items():
|
|
35
|
+
if isinstance(value, NumPyExpressionCfg):
|
|
36
|
+
values[key] = value.contextualize(values)
|
|
37
|
+
else:
|
|
38
|
+
values[key] = value.draw(values, rng)
|
|
39
|
+
return values
|