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
@@ -0,0 +1,627 @@
1
+ import math
2
+ from abc import ABC, abstractmethod
3
+ from itertools import chain
4
+ from typing import Sequence, Tuple, Iterable, Callable, TypeAlias
5
+
6
+ import numpy as np
7
+
8
+ from ck.pgm import Indicator, RandomVariable, rv_instances_as_indicators, number_of_states, rv_instances, Instance
9
+ from ck.utils.iter_extras import combos as _combos
10
+ from ck.utils.map_set import MapSet
11
+ from ck.utils.np_extras import dtype_for_number_of_states, NDArrayFloat64, DTypeStates, NDArrayNumeric
12
+
13
+ Condition: TypeAlias = None | Indicator | Iterable[Indicator]
14
+ """
15
+ Type defining a condition. A condition is logically a set of
16
+ indicators, each indicator representing a random variable being in some state.
17
+
18
+ If multiple indicators of the same random variable appear in
19
+ a condition, then they are interpreted as
20
+ a disjunction, otherwise indicators are interpreted as
21
+ a conjunction. E.g., the condition (X=0, Y=1, Y=3) means
22
+ X=0 and (Y=1 or Y=3).
23
+ """
24
+
25
+ _NAN: float = np.nan # Not-a-number (i.e., the result of an invalid calculation).
26
+
27
+
28
+ class ProbabilitySpace(ABC):
29
+ """
30
+ An abstract mixin class for a class providing probabilities over a state space defined by random variables.
31
+ Each possible world of the state space is referred to as an 'instance'.
32
+ """
33
+ __slots__ = ()
34
+
35
+ @property
36
+ @abstractmethod
37
+ def rvs(self) -> Sequence[RandomVariable]:
38
+ """
39
+ Return the random variables that define the state space.
40
+ Each random variable, rv, has a length len(rv) which
41
+ is the number of states, and rv[i] is the 'indicator' for
42
+ the ith state of the random variable. Indicators must
43
+ be unique across all rvs as rv[i] indicates the
44
+ condition 'rv == i'.
45
+ """
46
+
47
+ @abstractmethod
48
+ def wmc(self, *condition: Condition) -> float:
49
+ """
50
+ Return the weight of instances matching the given condition.
51
+
52
+ Args:
53
+ condition: a condition restricting the instances that are
54
+ considered in the result.
55
+ """
56
+
57
+ @property
58
+ @abstractmethod
59
+ def z(self) -> float:
60
+ """
61
+ Return the summed weight of all instances.
62
+ This is equivalent to self.wmc(), with no arguments.
63
+ This is also known as the "partition function".
64
+ """
65
+
66
+ def probability(self, *indicators: Indicator, condition: Condition = ()) -> float:
67
+ """
68
+ Return the joint probability of the given indicators,
69
+ conditioned on any conditions, and
70
+ marginalised over any unmentioned random variables.
71
+
72
+ If multiple indicators of the same random variable appear in
73
+ the parameters 'indicators' or 'condition' then they are interpreted as
74
+ a disjunction, otherwise indicators are interpreted as
75
+ a conjunction. E.g.: X=0, Y=1, Y=3 means X=0 and (Y=1 or Y=3).
76
+
77
+ Args:
78
+ indicators: Indicators that specify which set of instances to compute probability.
79
+ condition: Indicators that specify conditions for a conditional probability.
80
+ Returns:
81
+ the probability of the given indicators, conditioned on the given conditions.
82
+ """
83
+ condition: Tuple[Indicator, ...] = check_condition(condition)
84
+
85
+ if len(condition) == 0:
86
+ z = self.z
87
+ if z <= 0:
88
+ return _NAN
89
+ else:
90
+ z = self.wmc(*condition)
91
+ if z <= 0:
92
+ return _NAN
93
+
94
+ # Combine the indicators with the condition
95
+ # If a variable is mentioned in both the indicators and condition, then
96
+ # we need to take the intersection, and check for contradictions.
97
+ # If a variable is mentioned in the condition but not indicators, then
98
+ # the rv condition needs to be added to the indicators.
99
+ indicator_groups: MapSet[int, Indicator] = _group_indicators(indicators)
100
+ condition_groups: MapSet[int, Indicator] = _group_indicators(condition)
101
+
102
+ for rv_idx, indicators in condition_groups.items():
103
+ indicator_group = indicator_groups.get(rv_idx)
104
+ if indicator_group is None:
105
+ indicator_groups.add_all(rv_idx, indicators)
106
+ else:
107
+ indicator_group.intersection_update(indicators)
108
+ if len(indicator_group) == 0:
109
+ # A contradiction between the indicators and conditions
110
+ return 0.0
111
+
112
+ # Collect all the indicators from the updated indicator_groups
113
+ indicators = chain(*indicator_groups.values())
114
+
115
+ return self.wmc(*indicators) / z
116
+
117
+ def marginal_distribution(self, *rvs: RandomVariable, condition: Condition = ()) -> NDArrayNumeric:
118
+ """
119
+ What is the marginal probability distribution over the states of the given random variables.
120
+ Assumes that no indicators of rv in rvs appear in the conditions (if supplied).
121
+
122
+ When multiple rvs are supplied, the order of instantiations is as per
123
+ `rv_instances_as_indicators(*rvs)`.
124
+
125
+ If multiple indicators of the same random variable appear in
126
+ the parameter 'condition' then they are interpreted as
127
+ a disjunction, otherwise indicators are interpreted as
128
+ a conjunction. E.g.: X=0, Y=1, Y=3 means X=0 and (Y=1 or Y=3).
129
+
130
+ This is not an efficient implementation as it will call self.probability(...)
131
+ for each possible state of the given random variable. If efficient marginal
132
+ probability calculations are required, consider using a different method.
133
+
134
+ Warning:
135
+ If the probability of each state of rv (given the condition) is
136
+ zero, then the marginal distribution is il-defined and the returned probabilities will
137
+ all be NAN.
138
+
139
+ Args:
140
+ rvs: Random variables to compute the marginal distribution over.
141
+ condition: Indicators that specify conditions for conditional probability.
142
+
143
+ Returns:
144
+ marginal probability distribution as an array co-indexed with `rv_instances_as_indicators(*rvs)`.
145
+ """
146
+ condition = check_condition(condition)
147
+
148
+ # We have to be careful of the situation where indicators of rvs appear in condition.
149
+ # If an RV has at least 1 indicator in condition then it must match it to have non-zero probability.
150
+ wmc = self._get_wmc_for_marginals(rvs, condition)
151
+
152
+ result: NDArrayFloat64 = np.fromiter(
153
+ (wmc(indicators) for indicators in rv_instances_as_indicators(*rvs)),
154
+ count=number_of_states(*rvs),
155
+ dtype=np.float64
156
+ )
157
+ _normalise_marginal(result)
158
+ return result
159
+
160
+ def map(self, *rvs: RandomVariable, condition: Condition = ()) -> Tuple[float, Instance]:
161
+ """
162
+ Determine the maximum apriori probability (MAP).
163
+
164
+ If there are tied solutions, one solution is returned, which
165
+ is selected arbitrarily.
166
+
167
+ If multiple indicators of the same random variable appear in
168
+ the parameter 'condition' then they are interpreted as
169
+ a disjunction, otherwise indicators are interpreted as
170
+ a conjunction. E.g.: X=0, Y=1, Y=3 means X=0 and (Y=1 or Y=3)
171
+
172
+ Warning:
173
+ This is not an efficient implementation as it will call `self.wmc`
174
+ for each possible state of the given random variables. If efficient MAP
175
+ probability calculations are required, consider using a different method.
176
+
177
+ Args:
178
+ rvs: random variables to find the MAP over.
179
+ condition: any conditioning indicators.
180
+
181
+ Returns:
182
+ (probability, instance) where
183
+ probability: is the MAP probability
184
+ instance: is the MAP state (co-indexed with the given rvs).
185
+ """
186
+ condition: Sequence[Indicator] = check_condition(condition)
187
+
188
+ rv_indexes = set(rv.idx for rv in rvs)
189
+ assert len(rv_indexes) == len(rvs), 'duplicated random variables not allowed'
190
+
191
+ # Group conditioning indicators by random variable.
192
+ conditions_by_rvs = _group_states(condition)
193
+
194
+ # See if any MAP random variable is also conditioned.
195
+ # Reduce the state space of any conditioned MAP rv.
196
+ loop_rvs = []
197
+ reduced_space = False
198
+ for rv in rvs:
199
+ states = conditions_by_rvs.get(rv.idx)
200
+ if states is None:
201
+ loop_rvs.append(rv)
202
+ else:
203
+ loop_rvs.append([rv[i] for i in sorted(states)])
204
+ reduced_space = True
205
+
206
+ # If the random variables we are looping over does not have any conditions
207
+ # then it is expected to be faster by using computed marginal probabilities.
208
+ if not reduced_space:
209
+ prs = self.marginal_distribution(*rvs, condition=condition)
210
+ best_probability = float('-inf')
211
+ best_states = None
212
+ for probability, inst in zip(prs, rv_instances(*rvs)):
213
+ if probability > best_probability:
214
+ best_probability = probability
215
+ best_states = inst
216
+ return best_probability, best_states
217
+
218
+ else:
219
+ # Remove any condition indicators with rv in rvs.
220
+ new_conditions = tuple(ind for ind in condition if ind.rv_idx not in rv_indexes)
221
+
222
+ # Loop over the state space of the 'loop' rvs
223
+ best_probability = float('-inf')
224
+ best_states = None
225
+ indicators: Tuple[Indicator, ...]
226
+ for indicators in _combos(loop_rvs):
227
+ probability = self.wmc(*(indicators + new_conditions))
228
+ if probability > best_probability:
229
+ best_probability = probability
230
+ best_states = tuple(ind.state_idx for ind in indicators)
231
+ condition_probability = self.wmc(*condition)
232
+ return best_probability / condition_probability, best_states
233
+
234
+ def correlation(self, indicator1: Indicator, indicator2: Indicator, condition: Condition = ()) -> float:
235
+ """
236
+ What is the correlation between the two given indicators, r(indicator1, indicator2).
237
+
238
+ Args:
239
+ indicator1: a first random variable and its state.
240
+ indicator2: a second random variable and its state.
241
+ condition: any conditioning indicators.
242
+
243
+ Returns:
244
+ correlation between the two given indicators.
245
+ """
246
+ condition = check_condition(condition)
247
+
248
+ p1 = self.probability(indicator1, condition=condition)
249
+ p2 = self.probability(indicator2, condition=condition)
250
+ p12 = self._joint_probability(indicator1, indicator2, condition=condition)
251
+ d = p1 * (1.0 - p1) * p2 * (1.0 - p2)
252
+ if d == 0.0:
253
+ # As any marginal probability approaches zero, correlation approaches zero
254
+ return 0.0
255
+ else:
256
+ return (p12 - p1 * p2) / math.sqrt(d)
257
+
258
+ def entropy(self, rv: RandomVariable, condition: Condition = ()) -> float:
259
+ """
260
+ Calculate the entropy of the given random variable, H(rv).
261
+
262
+ Args:
263
+ rv: random variable to calculate the entropy for.
264
+ condition: any conditioning indicators.
265
+
266
+ Returns:
267
+ entropy of the given random variable.
268
+ """
269
+ condition = check_condition(condition)
270
+ e = 0.0
271
+ for ind in rv:
272
+ p = self.probability(ind, condition=condition)
273
+ if p > 0.0:
274
+ e -= p * math.log2(p)
275
+ return e
276
+
277
+ def conditional_entropy(self, rv1: RandomVariable, rv2: RandomVariable, condition: Condition = ()) -> float:
278
+ """
279
+ Calculate the conditional entropy, H(rv1 | rv2).
280
+
281
+ Args:
282
+ rv1: random variable to calculate the entropy for.
283
+ rv2: the conditioning random variable for entropy calculation.
284
+ condition: any conditioning indicators to restrict the state space.
285
+
286
+ Returns:
287
+ entropy of rv1, conditioned on rv2.
288
+ """
289
+ condition = check_condition(condition)
290
+ e = 0.0
291
+ for ind1 in rv1:
292
+ for ind2 in rv2:
293
+ p = self._joint_probability(ind1, ind2, condition=condition)
294
+ if p > 0.0:
295
+ # if p > 0 then p2 > 0, as p <= p2
296
+ p2 = self.probability(ind2, condition=condition)
297
+ e -= p * math.log2(p / p2)
298
+ return e
299
+
300
+ def joint_entropy(self, rv1: RandomVariable, rv2: RandomVariable, condition: Condition = ()) -> float:
301
+ """
302
+ Calculate the joint entropy of the two random variables, H(rv1; rv2).
303
+
304
+ Args:
305
+ rv1: a first random variable to calculate joint entropy.
306
+ rv2: a second random variable to calculate joint entropy.
307
+ condition: any conditioning indicators to restrict the state space.
308
+ Returns:
309
+ joint entropy of the given random variables.
310
+ """
311
+ condition = check_condition(condition)
312
+ e = 0.0
313
+ for ind1 in rv1:
314
+ for ind2 in rv2:
315
+ p = self._joint_probability(ind1, ind2, condition=condition)
316
+ if p > 0.0:
317
+ e -= p * math.log2(p)
318
+ return e
319
+
320
+ def mutual_information(self, rv1: RandomVariable, rv2: RandomVariable, condition: Condition = ()) -> float:
321
+ """
322
+ Calculate the mutual information between two random variables, I(rv1; rv2).
323
+
324
+ Args:
325
+ rv1: a first random variable
326
+ rv2: a second random variable
327
+ condition: indicators to specify a condition restricting the state space.
328
+ Returns:
329
+ mutual_information(rv1, rv2) / denominator
330
+ """
331
+ condition = check_condition(condition)
332
+ p1s = self.marginal_distribution(rv1, condition=condition)
333
+ p2s = self.marginal_distribution(rv2, condition=condition)
334
+ info = 0.0
335
+ for ind1, p1 in zip(rv1, p1s):
336
+ for ind2, p2 in zip(rv2, p2s):
337
+ p12 = self._joint_probability(ind1, ind2, condition=condition)
338
+ if p12 > 0.0:
339
+ info += p12 * math.log2(p12 / p1 / p2)
340
+ return info
341
+
342
+ def total_correlation(self, rv1: RandomVariable, rv2: RandomVariable, condition: Condition = ()) -> float:
343
+ """
344
+ Calculate the 'total correlation' measure.
345
+ total_correlation = I(rv1; rv2) / min(H(rv1), H(rv2)).
346
+ This is a normalised mutual information between two random variables.
347
+ 0 => no mutual information.
348
+ 1 => perfect mutual information.
349
+
350
+ Args:
351
+ rv1: a first random variable
352
+ rv2: a second random variable
353
+ condition: indicators to specify a condition restricting the state space.
354
+ Returns:
355
+ total correlation between the given random variables.
356
+ """
357
+ condition = check_condition(condition)
358
+ denominator = min(self.entropy(rv1), self.entropy(rv2, condition=condition))
359
+ return self._normalised_mutual_information(rv1, rv2, denominator, condition=condition)
360
+
361
+ def uncertainty(self, rv1: RandomVariable, rv2: RandomVariable, condition: Condition = ()) -> float:
362
+ """
363
+ Calculate the 'uncertainty' measure, C, between two random variables
364
+ C(rv1, rv2) = I(rv1; rv2) / H(rv2)
365
+ This is a normalised mutual information between two random variables.
366
+ Note that it is not a symmetric measure; in general C(rv1, rv2) does not equal C(rv2, rv1).
367
+ 0 => no mutual information.
368
+ 1 => perfect mutual information.
369
+
370
+ Args:
371
+ rv1: a first random variable
372
+ rv2: a second random variable
373
+ condition: indicators to specify a condition restricting the state space.
374
+ Returns:
375
+ uncertainty between the given random variables.
376
+ """
377
+ condition = check_condition(condition)
378
+ denominator = self.entropy(rv2, condition=condition)
379
+ return self._normalised_mutual_information(rv1, rv2, denominator, condition=condition)
380
+
381
+ def symmetric_uncertainty(self, rv1: RandomVariable, rv2: RandomVariable, condition: Condition = ()) -> float:
382
+ """
383
+ Calculate the 'symmetric uncertainty' measure.
384
+ symmetric_uncertainty = 2 * I(rv1, rv2) / (H(rv1) + H(rv2)).
385
+ This is the harmonic mean of the two uncertainty coefficients,
386
+ C(rv1, rv2) = I(rv1; rv2) / H(rv2) and C(rv2, rv1) = I(rv1; rv2) / H(rv1).
387
+ This is a normalised mutual information between two random variables.
388
+ 0 => no mutual information.
389
+ 1 => perfect mutual information.
390
+
391
+ Args:
392
+ rv1: a first random variable
393
+ rv2: a second random variable
394
+ condition: indicators to specify a condition restricting the state space.
395
+ Returns:
396
+ symmetric uncertainty between the given random variables.
397
+ """
398
+ condition = check_condition(condition)
399
+ denominator = self.entropy(rv1) + self.entropy(rv2, condition=condition)
400
+ return 2.0 * self._normalised_mutual_information(rv1, rv2, denominator, condition=condition)
401
+
402
+ def iqr(self, rv1: RandomVariable, rv2: RandomVariable, condition: Condition = ()) -> float:
403
+ """
404
+ Calculate the Information Quality Ratio (IQR).
405
+ IQR = I(rv1; rv2) / H(rv1; rv2).
406
+ Also known as 'dual total correlation'.
407
+ This is a normalised mutual information between two random variables.
408
+ 0 => no mutual information.
409
+ 1 => perfect mutual information.
410
+
411
+ Args:
412
+ rv1: a first random variable
413
+ rv2: a second random variable
414
+ condition: indicators to specify a condition restricting the state space.
415
+ Returns:
416
+ Information Quality Ratio between the given random variables.
417
+ """
418
+ condition = check_condition(condition)
419
+ denominator = self.joint_entropy(rv1, rv2, condition=condition)
420
+ return self._normalised_mutual_information(rv1, rv2, denominator, condition=condition)
421
+
422
+ def covariant_normalised_mutual_information(self, rv1: RandomVariable, rv2: RandomVariable,
423
+ condition: Condition = ()) -> float:
424
+ """
425
+ Calculate the covariant normalised mutual information
426
+ = I(rv1; rv2) / sqrt(H(rv1) * H(rv2)).
427
+ This is a normalised mutual information between two random variables.
428
+ 0 => no mutual information.
429
+ 1 => perfect mutual information.
430
+
431
+ Args:
432
+ rv1: a first random variable
433
+ rv2: a second random variable
434
+ condition: indicators to specify a condition restricting the state space.
435
+ Returns:
436
+ covariant normalised mutual information between the given random variables.
437
+ """
438
+ condition = check_condition(condition)
439
+ denominator = math.sqrt(self.entropy(rv1, condition=condition) * self.entropy(rv2, condition=condition))
440
+ return self._normalised_mutual_information(rv1, rv2, denominator, condition=condition)
441
+
442
+ def _normalised_mutual_information(
443
+ self,
444
+ rv1: RandomVariable,
445
+ rv2: RandomVariable,
446
+ denominator: float,
447
+ condition: Tuple[Indicator, ...],
448
+ ) -> float:
449
+ """
450
+ Helper function for normalised mutual information calculations.
451
+
452
+ Args:
453
+ rv1: a first random variable
454
+ rv2: a second random variable
455
+ denominator: the normalisation factor
456
+ condition: indicators to specify a condition restricting the state space.
457
+ Returns:
458
+ mutual_information(rv1, rv2) / denominator
459
+ """
460
+ if denominator == 0.0:
461
+ return 0.0
462
+ else:
463
+ return self.mutual_information(rv1, rv2, condition) / denominator
464
+
465
+ def _joint_probability(
466
+ self,
467
+ indicator1: Indicator,
468
+ indicator2: Indicator,
469
+ condition: Tuple[Indicator, ...],
470
+ ) -> float:
471
+ """
472
+ Helper function to correctly calculate a joint probability even if the two indicators
473
+ are from the same random variable.
474
+
475
+ If the indicators are from the different random variables then
476
+ probability(indicator1 and indicator2 | condition).
477
+
478
+ If the indicators are from the same random variable then
479
+ probability(indicator1 or indicator2 | condition).
480
+
481
+ Args:
482
+ indicator1: a first Indicator.
483
+ indicator2: a second Indicator
484
+ condition: indicators to specify a condition restricting the state space.
485
+ Returns:
486
+ joint probability of the two indicators, given the condition.
487
+ """
488
+ if indicator1 == indicator2:
489
+ # Ensure correct behaviour, same random variable and same states
490
+ return self.probability(indicator1, condition=condition)
491
+ elif indicator1.rv_idx == indicator2.rv_idx:
492
+ # Efficiency shortcut, same random variable but different states
493
+ return 0.0
494
+ else:
495
+ # General case, two different random variables
496
+ return self.probability(indicator1, indicator2, condition=condition)
497
+
498
+ def _get_wmc_for_marginals(
499
+ self,
500
+ rvs: Sequence[RandomVariable],
501
+ condition: Tuple[Indicator, ...],
502
+ ) -> Callable[[Sequence[Indicator]], float]:
503
+ """
504
+ Return a wmc function that is suitable for calculating marginal distributions.
505
+
506
+ This implementation is careful of the situation where indicators of rvs appear in condition.
507
+ If an RV has at least 1 indicator in condition then it must match it to have non-zero probability.
508
+
509
+ Args:
510
+ rvs: random variables to calculate marginal distributions for.
511
+ condition: indicators to specify a condition restricting the state space.
512
+ Returns:
513
+ A function from a condition, specified as a sequence of indicators, to a weighted model count.
514
+ """
515
+ if len(condition) > 0:
516
+ check_sets = []
517
+ overlap_detected = False
518
+ cond_set = set(condition)
519
+ for rv in rvs:
520
+ in_condition = set()
521
+ for ind in rv:
522
+ if ind in cond_set:
523
+ in_condition.add(ind)
524
+ cond_set.discard(ind)
525
+ overlap_detected = True
526
+ if len(in_condition) == 0:
527
+ in_condition.update(rv)
528
+ check_sets.append(in_condition)
529
+
530
+ if overlap_detected:
531
+ __wmc__condition = tuple(cond_set)
532
+
533
+ def wmc(indicators: Sequence[Indicator]) -> float:
534
+ for indicator, check_set in zip(indicators, check_sets):
535
+ if indicator not in check_set:
536
+ return 0.0
537
+ full_condition = tuple(indicators) + __wmc__condition
538
+ return self.wmc(*full_condition)
539
+ else:
540
+ __wmc__condition = tuple(condition)
541
+
542
+ def wmc(indicators: Sequence[Indicator]) -> float:
543
+ full_condition = tuple(indicators) + __wmc__condition
544
+ return self.wmc(*full_condition)
545
+ else:
546
+ def wmc(indicators: Sequence[Indicator]) -> float:
547
+ return self.wmc(*indicators)
548
+
549
+ return wmc
550
+
551
+
552
+ def check_condition(condition: Condition) -> Tuple[Indicator, ...]:
553
+ """
554
+ Make the best effort to interpret the given condition.
555
+
556
+ Args:
557
+ condition: a relaxed specification of a condition.
558
+ Returns:
559
+ a formal specification of the condition as a tuple of indicators with no duplicates.
560
+ """
561
+ if condition is None:
562
+ return ()
563
+ elif isinstance(condition, Indicator):
564
+ return (condition,)
565
+ else:
566
+ return tuple(set(condition))
567
+
568
+
569
+ def dtype_for_state_indexes(rvs: Iterable[RandomVariable]) -> DTypeStates:
570
+ """
571
+ Infer a numpy dtype to hold any state index from any given random variable.
572
+
573
+ Args:
574
+ rvs: some random variables.
575
+ Returns:
576
+ a numpy dtype.
577
+ """
578
+ return dtype_for_number_of_states(max((len(rv) for rv in rvs), default=0))
579
+
580
+
581
+ def _group_indicators(indicators: Iterable[Indicator]) -> MapSet[int, Indicator]:
582
+ """
583
+ Group the given indicators by rv_idx.
584
+
585
+ Args:
586
+ indicators: the indicators to group.
587
+
588
+ Returns:
589
+ A mapping from rv_idx to set of indicators.
590
+ """
591
+ groups: MapSet[int, Indicator] = MapSet()
592
+ for indicator in indicators:
593
+ groups.add(indicator.rv_idx, indicator)
594
+ return groups
595
+
596
+
597
+ def _group_states(indicators: Iterable[Indicator]) -> MapSet[int, int]:
598
+ """
599
+ Group the given indicator states by rv_idx.
600
+
601
+ Args:
602
+ indicators: the indicators to group.
603
+
604
+ Returns:
605
+ A mapping from rv_idx to set of state indexes.
606
+ """
607
+ groups: MapSet[int, int] = MapSet()
608
+ for indicator in indicators:
609
+ groups.add(indicator.rv_idx, indicator.state_idx)
610
+ return groups
611
+
612
+
613
+ def _normalise_marginal(distribution: NDArrayFloat64) -> None:
614
+ """
615
+ Update the values in the given distribution to
616
+ properly represent a marginal distribution.
617
+
618
+ The update is made in-place.
619
+
620
+ Args:
621
+ a 1D numpy array of likelihoods.
622
+ """
623
+ total = np.sum(distribution)
624
+ if total <= 0:
625
+ distribution[:] = _NAN
626
+ elif total != 1:
627
+ distribution /= total
ck/program/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .raw_program import RawProgram
2
+ from .program_buffer import ProgramBuffer
3
+ from .program import Program