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.

Files changed (35) hide show
  1. ck/circuit/__init__.py +2 -2
  2. ck/circuit/_circuit_cy.cp312-win_amd64.pyd +0 -0
  3. ck/circuit/{circuit.pyx → _circuit_cy.pyx} +65 -57
  4. ck/circuit/{circuit_py.py → _circuit_py.py} +14 -6
  5. ck/circuit_compiler/cython_vm_compiler/_compiler.c +1603 -2030
  6. ck/circuit_compiler/cython_vm_compiler/_compiler.cp312-win_amd64.pyd +0 -0
  7. ck/circuit_compiler/cython_vm_compiler/_compiler.pyx +85 -58
  8. ck/circuit_compiler/named_circuit_compilers.py +1 -1
  9. ck/in_out/parse_ace_nnf.py +71 -47
  10. ck/in_out/parser_utils.py +1 -1
  11. ck/pgm_compiler/ace/ace.py +8 -2
  12. ck/pgm_compiler/factor_elimination.py +23 -13
  13. ck/pgm_compiler/support/circuit_table/__init__.py +2 -2
  14. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cp312-win_amd64.pyd +0 -0
  15. ck/pgm_compiler/support/circuit_table/{circuit_table.pyx → _circuit_table_cy.pyx} +9 -9
  16. ck/pgm_compiler/support/circuit_table/{circuit_table_py.py → _circuit_table_py.py} +5 -5
  17. ck/pgm_compiler/support/clusters.py +16 -4
  18. ck/pgm_compiler/support/factor_tables.py +1 -1
  19. ck/pgm_compiler/support/join_tree.py +67 -10
  20. ck/pgm_compiler/variable_elimination.py +2 -0
  21. ck/utils/local_config.py +270 -0
  22. ck_demos/pgm_compiler/compare_pgm_compilers.py +2 -0
  23. ck_demos/pgm_compiler/demo_compiler_dump.py +10 -0
  24. ck_demos/pgm_compiler/time_fe_compiler.py +93 -0
  25. ck_demos/utils/compare.py +30 -20
  26. {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/METADATA +1 -1
  27. {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/RECORD +30 -31
  28. ck/circuit/circuit.c +0 -38861
  29. ck/circuit/circuit.cp312-win_amd64.pyd +0 -0
  30. ck/circuit/circuit_node.pyx +0 -138
  31. ck/pgm_compiler/support/circuit_table/circuit_table.c +0 -16042
  32. ck/pgm_compiler/support/circuit_table/circuit_table.cp312-win_amd64.pyd +0 -0
  33. {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/WHEEL +0 -0
  34. {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/licenses/LICENSE.txt +0 -0
  35. {compiled_knowledge-4.0.0a15.dist-info → compiled_knowledge-4.0.0a17.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- from pickletools import long1
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 CircuitNode, ConstNode, VarNode, OpNode, ADD, Circuit
9
+ from ck.circuit import OpNode, VarNode, CircuitNode
11
10
  from ck.circuit_compiler.support.circuit_analyser import CircuitAnalysis, analyze_circuit
12
- from ck.circuit_compiler.support.input_vars import infer_input_vars, InputVars
13
- from ck.program.raw_program import RawProgram, RawProgramFunction
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
- COPY: int = max(ADD, MUL) + 1
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
- VARS: int = 0
72
- TMPS: int = 1
73
- CONSTS: int = 2
74
- RESULT: int = 3
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
- def _make_instructions_from_analysis(
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
- node_to_element: Dict[int, ElementID] = {}
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: OpNode
111
+ cdef object op_node
114
112
  for op_node in analysis.op_nodes:
115
- dest: ElementID = node_to_element[id(op_node)]
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
- args: list[ElementID] = [node_to_element[id(node)]]
127
- instructions.append(COPY, args, dest)
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
- def append(self, int symbol, list[ElementID] args, ElementID dest) -> None:
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
- c_args = <ElementID*> PyMem_Malloc(
157
- num_args * sizeof(ElementID))
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
- for i in range(num_args):
162
- c_args[i] = args[i]
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, num_args, symbol
224
+ cdef int i, symbol
201
225
  cdef double accumulator
202
226
  cdef ElementID* args
203
- cdef ElementID elem
227
+ cdef ElementID elem
204
228
 
205
- # index the four arrays by constants VARS, TMPS, CONSTS, and RESULT
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
- for _ in range(instructions.num_instructions):
237
+ cdef int num_instructions = instructions.num_instructions
214
238
 
215
- symbol = instruction_ptr[0].symbol
216
- args = instruction_ptr[0].args
217
- num_args = instruction_ptr[0].num_args
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
- for i in range(1, num_args):
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
- for i in range(1, num_args):
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
- elif symbol == COPY:
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[0].dest
262
+ elem = instruction_ptr.dest
236
263
  arrays[elem.array][elem.index] = accumulator
237
264
 
238
265
  # Advance the instruction pointer
@@ -54,4 +54,4 @@ class NamedCircuitCompiler(Enum):
54
54
  return self.value[0]
55
55
 
56
56
 
57
- DEFAULT_CIRCUIT_COMPILER: NamedCircuitCompiler = NamedCircuitCompiler.LLVM_VM
57
+ DEFAULT_CIRCUIT_COMPILER: NamedCircuitCompiler = NamedCircuitCompiler.CYTHON_VM
@@ -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
- top_node, literal_slot_map = read_nnf(
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
- slot_map: Dict[SlotKey, int] = {
80
- indicator: _get_slot(literal_code)
81
- for literal_code, indicator in literal_map.indicators.items()
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] = literal_slot_map.get(literal_code)
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
- The returned slot map will have entries to get circuit vars for literal codes.
113
- E.g., given a literal code (int), use `circuit.vars[slot_map[literal_code]]` to get its var node.
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
- Software may simplify an NNF file by removing arcs, but it may not update the header.
116
- Although the resulting file is not conformant, it is still parsable.
117
- Parameter `check_header` can be set to false, which prevents an exception being raised.
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, literal_slot_map)
144
+ (circuit_top, var_literals)
128
145
  circuit_top: is the resulting top node from parsing the input.
129
- literal_slot_map: is a mapping from literal code (int) to a circuit var index (int).
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 circuit.number_of_vars != 0:
138
- raise ValueError('the given circuit must be empty')
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, parser.literal_slot_map
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.literal_slot_map: Dict[SlotKey, int] = {}
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
- elif literal_code in self.literal_slot_map.keys():
262
- raise_f(f'duplicated literal code: {literal_code}')
263
- return
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.literal_slot_map[literal_code] = node.idx
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
@@ -32,7 +32,7 @@ class ParserInput:
32
32
  def __init__(self, input_stream):
33
33
  self._input = _check_input(input_stream)
34
34
  self._prev_line_len = 0
35
- self._cur_line = 1
35
+ self._cur_line = 0
36
36
  self._cur_char = 1
37
37
  self._lookahead = []
38
38
 
@@ -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
- top_table.circuit.remove_unreachable_op_nodes(top)
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
- # The PGM factors allocated to this join tree node
174
- factors: List[CircuitTable] = [
175
- factor_tables.get_table(factor)
176
- for factor in join_tree.factors
177
- ]
178
-
179
- # The children of this join tree node
180
- factors.extend(
181
- _circuit_tables_from_join_tree(factor_tables, child, limit_product_tree_search)
182
- for child in join_tree.children
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 `options.optimise_products` is true, then heuristics are used
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: no factors
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:
@@ -1,5 +1,5 @@
1
- # from .circuit_table_py import (
2
- from .circuit_table import (
1
+ # from ._circuit_table_py import (
2
+ from ._circuit_table_cy import (
3
3
  CircuitTable,
4
4
  TableInstance,
5
5
  sum_out,
@@ -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)