compiled-knowledge 4.0.0a15__cp312-cp312-win_amd64.whl → 4.0.0a17__cp312-cp312-win_amd64.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/circuit/__init__.py +2 -2
- ck/circuit/_circuit_cy.cp312-win_amd64.pyd +0 -0
- ck/circuit/{circuit.pyx → _circuit_cy.pyx} +65 -57
- ck/circuit/{circuit_py.py → _circuit_py.py} +14 -6
- ck/circuit_compiler/cython_vm_compiler/_compiler.c +1603 -2030
- ck/circuit_compiler/cython_vm_compiler/_compiler.cp312-win_amd64.pyd +0 -0
- ck/circuit_compiler/cython_vm_compiler/_compiler.pyx +85 -58
- ck/circuit_compiler/named_circuit_compilers.py +1 -1
- ck/in_out/parse_ace_nnf.py +71 -47
- ck/in_out/parser_utils.py +1 -1
- ck/pgm_compiler/ace/ace.py +8 -2
- ck/pgm_compiler/factor_elimination.py +23 -13
- ck/pgm_compiler/support/circuit_table/__init__.py +2 -2
- ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cp312-win_amd64.pyd +0 -0
- ck/pgm_compiler/support/circuit_table/{circuit_table.pyx → _circuit_table_cy.pyx} +9 -9
- ck/pgm_compiler/support/circuit_table/{circuit_table_py.py → _circuit_table_py.py} +5 -5
- ck/pgm_compiler/support/clusters.py +16 -4
- ck/pgm_compiler/support/factor_tables.py +1 -1
- ck/pgm_compiler/support/join_tree.py +67 -10
- ck/pgm_compiler/variable_elimination.py +2 -0
- ck/utils/local_config.py +270 -0
- ck_demos/pgm_compiler/compare_pgm_compilers.py +2 -0
- ck_demos/pgm_compiler/demo_compiler_dump.py +10 -0
- ck_demos/pgm_compiler/time_fe_compiler.py +93 -0
- ck_demos/utils/compare.py +30 -20
- {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/METADATA +1 -1
- {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/RECORD +30 -31
- ck/circuit/circuit.c +0 -38861
- ck/circuit/circuit.cp312-win_amd64.pyd +0 -0
- ck/circuit/circuit_node.pyx +0 -138
- ck/pgm_compiler/support/circuit_table/circuit_table.c +0 -16042
- ck/pgm_compiler/support/circuit_table/circuit_table.cp312-win_amd64.pyd +0 -0
- {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/WHEEL +0 -0
- {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/licenses/LICENSE.txt +0 -0
- {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import Sequence, Dict, List, Tuple, Set, Optional, Iterator
|
|
3
|
+
from typing import Dict, Tuple, Sequence
|
|
5
4
|
|
|
6
5
|
import numpy as np
|
|
7
6
|
import ctypes as ct
|
|
8
7
|
|
|
9
8
|
from ck import circuit
|
|
10
|
-
from ck.circuit import
|
|
9
|
+
from ck.circuit import OpNode, VarNode, CircuitNode
|
|
11
10
|
from ck.circuit_compiler.support.circuit_analyser import CircuitAnalysis, analyze_circuit
|
|
12
|
-
from ck.
|
|
13
|
-
from ck.
|
|
14
|
-
from ck.utils.np_extras import DType, NDArrayNumeric, NDArray, DTypeNumeric
|
|
11
|
+
from ck.program.raw_program import RawProgramFunction
|
|
12
|
+
from ck.utils.np_extras import NDArrayNumeric, DTypeNumeric
|
|
15
13
|
|
|
16
14
|
from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
|
|
17
15
|
|
|
@@ -63,21 +61,21 @@ def make_function(
|
|
|
63
61
|
|
|
64
62
|
|
|
65
63
|
# VM instructions
|
|
66
|
-
ADD = circuit.ADD
|
|
67
|
-
MUL = circuit.MUL
|
|
68
|
-
|
|
64
|
+
cdef int ADD = circuit.ADD
|
|
65
|
+
cdef int MUL = circuit.MUL
|
|
66
|
+
cdef int COPY = max(ADD, MUL) + 1
|
|
69
67
|
|
|
70
68
|
# VM arrays
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
cdef int VARS = 0
|
|
70
|
+
cdef int TMPS = 1
|
|
71
|
+
cdef int CONSTS = 2
|
|
72
|
+
cdef int RESULT = 3
|
|
75
73
|
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
analysis: CircuitAnalysis,
|
|
79
|
-
dtype: DTypeNumeric,
|
|
80
|
-
) -> Tuple[Instructions, NDArrayNumeric]:
|
|
75
|
+
cdef tuple[Instructions, cnp.ndarray] _make_instructions_from_analysis(
|
|
76
|
+
object analysis: CircuitAnalysis,
|
|
77
|
+
object dtype: DTypeNumeric,
|
|
78
|
+
): # -> Tuple[Instructions, NDArrayNumeric]:
|
|
81
79
|
if dtype != np.float64:
|
|
82
80
|
raise RuntimeError(f'only DType {np.float64} currently supported')
|
|
83
81
|
|
|
@@ -91,7 +89,7 @@ def _make_instructions_from_analysis(
|
|
|
91
89
|
np_consts[i] = node.value
|
|
92
90
|
|
|
93
91
|
# Where to get input values for each possible node.
|
|
94
|
-
|
|
92
|
+
cdef dict[int, ElementID] node_to_element = {}
|
|
95
93
|
# const nodes
|
|
96
94
|
for node_id, const_idx in node_to_const_idx.items():
|
|
97
95
|
node_to_element[node_id] = ElementID(CONSTS, const_idx)
|
|
@@ -110,21 +108,16 @@ def _make_instructions_from_analysis(
|
|
|
110
108
|
# Build instructions
|
|
111
109
|
instructions: Instructions = Instructions()
|
|
112
110
|
|
|
113
|
-
op_node
|
|
111
|
+
cdef object op_node
|
|
114
112
|
for op_node in analysis.op_nodes:
|
|
115
|
-
|
|
116
|
-
args: list[ElementID] = [
|
|
117
|
-
node_to_element[id(arg)]
|
|
118
|
-
for arg in op_node.args
|
|
119
|
-
]
|
|
120
|
-
instructions.append(op_node.symbol, args, dest)
|
|
113
|
+
instructions.append_op(op_node.symbol, op_node, node_to_element)
|
|
121
114
|
|
|
122
115
|
# Add any copy operations, i.e., result nodes that are not op nodes
|
|
123
116
|
for i, node in enumerate(analysis.result_nodes):
|
|
124
117
|
if not isinstance(node, OpNode):
|
|
125
118
|
dest: ElementID = ElementID(RESULT, i)
|
|
126
|
-
|
|
127
|
-
instructions.
|
|
119
|
+
src: ElementID = node_to_element[id(node)]
|
|
120
|
+
instructions.append_copy(src, dest)
|
|
128
121
|
|
|
129
122
|
return instructions, np_consts
|
|
130
123
|
|
|
@@ -143,32 +136,63 @@ cdef struct Instruction:
|
|
|
143
136
|
|
|
144
137
|
cdef class Instructions:
|
|
145
138
|
cdef Instruction* instructions
|
|
139
|
+
cdef int allocated
|
|
146
140
|
cdef int num_instructions
|
|
147
141
|
|
|
148
|
-
def __init__(self):
|
|
149
|
-
self.instructions = <Instruction*> PyMem_Malloc(0)
|
|
142
|
+
def __init__(self) -> None:
|
|
150
143
|
self.num_instructions = 0
|
|
144
|
+
self.allocated = 64
|
|
145
|
+
self.instructions = <Instruction*> PyMem_Malloc(self.allocated * sizeof(Instruction))
|
|
146
|
+
|
|
147
|
+
cdef void append_copy(
|
|
148
|
+
self,
|
|
149
|
+
ElementID src,
|
|
150
|
+
ElementID dest,
|
|
151
|
+
) except*:
|
|
152
|
+
c_args = <ElementID*> PyMem_Malloc(sizeof(ElementID))
|
|
153
|
+
if not c_args:
|
|
154
|
+
raise MemoryError()
|
|
151
155
|
|
|
152
|
-
|
|
156
|
+
c_args[0] = src
|
|
157
|
+
self._append(COPY, 1, c_args, dest)
|
|
158
|
+
|
|
159
|
+
cdef void append_op(self, int symbol, object op_node: OpNode, dict[int, ElementID] node_to_element) except*:
|
|
160
|
+
args = op_node.args
|
|
153
161
|
cdef int num_args = len(args)
|
|
154
|
-
cdef int i
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
# Create the instruction arguments array
|
|
164
|
+
c_args = <ElementID*> PyMem_Malloc(num_args * sizeof(ElementID))
|
|
158
165
|
if not c_args:
|
|
159
166
|
raise MemoryError()
|
|
160
167
|
|
|
161
|
-
|
|
162
|
-
|
|
168
|
+
cdef int i = num_args
|
|
169
|
+
while i > 0:
|
|
170
|
+
i -= 1
|
|
171
|
+
c_args[i] = node_to_element[id(args[i])]
|
|
172
|
+
|
|
173
|
+
dest: ElementID = node_to_element[id(op_node)]
|
|
174
|
+
|
|
175
|
+
self._append(symbol, num_args, c_args, dest)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
cdef void _append(self, int symbol, int num_args, ElementID* c_args, ElementID dest) except *:
|
|
179
|
+
cdef int i
|
|
163
180
|
|
|
164
181
|
cdef int num_instructions = self.num_instructions
|
|
165
|
-
self.instructions = <Instruction*> PyMem_Realloc(
|
|
166
|
-
self.instructions,
|
|
167
|
-
sizeof(Instruction) * (num_instructions + 1)
|
|
168
|
-
)
|
|
169
|
-
if not self.instructions:
|
|
170
|
-
raise MemoryError()
|
|
171
182
|
|
|
183
|
+
# Ensure sufficient instruction memory
|
|
184
|
+
cdef int allocated = self.allocated
|
|
185
|
+
if num_instructions == allocated:
|
|
186
|
+
allocated *= 2
|
|
187
|
+
self.instructions = <Instruction*> PyMem_Realloc(
|
|
188
|
+
self.instructions,
|
|
189
|
+
allocated * sizeof(Instruction),
|
|
190
|
+
)
|
|
191
|
+
if not self.instructions:
|
|
192
|
+
raise MemoryError()
|
|
193
|
+
self.allocated = allocated
|
|
194
|
+
|
|
195
|
+
# Add the instruction
|
|
172
196
|
self.instructions[num_instructions] = Instruction(
|
|
173
197
|
symbol,
|
|
174
198
|
num_args,
|
|
@@ -177,7 +201,7 @@ cdef class Instructions:
|
|
|
177
201
|
)
|
|
178
202
|
self.num_instructions = num_instructions + 1
|
|
179
203
|
|
|
180
|
-
def __dealloc__(self):
|
|
204
|
+
def __dealloc__(self) -> None:
|
|
181
205
|
cdef Instruction* instructions = self.instructions
|
|
182
206
|
if instructions:
|
|
183
207
|
for i in range(self.num_instructions):
|
|
@@ -194,15 +218,15 @@ cdef void cvm_float64(
|
|
|
194
218
|
double* consts,
|
|
195
219
|
double* result,
|
|
196
220
|
Instructions instructions,
|
|
197
|
-
)
|
|
198
|
-
# Core virtual machine.
|
|
221
|
+
) except *:
|
|
222
|
+
# Core virtual machine (for dtype float64).
|
|
199
223
|
|
|
200
|
-
cdef int i,
|
|
224
|
+
cdef int i, symbol
|
|
201
225
|
cdef double accumulator
|
|
202
226
|
cdef ElementID* args
|
|
203
|
-
cdef ElementID
|
|
227
|
+
cdef ElementID elem
|
|
204
228
|
|
|
205
|
-
#
|
|
229
|
+
# Index the four arrays by constants VARS, TMPS, CONSTS, and RESULT
|
|
206
230
|
cdef (double*) arrays[4]
|
|
207
231
|
arrays[VARS] = vars_in
|
|
208
232
|
arrays[TMPS] = tmps
|
|
@@ -210,29 +234,32 @@ cdef void cvm_float64(
|
|
|
210
234
|
arrays[RESULT] = result
|
|
211
235
|
|
|
212
236
|
cdef Instruction* instruction_ptr = instructions.instructions
|
|
213
|
-
|
|
237
|
+
cdef int num_instructions = instructions.num_instructions
|
|
214
238
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
239
|
+
while num_instructions > 0:
|
|
240
|
+
num_instructions -= 1
|
|
241
|
+
|
|
242
|
+
symbol = instruction_ptr.symbol
|
|
243
|
+
args = instruction_ptr.args
|
|
218
244
|
|
|
219
245
|
elem = args[0]
|
|
220
246
|
accumulator = arrays[elem.array][elem.index]
|
|
221
247
|
|
|
222
248
|
if symbol == ADD:
|
|
223
|
-
|
|
249
|
+
i = instruction_ptr.num_args
|
|
250
|
+
while i > 1:
|
|
251
|
+
i -= 1
|
|
224
252
|
elem = args[i]
|
|
225
253
|
accumulator += arrays[elem.array][elem.index]
|
|
226
254
|
elif symbol == MUL:
|
|
227
|
-
|
|
255
|
+
i = instruction_ptr.num_args
|
|
256
|
+
while i > 1:
|
|
257
|
+
i -= 1
|
|
228
258
|
elem = args[i]
|
|
229
259
|
accumulator *= arrays[elem.array][elem.index]
|
|
230
|
-
|
|
231
|
-
pass
|
|
232
|
-
else:
|
|
233
|
-
raise RuntimeError('symbol not understood: ' + str(symbol))
|
|
260
|
+
# else symbol == COPY, nothing to do
|
|
234
261
|
|
|
235
|
-
elem = instruction_ptr
|
|
262
|
+
elem = instruction_ptr.dest
|
|
236
263
|
arrays[elem.array][elem.index] = accumulator
|
|
237
264
|
|
|
238
265
|
# Advance the instruction pointer
|
ck/in_out/parse_ace_nnf.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Tuple, Optional, Dict, List
|
|
2
|
+
from typing import Tuple, Optional, Dict, List, Sequence
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
6
|
from ck.circuit import Circuit, CircuitNode, VarNode, ConstValue, ConstNode
|
|
7
7
|
from ck.in_out.parse_ace_lmap import LiteralMap
|
|
8
8
|
from ck.in_out.parser_utils import ParseError, ParserInput
|
|
9
|
+
from ck.pgm import Indicator
|
|
9
10
|
from ck.pgm_circuit.slot_map import SlotKey, SlotMap
|
|
10
11
|
from ck.utils.np_extras import NDArrayFloat64
|
|
11
12
|
|
|
@@ -18,9 +19,9 @@ def read_nnf_with_literal_map(
|
|
|
18
19
|
input_stream,
|
|
19
20
|
literal_map: LiteralMap,
|
|
20
21
|
*,
|
|
22
|
+
indicators: Sequence[Indicator] = (),
|
|
21
23
|
const_parameters: bool = True,
|
|
22
24
|
optimise_ops: bool = True,
|
|
23
|
-
circuit: Optional[Circuit] = None,
|
|
24
25
|
check_header: bool = False,
|
|
25
26
|
) -> Tuple[CircuitNode, SlotMap, NDArrayFloat64]:
|
|
26
27
|
"""
|
|
@@ -31,8 +32,8 @@ def read_nnf_with_literal_map(
|
|
|
31
32
|
|
|
32
33
|
Args:
|
|
33
34
|
input_stream: to parse, as per `ParserInput` argument.
|
|
35
|
+
indicators: any indicators to pre allocate to circuit variables.
|
|
34
36
|
literal_map: mapping from literal code to indicators.
|
|
35
|
-
circuit: an optional circuit to reuse.
|
|
36
37
|
check_header: if true, an exception is raised if the number of nodes or arcs is not as expected.
|
|
37
38
|
const_parameters: if true, the potential function parameters will be circuit
|
|
38
39
|
constants, otherwise they will be circuit variables.
|
|
@@ -44,6 +45,7 @@ def read_nnf_with_literal_map(
|
|
|
44
45
|
slot_map: is a map from indicator to a circuit var index (int).
|
|
45
46
|
params: is a numpy array of parameter values, co-indexed with `circuit.vars[num_indicators:]`
|
|
46
47
|
"""
|
|
48
|
+
circuit = Circuit()
|
|
47
49
|
|
|
48
50
|
# Set the `const_literals` parameter for `read_nnf`
|
|
49
51
|
const_literals: Optional[Dict[int, ConstValue]]
|
|
@@ -57,38 +59,42 @@ def read_nnf_with_literal_map(
|
|
|
57
59
|
else:
|
|
58
60
|
const_literals = {}
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
# Make a slot map to map from an indicator to a circuit variable index.
|
|
63
|
+
# Preload `var_literals` to map literal codes to circuit vars.
|
|
64
|
+
# We allocate the circuit variables here to ensure that indicators
|
|
65
|
+
# come before parameters in the circuit variables.
|
|
66
|
+
slot_map: Dict[SlotKey, int] = {
|
|
67
|
+
indicator: i
|
|
68
|
+
for i, indicator in enumerate(indicators)
|
|
69
|
+
}
|
|
70
|
+
circuit.new_vars(len(slot_map))
|
|
71
|
+
var_literals: Dict[int, int] = {}
|
|
72
|
+
for literal_code, indicator in literal_map.indicators.items():
|
|
73
|
+
slot = slot_map.get(indicator)
|
|
74
|
+
if slot is None:
|
|
75
|
+
slot = circuit.new_var().idx
|
|
76
|
+
slot_map[indicator] = slot
|
|
77
|
+
var_literals[literal_code] = slot
|
|
78
|
+
num_indicators: int = len(slot_map)
|
|
79
|
+
|
|
80
|
+
# Parse the nnf file
|
|
81
|
+
top_node, vars_literals = read_nnf(
|
|
61
82
|
input_stream,
|
|
83
|
+
var_literals=var_literals,
|
|
62
84
|
const_literals=const_literals,
|
|
63
85
|
circuit=circuit,
|
|
64
86
|
check_header=check_header,
|
|
65
87
|
optimise_ops=optimise_ops,
|
|
66
88
|
)
|
|
67
|
-
circuit = top_node.circuit
|
|
68
|
-
|
|
69
|
-
# Build the slot map from indicator to slot.
|
|
70
|
-
# Some indicators may not be in `literal_slot_map` because they were not needed
|
|
71
|
-
# for the arithmetic circuit in the NNF file. For those indicators, we create
|
|
72
|
-
# dummy circuit vars.
|
|
73
|
-
def _get_slot(_literal_code: int) -> int:
|
|
74
|
-
_slot: Optional[int] = literal_slot_map.get(_literal_code)
|
|
75
|
-
if _slot is None:
|
|
76
|
-
_slot: int = circuit.new_var().idx
|
|
77
|
-
return _slot
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# Get the parameter values
|
|
85
|
-
num_indicators: int = len(literal_map.indicators)
|
|
90
|
+
# Get the parameter values.
|
|
91
|
+
# Any new circuit vars added to the circuit are parameters.
|
|
92
|
+
# Parameter IDs are not added to the slot map as we don't know them.
|
|
86
93
|
num_parameters: int = top_node.circuit.number_of_vars - num_indicators
|
|
87
94
|
assert num_parameters == 0 or not const_parameters, 'const_parameters -> num_parameters == 0'
|
|
88
|
-
|
|
89
95
|
params: NDArrayFloat64 = np.zeros(num_parameters, dtype=np.float64)
|
|
90
96
|
for literal_code, value in literal_map.params.items():
|
|
91
|
-
literal_slot: Optional[int] =
|
|
97
|
+
literal_slot: Optional[int] = var_literals.get(literal_code)
|
|
92
98
|
if literal_slot is not None and literal_slot >= num_indicators:
|
|
93
99
|
params[literal_slot - num_indicators] = value
|
|
94
100
|
|
|
@@ -98,6 +104,7 @@ def read_nnf_with_literal_map(
|
|
|
98
104
|
def read_nnf(
|
|
99
105
|
input_stream,
|
|
100
106
|
*,
|
|
107
|
+
var_literals: Optional[Dict[int, int]] = None,
|
|
101
108
|
const_literals: Optional[Dict[int, ConstValue]] = None,
|
|
102
109
|
circuit: Optional[Circuit] = None,
|
|
103
110
|
check_header: bool = False,
|
|
@@ -109,44 +116,51 @@ def read_nnf(
|
|
|
109
116
|
The input consists of propositional logical sentences in negative normal form (NNF).
|
|
110
117
|
This covers both ".ac" and ".nnf" files produced by the software ACE.
|
|
111
118
|
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
This function returns the last node parsed (or the constant zero node if no nodes passed).
|
|
120
|
+
It also returns a mapping from literal code (int) to circuit variable index.
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
Two optional dictionaries may be supplied. Dictionary `var_literals` maps a literal
|
|
123
|
+
code to a pre-existing circuit variable index. Dictionary `const_literals` maps a literal
|
|
124
|
+
code to a constant value. A literal code should not appear in both dictionaries.
|
|
125
|
+
|
|
126
|
+
Any literal code that is parsed but does not appear in `var_literals` or `const_literals`
|
|
127
|
+
results in a new circuit variable being created and a corresponding entry added to
|
|
128
|
+
`var_literals`.
|
|
129
|
+
|
|
130
|
+
External software may modify an NNF file by removing arcs, but it may not update the header.
|
|
131
|
+
Although the resulting file is not conformant, it is still parsable (by ignoring the header).
|
|
132
|
+
Parameter `check_header` can be set to true, which causes an exception being raised if the
|
|
133
|
+
header disagrees with the rest of the file.
|
|
118
134
|
|
|
119
135
|
Args:
|
|
120
136
|
input_stream: to parse, as per `ParserInput` argument.
|
|
137
|
+
var_literals: an optional mapping from literal code to existing circuit variable index.
|
|
121
138
|
const_literals: an optional mapping from literal code to constant value.
|
|
122
139
|
circuit: an optional empty circuit to reuse.
|
|
123
140
|
check_header: if true, an exception is raised if the number of nodes or arcs is not as expected.
|
|
124
141
|
optimise_ops: if true then circuit optimised operations will be used.
|
|
125
142
|
|
|
126
143
|
Returns:
|
|
127
|
-
(circuit_top,
|
|
144
|
+
(circuit_top, var_literals)
|
|
128
145
|
circuit_top: is the resulting top node from parsing the input.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Assumes:
|
|
132
|
-
If a circuit is provided, it is empty.
|
|
146
|
+
var_literals: is a mapping from literal code (int) to a circuit variable index (int).
|
|
133
147
|
"""
|
|
134
148
|
if circuit is None:
|
|
135
149
|
circuit: Circuit = Circuit()
|
|
136
150
|
|
|
137
|
-
if
|
|
138
|
-
|
|
151
|
+
if var_literals is None:
|
|
152
|
+
var_literals: Dict[int, int] = {}
|
|
139
153
|
|
|
140
154
|
if const_literals is None:
|
|
141
155
|
const_literals: Dict[int, ConstValue] = {}
|
|
142
156
|
|
|
143
|
-
parser = CircuitParser(circuit, check_header, const_literals, optimise_ops)
|
|
157
|
+
parser = CircuitParser(circuit, check_header, var_literals, const_literals, optimise_ops)
|
|
144
158
|
parser.parse(input_stream)
|
|
145
159
|
|
|
146
160
|
nodes = parser.nodes
|
|
147
161
|
cct_top = circuit.zero if len(nodes) == 0 else nodes[-1]
|
|
148
162
|
|
|
149
|
-
return cct_top,
|
|
163
|
+
return cct_top, var_literals
|
|
150
164
|
|
|
151
165
|
|
|
152
166
|
class Parser(ABC):
|
|
@@ -198,6 +212,8 @@ class Parser(ABC):
|
|
|
198
212
|
self.add_node(raise_f, args)
|
|
199
213
|
else:
|
|
200
214
|
self.mul_node(raise_f, args)
|
|
215
|
+
else:
|
|
216
|
+
raise_f(f'unexpected parser state: {state}')
|
|
201
217
|
line = input_stream.readline()
|
|
202
218
|
self.done(raise_f)
|
|
203
219
|
except ParseError as e:
|
|
@@ -236,17 +252,18 @@ class CircuitParser(Parser):
|
|
|
236
252
|
self,
|
|
237
253
|
circuit: Circuit,
|
|
238
254
|
check_header: bool,
|
|
255
|
+
var_literals: Dict[int, int],
|
|
239
256
|
const_literals: Dict[int, ConstValue],
|
|
240
257
|
optimise_ops: bool,
|
|
241
258
|
):
|
|
242
259
|
self.check_header: bool = check_header
|
|
243
|
-
self.
|
|
244
|
-
self.optimise_ops = optimise_ops
|
|
245
|
-
self.circuit = circuit
|
|
260
|
+
self.var_literals: Dict[int, int] = var_literals
|
|
246
261
|
self.const_literals: Dict[int, ConstValue] = const_literals
|
|
262
|
+
self.optimise_ops: bool = optimise_ops
|
|
263
|
+
self.circuit: Circuit = circuit
|
|
247
264
|
self.nodes: List[CircuitNode] = []
|
|
248
|
-
self.num_nodes = None
|
|
249
|
-
self.num_edges = None
|
|
265
|
+
self.num_nodes = None # read from the file header for checking
|
|
266
|
+
self.num_edges = None # read from the file header for checking
|
|
250
267
|
|
|
251
268
|
def comment(self, raise_f, message: str) -> None:
|
|
252
269
|
pass
|
|
@@ -257,13 +274,20 @@ class CircuitParser(Parser):
|
|
|
257
274
|
"""
|
|
258
275
|
const_value: Optional[ConstValue] = self.const_literals.get(literal_code)
|
|
259
276
|
if const_value is not None:
|
|
277
|
+
# Literal code maps to a constant value
|
|
278
|
+
if literal_code in self.var_literals:
|
|
279
|
+
raise_f('literal code both constant and variable: {literal_code}')
|
|
260
280
|
node: ConstNode = self.circuit.const(const_value)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
281
|
+
|
|
282
|
+
elif (var_idx := self.var_literals.get(literal_code)) is not None:
|
|
283
|
+
# Literal code maps to an existing circuit variable
|
|
284
|
+
node: VarNode = self.circuit.vars[var_idx]
|
|
285
|
+
|
|
264
286
|
else:
|
|
287
|
+
# Literal code maps to a new circuit variable
|
|
265
288
|
node: VarNode = self.circuit.new_var()
|
|
266
|
-
self.
|
|
289
|
+
self.var_literals[literal_code] = node.idx
|
|
290
|
+
|
|
267
291
|
self.nodes.append(node)
|
|
268
292
|
|
|
269
293
|
def add_node(self, raise_f, args: List[int]) -> None:
|
ck/in_out/parser_utils.py
CHANGED
ck/pgm_compiler/ace/ace.py
CHANGED
|
@@ -14,6 +14,7 @@ from ck.in_out.render_net import render_bayesian_network
|
|
|
14
14
|
from ck.pgm import PGM
|
|
15
15
|
from ck.pgm_circuit import PGMCircuit
|
|
16
16
|
from ck.pgm_circuit.slot_map import SlotMap
|
|
17
|
+
from ck.utils.local_config import config
|
|
17
18
|
from ck.utils.np_extras import NDArrayFloat64
|
|
18
19
|
from ck.utils.tmp_dir import tmp_dir
|
|
19
20
|
|
|
@@ -121,8 +122,9 @@ def compile_pgm(
|
|
|
121
122
|
parameter_values: NDArrayFloat64
|
|
122
123
|
circuit_top, slot_map, parameter_values = read_nnf_with_literal_map(
|
|
123
124
|
file,
|
|
125
|
+
indicators=pgm.indicators,
|
|
124
126
|
literal_map=literal_map,
|
|
125
|
-
const_parameters=const_parameters
|
|
127
|
+
const_parameters=const_parameters,
|
|
126
128
|
)
|
|
127
129
|
|
|
128
130
|
# Consistency checking
|
|
@@ -198,8 +200,12 @@ def copy_ace_to_default_location(
|
|
|
198
200
|
def default_ace_location() -> Path:
|
|
199
201
|
"""
|
|
200
202
|
Get the default location for Ace files.
|
|
203
|
+
|
|
204
|
+
This function checks the local config for the variable
|
|
205
|
+
CK_ACE_LOCATION. If that is not available, then the
|
|
206
|
+
directory that this Python module is in will be used.
|
|
201
207
|
"""
|
|
202
|
-
return Path(__file__).parent
|
|
208
|
+
return Path(config.get('CK_ACE_LOCATION', Path(__file__).parent))
|
|
203
209
|
|
|
204
210
|
|
|
205
211
|
@dataclass
|
|
@@ -149,7 +149,7 @@ def join_tree_to_circuit(
|
|
|
149
149
|
limit_product_tree_search,
|
|
150
150
|
)
|
|
151
151
|
top: CircuitNode = top_table.top()
|
|
152
|
-
|
|
152
|
+
top.circuit.remove_unreachable_op_nodes(top)
|
|
153
153
|
|
|
154
154
|
return PGMCircuit(
|
|
155
155
|
rvs=tuple(pgm.rvs),
|
|
@@ -169,27 +169,37 @@ def _circuit_tables_from_join_tree(
|
|
|
169
169
|
) -> CircuitTable:
|
|
170
170
|
"""
|
|
171
171
|
This is a basic algorithm for constructing a circuit table from a join tree.
|
|
172
|
+
Algorithm synopsis:
|
|
173
|
+
1) Get a CircuitTable for each factor allocated to this join tree node, and
|
|
174
|
+
for each child of the join tree node (recursive call to _circuit_tables_from_join_tree).
|
|
175
|
+
2) Form a binary tree of the collected circuit tables.
|
|
176
|
+
3) Perform table products and sum-outs for each node in the binary tree, which should
|
|
177
|
+
leave a single circuit table with a single row.
|
|
172
178
|
"""
|
|
173
|
-
#
|
|
174
|
-
factors: List[CircuitTable] =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
# Get all the factors to combine.
|
|
180
|
+
factors: List[CircuitTable] = list(
|
|
181
|
+
chain(
|
|
182
|
+
(
|
|
183
|
+
# The PGM factors allocated to this join tree node
|
|
184
|
+
factor_tables.get_table(factor)
|
|
185
|
+
for factor in join_tree.factors
|
|
186
|
+
),
|
|
187
|
+
(
|
|
188
|
+
# The children of this join tree node
|
|
189
|
+
_circuit_tables_from_join_tree(factor_tables, child, limit_product_tree_search)
|
|
190
|
+
for child in join_tree.children
|
|
191
|
+
),
|
|
192
|
+
)
|
|
183
193
|
)
|
|
184
194
|
|
|
185
195
|
# The usual join tree approach just forms the product all the tables in `factors`.
|
|
186
196
|
# The tree width is not affected by the order of products, however some orders
|
|
187
197
|
# lead to smaller numbers of arithmetic operations.
|
|
188
198
|
#
|
|
189
|
-
# If `
|
|
199
|
+
# If `limit_product_tree_search > 1`, then heuristics are used
|
|
190
200
|
# reduce the number of arithmetic operations.
|
|
191
201
|
|
|
192
|
-
# Deal with the special case:
|
|
202
|
+
# Deal with the special case: zero factors
|
|
193
203
|
if len(factors) == 0:
|
|
194
204
|
circuit = factor_tables.circuit
|
|
195
205
|
if len(join_tree.separator) == 0:
|
|
@@ -142,7 +142,7 @@ cpdef object sum_out(object table: CircuitTable, object rv_idxs: Iterable[int]):
|
|
|
142
142
|
|
|
143
143
|
for group_instance_tuple, to_add in groups.items():
|
|
144
144
|
node = circuit.optimised_add(to_add)
|
|
145
|
-
if not node.is_zero
|
|
145
|
+
if not node.is_zero:
|
|
146
146
|
rows[group_instance_tuple] = node
|
|
147
147
|
|
|
148
148
|
return new_table
|
|
@@ -159,7 +159,7 @@ cpdef object sum_out_all(object table: CircuitTable): # -> CircuitTable:
|
|
|
159
159
|
node = next(iter(table.rows.values()))
|
|
160
160
|
else:
|
|
161
161
|
node: CircuitNode = circuit.optimised_add(table.rows.values())
|
|
162
|
-
if node.is_zero
|
|
162
|
+
if node.is_zero:
|
|
163
163
|
return CircuitTable(circuit, ())
|
|
164
164
|
|
|
165
165
|
return CircuitTable(circuit, (), [((), node)])
|
|
@@ -190,7 +190,7 @@ cpdef object product(x: CircuitTable, y: CircuitTable): # -> CircuitTable:
|
|
|
190
190
|
|
|
191
191
|
# Special case: y == 0 or 1, and has no random variables.
|
|
192
192
|
if len(y.rv_idxs) == 0:
|
|
193
|
-
if len(y) == 1 and y.top().is_one
|
|
193
|
+
if len(y) == 1 and y.top().is_one:
|
|
194
194
|
return x
|
|
195
195
|
elif len(y) == 0:
|
|
196
196
|
return CircuitTable(circuit, x.rv_idxs)
|
|
@@ -259,7 +259,7 @@ cpdef object product(x: CircuitTable, y: CircuitTable): # -> CircuitTable:
|
|
|
259
259
|
co.append(x_instance[i])
|
|
260
260
|
co_tuple = tuple(co)
|
|
261
261
|
|
|
262
|
-
if x_node.is_one
|
|
262
|
+
if x_node.is_one:
|
|
263
263
|
# Multiplying by one.
|
|
264
264
|
# Iterate over matching y rows.
|
|
265
265
|
got = y_index.get(co_tuple)
|
|
@@ -301,7 +301,7 @@ cdef object _product_no_common_rvs(x: CircuitTable, y: CircuitTable): # -> Circ
|
|
|
301
301
|
cdef tuple[int, ...] instance
|
|
302
302
|
|
|
303
303
|
for x_instance, x_node in x.rows.items():
|
|
304
|
-
if x_node.is_one
|
|
304
|
+
if x_node.is_one:
|
|
305
305
|
for y_instance, y_node in y.rows.items():
|
|
306
306
|
instance = x_instance + y_instance
|
|
307
307
|
table.rows[instance] = y_node
|
|
@@ -314,12 +314,12 @@ cdef object _product_no_common_rvs(x: CircuitTable, y: CircuitTable): # -> Circ
|
|
|
314
314
|
|
|
315
315
|
|
|
316
316
|
cdef object _optimised_mul(object circuit: Circuit, object x: CircuitNode, object y: CircuitNode): # -> CircuitNode
|
|
317
|
-
if x.is_zero
|
|
317
|
+
if x.is_zero:
|
|
318
318
|
return x
|
|
319
|
-
if y.is_zero
|
|
319
|
+
if y.is_zero:
|
|
320
320
|
return y
|
|
321
|
-
if x.is_one
|
|
321
|
+
if x.is_one:
|
|
322
322
|
return y
|
|
323
|
-
if y.is_one
|
|
323
|
+
if y.is_one:
|
|
324
324
|
return x
|
|
325
325
|
return circuit.mul(x, y)
|