Compare commits

..

No commits in common. "main" and "master" have entirely different histories.
main ... master

7 changed files with 622 additions and 3 deletions

145
.gitignore vendored Normal file
View file

@ -0,0 +1,145 @@
data
data.zip
*.kate-swp
tags.json
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

View file

@ -1,4 +1,9 @@
# p-adic-numbers
# p-adic-numbers
class for p-adic number fields
fork from https://github.com/meagtan/p-adic-numbers
fork from https://github.com/meagtan/p-adic-numbers
Thank you meagtan. He told me by mail, that I can release this project as GPL3+.
This repository is hosted and maintained on https://git.jdmweb2.ch/beat/p-adic-numbers
If you have questions feel free to send me an Email. My address is in the commits.

42
hensel.py Normal file
View file

@ -0,0 +1,42 @@
# Finding roots of polynomials in p-adic integers using Hensel's lemma
from padic import *
from poly import *
def roots(p, poly):
'Yield all roots of polynomial in the given p-adic integers.'
for root in xrange(p):
try:
yield PAdicPoly(p, poly, root)
except ValueError:
pass
class PAdicPoly(PAdic):
'Result of lifting a root of a polynomial in the integers mod p to the p-adic integers.'
def __init__(self, p, poly, root):
PAdic.__init__(self, p)
self.root = root
self.poly = poly
self.deriv = derivative(poly)
# argument checks for the algorithm to work
if poly(root) % p:
raise ValueError("%d is not a root of %s modulo %d" % (root, poly, p))
if self.deriv(root) % p == 0:
raise ValueError("Polynomial %s is not separable modulo %d" % (poly, p))
# take care of trailing zeros
digit = self.root
self.val = str(digit)
self.exp = self.p
while digit == 0:
self.order += 1
digit = self._nextdigit()
# self.prec += 1
def _nextdigit(self):
self.root = ModP(self.exp * self.p, self.root)
self.root = self.root - self.poly(self.root) / self.deriv(self.root) # coercions automatically taken care of
digit = self.root // self.exp
self.exp *= self.p
return digit

50
modp.py Normal file
View file

@ -0,0 +1,50 @@
class ModP(int):
'Integers mod p, p a prime power.'
def __new__(cls, p, num):
self = int.__new__(cls, int(num) % p)
self.p = p
return self
def __str__(self):
return "%d (mod %d)" % (self, self.p)
def __repr__(self):
return "%d %% %d" % (self, self.p)
# arithmetic
def __neg__(self):
return ModP(self.p, self.p - int(self))
def __add__(self, other):
return ModP(self.p, int(self) + int(other))
def __radd__(self, other):
return ModP(self.p, int(other) + int(self))
def __sub__(self, other):
return ModP(self.p, int(self) - int(other))
def __rsub__(self, other):
return ModP(self.p, int(other) - int(self))
def __mul__(self, other):
return ModP(self.p, int(self) * int(other))
def __rmul__(self, other):
return ModP(self.p, int(other) * int(self))
def __div__(self, other):
if not isinstance(other, ModP):
other = ModP(self.p, other)
return self * other._inv()
def __rdiv__(self, other):
return other * self._inv()
def _inv(self):
'Find multiplicative inverse of self in Z mod p.'
# extended Euclidean algorithm
rcurr = self.p
rnext = int(self)
tcurr = 0
tnext = 1
while rnext:
q = rcurr // rnext
rcurr, rnext = rnext, rcurr - q * rnext
tcurr, tnext = tnext, tcurr - q * tnext
if rcurr != 1:
raise ValueError("%d not a unit modulo %d" % (self, self.p))
return ModP(self.p, tcurr)

271
padic.py Normal file
View file

