compiled-knowledge 4.0.0__cp313-cp313-musllinux_1_2_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.

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