compiled-knowledge 4.0.0a20__cp312-cp312-win32.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.

Potentially problematic release.


This version of compiled-knowledge might be problematic. Click here for more details.

Files changed (178) hide show
  1. ck/__init__.py +0 -0
  2. ck/circuit/__init__.py +17 -0
  3. ck/circuit/_circuit_cy.c +37523 -0
  4. ck/circuit/_circuit_cy.cp312-win32.pyd +0 -0
  5. ck/circuit/_circuit_cy.pxd +32 -0
  6. ck/circuit/_circuit_cy.pyx +768 -0
  7. ck/circuit/_circuit_py.py +836 -0
  8. ck/circuit/tmp_const.py +74 -0
  9. ck/circuit_compiler/__init__.py +2 -0
  10. ck/circuit_compiler/circuit_compiler.py +26 -0
  11. ck/circuit_compiler/cython_vm_compiler/__init__.py +1 -0
  12. ck/circuit_compiler/cython_vm_compiler/_compiler.c +19824 -0
  13. ck/circuit_compiler/cython_vm_compiler/_compiler.cp312-win32.pyd +0 -0
  14. ck/circuit_compiler/cython_vm_compiler/_compiler.pyx +380 -0
  15. ck/circuit_compiler/cython_vm_compiler/cython_vm_compiler.py +121 -0
  16. ck/circuit_compiler/interpret_compiler.py +223 -0
  17. ck/circuit_compiler/llvm_compiler.py +388 -0
  18. ck/circuit_compiler/llvm_vm_compiler.py +546 -0
  19. ck/circuit_compiler/named_circuit_compilers.py +57 -0
  20. ck/circuit_compiler/support/__init__.py +0 -0
  21. ck/circuit_compiler/support/circuit_analyser/__init__.py +13 -0
  22. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.c +10618 -0
  23. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.cp312-win32.pyd +0 -0
  24. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.pyx +98 -0
  25. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_py.py +93 -0
  26. ck/circuit_compiler/support/input_vars.py +148 -0
  27. ck/circuit_compiler/support/llvm_ir_function.py +234 -0
  28. ck/example/__init__.py +53 -0
  29. ck/example/alarm.py +366 -0
  30. ck/example/asia.py +28 -0
  31. ck/example/binary_clique.py +32 -0
  32. ck/example/bow_tie.py +33 -0
  33. ck/example/cancer.py +37 -0
  34. ck/example/chain.py +38 -0
  35. ck/example/child.py +199 -0
  36. ck/example/clique.py +33 -0
  37. ck/example/cnf_pgm.py +39 -0
  38. ck/example/diamond_square.py +68 -0
  39. ck/example/earthquake.py +36 -0
  40. ck/example/empty.py +10 -0
  41. ck/example/hailfinder.py +539 -0
  42. ck/example/hepar2.py +628 -0
  43. ck/example/insurance.py +504 -0
  44. ck/example/loop.py +40 -0
  45. ck/example/mildew.py +38161 -0
  46. ck/example/munin.py +22982 -0
  47. ck/example/pathfinder.py +53747 -0
  48. ck/example/rain.py +39 -0
  49. ck/example/rectangle.py +161 -0
  50. ck/example/run.py +30 -0
  51. ck/example/sachs.py +129 -0
  52. ck/example/sprinkler.py +30 -0
  53. ck/example/star.py +44 -0
  54. ck/example/stress.py +64 -0
  55. ck/example/student.py +43 -0
  56. ck/example/survey.py +46 -0
  57. ck/example/triangle_square.py +54 -0
  58. ck/example/truss.py +49 -0
  59. ck/in_out/__init__.py +3 -0
  60. ck/in_out/parse_ace_lmap.py +216 -0
  61. ck/in_out/parse_ace_nnf.py +322 -0
  62. ck/in_out/parse_net.py +480 -0
  63. ck/in_out/parser_utils.py +185 -0
  64. ck/in_out/pgm_pickle.py +42 -0
  65. ck/in_out/pgm_python.py +268 -0
  66. ck/in_out/render_bugs.py +111 -0
  67. ck/in_out/render_net.py +177 -0
  68. ck/in_out/render_pomegranate.py +184 -0
  69. ck/pgm.py +3475 -0
  70. ck/pgm_circuit/__init__.py +1 -0
  71. ck/pgm_circuit/marginals_program.py +352 -0
  72. ck/pgm_circuit/mpe_program.py +237 -0
  73. ck/pgm_circuit/pgm_circuit.py +79 -0
  74. ck/pgm_circuit/program_with_slotmap.py +236 -0
  75. ck/pgm_circuit/slot_map.py +35 -0
  76. ck/pgm_circuit/support/__init__.py +0 -0
  77. ck/pgm_circuit/support/compile_circuit.py +83 -0
  78. ck/pgm_circuit/target_marginals_program.py +103 -0
  79. ck/pgm_circuit/wmc_program.py +323 -0
  80. ck/pgm_compiler/__init__.py +2 -0
  81. ck/pgm_compiler/ace/__init__.py +1 -0
  82. ck/pgm_compiler/ace/ace.py +299 -0
  83. ck/pgm_compiler/factor_elimination.py +395 -0
  84. ck/pgm_compiler/named_pgm_compilers.py +63 -0
  85. ck/pgm_compiler/pgm_compiler.py +19 -0
  86. ck/pgm_compiler/recursive_conditioning.py +231 -0
  87. ck/pgm_compiler/support/__init__.py +0 -0
  88. ck/pgm_compiler/support/circuit_table/__init__.py +17 -0
  89. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.c +16396 -0
  90. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cp312-win32.pyd +0 -0
  91. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.pyx +332 -0
  92. ck/pgm_compiler/support/circuit_table/_circuit_table_py.py +304 -0
  93. ck/pgm_compiler/support/clusters.py +568 -0
  94. ck/pgm_compiler/support/factor_tables.py +406 -0
  95. ck/pgm_compiler/support/join_tree.py +332 -0
  96. ck/pgm_compiler/support/named_compiler_maker.py +43 -0
  97. ck/pgm_compiler/variable_elimination.py +91 -0
  98. ck/probability/__init__.py +0 -0
  99. ck/probability/empirical_probability_space.py +50 -0
  100. ck/probability/pgm_probability_space.py +32 -0
  101. ck/probability/probability_space.py +622 -0
  102. ck/program/__init__.py +3 -0
  103. ck/program/program.py +137 -0
  104. ck/program/program_buffer.py +180 -0
  105. ck/program/raw_program.py +67 -0
  106. ck/sampling/__init__.py +0 -0
  107. ck/sampling/forward_sampler.py +211 -0
  108. ck/sampling/marginals_direct_sampler.py +113 -0
  109. ck/sampling/sampler.py +62 -0
  110. ck/sampling/sampler_support.py +232 -0
  111. ck/sampling/uniform_sampler.py +72 -0
  112. ck/sampling/wmc_direct_sampler.py +171 -0
  113. ck/sampling/wmc_gibbs_sampler.py +153 -0
  114. ck/sampling/wmc_metropolis_sampler.py +165 -0
  115. ck/sampling/wmc_rejection_sampler.py +115 -0
  116. ck/utils/__init__.py +0 -0
  117. ck/utils/iter_extras.py +163 -0
  118. ck/utils/local_config.py +270 -0
  119. ck/utils/map_list.py +128 -0
  120. ck/utils/map_set.py +128 -0
  121. ck/utils/np_extras.py +51 -0
  122. ck/utils/random_extras.py +64 -0
  123. ck/utils/tmp_dir.py +94 -0
  124. ck_demos/__init__.py +0 -0
  125. ck_demos/ace/__init__.py +0 -0
  126. ck_demos/ace/copy_ace_to_ck.py +15 -0
  127. ck_demos/ace/demo_ace.py +49 -0
  128. ck_demos/all_demos.py +88 -0
  129. ck_demos/circuit/__init__.py +0 -0
  130. ck_demos/circuit/demo_circuit_dump.py +22 -0
  131. ck_demos/circuit/demo_derivatives.py +43 -0
  132. ck_demos/circuit_compiler/__init__.py +0 -0
  133. ck_demos/circuit_compiler/compare_circuit_compilers.py +32 -0
  134. ck_demos/circuit_compiler/show_llvm_program.py +26 -0
  135. ck_demos/pgm/__init__.py +0 -0
  136. ck_demos/pgm/demo_pgm_dump.py +18 -0
  137. ck_demos/pgm/demo_pgm_dump_stress.py +18 -0
  138. ck_demos/pgm/demo_pgm_string_rendering.py +15 -0
  139. ck_demos/pgm/show_examples.py +25 -0
  140. ck_demos/pgm_compiler/__init__.py +0 -0
  141. ck_demos/pgm_compiler/compare_pgm_compilers.py +63 -0
  142. ck_demos/pgm_compiler/demo_compiler_dump.py +60 -0
  143. ck_demos/pgm_compiler/demo_factor_elimination.py +47 -0
  144. ck_demos/pgm_compiler/demo_join_tree.py +25 -0
  145. ck_demos/pgm_compiler/demo_marginals_program.py +53 -0
  146. ck_demos/pgm_compiler/demo_mpe_program.py +55 -0
  147. ck_demos/pgm_compiler/demo_pgm_compiler.py +38 -0
  148. ck_demos/pgm_compiler/demo_recursive_conditioning.py +33 -0
  149. ck_demos/pgm_compiler/demo_variable_elimination.py +33 -0
  150. ck_demos/pgm_compiler/demo_wmc_program.py +29 -0
  151. ck_demos/pgm_compiler/time_fe_compiler.py +93 -0
  152. ck_demos/pgm_inference/__init__.py +0 -0
  153. ck_demos/pgm_inference/demo_inferencing_basic.py +188 -0
  154. ck_demos/pgm_inference/demo_inferencing_mpe_cancer.py +45 -0
  155. ck_demos/pgm_inference/demo_inferencing_wmc_and_mpe_sprinkler.py +154 -0
  156. ck_demos/pgm_inference/demo_inferencing_wmc_student.py +110 -0
  157. ck_demos/programs/__init__.py +0 -0
  158. ck_demos/programs/demo_program_buffer.py +24 -0
  159. ck_demos/programs/demo_program_multi.py +24 -0
  160. ck_demos/programs/demo_program_none.py +19 -0
  161. ck_demos/programs/demo_program_single.py +23 -0
  162. ck_demos/programs/demo_raw_program_interpreted.py +21 -0
  163. ck_demos/programs/demo_raw_program_llvm.py +21 -0
  164. ck_demos/sampling/__init__.py +0 -0
  165. ck_demos/sampling/check_sampler.py +71 -0
  166. ck_demos/sampling/demo_marginal_direct_sampler.py +40 -0
  167. ck_demos/sampling/demo_uniform_sampler.py +38 -0
  168. ck_demos/sampling/demo_wmc_direct_sampler.py +40 -0
  169. ck_demos/utils/__init__.py +0 -0
  170. ck_demos/utils/compare.py +120 -0
  171. ck_demos/utils/convert_network.py +45 -0
  172. ck_demos/utils/sample_model.py +216 -0
  173. ck_demos/utils/stop_watch.py +384 -0
  174. compiled_knowledge-4.0.0a20.dist-info/METADATA +50 -0
  175. compiled_knowledge-4.0.0a20.dist-info/RECORD +178 -0
  176. compiled_knowledge-4.0.0a20.dist-info/WHEEL +5 -0
  177. compiled_knowledge-4.0.0a20.dist-info/licenses/LICENSE.txt +21 -0
  178. compiled_knowledge-4.0.0a20.dist-info/top_level.txt +2 -0
