PyDTMC 8.3.0__tar.gz → 8.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. PyDTMC-8.4.0/MANIFEST.in +8 -0
  2. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/PKG-INFO +2 -1
  3. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/PyDTMC.egg-info/PKG-INFO +2 -1
  4. PyDTMC-8.4.0/PyDTMC.egg-info/SOURCES.txt +42 -0
  5. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/README.md +1 -0
  6. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/__init__.py +3 -2
  7. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/custom_types.py +4 -2
  8. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/generators.py +48 -1
  9. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/hidden_markov_model.py +2 -1
  10. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/markov_chain.py +112 -10
  11. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/plotting.py +209 -5
  12. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/validation.py +4 -1
  13. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/setup.py +1 -1
  14. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_generators_instance.py +13 -0
  15. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_generators_static.py +14 -2
  16. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_plotting.py +35 -4
  17. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_properties.py +6 -1
  18. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_validation.py +2 -2
  19. PyDTMC-8.3.0/MANIFEST.in +0 -15
  20. PyDTMC-8.3.0/PyDTMC.egg-info/SOURCES.txt +0 -84
  21. PyDTMC-8.3.0/data/empty +0 -0
  22. PyDTMC-8.3.0/docs/Makefile +0 -12
  23. PyDTMC-8.3.0/docs/build/empty +0 -0
  24. PyDTMC-8.3.0/docs/make.bat +0 -40
  25. PyDTMC-8.3.0/docs/source/_static/css/style.css +0 -64
  26. PyDTMC-8.3.0/docs/source/_templates/empty +0 -0
  27. PyDTMC-8.3.0/docs/source/assessment_functions.rst +0 -10
  28. PyDTMC-8.3.0/docs/source/conf.py +0 -369
  29. PyDTMC-8.3.0/docs/source/custom_exceptions.rst +0 -6
  30. PyDTMC-8.3.0/docs/source/genindex.rst +0 -2
  31. PyDTMC-8.3.0/docs/source/hidden_markov_model.rst +0 -15
  32. PyDTMC-8.3.0/docs/source/hidden_markov_model_methods_instance.rst +0 -19
  33. PyDTMC-8.3.0/docs/source/hidden_markov_model_methods_static.rst +0 -15
  34. PyDTMC-8.3.0/docs/source/hidden_markov_model_properties.rst +0 -17
  35. PyDTMC-8.3.0/docs/source/index.rst +0 -19
  36. PyDTMC-8.3.0/docs/source/markov_chain.rst +0 -15
  37. PyDTMC-8.3.0/docs/source/markov_chain_methods_instance.rst +0 -49
  38. PyDTMC-8.3.0/docs/source/markov_chain_methods_static.rst +0 -22
  39. PyDTMC-8.3.0/docs/source/markov_chain_properties.rst +0 -47
  40. PyDTMC-8.3.0/docs/source/plotting_functions.rst +0 -10
  41. PyDTMC-8.3.0/tests/__init__.py +0 -1
  42. PyDTMC-8.3.0/tests/benchmarks.py +0 -44
  43. PyDTMC-8.3.0/tests/conftest.py +0 -247
  44. PyDTMC-8.3.0/tests/files/file_empty.txt +0 -0
  45. PyDTMC-8.3.0/tests/files/file_filled.txt +0 -0
  46. PyDTMC-8.3.0/tests/files/file_noextension +0 -0
  47. PyDTMC-8.3.0/tests/fixtures/fixtures_aliases.json +0 -96
  48. PyDTMC-8.3.0/tests/fixtures/fixtures_assessments.json +0 -362
  49. PyDTMC-8.3.0/tests/fixtures/fixtures_benchmarks.json +0 -6
  50. PyDTMC-8.3.0/tests/fixtures/fixtures_classification.json +0 -92
  51. PyDTMC-8.3.0/tests/fixtures/fixtures_conversions.json +0 -42
  52. PyDTMC-8.3.0/tests/fixtures/fixtures_fitting.json +0 -160
  53. PyDTMC-8.3.0/tests/fixtures/fixtures_generators_instance.json +0 -428
  54. PyDTMC-8.3.0/tests/fixtures/fixtures_generators_static.json +0 -916
  55. PyDTMC-8.3.0/tests/fixtures/fixtures_hmm.json +0 -1765
  56. PyDTMC-8.3.0/tests/fixtures/fixtures_measures.json +0 -1702
  57. PyDTMC-8.3.0/tests/fixtures/fixtures_plotting.json +0 -36
  58. PyDTMC-8.3.0/tests/fixtures/fixtures_properties.json +0 -812
  59. PyDTMC-8.3.0/tests/fixtures/fixtures_simulations.json +0 -882
  60. PyDTMC-8.3.0/tests/fixtures/fixtures_utilities.json +0 -184
  61. PyDTMC-8.3.0/tests/fixtures/fixtures_validation.json +0 -3006
  62. PyDTMC-8.3.0/tests/utilities.py +0 -78
  63. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/LICENSE.md +0 -0
  64. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/PyDTMC.egg-info/dependency_links.txt +0 -0
  65. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/PyDTMC.egg-info/requires.txt +0 -0
  66. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/PyDTMC.egg-info/top_level.txt +0 -0
  67. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/assessments.py +0 -0
  68. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/base_classes.py +0 -0
  69. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/computations.py +0 -0
  70. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/decorators.py +0 -0
  71. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/exceptions.py +0 -0
  72. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/files_io.py +0 -0
  73. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/fitting.py +0 -0
  74. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/measures.py +0 -0
  75. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/simulations.py +0 -0
  76. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/pydtmc/utilities.py +0 -0
  77. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/requirements.txt +0 -0
  78. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/setup.cfg +0 -0
  79. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_aliases.py +0 -0
  80. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_assessments.py +0 -0
  81. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_classification.py +0 -0
  82. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_conversions.py +0 -0
  83. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_fitting.py +0 -0
  84. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_hmm.py +0 -0
  85. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_measures.py +0 -0
  86. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_simulations.py +0 -0
  87. {PyDTMC-8.3.0 → PyDTMC-8.4.0}/tests/test_utilities.py +0 -0
