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.
- ck/__init__.py +0 -0
- ck/circuit/__init__.py +13 -0
- ck/circuit/circuit.c +38749 -0
- ck/circuit/circuit.cpython-313-darwin.so +0 -0
- ck/circuit/circuit_py.py +807 -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 +17373 -0
- ck/circuit_compiler/cython_vm_compiler/_compiler.cpython-313-darwin.so +0 -0
- ck/circuit_compiler/cython_vm_compiler/cython_vm_compiler.py +96 -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.py +81 -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 +53674 -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 +288 -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 +3494 -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 +75 -0
- ck/pgm_circuit/program_with_slotmap.py +234 -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 +252 -0
- ck/pgm_compiler/factor_elimination.py +383 -0
- ck/pgm_compiler/named_pgm_compilers.py +63 -0
- ck/pgm_compiler/pgm_compiler.py +19 -0
- ck/pgm_compiler/recursive_conditioning.py +226 -0
- ck/pgm_compiler/support/__init__.py +0 -0
- ck/pgm_compiler/support/circuit_table/__init__.py +9 -0
- ck/pgm_compiler/support/circuit_table/circuit_table.c +16042 -0
- ck/pgm_compiler/support/circuit_table/circuit_table.cpython-313-darwin.so +0 -0
- ck/pgm_compiler/support/circuit_table/circuit_table_py.py +269 -0
- ck/pgm_compiler/support/clusters.py +556 -0
- ck/pgm_compiler/support/factor_tables.py +398 -0
- ck/pgm_compiler/support/join_tree.py +275 -0
- ck/pgm_compiler/support/named_compiler_maker.py +33 -0
- ck/pgm_compiler/variable_elimination.py +89 -0
- ck/probability/__init__.py +0 -0
- ck/probability/empirical_probability_space.py +47 -0
- ck/probability/probability_space.py +568 -0
- ck/program/__init__.py +3 -0
- ck/program/program.py +129 -0
- ck/program/program_buffer.py +180 -0
- ck/program/raw_program.py +61 -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 +66 -0
- ck/sampling/wmc_direct_sampler.py +169 -0
- ck/sampling/wmc_gibbs_sampler.py +147 -0
- ck/sampling/wmc_metropolis_sampler.py +159 -0
- ck/sampling/wmc_rejection_sampler.py +113 -0
- ck/utils/__init__.py +0 -0
- ck/utils/iter_extras.py +153 -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 +44 -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 +50 -0
- ck_demos/pgm_compiler/demo_compiler_dump.py +50 -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_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 +88 -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.0a5.dist-info/METADATA +50 -0
- compiled_knowledge-4.0.0a5.dist-info/RECORD +167 -0
- compiled_knowledge-4.0.0a5.dist-info/WHEEL +5 -0
- compiled_knowledge-4.0.0a5.dist-info/licenses/LICENSE.txt +21 -0
- compiled_knowledge-4.0.0a5.dist-info/top_level.txt +2 -0
ck/in_out/parse_net.py
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import string
|
|
4
|
+
from typing import Protocol, Optional, List
|
|
5
|
+
|
|
6
|
+
from ck.in_out.parser_utils import ParseError, ParserInput
|
|
7
|
+
from ck.pgm import PGM, Factor, State
|
|
8
|
+
from ck.utils.iter_extras import combos_ranges
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def read_network(input_stream, *, name: Optional[str] = None, network_builder: Optional[NetworkBuilder] = None) -> PGM:
|
|
12
|
+
"""
|
|
13
|
+
Parser for Hugin "net" file format.
|
|
14
|
+
|
|
15
|
+
Read a network and return it as a PGM.
|
|
16
|
+
The input can be a string or a stream.
|
|
17
|
+
If the input is empty, then its is treated as an error.
|
|
18
|
+
|
|
19
|
+
This input is expected to conform to the following format.
|
|
20
|
+
|
|
21
|
+
<network> ::= <net_block> <node_block>* <potential_block>*
|
|
22
|
+
|
|
23
|
+
<net_block> ::= 'net' <block>
|
|
24
|
+
|
|
25
|
+
<node_block> ::= 'node' <NAME> <block>
|
|
26
|
+
|
|
27
|
+
<potential_block> ::= 'potential' <link> <block>
|
|
28
|
+
|
|
29
|
+
<block> ::= '{' <sentence>* '}'
|
|
30
|
+
<sentence> ::= <NAME> '=' <value> ';'
|
|
31
|
+
|
|
32
|
+
<link> ::= '(' <NAME> ')'
|
|
33
|
+
| '(' <NAME> '|' <NAME>+ ')'
|
|
34
|
+
|
|
35
|
+
<value> ::= <STRING> | <NUMBER> | <list>
|
|
36
|
+
<list> ::='(' <value>* ')'
|
|
37
|
+
|
|
38
|
+
The sentences of a <net_block> are ignored.
|
|
39
|
+
|
|
40
|
+
The sentences of a <node_block>
|
|
41
|
+
<name> of 'states' mandatory, with value that is a list of <STRING>
|
|
42
|
+
other sentences are ignored.
|
|
43
|
+
|
|
44
|
+
The sentences of a <potential_block>
|
|
45
|
+
<name> of 'data' mandatory, with value that is a list of (list of)* <NUMBER> (shape matching the link)
|
|
46
|
+
other sentences are ignored.
|
|
47
|
+
|
|
48
|
+
Here is a simple example input:
|
|
49
|
+
net{}
|
|
50
|
+
node a
|
|
51
|
+
{
|
|
52
|
+
states = ( "a0" "a1" );
|
|
53
|
+
}
|
|
54
|
+
node b
|
|
55
|
+
{
|
|
56
|
+
states = ( "b0" "b1" "b2" );
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
potential ( a )
|
|
60
|
+
{
|
|
61
|
+
data = ( 0.5 0.5 );
|
|
62
|
+
}
|
|
63
|
+
potential ( b | a )
|
|
64
|
+
{
|
|
65
|
+
data = ((0.4 0.4 0.2)(0.4 0.4 0.2)) ;
|
|
66
|
+
}
|
|
67
|
+
"""
|
|
68
|
+
# Decorate the input stream
|
|
69
|
+
input_stream = ParserInput(input_stream)
|
|
70
|
+
|
|
71
|
+
if network_builder is None:
|
|
72
|
+
network_builder = PGM_NetworkBuilder(PGM_NetworkBuilder.add_function_dense)
|
|
73
|
+
|
|
74
|
+
bn = network_builder.start(name)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
_read_net(network_builder, bn, input_stream)
|
|
78
|
+
c = _read_past_space(input_stream)
|
|
79
|
+
if len(c) != 0:
|
|
80
|
+
input_stream.raise_error('unexpected extra characters at end of input')
|
|
81
|
+
except ParseError as e:
|
|
82
|
+
raise e
|
|
83
|
+
except Exception as e:
|
|
84
|
+
input_stream.raise_error(e)
|
|
85
|
+
|
|
86
|
+
return network_builder.done(bn)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class NetworkBuilder(Protocol):
|
|
90
|
+
"""
|
|
91
|
+
This is a protocol used by read_network to build the network
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def start(self, name) -> object:
|
|
95
|
+
...
|
|
96
|
+
|
|
97
|
+
def add_node(self, network, name, states):
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
def add_factor(self, network, child_name, parent_names, data):
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
def done(self, network) -> PGM:
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class PGM_NetworkBuilder(NetworkBuilder):
|
|
108
|
+
"""
|
|
109
|
+
This implementation of NetworkBuilder build a PGM object.
|
|
110
|
+
At construction time, pass one of the add_function_[...] methods
|
|
111
|
+
to the constructor, which selects the type of potential function
|
|
112
|
+
that is used to hold parameters.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def add_function_dense(factor: Factor, data):
|
|
117
|
+
function = factor.set_dense()
|
|
118
|
+
shape = function.shape
|
|
119
|
+
|
|
120
|
+
def my_iter():
|
|
121
|
+
for key in combos_ranges(shape, flip=True):
|
|
122
|
+
parent = key[1:]
|
|
123
|
+
child = key[0]
|
|
124
|
+
d = data
|
|
125
|
+
for i in parent:
|
|
126
|
+
d = d[i]
|
|
127
|
+
yield d[child]
|
|
128
|
+
|
|
129
|
+
function.set_iter(my_iter())
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def add_function_cpt(factor: Factor, data):
|
|
133
|
+
function = factor.set_cpt()
|
|
134
|
+
shape = function.shape
|
|
135
|
+
parent_shape = shape[1:]
|
|
136
|
+
for parent in combos_ranges(parent_shape):
|
|
137
|
+
cpd = data
|
|
138
|
+
for i in parent:
|
|
139
|
+
cpd = cpd[i]
|
|
140
|
+
function.set_cpd(parent, cpd)
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def add_function_sparse(factor: Factor, data):
|
|
144
|
+
function = factor.set_sparse()
|
|
145
|
+
shape = function.shape
|
|
146
|
+
for key in combos_ranges(shape):
|
|
147
|
+
parent = key[1:]
|
|
148
|
+
child = key[0]
|
|
149
|
+
d = data
|
|
150
|
+
for i in parent:
|
|
151
|
+
d = d[i]
|
|
152
|
+
function[key] = d[child]
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def add_function_compact(factor: Factor, data):
|
|
156
|
+
function = factor.set_compact()
|
|
157
|
+
shape = function.shape
|
|
158
|
+
for key in combos_ranges(shape):
|
|
159
|
+
parent = key[1:]
|
|
160
|
+
child = key[0]
|
|
161
|
+
d = data
|
|
162
|
+
for i in parent:
|
|
163
|
+
d = d[i]
|
|
164
|
+
function[key] = d[child]
|
|
165
|
+
|
|
166
|
+
def __init__(self, potential_function_type):
|
|
167
|
+
"""
|
|
168
|
+
Args:
|
|
169
|
+
potential_function_type: should be one of:
|
|
170
|
+
PGM_NetworkBuilder.add_function_dense,
|
|
171
|
+
PGM_NetworkBuilder.add_function_cpt,
|
|
172
|
+
PGM_NetworkBuilder.add_function_sparse,
|
|
173
|
+
PGM_NetworkBuilder.add_function_compact.
|
|
174
|
+
"""
|
|
175
|
+
self.add_pot_function = potential_function_type
|
|
176
|
+
|
|
177
|
+
def start(self, name):
|
|
178
|
+
return [PGM(name), {}]
|
|
179
|
+
|
|
180
|
+
def add_node(self, pgm_pair, name, states):
|
|
181
|
+
pgm, rv_map = pgm_pair
|
|
182
|
+
rv = pgm.new_rv(name, states)
|
|
183
|
+
rv_map[name] = rv
|
|
184
|
+
|
|
185
|
+
def add_factor(self, pgm_pair, child_name, parent_names, data):
|
|
186
|
+
pgm, rv_map = pgm_pair
|
|
187
|
+
rvs = [rv_map[rv_name] for rv_name in [child_name] + parent_names]
|
|
188
|
+
factor = pgm.new_factor(*rvs)
|
|
189
|
+
self.add_pot_function(factor, data)
|
|
190
|
+
|
|
191
|
+
def done(self, pgm_pair) -> PGM:
|
|
192
|
+
pgm, rv_map = pgm_pair
|
|
193
|
+
return pgm
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _read_net(network_builder: NetworkBuilder, bn, input_stream) -> None:
|
|
197
|
+
_read_net_block(input_stream)
|
|
198
|
+
_read_blocks(network_builder, bn, input_stream)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _pass_callback(*_) -> None:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _read_past_space(input_stream) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Returns:
|
|
208
|
+
either empty string, '', if end of input, otherwise a single character string that is not whitespace.
|
|
209
|
+
"""
|
|
210
|
+
return input_stream.read_past_space(single_line=False, comment_char='%')
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _read_name(input_stream) -> str:
|
|
214
|
+
"""
|
|
215
|
+
Returns:
|
|
216
|
+
the read name
|
|
217
|
+
"""
|
|
218
|
+
c = _read_past_space(input_stream)
|
|
219
|
+
name = ""
|
|
220
|
+
while len(c) == 1:
|
|
221
|
+
if (
|
|
222
|
+
c in string.ascii_letters
|
|
223
|
+
or c in string.digits
|
|
224
|
+
or c in "_-."
|
|
225
|
+
):
|
|
226
|
+
name += c
|
|
227
|
+
else:
|
|
228
|
+
input_stream.pushback(c)
|
|
229
|
+
break
|
|
230
|
+
c = input_stream.read_one()
|
|
231
|
+
|
|
232
|
+
if len(name) == 0:
|
|
233
|
+
input_stream.raise_error('expected a name but none read')
|
|
234
|
+
|
|
235
|
+
return name
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _read_keyword(input_stream) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Returns:
|
|
241
|
+
the read keyword
|
|
242
|
+
"""
|
|
243
|
+
c = _read_past_space(input_stream)
|
|
244
|
+
keyword = ""
|
|
245
|
+
while len(c) == 1:
|
|
246
|
+
if c in string.ascii_letters:
|
|
247
|
+
keyword += c
|
|
248
|
+
else:
|
|
249
|
+
input_stream.pushback(c)
|
|
250
|
+
break
|
|
251
|
+
c = input_stream.read_one()
|
|
252
|
+
if len(keyword) == 0:
|
|
253
|
+
input_stream.raise_error('expected a keyword but none read')
|
|
254
|
+
return keyword.lower()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _read_net_block(input_stream) -> None:
|
|
258
|
+
keyword = _read_keyword(input_stream)
|
|
259
|
+
if keyword != 'net':
|
|
260
|
+
input_stream.raise_error(f'expected keyword "net" but got: {keyword!r}')
|
|
261
|
+
_read_block(_pass_callback, input_stream)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _read_blocks(network_builder, bn, input_stream) -> None:
|
|
265
|
+
lookahead = _read_past_space(input_stream)
|
|
266
|
+
while lookahead != '':
|
|
267
|
+
input_stream.pushback(lookahead)
|
|
268
|
+
keyword = _read_keyword(input_stream)
|
|
269
|
+
if keyword == 'node':
|
|
270
|
+
_read_node_block(network_builder, bn, input_stream)
|
|
271
|
+
elif keyword == 'potential':
|
|
272
|
+
_read_potential_block(network_builder, bn, input_stream)
|
|
273
|
+
else:
|
|
274
|
+
input_stream.raise_error(f'expected keyword "node" or "potential" but got: {keyword!r}')
|
|
275
|
+
lookahead = _read_past_space(input_stream)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _read_node_block(network_builder, bn, input_stream) -> None:
|
|
279
|
+
"""
|
|
280
|
+
Assumes already read the 'node' keyword from the input.
|
|
281
|
+
"""
|
|
282
|
+
name = _read_name(input_stream)
|
|
283
|
+
|
|
284
|
+
names = [name, None]
|
|
285
|
+
states = [None]
|
|
286
|
+
|
|
287
|
+
def callback(_name: str, _value):
|
|
288
|
+
if _name == 'label':
|
|
289
|
+
if names[1] is not None:
|
|
290
|
+
input_stream.raise_error(f'duplicate "label" sentence in node: {_name}')
|
|
291
|
+
names[0] = names[1] = _value
|
|
292
|
+
elif _name == 'states':
|
|
293
|
+
if states[0] is not None:
|
|
294
|
+
input_stream.raise_error(f'duplicate "states" sentence in node: {_name}')
|
|
295
|
+
assert _value is not None
|
|
296
|
+
states[0] = _value
|
|
297
|
+
|
|
298
|
+
_read_block(callback, input_stream)
|
|
299
|
+
|
|
300
|
+
states: Optional[List[State]] = states[0]
|
|
301
|
+
name: str = names[0]
|
|
302
|
+
|
|
303
|
+
if states is None:
|
|
304
|
+
input_stream.raise_error(f'no "states" found in node: {name}')
|
|
305
|
+
elif len(states) < 2:
|
|
306
|
+
input_stream.raise_error(f'must be at least 2 states in a node: {name}')
|
|
307
|
+
network_builder.add_node(bn, name, states)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _read_potential_block(network_builder, bn, input_stream):
|
|
311
|
+
"""
|
|
312
|
+
Assumes already read the 'potential' keyword from the input.
|
|
313
|
+
"""
|
|
314
|
+
link = _read_link(input_stream)
|
|
315
|
+
data = [None]
|
|
316
|
+
|
|
317
|
+
def callback(name, value):
|
|
318
|
+
if name == 'data':
|
|
319
|
+
if data[0] is not None:
|
|
320
|
+
input_stream.raise_error(f'duplicate "data" sentence in potential: {link}')
|
|
321
|
+
assert value is not None
|
|
322
|
+
data[0] = value
|
|
323
|
+
|
|
324
|
+
_read_block(callback, input_stream)
|
|
325
|
+
network_builder.add_factor(bn, link[0], link[1:], data[0])
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _read_link(input_stream) -> List[str]:
|
|
329
|
+
"""
|
|
330
|
+
Returns:
|
|
331
|
+
the read link
|
|
332
|
+
"""
|
|
333
|
+
c = _read_past_space(input_stream)
|
|
334
|
+
if c != '(':
|
|
335
|
+
input_stream.raise_error('reading a link, expected "("')
|
|
336
|
+
|
|
337
|
+
name = _read_name(input_stream)
|
|
338
|
+
link = [name]
|
|
339
|
+
|
|
340
|
+
c = _read_past_space(input_stream)
|
|
341
|
+
if c == ')':
|
|
342
|
+
return link
|
|
343
|
+
if c != '|':
|
|
344
|
+
input_stream.raise_error('reading a link, expected ")" or "|"')
|
|
345
|
+
|
|
346
|
+
c = _read_past_space(input_stream)
|
|
347
|
+
while c != ')':
|
|
348
|
+
if c == '':
|
|
349
|
+
input_stream.raise_error('unexpected end of input while reading a link for a potential')
|
|
350
|
+
input_stream.pushback(c)
|
|
351
|
+
name = _read_name(input_stream)
|
|
352
|
+
link.append(name)
|
|
353
|
+
c = _read_past_space(input_stream)
|
|
354
|
+
return link
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _read_block(sentence_callback, input_stream):
|
|
358
|
+
c = _read_past_space(input_stream)
|
|
359
|
+
if c != '{':
|
|
360
|
+
input_stream.raise_error('parse error - expected "{"')
|
|
361
|
+
c = _read_past_space(input_stream)
|
|
362
|
+
while c != '}':
|
|
363
|
+
if c == '':
|
|
364
|
+
input_stream.raise_error('parse error - unexpected end of input while reading a block')
|
|
365
|
+
input_stream.pushback(c)
|
|
366
|
+
_read_sentence(sentence_callback, input_stream)
|
|
367
|
+
c = _read_past_space(input_stream)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _read_sentence(sentence_callback, input_stream):
|
|
371
|
+
name = _read_name(input_stream)
|
|
372
|
+
c = _read_past_space(input_stream)
|
|
373
|
+
if c != '=':
|
|
374
|
+
input_stream.raise_error('parse error - expected "="')
|
|
375
|
+
value = _read_value(input_stream)
|
|
376
|
+
c = _read_past_space(input_stream)
|
|
377
|
+
if c != ';':
|
|
378
|
+
input_stream.raise_error('parse error - expected ";"')
|
|
379
|
+
sentence_callback(name, value)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _read_value(input_stream) -> List | str | float:
|
|
383
|
+
"""
|
|
384
|
+
Returns:
|
|
385
|
+
(value, lookahead)
|
|
386
|
+
"""
|
|
387
|
+
lookahead = _read_past_space(input_stream)
|
|
388
|
+
if lookahead == '':
|
|
389
|
+
input_stream.raise_error('parse error - expected value - unexpected end of input')
|
|
390
|
+
|
|
391
|
+
input_stream.pushback(lookahead)
|
|
392
|
+
if lookahead == '(':
|
|
393
|
+
# reading a list
|
|
394
|
+
value = _read_list(input_stream)
|
|
395
|
+
elif lookahead == '"':
|
|
396
|
+
# reading a string
|
|
397
|
+
value = _read_string(input_stream)
|
|
398
|
+
else:
|
|
399
|
+
# reading a number
|
|
400
|
+
value = _read_number(input_stream)
|
|
401
|
+
return value
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _read_list(input_stream) -> List:
|
|
405
|
+
"""
|
|
406
|
+
Returns:
|
|
407
|
+
the read list of values
|
|
408
|
+
"""
|
|
409
|
+
c = _read_past_space(input_stream)
|
|
410
|
+
if c != '(':
|
|
411
|
+
input_stream.raise_error('parse error - expected "("')
|
|
412
|
+
result = []
|
|
413
|
+
c = _read_past_space(input_stream)
|
|
414
|
+
while c != ')':
|
|
415
|
+
if c == '':
|
|
416
|
+
input_stream.raise_error('parse error - unexpected end of input while reading list')
|
|
417
|
+
input_stream.pushback(c)
|
|
418
|
+
value = _read_value(input_stream)
|
|
419
|
+
result.append(value)
|
|
420
|
+
c = _read_past_space(input_stream)
|
|
421
|
+
return result
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _read_string(input_stream) -> str:
|
|
425
|
+
"""
|
|
426
|
+
Returns:
|
|
427
|
+
the read string value
|
|
428
|
+
"""
|
|
429
|
+
c = _read_past_space(input_stream)
|
|
430
|
+
if c != '"':
|
|
431
|
+
input_stream.raise_error('parse error - expected open quote (")')
|
|
432
|
+
result = ""
|
|
433
|
+
while True:
|
|
434
|
+
c = input_stream.read_one()
|
|
435
|
+
if c == '':
|
|
436
|
+
input_stream.raise_error('parse error - unexpected end of input while reading string')
|
|
437
|
+
if c == '"':
|
|
438
|
+
break
|
|
439
|
+
if c == '\\':
|
|
440
|
+
c = input_stream.read_one()
|
|
441
|
+
if c in '\\\'\"':
|
|
442
|
+
pass
|
|
443
|
+
elif c == 'n':
|
|
444
|
+
c = '\n'
|
|
445
|
+
elif c == 'r':
|
|
446
|
+
c = '\r'
|
|
447
|
+
elif c == 'v':
|
|
448
|
+
c = '\v'
|
|
449
|
+
elif c == 'f':
|
|
450
|
+
c = '\f'
|
|
451
|
+
elif c == 'a':
|
|
452
|
+
c = '\a'
|
|
453
|
+
elif c == 'b':
|
|
454
|
+
c = '\b'
|
|
455
|
+
elif c == 't':
|
|
456
|
+
c = '\t'
|
|
457
|
+
else:
|
|
458
|
+
input_stream.raise_error(f'parse error - unexpected escape code while reading string: \\{c}')
|
|
459
|
+
result += c
|
|
460
|
+
return result
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _read_number(input_stream) -> float:
|
|
464
|
+
"""
|
|
465
|
+
Return:
|
|
466
|
+
the read numeric value
|
|
467
|
+
"""
|
|
468
|
+
value = ''
|
|
469
|
+
c = _read_past_space(input_stream)
|
|
470
|
+
while len(c) == 1:
|
|
471
|
+
if not (c in string.digits or c in ".eE+-"):
|
|
472
|
+
input_stream.pushback(c)
|
|
473
|
+
break
|
|
474
|
+
value += c
|
|
475
|
+
c = input_stream.read_one()
|
|
476
|
+
try:
|
|
477
|
+
value = float(value)
|
|
478
|
+
except ValueError:
|
|
479
|
+
input_stream.raise_error('parse error - could not parse number')
|
|
480
|
+
return value
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Support for parsers.
|
|
3
|
+
|
|
4
|
+
Defines:
|
|
5
|
+
ParserInput
|
|
6
|
+
ParseError.
|
|
7
|
+
"""
|
|
8
|
+
import re as _re
|
|
9
|
+
import string as _string
|
|
10
|
+
from io import StringIO as _StringIO
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ParseError(Exception):
|
|
14
|
+
"""
|
|
15
|
+
Raised when a parse error occurs.
|
|
16
|
+
Keeps track of the location of the error (line number and character position in the line).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, line_num: int, char_pos: int, message: str):
|
|
20
|
+
full_msg = f'line {line_num}, pos {char_pos}: {message}'
|
|
21
|
+
super(ParseError, self).__init__(full_msg)
|
|
22
|
+
self.line_num: int = line_num
|
|
23
|
+
self.char_pos: int = char_pos
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ParserInput:
|
|
27
|
+
"""
|
|
28
|
+
A wrapper for an input stream of characters with
|
|
29
|
+
integrated exception raising and infinite pushback.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, input_stream):
|
|
33
|
+
self._input = _check_input(input_stream)
|
|
34
|
+
self._prev_line_len = 0
|
|
35
|
+
self._cur_line = 1
|
|
36
|
+
self._cur_char = 1
|
|
37
|
+
self._lookahead = []
|
|
38
|
+
|
|
39
|
+
def read_one(self) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Read one character from the input stream.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
a character from the input stream, or '' if EOF.
|
|
45
|
+
"""
|
|
46
|
+
if len(self._lookahead) > 0:
|
|
47
|
+
c = self._lookahead.pop()
|
|
48
|
+
else:
|
|
49
|
+
c = self._input.read(1)
|
|
50
|
+
if c == '\n':
|
|
51
|
+
self._prev_line_len = self._cur_char
|
|
52
|
+
self._cur_line += 1
|
|
53
|
+
self._cur_char = 1
|
|
54
|
+
else:
|
|
55
|
+
self._cur_char += 1
|
|
56
|
+
return c
|
|
57
|
+
|
|
58
|
+
def readline(self) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Returns:
|
|
61
|
+
the next line (including the trailing '\n') or '' if EOF.
|
|
62
|
+
"""
|
|
63
|
+
line = ''
|
|
64
|
+
while True:
|
|
65
|
+
c = self.read_one()
|
|
66
|
+
line += c
|
|
67
|
+
if c == '' or c == '\n':
|
|
68
|
+
return line
|
|
69
|
+
|
|
70
|
+
def read_past_space(self, single_line: bool, comment_char=None) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Returns:
|
|
73
|
+
either empty string, '', if end of input, otherwise a single character string that is not whitespace.
|
|
74
|
+
If single_line is True, then '\n' is treated as eof.
|
|
75
|
+
"""
|
|
76
|
+
c = self.read_one()
|
|
77
|
+
while True:
|
|
78
|
+
if not single_line:
|
|
79
|
+
while len(c) == 1 and c in _string.whitespace:
|
|
80
|
+
c = self.read_one()
|
|
81
|
+
else:
|
|
82
|
+
while len(c) == 1 and c in _string.whitespace and c != '\n':
|
|
83
|
+
c = self.read_one()
|
|
84
|
+
|
|
85
|
+
if comment_char is None or c != comment_char:
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
# in a comment
|
|
89
|
+
c = self.read_one()
|
|
90
|
+
while len(c) == 1 and c != '\n':
|
|
91
|
+
c = self.read_one()
|
|
92
|
+
|
|
93
|
+
if single_line and c == '\n':
|
|
94
|
+
self.pushback('\n')
|
|
95
|
+
c = ''
|
|
96
|
+
|
|
97
|
+
return c
|
|
98
|
+
|
|
99
|
+
def pushback(self, c: str) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Push a character back to the input stream.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
c: a character to push back to the input stream.
|
|
105
|
+
"""
|
|
106
|
+
assert (len(c) == 1)
|
|
107
|
+
self._lookahead.append(c)
|
|
108
|
+
# Do our best to keep the input position counters sensible
|
|
109
|
+
if c == '\n':
|
|
110
|
+
self._cur_line -= 1
|
|
111
|
+
self._cur_char = self._prev_line_len
|
|
112
|
+
self._prev_line_len = 0
|
|
113
|
+
else:
|
|
114
|
+
self._cur_char -= 1
|
|
115
|
+
|
|
116
|
+
def raise_error(self, message: str) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Raise a ParseError with the given message and the current line number and character position.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
message: the error message.
|
|
122
|
+
"""
|
|
123
|
+
if isinstance(message, Exception):
|
|
124
|
+
message = f'Exception: {message.__class__.__name__} {message}'
|
|
125
|
+
raise ParseError(self._cur_line, self._cur_char, message)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _check_input(input_stream):
|
|
129
|
+
"""
|
|
130
|
+
If the given argument is a string, then wrap it in a StringIO.
|
|
131
|
+
"""
|
|
132
|
+
if isinstance(input_stream, str):
|
|
133
|
+
return _StringIO(input_stream)
|
|
134
|
+
else:
|
|
135
|
+
return input_stream
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def escape_string(s: str, *, double_quotes: bool = False, single_quotes: bool = False) -> str:
|
|
139
|
+
"""
|
|
140
|
+
Return the given string with backslash escaping for special characters.
|
|
141
|
+
Args:
|
|
142
|
+
s: The string to process.
|
|
143
|
+
double_quotes: should double quotes be escaped?
|
|
144
|
+
single_quotes: should single quotes be escaped?
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
s = s.replace('\\', '\\\\') # must come first
|
|
150
|
+
s = s.replace('\n', '\\n')
|
|
151
|
+
s = s.replace('\r', '\\r')
|
|
152
|
+
s = s.replace('\v', '\\v')
|
|
153
|
+
s = s.replace('\f', '\\f')
|
|
154
|
+
s = s.replace('\f', '\\f')
|
|
155
|
+
s = s.replace('\a', '\\a')
|
|
156
|
+
s = s.replace('\b', '\\b')
|
|
157
|
+
s = s.replace('\t', '\\t')
|
|
158
|
+
if double_quotes:
|
|
159
|
+
s = s.replace('"', '\\"')
|
|
160
|
+
if single_quotes:
|
|
161
|
+
s = s.replace("'", "\\'")
|
|
162
|
+
return s
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def clean_string(s: str, valid_chars: str = '[a-zA-Z0-9_]', replace_char: str = '_') -> str:
|
|
166
|
+
"""
|
|
167
|
+
Remove any invalid char from the given string and replace with
|
|
168
|
+
the given 'replace_char'
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
s: The string to process.
|
|
172
|
+
valid_chars: the set of valid characters (as a regular expression).
|
|
173
|
+
replace_char: the character to replace invalid characters with.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
the cleaned string.
|
|
177
|
+
"""
|
|
178
|
+
re = _re.compile(valid_chars)
|
|
179
|
+
result = ''
|
|
180
|
+
for c in s:
|
|
181
|
+
if re.match(c):
|
|
182
|
+
result += c
|
|
183
|
+
else:
|
|
184
|
+
result += replace_char
|
|
185
|
+
return result
|
ck/in_out/pgm_pickle.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from ck.pgm import PGM
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def write_pickle(pgm: PGM, file) -> None:
|
|
8
|
+
"""
|
|
9
|
+
Write the PGM as a pickle file.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
pgm: The PGM to write.
|
|
13
|
+
file: Either a file name, file Path, or an object with a write method.
|
|
14
|
+
"""
|
|
15
|
+
if isinstance(file, (str, Path)):
|
|
16
|
+
with open(file, 'wb') as f:
|
|
17
|
+
pickle.dump(pgm, f)
|
|
18
|
+
else:
|
|
19
|
+
pickle.dump(pgm, file)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def read_pickle(file) -> PGM:
|
|
23
|
+
"""
|
|
24
|
+
Read a PGM as a pickle file.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
file: Either a file name, file Path, or an object with a read method.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
A PGM object.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
RuntimeError: if the unpickled object is not a PGM.
|
|
34
|
+
"""
|
|
35
|
+
if isinstance(file, (str, Path)):
|
|
36
|
+
with open(file, 'rb') as f:
|
|
37
|
+
pgm = pickle.load(f)
|
|
38
|
+
else:
|
|
39
|
+
pgm = pickle.load(file)
|
|
40
|
+
if not isinstance(pgm, PGM):
|
|
41
|
+
raise RuntimeError('not a PGM object')
|
|
42
|
+
return pgm
|