bella-companion 0.1.1__tar.gz → 0.1.20__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 (102) hide show
  1. {bella_companion-0.1.1 → bella_companion-0.1.20}/PKG-INFO +7 -5
  2. {bella_companion-0.1.1 → bella_companion-0.1.20}/pyproject.toml +17 -12
  3. bella_companion-0.1.20/src/bella_companion/backend/__init__.py +94 -0
  4. bella_companion-0.1.20/src/bella_companion/backend/activation_functions.py +72 -0
  5. bella_companion-0.1.20/src/bella_companion/backend/mlp.py +284 -0
  6. bella_companion-0.1.20/src/bella_companion/backend/plots.py +167 -0
  7. bella_companion-0.1.20/src/bella_companion/backend/type_hints.py +8 -0
  8. bella_companion-0.1.20/src/bella_companion/backend/utils/__init__.py +42 -0
  9. bella_companion-0.1.20/src/bella_companion/backend/utils/beast.py +304 -0
  10. bella_companion-0.1.20/src/bella_companion/backend/utils/metrics.py +135 -0
  11. bella_companion-0.1.20/src/bella_companion/backend/utils/misc.py +25 -0
  12. {bella_companion-0.1.1/src/bella_companion → bella_companion-0.1.20/src/bella_companion/backend}/utils/slurm.py +42 -6
  13. bella_companion-0.1.20/src/bella_companion/backend/xAI/__init__.py +27 -0
  14. bella_companion-0.1.20/src/bella_companion/backend/xAI/pdp.py +221 -0
  15. bella_companion-0.1.20/src/bella_companion/backend/xAI/shapley.py +164 -0
  16. bella_companion-0.1.20/src/bella_companion/cli.py +195 -0
  17. bella_companion-0.1.20/src/bella_companion/eucovid/__init__.py +15 -0
  18. bella_companion-0.1.1/src/bella_companion/eucovid/beast_configs/MLP.xml → bella_companion-0.1.20/src/bella_companion/eucovid/beast_configs/BELLA.xml +22 -8
  19. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/eucovid/beast_configs/GLM.xml +24 -21
  20. bella_companion-0.1.20/src/bella_companion/eucovid/data/flights_and_populations/changetimes.csv +6 -0
  21. bella_companion-0.1.20/src/bella_companion/eucovid/data/flights_and_populations/flights.csv +35 -0
  22. bella_companion-0.1.20/src/bella_companion/eucovid/data/flights_and_populations/populations.csv +35 -0
  23. bella_companion-0.1.20/src/bella_companion/eucovid/data/msa.fasta +470 -0
  24. bella_companion-0.1.20/src/bella_companion/eucovid/plot.py +111 -0
  25. bella_companion-0.1.20/src/bella_companion/eucovid/run.py +46 -0
  26. bella_companion-0.1.20/src/bella_companion/eucovid/settings.py +4 -0
  27. bella_companion-0.1.20/src/bella_companion/eucovid/summarize.py +50 -0
  28. bella_companion-0.1.20/src/bella_companion/platyrrhine/__init__.py +17 -0
  29. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/platyrrhine/beast_config.xml +3 -5
  30. bella_companion-0.1.20/src/bella_companion/platyrrhine/plot.py +218 -0
  31. bella_companion-0.1.20/src/bella_companion/platyrrhine/run.py +61 -0
  32. bella_companion-0.1.20/src/bella_companion/platyrrhine/settings.py +13 -0
  33. bella_companion-0.1.20/src/bella_companion/platyrrhine/summarize.py +54 -0
  34. bella_companion-0.1.20/src/bella_companion/simulations/__init__.py +25 -0
  35. bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/epi-multitype/MLP.xml → bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/epi-multitype/BELLA.xml +0 -1
  36. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/beast_configs/epi-multitype/GLM.xml +0 -1
  37. bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/epi-multitype/Nonparametric.xml → bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/epi-multitype/PA.xml +0 -1
  38. bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/fbd-2traits/MLP.xml → bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/fbd-2traits/BELLA.xml +1 -3
  39. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/beast_configs/fbd-2traits/GLM.xml +1 -3
  40. bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/fbd-2traits/Nonparametric.xml → bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/fbd-2traits/PA.xml +1 -3
  41. bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/fbd-no-traits/MLP.xml → bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/fbd-no-traits/BELLA.xml +1 -3
  42. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/beast_configs/fbd-no-traits/GLM.xml +1 -3
  43. bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/fbd-no-traits/Nonparametric.xml → bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/fbd-no-traits/PA.xml +1 -3
  44. bella_companion-0.1.1/src/bella_companion/simulations/generate_data.py → bella_companion-0.1.20/src/bella_companion/simulations/generate.py +11 -5
  45. bella_companion-0.1.20/src/bella_companion/simulations/metrics/__init__.py +3 -0
  46. bella_companion-0.1.20/src/bella_companion/simulations/metrics/run.py +144 -0
  47. bella_companion-0.1.20/src/bella_companion/simulations/metrics/template.tex +42 -0
  48. bella_companion-0.1.20/src/bella_companion/simulations/plot/__init__.py +23 -0
  49. bella_companion-0.1.20/src/bella_companion/simulations/plot/epi_multitype.py +70 -0
  50. bella_companion-0.1.20/src/bella_companion/simulations/plot/epi_skyline.py +88 -0
  51. bella_companion-0.1.20/src/bella_companion/simulations/plot/fbd_2traits.py +151 -0
  52. bella_companion-0.1.20/src/bella_companion/simulations/plot/fbd_no_traits.py +100 -0
  53. bella_companion-0.1.20/src/bella_companion/simulations/plot/globals.py +3 -0
  54. {bella_companion-0.1.1/src/bella_companion/simulations/figures → bella_companion-0.1.20/src/bella_companion/simulations/plot}/scenarios.py +35 -27
  55. bella_companion-0.1.20/src/bella_companion/simulations/run.py +69 -0
  56. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/scenarios/__init__.py +4 -4
  57. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/scenarios/epi_multitype.py +7 -12
  58. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/scenarios/epi_skyline.py +5 -12
  59. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/scenarios/fbd_2traits.py +5 -13
  60. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/scenarios/fbd_no_traits.py +5 -13
  61. bella_companion-0.1.20/src/bella_companion/simulations/scenarios/globals.py +7 -0
  62. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/scenarios/scenario.py +0 -3
  63. bella_companion-0.1.1/src/bella_companion/simulations/scenarios/common.py → bella_companion-0.1.20/src/bella_companion/simulations/scenarios/utils.py +0 -8
  64. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/summarize.py +7 -6
  65. bella_companion-0.1.1/src/bella_companion/.DS_Store +0 -0
  66. bella_companion-0.1.1/src/bella_companion/BELLA.jar +0 -0
  67. bella_companion-0.1.1/src/bella_companion/cli.py +0 -82
  68. bella_companion-0.1.1/src/bella_companion/eucovid/__init__.py +0 -3
  69. bella_companion-0.1.1/src/bella_companion/eucovid/data/msa.fasta +0 -79500
  70. bella_companion-0.1.1/src/bella_companion/eucovid/run_beast.py +0 -36
  71. bella_companion-0.1.1/src/bella_companion/features.py +0 -13
  72. bella_companion-0.1.1/src/bella_companion/platyrrhine/__init__.py +0 -5
  73. bella_companion-0.1.1/src/bella_companion/platyrrhine/results.py +0 -191
  74. bella_companion-0.1.1/src/bella_companion/platyrrhine/run_beast.py +0 -70
  75. bella_companion-0.1.1/src/bella_companion/platyrrhine/summarize.py +0 -41
  76. bella_companion-0.1.1/src/bella_companion/simulations/__init__.py +0 -13
  77. bella_companion-0.1.1/src/bella_companion/simulations/figures/__init__.py +0 -13
  78. bella_companion-0.1.1/src/bella_companion/simulations/figures/epi_multitype.py +0 -81
  79. bella_companion-0.1.1/src/bella_companion/simulations/figures/epi_skyline.py +0 -45
  80. bella_companion-0.1.1/src/bella_companion/simulations/figures/fbd_2traits.py +0 -83
  81. bella_companion-0.1.1/src/bella_companion/simulations/figures/fbd_no_traits.py +0 -56
  82. bella_companion-0.1.1/src/bella_companion/simulations/figures/utils.py +0 -98
  83. bella_companion-0.1.1/src/bella_companion/simulations/generate_figures.py +0 -15
  84. bella_companion-0.1.1/src/bella_companion/simulations/metrics.py +0 -59
  85. bella_companion-0.1.1/src/bella_companion/simulations/run_beast.py +0 -84
  86. bella_companion-0.1.1/src/bella_companion/utils/__init__.py +0 -19
  87. bella_companion-0.1.1/src/bella_companion/utils/beast.py +0 -69
  88. bella_companion-0.1.1/src/bella_companion/utils/explain/__init__.py +0 -7
  89. bella_companion-0.1.1/src/bella_companion/utils/explain/pdp.py +0 -138
  90. bella_companion-0.1.1/src/bella_companion/utils/explain/shap.py +0 -104
  91. bella_companion-0.1.1/src/bella_companion/utils/explain/typeguards.py +0 -20
  92. bella_companion-0.1.1/src/bella_companion/version.xml +0 -13
  93. {bella_companion-0.1.1 → bella_companion-0.1.20}/README.md +0 -0
  94. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/__init__.py +0 -0
  95. {bella_companion-0.1.1/src/bella_companion/eucovid/data → bella_companion-0.1.20/src/bella_companion/eucovid/data/flights_over_populations}/changetimes.csv +0 -0
  96. /bella_companion-0.1.1/src/bella_companion/eucovid/data/flights.csv → /bella_companion-0.1.20/src/bella_companion/eucovid/data/flights_over_populations/flights_over_populations.csv +0 -0
  97. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/platyrrhine/data/change_times.csv +0 -0
  98. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/platyrrhine/data/traits.csv +0 -0
  99. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/platyrrhine/data/trees.nwk +0 -0
  100. /bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/epi-skyline/MLP.xml → /bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/epi-skyline/BELLA.xml +0 -0
  101. {bella_companion-0.1.1 → bella_companion-0.1.20}/src/bella_companion/simulations/beast_configs/epi-skyline/GLM.xml +0 -0
  102. /bella_companion-0.1.1/src/bella_companion/simulations/beast_configs/epi-skyline/Nonparametric.xml → /bella_companion-0.1.20/src/bella_companion/simulations/beast_configs/epi-skyline/PA.xml +0 -0