@@ -0,0 +1,8 @@
1
+ # Inclusions
2
+ graft data docs tests
3
+ include README.md LICENSE.md requirements.txt setup.py
4
+
5
+ # Exclusions
6
+ global-exclude *.dll *.py[cdo] *.so .env .git* __pycache__ *~
7
+ prune .git .github .idea dist docs/build images
8
+ exclude .readthedocs.yaml CODE_OF_CONDUCT.md CONTRIBUTING.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyDTMC
3
- Version: 8.3.0
3
+ Version: 8.4.0
4
4
  Summary: A full-featured and lightweight library for discrete-time Markov chains analysis.
5
5
  Home-page: https://github.com/TommasoBelluzzo/PyDTMC
6
6
  Author: Tommaso Belluzzo
@@ -149,6 +149,7 @@ DISCRETE-TIME MARKOV CHAIN
149
149
  > APERIODIC: YES
150
150
  > IRREDUCIBLE: NO
151
151
  ABSORBING: YES
152
+ MONOTONE: NO
152
153
  REGULAR: NO
153
154
  REVERSIBLE: YES
154
155
  SYMMETRIC: NO
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyDTMC
3
- Version: 8.3.0
3
+ Version: 8.4.0
4
4
  Summary: A full-featured and lightweight library for discrete-time Markov chains analysis.
5
5
  Home-page: https://github.com/TommasoBelluzzo/PyDTMC
6
6
  Author: Tommaso Belluzzo
@@ -149,6 +149,7 @@ DISCRETE-TIME MARKOV CHAIN
149
149
  > APERIODIC: YES
150
150
  > IRREDUCIBLE: NO
151
151
  ABSORBING: YES
152
+ MONOTONE: NO
152
153
  REGULAR: NO
153
154
  REVERSIBLE: YES
154
155
  SYMMETRIC: NO
