Skip to content

Commit

Permalink
First release
Browse files Browse the repository at this point in the history
  • Loading branch information
WayneLin92 committed Nov 16, 2021
1 parent 737c7bd commit 297e6f8
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 49 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"monic",
"multinomial",
"mymath",
"pygroebner",
"rels",
"summands"
],
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,52 @@

This is a package for Groebner basis over prime 2.

The `pygroebner` module can be used to
* create an algebra/DGA over F2 by generators and relations;
* calculate the generators of an ideal of annihilators;
* calculate the subalgebra generated by some elements;
* export the algebra/DGA to latex;
* save the algebra/DGA in an sqlite3 database;
* load the algebra/DGA in an sqlite3 database.

In the release version of the package, the module will be able to
* compute the homology of a DGA;
* export the algebra to an HTML file so that you can visualize and interact with it.

The sqlite3 database is intended to be used as a compact form to be shared among people.
It also serve as the interface to my C++ groebner basis project, which runs much faster
but is not suitable for a casual use when the computation is not super heavy.

## Usage
```python
>>> from pygroebner import new_alg, load_alg
>>> A = new_alg(key_mo="Lex")
>>> x = A.add_gen("x", (1, 1))
>>> y = A.add_gen("y", (1, 1))
>>> A.add_rel(x * x + y * y)
>>> A.add_rel(y ** 3)
>>> x ** 2
y^2
>>> print(A.latex_alg())
\section{Gens}

$x$ (1, 1)

$y$ (1, 1)


\section{Relations}

$x^2 = y^2$

$y^3 = 0$

>>> A.save_alg("tmp.db", "A")
>>> B = load_alg("tmp.db", "A")
>>> B.gen['x'] * B.gen['y']
xy
>>> B.gens == A.gens
True
```


6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ name = pygroebner
version = 0.0.1
author = Weinan Lin
author_email = [email protected]
description = A package for groebner basis over prime 2 written in python
description = A package for groebner basis over prime 2 written implemented in python
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/pypa/sampleproject
url = https://github.com/WayneLin92/pygroebner
project_urls =
Bug Tracker = https://github.com/pypa/sampleproject/issues
Bug Tracker = https://github.com/WayneLin92/pygroebner/issues
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Expand Down
1 change: 0 additions & 1 deletion src/groebner/__init__.py

This file was deleted.

1 change: 1 addition & 0 deletions src/pygroebner/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .pygroebner import *
File renamed without changes.
File renamed without changes.
99 changes: 56 additions & 43 deletions src/groebner/groebner.py → src/pygroebner/pygroebner.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ def key_lex(m):
class AlgGb(BA.AlgebraMod2):
"""A factory for algebras based on Groebner basis.
`AlgGb.new_alg()` creates a new algebra with
its own gens and relations.
`new_alg()` creates a new algebra which is a subclass of `AlgGb` nad has
its own generators and relations.
"""

gens = None # type: dict[int, Gen]
rels = None # type: dict[tuple, set]
_rels_buffer = None # type: dict[int, list[set]]
key = None # type: Callable[[tuple], Any]
key_mo = None # type: Callable[[tuple], Any]
pred = None # type: Callable[[tuple], Any]
dim_grading = None # type: int

