regfans 0.0.3__py3-none-any.whl → 0.0.4__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/fan.py +27 -27
- regfans/util.py +68 -69
- regfans/vectorconfig.py +153 -52
- regfans-0.0.4.dist-info/METADATA +61 -0
- regfans-0.0.4.dist-info/RECORD +10 -0
- regfans-0.0.3.dist-info/METADATA +0 -37
- regfans-0.0.3.dist-info/RECORD +0 -10
- {regfans-0.0.3.dist-info → regfans-0.0.4.dist-info}/WHEEL +0 -0
- {regfans-0.0.3.dist-info → regfans-0.0.4.dist-info}/licenses/LICENSE +0 -0
- {regfans-0.0.3.dist-info → regfans-0.0.4.dist-info}/top_level.txt +0 -0
regfans/fan.py
CHANGED
|
@@ -45,7 +45,7 @@ class Fan:
|
|
|
45
45
|
function.
|
|
46
46
|
|
|
47
47
|
This class is *not* intended to be called directly. Instead, it is meant to
|
|
48
|
-
be called through VectorConfiguration.
|
|
48
|
+
be called through VectorConfiguration.triangulate.
|
|
49
49
|
|
|
50
50
|
**Arguments:**
|
|
51
51
|
- `vc`: The ambient vector configuration that this fan is over.
|
|
@@ -139,8 +139,8 @@ class Fan:
|
|
|
139
139
|
f"A "
|
|
140
140
|
+ fine_str
|
|
141
141
|
+ regular_str
|
|
142
|
-
+ f" {subdivision_str} of "
|
|
143
|
-
+ repr(self._vc)
|
|
142
|
+
+ f" {subdivision_str} of a"
|
|
143
|
+
+ repr(self._vc)[1:]
|
|
144
144
|
)
|
|
145
145
|
|
|
146
146
|
def __str__(self) -> str:
|
|
@@ -171,8 +171,8 @@ class Fan:
|
|
|
171
171
|
f"A "
|
|
172
172
|
+ fine_str
|
|
173
173
|
+ regular_str
|
|
174
|
-
+ f" {subdivision_str} of "
|
|
175
|
-
+ str(self._vc)
|
|
174
|
+
+ f" {subdivision_str} of a"
|
|
175
|
+
+ str(self._vc)[1:]
|
|
176
176
|
)
|
|
177
177
|
|
|
178
178
|
def __hash__(self) -> int:
|
|
@@ -417,7 +417,7 @@ class Fan:
|
|
|
417
417
|
"""
|
|
418
418
|
if not self.is_triangulation():
|
|
419
419
|
# the following assumes simplicial cones
|
|
420
|
-
raise NotImplementedError
|
|
420
|
+
raise NotImplementedError("Not implemented for non-triangulations")
|
|
421
421
|
|
|
422
422
|
# compute the facets as a map from facet labels to containing cones
|
|
423
423
|
facets = dict()
|
|
@@ -534,7 +534,7 @@ class Fan:
|
|
|
534
534
|
"""
|
|
535
535
|
**Description:**
|
|
536
536
|
Return whether or not the fan also defines a (star) subdivision of the
|
|
537
|
-
underlying point configuration.
|
|
537
|
+
original underlying point configuration.
|
|
538
538
|
|
|
539
539
|
**Arguments:**
|
|
540
540
|
None.
|
|
@@ -545,7 +545,7 @@ class Fan:
|
|
|
545
545
|
"""
|
|
546
546
|
if not self.is_regular():
|
|
547
547
|
# could be checked by MaxMP but I have't implemented that...
|
|
548
|
-
raise NotImplementedError
|
|
548
|
+
raise NotImplementedError("Not implemented for irregular fans")
|
|
549
549
|
|
|
550
550
|
# just check if central subdivision is a refinement
|
|
551
551
|
H = self.secondary_cone_hyperplanes()
|
|
@@ -1040,7 +1040,7 @@ class Fan:
|
|
|
1040
1040
|
# return
|
|
1041
1041
|
if formal:
|
|
1042
1042
|
# update neighb from being a tuple of cones to a formal Fan object
|
|
1043
|
-
neighb = self.vc.
|
|
1043
|
+
neighb = self.vc.triangulate(cells=neighb)
|
|
1044
1044
|
|
|
1045
1045
|
# pass along circuit info to the neighbor
|
|
1046
1046
|
neighb._circuits = self._circuits.copy()
|
|
@@ -1232,8 +1232,8 @@ class Fan:
|
|
|
1232
1232
|
else:
|
|
1233
1233
|
assert h_target is None
|
|
1234
1234
|
if verbosity >= 0:
|
|
1235
|
-
print("
|
|
1236
|
-
print("h_target = h_init+1000*direction...")
|
|
1235
|
+
print("(Warning: directions are handled in a sub-optimal way...)")
|
|
1236
|
+
print("( one sets h_target = h_init+1000*direction... )")
|
|
1237
1237
|
direction = 1000*np.array(direction)
|
|
1238
1238
|
h_target = h_init+direction
|
|
1239
1239
|
direction_norm2 = np.dot(direction,direction)
|
|
@@ -1678,13 +1678,13 @@ def flip_subgraph(
|
|
|
1678
1678
|
tri_init = seed
|
|
1679
1679
|
labels = [
|
|
1680
1680
|
{
|
|
1681
|
-
"
|
|
1681
|
+
"regular": tri_init.is_regular(),
|
|
1682
1682
|
"fine": tri_init.is_fine(),
|
|
1683
|
-
"
|
|
1683
|
+
"respects_ptconfig": tri_init.respects_ptconfig(),
|
|
1684
1684
|
}
|
|
1685
1685
|
]
|
|
1686
1686
|
else:
|
|
1687
|
-
tri_init = seed.
|
|
1687
|
+
tri_init = seed.triangulate()
|
|
1688
1688
|
if not tri_init.is_triangulation():
|
|
1689
1689
|
print("ERROR: seed isn't triangulation! Quitting!")
|
|
1690
1690
|
return
|
|
@@ -1697,9 +1697,9 @@ def flip_subgraph(
|
|
|
1697
1697
|
|
|
1698
1698
|
labels = [
|
|
1699
1699
|
{
|
|
1700
|
-
"
|
|
1700
|
+
"regular": True,
|
|
1701
1701
|
"fine": tri_init.is_fine(),
|
|
1702
|
-
"
|
|
1702
|
+
"respects_ptconfig": True,
|
|
1703
1703
|
}
|
|
1704
1704
|
]
|
|
1705
1705
|
|
|
@@ -1751,17 +1751,17 @@ def flip_subgraph(
|
|
|
1751
1751
|
continue
|
|
1752
1752
|
|
|
1753
1753
|
# check various user-imposed restrictions
|
|
1754
|
-
new_label = {"
|
|
1754
|
+
new_label = {"regular": None, "fine": None, "respects_ptconfig": None}
|
|
1755
1755
|
if compute_node_labels:
|
|
1756
1756
|
# compute regularity (/maybe check it)
|
|
1757
1757
|
# ------------------
|
|
1758
1758
|
if verbosity >= 2:
|
|
1759
1759
|
print("checking regularity...", end=" ")
|
|
1760
|
-
new_label["
|
|
1760
|
+
new_label["regular"] = neighb.is_regular()
|
|
1761
1761
|
if verbosity >= 2:
|
|
1762
|
-
print(new_label["
|
|
1762
|
+
print(new_label["regular"])
|
|
1763
1763
|
|
|
1764
|
-
if only_regular and not new_label["
|
|
1764
|
+
if only_regular and not new_label["regular"]:
|
|
1765
1765
|
continue
|
|
1766
1766
|
|
|
1767
1767
|
# compute fineness (/maybe check it)
|
|
@@ -1779,25 +1779,25 @@ def flip_subgraph(
|
|
|
1779
1779
|
# -----------------
|
|
1780
1780
|
if verbosity >= 2:
|
|
1781
1781
|
print("checking PC triangulation...", end=" ")
|
|
1782
|
-
new_label["
|
|
1782
|
+
new_label["respects_ptconfig"] = neighb.respects_ptconfig()
|
|
1783
1783
|
if verbosity >= 2:
|
|
1784
|
-
print(new_label["
|
|
1784
|
+
print(new_label["respects_ptconfig"])
|
|
1785
1785
|
|
|
1786
|
-
if only_pc_triang and (not new_label["
|
|
1786
|
+
if only_pc_triang and (not new_label["respects_ptconfig"]):
|
|
1787
1787
|
continue
|
|
1788
1788
|
else:
|
|
1789
1789
|
# lazily compute the labels
|
|
1790
1790
|
if only_regular:
|
|
1791
|
-
new_label["
|
|
1792
|
-
if not new_label["
|
|
1791
|
+
new_label["regular"] = neighb.is_regular()
|
|
1792
|
+
if not new_label["regular"]:
|
|
1793
1793
|
continue
|
|
1794
1794
|
if only_fine:
|
|
1795
1795
|
new_label["fine"] = neighb.is_fine()
|
|
1796
1796
|
if not new_label["fine"]:
|
|
1797
1797
|
continue
|
|
1798
1798
|
if only_pc_triang:
|
|
1799
|
-
new_label["
|
|
1800
|
-
if not new_label["
|
|
1799
|
+
new_label["respects_ptconfig"] = neighb.respects_ptconfig()
|
|
1800
|
+
if not new_label["respects_ptconfig"]:
|
|
1801
1801
|
continue
|
|
1802
1802
|
|
|
1803
1803
|
# this is a new triangulation!
|
regfans/util.py
CHANGED
|
@@ -28,6 +28,74 @@ import numpy as np
|
|
|
28
28
|
from ortools.linear_solver import pywraplp
|
|
29
29
|
from typing import Union
|
|
30
30
|
|
|
31
|
+
# basic math
|
|
32
|
+
# ----------
|
|
33
|
+
def gcd(vals: list[float], max_denom: float=10**6) -> float:
|
|
34
|
+
"""
|
|
35
|
+
**Description:**
|
|
36
|
+
Computes the 'GCD' of a collection of floating point numbers.
|
|
37
|
+
This is the smallest number, g, such that g*values is integral.
|
|
38
|
+
|
|
39
|
+
This is computed by
|
|
40
|
+
1) converting `values` to be rational [n0/d0, n1/d1, ...],
|
|
41
|
+
2) computing the LCM, l, of [d0, d1, ...],
|
|
42
|
+
3) computing the GCD, g', of [l*n0/d0, l*n1/d1, ...], and then
|
|
43
|
+
4) returning g=g'/l.
|
|
44
|
+
|
|
45
|
+
**Arguments:**
|
|
46
|
+
- `vals`: The numbers to compute the GCD of.
|
|
47
|
+
- `max_denom`: Assert |di| <= max_denom
|
|
48
|
+
|
|
49
|
+
**Returns:**
|
|
50
|
+
The minimum number g' such that g'*vals is integral.
|
|
51
|
+
"""
|
|
52
|
+
# compute the rational representation
|
|
53
|
+
rat = [fractions.Fraction(v).limit_denominator(max_denom) for v in vals]
|
|
54
|
+
numer = [r.numerator for r in rat]
|
|
55
|
+
denom = [r.denominator for r in rat]
|
|
56
|
+
|
|
57
|
+
# get the relevant LCM, GCD
|
|
58
|
+
l = functools.reduce(math.lcm, denom)
|
|
59
|
+
gprime= functools.reduce(math.gcd, [n*(l//d) for n,d in zip(numer,denom)])
|
|
60
|
+
|
|
61
|
+
# return the GCD
|
|
62
|
+
if gprime%l == 0:
|
|
63
|
+
# integral
|
|
64
|
+
return gprime//l
|
|
65
|
+
else:
|
|
66
|
+
return gprime/l
|
|
67
|
+
|
|
68
|
+
def primitive(vec: list[float], max_denom=10**10):
|
|
69
|
+
"""
|
|
70
|
+
**Description:**
|
|
71
|
+
Computes the primitive vector associated to the input ray {c*vec: c>=0}.
|
|
72
|
+
Very similar to the gcd function.
|
|
73
|
+
|
|
74
|
+
This is equivalent to
|
|
75
|
+
vec/gcd(vec)
|
|
76
|
+
but just uses a rational representation.
|
|
77
|
+
|
|
78
|
+
**Arguments:**
|
|
79
|
+
- `vec`: A vector defining the ray {c*vec: c>=0}
|
|
80
|
+
- `max_denom`: Assert |di| <= max_denom
|
|
81
|
+
|
|
82
|
+
**Returns:**
|
|
83
|
+
The primitive vector along the ray.
|
|
84
|
+
"""
|
|
85
|
+
# compute the rational representation
|
|
86
|
+
rat = [fractions.Fraction(v).limit_denominator(max_denom) for v in vec]
|
|
87
|
+
numer = [r.numerator for r in rat]
|
|
88
|
+
denom = [r.denominator for r in rat]
|
|
89
|
+
|
|
90
|
+
# get the LCM of the denominators
|
|
91
|
+
l = functools.reduce(math.lcm, denom)
|
|
92
|
+
|
|
93
|
+
# get the integral vector and scale it to be primitive
|
|
94
|
+
prim = [n*(l//d) for n,d in zip(numer,denom)]
|
|
95
|
+
gprime= functools.reduce(math.gcd, prim)
|
|
96
|
+
|
|
97
|
+
return [x//gprime for x in prim]
|
|
98
|
+
|
|
31
99
|
# basic geometry
|
|
32
100
|
# --------------
|
|
33
101
|
def lerp(p0: "ArrayLike", p1: "ArrayLike", t: float) -> "ArrayLike":
|
|
@@ -365,72 +433,3 @@ def find_interior_point(*,
|
|
|
365
433
|
else:
|
|
366
434
|
warnings.warn(f"Unexpected error")
|
|
367
435
|
return None
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
# basic math - UNUSED
|
|
371
|
-
# ----------
|
|
372
|
-
def gcd(vals: list[float], max_denom: float=10**6) -> float:
|
|
373
|
-
"""
|
|
374
|
-
**Description:**
|
|
375
|
-
Computes the 'GCD' of a collection of floating point numbers.
|
|
376
|
-
This is the smallest number, g, such that g*values is integral.
|
|
377
|
-
|
|
378
|
-
This is computed by
|
|
379
|
-
1) converting `values` to be rational [n0/d0, n1/d1, ...],
|
|
380
|
-
2) computing the LCM, l, of [d0, d1, ...],
|
|
381
|
-
3) computing the GCD, g', of [l*n0/d0, l*n1/d1, ...], and then
|
|
382
|
-
4) returning g=g'/l.
|
|
383
|
-
|
|
384
|
-
**Arguments:**
|
|
385
|
-
- `vals`: The numbers to compute the GCD of.
|
|
386
|
-
- `max_denom`: Assert |di| <= max_denom
|
|
387
|
-
|
|
388
|
-
**Returns:**
|
|
389
|
-
The minimum number g' such that g'*vals is integral.
|
|
390
|
-
"""
|
|
391
|
-
# compute the rational representation
|
|
392
|
-
rat = [fractions.Fraction(v).limit_denominator(max_denom) for v in vals]
|
|
393
|
-
numer = [r.numerator for r in rat]
|
|
394
|
-
denom = [r.denominator for r in rat]
|
|
395
|
-
|
|
396
|
-
# get the relevant LCM, GCD
|
|
397
|
-
l = functools.reduce(math.lcm, denom)
|
|
398
|
-
gprime= functools.reduce(math.gcd, [n*(l//d) for n,d in zip(numer,denom)])
|
|
399
|
-
|
|
400
|
-
# return the GCD
|
|
401
|
-
if gprime%l == 0:
|
|
402
|
-
# integral
|
|
403
|
-
return gprime//l
|
|
404
|
-
else:
|
|
405
|
-
return gprime/l
|
|
406
|
-
|
|
407
|
-
def primitive(vec: list[float], max_denom=10**10):
|
|
408
|
-
"""
|
|
409
|
-
**Description:**
|
|
410
|
-
Computes the primitive vector associated to the input ray {c*vec: c>=0}.
|
|
411
|
-
Very similar to the gcd function.
|
|
412
|
-
|
|
413
|
-
This is equivalent to
|
|
414
|
-
vec/gcd(vec)
|
|
415
|
-
but just uses a rational representation.
|
|
416
|
-
|
|
417
|
-
**Arguments:**
|
|
418
|
-
- `vec`: A vector defining the ray {c*vec: c>=0}
|
|
419
|
-
- `max_denom`: Assert |di| <= max_denom
|
|
420
|
-
|
|
421
|
-
**Returns:**
|
|
422
|
-
The primitive vector along the ray.
|
|
423
|
-
"""
|
|
424
|
-
# compute the rational representation
|
|
425
|
-
rat = [fractions.Fraction(v).limit_denominator(max_denom) for v in vec]
|
|
426
|
-
numer = [r.numerator for r in rat]
|
|
427
|
-
denom = [r.denominator for r in rat]
|
|
428
|
-
|
|
429
|
-
# get the LCM of the denominators
|
|
430
|
-
l = functools.reduce(math.lcm, denom)
|
|
431
|
-
|
|
432
|
-
# get the integral vector and scale it to be primitive
|
|
433
|
-
prim = [n*(l//d) for n,d in zip(numer,denom)]
|
|
434
|
-
gprime= functools.reduce(math.gcd, prim)
|
|
435
|
-
|
|
436
|
-
return [x//gprime for x in prim]
|
regfans/vectorconfig.py
CHANGED
|
@@ -29,6 +29,7 @@ import networkx as nx
|
|
|
29
29
|
import numpy as np
|
|
30
30
|
import scipy as sp
|
|
31
31
|
import triangulumancer
|
|
32
|
+
import warnings
|
|
32
33
|
|
|
33
34
|
# local imports
|
|
34
35
|
from . import util, circuits, fan
|
|
@@ -490,7 +491,7 @@ class VectorConfiguration:
|
|
|
490
491
|
# could definitely be generalized to non-solid
|
|
491
492
|
# likely just check if
|
|
492
493
|
# len(dual_cone(self.vectors())) == 2*(ambient-dim)
|
|
493
|
-
raise NotImplementedError
|
|
494
|
+
raise NotImplementedError("Not implemented for non-solid VCs")
|
|
494
495
|
|
|
495
496
|
return len(util.dual_cone(self.vectors())) == 0
|
|
496
497
|
|
|
@@ -677,12 +678,13 @@ class VectorConfiguration:
|
|
|
677
678
|
|
|
678
679
|
# generating triangulations
|
|
679
680
|
# =========================
|
|
680
|
-
def
|
|
681
|
+
def triangulate(
|
|
681
682
|
self,
|
|
682
683
|
heights: "ArrayLike" = None,
|
|
683
684
|
cells: "ArrayLike" = None,
|
|
684
685
|
tol: float = 1e-14,
|
|
685
|
-
|
|
686
|
+
backend: str = None,
|
|
687
|
+
check_heights: bool = True,
|
|
686
688
|
verbosity: int = 0,
|
|
687
689
|
) -> "Fan":
|
|
688
690
|
"""
|
|
@@ -691,11 +693,14 @@ class VectorConfiguration:
|
|
|
691
693
|
or by heights.
|
|
692
694
|
|
|
693
695
|
**Arguments:**
|
|
694
|
-
- `heights`:
|
|
695
|
-
- `cells`:
|
|
696
|
-
- `
|
|
697
|
-
- `
|
|
698
|
-
|
|
696
|
+
- `heights`: The heights to lift the vectors by.
|
|
697
|
+
- `cells`: The cells to use in the triangulation.
|
|
698
|
+
- `tol`: Numerical tolerance used for curing negative heights
|
|
699
|
+
- `backend`: The lifting backend. Currently allowed to be "cgal"
|
|
700
|
+
or "ppl".
|
|
701
|
+
- `check_heights`: Whether to check that the heights land in the
|
|
702
|
+
secondary cone of the output triangulation.
|
|
703
|
+
- `verbosity`: The verbosity level. Higher is more verbose
|
|
699
704
|
|
|
700
705
|
**Returns:**
|
|
701
706
|
The resultant subdivision.
|
|
@@ -709,13 +714,43 @@ class VectorConfiguration:
|
|
|
709
714
|
|
|
710
715
|
# triangulate via heights
|
|
711
716
|
# =======================
|
|
717
|
+
# flag as to whether the user didn't input heights
|
|
718
|
+
default_heights = (heights is None)
|
|
719
|
+
|
|
720
|
+
# check the backend
|
|
721
|
+
if backend is None:
|
|
722
|
+
if default_heights:
|
|
723
|
+
backend = "cgal"
|
|
724
|
+
else:
|
|
725
|
+
backend = "ppl"
|
|
726
|
+
else:
|
|
727
|
+
backend = backend.lower()
|
|
728
|
+
if backend not in ["cgal", "ppl"]:
|
|
729
|
+
raise ValueError(f"Unrecognized backend '{backend}'...")
|
|
730
|
+
|
|
731
|
+
# warning in case the user request PPL backend but gave no heights
|
|
732
|
+
if default_heights and (backend != "cgal"):
|
|
733
|
+
msg = "Non-cgal backends are not trustworthy for Deulaunay... "
|
|
734
|
+
msg += f"changing from '{backend}' to cgal..."
|
|
735
|
+
with warnings.catch_warnings():
|
|
736
|
+
warnings.simplefilter("always")
|
|
737
|
+
warnings.warn(msg)
|
|
738
|
+
backend = "cgal"
|
|
739
|
+
|
|
740
|
+
# allow small perturbations to the height of the origin to enforce that
|
|
741
|
+
# cgal gives a star triangulation
|
|
742
|
+
if backend == "cgal":
|
|
743
|
+
make_cgal_star = default_heights
|
|
744
|
+
|
|
712
745
|
# (if no heights are provided, compute Delaunay triangulation)
|
|
713
746
|
# (need to add noise to ensure it is a *triangulation* and not a
|
|
714
747
|
# subdivision)
|
|
715
748
|
# (allow retrying in case the noise brings the heights outside the
|
|
716
749
|
# secondary fan... exceedingly unlikely though)
|
|
717
|
-
if
|
|
750
|
+
if default_heights:
|
|
718
751
|
heights = np.sum(self.vectors()*self.vectors(), axis=1)
|
|
752
|
+
else:
|
|
753
|
+
heights = np.array(heights)
|
|
719
754
|
|
|
720
755
|
# ensure the heights are non-negative
|
|
721
756
|
already_nonneg = all([h_i >= 0 for h_i in heights])
|
|
@@ -729,7 +764,7 @@ class VectorConfiguration:
|
|
|
729
764
|
heights_new, res = sp.optimize.nnls(B.T, Bh)
|
|
730
765
|
if res>tol:
|
|
731
766
|
print(f"Residuals {res} > tol {tol}...")
|
|
732
|
-
raise ValueError
|
|
767
|
+
raise ValueError("Invalid heights")
|
|
733
768
|
|
|
734
769
|
# do the check
|
|
735
770
|
if False: # too slow... instead just check if we found coeffs...
|
|
@@ -762,37 +797,98 @@ class VectorConfiguration:
|
|
|
762
797
|
return self.subdivide(cells=[self.labels])
|
|
763
798
|
|
|
764
799
|
# nonzero heights -> lift via a point configuration
|
|
765
|
-
|
|
766
|
-
|
|
800
|
+
if backend == "cgal":
|
|
801
|
+
orig = np.zeros((1,self.ambient_dim),dtype=int)
|
|
802
|
+
pts = np.vstack([orig, self.vectors()])
|
|
803
|
+
pc = triangulumancer.PointConfiguration(pts)
|
|
804
|
+
|
|
805
|
+
if make_cgal_star:
|
|
806
|
+
# adjust heights for PC such that the triangulation is star...
|
|
807
|
+
# just ensure that 0 is in all simplices
|
|
808
|
+
#
|
|
809
|
+
# this should always be true by construction...
|
|
810
|
+
# but CGAL definitely perturbs heights a bit (e.g., lifting by
|
|
811
|
+
# heights = 0 doesn't lead to trivial subdivision)
|
|
812
|
+
#
|
|
813
|
+
# maybe this causes errors leading to non-star triangulations...
|
|
814
|
+
height_min = np.min(heights)
|
|
815
|
+
height_orig = -1e-6*height_min
|
|
816
|
+
while True:
|
|
817
|
+
heights_pc = np.concatenate(([height_orig], heights))
|
|
818
|
+
simp_pcinds = pc.triangulate_with_heights(heights_pc).simplices()
|
|
819
|
+
|
|
820
|
+
# lower the height of the origin if not star
|
|
821
|
+
if not all([0 in simp for simp in simp_pcinds]):
|
|
822
|
+
height_orig -= height_min
|
|
823
|
+
continue
|
|
824
|
+
|
|
825
|
+
# star :)
|
|
826
|
+
break
|
|
827
|
+
|
|
828
|
+
# check that we didn't lower the height of origin a crazy amount
|
|
829
|
+
if (verbosity >= 1) and (height_orig < -np.min(heights)):
|
|
830
|
+
msg = "Significantly lowered the height of the origin... "
|
|
831
|
+
msg += "maybe something went wrong..."
|
|
832
|
+
with warnings.catch_warnings():
|
|
833
|
+
warnings.simplefilter("always")
|
|
834
|
+
warnings.warn(msg)
|
|
835
|
+
else:
|
|
836
|
+
heights_pc = np.concatenate(([0], heights))
|
|
837
|
+
simp_pcinds = pc.triangulate_with_heights(heights_pc).simplices()
|
|
767
838
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
height_orig = -height_norm
|
|
776
|
-
while True:
|
|
777
|
-
heights_pc = np.concatenate(([height_orig], heights))
|
|
778
|
-
simp_pcinds = pc.triangulate_with_heights(heights_pc).simplices()
|
|
779
|
-
|
|
780
|
-
# lower the height of the origin if not star
|
|
781
|
-
if not all([0 in simp for simp in simp_pcinds]):
|
|
782
|
-
height_orig -= height_norm
|
|
783
|
-
continue
|
|
839
|
+
# read the simplices as indices in the VC
|
|
840
|
+
if not all([0 in s for s in simp_pcinds]):
|
|
841
|
+
msg = "cgal didn't produce a star triangulation... "
|
|
842
|
+
msg += f"cells = {simp_pcinds} (0 corresponds to origin). "
|
|
843
|
+
msg += "maybe try PPL..."
|
|
844
|
+
raise ValueError(msg)
|
|
845
|
+
simp_vcinds = [[pti-1 for pti in s if pti!=0] for s in simp_pcinds]
|
|
784
846
|
|
|
785
|
-
|
|
786
|
-
|
|
847
|
+
elif backend == "ppl":
|
|
848
|
+
# construct the rays of the lifted cone
|
|
849
|
+
lifted = np.hstack([heights.reshape(-1,1),self.vectors()])
|
|
850
|
+
lifted = np.array([util.primitive(v) for v in lifted]) # as integers
|
|
851
|
+
|
|
852
|
+
H = np.array(util.dual_cone(lifted))
|
|
853
|
+
satd = H@lifted.T
|
|
854
|
+
|
|
855
|
+
# read the simplices as indices in the VC
|
|
856
|
+
simp_vcinds = [np.where(facet==0)[0].tolist() for facet in satd]
|
|
787
857
|
|
|
788
858
|
# read the simplices as labels
|
|
789
|
-
simp_vcinds = [[pti-1 for pti in s if pti!=0] for s in simp_pcinds]
|
|
790
859
|
simp_labels = [[self.labels[vci] for vci in s] for s in simp_vcinds]
|
|
860
|
+
simp_labels = [sorted(simp) for simp in simp_labels]
|
|
861
|
+
|
|
862
|
+
# construct the fan
|
|
863
|
+
f = self.triangulate(cells=simp_labels)
|
|
791
864
|
|
|
792
|
-
|
|
865
|
+
# some sanity checks
|
|
866
|
+
if not f.is_triangulation():
|
|
867
|
+
if verbosity >= 1:
|
|
868
|
+
msg = "Upon lifting, a non-triangulation subdivision was "
|
|
869
|
+
msg += f"output... (cells = {f.simplices()}) "
|
|
870
|
+
msg += "double check with another backend (PPL is preferable) "
|
|
871
|
+
msg += "OR perturb heights..."
|
|
872
|
+
|
|
873
|
+
with warnings.catch_warnings():
|
|
874
|
+
warnings.simplefilter("always")
|
|
875
|
+
warnings.warn(msg)
|
|
876
|
+
else:
|
|
877
|
+
# yes a triangulation...
|
|
878
|
+
# verify that the secondary cone contains the heights
|
|
879
|
+
if (not default_heights) and check_heights:
|
|
880
|
+
H = np.array(f.secondary_cone_hyperplanes())
|
|
881
|
+
dists = H@heights
|
|
882
|
+
|
|
883
|
+
if np.any((dists)<=0):
|
|
884
|
+
msg = "Heights not contained in secondary cone... "
|
|
885
|
+
msg += f"distances = {dists}..."
|
|
886
|
+
raise Exception(msg)
|
|
887
|
+
|
|
888
|
+
return f
|
|
793
889
|
|
|
794
890
|
# aliases
|
|
795
|
-
|
|
891
|
+
subdivide = triangulate
|
|
796
892
|
|
|
797
893
|
def all_triangulations(
|
|
798
894
|
self,
|
|
@@ -846,7 +942,8 @@ class VectorConfiguration:
|
|
|
846
942
|
N: int = None,
|
|
847
943
|
as_list: bool = False,
|
|
848
944
|
attempts_per_triang: int = 1000,
|
|
849
|
-
backend: str =
|
|
945
|
+
backend: str = None,
|
|
946
|
+
seed: int = 0,
|
|
850
947
|
verbosity: int = 0,
|
|
851
948
|
) -> Generator["Fan"] | list["Fan"]:
|
|
852
949
|
"""
|
|
@@ -871,7 +968,9 @@ class VectorConfiguration:
|
|
|
871
968
|
or as a generator.
|
|
872
969
|
- `attempts_per_triang`:Quit if we can't generate a new triangulation
|
|
873
970
|
after this many tries.
|
|
874
|
-
- `backend`: The lifting backend.
|
|
971
|
+
- `backend`: The lifting backend. See
|
|
972
|
+
`VectorConfiguration.triangulate`.
|
|
973
|
+
- `seed`: A random number seed.
|
|
875
974
|
- `verbosity`: The verbosity level. Higher is more verbose.
|
|
876
975
|
|
|
877
976
|
**Returns:**
|
|
@@ -933,6 +1032,7 @@ class VectorConfiguration:
|
|
|
933
1032
|
iterator = range(N)
|
|
934
1033
|
|
|
935
1034
|
# generate the triangulations
|
|
1035
|
+
np.random.seed(seed)
|
|
936
1036
|
for _ in iterator:
|
|
937
1037
|
if method == "delaunay":
|
|
938
1038
|
# generate triangulations near Delaunay
|
|
@@ -953,7 +1053,7 @@ class VectorConfiguration:
|
|
|
953
1053
|
h = np.multiply(h, self._vector_norms)
|
|
954
1054
|
|
|
955
1055
|
try:
|
|
956
|
-
t = self.
|
|
1056
|
+
t = self.triangulate(heights=h, backend=backend)
|
|
957
1057
|
except sp.spatial.qhull.QhullError:
|
|
958
1058
|
# QHull error :(
|
|
959
1059
|
if verbosity >= 0:
|
|
@@ -1163,7 +1263,6 @@ class VectorConfiguration:
|
|
|
1163
1263
|
|
|
1164
1264
|
def secondary_fan(self,
|
|
1165
1265
|
only_fine: bool=False,
|
|
1166
|
-
project_lineality: bool=False,
|
|
1167
1266
|
formal_fan: bool=False,
|
|
1168
1267
|
verbosity: int=0):
|
|
1169
1268
|
"""
|
|
@@ -1171,11 +1270,9 @@ class VectorConfiguration:
|
|
|
1171
1270
|
Compute the secondary fan of the vector configuration.
|
|
1172
1271
|
|
|
1173
1272
|
**Arguments:**
|
|
1174
|
-
- `only_fine`:
|
|
1175
|
-
- `
|
|
1176
|
-
|
|
1177
|
-
- `formal_fan`: Save as a formal Fan object.
|
|
1178
|
-
- `verbosity`: The verbosity level. Higher is more verbose
|
|
1273
|
+
- `only_fine`: Restrict to fine triangulations.
|
|
1274
|
+
- `formal_fan`: Save as a formal Fan object.
|
|
1275
|
+
- `verbosity`: The verbosity level. Higher is more verbose
|
|
1179
1276
|
|
|
1180
1277
|
**Returns:**
|
|
1181
1278
|
The secondary fan triangulations.
|
|
@@ -1185,19 +1282,21 @@ class VectorConfiguration:
|
|
|
1185
1282
|
only_fine=only_fine,
|
|
1186
1283
|
verbosity=verbosity)
|
|
1187
1284
|
|
|
1188
|
-
# compute
|
|
1189
|
-
fan
|
|
1190
|
-
fan in triangs]
|
|
1285
|
+
# compute the (hyperplanes of the) secondary cones
|
|
1286
|
+
fan = [fan.secondary_cone_hyperplanes() for fan in triangs]
|
|
1191
1287
|
|
|
1192
1288
|
# map to a formal fan
|
|
1193
1289
|
if formal_fan:
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1290
|
+
fan_R = [util.dual_cone(H) for H in fan]
|
|
1291
|
+
all_rays = np.array(list({
|
|
1292
|
+
{tuple(r) for cone_R in fan for r in cone_R}
|
|
1293
|
+
}))
|
|
1294
|
+
|
|
1295
|
+
# construct the VC
|
|
1296
|
+
vc = VectorConfiguration(all_rays)
|
|
1198
1297
|
|
|
1199
|
-
cones_as_labels = sorted([sorted(vc.vectors_to_labels(
|
|
1200
|
-
|
|
1298
|
+
cones_as_labels = sorted([sorted(vc.vectors_to_labels(cone_R))\
|
|
1299
|
+
for cone_R in fan_R])
|
|
1201
1300
|
fan = vc.subdivide(cells=cones_as_labels)
|
|
1202
1301
|
|
|
1203
1302
|
return fan, triangs
|
|
@@ -1216,4 +1315,6 @@ class VectorConfiguration:
|
|
|
1216
1315
|
**Returns:**
|
|
1217
1316
|
The central fan.
|
|
1218
1317
|
"""
|
|
1219
|
-
return self.subdivide(
|
|
1318
|
+
return self.subdivide(
|
|
1319
|
+
heights=[1 for _ in self.labels],
|
|
1320
|
+
check_heights=False)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: regfans
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Regular fans of vector configurations
|
|
5
|
+
Author: Nate MacFadden
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Repository, https://github.com/natemacfadden/regfans
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Requires-Python: <3.14,>=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: python-flint
|
|
14
|
+
Requires-Dist: ortools
|
|
15
|
+
Requires-Dist: networkx
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: scipy
|
|
18
|
+
Requires-Dist: triangulumancer
|
|
19
|
+
Provides-Extra: test
|
|
20
|
+
Requires-Dist: pplpy; extra == "test"
|
|
21
|
+
Requires-Dist: pytest; extra == "test"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
# regfans
|
|
27
|
+
Software for studying vector configurations defined over the lattice vectors. This includes
|
|
28
|
+
- constructing regular triangulations (i.e., polyhedral fans) of such vector configurations via lifting,
|
|
29
|
+
- constructing all (regular) triangulations via computation of flip graphs,
|
|
30
|
+
- verification of various properties of the vector configuration/fan, and
|
|
31
|
+
- efficient linear flipping.
|
|
32
|
+
|
|
33
|
+
See [Triangulations: Structures for Algorithms and Applications](https://link.springer.com/book/10.1007/978-3-642-12971-1) by De Loera, Rambau, and Santos for a definitive resource on such topics.
|
|
34
|
+
|
|
35
|
+
This package, `regfans`, was originally developed for constructing toric varieties in the work [Calabi-Yau Threefolds from Vex Triangulations](https://arxiv.org/abs/2512.14817). Said work was supported in part by NSF grant PHY-2309456. All toric-geometric computations are isolated to [CYTools](https://github.com/LiamMcAllisterGroup/cytools), which has an extension `vector_config` building off of `regfans`.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
`regfans` can be installed using either conda or pip. To install `regfans` using conda, please see/use the provided `environment.yml` file:
|
|
39
|
+
```
|
|
40
|
+
conda env create -f environment.yml
|
|
41
|
+
conda activate regfans
|
|
42
|
+
```
|
|
43
|
+
To install `regfans` using pip, either run (to install the most recent release; also see [PyPI listing](https://pypi.org/project/regfans/))
|
|
44
|
+
```
|
|
45
|
+
pip install regfans
|
|
46
|
+
```
|
|
47
|
+
or (to install a local version)
|
|
48
|
+
```
|
|
49
|
+
pip install .
|
|
50
|
+
```
|
|
51
|
+
N.B.: many methods in `regfans` require computation of dual cones (i.e., the generators of a cone defined via hyperplanes or vice-versa). Currently, this requires [pplpy](https://pypi.org/project/pplpy/) which cannot be automatically installed via pip.
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
See [api.md](documentation/api.md) for full API reference.
|
|
56
|
+
|
|
57
|
+
(To update documentation, just run `pydoc-markdown; py documentation/clean_api.py`)
|
|
58
|
+
|
|
59
|
+
## Tutorials
|
|
60
|
+
|
|
61
|
+
See the [tutorials directory](tutorials/) for some commented example scripts showing how to construct a vector configuration, check properties of it, construct fans from it, and check the properties of said fans.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
regfans/__init__.py,sha256=ySoKB1J2jeUD9oHoPqD2d-utSO-cqd9eI1dq4Q2SMBo,103
|
|
2
|
+
regfans/circuits.py,sha256=5uLH8oQiAKe7UYl7sKvFpxMzNViP9tcLtqA91zbWduk,11244
|
|
3
|
+
regfans/fan.py,sha256=w3VMzH1DxQq0qiC5Isi0vQUf3Ugc2C4mqcwrkgtGhyY,65043
|
|
4
|
+
regfans/util.py,sha256=JeyaFZ2a5ujQNoQAgKUim9zo_Ot_QZZ04YnQaQU1y4Y,12923
|
|
5
|
+
regfans/vectorconfig.py,sha256=F8UsQslI5GIX9N9eun3H8aNOcA84L_-Ok-HicY-q3Bw,45214
|
|
6
|
+
regfans-0.0.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
7
|
+
regfans-0.0.4.dist-info/METADATA,sha256=UEIFtVgQDgdwYxbu3EWl0Um_G_INE7NY4Utfs9CrZJA,2803
|
|
8
|
+
regfans-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
regfans-0.0.4.dist-info/top_level.txt,sha256=h_8B8JgprCNKyinFEG8XvWOLKDouj_vmktFOf6_7Q0Q,8
|
|
10
|
+
regfans-0.0.4.dist-info/RECORD,,
|
regfans-0.0.3.dist-info/METADATA
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: regfans
|
|
3
|
-
Version: 0.0.3
|
|
4
|
-
Summary: Regular fans of vector configurations
|
|
5
|
-
Author: Nate MacFadden
|
|
6
|
-
License-Expression: GPL-3.0-or-later
|
|
7
|
-
Project-URL: Repository, https://github.com/natemacfadden/regfans
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
-
Requires-Python: <3.14,>=3.9
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
|
-
Requires-Dist: python-flint
|
|
14
|
-
Requires-Dist: ortools
|
|
15
|
-
Requires-Dist: networkx
|
|
16
|
-
Requires-Dist: numpy
|
|
17
|
-
Requires-Dist: scipy
|
|
18
|
-
Requires-Dist: triangulumancer
|
|
19
|
-
Provides-Extra: test
|
|
20
|
-
Requires-Dist: pplpy; extra == "test"
|
|
21
|
-
Requires-Dist: pytest; extra == "test"
|
|
22
|
-
Dynamic: license-file
|
|
23
|
-
|
|
24
|
-
# vectorconfigurations
|
|
25
|
-
Software for studying vector configurations and their triangulations. Originally developed for arXiv:2512.14817
|
|
26
|
-
|
|
27
|
-
## Installation
|
|
28
|
-
Computation of dual cones requires pplpy. Install via conda:
|
|
29
|
-
```
|
|
30
|
-
conda env create -f environment.yml
|
|
31
|
-
conda activate regfans
|
|
32
|
-
```
|
|
33
|
-
## API Documentation
|
|
34
|
-
|
|
35
|
-
See [API_DOC.md](API_DOC.md) for full API reference.
|
|
36
|
-
|
|
37
|
-
(Developer note: update documentation simply by running `pydoc-markdown; py clean_API_DOC.py` in current directory)
|
regfans-0.0.3.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
regfans/__init__.py,sha256=ySoKB1J2jeUD9oHoPqD2d-utSO-cqd9eI1dq4Q2SMBo,103
|
|
2
|
-
regfans/circuits.py,sha256=5uLH8oQiAKe7UYl7sKvFpxMzNViP9tcLtqA91zbWduk,11244
|
|
3
|
-
regfans/fan.py,sha256=dHLaDy00Qq0tWas6237vxyY88UiIo-4_3R7V7JllpY8,64783
|
|
4
|
-
regfans/util.py,sha256=HdowruB_t37RWosu7XRVHjOdcxgyuxhR1YxdxUY1K94,12933
|
|
5
|
-
regfans/vectorconfig.py,sha256=VUZpyzUjuMy9QCR3SayMwOE7Q-ve5V610kwQTDbl39Q,40932
|
|
6
|
-
regfans-0.0.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
7
|
-
regfans-0.0.3.dist-info/METADATA,sha256=roRs3mohUPcBGDq358QHR26BlCO_EiZ-4OPdtEit8Rk,1154
|
|
8
|
-
regfans-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
regfans-0.0.3.dist-info/top_level.txt,sha256=h_8B8JgprCNKyinFEG8XvWOLKDouj_vmktFOf6_7Q0Q,8
|
|
10
|
-
regfans-0.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|