@@ -0,0 +1,42 @@
1
+ LICENSE.md
2
+ MANIFEST.in
3
+ README.md
4
+ requirements.txt
5
+ setup.cfg
6
+ setup.py
7
+ PyDTMC.egg-info/PKG-INFO
8
+ PyDTMC.egg-info/SOURCES.txt
9
+ PyDTMC.egg-info/dependency_links.txt
10
+ PyDTMC.egg-info/requires.txt
11
+ PyDTMC.egg-info/top_level.txt
12
+ pydtmc/__init__.py
13
+ pydtmc/assessments.py
14
+ pydtmc/base_classes.py
15
+ pydtmc/computations.py
16
+ pydtmc/custom_types.py
17
+ pydtmc/decorators.py
18
+ pydtmc/exceptions.py
19
+ pydtmc/files_io.py
20
+ pydtmc/fitting.py
21
+ pydtmc/generators.py
22
+ pydtmc/hidden_markov_model.py
23
+ pydtmc/markov_chain.py
24
+ pydtmc/measures.py
25
+ pydtmc/plotting.py
26
+ pydtmc/simulations.py
27
+ pydtmc/utilities.py
28
+ pydtmc/validation.py
29
+ tests/test_aliases.py
30
+ tests/test_assessments.py
31
+ tests/test_classification.py
32
+ tests/test_conversions.py
33
+ tests/test_fitting.py
34
+ tests/test_generators_instance.py
35
+ tests/test_generators_static.py
36
+ tests/test_hmm.py
37
+ tests/test_measures.py
38
+ tests/test_plotting.py
39
+ tests/test_properties.py
40
+ tests/test_simulations.py
41
+ tests/test_utilities.py
42
+ tests/test_validation.py
@@ -108,6 +108,7 @@ DISCRETE-TIME MARKOV CHAIN
108
108
  > APERIODIC: YES
109
109
  > IRREDUCIBLE: NO
110
110
  ABSORBING: YES
111
+ MONOTONE: NO
111
112
  REGULAR: NO
112
113
  REVERSIBLE: YES
113
114
  SYMMETRIC: NO
@@ -1,14 +1,14 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  __title__ = 'PyDTMC'
4
- __version__ = '8.3.0'
4
+ __version__ = '8.4.0'
5
5
  __author__ = 'Tommaso Belluzzo'
6
6
 
7
7
  __all__ = [
8
8
  'ValidationError',
9
9
  'HiddenMarkovModel', 'MarkovChain',
10
10
  'assess_first_order', 'assess_homogeneity', 'assess_markov_property', 'assess_stationarity', 'assess_theoretical_compatibility',
11
- 'plot_comparison', 'plot_eigenvalues', 'plot_graph', 'plot_redistributions', 'plot_sequence', 'plot_trellis'
11
+ 'plot_comparison', 'plot_eigenvalues', 'plot_flow', 'plot_graph', 'plot_redistributions', 'plot_sequence', 'plot_trellis'
12
12
  ]
13
13
 
