compiled-knowledge 4.0.0a24__cp313-cp313-win_amd64.whl → 4.1.0a1__cp313-cp313-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 (42) hide show
  1. ck/circuit/_circuit_cy.c +1 -1
  2. ck/circuit/_circuit_cy.cp313-win_amd64.pyd +0 -0
  3. ck/circuit/tmp_const.py +5 -4
  4. ck/circuit_compiler/cython_vm_compiler/_compiler.c +152 -152
  5. ck/circuit_compiler/cython_vm_compiler/_compiler.cp313-win_amd64.pyd +0 -0
  6. ck/circuit_compiler/interpret_compiler.py +2 -2
  7. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.c +1 -1
  8. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.cp313-win_amd64.pyd +0 -0
  9. ck/circuit_compiler/support/llvm_ir_function.py +4 -4
  10. ck/dataset/__init__.py +1 -0
  11. ck/dataset/cross_table.py +270 -0
  12. ck/dataset/cross_table_probabilities.py +53 -0
  13. ck/dataset/dataset.py +577 -0
  14. ck/dataset/dataset_compute.py +140 -0
  15. ck/dataset/dataset_from_crosstable.py +45 -0
  16. ck/dataset/dataset_from_csv.py +147 -0
  17. ck/dataset/sampled_dataset.py +96 -0
  18. ck/example/diamond_square.py +3 -1
  19. ck/example/triangle_square.py +3 -1
  20. ck/example/truss.py +3 -1
  21. ck/in_out/parse_net.py +21 -19
  22. ck/in_out/parser_utils.py +7 -3
  23. ck/learning/__init__.py +0 -0
  24. ck/learning/train_generative.py +149 -0
  25. ck/pgm.py +95 -84
  26. ck/pgm_circuit/mpe_program.py +3 -4
  27. ck/pgm_circuit/pgm_circuit.py +27 -18
  28. ck/pgm_circuit/program_with_slotmap.py +27 -46
  29. ck/pgm_circuit/support/compile_circuit.py +2 -4
  30. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.c +1 -1
  31. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cp313-win_amd64.pyd +0 -0
  32. ck/probability/empirical_probability_space.py +1 -0
  33. ck/probability/probability_space.py +10 -11
  34. ck/program/raw_program.py +23 -16
  35. ck/sampling/sampler_support.py +5 -6
  36. ck/utils/iter_extras.py +3 -2
  37. ck/utils/local_config.py +16 -8
  38. {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/METADATA +1 -1
  39. {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/RECORD +42 -32
  40. {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/WHEEL +0 -0
  41. {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/licenses/LICENSE.txt +0 -0
  42. {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/top_level.txt +0 -0
ck/dataset/dataset.py ADDED
@@ -0,0 +1,577 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Sequence, Optional, Dict, Iterable, Tuple
4
+
5
+ import numpy as np
6
+
7
+ from ck.pgm import RandomVariable, State
8
+ from ck.utils.np_extras import NDArray, DTypeStates, dtype_for_number_of_states
9
+
10
+
11
+ class Dataset:
12
+ """
13
+ A dataset has instances (rows) for zero or more random variables.
14
+ Each instance has a weight, which is notionally one.
15
+ Weights of instances should be non-negative, and are normally positive.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ weights: Optional[NDArray | Sequence],
21
+ length: Optional[int],
22
+ ):
23
+ # Infer the length of the dataset.
24
+ if length is not None:
25
+ self._length: int = length
26
+ else:
27
+ self._length: int = len(weights)
28
+
29
+ # Set no random variables
30
+ self._rvs: Tuple[RandomVariable, ...] = ()
31
+
32
+ # Set the weights array, and confirm its shape
33
+ self._weights: NDArray
34
+ if weights is None:
35
+ weights = np.ones(self._length)
36
+ elif not isinstance(weights, np.ndarray):
37
+ weights = np.array(weights)
38
+ expected_shape = (self._length,)
39
+ if weights.shape != expected_shape:
40
+ raise ValueError(f'weights expected shape {expected_shape}, got {weights.shape}')
41
+
42
+ self._weights = weights
43
+
44
+ def __len__(self) -> int:
45
+ """
46
+ How many instances in the dataset.
47
+ """
48
+ return self._length
49
+
50
+ @property
51
+ def rvs(self) -> Sequence[RandomVariable]:
52
+ """
53
+ Return the random variables covered by this dataset.
54
+ """
55
+ return self._rvs
56
+
57
+ @property
58
+ def weights(self) -> NDArray:
59
+ """
60
+ Get the instance weights.
61
+ The notional weight of an instance is 1.
62
+ The index into the returned array is the instance index.
63
+
64
+ Returns:
65
+ A 1D array of random variable states, with shape = `(len(self), )`.
66
+ """
67
+ return self._weights
68
+
69
+ def total_weight(self) -> float:
70
+ """
71
+ Calculate the total weight of this dataset.
72
+ """
73
+ return self._weights.sum().item()
74
+
75
+ def _add_rv(self, rv: RandomVariable) -> None:
76
+ """
77
+ Add a random variable to self.rvs.
78
+ """
79
+ self._rvs += (rv,)
80
+
81
+ def _remove_rv(self, rv: RandomVariable) -> None:
82
+ """
83
+ Remove a random variable from self.rvs.
84
+ """
85
+ rvs = self._rvs
86
+ i: int = self._rvs.index(rv)
87
+ self._rvs = rvs[:i] + rvs[i+1:]
88
+
89
+
90
+ class HardDataset(Dataset):
91
+ """
92
+ A hard dataset is a dataset where for each instance (row) and each random variable,
93
+ there is a state for that random variable (a state is represented as a state index).
94
+ Each instance has a weight, which is notionally one.
95
+ """
96
+
97
+ @staticmethod
98
+ def from_soft_dataset(
99
+ soft_dataset: SoftDataset,
100
+ adjust_instance_weights: bool = True,
101
+ ) -> HardDataset:
102
+ """
103
+ Create a hard dataset from a soft dataset by repeated application
104
+ of `SoftDataset.add_rv_from_state_weights`.
105
+
106
+ The instance weights of the returned dataset will be a copy
107
+ of the instance weights of the soft dataset.
108
+
109
+ Args:
110
+ soft_dataset: The soft dataset providing random variables,
111
+ their states, and instance weights.
112
+ adjust_instance_weights: If `True` (default), then the instance weights will be
113
+ adjusted according to sum of state weights for each instance. That is, if
114
+ the sum is not one for some instance, then the weight of that instance will
115
+ be adjusted.
116
+
117
+ Returns:
118
+ A `HardDataset` instance.
119
+ """
120
+ dataset = HardDataset(weights=soft_dataset.weights.copy())
121
+ for rv in soft_dataset.rvs:
122
+ dataset.add_rv_from_state_weights(rv, soft_dataset.state_weights(rv), adjust_instance_weights)
123
+ return dataset
124
+
125
+ def __init__(
126
+ self,
127
+ data: Iterable[Tuple[RandomVariable, NDArray | Sequence[int]]] = (),
128
+ weights: Optional[NDArray | Sequence] = None,
129
+ length: Optional[int] = None,
130
+ ):
131
+ """
132
+ Create a hard dataset.
133
+
134
+ Args:
135
+ data: optional iterable of (random variable, state idxs), passed
136
+ to `self.add_rv_from_state_idxs`.
137
+ weights: optional array of instance weights.
138
+ length: optional length of the dataset, if omitted, the length is inferred.
139
+ """
140
+ self._data: Dict[RandomVariable, NDArray] = {}
141
+
142
+ # Initialise super by either weights, length or first data item.
143
+ super_initialised: bool = False
144
+ if weights is not None or length is not None:
145
+ super().__init__(weights, length)
146
+ super_initialised = True
147
+
148
+ for rv, states in data:
149
+ if not super_initialised:
150
+ super().__init__(weights, len(states))
151
+ super_initialised = True
152
+ self.add_rv_from_state_idxs(rv, states)
153
+
154
+ if not super_initialised:
155
+ super().__init__(weights, 0)
156
+
157
+ def states(self, rv: RandomVariable) -> NDArray:
158
+ """
159
+ Get the state values (by state index) for one random variable.
160
+ The index into the returned array is the instance index.
161
+
162
+ Returns:
163
+ A 1D array of random variable states, with shape = `(len(self), )`.
164
+
165
+ Raises:
166
+ KeyError: If the random variable is not in the dataset.
167
+ """
168
+ return self._data[rv]
169
+
170
+ def add_rv(self, rv: RandomVariable) -> NDArray:
171
+ """
172
+ Add a random variable to the dataset, allocating and returning
173
+ the state indices for the random variable.
174
+
175
+ Args:
176
+ rv: The random variable to add.
177
+
178
+ Returns:
179
+ A 1D array of random variable states, with shape = `(len(self), )`, initialised to zero.
180
+
181
+ Raises:
182
+ ValueError: If the random variable is already in the dataset.
183
+ """
184
+ dtype: DTypeStates = dtype_for_number_of_states(len(rv))
185
+ rv_data = np.zeros(len(self), dtype=dtype)
186
+ return self.add_rv_from_state_idxs(rv, rv_data)
187
+
188
+ def remove_rv(self, rv: RandomVariable) -> None:
189
+ """
190
+ Remove a random variable from the dataset.
191
+
192
+ Args:
193
+ rv: The random variable to remove.
194
+
195
+ Raises:
196
+ KeyError: If the random variable is not in the dataset.
197
+ """
198
+ del self._data[rv]
199
+ self._remove_rv(rv)
200
+
201
+ def add_rv_from_state_idxs(self, rv: RandomVariable, state_idxs: NDArray | Sequence[int]) -> NDArray:
202
+ """
203
+ Add a random variable to the dataset.
204
+
205
+ The dataset will directly reference the given `states` array.
206
+
207
+ Args:
208
+ rv: The random variable to add.
209
+ state_idxs: An 1D array of state indexes to add, with shape = `(len(self),)`.
210
+ Each element `state` should be `0 <= state < len(rv)`.
211
+
212
+ Returns:
213
+ A 1D array of random variable states, with shape = `(len(self), )`.
214
+
215
+ Raises:
216
+ ValueError: If the random variable is already in the dataset.
217
+ """
218
+ if rv in self._data.keys():
219
+ raise ValueError(f'data for {rv} already exists in the dataset')
220
+
221
+ if isinstance(state_idxs, np.ndarray):
222
+ expected_shape = (self._length,)
223
+ if state_idxs.shape == expected_shape:
224
+ rv_data = state_idxs
225
+ else:
226
+ raise ValueError(f'data for {rv} expected shape {expected_shape}, got {state_idxs.shape}')
227
+ else:
228
+ dtype: DTypeStates = dtype_for_number_of_states(len(rv))
229
+ if len(state_idxs) != self._length:
230
+ raise ValueError(f'data for {rv} expected length {self._length}, got {len(state_idxs)}')
231
+ rv_data = np.array(state_idxs, dtype=dtype)
232
+
233
+ self._data[rv] = rv_data
234
+ self._add_rv(rv)
235
+ return rv_data
236
+
237
+ def add_rv_from_states(self, rv: RandomVariable, states: Sequence[State]) -> NDArray:
238
+ """
239
+ Add a random variable to the dataset.
240
+
241
+ The dataset will allocate and populate a states array containing state indexes.
242
+ This will call `rv.state_idx(state)` for each state in `states`.
243
+
244
+ Args:
245
+ rv: The random variable to add.
246
+ states: An 1D array of state to add, with `len(states)` = `len(self)`.
247
+ Each element `state` should be in `rv.states`.
248
+
249
+ Returns:
250
+ A 1D array of random variable states, with shape = `(len(self), )`.
251
+
252
+ Raises:
253
+ ValueError: If the random variable is already in the dataset.
254
+ """
255
+ dtype: DTypeStates = dtype_for_number_of_states(len(rv))
256
+ rv_data = np.fromiter(
257
+ iter=(
258
+ rv.state_idx(state)
259
+ for state in states
260
+ ),
261
+ dtype=dtype,
262
+ count=len(states)
263
+ )
264
+ return self.add_rv_from_state_idxs(rv, rv_data)
265
+
266
+ def add_rv_from_state_weights(
267
+ self,
268
+ rv: RandomVariable,
269
+ state_weights: NDArray,
270
+ adjust_instance_weights: bool = True,
271
+ ) -> NDArray:
272
+ """
273
+ Add a random variable to the dataset.
274
+
275
+ The dataset will allocate and populate a states array containing state indexes.
276
+ For each instance, the state with the highest weight will be taken to be the
277
+ state of the random variable, with ties broken arbitrarily.
278
+
279
+ Args:
280
+ rv: The random variable to add.
281
+ state_weights: An 2D array of state weights, with shape = `(len(self), len(rv))`.
282
+ Each element `state` should be in `rv.states`.
283
+ adjust_instance_weights: If `True` (default), then the instance weights will be
284
+ adjusted according to sum of state weights for each instance. That is, if
285
+ the sum is not one for some instance, then the weight of that instance will
286
+ be adjusted.
287
+
288
+ Returns:
289
+ A 1D array of random variable states, with shape = `(len(self), )`.
290
+
291
+ Raises:
292
+ ValueError: If the random variable is already in the dataset.
293
+ """
294
+ expected_shape = (self._length, len(rv))
295
+ if state_weights.shape != expected_shape:
296
+ raise ValueError(f'data for {rv} expected shape {expected_shape}, got {state_weights.shape}')
297
+
298
+ dtype: DTypeStates = dtype_for_number_of_states(len(rv))
299
+ rv_data = np.fromiter(
300
+ iter=(
301
+ np.argmax(row)
302
+ for row in state_weights
303
+ ),
304
+ dtype=dtype,
305
+ count=self._length
306
+ )
307
+
308
+ if adjust_instance_weights:
309
+ row: NDArray
310
+ for i, row in enumerate(state_weights):
311
+ self._weights[i] *= row.sum()
312
+
313
+ return self.add_rv_from_state_idxs(rv, rv_data)
314
+
315
+ def dump(self, *, show_rvs: bool = True, show_weights: bool = True, as_states: bool = False) -> None:
316
+ """
317
+ Dump the dataset in a human-readable format.
318
+ If as_states is true, then instance states are dumped instead of just state indexes.
319
+
320
+ Args:
321
+ show_rvs: If `True`, the random variables are dumped.
322
+ show_weights: If `True`, the instance weights are dumped.
323
+ as_states: If `True`, the states are dumped Instead of just state indexes.
324
+ """
325
+ if show_rvs:
326
+ rvs = ', '.join(str(rv) for rv in self.rvs)
327
+ print(f'rvs: [{rvs}]')
328
+ print(f'instances ({len(self)}, with total weight {self.total_weight()}):')
329
+ cols = [self.states(rv) for rv in self.rvs]
330
+ for instance, weight in zip(zip(*cols), self.weights):
331
+ if as_states:
332
+ instance_str = ', '.join(repr(rv.states[idx]) for idx, rv in zip(instance, self.rvs))
333
+ else:
334
+ instance_str = ', '.join(str(idx) for idx in instance)
335
+ if show_weights:
336
+ print(f'({instance_str}) * {weight}')
337
+ else:
338
+ print(f'({instance_str})')
339
+
340
+
341
+ class SoftDataset(Dataset):
342
+ """
343
+ A soft dataset is a dataset where for each instance (row) and each random variable,
344
+ there is a distribution over the states of that random variable. That is,
345
+ for each instance, for each indicator, there is a weight. Additionally,
346
+ each instance has a weight.
347
+
348
+ Weights of random variable states are expected to be non-negative.
349
+ Notionally, the sum of weights for an instance and random variable is one.
350
+ """
351
+
352
+ @staticmethod
353
+ def from_hard_dataset(hard_dataset: HardDataset) -> SoftDataset:
354
+ """
355
+ Create a soft dataset from a hard dataset by repeated application
356
+ of `SoftDataset.add_rv_from_state_idxs`.
357
+
358
+ The instance weights of the returned dataset will be a copy
359
+ of the instance weights of the hard dataset.
360
+
361
+ Args:
362
+ hard_dataset: The hard dataset providing random variables,
363
+ their states, and instance weights.
364
+
365
+ Returns:
366
+ A `SoftDataset` instance.
367
+ """
368
+ dataset = SoftDataset(weights=hard_dataset.weights.copy())
369
+ for rv in hard_dataset.rvs:
370
+ dataset.add_rv_from_state_idxs(rv, hard_dataset.states(rv))
371
+ return dataset
372
+
373
+ def __init__(
374
+ self,
375
+ data: Iterable[Tuple[RandomVariable, NDArray]] = (),
376
+ weights: Optional[NDArray | Sequence] = None,
377
+ length: Optional[int] = None,
378
+ ):
379
+ """
380
+ Create a soft dataset.
381
+
382
+ Args:
383
+ data: optional iterable of (random variable, state weights), passed
384
+ to `self.add_rv_from_state_weights`.
385
+ weights: optional array of instance weights.
386
+ length: optional length of the dataset, if omitted, the length is inferred.
387
+ """
388
+ self._data: Dict[RandomVariable, NDArray] = {}
389
+
390
+ # Initialise super by either weights, length or first data item.
391
+ super_initialised: bool = False
392
+ if weights is not None or length is not None:
393
+ super().__init__(weights, length)
394
+ super_initialised = True
395
+
396
+ for rv, states_weights in data:
397
+ if not super_initialised:
398
+ super().__init__(weights, len(states_weights))
399
+ super_initialised = True
400
+ self.add_rv_from_state_weights(rv, states_weights)
401
+
402
+ if not super_initialised:
403
+ super().__init__(weights, 0)
404
+
405
+ def normalise(self, check_negative_instance: bool = True) -> None:
406
+ """
407
+ Adjust weights (for states and instances) so that the sum of state weights
408
+ for any random variable is 1 (or zero).
409
+
410
+ This performs an in-place modification.
411
+
412
+ If an instance weight is zero then all state weights for that instance will be zero.
413
+ If the state weights of an instance for any random variable sum to zero, then
414
+ that instance weight will be zero.
415
+
416
+ All other state weights of an instance for each random variable will sum to one.
417
+
418
+ Args:
419
+ check_negative_instance: if true (the default),then a RuntimeError is
420
+ raised if a negative instance weight is encountered.
421
+
422
+ Raises:
423
+ RuntimeError: if `check_negative_instance` is true and a negative
424
+ instance weight is encountered.
425
+ """
426
+ state_weights: NDArray
427
+ i: int
428
+
429
+ weights: NDArray = self.weights
430
+ for i in range(self._length):
431
+ for state_weights in self._data.values():
432
+ weight_sum = state_weights[i].sum()
433
+ if weight_sum == 0:
434
+ weights[i] = 0
435
+ elif weight_sum != 1:
436
+ state_weights[i] /= weight_sum
437
+ weights[i] *= weight_sum
438
+ instance_weight = weights[i]
439
+ if instance_weight == 0:
440
+ for state_weights in self._data.values():
441
+ state_weights[i, :] = 0
442
+ elif check_negative_instance and instance_weight < 0:
443
+ raise RuntimeError(f'negative instance weight: {i}')
444
+
445
+ def state_weights(self, rv: RandomVariable) -> NDArray:
446
+ """
447
+ Get the state weights for one random variable.
448
+ The first index into the returned array is the instance index.
449
+ The second index into the returned array is the state index.
450
+
451
+ Returns:
452
+ A 2D array of random variable states, with shape = `(len(self), len(rv))`.
453
+
454
+ Raises:
455
+ KeyError: If the random variable is not in the dataset.
456
+ """
457
+ return self._data[rv]
458
+
459
+ def add_rv(self, rv: RandomVariable) -> NDArray:
460
+ """
461
+ Add a random variable to the dataset, allocating and returning
462
+ the state indices for the random variable.
463
+
464
+ Args:
465
+ rv: The random variable to add.
466
+
467
+ Returns:
468
+ A 2D array of random variable states, with shape = `(len(self), len(rv))`,
469
+ initialised to zero.
470
+
471
+ Raises:
472
+ ValueError: If the random variable is already in the dataset.
473
+ """
474
+ rv_data = np.zeros((len(self), len(rv)), dtype=np.float64)
475
+ return self.add_rv_from_state_weights(rv, rv_data)
476
+
477
+ def remove_rv(self, rv: RandomVariable) -> None:
478
+ """
479
+ Remove a random variable from the dataset.
480
+
481
+ Args:
482
+ rv: The random variable to remove.
483
+
484
+ Raises:
485
+ KeyError: If the random variable is not in the dataset.
486
+ """
487
+ del self._data[rv]
488
+ self._remove_rv(rv)
489
+
490
+ def add_rv_from_state_weights(self, rv: RandomVariable, state_weights: NDArray) -> NDArray:
491
+ """
492
+ Add a random variable to the dataset.
493
+
494
+ The dataset will directly reference the given `states` array.
495
+
496
+ Args:
497
+ rv: The random variable to add.
498
+ state_weights: A 2D array of state weights, with shape = `(len(self), len(rv))`.
499
+
500
+ Raises:
501
+ ValueError: If the random variable is already in the dataset.
502
+ """
503
+ if rv in self._data.keys():
504
+ raise ValueError(f'data for {rv} already exists in the dataset')
505
+
506
+ expected_shape = (self._length, len(rv))
507
+ if state_weights.shape == expected_shape:
508
+ rv_data = state_weights
509
+ else:
510
+ raise ValueError(f'data for {rv} expected shape {expected_shape}, got {state_weights.shape}')
511
+
512
+ self._data[rv] = rv_data
513
+ self._add_rv(rv)
514
+ return rv_data
515
+
516
+ def add_rv_from_state_idxs(self, rv: RandomVariable, state_idxs: NDArray | Sequence[int]) -> NDArray:
517
+ """
518
+ Add a random variable to the dataset.
519
+
520
+ The dataset will directly reference the given `states` array.
521
+
522
+ Args:
523
+ rv: The random variable to add.
524
+ state_idxs: An 1D array of state indexes to add, with shape = `(len(self),)`.
525
+ Each element `state` should be `0 <= state < len(rv)`.
526
+
527
+ Raises:
528
+ ValueError: If the random variable is already in the dataset.
529
+ """
530
+ rv_data = np.zeros((len(state_idxs), len(rv)), dtype=np.float64)
531
+ for i, state_idx in enumerate(state_idxs):
532
+ rv_data[i, state_idx] = 1
533
+
534
+ return self.add_rv_from_state_weights(rv, rv_data)
535
+
536
+ def add_rv_from_states(self, rv: RandomVariable, states: Sequence[State]) -> NDArray:
537
+ """
538
+ Add a random variable to the dataset.
539
+
540
+ The dataset will allocate and populate a states array containing state indexes.
541
+ This will call `rv.state_idx(state)` for each state in `states`.
542
+
543
+ Args:
544
+ rv: The random variable to add.
545
+ states: An 1D array of state to add, with `len(states)` = `len(self)`.
546
+ Each element `state` should be in `rv.states`.
547
+
548
+ Raises:
549
+ ValueError: If the random variable is already in the dataset.
550
+ """
551
+ rv_data = np.zeros((len(states), len(rv)), dtype=np.float64)
552
+ for i, state in enumerate(states):
553
+ state_idx = rv.state_idx(state)
554
+ rv_data[i, state_idx] = 1
555
+
556
+ return self.add_rv_from_state_weights(rv, rv_data)
557
+
558
+ def dump(self, *, show_rvs: bool = True, show_weights: bool = True) -> None:
559
+ """
560
+ Dump the dataset in a human-readable format.
561
+ If as_states is true, then instance states are dumped instead of just state indexes.
562
+
563
+ Args:
564
+ show_rvs: If `True`, the random variables are dumped.
565
+ show_weights: If `True`, the instance weights are dumped.
566
+ """
567
+ if show_rvs:
568
+ rvs = ', '.join(str(rv) for rv in self.rvs)
569
+ print(f'rvs: [{rvs}]')
570
+ print(f'instances ({len(self)}, with total weight {self.total_weight()}):')
571
+ cols = [self.state_weights(rv) for rv in self.rvs]
572
+ for instance, weight in zip(zip(*cols), self.weights):
573
+ instance_str = ', '.join(str(state_weights) for state_weights in instance)
574
+ if show_weights:
575
+ print(f'({instance_str}) * {weight}')
576
+ else:
577
+ print(f'({instance_str})')