Skip to content

Commit 424c5e4

Browse files
committed
initial commit
1 parent 2eb27ce commit 424c5e4

File tree

11 files changed

+555
-0
lines changed

11 files changed

+555
-0
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,34 @@
11
# modular-nested-exponentiation
2+
23
An algorithm that computes modular nested exponentiation efficiently
4+
5+
<table>
6+
<tr>
7+
<td>License</td>
8+
<td><img src='https://img.shields.io/pypi/l/mod-nest-exp.svg'></td>
9+
<td>Version</td>
10+
<td><img src='https://img.shields.io/pypi/v/mod-nest-exp.svg'></td>
11+
</tr>
12+
<tr>
13+
<td>Wheel</td>
14+
<td><img src='https://img.shields.io/pypi/wheel/mod-nest-exp.svg'></td>
15+
<td>Implementation</td>
16+
<td><img src='https://img.shields.io/pypi/implementation/mod-nest-exp.svg'></td>
17+
</tr>
18+
<tr>
19+
<td>Status</td>
20+
<td><img src='https://img.shields.io/pypi/status/mod-nest-exp.svg'></td>
21+
<td>Supported versions</td>
22+
<td><img src='https://img.shields.io/pypi/pyversions/mod-nest-exp.svg'></td>
23+
</tr>
24+
<tr>
25+
<td>Downloads</td>
26+
<td><img src='https://img.shields.io/pypi/dm/mod-nest-exp.svg'></td>
27+
</tr>
28+
</table>
29+
30+
## Generalised modular exponentiation
31+
32+
We present an algorithm that takes as input an arbitrarily long sequence of positive integers `a₁, a₂, a₃, ..., aₙ` and a positive integer `m` and computes `a₁^(a₂^(···^aₙ)) mod m` efficiently (that is, without computing the value of the nested exponent).
33+
34+
Without this algorithm, this type of computation is unfeasible for modern computers even for short input sequences containing small integers.

