compiled-knowledge 4.0.0a5__cp313-cp313-macosx_10_13_universal2.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 (167) hide show
  1. ck/__init__.py +0 -0
  2. ck/circuit/__init__.py +13 -0
  3. ck/circuit/circuit.c +38749 -0
  4. ck/circuit/circuit.cpython-313-darwin.so +0 -0
  5. ck/circuit/circuit_py.py +807 -0
  6. ck/circuit/tmp_const.py +74 -0
  7. ck/circuit_compiler/__init__.py +2 -0
  8. ck/circuit_compiler/circuit_compiler.py +26 -0
  9. ck/circuit_compiler/cython_vm_compiler/__init__.py +1 -0
  10. ck/circuit_compiler/cython_vm_compiler/_compiler.c +17373 -0
  11. ck/circuit_compiler/cython_vm_compiler/_compiler.cpython-313-darwin.so +0 -0
  12. ck/circuit_compiler/cython_vm_compiler/cython_vm_compiler.py +96 -0
  13. ck/circuit_compiler/interpret_compiler.py +223 -0
  14. ck/circuit_compiler/llvm_compiler.py +388 -0
  15. ck/circuit_compiler/llvm_vm_compiler.py +546 -0
  16. ck/circuit_compiler/named_circuit_compilers.py +57 -0
  17. ck/circuit_compiler/support/__init__.py +0 -0
  18. ck/circuit_compiler/support/circuit_analyser.py +81 -0
  19. ck/circuit_compiler/support/input_vars.py +148 -0
  20. ck/circuit_compiler/support/llvm_ir_function.py +234 -0
  21. ck/example/__init__.py +53 -0
  22. ck/example/alarm.py +366 -0
  23. ck/example/asia.py +28 -0
  24. ck/example/binary_clique.py +32 -0
  25. ck/example/bow_tie.py +33 -0
  26. ck/example/cancer.py +37 -0
  27. ck/example/chain.py +38 -0
  28. ck/example/child.py +199 -0
  29. ck/example/clique.py +33 -0
  30. ck/example/cnf_pgm.py +39 -0
  31. ck/example/diamond_square.py +68 -0
  32. ck/example/earthquake.py +36 -0
  33. ck/example/empty.py +10 -0
  34. ck/example/hailfinder.py +539 -0
  35. ck/example/hepar2.py +628 -0
  36. ck/example/insurance.py +504 -0
  37. ck/example/loop.py +40 -0
  38. ck/example/mildew.py +38161 -0
  39. ck/example/munin.py +22982 -0
  40. ck/example/pathfinder.py +53674 -0
  41. ck/example/rain.py +39 -0
  42. ck/example/rectangle.py +161 -0
  43. ck/example/run.py +30 -0
  44. ck/example/sachs.py +129 -0
  45. ck/example/sprinkler.py +30 -0
  46. ck/example/star.py +44 -0
  47. ck/example/stress.py +64 -0
  48. ck/example/student.py +43 -0
  49. ck/example/survey.py +46 -0
  50. ck/example/triangle_square.py +54 -0
  51. ck/example/truss.py +49 -0
  52. ck/in_out/__init__.py +3 -0
  53. ck/in_out/parse_ace_lmap.py +216 -0
  54. ck/in_out/parse_ace_nnf.py +288 -0
  55. ck/in_out/parse_net.py +480 -0
  56. ck/in_out/parser_utils.py +185 -0
  57. ck/in_out/pgm_pickle.py +42 -0
  58. ck/in_out/pgm_python.py +268 -0
  59. ck/in_out/render_bugs.py +111 -0
  60. ck/in_out/render_net.py +177 -0
  61. ck/in_out/render_pomegranate.py +184 -0
  62. ck/pgm.py +3494 -0
  63. ck/pgm_circuit/__init__.py +1 -0
  64. ck/pgm_circuit/marginals_program.py +352 -0
  65. ck/pgm_circuit/mpe_program.py +237 -0
  66. ck/pgm_circuit/pgm_circuit.py +75 -0
  67. ck/pgm_circuit/program_with_slotmap.py +234 -0
  68. ck/pgm_circuit/slot_map.py +35 -0
  69. ck/pgm_circuit/support/__init__.py +0 -0
  70. ck/pgm_circuit/support/compile_circuit.py +83 -0
  71. ck/pgm_circuit/target_marginals_program.py +103 -0
  72. ck/pgm_circuit/wmc_program.py +323 -0
  73. ck/pgm_compiler/__init__.py +2 -0
  74. ck/pgm_compiler/ace/__init__.py +1 -0
  75. ck/pgm_compiler/ace/ace.py +252 -0
  76. ck/pgm_compiler/factor_elimination.py +383 -0
  77. ck/pgm_compiler/named_pgm_compilers.py +63 -0
  78. ck/pgm_compiler/pgm_compiler.py +19 -0
  79. ck/pgm_compiler/recursive_conditioning.py +226 -0
  80. ck/pgm_compiler/support/__init__.py +0 -0
  81. ck/pgm_compiler/support/circuit_table/__init__.py +9 -0
  82. ck/pgm_compiler/support/circuit_table/circuit_table.c +16042 -0
  83. ck/pgm_compiler/support/circuit_table/circuit_table.cpython-313-darwin.so +0 -0
  84. ck/pgm_compiler/support/circuit_table/circuit_table_py.py +269 -0
  85. ck/pgm_compiler/support/clusters.py +556 -0
  86. ck/pgm_compiler/support/factor_tables.py +398 -0
  87. ck/pgm_compiler/support/join_tree.py +275 -0
  88. ck/pgm_compiler/support/named_compiler_maker.py +33 -0
  89. ck/pgm_compiler/variable_elimination.py +89 -0
  90. ck/probability/__init__.py +0 -0
  91. ck/probability/empirical_probability_space.py +47 -0
  92. ck/probability/probability_space.py +568 -0
  93. ck/program/__init__.py +3 -0
  94. ck/program/program.py +129 -0
  95. ck/program/program_buffer.py +180 -0
  96. ck/program/raw_program.py +61 -0
  97. ck/sampling/__init__.py +0 -0
  98. ck/sampling/forward_sampler.py +211 -0
  99. ck/sampling/marginals_direct_sampler.py +113 -0
  100. ck/sampling/sampler.py +62 -0
  101. ck/sampling/sampler_support.py +232 -0
  102. ck/sampling/uniform_sampler.py +66 -0
  103. ck/sampling/wmc_direct_sampler.py +169 -0
  104. ck/sampling/wmc_gibbs_sampler.py +147 -0
  105. ck/sampling/wmc_metropolis_sampler.py +159 -0
  106. ck/sampling/wmc_rejection_sampler.py +113 -0
  107. ck/utils/__init__.py +0 -0
  108. ck/utils/iter_extras.py +153 -0
  109. ck/utils/map_list.py +128 -0
  110. ck/utils/map_set.py +128 -0
  111. ck/utils/np_extras.py +51 -0
  112. ck/utils/random_extras.py +64 -0
  113. ck/utils/tmp_dir.py +94 -0
  114. ck_demos/__init__.py +0 -0
  115. ck_demos/ace/__init__.py +0 -0
  116. ck_demos/ace/copy_ace_to_ck.py +15 -0
  117. ck_demos/ace/demo_ace.py +44 -0
  118. ck_demos/all_demos.py +88 -0
  119. ck_demos/circuit/__init__.py +0 -0
  120. ck_demos/circuit/demo_circuit_dump.py +22 -0
  121. ck_demos/circuit/demo_derivatives.py +43 -0
  122. ck_demos/circuit_compiler/__init__.py +0 -0
  123. ck_demos/circuit_compiler/compare_circuit_compilers.py +32 -0
  124. ck_demos/circuit_compiler/show_llvm_program.py +26 -0
  125. ck_demos/pgm/__init__.py +0 -0
  126. ck_demos/pgm/demo_pgm_dump.py +18 -0
  127. ck_demos/pgm/demo_pgm_dump_stress.py +18 -0
  128. ck_demos/pgm/demo_pgm_string_rendering.py +15 -0
  129. ck_demos/pgm/show_examples.py +25 -0
  130. ck_demos/pgm_compiler/__init__.py +0 -0
  131. ck_demos/pgm_compiler/compare_pgm_compilers.py +50 -0
  132. ck_demos/pgm_compiler/demo_compiler_dump.py +50 -0
  133. ck_demos/pgm_compiler/demo_factor_elimination.py +47 -0
  134. ck_demos/pgm_compiler/demo_join_tree.py +25 -0
  135. ck_demos/pgm_compiler/demo_marginals_program.py +53 -0
  136. ck_demos/pgm_compiler/demo_mpe_program.py +55 -0
  137. ck_demos/pgm_compiler/demo_pgm_compiler.py +38 -0
  138. ck_demos/pgm_compiler/demo_recursive_conditioning.py +33 -0
  139. ck_demos/pgm_compiler/demo_variable_elimination.py +33 -0
  140. ck_demos/pgm_compiler/demo_wmc_program.py +29 -0
  141. ck_demos/pgm_inference/__init__.py +0 -0
  142. ck_demos/pgm_inference/demo_inferencing_basic.py +188 -0
  143. ck_demos/pgm_inference/demo_inferencing_mpe_cancer.py +45 -0
  144. ck_demos/pgm_inference/demo_inferencing_wmc_and_mpe_sprinkler.py +154 -0
  145. ck_demos/pgm_inference/demo_inferencing_wmc_student.py +110 -0
  146. ck_demos/programs/__init__.py +0 -0
  147. ck_demos/programs/demo_program_buffer.py +24 -0
  148. ck_demos/programs/demo_program_multi.py +24 -0
  149. ck_demos/programs/demo_program_none.py +19 -0
  150. ck_demos/programs/demo_program_single.py +23 -0
  151. ck_demos/programs/demo_raw_program_interpreted.py +21 -0
  152. ck_demos/programs/demo_raw_program_llvm.py +21 -0
  153. ck_demos/sampling/__init__.py +0 -0
  154. ck_demos/sampling/check_sampler.py +71 -0
  155. ck_demos/sampling/demo_marginal_direct_sampler.py +40 -0
  156. ck_demos/sampling/demo_uniform_sampler.py +38 -0
  157. ck_demos/sampling/demo_wmc_direct_sampler.py +40 -0
  158. ck_demos/utils/__init__.py +0 -0
  159. ck_demos/utils/compare.py +88 -0
  160. ck_demos/utils/convert_network.py +45 -0
  161. ck_demos/utils/sample_model.py +216 -0
  162. ck_demos/utils/stop_watch.py +384 -0
  163. compiled_knowledge-4.0.0a5.dist-info/METADATA +50 -0
  164. compiled_knowledge-4.0.0a5.dist-info/RECORD +167 -0
  165. compiled_knowledge-4.0.0a5.dist-info/WHEEL +5 -0
  166. compiled_knowledge-4.0.0a5.dist-info/licenses/LICENSE.txt +21 -0
  167. compiled_knowledge-4.0.0a5.dist-info/top_level.txt +2 -0
