forked from probabilistic-numerics/probnum
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_config.py
More file actions
175 lines (144 loc) · 6.06 KB
/
_config.py
File metadata and controls
175 lines (144 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""ProbNum library configuration."""
import contextlib
import dataclasses
from typing import Any
class Configuration:
r"""Configuration by which some mechanics of ProbNum can be controlled dynamically.
ProbNum provides some configurations together with default values. These
are listed in the tables below.
Additionally, users can register their own configuration entries via
:meth:`register`. Configuration entries can only be registered once and can only
be used (accessed or overwritten) once they have been registered.
.. probnum-config-options::
Examples
========
>>> import probnum
>>> probnum.config.covariance_inversion_damping
1e-12
>>> with probnum.config(
... covariance_inversion_damping=1e-2,
... ):
... probnum.config.covariance_inversion_damping
0.01
"""
_NON_REGISTERED_KEY_ERR_MSG = (
'Configuration option "%s" does not exist yet. '
"Configuration options must be `register`ed before they can be "
"accessed."
)
@dataclasses.dataclass
class Option:
"""Representation of a single configuration option as a key-value pair with a
default value and a description string for documentation purposes."""
name: str
default_value: Any
description: str
value: Any
def __repr__(self) -> str:
_r = "<Configuration.Option "
_r += f"name={self.name}, value={self.value}>"
return _r
def __init__(self) -> None:
# This is the equivalent of `self._options_registry = dict()`.
# After rewriting the `__setattr__` method, we have to fall back on the
# `__setattr__` method of the super class.
object.__setattr__(self, "_options_registry", {})
def __getattr__(self, key: str) -> Any:
if key not in self._options_registry:
raise AttributeError(f'Configuration option "{key}" does not exist.')
return self._options_registry[key].value
def __setattr__(self, key: str, value: Any) -> None:
if key not in self._options_registry:
raise AttributeError(Configuration._NON_REGISTERED_KEY_ERR_MSG % key)
self._options_registry[key].value = value
def __repr__(self) -> str:
return repr(self._options_registry)
@contextlib.contextmanager
def __call__(self, **kwargs) -> None:
"""Context manager used to set values of registered config options."""
old_options = {}
for key, value in kwargs.items():
if key not in self._options_registry:
raise AttributeError(Configuration._NON_REGISTERED_KEY_ERR_MSG % key)
old_options[key] = self._options_registry[key].value
self._options_registry[key].value = value
try:
yield
finally:
for key, old_value in old_options.items():
self._options_registry[key].value = old_value
def register(self, key: str, default_value: Any, description: str) -> None:
r"""Register a new configuration option.
Parameters
----------
key:
The name of the configuration option. This will be the ``key`` when calling
``with config(key=<some_value>): ...``.
default_value:
The default value of the configuration option.
description:
A short description of the configuration option and what it controls.
Raises
------
KeyError
If the configuration option already exists.
"""
if key in self._options_registry:
raise KeyError(
f"Configuration option {key} does already exist and "
"cannot be registered again."
)
new_config_option = Configuration.Option(
name=key,
default_value=default_value,
description=description,
value=default_value,
)
self._options_registry[key] = new_config_option
# Create a single, global configuration object,...
_GLOBAL_CONFIG_SINGLETON = Configuration()
# ... define some configuration options, and the respective default values
# (which have to be documented in the Configuration-class docstring!!), ...
_DEFAULT_CONFIG_OPTIONS = [
# list of tuples (config_key, default_value)
(
"covariance_inversion_damping",
1e-12,
(
"A (typically small) value that is per default added to the diagonal "
"of covariance matrices in order to make inversion numerically stable."
),
),
(
"matrix_free",
False,
(
r"If :obj:`True`, wherever possible, :class:`~.linops.LinearOperator`\ s "
r"are used instead of arrays. :class:`~.linops.LinearOperator`\ s "
r"define a matrix-vector product implicitly without instantiating the full "
r"matrix in memory. This makes them memory- and runtime-efficient for "
r"linear algebra operations."
),
),
(
"lazy_matrix_matrix_matmul",
True,
(
r"If this is set to :obj:`False`, the matrix multiplication operator ``@`` "
r"applied to two :class:`~probnum.linops.LinearOperator`\ s of type "
r":class:`~probnum.linops.Matrix` multiplies the two matrices immediately "
r"and returns the product as a :class:`~probnum.linops.Matrix`. Otherwise, "
r"i.e. if this option is set to :obj:`True`, a :class:`~probnum.linops."
r"ProductLinearOperator`, representing the matrix product, is returned. "
r"Multiplying a vector with the :class:`~probnum.linops."
r"ProductLinearOperator` is often more efficient than computing the "
r"full matrix-matrix product first. This is why this option is set to "
r":obj:`True` by default."
),
),
]
# ... and register the default configuration options.
def _register_defaults():
for key, default_value, descr in _DEFAULT_CONFIG_OPTIONS:
_GLOBAL_CONFIG_SINGLETON.register(key, default_value, descr)
_register_defaults()