compiled-knowledge 4.0.0a20__cp313-cp313-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.
- ck/__init__.py +0 -0
- ck/circuit/__init__.py +17 -0
- ck/circuit/_circuit_cy.c +37525 -0
- ck/circuit/_circuit_cy.cpython-313-darwin.so +0 -0
- ck/circuit/_circuit_cy.pxd +32 -0
- ck/circuit/_circuit_cy.pyx +768 -0
- ck/circuit/_circuit_py.py +836 -0
- ck/circuit/tmp_const.py +74 -0
- ck/circuit_compiler/__init__.py +2 -0
- ck/circuit_compiler/circuit_compiler.py +26 -0
- ck/circuit_compiler/cython_vm_compiler/__init__.py +1 -0
- ck/circuit_compiler/cython_vm_compiler/_compiler.c +19826 -0
- ck/circuit_compiler/cython_vm_compiler/_compiler.cpython-313-darwin.so +0 -0
- ck/circuit_compiler/cython_vm_compiler/_compiler.pyx +380 -0
- ck/circuit_compiler/cython_vm_compiler/cython_vm_compiler.py +121 -0
- ck/circuit_compiler/interpret_compiler.py +223 -0
- ck/circuit_compiler/llvm_compiler.py +388 -0
- ck/circuit_compiler/llvm_vm_compiler.py +546 -0
- ck/circuit_compiler/named_circuit_compilers.py +57 -0
- ck/circuit_compiler/support/__init__.py +0 -0
- ck/circuit_compiler/support/circuit_analyser/__init__.py +13 -0
- ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.c +10620 -0
- ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.cpython-313-darwin.so +0 -0
- ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.pyx +98 -0
- ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_py.py +93 -0
- ck/circuit_compiler/support/input_vars.py +148 -0
- ck/circuit_compiler/support/llvm_ir_function.py +234 -0
- ck/example/__init__.py +53 -0
- ck/example/alarm.py +366 -0
- ck/example/asia.py +28 -0
- ck/example/binary_clique.py +32 -0
- ck/example/bow_tie.py +33 -0
- ck/example/cancer.py +37 -0
- ck/example/chain.py +38 -0
- ck/example/child.py +199 -0
- ck/example/clique.py +33 -0
- ck/example/cnf_pgm.py +39 -0
- ck/example/diamond_square.py +68 -0
- ck/example/earthquake.py +36 -0
- ck/example/empty.py +10 -0
- ck/example/hailfinder.py +539 -0
- ck/example/hepar2.py +628 -0
- ck/example/insurance.py +504 -0
- ck/example/loop.py +40 -0
- ck/example/mildew.py +38161 -0
- ck/example/munin.py +22982 -0
- ck/example/pathfinder.py +53747 -0
- ck/example/rain.py +39 -0
- ck/example/rectangle.py +161 -0
- ck/example/run.py +30 -0
- ck/example/sachs.py +129 -0
- ck/example/sprinkler.py +30 -0
- ck/example/star.py +44 -0
- ck/example/stress.py +64 -0
- ck/example/student.py +43 -0
- ck/example/survey.py +46 -0
- ck/example/triangle_square.py +54 -0
- ck/example/truss.py +49 -0
- ck/in_out/__init__.py +3 -0
- ck/in_out/parse_ace_lmap.py +216 -0
- ck/in_out/parse_ace_nnf.py +322 -0
- ck/in_out/parse_net.py +480 -0
- ck/in_out/parser_utils.py +185 -0
- ck/in_out/pgm_pickle.py +42 -0
- ck/in_out/pgm_python.py +268 -0
- ck/in_out/render_bugs.py +111 -0
- ck/in_out/render_net.py +177 -0
- ck/in_out/render_pomegranate.py +184 -0
- ck/pgm.py +3475 -0
- ck/pgm_circuit/__init__.py +1 -0
- ck/pgm_circuit/marginals_program.py +352 -0
- ck/pgm_circuit/mpe_program.py +237 -0
- ck/pgm_circuit/pgm_circuit.py +79 -0
- ck/pgm_circuit/program_with_slotmap.py +236 -0
- ck/pgm_circuit/slot_map.py +35 -0
- ck/pgm_circuit/support/__init__.py +0 -0
- ck/pgm_circuit/support/compile_circuit.py +83 -0
- ck/pgm_circuit/target_marginals_program.py +103 -0
- ck/pgm_circuit/wmc_program.py +323 -0
- ck/pgm_compiler/__init__.py +2 -0
- ck/pgm_compiler/ace/__init__.py +1 -0
- ck/pgm_compiler/ace/ace.py +299 -0
- ck/pgm_compiler/factor_elimination.py +395 -0
- ck/pgm_compiler/named_pgm_compilers.py +63 -0
- ck/pgm_compiler/pgm_compiler.py +19 -0
- ck/pgm_compiler/recursive_conditioning.py +231 -0
- ck/pgm_compiler/support/__init__.py +0 -0
- ck/pgm_compiler/support/circuit_table/__init__.py +17 -0
- ck/pgm_compiler/support/circuit_table/_circuit_table_cy.c +16398 -0
- ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cpython-313-darwin.so +0 -0
- ck/pgm_compiler/support/circuit_table/_circuit_table_cy.pyx +332 -0
- ck/pgm_compiler/support/circuit_table/_circuit_table_py.py +304 -0
- ck/pgm_compiler/support/clusters.py +568 -0
- ck/pgm_compiler/support/factor_tables.py +406 -0
- ck/pgm_compiler/support/join_tree.py +332 -0
- ck/pgm_compiler/support/named_compiler_maker.py +43 -0
- ck/pgm_compiler/variable_elimination.py +91 -0
- ck/probability/__init__.py +0 -0
- ck/probability/empirical_probability_space.py +50 -0
- ck/probability/pgm_probability_space.py +32 -0
- ck/probability/probability_space.py +622 -0
- ck/program/__init__.py +3 -0
- ck/program/program.py +137 -0
- ck/program/program_buffer.py +180 -0
- ck/program/raw_program.py +67 -0
- ck/sampling/__init__.py +0 -0
- ck/sampling/forward_sampler.py +211 -0
- ck/sampling/marginals_direct_sampler.py +113 -0
- ck/sampling/sampler.py +62 -0
- ck/sampling/sampler_support.py +232 -0
- ck/sampling/uniform_sampler.py +72 -0
- ck/sampling/wmc_direct_sampler.py +171 -0
- ck/sampling/wmc_gibbs_sampler.py +153 -0
- ck/sampling/wmc_metropolis_sampler.py +165 -0
- ck/sampling/wmc_rejection_sampler.py +115 -0
- ck/utils/__init__.py +0 -0
- ck/utils/iter_extras.py +163 -0
- ck/utils/local_config.py +270 -0
- ck/utils/map_list.py +128 -0
- ck/utils/map_set.py +128 -0
- ck/utils/np_extras.py +51 -0
- ck/utils/random_extras.py +64 -0
- ck/utils/tmp_dir.py +94 -0
- ck_demos/__init__.py +0 -0
- ck_demos/ace/__init__.py +0 -0
- ck_demos/ace/copy_ace_to_ck.py +15 -0
- ck_demos/ace/demo_ace.py +49 -0
- ck_demos/all_demos.py +88 -0
- ck_demos/circuit/__init__.py +0 -0
- ck_demos/circuit/demo_circuit_dump.py +22 -0
- ck_demos/circuit/demo_derivatives.py +43 -0
- ck_demos/circuit_compiler/__init__.py +0 -0
- ck_demos/circuit_compiler/compare_circuit_compilers.py +32 -0
- ck_demos/circuit_compiler/show_llvm_program.py +26 -0
- ck_demos/pgm/__init__.py +0 -0
- ck_demos/pgm/demo_pgm_dump.py +18 -0
- ck_demos/pgm/demo_pgm_dump_stress.py +18 -0
- ck_demos/pgm/demo_pgm_string_rendering.py +15 -0
- ck_demos/pgm/show_examples.py +25 -0
- ck_demos/pgm_compiler/__init__.py +0 -0
- ck_demos/pgm_compiler/compare_pgm_compilers.py +63 -0
- ck_demos/pgm_compiler/demo_compiler_dump.py +60 -0
- ck_demos/pgm_compiler/demo_factor_elimination.py +47 -0
- ck_demos/pgm_compiler/demo_join_tree.py +25 -0
- ck_demos/pgm_compiler/demo_marginals_program.py +53 -0
- ck_demos/pgm_compiler/demo_mpe_program.py +55 -0
- ck_demos/pgm_compiler/demo_pgm_compiler.py +38 -0
- ck_demos/pgm_compiler/demo_recursive_conditioning.py +33 -0
- ck_demos/pgm_compiler/demo_variable_elimination.py +33 -0
- ck_demos/pgm_compiler/demo_wmc_program.py +29 -0
- ck_demos/pgm_compiler/time_fe_compiler.py +93 -0
- ck_demos/pgm_inference/__init__.py +0 -0
- ck_demos/pgm_inference/demo_inferencing_basic.py +188 -0
- ck_demos/pgm_inference/demo_inferencing_mpe_cancer.py +45 -0
- ck_demos/pgm_inference/demo_inferencing_wmc_and_mpe_sprinkler.py +154 -0
- ck_demos/pgm_inference/demo_inferencing_wmc_student.py +110 -0
- ck_demos/programs/__init__.py +0 -0
- ck_demos/programs/demo_program_buffer.py +24 -0
- ck_demos/programs/demo_program_multi.py +24 -0
- ck_demos/programs/demo_program_none.py +19 -0
- ck_demos/programs/demo_program_single.py +23 -0
- ck_demos/programs/demo_raw_program_interpreted.py +21 -0
- ck_demos/programs/demo_raw_program_llvm.py +21 -0
- ck_demos/sampling/__init__.py +0 -0
- ck_demos/sampling/check_sampler.py +71 -0
- ck_demos/sampling/demo_marginal_direct_sampler.py +40 -0
- ck_demos/sampling/demo_uniform_sampler.py +38 -0
- ck_demos/sampling/demo_wmc_direct_sampler.py +40 -0
- ck_demos/utils/__init__.py +0 -0
- ck_demos/utils/compare.py +120 -0
- ck_demos/utils/convert_network.py +45 -0
- ck_demos/utils/sample_model.py +216 -0
- ck_demos/utils/stop_watch.py +384 -0
- compiled_knowledge-4.0.0a20.dist-info/METADATA +50 -0
- compiled_knowledge-4.0.0a20.dist-info/RECORD +178 -0
- compiled_knowledge-4.0.0a20.dist-info/WHEEL +6 -0
- compiled_knowledge-4.0.0a20.dist-info/licenses/LICENSE.txt +21 -0
- 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
|
ck/sampling/__init__.py
ADDED
|
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
|