notebook.ipynb

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
{
2+
"metadata": {
3+
"language_info": {
4+
"codemirror_mode": {
5+
"name": "ipython",
6+
"version": 3
7+
},
8+
"file_extension": ".py",
9+
"mimetype": "text/x-python",
10+
"name": "python",
11+
"nbconvert_exporter": "python",
12+
"pygments_lexer": "ipython3",
13+
"version": "3.9.1-final"
14+
},
15+
"orig_nbformat": 2,
16+
"kernelspec": {
17+
"name": "python3",
18+
"display_name": "Python 3",
19+
"language": "python"
20+
}
21+
},
22+
"nbformat": 4,
23+
"nbformat_minor": 2,
24+
"cells": [
25+
{
26+
"source": [
27+
"# Summary\n",
28+
"\n",
29+
"We present an algorithm that takes as input an arbitrarily long sequence of positive integers $a_1,a_2,\\ldots,a_\\ell$ and a positive integer $m$ and computes\n",
30+
"$$a_1^{a_2^{\\cdot^{\\cdot^{a_\\ell}}}}\\bmod m$$\n",
31+
"efficiently (that is, without computing the value of the nested exponent)."
32+
],
33+
"cell_type": "markdown",
34+
"metadata": {}
35+
},
36+
{
37+
"source": [
38+
"# Notation\n",
39+
"\n",
40+
"For convenience, we define an operator $E$ as a shorthand for nested exponentiation.\n",
41+
"\n",
42+
"---\n",
43+
"\n",
44+
"> **Definition.** Given a tuple of $\\ell$ positive integers $(a_1,a_2,\\ldots,a_\\ell)$, define the operator $E$ recursively as follows.\n",
45+
"$$E(a_1,a_2,\\ldots,a_\\ell)=\\begin{cases}1&\\ell=0\\\\a_1^{E(a_2,\\ldots,a_\\ell)}&\\ell\\gt0\\end{cases}$$\n",
46+
"We call $a_1$ the **base** and $E(a_2,\\ldots,a_\\ell)$ the **exponent** of $E(a_1,a_2,\\ldots,a_\\ell)$.\n",
47+
"\n",
48+
"---\n",
49+
"\n",
50+
"We are interested in computing $E(a_1,a_2,\\ldots,a_\\ell)\\bmod m$."
51+
],
52+
"cell_type": "markdown",
53+
"metadata": {}
54+
},
55+
{
56+
"source": [
57+
"# Preliminaries"
58+
],
59+
"cell_type": "markdown",
60+
"metadata": {}
61+
},
62+
{
63+
"cell_type": "code",
64+
"execution_count": 1,
65+
"metadata": {},
66+
"outputs": [
67+
{
68+
"output_type": "stream",
69+
"name": "stdout",
70+
"text": [
71+
"Defaulting to user installation because normal site-packages is not writeable\n",
72+
"Requirement already satisfied: mod-nest-exp in /home/avbrook/.local/lib/python3.9/site-packages (1.1.0)\n"
73+
]
74+
}
75+
],
76+
"source": [
77+
"!python3 -m pip install mod-nest-exp"
78+
]
79+
},
80+
{
81+
"source": [
82+
"# The algorithm"
83+
],
84+
"cell_type": "markdown",
85+
"metadata": {}
86+
},
87+
{
88+
"source": [
89+
"## `pow_lt`\n",
90+
"\n",
91+
"`pow_lt` takes as input a sequence of positive integers $e_1,e_2,\\ldots,e_\\ell$ and a positive number $k$ and returns `True` iff $E(e_1,e_2,\\ldots,e_\\ell)\\lt k$."
92+
],
93+
"cell_type": "markdown",
94+
"metadata": {}
95+
},
96+
{
97+
"cell_type": "code",
98+
"execution_count": 2,
99+
"metadata": {},
100+
"outputs": [],
101+
"source": [
102+
"from decimal import Decimal\n",
103+
"from math import ceil\n",
104+
"\n",
105+
"def pow_lt(seq, k):\n",
106+
" if not len(seq): # if len(seq) == 0\n",
107+
" return 1 < k\n",
108+
"\n",
109+
" def _pow_lt(seq, k):\n",
110+
" if len(seq) == 1 or seq[0] == 1:\n",
111+
" return seq[0] < k\n",
112+
" if seq[1]*(seq[0].bit_length()-1) >= ceil(k).bit_length():\n",
113+
" return False\n",
114+
" l = Decimal(k).ln()/Decimal(seq[0]).ln() # high precision logarithm\n",
115+
" return _pow_lt(seq[1:], l) if l > 1 else False\n",
116+
"\n",
117+
" return _pow_lt(seq, k)"
118+
]
119+
},
120+
{
121+
"source": [
122+
"## `pow_list`\n",
123+
"\n",
124+
"`pow_list` takes as input a sequence of numbers $e_1,e_2,\\ldots,e_\\ell$ and returns the value of $E(e_1,e_2,\\ldots,e_\\ell)$."
125+
],
126+
"cell_type": "markdown",
127+
"metadata": {}
128+
},
129+
{
130+
"cell_type": "code",
131+
"execution_count": 3,
132+
"metadata": {},
133+
"outputs": [],
134+
"source": [
135+
"def pow_list(seq):\n",
136+
" l = len(seq)\n",
137+
" if not l: # if len(seq) == 0\n",
138+
" return 1\n",
139+
" elif l == 1: # if len(seq) == 1\n",
140+
" return seq[0]\n",
141+
"\n",
142+
" def _pow_list(seq):\n",
143+
" if seq[0] == 1:\n",
144+
" return 1\n",
145+
" if len(seq) == 2:\n",
146+
" return seq[0]**seq[1]\n",
147+
" return seq[0]**_pow_list(seq[1:])\n",
148+
" \n",
149+
" return _pow_list(seq)"
150+
]
151+
},
152+
{
153+
"source": [
154+
"## The main function\n",
155+
"\n",
156+
"`mod_nest_exp` takes as input a sequence of positive integers $a_1,a_2,\\ldots,a_\\ell$ and a positive integer $m$ and returns $E(a_1,a_2,\\ldots,a_\\ell)\\bmod m$."
157+
],
158+
"cell_type": "markdown",
159+
"metadata": {}
160+
},
161+
{
162+
"cell_type": "code",
163+
"execution_count": 4,
164+
"metadata": {},
165+
"outputs": [],
166+
"source": [
167+
"from gmpy2 import gcd, powmod, gcdext\n",
168+
"from sympy.ntheory import totient\n",
169+
"\n",
170+
"def mod_nest_exp(seq, m):\n",
171+
" if m == 1: # 1 divides every integer\n",
172+
" return 0\n",
173+
" l = len(seq)\n",
174+
" if not l: # if len(seq) == 0\n",
175+
" return 1%m\n",
176+
" elif l == 1: # if len(seq) == 1\n",
177+
" return seq[0]%m\n",
178+
"\n",
179+
" def _mod_nest_exp(seq, m):\n",
180+
" if m == 1: # 1 divides every integer\n",
181+
" return 0\n",
182+
" if len(seq) == 2: # recursive base case\n",
183+
" return powmod(seq[0], seq[1], m)\n",
184+
" \n",
185+
" b, e = seq[0], seq[1:] # base and exponent\n",
186+
" g = gcd(b, m)\n",
187+
" if g == 1:\n",
188+
" return powmod(b, _mod_nest_exp(e, totient(m)), m)\n",
189+
" \n",
190+
" n, k = m//g, 1\n",
191+
" g_ = gcd(g, n)\n",
192+
" while g_ > 1:\n",
193+
" n //= g_\n",
194+
" k += 1\n",
195+
" g_ = gcd(g, n)\n",
196+
" h = m//n\n",
197+
" _, x, y = gcdext(n, h)\n",
198+
" return (h*(y%n)*powmod(b, _mod_nest_exp(e, totient(n)), n)+\n",
199+
" n*(x%h)*(powmod(b, pow_list(e), h) if pow_lt(e, k) else 0))%m\n",
200+
" \n",
201+
" return _mod_nest_exp(seq, m)"
202+
]
203+
},
204+
{
205+
"cell_type": "code",
206+
"execution_count": 5,
207+
"metadata": {},
208+
"outputs": [
209+
{
210+
"output_type": "stream",
211+
"name": "stdout",
212+
"text": [
213+
"================================================================================\n",
214+
"Benchmarking mod_nest_exp (64-bit moduli)\n",
215+
"================================================================================\n",
216+
"10 bit numbers:\n",
217+
"\tlists of length 10\n",
218+
"\tstats from 1000 runs\n",
219+
"\t\tmean: 1.31e-02 seconds\n",
220+
"\t\tmedian: 2.67e-03 seconds\n",
221+
"\t\tstdev: 4.97e-02 seconds\n",
222+
"\tlists of length 100\n",
223+
"\tstats from 1000 runs\n",
224+
"\t\tmean: 1.22e-02 seconds\n",
225+
"\t\tmedian: 2.89e-03 seconds\n",
226+
"\t\tstdev: 4.64e-02 seconds\n",
227+
"\tlists of length 1000\n",
228+
"\tstats from 1000 runs\n",
229+
"\t\tmean: 1.52e-02 seconds\n",
230+
"\t\tmedian: 3.28e-03 seconds\n",
231+
"\t\tstdev: 5.12e-02 seconds\n",
232+
"100 bit numbers:\n",
233+
"\tlists of length 10\n",
234+
"\tstats from 1000 runs\n",
235+
"\t\tmean: 1.38e-02 seconds\n",
236+
"\t\tmedian: 2.90e-03 seconds\n",
237+
"\t\tstdev: 5.20e-02 seconds\n",
238+
"\tlists of length 100\n",
239+
"\tstats from 1000 runs\n",
240+
"\t\tmean: 9.73e-03 seconds\n",
241+
"\t\tmedian: 2.61e-03 seconds\n",
242+
"\t\tstdev: 3.39e-02 seconds\n",
243+
"\tlists of length 1000\n",
244+
"\tstats from 1000 runs\n",
245+
"\t\tmean: 1.32e-02 seconds\n",
246+
"\t\tmedian: 3.46e-03 seconds\n",
247+
"\t\tstdev: 6.01e-02 seconds\n",
248+
"1000 bit numbers:\n",
249+
"\tlists of length 10\n",
250+
"\tstats from 1000 runs\n",
251+
"\t\tmean: 1.74e-02 seconds\n",
252+
"\t\tmedian: 3.50e-03 seconds\n",
253+
"\t\tstdev: 6.42e-02 seconds\n",
254+
"\tlists of length 100\n",
255+
"\tstats from 1000 runs\n",
256+
"\t\tmean: 1.06e-02 seconds\n",
257+
"\t\tmedian: 3.31e-03 seconds\n",
258+
"\t\tstdev: 3.25e-02 seconds\n",
259+
"\tlists of length 1000\n",
260+
"\tstats from 1000 runs\n",
261+
"\t\tmean: 1.07e-02 seconds\n",
262+
"\t\tmedian: 3.20e-03 seconds\n",
263+
"\t\tstdev: 3.12e-02 seconds\n"
264+
]
265+
}
266+
],
267+
"source": [
268+
"!python3 tests/test_core.py"
269+
]
270+
}
271+
]
272+
}

