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.
Files changed (69) hide show
  1. phyloom/__init__.py +0 -0
  2. phyloom/configs/__init__.py +15 -0
  3. phyloom/configs/events.py +49 -0
  4. phyloom/configs/generator.py +143 -0
  5. phyloom/configs/hazards.py +88 -0
  6. phyloom/configs/models/__init__.py +0 -0
  7. phyloom/configs/models/bdsh.py +141 -0
  8. phyloom/configs/models/coalescent.py +62 -0
  9. phyloom/configs/models/epidemic.py +102 -0
  10. phyloom/configs/roots/__init__.py +32 -0
  11. phyloom/configs/roots/context.py +39 -0
  12. phyloom/configs/roots/float.py +28 -0
  13. phyloom/configs/roots/int.py +19 -0
  14. phyloom/configs/roots/source.py +111 -0
  15. phyloom/configs/roots/str.py +25 -0
  16. phyloom/configs/roots/typeguards.py +43 -0
  17. phyloom/configs/roots/utils.py +38 -0
  18. phyloom/core/__init__.py +8 -0
  19. phyloom/core/_algo/__init__.py +5 -0
  20. phyloom/core/_algo/iter.py +38 -0
  21. phyloom/core/_algo/pprint.py +62 -0
  22. phyloom/core/_algo/utils.py +15 -0
  23. phyloom/core/edge.py +32 -0
  24. phyloom/core/msa.py +81 -0
  25. phyloom/core/net.py +127 -0
  26. phyloom/core/node.py +25 -0
  27. phyloom/core/sequence.py +40 -0
  28. phyloom/core/tree.py +78 -0
  29. phyloom/io/__init__.py +11 -0
  30. phyloom/io/_utils.py +15 -0
  31. phyloom/io/fasta.py +72 -0
  32. phyloom/io/newick.py +221 -0
  33. phyloom/main.py +85 -0
  34. phyloom/py.typed +0 -0
  35. phyloom/simulator/__init__.py +17 -0
  36. phyloom/simulator/bdsh/__init__.py +12 -0
  37. phyloom/simulator/bdsh/events.py +115 -0
  38. phyloom/simulator/bdsh/model.py +111 -0
  39. phyloom/simulator/coalescent/__init__.py +4 -0
  40. phyloom/simulator/coalescent/events.py +34 -0
  41. phyloom/simulator/coalescent/model.py +82 -0
  42. phyloom/simulator/core.py +164 -0
  43. phyloom/simulator/epidemic/__init__.py +4 -0
  44. phyloom/simulator/epidemic/events.py +69 -0
  45. phyloom/simulator/epidemic/model.py +120 -0
  46. phyloom/simulator/errors.py +10 -0
  47. phyloom/simulator/hazards/__init__.py +15 -0
  48. phyloom/simulator/hazards/constant.py +11 -0
  49. phyloom/simulator/hazards/delta.py +6 -0
  50. phyloom/simulator/hazards/exponential.py +28 -0
  51. phyloom/simulator/hazards/hazard.py +15 -0
  52. phyloom/simulator/hazards/numeric.py +57 -0
  53. phyloom/simulator/hazards/skyline.py +55 -0
  54. phyloom/simulator/net/__init__.py +4 -0
  55. phyloom/simulator/net/events.py +50 -0
  56. phyloom/simulator/net/model.py +157 -0
  57. phyloom/simulator/utils.py +34 -0
  58. phyloom/types.py +29 -0
  59. phyloom/utils/__init__.py +6 -0
  60. phyloom/utils/indexed_set.py +53 -0
  61. phyloom/utils/priority_buckets.py +47 -0
  62. phyloom/utils/registry.py +83 -0
  63. phyloom/utils/set_view.py +23 -0
  64. phyloom-0.0.0.dist-info/METADATA +15 -0
  65. phyloom-0.0.0.dist-info/RECORD +69 -0
  66. phyloom-0.0.0.dist-info/WHEEL +5 -0
  67. phyloom-0.0.0.dist-info/entry_points.txt +7 -0
  68. phyloom-0.0.0.dist-info/licenses/LICENSE.txt +22 -0
  69. 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