14
14
  from pydtmc.exceptions import (
@@ -34,6 +34,7 @@ from pydtmc.assessments import (
34
34
  from pydtmc.plotting import (
35
35
  plot_comparison,
36
36
  plot_eigenvalues,
37
+ plot_flow,
37
38
  plot_graph,
38
39
  plot_redistributions,
39
40
  plot_sequence,
@@ -1,5 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  __all__ = [
4
6
  # Base Types
5
7
  'tany',
@@ -157,11 +159,11 @@ tstack = _tp.List[_ins.FrameInfo]
157
159
  ostack = _tp.Optional[tstack]
158
160
 
159
161
  # noinspection PyTypeHints
160
- thmm = _tp.TypeVar('HiddenMarkovModel')
162
+ thmm = _tp.ForwardRef('HiddenMarkovModel')
161
163
  ohmm = _tp.Optional[thmm]
162
164
 
163
165
  # noinspection PyTypeHints
164
- tmc = _tp.TypeVar('MarkovChain')
166
+ tmc = _tp.ForwardRef('MarkovChain')
165
167
  omc = _tp.Optional[tmc]
166
168
 
167
169
  tmodel = _tp.Union[thmm, tmc]
@@ -15,6 +15,7 @@ __all__ = [
15
15
  'mc_gamblers_ruin',
16
16
  'mc_lazy',
17
17
  'mc_lump',
18
+ 'mc_population_genetics_model',
18
19
  'mc_random',
19
20
  'mc_sub',
20
21
  'mc_urn_model'
@@ -883,6 +884,52 @@ def mc_lump(p: _tarray, states: _tlist_str, partitions: _tlists_int) -> _tmc_gen
883
884
  return p_lump, state_names, None
884
885
 
885
886
 
887
+ def mc_population_genetics_model(model: str, n: int, s: float, u: float, v: float) -> _tmc_generation:
888
+
889
+ size = n + 1
890
+
891
+ p = _np.zeros((size, size), dtype=float)
892
+ p[0, 0] = 1.0
893
+ p[n, n] = 1.0
894
+
895
+ ui = 1.0 - u
896
+ vi = 1.0 - v
897
+
898
+ if model == 'moran':
899
+
900
+ r = 1.0 - s
901
+
902
+ for i in range(1, n):
903
+ nmi = n - i
904
+ ri = r * i
905
+
906
+ pm1 = (i / n) * (((ri * v) + (nmi * vi)) / (ri + nmi))
907
+ pp1 = (nmi / n) * (((ri * ui) + (nmi * v)) / (ri + nmi))
908
+
909
+ p[i, i - 1] = pm1
910
+ p[i, i] = 1.0 - pm1 - pp1
911
+ p[i, i + 1] = pp1
912
+
913
+ else:
914
+
915
+ q = _np.arange(0, size)
916
+
917
+ for i in range(1, n):
918
+
919
+ k = i / n
920
+
921
+ pm = (k * ui) + ((1.0 - k) * v)
922
+ ps = min((pm * (1.0 + s)) / ((pm * (1.0 + s)) - pm + 1.0), 1.0)
923
+
924
+ p[i, :] = _np.exp(_sps.binom.logpmf(q, n, ps))
925
+
926
+ p /= _np.sum(p, axis=1, keepdims=True)
927
+
928
+ state_names = [f'{i:d}' for i in range(1, size + 1)]
929
+
930
+ return p, state_names, None
931
+
932
+
886
933
  # noinspection DuplicatedCode
887
934
  def mc_random(rng: _trand, size: int, zeros: int, mask: _tarray) -> _tmc_generation:
888
935
 
@@ -968,7 +1015,7 @@ def mc_sub(p: _tarray, states: _tlist_str, adjacency_matrix: _tarray, sub_states
968
1015
  return p, state_names, None
969
1016
 
970
1017
 
971
- def mc_urn_model(n: int, model: str) -> _tmc_generation:
1018
+ def mc_urn_model(model: str, n: int) -> _tmc_generation:
972
1019
 
973
1020
  dn = n * 2
974
1021
  size = dn + 1
@@ -542,7 +542,8 @@ class HiddenMarkovModel(_Model):
542
542
  def to_matrices(self) -> _tpair_array:
543
543
 
544
544
  """
545
- | The method returns a tuple of two items representing the underlying matrices of the hidden Markov model.
545
+ The method returns a tuple of two items representing the underlying matrices of the hidden Markov model.
546
+
546
547
  | The first item is the transition matrix and the second item is the emission matrix.
547
548
  """
548
549
 
@@ -117,6 +117,7 @@ from .generators import (
117
117
  mc_gamblers_ruin as _gamblers_ruin,
118
118
  mc_lazy as _lazy,
119
119
  mc_lump as _lump,
120
+ mc_population_genetics_model as _population_genetics_model,
120
121
  mc_random as _random,
121
122
  mc_sub as _sub,
122
123
  mc_urn_model as _urn_model
@@ -174,6 +175,7 @@ from .validation import (
174
175
  validate_labels_current as _validate_labels_current,
175
176
  validate_labels_input as _validate_labels_input,
176
177
  validate_mask as _validate_mask,
178
+ validate_markov_chain as _validate_markov_chain,
177
179
  validate_matrix as _validate_matrix,
178
180
  validate_partitions as _validate_partitions,
179
181
  validate_random_distribution as _validate_random_distribution,
@@ -257,6 +259,7 @@ class MarkovChain(_Model):
257
259
  lines.append(f' > APERIODIC: {("YES" if self.is_aperiodic else "NO (" + str(self.period) + ")")}')
258
260
  lines.append(f' > IRREDUCIBLE: {("YES" if self.is_irreducible else "NO")}')
259
261
  lines.append(f' ABSORBING: {("YES" if self.is_absorbing else "NO")}')
262
+ lines.append(f' MONOTONE: {("YES" if self.is_stochastically_monotone else "NO")}')
260
263
  lines.append(f' REGULAR: {("YES" if self.is_regular else "NO")}')
261
264
  lines.append(f' REVERSIBLE: {("YES" if self.is_reversible else "NO")}')
262
265
  lines.append(f' SYMMETRIC: {("YES" if self.is_symmetric else "NO")}')
@@ -690,6 +693,31 @@ class MarkovChain(_Model):
690
693
 
691
694
  return result
692
695
 
696
+ @_cached_property
697
+ @_object_mark(aliases=['is_monotone'])
698
+ def is_stochastically_monotone(self) -> bool:
699
+
700
+ """
701
+ A property indicating whether the Markov chain is stochastically monotone.
702
+ """
703
+
704
+ result = True
705
+
706
+ for m in range(1, self.__size):
707
+
708
+ sm = _np.sum(self.__p[:, m:], axis=1)
709
+
710
+ for k, l in zip(range(self.__size - 1), range(1, self.__size)):
711
+
712
+ sk = sm[k]
713
+ sl = sm[l]
714
+
715
+ if sl < sk:
716
+ result = False
717
+ break
718
+
719
+ return result
720
+
693
721
  @_cached_property
694
722
  def is_symmetric(self) -> bool:
695
723
 
@@ -1547,6 +1575,36 @@ class MarkovChain(_Model):
1547
1575
 
1548
1576
  return self.__cache['mrt']
1549
1577
 
1578
+ @_object_mark(instance_generator=True)
1579
+ def merge_with(self, other: _tmc, gamma: float) -> _tmc:
1580
+
1581
+ """
1582
+ The method returns a Markov chain whose transition matrix is defined below.
1583
+
1584
+ | :math:`p_{new} = (1 - \gamma) p_{current} + \gamma p_{other}`
1585
+
1586
+ :param other: the other Markov chain to be merged.
1587
+ :param gamma: the merger blending factor.
1588
+ :raises ValidationError: if any input argument is not compliant.
1589
+ """
1590
+
1591
+ try:
1592
+
1593
+ other = _validate_markov_chain(other, self.__size)
1594
+ gamma = _validate_float(gamma, lower_limit=(0.0, True), upper_limit=(1.0, True))
1595
+
1596
+ except Exception as ex: # pragma: no cover
1597
+ raise _create_validation_error(ex, _ins.trace()) from None
1598
+
1599
+ p_current = self.__p
1600
+ p_other = other.p
1601
+
1602
+ p = ((1.0 - gamma) * p_current) + (gamma * p_other)
1603
+ states = _create_labels(self.__size, 'M')
1604
+ mc = MarkovChain(p, states)
1605
+
1606
+ return mc
1607
+
1550
1608
  @_object_mark(aliases=['mt'])
1551
1609
  def mixing_time(self, initial_distribution: _onumeric = None, jump: int = 1, cutoff_type: str = 'natural') -> _oint:
1552
1610
 
@@ -2050,7 +2108,7 @@ class MarkovChain(_Model):
2050
2108
  sigma = _validate_float(sigma, lower_limit=(0.0, True))
2051
2109
  rho = _validate_float(rho, lower_limit=(-1.0, False), upper_limit=(1.0, False))
2052
2110
 
2053
- if states is not None:
2111
+ if states is not None: # pragma: no cover
2054
2112
  states = _validate_labels_input(states, size)
2055
2113
 
2056
2114
  if approximation_type == 'tauchen':
@@ -2091,7 +2149,7 @@ class MarkovChain(_Model):
2091
2149
  p = _validate_vector(p, 'creation', False)
2092
2150
  q = _validate_vector(q, 'annihilation', False)
2093
2151
 
2094
- if states is not None:
2152
+ if states is not None: # pragma: no cover
2095
2153
  states = _validate_labels_input(states, {p.shape[0], q.shape[0]}.pop())
2096
2154
 
2097
2155
  except Exception as ex: # pragma: no cover
@@ -2136,7 +2194,7 @@ class MarkovChain(_Model):
2136
2194
  diagonal_bias_factor = None if diagonal_bias_factor is None else _validate_float(diagonal_bias_factor, lower_limit=(0.0, True))
2137
2195
  shift_concentration = _validate_boolean(shift_concentration)
2138
2196
 
2139
- if states is not None:
2197
+ if states is not None: # pragma: no cover
2140
2198
  states = _validate_labels_input(states, size)
2141
2199
 
2142
2200
  except Exception as ex: # pragma: no cover
@@ -2463,7 +2521,7 @@ class MarkovChain(_Model):
2463
2521
  size = _validate_integer(size, lower_limit=(3, False))
2464
2522
  w = _validate_float(w, lower_limit=(0.0, True), upper_limit=(1.0, True))
2465
2523
 
2466
- if states is not None:
2524
+ if states is not None: # pragma: no cover
2467
2525
  states = _validate_labels_input(states, size)
2468
2526
 
2469
2527
  except Exception as ex: # pragma: no cover
@@ -2503,6 +2561,50 @@ class MarkovChain(_Model):
2503
2561
 
2504
2562
  return mc
2505
2563
 
2564
+ @staticmethod
2565
+ @_object_mark(instance_generator=True)
2566
+ def population_genetics_model(model: str, n: int, s: float = 0.0, u: float = 1.0e-9, v: float = 1.0e-9, states: _olist_str = None) -> _tmc:
2567
+
2568
+ """
2569
+ The method generates a Markov chain based on the specified population genetics model.
2570
+
2571
+ :param model:
2572
+ - **moran** for the Moran model;
2573
+ - **wright-fisher** for the Wright-Fisher model.
2574
+ :param n: the number of individuals.
2575
+ :param s: the selection intensity.
2576
+ :param u: the backward mutation rate.
2577
+ :param v: the forward mutation rate.
2578
+ :param states: the name of each state (*if omitted, an increasing sequence of integers starting at 1*).
2579
+ :raises ValidationError: if any input argument is not compliant.
2580
+ """
2581
+
2582
+ try:
2583
+
2584
+ model = _validate_enumerator(model, ['moran', 'wright-fisher'])
2585
+ n = _validate_integer(n, lower_limit=(2, False), upper_limit=(500, False))
2586
+ s = _validate_float(s, lower_limit=(-1.0, False))
2587
+ u = _validate_float(u, lower_limit=(0.0, False), upper_limit=(1.0, False))
2588
+ v = _validate_float(v, lower_limit=(0.0, False), upper_limit=(1.0, False))
2589
+
2590
+ if states is not None: # pragma: no cover
2591
+ states = _validate_labels_input(states, n + 1)
2592
+
2593
+ except Exception as ex: # pragma: no cover
2594
+ raise _create_validation_error(ex, _ins.trace()) from None
2595
+
2596
+ if model == 'wright-fisher' and (4.0 * n * max(u, v)) > 1.0:
2597
+ raise _ValidationError('The highest mutation rate specified is too high for the Wright-Fisher model.')
2598
+
2599
+ p, states_out, _ = _population_genetics_model(model, n, s, u, v)
2600
+
2601
+ if states is None:
2602
+ states = states_out
2603
+
2604
+ mc = MarkovChain(p, states)
2605
+
2606
+ return mc
2607
+
2506
2608
  # noinspection DuplicatedCode
2507
2609
  @staticmethod
2508
2610
  @_object_mark(instance_generator=True, random_output=True)
@@ -2528,7 +2630,7 @@ class MarkovChain(_Model):
2528
2630
  rng = _create_rng(seed)
2529
2631
  size = _validate_integer(size, lower_limit=(2, False))
2530
2632
 
2531
- if states is not None:
2633
+ if states is not None: # pragma: no cover
2532
2634
  states = _validate_labels_input(states, size)
2533
2635
 
2534
2636
  zeros = _validate_integer(zeros, lower_limit=(0, False))
@@ -2597,31 +2699,31 @@ class MarkovChain(_Model):
2597
2699
 
2598
2700
  @staticmethod
2599
2701
  @_object_mark(instance_generator=True)
2600
- def urn_model(n: int, model: str, states: _olist_str = None) -> _tmc:
2702
+ def urn_model(model: str, n: int, states: _olist_str = None) -> _tmc:
2601
2703
 
2602
2704
  """
2603
2705
  The method generates a Markov chain of size **2N + 1** based on the specified urn model.
2604
2706
 
2605
- :param n: the number of elements in each urn.
2606
2707
  :param model:
2607
2708
  - **bernoulli-laplace** for the Bernoulli-Laplace urn model;
2608
2709
  - **ehrenfest** for the Ehrenfest urn model.
2710
+ :param n: the number of elements in each urn.
2609
2711
  :param states: the name of each state (*if omitted, an increasing sequence of integers starting at 1*).
2610
2712
  :raises ValidationError: if any input argument is not compliant.
2611
2713
  """
2612
2714
 
2613
2715
  try:
2614
2716
 
2615
- n = _validate_integer(n, lower_limit=(1, False))
2616
2717
  model = _validate_enumerator(model, ['bernoulli-laplace', 'ehrenfest'])
2718
+ n = _validate_integer(n, lower_limit=(1, False))
2617
2719
 
2618
- if states is not None:
2720
+ if states is not None: # pragma: no cover
2619
2721
  states = _validate_labels_input(states, (2 * n) + 1)
2620
2722
 
2621
2723
  except Exception as ex: # pragma: no cover
2622
2724
  raise _create_validation_error(ex, _ins.trace()) from None
2623
2725
 
2624
- p, states_out, _ = _urn_model(n, model)
2726
+ p, states_out, _ = _urn_model(model, n)
2625
2727
 
2626
2728
  if states is None:
2627
2729
  states = states_out