@@ -0,0 +1,270 @@
1
+ """
2
+ This module provides access to local configuration variables.
3
+
4
+ Local configuration variables are {variable} = {value} pairs that
5
+ are defined externally to CK for the purposes of adapting
6
+ to the local environment that CK is installed in. Local
7
+ configuration variables are not expected to modify the
8
+ behaviour of algorithms implemented in CK.
9
+
10
+ The primary method to access local configuration is `get`. Various
11
+ other getter methods wrap `get`.
12
+
13
+ The `get` method will search for a value for a requested variable
14
+ using the following steps.
15
+ 1) Check the `programmatic config` which is a dictionary that
16
+ can be directly updated.
17
+ 2) Check the PYTHONPATH for a module called `config` (i.e., a
18
+ `config.py` file) for global variables defined in that module.
19
+ 3) Check the system environment variables (`os.environ`).
20
+
21
+ Variable names must be a valid Python identifier. Only valid
22
+ value types are supported, as per the function `valid_value`.
23
+
24
+ Usage:
25
+ from ck.utils.local_config import config
26
+
27
+ # assume `config.py` is in the PYTHONPATH and contains:
28
+ # ABC = 123
29
+ # DEF = 456
30
+
31
+ val = config.ABC # val = 123
32
+ val = config.XYZ # will raise an exception
33
+ val = config.get('ABC') # val = 123
34
+ val = config['DEF'] # val = 456
35
+ val = config['XYZ'] # will raise an exception
36
+ val = config.get('XYZ') # val = None
37
+ val = config.get('XYZ', 999) # val = 999
38
+
39
+ from ck.utils.local_config import get_params
40
+
41
+ val = get_params('ABC') # val = ('ABC', 123)
42
+ val = get_params('ABC', 'DEF') # val = (('ABC', 123), ('DEF', 456))
43
+ val = get_params('ABC', 'DEF', sep='=') # val = ('ABC=123', 'DEF=456')
44
+ val = get_params('ABC;DEF', delim=';') # val = 'ABC=123;DEF=456'
45
+
46
+ """
47
+
48
+ import inspect
49
+ import os
50
+ from ast import literal_eval
51
+ from itertools import chain
52
+ from typing import Optional, Dict, Any, Sequence, Iterable
53
+
54
+ from ck.utils.iter_extras import flatten
55
+
56
+ try:
57
+ # Try to import the user's `config.py`
58
+ import config as _user_config
59
+ except ImportError:
60
+ _user_config = None
61
+
62
+ # Sentinel object
63
+ _NIL = object()
64
+
65
+
66
+ class Config:
67
+
68
+ def __init__(self):
69
+ self._programmatic_config: Dict[str, Any] = {}
70
+
71
+ def get(self, key: str, default: Any = None) -> Any:
72
+ """
73
+ Get the value of the given local configuration variable.
74
+ If the configuration variable is not available, return the given default value.
75
+ """
76
+ if not key.isidentifier():
77
+ raise KeyError(f'invalid local configuration parameter: {key!r}')
78
+
79
+ # Check the programmatic config
80
+ value = self._programmatic_config.get(key, _NIL)
81
+ if value is not _NIL:
82
+ return value
83
+
84
+ # Check config.py
85
+ if _user_config is not None:
86
+ value = vars(_user_config).get(key, _NIL)
87
+ if value is not _NIL:
88
+ if not valid_value(value):
89
+ raise KeyError(f'user configuration file contains an invalid value for variable: {key!r}')
90
+ return value
91
+
92
+ # Check the OS environment
93
+ value = os.environ.get(key, _NIL)
94
+ if value is not _NIL:
95
+ return value
96
+
97
+ # Not found - return the default value
98
+ return default
99
+
100
+ def __contains__(self, key: str) -> bool:
101
+ return self.get(key, _NIL) is not _NIL
102
+
103
+ def __setitem__(self, key: str, value: Any) -> None:
104
+ """
105
+ Programmatically overwrite a local configuration variable.
106
+ """
107
+ if not key.isidentifier():
108
+ raise KeyError(f'invalid local configuration parameter: {key!r}')
109
+ if not valid_value(value):
110
+ raise ValueError(f'invalid local configuration parameter value: {value!r}')
111
+ self._programmatic_config[key] = value
112
+
113
+ def __getitem__(self, key: str):
114
+ """
115
+ Get the value of the given configuration variable.
116
+ If the configuration variable is not available, raise a KeyError.
117
+ """
118
+ value = self.get(key, _NIL)
119
+ if value is _NIL:
120
+ raise KeyError(f'undefined local configuration parameter: {key}')
121
+ return value
122
+
123
+ def __getattr__(self, key: str):
124
+ """
125
+ Get the value of the given configuration variable.
126
+ If the configuration variable is not available, raise a KeyError.
127
+ """
128
+ value = self.get(key, _NIL)
129
+ if value is _NIL:
130
+ raise KeyError(f'undefined local configuration parameter: {key}')
131
+ return value
132
+
133
+
134
+ # The global local config object.
135
+ config = Config()
136
+
137
+
138
+ def valid_value(value: Any) -> bool:
139
+ """
140
+ Does the given value have an acceptable type for
141
+ a configuration variable?
142
+ """
143
+ if isinstance(value, (list, tuple, set)):
144
+ return all(valid_value(elem) for elem in value)
145
+ if isinstance(value, dict):
146
+ return all(valid_value(elem) for elem in chain(value.keys(), value.values()))
147
+ if callable(value) or inspect.isfunction(value) or inspect.ismodule(value):
148
+ return False
149
+ # All tests pass
150
+ return True
151
+
152
+
153
+ # noinspection PyShadowingNames
154
+ def get_params(
155
+ *keys: str,
156
+ sep: Optional[str] = None,
157
+ delim: Optional[str] = None,
158
+ config: Config = config,
159
+ ):
160
+ """
161
+ Return one or more configuration parameter as key-value pairs.
162
+
163
+ If `sep` is None then each key-value pair is returned as a tuple, otherwise
164
+ each key-value pair is returned as a string with `sep` as the separator.
165
+
166
+ If `delim` is None then each key is treated as is. If one key is provided then
167
+ its value is returned. If multiple keys are provided, then multiple values
168
+ are returned in a tuple.
169
+
170
+ If `delim` is not None, then keys are split using `delim`, and results
171
+ are returned as a single string with `delim` as the delimiter. If
172
+ `delim` is not None then the default value for `sep` is '='.
173
+
174
+ For example, assume config.py contains: ABC = 123 and DEF = 456,
175
+ then:
176
+ get_params('ABC') -> ('ABC', 123)
177
+ get_params('ABC', 'DEF') -> ('ABC', 123), ('DEF', 456)
178
+ get_params('ABC', sep='=') = 'ABC=123'
179
+ get_params('ABC', 'DEF', sep='=') = 'ABC=123', 'DEF=456'
180
+ get_params('ABC;DEF', delim=';') = 'ABC=123;DEF=456'
181
+ get_params('ABC;DEF', sep='==', delim=';') = 'ABC==123;DEF==456'
182
+
183
+ :param keys: the names of variables to access.
184
+ :param sep: the separator character between {variable} and {value}.
185
+ :param delim: the delimiter character between key-value pairs.
186
+ :param config: a Config instance to update. Default is the global config.
187
+ """
188
+ if delim is not None:
189
+ keys = flatten(key.split(delim) for key in keys)
190
+ if sep is None:
191
+ sep = '='
192
+
193
+ if sep is None:
194
+ items = ((key, config[key]) for key in keys)
195
+ else:
196
+ items = (f'{key}{sep}{config[key]!r}' for key in keys)
197
+
198
+ if delim is None:
199
+ result = tuple(items)
200
+ if len(result) == 1:
201
+ result = result[0]
202
+ else:
203
+ result = delim.join(str(item) for item in items)
204
+
205
+ return result
206
+
207
+
208
+ # noinspection PyShadowingNames
209
+ def update_config(
210
+ argv: Sequence[str],
211
+ valid_keys: Optional[Iterable[str]] = None,
212
+ *,
213
+ sep: str = '=',
214
+ strip_whitespace: bool = True,
215
+ config: Config = config,
216
+ ) -> None:
217
+ """
218
+ Programmatically overwrite a local configuration variable from a command line `argv`.
219
+
220
+ Variable values are interpreted as per a Python literal.
221
+
222
+ Example usage:
223
+ import sys
224
+ from ck.utils.local_config import update_config
225
+
226
+ def main():
227
+ ...
228
+
229
+ if __name__ == '__main__':
230
+ update_config(sys.argv, ['in_name', 'out_name'])
231
+ main()
232
+
233
+ :param argv: a collection of strings in the form '{variable}={value}'.
234
+ Variables not in `valid_keys` will raise a ValueError.
235
+ :param valid_keys: an optional collection of strings that are valid variables to
236
+ process from argv, or None to accept all variables.
237
+ :param sep: the separator character between {variable} and {value}.
238
+ Defaults is '='.
239
+ :param strip_whitespace: If True, then whitespace is stripped from
240
+ the value before updating the config. Whitespace is always stripped
241
+ from the variable name.
242
+ :param config: a Config instance to update. Default is the global config.
243
+ """
244
+ if valid_keys is not None:
245
+ valid_keys = set(valid_keys)
246
+
247
+ for arg in argv:
248
+ var_val = str(arg).split(sep, maxsplit=1)
249
+ if len(var_val) != 2:
250
+ raise ValueError(f'cannot split argument: {arg!r} using separator {sep!r}')
251
+
252
+ var, val = var_val
253
+ var = var.strip()
254
+ if strip_whitespace:
255
+ val = val.strip()
256
+
257
+ if valid_keys is not None and var not in valid_keys:
258
+ raise KeyError(f'invalid key: {arg!r}')
259
+
260
+ try:
261
+ interpreted = literal_eval(val)
262
+ except (ValueError, SyntaxError) as err:
263
+ # Some operating systems strip quotes off
264
+ # strings, so we try to recover.
265
+ if '"' in val or "'" in val:
266
+ # Too hard... forget it.
267
+ raise err
268
+ interpreted = str(val)
269
+
270
+ config[var] = interpreted
ck/utils/map_list.py ADDED
@@ -0,0 +1,128 @@
1
+ """
2
+ This module defines a class "MapList" for mapping keys to lists.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import TypeVar, Generic, List, Dict, MutableMapping, KeysView, ValuesView, ItemsView, Iterable, Iterator
7
+
8
+ _K = TypeVar('_K')
9
+ _V = TypeVar('_V')
10
+
11
+
12
+ class MapList(Generic[_K, _V], MutableMapping[_K, List[_V]]):
13
+ """
14
+ A MapList keeps a list for each key, unlike a dict which keeps only
15
+ a single element for each key.
16
+ """
17
+ __slots__ = ('_map', )
18
+
19
+ def __init__(self, *args, **kwargs):
20
+ self._map: Dict[_K, List[_V]] = {}
21
+ self.update(*args, extend=False, **kwargs)
22
+
23
+ def __str__(self) -> str:
24
+ return str(self._map)
25
+
26
+ def __repr__(self) -> str:
27
+ args = ', '.join(f'{key!r}:{val!r}' for key, val in self.items())
28
+ class_name = self.__class__.__name__
29
+ return f'{class_name}({args})'
30
+
31
+ def __len__(self) -> int:
32
+ return len(self._map)
33
+
34
+ def __bool__(self) -> bool:
35
+ return len(self) > 0
36
+
37
+ def __getitem__(self, key) -> List[_V]:
38
+ return self._map[key]
39
+
40
+ def __setitem__(self, key: _K, val: List[_V]):
41
+ if not isinstance(val, list):
42
+ class_name = self.__class__.__name__
43
+ raise RuntimeError(f'every {class_name} value must be a list')
44
+ self._map[key] = val
45
+
46
+ def __delitem__(self, key: _K):
47
+ del self._map[key]
48
+
49
+ def __iter__(self) -> Iterator[_K]:
50
+ return iter(self._map)
51
+
52
+ def __contains__(self, key: _K) -> bool:
53
+ return key in self._map
54
+
55
+ def update(self, *args, extend=False, **kwargs):
56
+ k: _K
57
+ v: List[_V]
58
+ if extend:
59
+ for k, v in dict(*args, **kwargs).items():
60
+ self.extend(k, v)
61
+ else:
62
+ for k, v in dict(*args, **kwargs).items():
63
+ self[k] = v
64
+
65
+ def keys(self) -> KeysView[_K]:
66
+ return self._map.keys()
67
+
68
+ def values(self) -> ValuesView[List[_V]]:
69
+ return self._map.values()
70
+
71
+ def items(self) -> ItemsView[_K, List[_V]]:
72
+ return self._map.items()
73
+
74
+ def get(self, key: _K, default=None):
75
+ """
76
+ Get the list corresponding to the given key.
77
+ If the key is not yet in the MapList then the
78
+ supplied default will be returned.
79
+ """
80
+ return self._map.get(key, default)
81
+
82
+ def get_list(self, key: _K) -> List[_V]:
83
+ """
84
+ Get the list corresponding to the given key.
85
+
86
+ This method will always return a list in the MapList, even if
87
+ it requires a new list being created.
88
+
89
+ Modifying the returned list affects this MapList object.
90
+ """
91
+ the_list = self._map.get(key)
92
+ if the_list is None:
93
+ the_list = []
94
+ self._map[key] = the_list
95
+ return the_list
96
+
97
+ def append(self, key: _K, item: _V):
98
+ """
99
+ Append the given item to the list identified by the given key.
100
+ """
101
+ self.get_list(key).append(item)
102
+
103
+ def extend(self, key: _K, items: Iterable[_V]):
104
+ """
105
+ Extend the given item to the list identified by the given key.
106
+ """
107
+ return self.get_list(key).extend(items)
108
+
109
+ def extend_map_list(self, map_list: MapList[_K, _V]):
110
+ """
111
+ Add all the keyed given items to the list identified by each key.
112
+ """
113
+ for key, items in map_list.items():
114
+ self.extend(key, items)
115
+
116
+ def clear(self):
117
+ """
118
+ Remove all items.
119
+ """
120
+ return self._map.clear()
121
+
122
+ def clear_empty(self):
123
+ """
124
+ Remove all empty values.
125
+ """
126
+ keys_to_remove = [key for key, value in self._map.items() if len(value) == 0]
127
+ for key in keys_to_remove:
128
+ del self._map[key]
ck/utils/map_set.py ADDED
@@ -0,0 +1,128 @@
1
+ """
2
+ This module defines a class "MapSet" for mapping keys to sets.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from typing import TypeVar, Generic, Set, Dict, MutableMapping, Iterator, KeysView, ValuesView, ItemsView, Iterable
7
+
8
+ _K = TypeVar('_K')
9
+ _V = TypeVar('_V')
10
+
11
+
12
+ class MapSet(Generic[_K, _V], MutableMapping[_K, Set[_V]]):
13
+ """
14
+ A MapSet keeps a set for each key, unlike a dict which keeps only
15
+ a single element for each key.
16
+ """
17
+ __slots__ = ('_map',)
18
+
19
+ def __init__(self, *args, **kwargs):
20
+ self._map: Dict[_K, Set[_V]] = {}
21
+ self.update(*args, add_all=False, **kwargs)
22
+
23
+ def __str__(self) -> str:
24
+ return str(self._map)
25
+
26
+ def __repr__(self) -> str:
27
+ args = ', '.join(f'{key!r}:{val!r}' for key, val in self.items())
28
+ class_name = self.__class__.__name__
29
+ return f'{class_name}({args})'
30
+
31
+ def __len__(self) -> int:
32
+ return len(self._map)
33
+
34
+ def __bool__(self) -> bool:
35
+ return len(self) > 0
36
+
37
+ def __getitem__(self, key: _K) -> Set[_V]:
38
+ return self._map[key]
39
+
40
+ def __setitem__(self, key: _K, val: Set[_V]):
41
+ if not isinstance(val, set):
42
+ class_name = self.__class__.__name__
43
+ raise RuntimeError(f'every {class_name} value must be a set')
44
+ self._map[key] = val
45
+
46
+ def __delitem__(self, key: _K):
47
+ del self._map[key]
48
+
49
+ def __iter__(self) -> Iterator[_K]:
50
+ return iter(self._map)
51
+
52
+ def __contains__(self, key: _K) -> bool:
53
+ return key in self._map
54
+
55
+ def update(self, *args, add_all=True, **kwargs):
56
+ k: _K
57
+ v: Set[_V]
58
+ if add_all:
59
+ for k, v in dict(*args, **kwargs).items():
60
+ self.add_all(k, v)
61
+ else:
62
+ for k, v in dict(*args, **kwargs).items():
63
+ self[k] = v
64
+
65
+ def keys(self) -> KeysView[_K]:
66
+ return self._map.keys()
67
+
68
+ def values(self) -> ValuesView[Set[_V]]:
69
+ return self._map.values()
70
+
71
+ def items(self) -> ItemsView[_K, Set[_V]]:
72
+ return self._map.items()
73
+
74
+ def get(self, key: _K, default=None):
75
+ """
76
+ Get the set corresponding to the given key.
77
+ If the key is not yet in the MapSet then the
78
+ supplied default will be returned.
79
+ """
80
+ return self._map.get(key, default)
81
+
82
+ def get_set(self, key: _K) -> Set[_V]:
83
+ """
84
+ Get the set corresponding to the given key.
85
+
86
+ This method will always return a set in the MapSet, even if
87
+ it requires a new set being created.
88
+
89
+ Modifying the returned set affects this MapSet object.
90
+ """
91
+ the_set = self._map.get(key)
92
+ if the_set is None:
93
+ the_set = set()
94
+ self._map[key] = the_set
95
+ return the_set
96
+
97
+ def add(self, key: _K, item: _V):
98
+ """
99
+ Add the given item to the set identified by the given key.
100
+ """
101
+ self.get_set(key).add(item)
102
+
103
+ def add_all(self, key: _K, items: Iterable[_V]):
104
+ """
105
+ Add all the given items to the set identified by the given key.
106
+ """
107
+ return self.get_set(key).update(items)
108
+
109
+ def add_map_set(self, map_set: MapSet[_K, _V]):
110
+ """
111
+ Add all the keyed given items to the set identified by each key.
112
+ """
113
+ for key, items in map_set.items():
114
+ self.add_all(key, items)
115
+
116
+ def clear(self):
117
+ """
118
+ Remove all items.
119
+ """
120
+ return self._map.clear()
121
+
122
+ def clear_empty(self):
123
+ """
124
+ Remove all empty values.
125
+ """
126
+ keys_to_remove = [key for key, value in self._map.items() if len(value) == 0]
127
+ for key in keys_to_remove:
128
+ del self._map[key]
ck/utils/np_extras.py ADDED
@@ -0,0 +1,51 @@
1
+ # Type used by numpy and ctypes for data type definition
2
+
3
+ import numpy as np
4
+
5
+ # A numpy/ctypes data type.
6
+ DType = np.typing.DTypeLike
7
+
8
+ # A numpy data type for holding state indexes.
9
+ DTypeStates = np.dtypes.UInt8DType | np.dtypes.UInt16DType | np.dtypes.UInt32DType | np.dtypes.UInt64DType
10
+
11
+ DTypeNumeric = (
12
+ np.dtypes.Float64DType | np.dtypes.Float32DType
13
+ | np.dtypes.Int8DType | np.dtypes.Int16DType | np.dtypes.Int32DType | np.dtypes.Int64DType
14
+ | np.dtypes.UInt8DType | np.dtypes.UInt16DType | np.dtypes.UInt32DType | np.dtypes.UInt64DType
15
+ )
16
+
17
+ # A numpy array data type.
18
+ NDArray = np.typing.NDArray
19
+
20
+ NDArrayUInt8 = NDArray[np.dtypes.UInt8DType]
21
+ NDArrayUInt16 = NDArray[np.dtypes.UInt16DType]
22
+ NDArrayUInt32 = NDArray[np.dtypes.UInt32DType]
23
+ NDArrayUInt64 = NDArray[np.dtypes.UInt64DType]
24
+
25
+ NDArrayFloat64 = NDArray[np.dtypes.Float64DType]
26
+
27
+ NDArrayStates = NDArray[DTypeStates]
28
+ NDArrayNumeric = NDArray[DTypeNumeric]
29
+
30
+
31
+ # Constants for maximum number of states.
32
+ _MAX_STATES_8: int = 2 ** 8 - 1
33
+ _MAX_STATES_16: int = 2 ** 16 - 1
34
+ _MAX_STATES_32: int = 2 ** 32 - 1
35
+ _MAX_STATES_64: int = 2 ** 64 - 1
36
+
37
+
38
+ def dtype_for_number_of_states(number_of_states: int) -> DTypeStates:
39
+ """
40
+ Infer the numpy dtype required to store any state index of the given PGM.
41
+ """
42
+ # Infer the needed size of
43
+ if number_of_states <= _MAX_STATES_8:
44
+ return np.uint8
45
+ if number_of_states <= _MAX_STATES_16:
46
+ return np.uint16
47
+ if number_of_states <= _MAX_STATES_32:
48
+ return np.uint32
49
+ if number_of_states <= _MAX_STATES_64:
50
+ return np.uint64
51
+ raise ValueError(f'cannot determine dtype for the given number of states: {number_of_states!r}')
@@ -0,0 +1,64 @@
1
+ """
2
+ A module with extra randomisation functions.
3
+ """
4
+ import random
5
+ from typing import Protocol, Sequence, Tuple, Any, List
6
+
7
+
8
+ class Random(Protocol):
9
+ """
10
+ A minimum protocol for a random number generator as used by CK.
11
+ The usual `random` package implements this protocol.
12
+ """
13
+
14
+ def random(self) -> float:
15
+ """
16
+ Returns a random float in the interval [0, 1), includes 0, excludes 1.
17
+ """
18
+ ...
19
+
20
+ def randrange(self, a: int, b: int) -> int:
21
+ """
22
+ Returns a random integer in interval [a, b), includes `a`, excludes `b`.
23
+ """
24
+ ...
25
+
26
+
27
+ def random_pair(size: int, rand: Random = random) -> Tuple[int, int]:
28
+ """
29
+ Return a random pair (i, j) where:
30
+ 0 <= i < size,
31
+ 0 <= j < size,
32
+ i != j.
33
+ """
34
+ i = rand.randrange(0, size)
35
+ j = (i + rand.randrange(1, size)) % size
36
+ return i, j
37
+
38
+
39
+ def random_permute(items: List[Any], rand: Random = random) -> None:
40
+ """
41
+ Randomly permute the given items.
42
+ For a list of length `n`, this method calls rand.randrange(...) `n - 1` times.
43
+
44
+ There is a numpy method to do this, but it uses its own random number generator.
45
+ """
46
+ for i in range(len(items) - 1, 0, -1): # i = n - 1 down to 1
47
+ j = rand.randrange(0, i + 1) # 0 <= j <= i
48
+ items[i], items[j] = items[j], items[i] # exchange
49
+
50
+
51
+ def random_permutation(size: int, rand: Random = random) -> Sequence[int]:
52
+ """
53
+ Return a random permutation of the given size.
54
+
55
+ The returned list contains each integer 0 to size - 1
56
+ in a random order.
57
+
58
+ This method calls rand.randrange(...) 'size' times.
59
+
60
+ There is a numpy method to do this, but it uses its own random number generator.
61
+ """
62
+ result = list(range(size))
63
+ random_permute(result, rand)
64
+ return result