@@ -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,61 @@
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 parameter values,
13
+ # [1]: 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
+ Fields:
22
+ function: is a function of three ctypes arrays, returning nothing.
23
+ dtype: the numpy data type of the array values.
24
+ number_of_vars: the number of input parameter values (first function argument).
25
+ number_of_tmps: the number of working memory values (second function argument).
26
+ number_of_results: the number of result values (third function argument).
27
+ var_indices: maps the index of inputs (from 0 to self.number_of_vars - 1) to the index
28
+ of the corresponding circuit var.
29
+ """
30
+
31
+ function: RawProgramFunction
32
+ dtype: DTypeNumeric
33
+ number_of_vars: int
34
+ number_of_tmps: int
35
+ number_of_results: int
36
+ var_indices: Sequence[int]
37
+
38
+ def __call__(self, in_vars: NDArrayNumeric | Sequence[int | float]) -> NDArrayNumeric:
39
+ """
40
+ Call the raw program as a function from an array to an array.
41
+ """
42
+ array_vars: NDArrayNumeric
43
+ if isinstance(vars, np.ndarray):
44
+ array_vars = in_vars
45
+ else:
46
+ array_vars = np.array(in_vars, dtype=self.dtype)
47
+ if array_vars.shape != (self.number_of_vars,):
48
+ raise ValueError(f'input array incorrect shape: got {array_vars.shape} expected ({self.number_of_vars},)')
49
+ if array_vars.dtype != self.dtype:
50
+ raise ValueError(f'input array incorrect dtype: got {array_vars.dtype} expected {self.dtype}')
51
+
52
+ array_tmps: NDArrayNumeric = np.zeros(self.number_of_tmps, dtype=self.dtype)
53
+ array_outs: NDArrayNumeric = np.zeros(self.number_of_results, dtype=self.dtype)
54
+
55
+ ptr_type = ct.POINTER(np.ctypeslib.as_ctypes_type(self.dtype))
56
+ c_array_vars = array_vars.ctypes.data_as(ptr_type)
57
+ c_array_tmps = array_tmps.ctypes.data_as(ptr_type)
58
+ c_array_outs = array_outs.ctypes.data_as(ptr_type)
59
+
60
+ self.function(c_array_vars, c_array_tmps, c_array_outs)
61
+ 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
@@ -0,0 +1,113 @@
1
+ from typing import Collection, Iterator, Dict, Sequence
2
+
3
+ import numpy as np
4
+
5
+ from ck.pgm import Instance
6
+ from ck.probability.probability_space import dtype_for_state_indexes
7
+ from ck.program.program_buffer import ProgramBuffer
8
+ from ck.program.raw_program import RawProgram
9
+ from ck.sampling.sampler import Sampler
10
+ from ck.sampling.sampler_support import SampleRV, YieldF, SamplerInfo
11
+ from ck.utils.np_extras import NDArray, NDArrayNumeric
12
+ from ck.utils.random_extras import Random
13
+
14
+
15
+ class MarginalsDirectSampler(Sampler):
16
+
17
+ def __init__(
18
+ self,
19
+ sampler_info: SamplerInfo,
20
+ raw_program: RawProgram,
21
+ rand: Random,
22
+ rv_idx_to_result_offset: Dict[int, int],
23
+ ):
24
+ super().__init__(sampler_info.rvs, sampler_info.condition)
25
+ self._yield_f: YieldF = sampler_info.yield_f
26
+ self._rand: Random = rand
27
+ self._program_buffer = ProgramBuffer(raw_program)
28
+ self._sample_rvs: Sequence[SampleRV] = tuple(sampler_info.sample_rvs)
29
+ self._chain_rvs: Sequence[SampleRV] = tuple(
30
+ sample_rv for sample_rv in sampler_info.sample_rvs if sample_rv.copy_index is not None)
31
+ self._state_dtype = dtype_for_state_indexes(self.rvs)
32
+ self._max_number_of_states: int = max((len(rv) for rv in self.rvs), default=0)
33
+ self._slots_1: Collection[int] = sampler_info.slots_1
34
+
35
+ self._marginals: Sequence[NDArrayNumeric] = tuple(
36
+ self._program_buffer.results[
37
+ rv_idx_to_result_offset[sample_rv.rv.idx]
38
+ :
39
+ rv_idx_to_result_offset[sample_rv.rv.idx] + len(sample_rv.rv)
40
+ ]
41
+ for sample_rv in sampler_info.sample_rvs
42
+ )
43
+ # Set up the input slots to 0 or 1 to respect conditioning and initial Markov chain states.
44
+ slots: NDArray = self._program_buffer.vars
45
+ for slot in sampler_info.slots_0:
46
+ slots[slot] = 0
47
+ for slot in sampler_info.slots_1:
48
+ slots[slot] = 1
49
+
50
+ def __iter__(self) -> Iterator[Instance] | Iterator[int]:
51
+ yield_f = self._yield_f
52
+ rand = self._rand
53
+ sample_rvs = self._sample_rvs
54
+ chain_rvs = self._chain_rvs
55
+ program_buffer = self._program_buffer
56
+ slots: NDArray = program_buffer.vars
57
+ marginals = self._marginals
58
+ slots_1 = self._slots_1
59
+
60
+ # Set up working memory buffer
61
+ states = np.zeros(len(sample_rvs), dtype=self._state_dtype)
62
+
63
+ def compute() -> float:
64
+ # Compute the program results based on the current input slot values.
65
+ # Return the WMC.
66
+ return program_buffer.compute().item(-1)
67
+
68
+ while True:
69
+ wmc: float = compute()
70
+ rnd: float = rand.random() * wmc
71
+
72
+ for sample_rv in sample_rvs:
73
+ index: int = sample_rv.index
74
+ if index > 0:
75
+ # No need to execute the program on the first time through
76
+ # as it was done just before entering the loop.
77
+ wmc = compute()
78
+
79
+ rv_dist: NDArray = marginals[sample_rv.index]
80
+
81
+ rv_dist_sum: float = rv_dist.sum()
82
+ if rv_dist_sum <= 0:
83
+ raise RuntimeError('zero probability')
84
+ rv_dist *= wmc / rv_dist_sum
85
+
86
+ state_index: int = -1
87
+ for i in range(len(sample_rv.rv)):
88
+ w = rv_dist.item(i)
89
+ if rnd < w:
90
+ state_index = i
91
+ break
92
+ rnd -= w
93
+ assert state_index >= 0
94
+
95
+ for slot in sample_rv.slots:
96
+ slots[slot] = 0
97
+ slots[sample_rv.slots[state_index]] = 1
98
+ states[index] = state_index
99
+
100
+ yield yield_f(states)
101
+
102
+ # Reset the one slots for the next iteration.
103
+ for slot in slots_1:
104
+ slots[slot] = 1
105
+
106
+ # Copy chain pairs for next iteration.
107
+ # (This writes over any initial chain conditions from slots_1.)
108
+ for sample_rv in chain_rvs:
109
+ rv_slots = sample_rv.slots
110
+ prev_state_idx: int = states.item(sample_rv.copy_index)
111
+ for slot in rv_slots:
112
+ slots[slot] = 0
113
+ slots[rv_slots[prev_state_idx]] = 1
ck/sampling/sampler.py ADDED
@@ -0,0 +1,62 @@
1
+ from abc import ABC, abstractmethod
2
+ from itertools import islice
3
+ from typing import Sequence, Iterator
4
+
5
+ from ck.pgm import RandomVariable, Instance, Indicator
6
+
7
+
8
+ class Sampler(ABC):
9
+ """
10
+ A Sampler provides an unlimited series of samples for one or more random variables.
11
+ The random variables being sampled are provided as a tuple via the `rvs` property.
12
+
13
+ A Sampler will either iterate over Instance objects, where each instance is co-indexed
14
+ with `self.rvs`, or may iterate over single state indexes. Whether a Sampler iterates
15
+ over Instance objects or single state indexes is determined by the implementation.
16
+ If iterating over single state indexes, then `len(self.rvs) == 1`.
17
+ """
18
+ __slots__ = ('_rvs', '_condition')
19
+
20
+ def __init__(self, rvs: Sequence[RandomVariable], condition: Sequence[Indicator]):
21
+ """
22
+ Args:
23
+ rvs: a collection of the random variables being
24
+ sampled, co-indexed with each sample provided by `iter(self)`.
25
+ condition: condition on `rvs` that are compiled into the sampler.
26
+ """
27
+ self._rvs: Sequence[RandomVariable] = tuple(rvs)
28
+ self._condition: Sequence[Indicator] = tuple(condition)
29
+
30
+ @property
31
+ def rvs(self) -> Sequence[RandomVariable]:
32
+ """
33
+ What random variables are being sampled.
34
+
35
+ Returns:
36
+ the random variables being sampled, co-indexed with each sample from `iter(self)`.
37
+ """
38
+ return self._rvs
39
+
40
+ @property
41
+ def condition(self) -> Sequence[Indicator]:
42
+ """
43
+ Condition on `self.rvs` that are compiled into the sampler.
44
+ """
45
+ return self._condition
46
+
47
+ @abstractmethod
48
+ def __iter__(self) -> Iterator[Instance] | Iterator[int]:
49
+ """
50
+ An unlimited series of samples from a random process.
51
+ Each sample is co-indexed with the random variables provided by `self.rvs`.
52
+ """
53
+ ...
54
+
55
+ def take(self, number_of_samples: int) -> Iterator[Instance] | Iterator[int]:
56
+ """
57
+ Take a limited number of samples from `iter(self)`.
58
+
59
+ Args:
60
+ number_of_samples: a limit on the number of samples to provide.
61
+ """
62
+ return islice(self, number_of_samples)