cofi 0.1.3.dev1__tar.gz → 0.2.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.
- {cofi-0.1.3.dev1/src/cofi.egg-info → cofi-0.2.0}/PKG-INFO +6 -5
- {cofi-0.1.3.dev1 → cofi-0.2.0}/README.md +5 -4
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/_base_problem.py +37 -116
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/_exceptions.py +2 -2
- cofi-0.2.0/src/cofi/_version.py +1 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/_base_inference_tool.py +4 -2
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/_scipy_lstsq.py +2 -3
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/_scipy_opt_min.py +5 -5
- cofi-0.2.0/src/cofi/utils/__init__.py +26 -0
- cofi-0.2.0/src/cofi/utils/_reg_base.py +214 -0
- cofi-0.2.0/src/cofi/utils/_reg_lp_norm.py +371 -0
- cofi-0.2.0/src/cofi/utils/_reg_model_cov.py +161 -0
- cofi-0.2.0/src/cofi/version.py +1 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0/src/cofi.egg-info}/PKG-INFO +6 -5
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi.egg-info/SOURCES.txt +7 -2
- cofi-0.2.0/tests/test_base_problem_basics.py +92 -0
- cofi-0.2.0/tests/test_base_problem_for_optimization.py +221 -0
- cofi-0.2.0/tests/test_base_problem_for_sampling.py +39 -0
- cofi-0.2.0/tests/test_base_problem_misc.py +173 -0
- cofi-0.1.3.dev1/src/cofi/_version.py +0 -1
- cofi-0.1.3.dev1/src/cofi/utils/__init__.py +0 -13
- cofi-0.1.3.dev1/src/cofi/utils/_regularization.py +0 -463
- cofi-0.1.3.dev1/src/cofi/version.py +0 -1
- cofi-0.1.3.dev1/tests/test_base_problem.py +0 -581
- {cofi-0.1.3.dev1 → cofi-0.2.0}/LICENCE +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/pyproject.toml +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/setup.cfg +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/__init__.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/_inversion.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/_inversion_options.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/__init__.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/_cofi_simple_newton.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/_emcee.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/_pytorch_optim.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi/tools/_scipy_opt_lstsq.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi.egg-info/dependency_links.txt +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi.egg-info/requires.txt +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/src/cofi.egg-info/top_level.txt +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/tests/test_deprecation.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/tests/test_inversion.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/tests/test_inversion_options.py +0 -0
- {cofi-0.1.3.dev1 → cofi-0.2.0}/tests/test_inversion_result.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cofi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Common Framework for Inference
|
|
5
5
|
Author: InLab, CoFI development team
|
|
6
6
|
Keywords: inversion,inference,python package,geoscience,geophysics
|
|
@@ -24,7 +24,6 @@ License-File: LICENCE
|
|
|
24
24
|
|
|
25
25
|
# <img src="https://raw.githubusercontent.com/inlab-geo/cofi/main/docs/source/_static/latte_art_cropped.png" width="5%" style="vertical-align:bottom"/> CoFI (Common Framework for Inference)
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
[](https://pypi.org/project/cofi/)
|
|
29
28
|
[](https://anaconda.org/conda-forge/cofi)
|
|
30
29
|
[](https://cofi.readthedocs.io/en/latest/?badge=latest)
|
|
@@ -32,15 +31,17 @@ License-File: LICENCE
|
|
|
32
31
|
[](https://join.slack.com/t/inlab-community/shared_invite/zt-1ejny069z-v5ZyvP2tDjBR42OAu~TkHg)
|
|
33
32
|
<!-- [](https://pypi.org/project/cofi/) -->
|
|
34
33
|
|
|
34
|
+
> Related repositories by [InLab](https://inlab.edu.au/community/):
|
|
35
|
+
> - [CoFI Examples](https://github.com/inlab-geo/cofi-examples)
|
|
36
|
+
> - [Espresso](https://github.com/inlab-geo/espresso)
|
|
35
37
|
|
|
36
38
|
## Introduction
|
|
37
39
|
|
|
38
|
-
CoFI (Common Framework for Inference) is an open
|
|
40
|
+
CoFI (Common Framework for Inference) is an open source initiative for interfacing between generic inference algorithms and specific geoscience problems.
|
|
39
41
|
|
|
40
42
|
With a mission to bridge the gap between the domain expertise and the inference expertise, CoFI provides an interface across a wide range of inference algorithms from different sources, underpinned by a rich set of domain relevant [examples](https://cofi.readthedocs.io/en/latest/examples/generated/index.html).
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
Read [the documentation](https://cofi.readthedocs.io/en/latest/), and let us know your feedback or any issues!
|
|
44
45
|
|
|
45
46
|
## Installation
|
|
46
47
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# <img src="https://raw.githubusercontent.com/inlab-geo/cofi/main/docs/source/_static/latte_art_cropped.png" width="5%" style="vertical-align:bottom"/> CoFI (Common Framework for Inference)
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
[](https://pypi.org/project/cofi/)
|
|
7
6
|
[](https://anaconda.org/conda-forge/cofi)
|
|
8
7
|
[](https://cofi.readthedocs.io/en/latest/?badge=latest)
|
|
@@ -10,15 +9,17 @@
|
|
|
10
9
|
[](https://join.slack.com/t/inlab-community/shared_invite/zt-1ejny069z-v5ZyvP2tDjBR42OAu~TkHg)
|
|
11
10
|
<!-- [](https://pypi.org/project/cofi/) -->
|
|
12
11
|
|
|
12
|
+
> Related repositories by [InLab](https://inlab.edu.au/community/):
|
|
13
|
+
> - [CoFI Examples](https://github.com/inlab-geo/cofi-examples)
|
|
14
|
+
> - [Espresso](https://github.com/inlab-geo/espresso)
|
|
13
15
|
|
|
14
16
|
## Introduction
|
|
15
17
|
|
|
16
|
-
CoFI (Common Framework for Inference) is an open
|
|
18
|
+
CoFI (Common Framework for Inference) is an open source initiative for interfacing between generic inference algorithms and specific geoscience problems.
|
|
17
19
|
|
|
18
20
|
With a mission to bridge the gap between the domain expertise and the inference expertise, CoFI provides an interface across a wide range of inference algorithms from different sources, underpinned by a rich set of domain relevant [examples](https://cofi.readthedocs.io/en/latest/examples/generated/index.html).
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
Read [the documentation](https://cofi.readthedocs.io/en/latest/), and let us know your feedback or any issues!
|
|
22
23
|
|
|
23
24
|
## Installation
|
|
24
25
|
|
|
@@ -5,6 +5,7 @@ import json
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
|
+
from .utils import BaseRegularization
|
|
8
9
|
from ._exceptions import (
|
|
9
10
|
DimensionMismatchError,
|
|
10
11
|
InvalidOptionError,
|
|
@@ -197,7 +198,6 @@ class BaseProblem:
|
|
|
197
198
|
BaseProblem.data_misfit
|
|
198
199
|
BaseProblem.regularization
|
|
199
200
|
BaseProblem.regularization_matrix
|
|
200
|
-
BaseProblem.regularization_factor
|
|
201
201
|
BaseProblem.forward
|
|
202
202
|
BaseProblem.name
|
|
203
203
|
BaseProblem.data
|
|
@@ -230,7 +230,6 @@ class BaseProblem:
|
|
|
230
230
|
"data_misfit",
|
|
231
231
|
"regularization",
|
|
232
232
|
"regularization_matrix",
|
|
233
|
-
"regularization_factor",
|
|
234
233
|
"forward",
|
|
235
234
|
"data",
|
|
236
235
|
"data_covariance",
|
|
@@ -243,7 +242,13 @@ class BaseProblem:
|
|
|
243
242
|
]
|
|
244
243
|
|
|
245
244
|
def __init__(self, **kwargs):
|
|
246
|
-
|
|
245
|
+
for kw, val in kwargs.items():
|
|
246
|
+
if kw in self.all_components:
|
|
247
|
+
set_func = getattr(self, f"set_{kw}")
|
|
248
|
+
if isinstance(val, dict):
|
|
249
|
+
set_func(**val)
|
|
250
|
+
else:
|
|
251
|
+
set_func(val)
|
|
247
252
|
|
|
248
253
|
def objective(self, model: np.ndarray, *args, **kwargs) -> Number:
|
|
249
254
|
"""Method for computing the objective function given a model
|
|
@@ -962,8 +967,7 @@ class BaseProblem:
|
|
|
962
967
|
|
|
963
968
|
def set_regularization(
|
|
964
969
|
self,
|
|
965
|
-
regularization: Union[
|
|
966
|
-
regularization_factor: Number = 1,
|
|
970
|
+
regularization: Union[Callable[[np.ndarray], Number], BaseRegularization],
|
|
967
971
|
regularization_matrix: Union[
|
|
968
972
|
np.ndarray, Callable[[np.ndarray], np.ndarray]
|
|
969
973
|
] = None,
|
|
@@ -983,11 +987,6 @@ class BaseProblem:
|
|
|
983
987
|
regularization : str or (function - np.ndarray -> Number)
|
|
984
988
|
either a string from pre-built functions above, or a regularization function that
|
|
985
989
|
matches :meth:`regularization` in signature.
|
|
986
|
-
regularization_factor : Number, optional
|
|
987
|
-
the regularization factor (lamda) that adjusts the ratio of the regularization
|
|
988
|
-
term over the data misfit, by default 1. If ``regularization`` and ``data_misfit``
|
|
989
|
-
are set but ``objective`` isn't, then we will generate ``objective`` function as
|
|
990
|
-
following: :math:`\text{objective}(model)=\text{data_misfit}(model)+\text{factor}\times\text{regularization}(model)`
|
|
991
990
|
regularization_matrix : np.ndarray or (function - np.ndarray -> np.ndarray)
|
|
992
991
|
a matrix of shape ``(model_size, model_size)``, or a function that takes in
|
|
993
992
|
a model and calculates the (weighting) matrix.
|
|
@@ -1004,11 +1003,6 @@ class BaseProblem:
|
|
|
1004
1003
|
kwargs : dict, optional
|
|
1005
1004
|
extra dict of keyword arguments for regularization function
|
|
1006
1005
|
|
|
1007
|
-
Raises
|
|
1008
|
-
------
|
|
1009
|
-
InvalidOptionError
|
|
1010
|
-
when you've passed in a string not in our supported regularization list
|
|
1011
|
-
|
|
1012
1006
|
Examples
|
|
1013
1007
|
--------
|
|
1014
1008
|
|
|
@@ -1017,87 +1011,41 @@ class BaseProblem:
|
|
|
1017
1011
|
>>> from cofi import BaseProblem
|
|
1018
1012
|
>>> inv_problem = BaseProblem()
|
|
1019
1013
|
|
|
1020
|
-
1. Example with
|
|
1021
|
-
|
|
1022
|
-
>>> inv_problem.set_regularization(1)
|
|
1023
|
-
>>> inv_problem.regularization([1,1])
|
|
1024
|
-
2
|
|
1025
|
-
|
|
1026
|
-
2. Example with an inf norm
|
|
1027
|
-
|
|
1028
|
-
>>> inv_problem.set_regularization("inf")
|
|
1029
|
-
>>> inv_problem.regularization([1,1])
|
|
1030
|
-
1
|
|
1031
|
-
|
|
1032
|
-
3. Example with a custom regularization function
|
|
1014
|
+
1. Example with a custom regularization function
|
|
1033
1015
|
|
|
1034
1016
|
>>> inv_problem.set_regularization(lambda x: sum(x))
|
|
1035
1017
|
>>> inv_problem.regularization([1,1])
|
|
1036
1018
|
2
|
|
1037
1019
|
|
|
1038
|
-
|
|
1020
|
+
2. Example with a custom regularization + a regularization matrix
|
|
1039
1021
|
|
|
1040
|
-
>>> inv_problem.set_regularization(2,
|
|
1022
|
+
>>> inv_problem.set_regularization(lambda x: np.sum(x**2), np.eye(3))
|
|
1041
1023
|
>>> inv_problem.regularization([1,1])
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
5. Example with a regularization matrix
|
|
1045
|
-
|
|
1046
|
-
>>> inv_problem.set_regularization(2, 0.5, np.array([[2,0], [0,1]]))
|
|
1047
|
-
>>> inv_problem.regularization([1,1])
|
|
1048
|
-
1.118033988749895
|
|
1024
|
+
2
|
|
1049
1025
|
"""
|
|
1050
|
-
# preprocess regularization_matrix
|
|
1051
|
-
|
|
1026
|
+
# preprocess regularization_matrix if there is one
|
|
1027
|
+
_reg_matrix = None
|
|
1028
|
+
if regularization_matrix is not None:
|
|
1029
|
+
_reg_matrix = regularization_matrix
|
|
1030
|
+
elif isinstance(regularization, BaseRegularization) and hasattr(
|
|
1031
|
+
regularization, "matrix"
|
|
1032
|
+
):
|
|
1033
|
+
_reg_matrix = regularization.matrix
|
|
1034
|
+
# wrap regularization_matrix as a function
|
|
1035
|
+
if _reg_matrix is not None and np.ndim(_reg_matrix) != 0:
|
|
1052
1036
|
self.regularization_matrix = _FunctionWrapper(
|
|
1053
|
-
"regularization_matrix", _matrix_to_func, args=[
|
|
1037
|
+
"regularization_matrix", _matrix_to_func, args=[_reg_matrix]
|
|
1054
1038
|
)
|
|
1055
|
-
elif callable(
|
|
1039
|
+
elif _reg_matrix is not None and callable(_reg_matrix):
|
|
1056
1040
|
self.regularization_matrix = _FunctionWrapper(
|
|
1057
|
-
"regularization_matrix",
|
|
1041
|
+
"regularization_matrix", _reg_matrix
|
|
1058
1042
|
)
|
|
1059
1043
|
else:
|
|
1060
1044
|
self.regularization_matrix = None
|
|
1061
|
-
#
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
isinstance(order, str)
|
|
1066
|
-
and order not in ["fro", "nuc", "inf", "-inf"]
|
|
1067
|
-
or isinstance(order, Number)
|
|
1068
|
-
and order < 0
|
|
1069
|
-
):
|
|
1070
|
-
raise InvalidOptionError(
|
|
1071
|
-
name="regularization order",
|
|
1072
|
-
invalid_option=order,
|
|
1073
|
-
valid_options=(
|
|
1074
|
-
"[None, 'fro', 'nuc', numpy.inf, -numpy.inf] or any positive"
|
|
1075
|
-
" number"
|
|
1076
|
-
),
|
|
1077
|
-
)
|
|
1078
|
-
elif isinstance(order, str) and order in ["inf", "-inf"]:
|
|
1079
|
-
order = float(order)
|
|
1080
|
-
_reg = _FunctionWrapper(
|
|
1081
|
-
"regularization_none_lamda", np.linalg.norm, args=[order]
|
|
1082
|
-
)
|
|
1083
|
-
else:
|
|
1084
|
-
_reg = _FunctionWrapper(
|
|
1085
|
-
"regularization_none_lamda", regularization, args, kwargs
|
|
1086
|
-
)
|
|
1087
|
-
# wrapper function that calculates: lambda * raw regularization value
|
|
1088
|
-
self._regularization_factor = regularization_factor
|
|
1089
|
-
if self.regularization_matrix is None:
|
|
1090
|
-
self.regularization = _FunctionWrapper(
|
|
1091
|
-
"regularization",
|
|
1092
|
-
_regularization_with_lamda,
|
|
1093
|
-
args=[_reg, regularization_factor],
|
|
1094
|
-
)
|
|
1095
|
-
else:
|
|
1096
|
-
self.regularization = _FunctionWrapper(
|
|
1097
|
-
"regularization",
|
|
1098
|
-
_regularization_with_lamda_n_matrix,
|
|
1099
|
-
args=[_reg, regularization_factor, self.regularization_matrix],
|
|
1100
|
-
)
|
|
1045
|
+
# process regularization function
|
|
1046
|
+
self.regularization = _FunctionWrapper(
|
|
1047
|
+
"regularization", regularization, args, kwargs
|
|
1048
|
+
)
|
|
1101
1049
|
# update some autogenerated functions (as usual)
|
|
1102
1050
|
self._update_autogen("regularization")
|
|
1103
1051
|
|
|
@@ -1438,24 +1386,6 @@ class BaseProblem:
|
|
|
1438
1386
|
return self._blobs_dtype
|
|
1439
1387
|
raise NotDefinedError(needs="blobs name and type")
|
|
1440
1388
|
|
|
1441
|
-
@property
|
|
1442
|
-
def regularization_factor(self) -> Number:
|
|
1443
|
-
r"""regularization factor (lambda) that adjusts weights of the regularization
|
|
1444
|
-
term
|
|
1445
|
-
|
|
1446
|
-
Raises
|
|
1447
|
-
------
|
|
1448
|
-
NotDefinedError
|
|
1449
|
-
when this property has not been defined (by
|
|
1450
|
-
:meth:`set_regularization`
|
|
1451
|
-
"""
|
|
1452
|
-
if (
|
|
1453
|
-
hasattr(self, "_regularization_factor")
|
|
1454
|
-
and self._regularization_factor is not None
|
|
1455
|
-
):
|
|
1456
|
-
return self._regularization_factor
|
|
1457
|
-
raise NotDefinedError(needs="regularization_factor (lamda)")
|
|
1458
|
-
|
|
1459
1389
|
@property
|
|
1460
1390
|
def bounds(self):
|
|
1461
1391
|
r"""TODO: document me!
|
|
@@ -1591,11 +1521,6 @@ class BaseProblem:
|
|
|
1591
1521
|
r"""indicates whether :meth:`blobs_dtype` has been defined"""
|
|
1592
1522
|
return self._check_property_defined("blobs_dtype")
|
|
1593
1523
|
|
|
1594
|
-
@property
|
|
1595
|
-
def regularization_factor_defined(self) -> bool:
|
|
1596
|
-
r"""indicates whether :meth:`regularization_factor` has been defined"""
|
|
1597
|
-
return self._check_property_defined("regularization_factor")
|
|
1598
|
-
|
|
1599
1524
|
@property
|
|
1600
1525
|
def bounds_defined(self) -> bool:
|
|
1601
1526
|
r"""indicates whether :meth:`bounds` has been defined"""
|
|
@@ -1618,6 +1543,8 @@ class BaseProblem:
|
|
|
1618
1543
|
return False
|
|
1619
1544
|
except Exception: # ok if there're errors caused by dummy input
|
|
1620
1545
|
return True
|
|
1546
|
+
else:
|
|
1547
|
+
return True # ok if the function works without a wrapper
|
|
1621
1548
|
|
|
1622
1549
|
def _check_property_defined(self, prop):
|
|
1623
1550
|
try:
|
|
@@ -1710,12 +1637,12 @@ class BaseProblem:
|
|
|
1710
1637
|
if self.data_covariance_inv_defined:
|
|
1711
1638
|
if _is_diag(self.data_covariance_inv):
|
|
1712
1639
|
weighted_res = np.diag(self.data_covariance_inv) * res
|
|
1713
|
-
return
|
|
1640
|
+
return res @ weighted_res
|
|
1714
1641
|
else:
|
|
1715
1642
|
return res.T @ self.data_covariance_inv @ res
|
|
1716
1643
|
elif self.data_covariance_defined and _is_diag(self.data_covariance):
|
|
1717
1644
|
weighted_res = res / np.diag(self.data_covariance)
|
|
1718
|
-
return
|
|
1645
|
+
return res @ weighted_res
|
|
1719
1646
|
else:
|
|
1720
1647
|
return np.sum(np.square(res))
|
|
1721
1648
|
except Exception as exception:
|
|
@@ -1835,8 +1762,10 @@ def _objective_from_dm(model, data_misfit):
|
|
|
1835
1762
|
|
|
1836
1763
|
def _log_posterior_with_blobs_from_ll_lp(model, log_likelihood, log_prior):
|
|
1837
1764
|
try:
|
|
1838
|
-
ll = log_likelihood(model)
|
|
1839
1765
|
lp = log_prior(model)
|
|
1766
|
+
if lp == float("-inf"):
|
|
1767
|
+
return lp, None, lp
|
|
1768
|
+
ll = log_likelihood(model)
|
|
1840
1769
|
return ll + lp, ll, lp
|
|
1841
1770
|
except Exception as exception:
|
|
1842
1771
|
raise InvocationError(
|
|
@@ -1885,14 +1814,6 @@ def _jacobian_times_vector_from_jcb(model, vector, jacobian):
|
|
|
1885
1814
|
) from exception
|
|
1886
1815
|
|
|
1887
1816
|
|
|
1888
|
-
def _regularization_with_lamda(model, reg_func, lamda):
|
|
1889
|
-
return lamda * reg_func(model)
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
def _regularization_with_lamda_n_matrix(model, reg_func, lamda, reg_matrix_func):
|
|
1893
|
-
return lamda * reg_func(reg_matrix_func(model) @ model)
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
1817
|
def _matrix_to_func(_, matrix):
|
|
1897
1818
|
return matrix
|
|
1898
1819
|
|
|
@@ -123,8 +123,8 @@ class NotDefinedError(CofiError, NotImplementedError):
|
|
|
123
123
|
def __str__(self) -> str:
|
|
124
124
|
super_msg = super().__str__()
|
|
125
125
|
msg = (
|
|
126
|
-
f"`{self._needs}` is
|
|
127
|
-
"
|
|
126
|
+
f"`{self._needs}` is called but you haven't implemented or added it to the"
|
|
127
|
+
" problem setup"
|
|
128
128
|
)
|
|
129
129
|
return self._form_str(super_msg, msg)
|
|
130
130
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -310,8 +310,10 @@ def error_handler(when, context):
|
|
|
310
310
|
return func(*args, **kwargs)
|
|
311
311
|
except Exception as e:
|
|
312
312
|
raise CofiError(
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
(
|
|
314
|
+
f"error ocurred {when} ({context}). Check exception details "
|
|
315
|
+
"from message above."
|
|
316
|
+
),
|
|
315
317
|
) from e
|
|
316
318
|
|
|
317
319
|
return wrapped_func
|
|
@@ -119,7 +119,6 @@ class ScipyLstSq(BaseInferenceTool):
|
|
|
119
119
|
)
|
|
120
120
|
# get lamda and L matrix if needed
|
|
121
121
|
if self._params["with_tikhonov"]:
|
|
122
|
-
self._lamda = inv_problem.regularization_factor
|
|
123
122
|
if inv_problem.regularization_matrix_defined:
|
|
124
123
|
try:
|
|
125
124
|
_L = inv_problem.regularization_matrix(dummy_model)
|
|
@@ -129,11 +128,11 @@ class ScipyLstSq(BaseInferenceTool):
|
|
|
129
128
|
"squares solver, this should return a matrix unrelated to "
|
|
130
129
|
"model vector"
|
|
131
130
|
) from exception
|
|
132
|
-
self._LtL =
|
|
131
|
+
self._LtL = _L.T @ _L
|
|
133
132
|
self._components_used.append("regularization_matrix")
|
|
134
133
|
else:
|
|
135
134
|
self._LtL = np.identity(self._G.shape[1])
|
|
136
|
-
self._a += self.
|
|
135
|
+
self._a += self._LtL
|
|
137
136
|
|
|
138
137
|
def __call__(self) -> dict:
|
|
139
138
|
res_p, residual, rank, singular_vals = self._call_lstsq()
|
|
@@ -6,20 +6,20 @@ from . import BaseInferenceTool, error_handler
|
|
|
6
6
|
# Official documentation for scipy.optimize.minimize
|
|
7
7
|
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
|
|
8
8
|
|
|
9
|
-
# 'jac'
|
|
9
|
+
# 'jac' will be used when choosing the following methods:
|
|
10
10
|
# CG, BFGS, Newton-CG, L-BFGS-B, TNC, SLSQP, dogleg, trust-ncg, trust-krylov, trust-exact
|
|
11
11
|
# and trust-constr
|
|
12
12
|
|
|
13
|
-
# 'hess'
|
|
13
|
+
# 'hess' will be used when choosing the following methods:
|
|
14
14
|
# Newton-CG, dogleg, trust-ncg, trust-krylov, trust-exact and trust-constr
|
|
15
15
|
|
|
16
|
-
# 'hessp'
|
|
16
|
+
# 'hessp' will be used when choosing the following methods:
|
|
17
17
|
# Newton-CG, trust-ncg, trust-krylov, trust-constr
|
|
18
18
|
|
|
19
|
-
# 'bounds'
|
|
19
|
+
# 'bounds' will be used when choosing the following methods:
|
|
20
20
|
# Nelder-Mead, L-BFGS-B, TNC, SLSQP, Powell, and trust-constr
|
|
21
21
|
|
|
22
|
-
# 'constraints'
|
|
22
|
+
# 'constraints' will be used when choosing the following methods:
|
|
23
23
|
# COBYLA, SLSQP and trust-constr
|
|
24
24
|
|
|
25
25
|
# other arguments include: tol, options, callback
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
r"""Utility classes and functions (e.g. to generate regularization terms and more)
|
|
2
|
+
|
|
3
|
+
The class inheritance of regularization classes:
|
|
4
|
+
|
|
5
|
+
.. mermaid::
|
|
6
|
+
|
|
7
|
+
graph TD;
|
|
8
|
+
BaseRegularization --> LpNormRegularization;
|
|
9
|
+
LpNormRegularization --> QuadraticReg;
|
|
10
|
+
BaseRegularization --> ModelCovariance;
|
|
11
|
+
ModelCovariance --> GaussianPrior;
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from ._reg_base import BaseRegularization
|
|
16
|
+
from ._reg_lp_norm import LpNormRegularization, QuadraticReg
|
|
17
|
+
from ._reg_model_cov import ModelCovariance, GaussianPrior
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"BaseRegularization",
|
|
22
|
+
"LpNormRegularization",
|
|
23
|
+
"QuadraticReg",
|
|
24
|
+
"ModelCovariance",
|
|
25
|
+
"GaussianPrior",
|
|
26
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from abc import abstractmethod, ABCMeta
|
|
2
|
+
from numbers import Number
|
|
3
|
+
from functools import reduce
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .._exceptions import DimensionMismatchError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseRegularization(metaclass=ABCMeta):
|
|
10
|
+
r"""Base class for a regularization term
|
|
11
|
+
|
|
12
|
+
Check :class:`QuadraticReg` for a concrete example.
|
|
13
|
+
|
|
14
|
+
.. rubric:: Basic interface
|
|
15
|
+
|
|
16
|
+
The basic properties / methods for a regularization term in ``cofi.utils``
|
|
17
|
+
include the following:
|
|
18
|
+
|
|
19
|
+
.. autosummary::
|
|
20
|
+
BaseRegularization.model_size
|
|
21
|
+
BaseRegularization.reg
|
|
22
|
+
BaseRegularization.gradient
|
|
23
|
+
BaseRegularization.hessian
|
|
24
|
+
BaseRegularization.__call__
|
|
25
|
+
|
|
26
|
+
.. rubric:: Adding two terms
|
|
27
|
+
|
|
28
|
+
Two instances of ``BaseRegularization`` can also be added together using the ``+``
|
|
29
|
+
operator:
|
|
30
|
+
|
|
31
|
+
.. autosummary::
|
|
32
|
+
BaseRegularization.__add__
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def model_shape(self) -> tuple:
|
|
44
|
+
"""the shape of models that current regularization function accepts"""
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def model_size(self) -> Number:
|
|
49
|
+
"""the number of unknowns that current regularization function accepts"""
|
|
50
|
+
return reduce(lambda a, b: a * b, np.array(self.model_shape), 1)
|
|
51
|
+
|
|
52
|
+
def __call__(self, model: np.ndarray) -> Number:
|
|
53
|
+
r"""a class instance itself can also be called as a function
|
|
54
|
+
|
|
55
|
+
It works exactly the same as :meth:`reg`.
|
|
56
|
+
|
|
57
|
+
In other words, the following two usages are exactly the same::
|
|
58
|
+
|
|
59
|
+
>>> my_reg = QuadraticReg(factor=1, model_size=3)
|
|
60
|
+
>>> my_reg_value = my_reg(np.array([1,2,3])) # usage 1
|
|
61
|
+
>>> my_reg_value = my_reg.reg(np.array([1,2,3])) # usage 2
|
|
62
|
+
"""
|
|
63
|
+
return self.reg(model)
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def reg(self, model: np.ndarray) -> Number:
|
|
67
|
+
"""the regularization function value given a model to evaluate"""
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def gradient(self, model: np.ndarray) -> np.ndarray:
|
|
72
|
+
"""the gradient of regularization function with respect to model given a model
|
|
73
|
+
|
|
74
|
+
The usual size for the gradient is :math:`(M,)` where :math:`M` is the number
|
|
75
|
+
of model parameters
|
|
76
|
+
"""
|
|
77
|
+
raise NotImplementedError
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def hessian(self, model: np.ndarray) -> np.ndarray:
|
|
81
|
+
"""the hessian of regularization function with respect to model given a model
|
|
82
|
+
|
|
83
|
+
The usual size for the Hessian is :math:`(M,M)` where :math:`M` is the number
|
|
84
|
+
of model parameters
|
|
85
|
+
"""
|
|
86
|
+
raise NotImplementedError
|
|
87
|
+
|
|
88
|
+
def __add__(self, other_reg):
|
|
89
|
+
r"""Adds two regularization terms
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
other_reg : BaseRegularization
|
|
94
|
+
the second argument of "+" operator; must also be a
|
|
95
|
+
:class:`BaseRegularization` instance
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
BaseRegularization
|
|
100
|
+
a regularization term ``resRegularization`` such that:
|
|
101
|
+
|
|
102
|
+
- :math:`\text{resRegularization.reg}(m)=\text{self.reg}(m)+\text{other_reg.reg}(m)`
|
|
103
|
+
- :math:`\text{resRegularization.gradient}(m)=\text{self.gradient}(m)+\text{other_reg.gradient}(m)`
|
|
104
|
+
- :math:`\text{resRegularization.hessian}(m)=\text{self.hessian}(m)+\text{other_reg.hessian}(m)`
|
|
105
|
+
|
|
106
|
+
Raises
|
|
107
|
+
------
|
|
108
|
+
TypeError
|
|
109
|
+
when the ``other_reg`` is not a regularization term generated by CoFI Utils
|
|
110
|
+
DimensionMismatchError
|
|
111
|
+
when the ``other_reg`` doesn't accept model_size that matches the one of
|
|
112
|
+
``self``
|
|
113
|
+
|
|
114
|
+
Examples
|
|
115
|
+
--------
|
|
116
|
+
|
|
117
|
+
>>> from cofi import BaseProblem
|
|
118
|
+
>>> from cofi.utils import QuadraticReg
|
|
119
|
+
>>> reg1 = QuadraticReg(model_shape=(3,), weighting_matrix="damping")
|
|
120
|
+
>>> reg2 = QuadraticReg(model_shape=(3,), weighting_matrix="smoothing")
|
|
121
|
+
>>> my_problem = BaseProblem()
|
|
122
|
+
>>> my_problem.set_regularization(reg1 + reg2)
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
if not isinstance(other_reg, BaseRegularization):
|
|
126
|
+
raise TypeError(
|
|
127
|
+
f"unsupported operand type(s) for +: '{self.__class__.__name__}' "
|
|
128
|
+
f"and '{other_reg.__class__.__name__}"
|
|
129
|
+
)
|
|
130
|
+
if self.model_size != other_reg.model_size:
|
|
131
|
+
raise DimensionMismatchError(
|
|
132
|
+
entered_name="the second regularization term",
|
|
133
|
+
entered_dimension=other_reg.model_size,
|
|
134
|
+
expected_source="the first regularization term",
|
|
135
|
+
expected_dimension=self.model_size,
|
|
136
|
+
)
|
|
137
|
+
tmp_model_shape = self.model_shape
|
|
138
|
+
tmp_reg = self.reg
|
|
139
|
+
tmp_grad = self.gradient
|
|
140
|
+
tmp_hess = self.hessian
|
|
141
|
+
|
|
142
|
+
class CompositeRegularization(BaseRegularization):
|
|
143
|
+
@property
|
|
144
|
+
def model_shape(self):
|
|
145
|
+
return tmp_model_shape
|
|
146
|
+
|
|
147
|
+
def reg(self, model):
|
|
148
|
+
return tmp_reg(model) + other_reg(model)
|
|
149
|
+
|
|
150
|
+
def gradient(self, model):
|
|
151
|
+
return tmp_grad(model) + other_reg.gradient(model)
|
|
152
|
+
|
|
153
|
+
def hessian(self, model):
|
|
154
|
+
return tmp_hess(model) + other_reg.hessian(model)
|
|
155
|
+
|
|
156
|
+
return CompositeRegularization()
|
|
157
|
+
|
|
158
|
+
def __rmul__(self, coefficient):
|
|
159
|
+
r"""Multiply a regularization term with a constant number
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
coefficient : Number
|
|
164
|
+
the first argument of "*" operator; must be a Number
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
BaseRegularization
|
|
169
|
+
a regularization term ``resRegularization`` such that:
|
|
170
|
+
|
|
171
|
+
- :math:`\text{resRegularization.reg}(m)=\text{coefficient}\times\text{self.reg}(m)`
|
|
172
|
+
- :math:`\text{resRegularization.gradient}(m)=\text{coefficient}\times\text{self.gradient}(m)`
|
|
173
|
+
- :math:`\text{resRegularization.hessian}(m)=\text{coefficient}\times\text{self.hessian}(m)`
|
|
174
|
+
|
|
175
|
+
Raises
|
|
176
|
+
------
|
|
177
|
+
TypeError
|
|
178
|
+
when the ``coefficient`` is not of a python Number type
|
|
179
|
+
|
|
180
|
+
Examples
|
|
181
|
+
--------
|
|
182
|
+
|
|
183
|
+
>>> from cofi import BaseProblem
|
|
184
|
+
>>> from cofi.utils import QuadraticReg
|
|
185
|
+
>>> reg = QuadraticReg(model_shape=(3,), weighting_matrix="damping")
|
|
186
|
+
>>> my_problem = BaseProblem()
|
|
187
|
+
>>> my_problem.set_regularization(10 * reg)
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
if not isinstance(coefficient, Number):
|
|
191
|
+
raise TypeError(
|
|
192
|
+
f"unsupported operand type(s) for *: '{coefficient.__class__.__name__}'"
|
|
193
|
+
f" and '{self.__class__.__name__}"
|
|
194
|
+
)
|
|
195
|
+
tmp_model_shape = self.model_shape
|
|
196
|
+
tmp_reg = self.reg
|
|
197
|
+
tmp_grad = self.gradient
|
|
198
|
+
tmp_hess = self.hessian
|
|
199
|
+
|
|
200
|
+
class CompositeRegularization(BaseRegularization):
|
|
201
|
+
@property
|
|
202
|
+
def model_shape(self):
|
|
203
|
+
return tmp_model_shape
|
|
204
|
+
|
|
205
|
+
def reg(self, model):
|
|
206
|
+
return coefficient * tmp_reg(model)
|
|
207
|
+
|
|
208
|
+
def gradient(self, model):
|
|
209
|
+
return coefficient * tmp_grad(model)
|
|
210
|
+
|
|
211
|
+
def hessian(self, model):
|
|
212
|
+
return coefficient * tmp_hess(model)
|
|
213
|
+
|
|
214
|
+
return CompositeRegularization()
|