@@ -1,15 +1,17 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bella-companion
3
- Version: 0.1.1
3
+ Version: 0.1.20
4
4
  Summary: Bayesian Evolutionary Layered Learning Architectures (BELLA) companion
5
5
  Author: gabriele-marino
6
6
  Author-email: gabriele-marino <gabmarino.8601@gmail.com>
7
- Requires-Dist: arviz>=0.22.0
8
- Requires-Dist: bella-lumiere>=0.1.0
7
+ Requires-Dist: arviz==0.22.0
8
+ Requires-Dist: autoregistry>=1.2.0
9
9
  Requires-Dist: dotenv>=0.9.9
10
- Requires-Dist: phylogenie>=3.1.4
11
- Requires-Dist: poetry>=2.2.1
10
+ Requires-Dist: phylogenie>=3.1.14
11
+ Requires-Dist: plotly>=6.5.0
12
+ Requires-Dist: scipy>=1.15.3
12
13
  Requires-Dist: seaborn>=0.13.2
14
+ Requires-Dist: shap>=0.49.1
13
15
  Requires-Python: >=3.10
14
16
  Description-Content-Type: text/markdown
15
17
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bella-companion"
3
- version = "0.1.1"
3
+ version = "0.1.20"
4
4
  description = "Bayesian Evolutionary Layered Learning Architectures (BELLA) companion"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -8,27 +8,32 @@ authors = [
8
8
  ]
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
- "arviz>=0.22.0",
12
- "bella-lumiere>=0.1.0",
11
+ "arviz==0.22.0",
12
+ "autoregistry>=1.2.0",
13
13
  "dotenv>=0.9.9",
