regfans 0.0.1__py3-none-any.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.
regfans/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # regfans/__init__.py
2
+ from .vectorconfig import VectorConfiguration
3
+
4
+ __all__ = ["VectorConfiguration"]
regfans/circuits.py ADDED
@@ -0,0 +1,371 @@
1
+ # =============================================================================
2
+ # Copyright (C) 2025 Nate MacFadden for the Liam McAllister Group
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ # =============================================================================
17
+ #
18
+ # -----------------------------------------------------------------------------
19
+ # Description: This module contains a class containing information about
20
+ # circuits
21
+ # -----------------------------------------------------------------------------
22
+
23
+ # external imports
24
+ from collections.abc import Iterable
25
+ from typing import Union
26
+
27
+ class Circuit():
28
+ """
29
+ This class is a helper data structure to contain a single circuit of some
30
+ vector configuration.
31
+
32
+ **Description:**
33
+ Constructs a `Circuit` object describing a circuit of a vector
34
+ configuration. This is handled by the hidden [`__init__`](#__init__)
35
+ function.
36
+
37
+ **Arguments:**
38
+ - `vc`: The ambient vector configuration.
39
+ - `Z`: The support of the circuit.
40
+ - `Zpos`: The 'positive' side of the circuit.
41
+ - `Zpos`: The 'negative' side of the circuit.
42
+ - `lmbda`: A dependency vector demonstrating the circuit.
43
+ - `signature`: The signature (|Zpos|, |Zneg|) of the circuit.
44
+
45
+ **Returns:**
46
+ Nothing.
47
+ """
48
+ def __init__(self, vc, Z, Zpos, Zneg, lmbda, signature):
49
+ """
50
+ **Description:**
51
+ Initializes a `Circuit` object.
52
+
53
+ **Arguments:**
54
+ - `vc`: The ambient vector configuration.
55
+ - `Z`: The support of the circuit.
56
+ - `Zpos`: The 'positive' side of the circuit.
57
+ - `Zpos`: The 'negative' side of the circuit.
58
+ - `lmbda`: A dependency vector demonstrating the circuit.
59
+ - `signature`: The signature (|Zpos|, |Zneg|) of the circuit.
60
+
61
+ **Returns:**
62
+ Nothing.
63
+ """
64
+ self.vc = vc
65
+ self.Z = Z
66
+ self.Zpos = Zpos
67
+ self.Zneg = Zneg
68
+ self.lmbda = lmbda
69
+ self.signature = tuple(signature)
70
+
71
+ # positive/negative triangulations
72
+ self.Tpos = []
73
+ self.Tneg = []
74
+
75
+ self.normal = [0] * self.vc.size
76
+ for i,j in enumerate([self.vc.label_to_ind(z) for z in self.Z]):
77
+ self.normal[j] = self.lmbda[i]
78
+ self.normal = tuple(self.normal)
79
+
80
+ def __repr__(self):
81
+ out = f"A circuit with (Z+,Z-)= ({self.Zpos}, {self.Zneg})"
82
+ out += f"; lambda = {self.lmbda}"
83
+ return out
84
+
85
+ @property
86
+ def data(self):
87
+ # spoof dictionaries
88
+ return {
89
+ 'Z':self.Z,
90
+ 'Z+':self.Zpos,
91
+ 'Z-':self.Zneg,
92
+ 'lambda':self.lmbda,
93
+ 'type':self.signature,
94
+ }
95
+
96
+ def reorient(self):
97
+ # allow swapping Zpos <-> Zneg
98
+ reoriented = Circuit(
99
+ self.vc,
100
+ self.Z,
101
+ self.Zneg,
102
+ self.Zpos,
103
+ tuple([-x for x in self.lmbda]),
104
+ (self.signature[1], self.signature[0])
105
+ )
106
+ return reoriented
107
+
108
+ class Circuits():
109
+ """
110
+ This class is a helper data structure to contain the circuits of some
111
+ vector configuration.
112
+
113
+ **Description:**
114
+ Constructs a `Circuits` object describing all circuits of some VC. This
115
+ is handled by the hidden [`__init__`](#__init__) function.
116
+
117
+ **Arguments:**
118
+ None.
119
+
120
+ **Returns:**
121
+ Nothing.
122
+ """
123
+ def __init__(self):
124
+ """
125
+ **Description:**
126
+ Initializes a `Circuits` object.
127
+
128
+ **Arguments:**
129
+ None.
130
+
131
+ **Returns:**
132
+ Nothing.
133
+ """
134
+ self.clear_cache() # set attributes here
135
+
136
+ # clear cache
137
+ # -----------
138
+ def clear_cache(self):
139
+ # main data type - map from the (encoded) unsigned circuit to a Circuit
140
+ # object
141
+ # **Don't directly access... use getters/setters instead...***
142
+ self.circuits = dict()
143
+
144
+ # map from cone to the circuits it is involved in
145
+ self.cone_to_circuit = dict()
146
+
147
+ # non-dependencies
148
+ self.non_dependencies = set()
149
+
150
+ # whether we have computed/saved all circuits
151
+ self.know_all_circuits = False
152
+
153
+ # default methods
154
+ # ---------------
155
+ def __repr__(self):
156
+ # hijack `dict` and `Circuit`
157
+ return self.circuits.__repr__()
158
+
159
+ def __str__(self):
160
+ # hijack `dict` and `Circuit`
161
+ return self.circuits.__str__()
162
+
163
+ def __len__(self):
164
+ # hijack `dict`
165
+ return len(self.circuits)
166
+
167
+ def __contains__(self, label_inds: Iterable[int]):
168
+ encoding = self.encode(label_inds)
169
+ return encoding in self.circuits
170
+
171
+ def __getitem__(self, label_inds: Iterable[int]) -> Union["Circuit", int]:
172
+ """
173
+ **Description:**
174
+ Get the circuit corresponding to the indicated indices.
175
+
176
+ **Arguments:**
177
+ - `label_inds`: The iterable of vector/label indices.
178
+
179
+ **Returns:**
180
+ Cases
181
+ - if indices correspond to known circuit -> the `Circuit`
182
+ - if indices correspond to non-circuit -> -1
183
+ - if indices aren't known -> 0
184
+ """
185
+ encoding = self.encode(label_inds)
186
+
187
+ # check if these labels are known to contain a circuit
188
+ if encoding in self.circuits:
189
+ return self.circuits[encoding]
190
+
191
+ # if we know all circuits, then indices cannot correspond to a circuit
192
+ if self.know_all_circuits:
193
+ return -1
194
+
195
+ # if we don't know all circuits, we check against known non-circuits
196
+ for non_dependency in self.non_dependencies:
197
+ if self.is_subset(encoding, non_dependency):
198
+ return -1
199
+
200
+ # unclear if this is a circuit
201
+ return 0
202
+
203
+ def set_circuit(self,
204
+ circuit: "Circuit",
205
+ verbosity: int = 0) -> None:
206
+ """
207
+ **Description:**
208
+ Set the circuit properties corresponding to the indicated indices.
209
+
210
+ **Arguments:**
211
+ - `circuit`: Dict describing the circuit.
212
+ - `verbosity`: The verbosity level.
213
+
214
+ **Returns:**
215
+ Nothing.
216
+ """
217
+ encoding = self.encode(circuit.Z)
218
+
219
+ # setting a circuit
220
+ self.circuits[encoding] = circuit
221
+
222
+ # keep a map from cones to the circuits they have
223
+ for c in circuit.Tpos:
224
+ self.cone_to_circuit[c] = self.cone_to_circuit.get(c,set())
225
+ self.cone_to_circuit[c].add(encoding)
226
+
227
+ def set_non_dependency(self,
228
+ label_inds: Iterable[int],
229
+ verbosity: int = 0) -> None:
230
+ """
231
+ **Description:**
232
+ Record a set of points that is not dependent
233
+
234
+ **Arguments:**
235
+ - `label_inds`: The iterable of vector/label indices.
236
+ - `verbosity`: The verbosity level.
237
+
238
+ **Returns:**
239
+ Nothing.
240
+ """
241
+ encoding = self.encode(label_inds)
242
+
243
+ new_non_dependencies= set()
244
+ for non_dependency in self.non_dependencies:
245
+ if self.is_subset(non_dependency, encoding):
246
+ # non_dependency is weaker than encoding...
247
+ # don't save it in our new list
248
+ if verbosity >= 1:
249
+ print(f"Outdated non-dependency = {non_dependency}...")
250
+
251
+ pass
252
+ else:
253
+ # not a subset of encoding -> not trivial
254
+ new_non_dependencies.add(non_dependency)
255
+
256
+ new_non_dependencies.add(encoding)
257
+ self.non_dependencies = new_non_dependencies
258
+
259
+ # dictionary methods
260
+ # ------------------
261
+ def values(self) -> Iterable["Circuit"]:
262
+ """
263
+ **Description:**
264
+ Get the values (the actual circuits)
265
+
266
+ **Arguments:**
267
+ None
268
+
269
+ **Returns:**
270
+ The circuits.
271
+ """
272
+ return self.circuits.values()
273
+
274
+ def copy(self) -> "Circuits":
275
+ """
276
+ **Description:**
277
+ Copy the circuits object
278
+
279
+ **Arguments:**
280
+ None
281
+
282
+ **Returns:**
283
+ A copy of the circuits.
284
+ """
285
+ copied = Circuits()
286
+ copied.circuits = {Z:circ for Z,circ in self.circuits.items()}
287
+ copied.cone_to_circuit = {c:Zs.copy() for c,Zs in \
288
+ self.cone_to_circuit.items()}
289
+ copied.non_dependencies = self.non_dependencies.copy()
290
+ copied.know_all_circuits = self.know_all_circuits
291
+
292
+ return copied
293
+
294
+ def pop(self, *args, **kwargs):
295
+ """
296
+ Pop an element from the circuits dict
297
+ """
298
+ out = self.circuits.pop(*args, **kwargs)
299
+
300
+ # basic bit helpers
301
+ # -----------------
302
+ def encode(self, label_inds: Iterable[int]) -> int:
303
+ """
304
+ **Description:**
305
+ Convert an iterable of integers to a binary vector, b, such that
306
+ b_i = 1 <=> i in label_inds
307
+
308
+ **Arguments:**
309
+ - `label_inds`: The iterable of integers.
310
+
311
+ **Returns:**
312
+ The encoding
313
+ """
314
+ # as bitvector
315
+ if isinstance(label_inds, int):
316
+ return label_inds
317
+
318
+ encoding = 0
319
+ for label_ind in label_inds:
320
+ encoding |= (1 << int(label_ind))
321
+ return encoding
322
+
323
+ def decode(self, encoding) -> list[int]:
324
+ """
325
+ **Description:**
326
+ Convert a binary vector b to a list of of integers such that
327
+ b_i = 1 <=> i in label_inds
328
+
329
+ **Arguments:**
330
+ - `encoding`: The encoding to map to label indices
331
+
332
+ **Returns:**
333
+ The label indices
334
+ """
335
+ # as bitvector
336
+ label_inds = []
337
+
338
+ for shift in range(len(bin(encoding))-2):
339
+ if 1&(encoding>>shift):
340
+ label_inds.append(shift)
341
+
342
+ return label_inds
343
+
344
+ def is_superset(self, setA, setB) -> bool:
345
+ """
346
+ **Description:**
347
+ Check if the set encoded by setA is a superset of setB.
348
+
349
+ **Arguments:**
350
+ - `setA`: The candidate-superset encoding.
351
+ - `setB`: The candidate-subset encoding.
352
+
353
+ **Returns:**
354
+ Whether setA is a superset of setB.
355
+ """
356
+ # as bitvector
357
+ return (setA & setB) == setB
358
+
359
+ def is_subset(self, setA: int, setB: int) -> bool:
360
+ """
361
+ **Description:**
362
+ Check if the set encoded by setA is a subset of setB.
363
+
364
+ **Arguments:**
365
+ - `setA`: The candidate-superset encoding.
366
+ - `setB`: The candidate-subset encoding.
367
+
368
+ **Returns:**
369
+ Whether setA is a subset of setB.
370
+ """
371
+ return self.is_superset(setB, setA)