phylogenie 1.0.3__py3-none-any.whl → 1.0.5__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.
@@ -25,9 +25,6 @@ class SkylineVectorValueModel(StrictBaseModel):
25
25
  SkylineVectorCoercibleConfig = (
26
26
  str | pgt.Scalar | list[SkylineParameterLikeConfig] | SkylineVectorValueModel
27
27
  )
28
- SkylineVectorLikeConfig = (
29
- str | list[SkylineParameterLikeConfig] | SkylineVectorValueModel
30
- )
31
28
 
32
29
 
33
30
  class SkylineMatrixValueModel(StrictBaseModel):
@@ -36,5 +33,5 @@ class SkylineMatrixValueModel(StrictBaseModel):
36
33
 
37
34
 
38
35
  SkylineMatrixCoercibleConfig = (
39
- str | pgt.Scalar | list[SkylineVectorLikeConfig] | SkylineMatrixValueModel
36
+ str | pgt.Scalar | list[SkylineVectorCoercibleConfig] | SkylineMatrixValueModel
40
37
  )
@@ -1,7 +1,3 @@
1
- from typing import Annotated, Literal
2
-
3
- from pydantic import Field
4
-
5
1
  from phylogenie.configs import StrictBaseModel
6
2
  from phylogenie.core.context.distributions import (
7
3
  DistributionConfig,
@@ -11,28 +7,22 @@ from phylogenie.core.context.distributions import (
11
7
 
12
8
  class VectorModel(StrictBaseModel):
13
9
  x: ScalarDistributionConfig
14
- N: int
15
10
 
16
11
 
17
12
  class Vector1DModel(VectorModel):
18
- n_dim: Literal[1] = 1
13
+ size: int
19
14
 
20
15
 
21
16
  class Vector2DModel(VectorModel):
22
- n_dim: Literal[2] = 2
17
+ size: tuple[int, int]
23
18
  zero_diagonal: bool = False
24
19
 
25
20
 
26
21
  class Vector3DModel(VectorModel):
27
- n_dim: Literal[3] = 3
28
- T: int
22
+ size: tuple[int, int, int]
29
23
  zero_diagonal: bool = False
30
24
 
31
25
 
32
26
  ContextConfig = dict[
33
- str,
34
- DistributionConfig
35
- | Annotated[
36
- Vector1DModel | Vector2DModel | Vector3DModel, Field(discriminator="n_dim")
37
- ],
27
+ str, DistributionConfig | Vector1DModel | Vector2DModel | Vector3DModel
38
28
  ]
@@ -77,7 +77,7 @@ class Exponential(Scalar):
77
77
  scale: float
78
78
 
79
79
  def _sample(self, rng: Generator) -> float:
80
- return rng.exponential(scale=self.scale)
80
+ return rng.exponential(self.scale)
81
81
 
82
82
 
83
83
  class Gamma(Scalar):
@@ -86,7 +86,7 @@ class Gamma(Scalar):
86
86
  shape: float
87
87
 
88
88
  def _sample(self, rng: Generator) -> float:
89
- return rng.gamma(shape=self.shape, scale=self.scale)
89
+ return rng.gamma(self.shape, self.scale)
90
90
 
91
91
 
92
92
  class Beta(Scalar):
@@ -95,7 +95,7 @@ class Beta(Scalar):
95
95
  beta: float
96
96
 
97
97
  def _sample(self, rng: Generator) -> float:
98
- return rng.beta(a=self.alpha, b=self.beta)
98
+ return rng.beta(self.alpha, self.beta)
99
99
 
100
100
 
101
101
  class IntUniform(Scalar):
@@ -10,19 +10,34 @@ def _sample_vector1D(x: distributions.Scalar, N: int, rng: Generator) -> pgt.Vec
10
10
 
11
11
 
12
12
  def _sample_vector2D(
13
- x: distributions.Scalar, N: int, zero_diagonal: bool, rng: Generator
13
+ x: distributions.Scalar,
14
+ size: tuple[int, int],
15
+ zero_diagonal: bool,
16
+ rng: Generator,
14
17
  ) -> pgt.Vector2D:
15
- v = [_sample_vector1D(x, N, rng) for _ in range(N)]
18
+ n_rows, n_cols = size
19
+ v = [_sample_vector1D(x, n_cols, rng) for _ in range(n_rows)]
16
20
  if zero_diagonal:
17
- for i in range(N):
21
+ if n_rows != n_cols:
22
+ raise ValueError(
23
+ f"It is impossible to initialize a non-square matrix with zero the diagonal (got x={x}, size={size} and zero_diagonal=True)"
24
+ )
25
+ for i in range(n_rows):
18
26
  v[i][i] = 0
19
27
  return v
20
28
 
21
29
 
22
30
  def _sample_vector3D(
23
- x: distributions.Scalar, N: int, T: int, zero_diagonal: bool, rng: Generator
31
+ x: distributions.Scalar,
32
+ size: tuple[int, int, int],
33
+ zero_diagonal: bool,
34
+ rng: Generator,
24
35
  ) -> pgt.Vector3D:
25
- return [_sample_vector2D(x, N, zero_diagonal, rng) for _ in range(T)]
36
+ n_matrices, n_rows, n_cols = size
37
+ return [
38
+ _sample_vector2D(x, (n_rows, n_cols), zero_diagonal, rng)
39
+ for _ in range(n_matrices)
40
+ ]
26
41
 
27
42
 
28
43
  def context_factory(x: cfg.ContextConfig, rng: Generator) -> pgt.Data:
@@ -31,11 +46,9 @@ def context_factory(x: cfg.ContextConfig, rng: Generator) -> pgt.Data:
31
46
  if isinstance(value, distributions.Distribution):
32
47
  data[key] = value.sample(rng)
33
48
  elif isinstance(value, cfg.Vector1DModel):
34
- data[key] = _sample_vector1D(value.x, value.N, rng)
49
+ data[key] = _sample_vector1D(value.x, value.size, rng)
35
50
  elif isinstance(value, cfg.Vector2DModel):
36
- data[key] = _sample_vector2D(value.x, value.N, value.zero_diagonal, rng)
51
+ data[key] = _sample_vector2D(value.x, value.size, value.zero_diagonal, rng)
37
52
  else:
38
- data[key] = _sample_vector3D(
39
- value.x, value.N, value.T, value.zero_diagonal, rng
40
- )
53
+ data[key] = _sample_vector3D(value.x, value.size, value.zero_diagonal, rng)
41
54
  return data
@@ -1,4 +1,4 @@
1
- from typing import Any, Literal, overload
1
+ from typing import Any
2
2
 
3
3
  import numpy as np
4
4
 
@@ -13,7 +13,6 @@ from phylogenie.skyline import (
13
13
  SkylineParameterLike,
14
14
  SkylineVector,
15
15
  SkylineVectorCoercible,
16
- SkylineVectorLike,
17
16
  )
18
17
 
19
18
 
@@ -100,17 +99,23 @@ def skyline_parameter_like_factory(
100
99
  )
101
100
 
102
101
 
103
- @overload
104
- def _parse_skyline_vector_value_model(
105
- x: cfg.SkylineVectorValueModel, data: pgt.Data, coercible: Literal[True]
106
- ) -> SkylineVectorCoercible: ...
107
- @overload
108
- def _parse_skyline_vector_value_model(
109
- x: cfg.SkylineVectorValueModel, data: pgt.Data, coercible: Literal[False]
110
- ) -> SkylineVectorLike: ...
111
- def _parse_skyline_vector_value_model(
112
- x: cfg.SkylineVectorValueModel, data: pgt.Data, coercible: bool
102
+ def skyline_vector_coercible_factory(
103
+ x: cfg.SkylineVectorCoercibleConfig, data: pgt.Data
113
104
  ) -> SkylineVectorCoercible:
105
+ if isinstance(x, str):
106
+ e = _eval_expression(x, data)
107
+ if tg.is_one_or_many_scalars(e):
108
+ return e
109
+ raise ValueError(
110
+ f"Expression '{x}' evaluated to {e} of type {type(e)}, expected a SkylineVectorCoercible object (e.g., a scalar or a sequence of them)."
111
+ )
112
+ if isinstance(x, pgt.Scalar):
113
+ return x
114
+ if ctg.is_list_of_skyline_parameter_like_configs(x):
115
+ return [skyline_parameter_like_factory(p, data) for p in x]
116
+
117
+ assert isinstance(x, cfg.SkylineVectorValueModel)
118
+
114
119
  change_times = many_scalars_factory(x.change_times, data)
115
120
  if isinstance(x.value, str):
116
121
  e = _eval_expression(x.value, data)
@@ -124,12 +129,7 @@ def _parse_skyline_vector_value_model(
124
129
  value = [one_or_many_scalars_factory(v, data) for v in x.value]
125
130
 
126
131
  if tg.is_many_scalars(value):
127
- if coercible:
128
- return SkylineParameter(value=value, change_times=change_times)
129
- else:
130
- raise ValueError(
131
- f"Parsing SkylineVector config {x.value} yielded a sequence of scalars {value} when a nested (2D) sequence of scalars was expected."
132
- )
132
+ return SkylineParameter(value=value, change_times=change_times)
133
133
 
134
134
  Ns = {len(elem) for elem in value if tg.is_many(elem)}
135
135
  if len(Ns) > 1:
@@ -139,46 +139,7 @@ def _parse_skyline_vector_value_model(
139
139
  (N,) = Ns
140
140
  value = [[p] * N if isinstance(p, pgt.Scalar) else p for p in value]
141
141
 
142
- return SkylineVector(
143
- value=value,
144
- change_times=change_times,
145
- )
146
-
147
-
148
- def skyline_vector_coercible_factory(
149
- x: cfg.SkylineVectorCoercibleConfig, data: pgt.Data
150
- ) -> SkylineVectorCoercible:
151
- if isinstance(x, str):
152
- e = _eval_expression(x, data)
153
- if tg.is_one_or_many_scalars(e):
154
- return e
155
- raise ValueError(
156
- f"Expression '{x}' evaluated to {e} of type {type(e)}, expected a SkylineVectorCoercible object (e.g., a scalar or a sequence of them)."
157
- )
158
- if isinstance(x, pgt.Scalar):
159
- return x
160
- if ctg.is_list_of_skyline_parameter_like_configs(x):
161
- return [skyline_parameter_like_factory(p, data) for p in x]
162
-
163
- assert isinstance(x, cfg.SkylineVectorValueModel)
164
- return _parse_skyline_vector_value_model(x, data, coercible=True)
165
-
166
-
167
- def skyline_vector_like_factory(
168
- x: cfg.SkylineVectorLikeConfig, data: pgt.Data
169
- ) -> SkylineVectorLike:
170
- if isinstance(x, str):
171
- e = _eval_expression(x, data)
172
- if tg.is_many_scalars(e):
173
- return e
174
- raise ValueError(
175
- f"Expression '{x}' evaluated to {e} of type {type(e)}, expected a SkylineVectorLike object (e.g., a sequence of scalars)."
176
- )
177
- if ctg.is_list_of_skyline_parameter_like_configs(x):
178
- return [skyline_parameter_like_factory(p, data) for p in x]
179
-
180
- assert isinstance(x, cfg.SkylineVectorValueModel)
181
- return _parse_skyline_vector_value_model(x, data, coercible=False)
142
+ return SkylineVector(value=value, change_times=change_times)
182
143
 
183
144
 
184
145
  def one_or_many_2D_scalars_factory(
@@ -208,8 +169,8 @@ def skyline_matrix_coercible_factory(
208
169
  )
209
170
  if isinstance(x, pgt.Scalar):
210
171
  return x
211
- if ctg.is_list_of_skyline_vector_like_configs(x):
212
- return [skyline_vector_like_factory(v, data) for v in x]
172
+ if ctg.is_list_of_skyline_vector_coercible_configs(x):
173
+ return [skyline_vector_coercible_factory(v, data) for v in x]
213
174
 
214
175
  assert isinstance(x, cfg.SkylineMatrixValueModel)
215
176
 
@@ -245,7 +206,4 @@ def skyline_matrix_coercible_factory(
245
206
  (N,) = Ns
246
207
  value = [[[p] * N] * N if isinstance(p, pgt.Scalar) else p for p in value]
247
208
 
248
- return SkylineMatrix(
249
- value=value,
250
- change_times=change_times,
251
- )
209
+ return SkylineMatrix(value=value, change_times=change_times)
@@ -1,7 +1,6 @@
1
1
  import os
2
2
  from abc import abstractmethod
3
3
  from enum import Enum
4
- from pathlib import Path
5
4
  from typing import Literal
6
5
 
7
6
  from numpy.random import Generator
@@ -17,8 +16,7 @@ class BackendType(str, Enum):
17
16
 
18
17
  class MSAsGenerator(DatasetGenerator):
19
18
  data_type: Literal[DataType.MSAS] = DataType.MSAS
20
- trees: str | TreesGeneratorConfig
21
- output_trees_dir: str | None = None
19
+ trees: TreesGeneratorConfig
22
20
 
23
21
  @abstractmethod
24
22
  def _generate_one_from_tree(
@@ -26,26 +24,11 @@ class MSAsGenerator(DatasetGenerator):
26
24
  ) -> None: ...
27
25
 
28
26
  def _generate_one(self, filename: str, rng: Generator, data: pgt.Data) -> None:
29
- if isinstance(self.trees, str):
30
- tree_files = os.listdir(self.trees)
31
- tree_file = os.path.join(self.trees, str(rng.choice(tree_files)))
32
- self._generate_one_from_tree(
33
- filename=filename, tree_file=tree_file, rng=rng, data=data
34
- )
35
- elif isinstance(self.output_trees_dir, str):
36
- os.makedirs(self.output_trees_dir, exist_ok=True)
37
- self._generate_one_from_tree(
38
- filename=filename,
39
- tree_file=os.path.join(self.output_trees_dir, f"{Path(filename).stem}"),
40
- rng=rng,
41
- data=data,
42
- )
43
- else:
44
- tree_filename = f"{filename}.temp-tree"
45
- self.trees.generate_one(
46
- filename=tree_filename, data=data, seed=int(rng.integers(0, 2**32 - 1))
47
- )
48
- self._generate_one_from_tree(
49
- filename=filename, tree_file=f"{tree_filename}.nwk", rng=rng, data=data
50
- )
51
- os.remove(f"{tree_filename}.nwk")
27
+ tree_filename = f"{filename}.temp-tree"
28
+ self.trees.generate_one(
29
+ filename=tree_filename, data=data, seed=int(rng.integers(0, 2**32 - 1))
30
+ )
31
+ self._generate_one_from_tree(
32
+ filename=filename, tree_file=f"{tree_filename}.nwk", rng=rng, data=data
33
+ )
34
+ os.remove(f"{tree_filename}.nwk")
@@ -67,9 +67,6 @@ class ReMASTERGenerator(TreesGenerator):
67
67
  beast_path=self.beast_path,
68
68
  )
69
69
 
70
- def _generate_one(self, filename: str, rng: Generator, data: pgt.Data) -> None:
71
- self._generate_one_from_extra_reactions(filename, rng, data, reactions=[])
72
-
73
70
 
74
71
  class CanonicalReMASTERGenerator(ReMASTERGenerator):
75
72
  parameterization: Literal[ParameterizationType.CANONICAL] = (
@@ -25,7 +25,7 @@ from phylogenie.core.trees.base import BackendType, TreesGenerator
25
25
 
26
26
 
27
27
  class ParameterizationType(str, Enum):
28
- CANONICAL = "canonical"
28
+ MTBD = "MTBD"
29
29
  BD = "BD"
30
30
  BDEI = "BDEI"
31
31
 
@@ -70,10 +70,8 @@ class TreeSimulatorGenerator(TreesGenerator):
70
70
  )
71
71
 
72
72
 
73
- class CanonicalTreeSimulatorGenerator(TreeSimulatorGenerator):
74
- parameterization: Literal[ParameterizationType.CANONICAL] = (
75
- ParameterizationType.CANONICAL
76
- )
73
+ class MTBDTreeSimulatorGenerator(TreeSimulatorGenerator):
74
+ parameterization: Literal[ParameterizationType.MTBD] = ParameterizationType.MTBD
77
75
  populations: str | list[str] = DEFAULT_POPULATION
78
76
  transition_rates: cfg.SkylineMatrixCoercibleConfig = 0
79
77
  transmission_rates: cfg.SkylineMatrixCoercibleConfig = 0
@@ -158,8 +156,6 @@ class BDEITreeSimulatorGenerator(TreeSimulatorGenerator):
158
156
 
159
157
 
160
158
  TreeSimulatorGeneratorConfig = Annotated[
161
- CanonicalTreeSimulatorGenerator
162
- | BDTreeSimulatorGenerator
163
- | BDEITreeSimulatorGenerator,
159
+ MTBDTreeSimulatorGenerator | BDTreeSimulatorGenerator | BDEITreeSimulatorGenerator,
164
160
  Field(discriminator="parameterization"),
165
161
  ]
@@ -1,6 +1,7 @@
1
1
  from typing import TypeGuard
2
2
 
3
3
  import phylogenie.core.configs as cfg
4
+ import phylogenie.typings as pgt
4
5
 
5
6
 
6
7
  def is_list(x: object) -> TypeGuard[list[object]]:
@@ -17,11 +18,15 @@ def is_list_of_skyline_parameter_like_configs(
17
18
  return is_list(x) and all(isinstance(v, cfg.SkylineParameterLikeConfig) for v in x)
18
19
 
19
20
 
20
- def is_list_of_skyline_vector_like_configs(
21
+ def is_skyline_vector_coercible_config(
21
22
  x: object,
22
- ) -> TypeGuard[list[cfg.SkylineVectorLikeConfig]]:
23
- return is_list(x) and all(
24
- isinstance(v, str | cfg.SkylineVectorValueModel)
25
- or is_list_of_skyline_parameter_like_configs(v)
26
- for v in x
27
- )
23
+ ) -> TypeGuard[cfg.SkylineVectorCoercibleConfig]:
24
+ return isinstance(
25
+ x, str | pgt.Scalar | cfg.SkylineVectorValueModel
26
+ ) or is_list_of_skyline_parameter_like_configs(x)
27
+
28
+
29
+ def is_list_of_skyline_vector_coercible_configs(
30
+ x: object,
31
+ ) -> TypeGuard[list[cfg.SkylineVectorCoercibleConfig]]:
32
+ return is_list(x) and all(is_skyline_vector_coercible_config(v) for v in x)
@@ -1,28 +1,24 @@
1
1
  from collections.abc import Callable, Iterator
2
- from typing import TypeGuard, Union
2
+ from typing import TypeGuard, Union, overload
3
3
 
4
4
  import phylogenie.typeguards as tg
5
5
  import phylogenie.typings as pgt
6
- from phylogenie.skyline.parameter import (
7
- SkylineParameter,
8
- SkylineParameterLike,
9
- is_skyline_parameter_like,
10
- skyline_parameter,
11
- )
6
+ from phylogenie.skyline.parameter import SkylineParameter, is_skyline_parameter_like
12
7
  from phylogenie.skyline.vector import (
13
8
  SkylineVector,
9
+ SkylineVectorCoercible,
14
10
  SkylineVectorLike,
15
11
  SkylineVectorOperand,
12
+ is_many_skyline_vectors_coercible,
16
13
  is_many_skyline_vectors_like,
14
+ is_skyline_vector_coercible,
17
15
  is_skyline_vector_like,
18
16
  is_skyline_vector_operand,
19
17
  skyline_vector,
20
18
  )
21
19
 
22
20
  SkylineMatrixOperand = Union[SkylineVectorOperand, "SkylineMatrix"]
23
- SkylineMatrixCoercible = Union[
24
- SkylineParameterLike, pgt.Many[SkylineVectorLike], "SkylineMatrix"
25
- ]
21
+ SkylineMatrixCoercible = Union[pgt.OneOrMany[SkylineVectorCoercible], "SkylineMatrix"]
26
22
 
27
23
 
28
24
  def is_skyline_matrix_operand(x: object) -> TypeGuard[SkylineMatrixOperand]:
@@ -43,7 +39,7 @@ class SkylineMatrix:
43
39
  raise TypeError(
44
40
  f"It is impossible to create a SkylineMatrix from `params` {params} of type {type(params)}. Please provide a sequence composed of SkylineVectorLike objects (a SkylineVectorLike object can either be a SkylineVector or a sequence of scalars and/or SkylineParameters)."
45
41
  )
46
- elif value is not None and change_times is not None:
42
+ elif params is None and value is not None and change_times is not None:
47
43
  if tg.is_many_3D_scalars(value):
48
44
  matrix_lengths = {len(matrix) for matrix in value}
49
45
  if any(ml != len(value[0]) for ml in matrix_lengths):
@@ -80,45 +76,49 @@ class SkylineMatrix:
80
76
  def get_value_at_time(self, time: pgt.Scalar) -> pgt.Vector2D:
81
77
  return [param.get_value_at_time(time) for param in self.params]
82
78
 
83
- def operate(
79
+ def _operate(
84
80
  self,
85
81
  other: SkylineMatrixOperand,
86
82
  func: Callable[
87
83
  [SkylineVector, SkylineVector | SkylineParameter], SkylineVector
88
84
  ],
89
85
  ) -> "SkylineMatrix":
90
- if not is_skyline_matrix_operand(other):
91
- return NotImplemented
92
86
  if is_skyline_vector_operand(other):
93
87
  other = skyline_vector(other, N=self.N)
94
- assert isinstance(other, SkylineVector | SkylineMatrix)
88
+ elif isinstance(other, SkylineMatrix):
89
+ if other.N != self.N:
90
+ raise ValueError(
91
+ f"Expected a SkylineMatrix with the same size as self (N={self.N}), but got {other} with N={other.N}."
92
+ )
93
+ else:
94
+ return NotImplemented
95
95
  return SkylineMatrix(
96
96
  [func(p1, p2) for p1, p2 in zip(self.params, other.params)]
97
97
  )
98
98
 
99
99
  def __add__(self, operand: SkylineMatrixOperand) -> "SkylineMatrix":
100
- return self.operate(operand, lambda x, y: x + y)
100
+ return self._operate(operand, lambda x, y: x + y)
101
101
 
102
102
  def __radd__(self, operand: SkylineVectorOperand) -> "SkylineMatrix":
103
- return self.operate(operand, lambda x, y: y + x)
103
+ return self._operate(operand, lambda x, y: y + x)
104
104
 
105
105
  def __sub__(self, operand: SkylineMatrixOperand) -> "SkylineMatrix":
106
- return self.operate(operand, lambda x, y: x - y)
106
+ return self._operate(operand, lambda x, y: x - y)
107
107
 
108
108
  def __rsub__(self, operand: SkylineVectorOperand) -> "SkylineMatrix":
109
- return self.operate(operand, lambda x, y: y - x)
109
+ return self._operate(operand, lambda x, y: y - x)
110
110
 
111
111
  def __mul__(self, operand: SkylineMatrixOperand) -> "SkylineMatrix":
112
- return self.operate(operand, lambda x, y: x * y)
112
+ return self._operate(operand, lambda x, y: x * y)
113
113
 
114
114
  def __rmul__(self, operand: SkylineVectorOperand) -> "SkylineMatrix":
115
- return self.operate(operand, lambda x, y: y * x)
115
+ return self._operate(operand, lambda x, y: y * x)
116
116
 
117
117
  def __truediv__(self, operand: SkylineMatrixOperand) -> "SkylineMatrix":
118
- return self.operate(operand, lambda x, y: x / y)
118
+ return self._operate(operand, lambda x, y: x / y)
119
119
 
120
120
  def __rtruediv__(self, operand: SkylineVectorOperand) -> "SkylineMatrix":
121
- return self.operate(operand, lambda x, y: y / x)
121
+ return self._operate(operand, lambda x, y: y / x)
122
122
 
123
123
  @property
124
124
  def T(self) -> "SkylineMatrix":
@@ -139,7 +139,13 @@ class SkylineMatrix:
139
139
  def __len__(self) -> int:
140
140
  return self.N
141
141
 
142
- def __getitem__(self, item: int) -> "SkylineVector":
142
+ @overload
143
+ def __getitem__(self, item: int) -> SkylineVector: ...
144
+ @overload
145
+ def __getitem__(self, item: slice) -> list[SkylineVector]: ...
146
+ def __getitem__(
147
+ self, item: int | slice
148
+ ) -> Union[SkylineVector, list[SkylineVector]]:
143
149
  return self.params[item]
144
150
 
145
151
  def __setitem__(self, item: int, value: SkylineVectorLike) -> None:
@@ -153,20 +159,36 @@ class SkylineMatrix:
153
159
  def skyline_matrix(
154
160
  x: SkylineMatrixCoercible, N: int, zero_diagonal: bool = False
155
161
  ) -> SkylineMatrix:
156
- if is_skyline_parameter_like(x):
157
- x = SkylineMatrix([[skyline_parameter(x)] * N] * N)
162
+ if N <= 0:
163
+ raise ValueError(
164
+ f"N must be a positive integer for SkylineMatrix construction (got N={N})."
165
+ )
166
+ if is_skyline_vector_coercible(x):
167
+ x = SkylineMatrix([[p] * N for p in skyline_vector(x, N)])
158
168
  if zero_diagonal:
159
169
  for i in range(N):
160
170
  x[i][i] = 0
161
171
  return x
162
- elif is_many_skyline_vectors_like(x):
163
- x = SkylineMatrix(x)
172
+ elif is_many_skyline_vectors_coercible(x):
173
+ x = SkylineMatrix(
174
+ [
175
+ [
176
+ (
177
+ 0
178
+ if i == j and is_skyline_parameter_like(v) and zero_diagonal
179
+ else p
180
+ )
181
+ for j, p in enumerate(skyline_vector(v, N))
182
+ ]
183
+ for i, v in enumerate(x)
184
+ ]
185
+ )
164
186
  if not isinstance(x, SkylineMatrix):
165
187
  raise TypeError(
166
188
  f"It is impossible to coerce {x} of type {type(x)} into a SkylineMatrix. Please provide either:\n"
167
189
  "- a SkylineMatrix,\n"
168
- "- a SkylineParameterLike object (i.e., a scalar or a SkylineParameter),\n"
169
- "- a sequence of SkylineVectorLike objects (a SkylineVectorLike object can be a SkylineVector or a sequence of SkylineParameterLike objects)."
190
+ "- a SkylineVectorCoercible object (i.e., a scalar, a SkylineParameter, a SkylineVector, or a sequence of scalars and/or SkylineParameters),\n"
191
+ "- a sequence of SkylineVectorCoercible objects."
170
192
  )
171
193
 
172
194
  if x.N != N:
@@ -23,7 +23,7 @@ class SkylineParameter:
23
23
  self,
24
24
  value: pgt.OneOrManyScalars,
25
25
  change_times: pgt.ManyScalars | None = None,
26
- ) -> None:
26
+ ):
27
27
  if isinstance(value, pgt.Scalar):
28
28
  value = [value]
29
29
  elif not tg.is_many_scalars(value):
@@ -42,23 +42,37 @@ class SkylineParameter:
42
42
  raise ValueError(
43
43
  f"`value` must have exactly one more element than `change_times` (got value={value} of length {len(value)} and change_times={change_times} of length {len(change_times)})."
44
44
  )
45
+ if any(t1 >= t2 for t1, t2 in zip(change_times, change_times[1:])):
46
+ raise ValueError(
47
+ f"`change_times` must be sorted in strictly increasing order "
48
+ f"(got change_times={change_times})."
49
+ )
50
+ if any(t < 0 for t in change_times):
51
+ raise ValueError(
52
+ f"`change_times` must be non-negative (got change_times={change_times})."
53
+ )
45
54
 
46
55
  self.value = [value[0]]
47
56
  self.change_times: list[pgt.Scalar] = []
48
57
  for i in range(1, len(value)):
49
58
  if value[i] != value[i - 1]:
50
59
  self.value.append(value[i])
51
- self.value.append(change_times[i - 1])
60
+ self.change_times.append(change_times[i - 1])
52
61
 
53
62
  def get_value_at_time(self, t: pgt.Scalar) -> pgt.Scalar:
63
+ if t < 0:
64
+ raise ValueError(f"Time cannot be negative (got t={t}).")
54
65
  return self.value[bisect_right(self.change_times, t)]
55
66
 
56
- def operate(
67
+ def _operate(
57
68
  self,
58
69
  other: SkylineParameterLike,
59
70
  f: Callable[[pgt.Scalar, pgt.Scalar], pgt.Scalar],
60
71
  ) -> "SkylineParameter":
61
- other = skyline_parameter(other)
72
+ if is_skyline_parameter_like(other):
73
+ other = skyline_parameter(other)
74
+ else:
75
+ return NotImplemented
62
76
  change_times = sorted(set(self.change_times + other.change_times))
63
77
  value = [
64
78
  f(self.get_value_at_time(t), other.get_value_at_time(t))
@@ -67,28 +81,28 @@ class SkylineParameter:
67
81
  return SkylineParameter(value, change_times)
68
82
 
69
83
  def __add__(self, other: SkylineParameterLike) -> "SkylineParameter":
70
- return self.operate(other, lambda x, y: x + y)
84
+ return self._operate(other, lambda x, y: x + y)
71
85
 
72
86
  def __radd__(self, other: pgt.Scalar) -> "SkylineParameter":
73
- return self.operate(other, lambda x, y: y + x)
87
+ return self._operate(other, lambda x, y: y + x)
74
88
 
75
89
  def __sub__(self, other: SkylineParameterLike) -> "SkylineParameter":
76
- return self.operate(other, lambda x, y: x - y)
90
+ return self._operate(other, lambda x, y: x - y)
77
91
 
78
92
  def __rsub__(self, other: pgt.Scalar) -> "SkylineParameter":
79
- return self.operate(other, lambda x, y: y - x)
93
+ return self._operate(other, lambda x, y: y - x)
80
94
 
81
95
  def __mul__(self, other: SkylineParameterLike) -> "SkylineParameter":
82
- return self.operate(other, lambda x, y: x * y)
96
+ return self._operate(other, lambda x, y: x * y)
83
97
 
84
98
  def __rmul__(self, other: pgt.Scalar) -> "SkylineParameter":
85
- return self.operate(other, lambda x, y: y * x)
99
+ return self._operate(other, lambda x, y: y * x)
86
100
 
87
101
  def __truediv__(self, other: SkylineParameterLike) -> "SkylineParameter":
88
- return self.operate(other, lambda x, y: x / y)
102
+ return self._operate(other, lambda x, y: x / y)
89
103
 
90
104
  def __rtruediv__(self, other: pgt.Scalar) -> "SkylineParameter":
91
- return self.operate(other, lambda x, y: y / x)
105
+ return self._operate(other, lambda x, y: y / x)
92
106
 
93
107
  def __bool__(self) -> bool:
94
108
  return any(self.value)
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Callable, Iterator
2
- from typing import TypeGuard, Union
2
+ from typing import TypeGuard, Union, overload
3
3
 
4
4
  import phylogenie.typeguards as tg
5
5
  import phylogenie.typings as pgt
@@ -24,17 +24,29 @@ def is_skyline_vector_like(x: object) -> TypeGuard[SkylineVectorLike]:
24
24
  return isinstance(x, SkylineVector) or is_many_skyline_parameters_like(x)
25
25
 
26
26
 
27
+ def is_skyline_vector_coercible(
28
+ x: object,
29
+ ) -> TypeGuard[SkylineVectorCoercible]:
30
+ return is_skyline_parameter_like(x) or is_skyline_vector_like(x)
31
+
32
+
27
33
  def is_many_skyline_vectors_like(x: object) -> TypeGuard[pgt.Many[SkylineVectorLike]]:
28
34
  return tg.is_many(x) and all(is_skyline_vector_like(v) for v in x)
29
35
 
30
36
 
37
+ def is_many_skyline_vectors_coercible(
38
+ x: object,
39
+ ) -> TypeGuard[pgt.Many[SkylineVectorCoercible]]:
40
+ return tg.is_many(x) and all(is_skyline_vector_coercible(v) for v in x)
41
+
42
+
31
43
  class SkylineVector:
32
44
  def __init__(
33
45
  self,
34
46
  params: pgt.Many[SkylineParameterLike] | None = None,
35
47
  value: pgt.Many2DScalars | None = None,
36
48
  change_times: pgt.ManyScalars | None = None,
37
- ) -> None:
49
+ ):
38
50
  if params is not None and value is None and change_times is None:
39
51
  if is_many_skyline_parameters_like(params):
40
52
  self.params = [skyline_parameter(param) for param in params]
@@ -42,7 +54,7 @@ class SkylineVector:
42
54
  raise TypeError(
43
55
  f"It is impossible to create a SkylineVector from `params` {params} of type {type(params)}. Please provide a sequence of SkylineParameterLike objects (a SkylineParameterLike object can either be a SkylineParameter or a scalar)."
44
56
  )
45
- elif value is not None and change_times is not None:
57
+ elif params is None and value is not None and change_times is not None:
46
58
  if tg.is_many_2D_scalars(value):
47
59
  vector_lengths = {len(vector) for vector in value}
48
60
  if any(vl != len(value[0]) for vl in vector_lengths):
@@ -77,7 +89,7 @@ class SkylineVector:
77
89
  def get_value_at_time(self, t: pgt.Scalar) -> pgt.Vector1D:
78
90
  return [param.get_value_at_time(t) for param in self.params]
79
91
 
80
- def operate(
92
+ def _operate(
81
93
  self,
82
94
  other: SkylineVectorOperand,
83
95
  func: Callable[[SkylineParameter, SkylineParameter], SkylineParameter],
@@ -90,28 +102,28 @@ class SkylineVector:
90
102
  )
91
103
 
92
104
  def __add__(self, operand: SkylineVectorOperand) -> "SkylineVector":
93
- return self.operate(operand, lambda x, y: x + y)
105
+ return self._operate(operand, lambda x, y: x + y)
94
106
 
95
107
  def __radd__(self, operand: SkylineParameterLike) -> "SkylineVector":
96
- return self.operate(operand, lambda x, y: y + x)
108
+ return self._operate(operand, lambda x, y: y + x)
97
109
 
98
110
  def __sub__(self, operand: SkylineVectorOperand) -> "SkylineVector":
99
- return self.operate(operand, lambda x, y: x - y)
111
+ return self._operate(operand, lambda x, y: x - y)
100
112
 
101
113
  def __rsub__(self, operand: SkylineParameterLike) -> "SkylineVector":
102
- return self.operate(operand, lambda x, y: y - x)
114
+ return self._operate(operand, lambda x, y: y - x)
103
115
 
104
116
  def __mul__(self, operand: SkylineVectorOperand) -> "SkylineVector":
105
- return self.operate(operand, lambda x, y: x * y)
117
+ return self._operate(operand, lambda x, y: x * y)
106
118
 
107
119
  def __rmul__(self, operand: SkylineParameterLike) -> "SkylineVector":
108
- return self.operate(operand, lambda x, y: y * x)
120
+ return self._operate(operand, lambda x, y: y * x)
109
121
 
110
122
  def __truediv__(self, operand: SkylineVectorOperand) -> "SkylineVector":
111
- return self.operate(operand, lambda x, y: x / y)
123
+ return self._operate(operand, lambda x, y: x / y)
112
124
 
113
125
  def __rtruediv__(self, operand: SkylineParameterLike) -> "SkylineVector":
114
- return self.operate(operand, lambda x, y: y / x)
126
+ return self._operate(operand, lambda x, y: y / x)
115
127
 
116
128
  def __len__(self) -> int:
117
129
  return self.N
@@ -128,7 +140,13 @@ class SkylineVector:
128
140
  def __iter__(self) -> Iterator[SkylineParameter]:
129
141
  return iter(self.params)
130
142
 
131
- def __getitem__(self, item: int) -> "SkylineParameter":
143
+ @overload
144
+ def __getitem__(self, item: int) -> SkylineParameter: ...
145
+ @overload
146
+ def __getitem__(self, item: slice) -> list[SkylineParameter]: ...
147
+ def __getitem__(
148
+ self, item: int | slice
149
+ ) -> Union[SkylineParameter, list[SkylineParameter]]:
132
150
  return self.params[item]
133
151
 
134
152
  def __setitem__(self, item: int, value: SkylineParameterLike) -> None:
@@ -140,6 +158,10 @@ class SkylineVector:
140
158
 
141
159
 
142
160
  def skyline_vector(x: SkylineVectorCoercible, N: int) -> SkylineVector:
161
+ if N <= 0:
162
+ raise ValueError(
163
+ f"N must be a positive integer for SkylineVector construction (got N={N})."
164
+ )
143
165
  if is_skyline_parameter_like(x):
144
166
  return SkylineVector([skyline_parameter(x)] * N)
145
167
  elif is_many_skyline_parameters_like(x):
phylogenie/typings.py CHANGED
@@ -21,3 +21,4 @@ Vector2D = list[Vector1D]
21
21
  Vector3D = list[Vector2D]
22
22
 
23
23
  Data = dict[str, str | Scalar | Vector1D | Vector2D | Vector3D]
24
+ Size = int | tuple[int, int] | tuple[int, int, int]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phylogenie
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Author: Gabriele Marino
6
6
  Author-email: gabmarino.8601@gmail.com
@@ -6,34 +6,34 @@ phylogenie/backend/remaster/reactions.py,sha256=oc2ZY9WtTajbOWjARDmA0JnS255tbVeM
6
6
  phylogenie/backend/treesimulator.py,sha256=wvN7WZwUKGPWt7AetumQz1ZrUc4k_D6lftSaYG1hNBg,4587
7
7
  phylogenie/configs.py,sha256=HtRUWZ-zNq1--zTBWL3QFXX27Ybw5x1qSWcmx7Sz8YA,125
8
8
  phylogenie/core/__init__.py,sha256=pvQMohKFAPaSvujw7H5sQJn7SOSqENQUHECuVfUBVNg,402
9
- phylogenie/core/configs.py,sha256=QwY4Pxw7Cwx80ySxJMCBFDlJdmactKF_R-lhs8QcsbE,1136
9
+ phylogenie/core/configs.py,sha256=9tUYWrmdDn_Gg6xnywCDcGDEk0gne0vYqFH9dXixJbM,1042
10
10
  phylogenie/core/context/__init__.py,sha256=ZiCweJgf1REKbhZTfHuzz1lIgVmio9bTYW3-srOUqUo,168
11
- phylogenie/core/context/configs.py,sha256=zd-ADFzJbb6KPkol-tXxSdS8LUBeQYQq8fDzXot8WM0,730
12
- phylogenie/core/context/distributions.py,sha256=QF14tM2ibjE7f6WK3s4hTaz_sLQBTNVr2ZBNe2refeE,3059
13
- phylogenie/core/context/factories.py,sha256=QO96wZwrQbgX2Rkd0wY3qQDahiZ8fDg8Mg8KKY0lr2c,1390
11
+ phylogenie/core/context/configs.py,sha256=TEJZykoNLy-77nVWqjrZuoFhJLRggJuFA_GLMPDrUVM,570
12
+ phylogenie/core/context/distributions.py,sha256=zQrUXDqQH_Goz0jGFb1tUMUU0YPOG4rMIilR8DDmQp4,3037
13
+ phylogenie/core/context/factories.py,sha256=I6J3e_ZWdETehiSXH9pCVPBuun6vHpuyC48zjayKMX8,1752
14
14
  phylogenie/core/dataset.py,sha256=mgPAexXTat6x7YB9-BI6d5HWwrAvt8xydmiWVzwVD3M,2431
15
- phylogenie/core/factories.py,sha256=Ph9qlQ6AthVKbzQZvvzj6EQye5LS2nl6d_r2dFuBfEo,8881
15
+ phylogenie/core/factories.py,sha256=DwuocGd48Ham7wD7uyGnGA0tHvXhzuP1Ji0PW5-onwM,7397
16
16
  phylogenie/core/msas/__init__.py,sha256=-2XjTmiTA6zAwiLs2ksKecCrSbNLheo7KKjDyvuLipg,207
17
17
  phylogenie/core/msas/alisim.py,sha256=TG4LAHJaH3rGWa3cwXzX6MgaXuh2tLzhdoALyOkoiXY,1047
18
- phylogenie/core/msas/base.py,sha256=cKH0FGALmObmOLZ2dJ3vrpb3jTLg2oeWJsIE6HznJ0Q,1803
18
+ phylogenie/core/msas/base.py,sha256=OaGYxSmlARWy531g-lkPFgg5CcMCUQw8-SXUQc8mlRs,1042
19
19
  phylogenie/core/trees/__init__.py,sha256=epKgJ-EI04kBEuS4kfBcnsAj7dMObT1T742peBAnB78,335
20
20
  phylogenie/core/trees/base.py,sha256=sNBCJRtWGYaMog4WoyAkrK4F2SXrgjXrxjuVQ6Ae5Js,305
21
21
  phylogenie/core/trees/remaster/__init__.py,sha256=FfgXYjkeosb22Anbp78re2NssWtNcNNaj7hFQZx8JLE,116
22
22
  phylogenie/core/trees/remaster/configs.py,sha256=d4EqowYMb5I2TfBTgNf9H_X1t8aNCYJbh1RQmFoDxs4,362
23
23
  phylogenie/core/trees/remaster/factories.py,sha256=qla4pg4OgfE5lwQZuP3bEaMt7xIF4P6fQ1Z0IPpFxUs,812
24
- phylogenie/core/trees/remaster/generator.py,sha256=og6GfsLyVR88A5Yd1PetiPpy7ZcYiyeGSNyWzqe-vsY,7428
25
- phylogenie/core/trees/treesimulator.py,sha256=rgYuL-A40vPR9uWp-t0Rg2d5TNrwodELYqsb4AKVwAA,5951
26
- phylogenie/core/typeguards.py,sha256=yxTdE4G_1gjBFqplyei-GKz4KZo2KFh8qsEsPQ2Z5OQ,794
24
+ phylogenie/core/trees/remaster/generator.py,sha256=LZTnNHZej6E1QakTBaBwRQQRaeb6rfG61U61bGbgE5Y,7260
25
+ phylogenie/core/trees/treesimulator.py,sha256=cr5Xod_OR6QQ-Lej86aXDYn_6J-tBIusXlqUTiKGSN4,5897
26
+ phylogenie/core/typeguards.py,sha256=wEBYJZZ_Q_bY7ZJSh00AXJeyc1X8ZoysoOiLwo24N1w,990
27
27
  phylogenie/main.py,sha256=n_joau3dWJIq0ZMHe4a_1_2GigTFagkfzUFuQEMlyRI,1158
28
28
  phylogenie/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
29
  phylogenie/skyline/__init__.py,sha256=7pF4CUb4ZCLzNYJNhOjpuTOLTRhlK7L6ugfccNqjIGo,620
30
- phylogenie/skyline/matrix.py,sha256=WH1aT23hv4QYuH4GDwcJgahrqcgB4mulfzKLGUx3R6M,7091
31
- phylogenie/skyline/parameter.py,sha256=wrtvQDho3OC5tzrMnnFTy95GXaYN20KN8P5Gwyf-rHI,4023
32
- phylogenie/skyline/vector.py,sha256=NgmVbWjWG_itZ9a57q9QqSybvG9-Cr9X264RGPVyp_Q,6399
30
+ phylogenie/skyline/matrix.py,sha256=T2g_MtfrqXVVGug40Nka1Q0YrpIi4KKx72Z573J-V18,7999
31
+ phylogenie/skyline/parameter.py,sha256=CJ5OEyRQG2Tg1WJWQ1IpfX-6hjJv80Zj8lMoRke5nnQ,4648
32
+ phylogenie/skyline/vector.py,sha256=Zh6HWoziXQFKDz-XvVE2e_Tw1706NrbwcvBpyPpw_cc,7120
33
33
  phylogenie/typeguards.py,sha256=WBOSJSaOC8VDtrYoA2w_AYEXTpyKdCfmsM29KaKXl3A,1350
34
- phylogenie/typings.py,sha256=93VRedBxrpzXkT4uaNu_1JiMzsOjp7fUy4kLv_eYxUE,565
35
- phylogenie-1.0.3.dist-info/LICENSE.txt,sha256=NUrDqElK-eD3I0WqC004CJsy6cs0JgsAoebDv_42-pw,1071
36
- phylogenie-1.0.3.dist-info/METADATA,sha256=JVGcZNn6O2Wuuek_ZTQp5CBdw5_xoSHzvSyTmENmxNw,6251
37
- phylogenie-1.0.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
38
- phylogenie-1.0.3.dist-info/entry_points.txt,sha256=Rt6_usN0FkBX1ZfiqCirjMN9FKOgFLG8rydcQ8kugeE,51
39
- phylogenie-1.0.3.dist-info/RECORD,,
34
+ phylogenie/typings.py,sha256=cW0WEQEKmjuz_KcRsOfE7aQH5QLM1i55R-2DDWJG8xM,617
35
+ phylogenie-1.0.5.dist-info/LICENSE.txt,sha256=NUrDqElK-eD3I0WqC004CJsy6cs0JgsAoebDv_42-pw,1071
36
+ phylogenie-1.0.5.dist-info/METADATA,sha256=0gil5OBrrH5SUNvF5zyogVe3mZh3KdT5K84Tc1YHi78,6251
37
+ phylogenie-1.0.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
38
+ phylogenie-1.0.5.dist-info/entry_points.txt,sha256=Rt6_usN0FkBX1ZfiqCirjMN9FKOgFLG8rydcQ8kugeE,51
39
+ phylogenie-1.0.5.dist-info/RECORD,,