14
- "phylogenie>=3.1.4",
15
- "poetry>=2.2.1",
14
+ "phylogenie>=3.1.14",
15
+ "plotly>=6.5.0",
16
+ "scipy>=1.15.3",
16
17
  "seaborn>=0.13.2",
18
+ "shap>=0.49.1",
17
19
  ]
18
20
 
19
- [project.scripts]
20
- bella = "bella_companion.cli:main"
21
-
22
- [build-system]
23
- requires = ["uv_build>=0.9.5,<0.10.0"]
24
- build-backend = "uv_build"
25
-
26
21
  [dependency-groups]
27
22
  dev = [
28
23
  "joblib-stubs>=1.5.2.0.20250831",
24
+ "pandas-stubs>=2.3.2.250926",
25
+ "plotly-stubs>=0.0.6",
29
26
  "pyright>=1.1.407",
27
+ "requests>=2.32.5",
28
+ "scipy-stubs>=1.15.3.0",
30
29
  ]
31
30
 
32
31
  [tool.pyright]
33
32
  typeCheckingMode = "strict"
34
33
 
34
+ [project.scripts]
35
+ bella = "bella_companion.cli:main"
36
+
37
+ [build-system]
38
+ requires = ["uv_build>=0.9.5,<0.10.0"]
39
+ build-backend = "uv_build"
@@ -0,0 +1,94 @@
1
+ from bella_companion.backend.activation_functions import (
2
+ ActivationFunction,
3
+ ActivationFunctionLike,
4
+ Identity,
5
+ RegisteredActivationFunction,
6
+ ReLU,
7
+ Sigmoid,
8
+ Softplus,
9
+ Tanh,
10
+ as_activation_function,
11
+ )
12
+ from bella_companion.backend.mlp import MLP, MLPEnsemble, mlp_ensembles_from_logs_dir
13
+ from bella_companion.backend.plots import ribbon_plot, skyline_plot
14
+ from bella_companion.backend.type_hints import Array, Model, Weights
15
+ from bella_companion.backend.utils import (
16
+ ESS_POSTFIX,
17
+ LOWER_POSTFIX,
18
+ MEDIAN_POSTFIX,
19
+ UPPER_POSTFIX,
20
+ avg_ci_width_from_summaries,
21
+ coverage_from_summaries,
22
+ get_job_metadata,
23
+ mae_distribution_from_summaries,
24
+ mae_from_summaries,
25
+ mean_ess_per_hour_from_summaries,
26
+ normalize,
27
+ read_log_file,
28
+ read_weights,
29
+ read_weights_dir,
30
+ submit_beast_job,
31
+ submit_job,
32
+ summarize_log,
33
+ summarize_logs_dir,
34
+ )
35
+ from bella_companion.backend.xAI import (
36
+ get_median_partial_dependence_plot,
37
+ get_median_partial_dependence_plot_distribution,
38
+ get_median_shap_feature_importance_distribution,
39
+ get_partial_dependence_plot,
40
+ get_partial_dependence_plot_distribution,
41
+ get_partial_dependence_plots,
42
+ get_partial_dependence_plots_distribution,
43
+ get_shap_feature_importance_distribution,
44
+ get_shap_features_importance,
45
+ get_shap_values,
46
+ )
47
+
48
+ __all__ = [
49
+ "ActivationFunction",
50
+ "ActivationFunctionLike",
51
+ "Identity",
52
+ "RegisteredActivationFunction",
53
+ "ReLU",
54
+ "Sigmoid",
55
+ "Softplus",
56
+ "Tanh",
57
+ "as_activation_function",
58
+ "MLP",
59
+ "MLPEnsemble",
60
+ "mlp_ensembles_from_logs_dir",
61
+ "ribbon_plot",
62
+ "skyline_plot",
63
+ "Array",
64
+ "Model",
65
+ "Weights",
66
+ "ESS_POSTFIX",
67
+ "LOWER_POSTFIX",
68
+ "MEDIAN_POSTFIX",
69
+ "UPPER_POSTFIX",
70
+ "avg_ci_width_from_summaries",
71
+ "coverage_from_summaries",
72
+ "get_job_metadata",
73
+ "mae_distribution_from_summaries",
74
+ "mae_from_summaries",
75
+ "mean_ess_per_hour_from_summaries",
76
+ "normalize",
77
+ "read_log_file",
78
+ "read_weights",
79
+ "read_weights_dir",
80
+ "submit_beast_job",
81
+ "submit_job",
82
+ "summarize_log",
83
+ "summarize_logs_dir",
84
+ "get_median_partial_dependence_plot",
85
+ "get_median_partial_dependence_plot_distribution",
86
+ "get_median_shap_feature_importance_distribution",
87
+ "get_partial_dependence_plot",
88
+ "get_partial_dependence_plot_distribution",
89
+ "get_partial_dependence_plots",
90
+ "get_partial_dependence_plots_distribution",
91
+ "get_shap_feature_importance_distribution",
92
+ "get_shap_features_importance",
93
+ "get_shap_values",
94
+ ]
@@ -0,0 +1,72 @@
1
+ from abc import abstractmethod
2
+ from typing import Callable
3
+
4
+ import numpy as np
5
+ from autoregistry import Registry
6
+ from numpy.typing import ArrayLike
7
+
8
+ from bella_companion.backend.type_hints import Array
9
+
10
+ ActivationFunction = Callable[[ArrayLike], Array]
11
+ ActivationFunctionLike = str | ActivationFunction
12
+
13
+
14
+ class RegisteredActivationFunction(Registry):
15
+ @abstractmethod
16
+ def __call__(self, x: ArrayLike) -> Array: ...
17
+
18
+
19
+ class Identity(RegisteredActivationFunction):
20
+ def __call__(self, x: ArrayLike) -> Array:
21
+ return np.asarray(x, dtype=np.float64)
22
+
23
+ def __repr__(self) -> str:
24
+ return "Identity()"
25
+
26
+
27
+ class Sigmoid(RegisteredActivationFunction):
28
+ def __init__(self, lower: float = 0.0, upper: float = 1.0, shape: float = 1.0):
29
+ self._lower = lower
30
+ self._upper = upper
31
+ self._shape = shape
32
+
33
+ def __call__(self, x: ArrayLike) -> Array:
34
+ x = np.asarray(x, dtype=np.float64)
35
+ return self._lower + (self._upper - self._lower) / (
36
+ 1 + np.exp(-self._shape * x)
37
+ )
38
+
39
+ def __repr__(self) -> str:
40
+ return f"Sigmoid(lower={self._lower}, upper={self._upper}, shape={self._shape})"
41
+
42
+
43
+ class ReLU(RegisteredActivationFunction):
44
+ def __call__(self, x: ArrayLike) -> Array:
45
+ return np.maximum(0, x)
46
+
47
+ def __repr__(self) -> str:
48
+ return "ReLU()"
49
+
50
+
51
+ class Softplus(RegisteredActivationFunction):
52
+ def __call__(self, x: ArrayLike) -> Array:
53
+ return np.log1p(np.exp(x))
54
+
55
+ def __repr__(self) -> str:
56
+ return "Softplus()"
57
+
58
+
59
+ class Tanh(RegisteredActivationFunction):
60
+ def __call__(self, x: ArrayLike) -> Array:
61
+ return np.tanh(x)
62
+
63
+ def __repr__(self) -> str:
64
+ return "Tanh()"
65
+
66
+
67
+ def as_activation_function(
68
+ activation: ActivationFunctionLike,
69
+ ) -> ActivationFunction:
70
+ if isinstance(activation, str):
71
+ return RegisteredActivationFunction[activation]()
72
+ return activation
@@ -0,0 +1,284 @@
1
+ from collections.abc import Iterator
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+ from joblib import Parallel, delayed
6
+ from numpy.typing import ArrayLike
7
+ from tqdm import tqdm
8
+
9
+ from bella_companion.backend.activation_functions import (
10
+ ActivationFunctionLike,
11
+ as_activation_function,
12
+ )
13
+ from bella_companion.backend.type_hints import Array, Weights
14
+ from bella_companion.backend.utils import read_weights
15
+
16
+
17
+ class MLP:
18
+ def __init__(
19
+ self,
20
+ weights: Weights,
21
+ hidden_activation: ActivationFunctionLike = "relu",
22
+ output_activation: ActivationFunctionLike = "sigmoid",
23
+ ):
24
+ """
25
+ Initialize the MLP with given weights and activation functions.
26
+
27
+ Parameters
28
+ ----------
29
+ weights : Weights
30
+ A list of weight matrices for each layer in the MLP.
31
+ The length of the list determines the number of layers.
32
+ The shape of each weight matrix should be (n_inputs + 1, n_outputs),
33
+ where n_inputs is the number of inputs to the layer (excluding bias)
34
+ and n_outputs is the number of outputs from the layer.
35
+ hidden_activation : ActivationFunctionLike, optional
36
+ Activation function to use for hidden layers, by default "relu".
37
+ output_activation : ActivationFunctionLike, optional
38
+ Activation function to use for the output layer, by default "sigmoid".
39
+
40
+ Raises
41
+ ------
42
+ ValueError
43
+ If the output layer does not have a single output neuron.
44
+ """
45
+ if weights[-1].shape[1] != 1:
46
+ raise ValueError("Output layer must have a single output neuron.")
47
+
48
+ self._weights = weights
49
+ self._hidden_activation = as_activation_function(activation=hidden_activation)
50
+ self._output_activation = as_activation_function(activation=output_activation)
51
+ self._activations = [self._hidden_activation] * (self.n_layers - 1) + [
52
+ self._output_activation
53
+ ]
54
+
55
+ @property
56
+ def n_layers(self) -> int:
57
+ return len(self._weights)
58
+
59
+ def forward(self, inputs: ArrayLike) -> Array:
60
+ """
61
+ Perform a forward pass through the MLP.
62
+
63
+ Parameters
64
+ ----------
65
+ inputs : ArrayLike
66
+ Input data of shape (n_samples, n_features).
67
+
68
+ Returns
69
+ -------
70
+ Array
71
+ The output of the MLP after the forward pass, of shape (n_samples,).
72
+ """
73
+ x = np.asarray(inputs, dtype=np.float64)
74
+ n_samples, _ = x.shape
75
+ for layer_weights, activation in zip(self._weights, self._activations):
76
+ bias = np.ones((n_samples, 1))
77
+ x = np.hstack((bias, x))
78
+ x = np.dot(x, layer_weights)
79
+ x = activation(x)
80
+ return x.flatten()
81
+
82
+ def __call__(self, inputs: ArrayLike) -> Array:
83
+ return self.forward(inputs)
84
+
85
+ def __repr__(self) -> str:
86
+ return (
87
+ f"MLP(n_layers={self.n_layers}, "
88
+ f"hidden_activation={self._hidden_activation}, "
89
+ f"output_activation={self._output_activation})"
90
+ )
91
+
92
+
93
+ class MLPEnsemble:
94
+ def __init__(
95
+ self,
96
+ weights_list: list[Weights],
97
+ hidden_activation: ActivationFunctionLike = "relu",
98
+ output_activation: ActivationFunctionLike = "sigmoid",
99
+ ):
100
+ """
101
+ Initialize the MLP ensemble with given weights and activation functions.
102
+
103
+ Parameters
104
+ ----------
105
+ weights_list : list[Weights]
106
+ A list of weight sets, where each set corresponds to an MLP in the ensemble.
107
+ hidden_activation : ActivationFunctionLike, optional
108
+ Activation function to use for hidden layers, by default "relu".
109
+ output_activation : ActivationFunctionLike, optional
110
+ Activation function to use for the output layer, by default "sigmoid".
111
+ """
112
+ self._mlps = [
113
+ MLP(
114
+ weights=weights,
115
+ hidden_activation=hidden_activation,
116
+ output_activation=output_activation,
117
+ )
118
+ for weights in weights_list
119
+ ]
120
+
121
+ @property
122
+ def n_models(self) -> int:
123
+ return len(self._mlps)
124
+
125
+ @property
126
+ def mlps(self) -> tuple[MLP, ...]:
127
+ return tuple(self._mlps)
128
+
129
+ def forward(
130
+ self,
131
+ inputs: ArrayLike, # (n_samples, n_features)
132
+ ) -> Array: # (n_models, n_samples)
133
+ """
134
+ Perform a forward pass through the MLP ensemble.
135
+
136
+ Parameters
137
+ ----------
138
+ inputs : ArrayLike
139
+ Input data of shape (n_samples, n_features).
140
+
141
+ Returns
142
+ -------
143
+ Array
144
+ The outputs of the MLP ensemble after the forward pass,
145
+ of shape (n_models, n_samples).
146
+ """
147
+ return np.array([mlp(inputs) for mlp in self._mlps])
148
+
149
+ def forward_median(
150
+ self,
151
+ inputs: ArrayLike, # (n_samples, n_features)
152
+ ) -> Array: # (n_samples,)
153
+ """
154
+ Perform a forward pass through the MLP ensemble and compute the median output.
155
+
156
+ Parameters
157
+ ----------
158
+ inputs : ArrayLike
159
+ Input data of shape (n_samples, n_features).
160
+
161
+ Returns
162
+ -------
163
+ Array
164
+ The median output of the MLP ensemble after the forward pass,
165
+ of shape (n_samples,).
166
+ """
167
+ outputs = self.forward(inputs) # (n_models, n_samples)
168
+ return np.median(outputs, axis=0) # (n_samples,)
169
+
170
+ @classmethod
171
+ def from_log_file(
172
+ cls,
173
+ log_file: str | Path,
174
+ target_name: str,
175
+ burn_in: int | float = 0.1,
176
+ n_samples: int | None = 100,
177
+ random_seed: int | None = 42,
178
+ hidden_activation: ActivationFunctionLike = "relu",
179
+ output_activation: ActivationFunctionLike = "sigmoid",
180
+ ) -> "MLPEnsemble":
181
+ """
182
+ Create an MLPEnsemble instance from a BEAST log file.
183
+
184
+ Parameters
185
+ ----------
186
+ log_file : str | Path
187
+ Path to the BEAST log file.
188
+ target_name : str
189
+ The target variable name for which to extract weights.
190
+ burn_in : int | float, optional
191
+ If int, number of initial samples to discard.
192
+ If float, fraction of samples to discard, by default 0.1.
193
+ n_samples : int | None, optional
194
+ Number of weight samples to return, by default 100.
195
+ If None, returns all available samples after burn-in.
196
+ random_seed : int | None, optional
197
+ Random seed for sampling weights when n_samples is specified, by default 42.
198
+ hidden_activation : ActivationFunctionLike, optional
199
+ Activation function to use for hidden layers, by default "relu".
200
+ output_activation : ActivationFunctionLike, optional
201
+ Activation function to use for the output layer, by default "sigmoid".
202
+
203
+ Returns
204
+ -------
205
+ MLPEnsemble
206
+ An instance of MLPEnsemble initialized with weights from the log file.
207
+ """
208
+ weights_list = read_weights(
209
+ log_file=log_file,
210
+ burn_in=burn_in,
211
+ n_samples=n_samples,
212
+ random_seed=random_seed,
213
+ )
214
+ return cls(
215
+ weights_list=weights_list[target_name],
216
+ hidden_activation=hidden_activation,
217
+ output_activation=output_activation,
218
+ )
219
+
220
+ def __call__(self, inputs: ArrayLike) -> Array:
221
+ return self.forward(inputs)
222
+
223
+ def __len__(self) -> int:
224
+ return self.n_models
225
+
226
+ def __iter__(self) -> Iterator[MLP]:
227
+ return iter(self._mlps)
228
+
229
+ def __repr__(self) -> str:
230
+ return f"MLPEnsemble(n_models={self.n_models})"
231
+
232
+
233
+ def mlp_ensembles_from_logs_dir(
234
+ logs_dir: str | Path,
235
+ target_name: str,
236
+ burn_in: int | float = 0.1,
237
+ n_samples: int | None = 100,
238
+ random_seed: int | None = 42,
239
+ hidden_activation: ActivationFunctionLike = "relu",
240
+ output_activation: ActivationFunctionLike = "sigmoid",
241
+ n_jobs: int = -1,
242
+ ) -> list[MLPEnsemble]:
243
+ """
244
+ Create a list of MLPEnsemble instances from all BEAST log files in a directory.
245
+
246
+ Parameters
247
+ ----------
248
+ logs_dir : str | Path
249
+ Path to the directory containing BEAST log files.
250
+ target_name : str
251
+ The target variable name for which to extract weights.
252
+ burn_in : int | float, optional
253
+ If int, number of initial samples to discard.
254
+ If float, fraction of samples to discard, by default 0.1.
255
+ n_samples : int | None, optional
256
+ Number of weight samples to return, by default 100.
257
+ If None, returns all available samples after burn-in.
258
+ random_seed : int | None, optional
259
+ Random seed for sampling weights when n_samples is specified, by default 42.
260
+ hidden_activation : ActivationFunctionLike, optional
261
+ Activation function to use for hidden layers, by default "relu".
262
+ output_activation : ActivationFunctionLike, optional
263
+ Activation function to use for the output layer, by default "sigmoid".
264
+ n_jobs : int, optional
265
+ Number of parallel jobs to use, by default -1 (use all available cores).
266
+
267
+ Returns
268
+ -------
269
+ list[MLPEnsemble]
270
+ A list of MLPEnsemble instances initialized with weights from the log files.
271
+ """
272
+ log_files = list(Path(logs_dir).glob("*.log"))
273
+ return Parallel(n_jobs=n_jobs)(
274
+ delayed(MLPEnsemble.from_log_file)(
275
+ log_file=log_file,
276
+ target_name=target_name,
277
+ burn_in=burn_in,
278
+ n_samples=n_samples,
279
+ random_seed=random_seed,
280
+ hidden_activation=hidden_activation,
281
+ output_activation=output_activation,
282
+ )
283
+ for log_file in tqdm(log_files, desc="Loading MLP ensembles from logs")
284
+ )
@@ -0,0 +1,167 @@
1
+ from typing import Any
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+ from matplotlib.axes import Axes
6
+ from numpy.typing import ArrayLike
7
+
8
+ Color = (
9
+ str
10
+ | np.typing.NDArray[np.floating]
11
+ | tuple[float, float, float]
12
+ | tuple[float, float, float, float]
13
+ )
14
+
15
+
16
+ def skyline_plot(
17
+ data: ArrayLike,
18
+ x: ArrayLike | None = None,
19
+ ax: Axes | None = None,
20
+ step_kwargs: dict[str, Any] | None = None,
21
+ ) -> Axes:
22
+ """
23
+ Plot a skyline (step) plot.
24
+
25
+ Parameters
26
+ ----------
27
+ data : ArrayLike
28
+ The y values for the skyline plot, of shape (n_points,).
29
+ x : ArrayLike | None, optional
30
+ The x values, of shape (n_points + 1,), by default None (uses indices).
31
+ The first x value corresponds to the start of the first step, the
32
+ last x value corresponds to the end of the last step.
33
+ ax : Axes | None, optional
34
+ The matplotlib Axes to plot on, by default None (uses current Axes).
35
+ step_kwargs : dict[str, Any] | None, optional
36
+ Additional keyword arguments for the step plot, by default None.
37
+
38
+ Returns
39
+ -------
40
+ Axes
41
+ The matplotlib Axes with the plot.
42
+ """
43
+ data = np.asarray(data, dtype=np.float64)
44
+ data = [data[0], *data]
45
+
46
+ if ax is None:
47
+ ax = plt.gca()
48
+ if x is None:
49
+ x = list(range(len(data)))
50
+ if step_kwargs is None:
51
+ step_kwargs = {}
52
+
53
+ ax.step(x, data, **(step_kwargs or {})) # pyright: ignore
54
+ return ax
55
+
56
+
57
+ def ribbon_plot(
58
+ y: ArrayLike,
59
+ x: ArrayLike | None = None,
60
+ color: Color | None = None,
61
+ label: str | None = None,
62
+ ax: Axes | None = None,
63
+ skyline: bool = False,
64
+ lower_percentile: float = 2.5,
65
+ upper_percentile: float = 97.5,
66
+ show_fill: bool = True,
67
+ fill_kwargs: dict[str, Any] | None = None,
68
+ show_samples: bool = True,
69
+ samples_kwargs: dict[str, Any] | None = None,
70
+ show_median: bool = True,
71
+ median_kwargs: dict[str, Any] | None = None,
72
+ ) -> Axes:
73
+ """
74
+ Plot a ribbon plot with uncertainty intervals.
75
+
76
+ Parameters
77
+ ----------
78
+ y : ArrayLike
79
+ The y values, of shape (n_samples, n_points).
80
+ x : ArrayLike | None, optional
81
+ The x values, by default None (uses indices).
82
+ If skyline is True, x should have shape (n_points + 1,),
83
+ where the first x value corresponds to the start of the first step,
84
+ the last x value corresponds to the end of the last step.
85
+ If skyline is False, x should have shape (n_points,).
86
+ color : str | None, optional
87
+ The color for the plot, by default None.
88
+ label : str | None, optional
89
+ The label for the median line, by default None.
90
+ ax : Axes | None, optional
91
+ The matplotlib Axes to plot on, by default None (uses current Axes).
92
+ skyline : bool, optional
93
+ Whether to use a skyline (step) plot, by default False.
94
+ lower_percentile : float, optional
95
+ The lower percentile for the percentile interval, by default 2.5.
96
+ upper_percentile : float, optional
97
+ The upper percentile for the percentile interval, by default 97.5.
98
+ show_fill : bool, optional
99
+ Whether to show the percentile interval fill, by default True.
100
+ fill_kwargs : dict[str, Any] | None, optional
101
+ Additional keyword arguments for the fill_between call, by default None.
102
+ show_samples : bool, optional
103
+ Whether to show individual sample lines, by default True.
104
+ samples_kwargs : dict[str, Any] | None, optional
105
+ Additional keyword arguments for the sample lines, by default None.
106
+ show_median : bool, optional
107
+ Whether to show the median line, by default True.
108
+ median_kwargs : dict[str, Any] | None, optional
109
+ Additional keyword arguments for the median line, by default None.
110
+
111
+ Returns
112
+ -------
113
+ Axes
114
+ The matplotlib Axes with the plot.
115
+ """
116
+ if ax is None:
117
+ ax = plt.gca()
118
+
119
+ y = np.asarray(y, dtype=np.float64)
120
+ if x is None:
121
+ _, n_points = y.shape
122
+ if skyline:
123
+ n_points += 1
124
+ x = list(range(n_points))
125
+
126
+ if show_fill:
127
+ lower = np.percentile(y, lower_percentile, axis=0)
128
+ high = np.percentile(y, upper_percentile, axis=0)
129
+ if fill_kwargs is None:
130
+ fill_kwargs = {}
131
+ if "alpha" not in fill_kwargs:
132
+ fill_kwargs["alpha"] = 0.25
133
+ if "color" not in fill_kwargs:
134
+ fill_kwargs["color"] = color
135
+ if skyline:
136
+ fill_kwargs["step"] = "pre"
137
+ lower = [lower[0], *lower]
138
+ high = [high[0], *high]
139
+ ax.fill_between(x, lower, high, **fill_kwargs) # pyright: ignore
140
+
141
+ if show_samples:
142
+ if samples_kwargs is None:
143
+ samples_kwargs = {}
144
+ if "alpha" not in samples_kwargs:
145
+ samples_kwargs["alpha"] = 0.25
146
+ if "color" not in samples_kwargs:
147
+ samples_kwargs["color"] = color
148
+ for sample_y in y:
149
+ if skyline:
150
+ skyline_plot(data=sample_y, x=x, ax=ax, step_kwargs=samples_kwargs)
151
+ else:
152
+ ax.plot(x, sample_y, **samples_kwargs) # pyright: ignore
153
+
154
+ if show_median:
155
+ median = np.median(y, axis=0)
156
+ if median_kwargs is None:
157
+ median_kwargs = {}
158
+ if "color" not in median_kwargs:
159
+ median_kwargs["color"] = color
160
+ if "label" not in median_kwargs:
161
+ median_kwargs["label"] = label
162
+ if skyline:
163
+ skyline_plot(data=median, x=x, ax=ax, step_kwargs=median_kwargs)
164
+ else:
165
+ ax.plot(x, median, **median_kwargs) # pyright: ignore
166
+
167
+ return ax
@@ -0,0 +1,8 @@
1
+ from collections.abc import Callable
2
+
3
+ import numpy as np
4
+ from numpy.typing import ArrayLike
5
+
6
+ Array = np.typing.NDArray[np.float64]
7
+ Weights = list[Array]
8
+ Model = Callable[[ArrayLike], Array]