@ -0,0 +1,271 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# License: GLP3+
# Copyright 2017 meagtan
# Copyright 2022 Beat Jäckle <beat@git.jdmweb2.ch>
from fractions import Fraction
from sys import maxsize
from modp import ModP
class PAdic:
def __init__(self, p, printInvers=False):
self.p = p
# current known value
self.digitP = [] # digitP[0]=c_0; digitP[i]=c_i
self.digitN = [] # digitN[0]=c_-1; digitN[i]=c_-1-i
self.prec = 0 # current known precision, not containing trailing zeros
self.order = 0 # order/valuation of number
self.printInvers = printInvers
return None
def calc(self, prec):
# First calculation
if len(self.digitN) + len(self.digitP) == 0:
if self.value == 0:
self.zero = True
self.digitP = [0]
return None
if self.order < 0:
absorder = abs(self.order)
self.digitN = [None]*absorder
for i in range(absorder):
self.digitN[absorder-i-1] = self._nextdigit()
if absorder >= prec:
self.prec = absorder
self.digitP.append(self._nextdigit())
elif self.order > 0:
self.digitP = [0]*self.order
while len(self.digitN) + len(self.digitP) < prec:
if self.value == 0:
break
self.digitP.append(self._nextdigit())
return None
def get(self, prec, decimal=True):
self.calc(prec)
'Return value of number with given precision.'
s = ''
for d in self.digitP[::-1]:
s += str(d)
if self.digitN:
if decimal:
s += '.'
for d in self.digitN:
s += str(d)
return s
def _nextdigit(self):
'Calculate next digit of p-adic number.'
raise NotImplementedError
def getdigit(self, index):
if index < self.order:
# print('not relevant 0')
return 0
# be sure to calculate the digits
prec = index+1
if self.order < 0:
prec += abs(self.order)
self.calc(prec)
# negative index
if index < 0:
try:
return self.digitN[abs(index)-1]
except IndexError:
print('Should not happen', index)
print('__dict__', self.__dict__())
raise IndexError(
f"Digit not Found\n"
f"index: {index}\n"
f"dict: {self.__dict__()}"
)
# positive index
else:
try:
return self.digitP[index]
except IndexError:
if self.value == 0:
# print('not significant',0)
return 0
else:
print('Logical Error')
print(f'Index = {index}')
print(f'pAdic = {self.__dict__()}')
raise ValueError('Logical Error')
# return value with precision up to 32 bits
def __int__(self):
return int(self.get(32), self.p)
def __getitem__(self, index):
return self.getdigit(index)
def __str__(self):
if self.printInvers:
return self.get(32)[::-1]
return self.get(32)
def __repr__(self):
return str(self)
# arithmetic operations
def __neg__(self):
return PAdicNeg(self.p, self)
def __add__(self, other):
return PAdicAdd(self.p, self, other)
def __radd__(self, other):
return PAdicAdd(self.p, other, self)
def __sub__(self, other):
return PAdicAdd(self.p, self, PAdicNeg(self.p, other))
def __rsub__(self, other):
return PAdicAdd(self.p, other, PAdicNeg(self.p, self))
def __mul__(self, other):
return PAdicMul(self.p, self, other)
def __rmul__(self, other):
return PAdicMul(self.p, other, self)
# p-adic norm
def __abs__(self):
if self.order == maxsize:
return 0
numer = denom = 1
if self.order > 0:
numer = self.p ** self.order
if self.order < 0:
denom = self.p ** self.order
return Fraction(numer, denom)
# determines the p-value of an p-adic number
def pValue(self):
return self.order
def fractionvalue(self):
self.calc(1)
s = Fraction(0)
for (i, d) in enumerate([self.digitP[0]]+self.digitN):
s += Fraction(d, self.p**i)
return s
def getOffset(self):
if self.order >= 0:
return 0
else:
return -self.order
class PAdicConst(PAdic):
def __init__(self, p, value, printInvers=False):
super().__init__(p, printInvers)
value = Fraction(value)
# calculate valuation
if value == 0:
self.value = value
self.order = float('inf')
self.zero = True
return None
while not value.numerator % self.p:
self.order += 1
value /= self.p
while not value.denominator % self.p:
self.order -= 1
value *= self.p
self.value = value
return None
def get(self, prec, decimal=True):
if self.zero:
return '0' * prec
return PAdic.get(self, prec, decimal)
def _nextdigit(self):
'Calculate next digit of p-adic number.'
rem = int(
ModP(self.p, self.value.numerator) *
ModP(self.p, self.value.denominator)._inv()
)
self.value -= rem
self.value /= self.p
return rem
class PAdicAdd(PAdic):
'Sum of two p-adic numbers.'
def __init__(self, p, arg1, arg2, printInvers=False):
super.__init__(self, p, printInvers)
self.carry = 0
_nextdigitCache = []
self.arg1 = arg1
self.arg2 = arg2
# might be larger than this
self.order = self.prec = min(arg1.order, arg2.order)
arg1.order -= self.order
arg2.order -= self.order
# loop until first nonzero digit is found
self.index = self.order
digit = self._nextdigit()
while digit == 0:
self.index += 1
digit = self._nextdigit()
self.order = self.index
_nextdigitCache.append(digit)
def _nextdigit(self):
if _nextdigitCache:
return _nextdigitCache.pop()
s = self.arg1.getdigit(self.index) + \
self.arg2.getdigit(self.index) + self.carry
self.carry = s // self.p
self.index += 1
return s % self.p
class PAdicNeg(PAdic):
'Negation of a p-adic number.'
def __init__(self, p, arg, printInvers=False):
super.__init__(self, p, printInvers)
self.arg = arg
self.order = arg.order
def _nextdigit(self):
if self.prec == 0:
# cannot be p, 0th digit of arg must be nonzero
return self.p - self.arg.getdigit(0 - self.getOffset())
return self.p - 1 - self.arg.getdigit(self.prec - self.getOffset())
class PAdicMul(PAdic):
'Product of two p-adic numbers.'
def __init__(self, p, arg1, arg2):
PAdic.__init__(self, p)
self.carry = 0
self.arg1 = arg1
self.arg2 = arg2
self.order = arg1.order + arg2.order
self.arg1.order = self.arg2.order = 0 # TODO requires copy
self.index = 0
def _nextdigit(self):
s = sum(
self.arg1.getdigit(i) * self.arg2.getdigit(self.index - i)
for i in xrange(self.index + 1)
) + self.carry
self.carry = s // self.p
self.index += 1
return s % self.p