setup.cfg

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[metadata]
2+
name = mod-nest-exp
3+
version = 1.0.2
4+
author = Aviv Brook
5+
author_email = avbrook@ucsc.edu
6+
description = An algorithm that computes modular nested exponentiation efficiently
7+
long_description = file: README.md
8+
long_description_content_type = text/markdown
9+
url = https://github.com/avivbrook/modular-nested-exponentiation
10+
classifiers =
11+
Programming Language :: Python :: 3
12+
Programming Language :: Python :: 3.6
13+
Programming Language :: Python :: 3.9
14+
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
15+
Topic :: Scientific/Engineering :: Mathematics
16+
Operating System :: OS Independent
17+
18+
[options]
19+
package_dir =
20+
= src
21+
packages = find:
22+
python_requires = >=3.6
23+
24+
[options.packages.find]
25+
where = src

setup.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import setuptools
2+
3+
with open('README.md', 'r', encoding='utf-8') as fh:
4+
long_description = fh.read()
5+
6+
setuptools.setup(
7+
name='mod-nest-exp',
8+
version='1.0.2',
9+
author='Aviv Brook',
10+
author_email='avbrook@ucsc.edu',
11+
description='An algorithm that computes modular nested exponentiation efficiently',
12+
long_description=long_description,
13+
long_description_content_type='text/markdown',
14+
url='https://github.com/avivbrook/modular-nested-exponentiation',
15+
classifiers=[
16+
'Programming Language :: Python :: 3',
17+
'Programming Language :: Python :: 3.6',
18+
'Programming Language :: Python :: 3.9',
19+
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
20+
'Topic :: Scientific/Engineering :: Mathematics',
21+
'Operating System :: OS Independent'
22+
],
23+
package_dir={'': 'src'},
24+
packages=setuptools.find_packages(where='src'),
25+
python_requires='>=3.6'
26+
)

src/mod_nest_exp/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .core import mod_nest_exp

0 commit comments

Comments
 (0)