Expand All @@ -97,7 +97,7 @@ class AlgGb(BA.AlgebraMod2):
@classmethod
def copy_alg(cls) -> Type[AlgGb]:
"""Return a copy of current algebra."""
A = new_alg(key=cls.key, pred=cls.pred)
A = new_alg(key_mo=cls.key_mo, pred=cls.pred)
A.gens = cls.gens.copy()
A.rels = copy.deepcopy(cls.rels)
A._rels_buffer = copy.deepcopy(cls._rels_buffer)
Expand Down Expand Up @@ -133,7 +133,7 @@ def save_alg(
sql_mo = {None: "Revlex", "Lex": "Lex"}
info = {
"version": "0.0",
"mo": (sql_mo[cls.key] if cls.key in sql_mo else ""),
"mo": (sql_mo[cls.key_mo] if cls.key_mo in sql_mo else ""),
"date": sql_date,
"grading": ",".join(grading),
}
Expand Down Expand Up @@ -161,8 +161,8 @@ def latex_alg(cls) -> str:
result = "\\section{Gens}\n\n"
gens = defaultdict(list)
for g in cls.gens.values():
result += f"{g.deg} {g.name}"
result += "\\section{Relations}\n\n"
result += f"${g.name}$ {g.deg}\n\n"
result += "\n\\section{Relations}\n\n"
for m in cls.rels:
result += f"${cls(m)} = {cls(cls.rels[m])}$\n\n"
return result
Expand All @@ -175,7 +175,7 @@ def to_dga(cls, deg_diff: tuple) -> Type["DgaGb"]:
"gens": {i: DgaGen(*g, None) for i, g in cls.gens.items()},
"rels": copy.deepcopy(cls.rels),
"_rels_buffer": copy.deepcopy(cls._rels_buffer),
"key": cls.key,
"key_mo": cls.key_mo,
"pred": cls.pred,
"dim_grading": cls.dim_grading,
"deg_diff": Vector(deg_diff),
Expand All @@ -192,7 +192,8 @@ def str_sqlite3_mon(cls, mon: tuple):
def str_sqlite3_data(cls, data: set):
"""Convert self to a string to be stored in sqlite."""
return ";".join(
cls.str_sqlite3_mon(mon) for mon in sorted(data, key=cls.key, reverse=True)
cls.str_sqlite3_mon(mon)
for mon in sorted(data, key=cls.key_mo, reverse=True)
)

@classmethod
Expand Down Expand Up @@ -220,7 +221,7 @@ def data_sqlite3(cls, s: str):
# ---------- AlgebraMod2 ----------
@classmethod
def mul_mons(cls, mon1: tuple, mon2: tuple):
m = add_dtuple(mon1, mon2)
m = add_dtuple(mon1, mon2)
return cls.reduce_data({m})

@classmethod
Expand Down Expand Up @@ -288,7 +289,7 @@ def gen(cls, k: int | str):
return cls(m).reduce()
else:
raise BA.MyKeyError(f"No generator named {k}")

@classmethod
def add_gens(cls, name_deg_s):
"""Add gens. name_deg_s is a list of tuples (name, deg)."""
Expand Down Expand Up @@ -346,10 +347,10 @@ def rename_gen(cls, old_name, new_name):
cls.gens[i] = Gen(new_name, g.deg)

@classmethod
def reorder_gens(cls, index_map: dict = None, key=None):
def reorder_gens(cls, index_map: dict = None, key_mo=None):
"""Reorganize the relations by a new ordering of gens and a new key function.
The old i'th generator is the new `index_map[i]`'th generator."""
A = AlgGb.new_alg(pred=cls.pred, key=key)
A = new_alg(pred=cls.pred, key_mo=key_mo)
num_gens = len(cls.gens)
if index_map:

Expand Down Expand Up @@ -448,8 +449,8 @@ def add_rels(cls, rels: Iterable[AlgGb], d_max=None):
@classmethod
def get_lead(cls, data):
"""Return the leading term of `data`."""
return max(data, key=cls.key) if cls.key else max(data)
return max(data, key=cls.key_mo) if cls.key_mo else max(data)

@classmethod
def reduce_data(cls, data: set) -> set:
"""Return reduced `data`. `data` will not be changed."""
Expand Down Expand Up @@ -600,14 +601,18 @@ def ann_seq(cls, ele_names: list[tuple["AlgGb", str]]):
"""Return relations among elements: $\\sum a_ie_i=0$."""
A = cls.copy_alg()
index = max(A.gens, default=-1)
if cls.key:
A.key = lambda _m: (
if cls.key_mo:
A.key_mo = lambda _m: (
not (_m1 := _m[bisect_left(_m, (index, 0)) :]),
_m1,
cls.key(_m),
cls.key_mo(_m),
)
else:
A.key = lambda _m: (not (_m1 := _m[bisect_left(_m, (index, 0)) :]), _m1, _m)
A.key_mo = lambda _m: (
not (_m1 := _m[bisect_left(_m, (index, 0)) :]),
_m1,
_m,
)
rels_new = []
for ele, name in ele_names:
x = A.add_gen(name, ele.deg())
Expand Down Expand Up @@ -637,17 +642,17 @@ def ann_seq(cls, ele_names: list[tuple["AlgGb", str]]):
for m2 in ele2.data:
a.append((m2, name1, deg1))
annilators.append(a)
if cls.key:
if cls.key_mo:

def key(_m):
return A.deg0_mon(_m[bisect_left(_m, (index, 0)) :]), cls.key(_m)
return A.deg0_mon(_m[bisect_left(_m, (index, 0)) :]), cls.key_mo(_m)

else:

def key(_m):
return A.deg0_mon(_m[bisect_left(_m, (index, 0)) :]), _m

A = A.reorder_gens(key=key)
A = A.reorder_gens(key_mo=key)
annilators = [
[(cls(A.reduce({_m})), name, deg) for _m, name, deg in a]
for a in annilators
Expand All @@ -667,7 +672,7 @@ def latex_annilators(annilators: list[list[tuple["AlgGb", str, int]]]):
return Latex(result)

@classmethod
def subalgebra(cls, ele_names: list[tuple["AlgGb", str]], *, key=None):
def subalgebra(cls, ele_names: list[tuple["AlgGb", str]], *, key_mo=None):
"""Return the subalgebra generated by `ele_names`."""
A = cls.copy_alg()
index = max(A.gens, default=-1)
Expand All @@ -676,17 +681,17 @@ def key1(_m):
_m1, _m2 = _m[: (i := bisect_left(_m, (index, 0)))], _m[i:]
return (
cls.deg0_mon(_m1),
(cls.key(_m1) if cls.key else _m1),
(key(_m2) if key else _m2),
(cls.key_mo(_m1) if cls.key_mo else _m1),
(key_mo(_m2) if key_mo else _m2),
)

A.key = key1
A.key_mo = key1
for ele, name in ele_names:
x = A.add_gen(name, ele.deg())
A.add_rel(x + ele)
A.add_rels_buffer()
A.gens = {i: v for i, v in A.gens.items() if i > index}
A.key = key
A.key_mo = key_mo
rels, A.rels = A.rels, {}
for m in rels:
if m[0][0] > index:
Expand Down Expand Up @@ -732,7 +737,7 @@ def copy_alg(cls) -> "Type[DgaGb]":
"gens": cls.gens.copy(),
"rels": copy.deepcopy(cls.rels),
"_rels_buffer": copy.deepcopy(cls._rels_buffer),
"key": cls.key,
"key_mo": cls.key_mo,
"pred": cls.pred,
"deg_diff": cls.deg_diff,
}
Expand Down Expand Up @@ -900,25 +905,31 @@ def determine_diffs(cls, basis: dict, image_gens=None):
cls.determine_diff(i, basis, image_gens)

@classmethod
def print_latex_alg(cls, show_gb=False):
def latex_alg(cls, show_gb=False):
"""For latex."""
super().print_latex_alg(show_gb)
print("\\section{Differentials}\n")
result = super().latex_alg(show_gb)
result += "\\section{Differentials}\n\n"
for gen in cls.gens.values():
print(f"$d({gen.name})={cls(gen.diff)}$\\vspace{{3pt}}\n")
result += f"$d({gen.name})={cls(gen.diff)}$\\vspace{{3pt}}\n\n"


def new_alg(*, key=None, pred=None) -> Type[AlgGb]:
def new_alg(*, key_mo: str | Callable = None, pred=None) -> Type[AlgGb]:
"""Return a dynamically created subclass of AlgGb.
When key=None, use revlex ordering by default."""
When `key_mo=None`, use revlex ordering by default."""
class_name = f"AlgGb_{AlgGb._index_subclass}"
AlgGb._index_subclass += 1
if key_mo == "Lex" or key_mo == "lex":
key_mo = key_lex
elif key_mo == "Revlex" or key_mo == "revlex":
key_mo = None
else:
raise BA.MyError("unknown monomial ordering")
dct = {
"gens": {},
"rels": {},
"_rels_buffer": defaultdict(list),
"key": key,
"key_mo": key_mo,
"pred": pred or pred_always_true,
"dim_grading": None,
}
Expand All @@ -943,18 +954,20 @@ def load_alg(
mo == key_lex
else:
raise BA.MyError("Can not determine the key function")
grading = get_one_element(c.execute('SELECT value FROM info WHERE key="grading"'))[
0
]
grading = get_one_element(
c.execute('SELECT value FROM info WHERE key="grading"')
)[0]

A = new_alg(key=mo, pred=pred)
A = new_alg(key_mo=mo, pred=pred)
A.dim_grading = grading.count(",") + 1
if version == "0.0":
for id, name, *deg in c.execute(
f"SELECT gen_id, gen_name, {grading} FROM {tablename}_generators ORDER BY gen_id"
):
A.gens[id] = Gen(name, Vector(deg))
for m, b in c.execute(f"SELECT leading_term, basis FROM {tablename}_relations"):
for m, b in c.execute(
f"SELECT leading_term, basis FROM {tablename}_relations"
):
A.rels[AlgGb.mon_sqlite3(m)] = AlgGb.data_sqlite3(b)
else:
raise BA.MyError("Version unknown")
Expand All @@ -965,10 +978,10 @@ def load_alg(
return A


def new_dga(*, key=None, pred=None, deg_diff=None) -> Type[DgaGb]:
def new_dga(*, key_mo=None, pred=None, deg_diff=None) -> Type[DgaGb]:
"""Return a dynamically created subclass of GbDga.
When key=None, use revlex ordering by default."""
When key_mo=None, use revlex ordering by default."""
class_name = f"GbDga_{DgaGb._index_subclass}"
DgaGb._index_subclass += 1
if deg_diff is not None:
Expand All @@ -979,7 +992,7 @@ def new_dga(*, key=None, pred=None, deg_diff=None) -> Type[DgaGb]:
"gens": {},
"rels": {},
"_rels_buffer": {},
"key": key,
"key_mo": key_mo,
"pred": pred or pred_always_true,
"dim_grading": None,
"deg_diff": deg_diff,
Expand Down
4 changes: 2 additions & 2 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class GroebnerTestCase(unittest.TestCase):
def setUp(self):
print(sys.path)
from groebner import new_alg
from pygroebner import new_alg
from itertools import permutations

self.new_alg = new_alg
Expand Down Expand Up @@ -73,7 +73,7 @@ def test_subalgebra(self):
for i in range(n_max + 1 - d):
j = i + d
ele_names.append((R(i, j) * R(i, j), f"b_{{{i}{j}}}"))
HX = E1.subalgebra(ele_names, key=E1.key)
HX = E1.subalgebra(ele_names, key_mo=E1.key_mo)
HX.reduce_rels()
self.assertEqual(15, len(HX.rels))

Expand Down

0 comments on commit 297e6f8

Please sign in to comment.