80
poly.py Normal file
View file

@ -0,0 +1,80 @@
# Polynomial class on Z or Z_p
from collections import defaultdict
class Poly:
'Polynomial class.'
def __init__(self, coeffs = None):
self.coeffs = defaultdict(int, isinstance(coeffs, int) and {0:coeffs} or coeffs or {})
self.deg = int(len(self.coeffs) and max(self.coeffs.keys()))
def __call__(self, val):
'Evaluate polynomial for a given value.'
res = 0
for i in xrange(self.deg, -1, -1):
res = res * val + self.coeffs[i]
return res
def __str__(self):
def term(coeff, expt):
if coeff == 1 and expt == 0:
return '1'
return ' * '.join(([] if coeff == 1 else [str(coeff)]) + \
([] if expt == 0 else ['X'] if expt == 1 else ['X ** %d' % expt]))
return ' + '.join(term(self.coeffs[i], i) for i in self.coeffs if self.coeffs[i] != 0)
def __repr__(self):
return str(self)
# arithmetic
def __neg__(self):
return Poly({(i, -self.coeffs[i]) for i in self.coeffs})
def __add__(self, other):
if not isinstance(other, Poly):
other = Poly(other)
res = Poly()
res.deg = max(self.deg, other.deg)
for i in xrange(res.deg+1):
res.coeffs[i] = self.coeffs[i] + other.coeffs[i]
return res
def __radd__(self, other):
if not isinstance(other, Poly):
other = Poly(other)
return other.__add__(self)
def __sub__(self, other):
if not isinstance(other, Poly):
other = Poly(other)
return self.__add__(other.__neg__())
def __rsub__(self, other):
if not isinstance(other, Poly):
other = Poly(other)
return other.__add__(self.__neg__())
def __mul__(self, other):
if not isinstance(other, Poly):
other = Poly(other)
res = Poly()
res.deg = self.deg + other.deg # consider case where other is 0
for i in xrange(res.deg+1):
for j in xrange(i+1):
res.coeffs[i] += self.coeffs[j] * other.coeffs[i - j]
return res
def __rmul__(self, other):
if not isinstance(other, Poly):
other = Poly(other)
return other.__mul__(self)
def __pow__(self, other):
if not isinstance(other, int) or other < 0:
raise ValueError("Exponent %d is not a natural number" % other)
res = Poly(1)
while other:
res *= self
other -= 1
return res
X = Poly({1:1})
def derivative(p):
'Return derivative of polynomial.'
return Poly({(i - 1, i * p.coeffs[i]) for i in p.coeffs if i != 0})

26
pyproject.toml Normal file
View file

@ -0,0 +1,26 @@
[build-system]
requires = ["setuptools >= 58.0"]
build-backend = "setuptools.build_meta"
[project]
name = "padic"
version = "0.0.2"
authors = [
{ name="Beat Jäckle", email="beat@git.jdmweb2.ch" },
]
description = "class for p-adic number fields"
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GPL3",
"Operating System :: OS Independent",
]
[project.urls]
"Homepage" = "https://git.jdmweb2.ch/beat/p-adic-numbers"
"Bug Tracker" = "https://git.jdmweb2.ch/beat/p-adic-numbers/issues"
[tool.setuptools]
py-modules = ['hensel', 'modp', 'poly', 'padic']