compiled-knowledge 4.0.0a24__cp312-cp312-macosx_11_0_arm64.whl → 4.1.0a1__cp312-cp312-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of compiled-knowledge might be problematic. Click here for more details.
- ck/circuit/_circuit_cy.c +1 -1
- ck/circuit/_circuit_cy.cpython-312-darwin.so +0 -0
- ck/circuit/tmp_const.py +5 -4
- ck/circuit_compiler/cython_vm_compiler/_compiler.c +152 -152
- ck/circuit_compiler/cython_vm_compiler/_compiler.cpython-312-darwin.so +0 -0
- ck/circuit_compiler/interpret_compiler.py +2 -2
- ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.c +1 -1
- ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.cpython-312-darwin.so +0 -0
- ck/circuit_compiler/support/llvm_ir_function.py +4 -4
- ck/dataset/__init__.py +1 -0
- ck/dataset/cross_table.py +270 -0
- ck/dataset/cross_table_probabilities.py +53 -0
- ck/dataset/dataset.py +577 -0
- ck/dataset/dataset_compute.py +140 -0
- ck/dataset/dataset_from_crosstable.py +45 -0
- ck/dataset/dataset_from_csv.py +147 -0
- ck/dataset/sampled_dataset.py +96 -0
- ck/example/diamond_square.py +3 -1
- ck/example/triangle_square.py +3 -1
- ck/example/truss.py +3 -1
- ck/in_out/parse_net.py +21 -19
- ck/in_out/parser_utils.py +7 -3
- ck/learning/__init__.py +0 -0
- ck/learning/train_generative.py +149 -0
- ck/pgm.py +95 -84
- ck/pgm_circuit/mpe_program.py +3 -4
- ck/pgm_circuit/pgm_circuit.py +27 -18
- ck/pgm_circuit/program_with_slotmap.py +27 -46
- ck/pgm_circuit/support/compile_circuit.py +2 -4
- ck/pgm_compiler/support/circuit_table/_circuit_table_cy.c +1 -1
- ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cpython-312-darwin.so +0 -0
- ck/probability/empirical_probability_space.py +1 -0
- ck/probability/probability_space.py +10 -11
- ck/program/raw_program.py +23 -16
- ck/sampling/sampler_support.py +5 -6
- ck/utils/iter_extras.py +3 -2
- ck/utils/local_config.py +16 -8
- {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/METADATA +1 -1
- {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/RECORD +42 -32
- {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/WHEEL +0 -0
- {compiled_knowledge-4.0.0a24.dist-info → compiled_knowledge-4.1.0a1.dist-info}/licenses/LICENSE.txt +0 -0
- {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})')
|