compiled-knowledge 4.0.0a20__cp312-cp312-macosx_10_13_x86_64.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 +37525 -0
  4. ck/circuit/_circuit_cy.cpython-312-darwin.so +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 +19826 -0
  13. ck/circuit_compiler/cython_vm_compiler/_compiler.cpython-312-darwin.so +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 +10620 -0
  23. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.cpython-312-darwin.so +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 +16398 -0
  90. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cpython-312-darwin.so +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 +6 -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
ck/program/program.py ADDED
@@ -0,0 +1,137 @@
1
+ """
2
+ For more documentation on this module, refer to the Jupyter notebook docs/6_circuits_and_programs.ipynb.
3
+ """
4
+ from typing import Callable, Sequence
5
+
6
+ import numpy as np
7
+
8
+ from ck.program.raw_program import RawProgram
9
+ from ck.utils.np_extras import DTypeNumeric, NDArrayNumeric
10
+
11
+
12
+ class Program:
13
+ """
14
+ A program represents an arithmetic a function from input values to output values.
15
+
16
+ Internally a `Program` wraps a `RawProgram` which is the object returned by a circuit compiler.
17
+
18
+ Every `Program` has a numpy `dtype` which defines the numeric data type for input and output values.
19
+ Typically, the `dtype` of a program is a C style double.
20
+ """
21
+
22
+ def __init__(self, raw_program: RawProgram):
23
+ self._raw_program = raw_program
24
+ self.__call = self.__get_call_method()
25
+
26
+ @property
27
+ def dtype(self) -> DTypeNumeric:
28
+ """
29
+ What is the C data type of values.
30
+ This is the same as numpy and ctypes `dtype`.
31
+ """
32
+ return self._raw_program.dtype
33
+
34
+ @property
35
+ def number_of_vars(self) -> int:
36
+ """
37
+ How many input values are there to the function.
38
+ Each input value relates to a circuit VarNode, as
39
+ per method `var_indices`.
40
+
41
+ Returns:
42
+ the number of input values.
43
+ """
44
+ return self._raw_program.number_of_vars
45
+
46
+ @property
47
+ def number_of_tmps(self) -> int:
48
+ """
49
+ How many temporary values are there to the function.
50
+
51
+ Returns:
52
+ the number of temporary values.
53
+ """
54
+ return self._raw_program.number_of_tmps
55
+
56
+ @property
57
+ def number_of_results(self) -> int:
58
+ """
59
+ How many output values are there from the function.
60
+
61
+ Returns:
62
+ the number of output values.
63
+ """
64
+ return self._raw_program.number_of_results
65
+
66
+ @property
67
+ def var_indices(self) -> Sequence[int]:
68
+ """
69
+ Get the circuit `VarNode.index` for each function input.
70
+
71
+ Returns:
72
+ a list of the circuit VarNode indices, co-indexed
73
+ with the function input values.
74
+ """
75
+ return self._raw_program.var_indices
76
+
77
+ def __call__(self, *args: float | int) -> NDArrayNumeric | int | float:
78
+ """
79
+ Call the compiled program.
80
+
81
+ Returns:
82
+ either a single value or a numpy array, depending
83
+ on the construction arguments.
84
+
85
+ Raises:
86
+ ValueError: if the given number of argument != self.number_of_vars.
87
+ """
88
+ # dynamically defined at construction time
89
+ return self.__call(*args)
90
+
91
+ def __call_single(self, *args: float) -> int | float:
92
+ """
93
+ Returns a single result of type self._dtype.value.dtype
94
+ """
95
+ return self._raw_program(args).item()
96
+
97
+ def __call_empty(self, *args: float | int) -> NDArrayNumeric:
98
+ """
99
+ Returns a numpy array result of dtype self.dtype
100
+ """
101
+ if len(args) != self.number_of_vars:
102
+ raise ValueError(f'incorrect number of arguments: expected {self.number_of_vars}, got {len(args)}')
103
+ np_out = np.zeros(0, dtype=self.dtype)
104
+ return np_out
105
+
106
+ def __call_multi(self, *args: float | int) -> NDArrayNumeric:
107
+ """
108
+ Returns a numpy array result of dtype self.dtype
109
+ """
110
+ return self._raw_program(args)
111
+
112
+ def __get_call_method(self) -> Callable:
113
+ """
114
+ Choose a call method based on self._number_of_results
115
+ """
116
+ match self.number_of_results:
117
+ case 0:
118
+ return self.__call_empty
119
+ case 1:
120
+ return self.__call_single
121
+ case _:
122
+ return self.__call_multi
123
+
124
+ def __getstate__(self):
125
+ """
126
+ Support for pickle.
127
+ """
128
+ return {
129
+ '_raw_program': self._raw_program,
130
+ }
131
+
132
+ def __setstate__(self, state):
133
+ """
134
+ Support for pickle.
135
+ """
136
+ self._raw_program = state['_raw_program']
137
+ self.__call = self.__get_call_method()
@@ -0,0 +1,180 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes as ct
4
+ from typing import Sequence
5
+
6
+ import numpy as np
7
+
8
+ from ck.program.raw_program import RawProgram, RawProgramFunction
9
+ from ck.utils.np_extras import DTypeNumeric, NDArrayNumeric
10
+
11
+
12
+ class ProgramBuffer:
13
+ """
14
+ A ProgramBuffer wraps a RawProgram with pre-allocated input, tmp, and out buffers.
15
+ The buffers are numpy arrays.
16
+ """
17
+
18
+ def __init__(self, program: RawProgram):
19
+ self._raw_program: RawProgram = program
20
+
21
+ # Allocate the buffers
22
+ self._array_vars = np.zeros(self.number_of_vars, dtype=self.dtype)
23
+ self._array_tmps = np.zeros(self.number_of_tmps, dtype=self.dtype)
24
+ self._array_outs = np.zeros(self.number_of_results, dtype=self.dtype)
25
+
26
+ # Access the c-buffers
27
+ ptr_type = ct.POINTER(np.ctypeslib.as_ctypes_type(self.dtype))
28
+ self._c_array_vars = self._array_vars.ctypes.data_as(ptr_type)
29
+ self._c_array_tmps = self._array_tmps.ctypes.data_as(ptr_type)
30
+ self._c_array_outs = self._array_outs.ctypes.data_as(ptr_type)
31
+
32
+ # Keep a direct reference to the internal callable.
33
+ self._function: RawProgramFunction = program.function
34
+
35
+ def clone(self) -> ProgramBuffer:
36
+ """
37
+ Take a copy of this program buffer, with the same raw program
38
+ and same input and output values, but with new memory allocation
39
+ for the buffers.
40
+ """
41
+ clone = ProgramBuffer(self._raw_program)
42
+ clone[:] = self._array_vars
43
+ clone.results[:] = self._array_outs
44
+ return clone
45
+
46
+ @property
47
+ def raw_program(self) -> RawProgram:
48
+ """
49
+ What is the wrapped Program.
50
+ """
51
+ return self._raw_program
52
+
53
+ @property
54
+ def dtype(self) -> DTypeNumeric:
55
+ """
56
+ What is the numpy data type of values.
57
+ This is the same as numpy and ctypes `dtype`.
58
+ """
59
+ return self._raw_program.dtype
60
+
61
+ @property
62
+ def number_of_vars(self) -> int:
63
+ """
64
+ How many input values are there to the function.
65
+ Each input value relates to a circuit VarNode, as
66
+ per method `var_indices`.
67
+
68
+ Returns:
69
+ the number of input values.
70
+ """
71
+ return self._raw_program.number_of_vars
72
+
73
+ @property
74
+ def number_of_tmps(self) -> int:
75
+ """
76
+ How many temporary values are there to the function.
77
+
78
+ Returns:
79
+ the number of temporary values.
80
+ """
81
+ return self._raw_program.number_of_tmps
82
+
83
+ @property
84
+ def number_of_results(self) -> int:
85
+ """
86
+ How many output values are there from the function.
87
+
88
+ Returns:
89
+ the number of output values.
90
+ """
91
+ return self._raw_program.number_of_results
92
+
93
+ @property
94
+ def var_indices(self) -> Sequence[int]:
95
+ """
96
+ Get the circuit `VarNode.index` for each function input.
97
+
98
+ Returns:
99
+ a list of the circuit VarNode indices, co-indexed
100
+ with the function input values.
101
+ """
102
+ return self._raw_program.var_indices
103
+
104
+ @property
105
+ def vars(self) -> NDArrayNumeric:
106
+ """
107
+ Return the input variables as a numpy array.
108
+ Writing to the returned array will write to the input slots of the program buffer.
109
+
110
+ Warning:
111
+ the array is backed by the program buffer memory, not a copy.
112
+ """
113
+ return self._array_vars
114
+
115
+ @property
116
+ def results(self) -> NDArrayNumeric:
117
+ """
118
+ Return the results as a numpy array.
119
+
120
+ Warning:
121
+ the array is backed by the program buffer memory, not a copy.
122
+ """
123
+ return self._array_outs
124
+
125
+ def compute(self) -> NDArrayNumeric:
126
+ """
127
+ Compute and return the results, as per `self.results`.
128
+
129
+ Warning:
130
+ the array is backed by the program buffer memory, not a copy.
131
+ """
132
+ self._function(self._c_array_vars, self._c_array_tmps, self._c_array_outs)
133
+ return self._array_outs
134
+
135
+ def __setitem__(self, idx: int | slice, value: float) -> None:
136
+ """
137
+ Set the value of the indexed input variable.
138
+ """
139
+ self._array_vars[idx] = value
140
+
141
+ def __getitem__(self, idx: int | slice) -> NDArrayNumeric:
142
+ """
143
+ Get the value of the indexed input variable.
144
+ """
145
+ return self._array_vars[idx]
146
+
147
+ def __len__(self) -> int:
148
+ """
149
+ Number of input variables.
150
+ """
151
+ return len(self._array_vars)
152
+
153
+ def __getstate__(self):
154
+ """
155
+ Support for pickle.
156
+ """
157
+ return {
158
+ '_raw_program': self._raw_program,
159
+ '_array_vars': self._array_vars,
160
+ '_array_tmps': self._array_tmps,
161
+ '_array_outs': self._array_outs,
162
+ }
163
+
164
+ def __setstate__(self, state):
165
+ """
166
+ Support for pickle.
167
+ """
168
+ self._raw_program = state['_raw_program']
169
+ self._array_vars = state['_array_vars']
170
+ self._array_tmps = state['_array_tmps']
171
+ self._array_outs = state['_array_outs']
172
+
173
+ # Access the c-buffers
174
+ ptr_type = ct.POINTER(self.dtype)
175
+ self._c_array_vars = self._array_vars.ctypes.data_as(ptr_type)
176
+ self._c_array_tmps = self._array_tmps.ctypes.data_as(ptr_type)
177
+ self._c_array_outs = self._array_outs.ctypes.data_as(ptr_type)
178
+
179
+ # Keep a direct reference to the internal callable.
180
+ self._function: RawProgramFunction = self._raw_program.function
@@ -0,0 +1,67 @@
1
+ from dataclasses import dataclass
2
+ from typing import Callable, Sequence
3
+
4
+ import numpy as np
5
+ import ctypes as ct
6
+
7
+
8
+ from ck.utils.np_extras import NDArrayNumeric, DTypeNumeric
9
+
10
+ # RawProgramFunction is a function of three ctypes arrays, returning nothing.
11
+ # Args:
12
+ # [0]: input values,
13
+ # [1]: temporary working memory,
14
+ # [2]: output values.
15
+ RawProgramFunction = Callable[[ct.POINTER, ct.POINTER, ct.POINTER], None]
16
+
17
+
18
+ @dataclass
19
+ class RawProgram:
20
+ """
21
+ A raw program is returned by a circuit compiler to provide execution of
22
+ the function defined by a compiled circuit.
23
+
24
+ A `RawProgram` is a `Callable` with the signature:
25
+
26
+
27
+ Fields:
28
+ function: is a function of three ctypes arrays, returning nothing.
29
+ dtype: the numpy data type of the array values.
30
+ number_of_vars: the number of input values (first function argument).
31
+ number_of_tmps: the number of working memory values (second function argument).
32
+ number_of_results: the number of result values (third function argument).
33
+ var_indices: maps the index of inputs (from 0 to self.number_of_vars - 1) to the index
34
+ of the corresponding circuit var.
35
+ """
36
+
37
+ function: RawProgramFunction
38
+ dtype: DTypeNumeric
39
+ number_of_vars: int
40
+ number_of_tmps: int
41
+ number_of_results: int
42
+ var_indices: Sequence[int]
43
+
44
+ def __call__(self, in_vars: NDArrayNumeric | Sequence[int | float]) -> NDArrayNumeric:
45
+ """
46
+ Call the raw program as a function from an array to an array.
47
+ """
48
+ array_vars: NDArrayNumeric
49
+ if isinstance(vars, np.ndarray):
50
+ array_vars = in_vars
51
+ else:
52
+ array_vars = np.array(in_vars, dtype=self.dtype)
53
+ if array_vars.shape != (self.number_of_vars,):
54
+ raise ValueError(f'input array incorrect shape: got {array_vars.shape} expected ({self.number_of_vars},)')
55
+ if array_vars.dtype != self.dtype:
56
+ raise ValueError(f'input array incorrect dtype: got {array_vars.dtype} expected {self.dtype}')
57
+
58
+ array_tmps: NDArrayNumeric = np.zeros(self.number_of_tmps, dtype=self.dtype)
59
+ array_outs: NDArrayNumeric = np.zeros(self.number_of_results, dtype=self.dtype)
60
+
61
+ ptr_type = ct.POINTER(np.ctypeslib.as_ctypes_type(self.dtype))
62
+ c_array_vars = array_vars.ctypes.data_as(ptr_type)
63
+ c_array_tmps = array_tmps.ctypes.data_as(ptr_type)
64
+ c_array_outs = array_outs.ctypes.data_as(ptr_type)
65
+
66
+ self.function(c_array_vars, c_array_tmps, c_array_outs)
67
+ return array_outs
File without changes
@@ -0,0 +1,211 @@
1
+ """
2
+ This module defines a Sampler for Bayesian networks using
3
+ the Forward Sampling method.
4
+ """
5
+ import random
6
+ from dataclasses import dataclass
7
+ from typing import Optional, Iterator, Tuple, Sequence, Dict, List, Set
8
+
9
+ import numpy as np
10
+
11
+ from ck.pgm import PGM, RandomVariable, PotentialFunction, Instance, Indicator
12
+ from ck.probability.probability_space import Condition, check_condition, dtype_for_state_indexes
13
+ from ck.sampling.sampler import Sampler
14
+ from ck.sampling.sampler_support import YieldF
15
+ from ck.utils.np_extras import NDArrayStates, DTypeStates
16
+ from ck.utils.random_extras import Random
17
+
18
+
19
+ class ForwardSampler(Sampler):
20
+ """
21
+ A ForwardSampler operates directly on a Bayesian network PGM.
22
+ It does not compile the PGM but directly uses the CPTs.
23
+
24
+ It determines the parent random variables to sample (recursively)
25
+ and samples them in a 'forward' order.
26
+
27
+ Conditioning is implemented by rejecting samples incompatible with the condition,
28
+ thus if the probability of the condition is low, the sampler will reject
29
+ many samples, which may be slow.
30
+
31
+ When unconditioned, this sampler can be very efficient.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ pgm: PGM,
37
+ rvs: Optional[RandomVariable | Sequence[RandomVariable]] = None,
38
+ condition: Condition = (),
39
+ rand: Random = random,
40
+ check_is_bayesian_network: bool = True
41
+ ):
42
+ """
43
+ Construct a forward sampler for the given PGM.
44
+
45
+ Args:
46
+ pgm: is the model to be sampled. It is assumed to be a proper Bayesian network.
47
+ rvs: the list of random variables to sample; the yielded state vectors are
48
+ co-indexed with rvs; if None, then the WMC rvs are used; if rvs is a single
49
+ random variable, then single samples are yielded.
50
+ condition: is a collection of zero or more conditioning indicators.
51
+ rand: provides the stream of random numbers.
52
+ check_is_bayesian_network: is a Boolean flag. If true, then the
53
+ constructor will raise an exception if pgm.check_is_bayesian_network()
54
+ is false.
55
+ """
56
+ if check_is_bayesian_network and not pgm.check_is_bayesian_network():
57
+ raise ValueError('the given PGM is not a Bayesian network')
58
+
59
+ condition: Sequence[Indicator] = check_condition(condition)
60
+
61
+ self._yield_f: YieldF
62
+ if rvs is None:
63
+ # all input rvs
64
+ rvs = pgm.rvs
65
+ self._yield_f = lambda x: x.tolist()
66
+ elif rvs in pgm.rvs:
67
+ # a single rv
68
+ rvs = (rvs,)
69
+ self._yield_f = lambda x: x.item()
70
+ else:
71
+ # a sequence of rvs
72
+ self._yield_f = lambda x: x.tolist()
73
+
74
+ super().__init__(rvs, condition)
75
+
76
+ # Create a map from rv_idx to its factor.
77
+ # This assumes a Bayesian network structure - one factor per rv.
78
+ rv_factors = {
79
+ factor.rvs[0].idx: factor
80
+ for factor in pgm.factors
81
+ }
82
+
83
+ # Infer sampling order of random variables.
84
+ # Infer the mapping from random variables to yielded samples.
85
+ # Get a _SampleRV for each rv in rvs, and for pre-requisites.
86
+ # This assumes a Bayesian network structure - no directed loops.
87
+ sample_rvs_map: Dict[int, _SampleRV] = {} # map from rv index to _SampleRV object
88
+ sample_rvs_list: List[_SampleRV] = [] # sequence of _SampleRV objects for sampling order
89
+ output_sample_rvs: Sequence[_SampleRV] = tuple(
90
+ _get_sample_rv(rv.idx, rv_factors, sample_rvs_map, sample_rvs_list)
91
+ for rv in rvs
92
+ )
93
+
94
+ # Add constraints to sample rvs, based on conditions.
95
+ ind: Indicator
96
+ for ind in condition:
97
+ sample_rv: _SampleRV = _get_sample_rv(ind.rv_idx, rv_factors, sample_rvs_map, sample_rvs_list)
98
+ constraints = sample_rv.constraints
99
+ if constraints is None:
100
+ constraints = sample_rv.constraints = set()
101
+ constraints.add(ind.state_idx)
102
+
103
+ # Store needed data.
104
+ self._dtype: DTypeStates = dtype_for_state_indexes(pgm.rvs)
105
+ self._rand: Random = rand
106
+ self._sample_rvs_list: Sequence[_SampleRV] = sample_rvs_list
107
+ self._output_sample_rvs: Sequence[_SampleRV] = output_sample_rvs
108
+
109
+ def __iter__(self) -> Iterator[Instance] | Iterator[int]:
110
+ yield_f = self._yield_f
111
+ output_sample_rvs = self._output_sample_rvs
112
+
113
+ # Allocate working memory for yielded result
114
+ instance: NDArrayStates = np.zeros(len(output_sample_rvs), dtype=self._dtype)
115
+
116
+ # Allocate working memory for sampled states
117
+ states: NDArrayStates = np.zeros(len(self._sample_rvs_list), dtype=self._dtype)
118
+
119
+ while True:
120
+ success = False
121
+ while not success:
122
+ success = self._set_sample(states)
123
+
124
+ for i, sample_rv in enumerate(output_sample_rvs):
125
+ instance[i] = states[sample_rv.val_idx]
126
+
127
+ yield yield_f(instance)
128
+
129
+ def _set_sample(self, states: NDArrayStates) -> bool:
130
+ """
131
+ Set the states array with a sample.
132
+ The states array is co-indexed with self._sample_rvs_list.
133
+ If the sample has zero probability (i.e. it should be rejected)
134
+ then False is returned, otherwise True is returned.
135
+ """
136
+ sample_rvs_list = self._sample_rvs_list
137
+ rand = self._rand
138
+
139
+ for sample_rv in sample_rvs_list:
140
+ state = sample_rv.sample(rand, states)
141
+
142
+ # Check that a sample was possible (i.e., positive conditioned
143
+ # probability for some state of `sample_rv`).
144
+ if state is None:
145
+ return False
146
+
147
+ # Check for possible rejection based on conditioning
148
+ if sample_rv.constraints is not None and state not in sample_rv.constraints:
149
+ return False
150
+
151
+ states[sample_rv.val_idx] = state
152
+ return True
153
+
154
+
155
+ @dataclass
156
+ class _SampleRV:
157
+ rv: RandomVariable
158
+ function: PotentialFunction
159
+ val_idx: int
160
+ parent_val_indexes: Sequence[int]
161
+ constraints: Optional[Set[int]]
162
+
163
+ def sample(self, rand: Random, states: NDArrayStates) -> Optional[int]:
164
+ """
165
+ Return a random state index for the random variable, using
166
+ the appropriate distribution over possible states,
167
+ conditioned on the parent states that can be found in 'states'.
168
+
169
+ The function returns None if there was no state with a non-zero probability.
170
+
171
+ Returns:
172
+ state index from this random variable, or None if no state with a non-zero probability.
173
+ """
174
+ num_states: int = len(self.rv)
175
+ function: PotentialFunction = self.function
176
+ parent_val_indexes: Sequence[int] = self.parent_val_indexes
177
+
178
+ x: float = rand.random() # uniform variate in [0, 1)
179
+
180
+ # Get the cumulative CPD, conditioned on parent states.
181
+ parent_states: Tuple[int, ...] = tuple(states.item(i) for i in parent_val_indexes)
182
+ total: float = 0
183
+ for state in range(num_states):
184
+ total += function[(state,) + parent_states]
185
+ if x < total:
186
+ return state
187
+
188
+ if total <= 0:
189
+ # No state with positive probability
190
+ return None
191
+ else:
192
+ return num_states - 1
193
+
194
+
195
+ def _get_sample_rv(rv_idx: int, rv_factors, sample_rvs_map, sample_rvs_list) -> _SampleRV:
196
+ """
197
+ Get a _SampleRV for the indexed random variable.
198
+ This may be called recursively for the parents of the nominated rv.
199
+ """
200
+ sample_rv = sample_rvs_map.get(rv_idx)
201
+ if sample_rv is None:
202
+ factor = rv_factors[rv_idx]
203
+ rv = factor.rvs[0]
204
+ parent_val_indexes = tuple(
205
+ _get_sample_rv(parent.idx, rv_factors, sample_rvs_map, sample_rvs_list).val_idx
206
+ for parent in factor[1:]
207
+ )
208
+ sample_rv = _SampleRV(rv, factor.function, len(sample_rvs_map), parent_val_indexes, None)
209
+ sample_rvs_map[rv_idx] = sample_rv
210
+ sample_rvs_list.append(sample_rv)
211
+ return sample_rv