Skip to content

Commit 7cb3378

Browse files
hamishunGitHub Enterprise
authored andcommitted
algen (homenc#378)
1 parent 863e0ba commit 7cb3378

File tree

3 files changed

+439
-0
lines changed

3 files changed

+439
-0
lines changed

misc/algen/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Program for generating algebras for BGV.
2+
3+
## Contents in directory
4+
5+
* algen.py -- the program to generate BGV algebras.
6+
* numth.py -- utility functions.
7+
8+
Developed with Python 3.6.9, but may work on lower.
9+
10+
## Optional dependency
11+
12+
algen works faster if it uses Linux `factor` utility for factorizing integers.
13+
14+
On mac, you can add it with home brew. It gets installed as `gfactor` algen
15+
detects it.
16+
17+
## How to use
18+
19+
Run
20+
```
21+
./algen -h
22+
```
23+
for options.
24+
25+
But essentially, you are deciding which prime numbers `p` and how many
26+
polynomial coefficients you want in a slot `d`.
27+
28+
The nice thing is that you can provide ranges (inclusive that is [a,b]) as
29+
```
30+
./algen -p 2,5 -d 1,4
31+
```
32+
33+
## Future plan
34+
35+
Rewrite this in C++.

misc/algen/algen.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
#! /usr/bin/env python3
2+
3+
# Copyright (C) 2020 IBM Corp.
4+
# This program is Licensed under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance
6+
# with the License. You may obtain a copy of the License at
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License. See accompanying LICENSE file.
13+
14+
import re
15+
import sys
16+
import argparse
17+
import math
18+
import numth
19+
20+
def parseRange(rangeStr):
21+
"""
22+
Parses the ranges and numbers given to it.
23+
24+
Args:
25+
rangeStr: a string e.g. '2-5,7,10-11'
26+
Returns:
27+
A list of values to try out.
28+
Usage:
29+
>>> parseRange('2-5,7,10-11')
30+
[2, 3, 4, 5, 7, 10, 11]
31+
"""
32+
rangeList = rangeStr.split(',')
33+
34+
# Group1 regex for range
35+
# Group2 start number
36+
# Group3 end number
37+
# Group4 OR regex for single number
38+
# Group5 end number
39+
40+
regex = re.compile(r'^((\d+)\s*-\s*(\d+))|(\s*(\d+)\s*)$')
41+
42+
try:
43+
matches = [ regex.fullmatch(r).groups() for r in rangeList ]
44+
except AttributeError:
45+
raise argparse.ArgumentTypeError(\
46+
"Wrong syntax for range given '%s'. Correct example '2-5,7' " % rangeStr)
47+
48+
def buildRanges(match):
49+
matchRange, startRange, endRange, matchSingle, endSingle = match
50+
51+
if matchRange != None:
52+
if endRange < startRange:
53+
raise argparse.ArgumentTypeError(\
54+
"Range going from high to low '%s'" % matchRange)
55+
else:
56+
return range(int(startRange), int(endRange)+1)
57+
elif matchSingle != None:
58+
return range(int(endSingle), int(endSingle)+1)
59+
else:
60+
raise ValueError(\
61+
"Something went wrong generating a range with match '%s'"%(match, ) )
62+
63+
# Transform into a list of range objects
64+
ranges = list(map(buildRanges, matches))
65+
# Set Comprehension - guarantee uniqueness
66+
values = { x for r in ranges for x in r }
67+
# Convert to sorted list
68+
values = sorted(list(values))
69+
70+
return values
71+
72+
73+
def parsePrimeRange(rangeStr):
74+
"""
75+
Parses the ranges and numbers given to it, but only of primes.
76+
77+
Args:
78+
rangeStr: a string e.g. '2-5,7,10-11'
79+
Returns:
80+
A list of prime numbers to try out.
81+
Usage:
82+
>>> parsePrimeRange('2-5,7,10-11')
83+
[2, 3, 5, 7, 11]
84+
"""
85+
86+
p_prime = list(\
87+
filter(lambda x: numth.factorize(x)[x] == 1, \
88+
parseRange(rangeStr))\
89+
)
90+
if len(p_prime) == 0:
91+
raise argparse.ArgumentTypeError("No primes found in range given.")
92+
93+
return p_prime
94+
95+
96+
def algebras(m_maxs):
97+
"""
98+
Generator for the possible algebras.
99+
100+
Args:
101+
m_maxs: Maximum possible m values given p and d as a tuple e.g. (m, p, d).
102+
Returns:
103+
A 5-tuple ( m, p, d, phi_m, nSlots ) for the algebra.
104+
Usage:
105+
>>> print( '\\n'.join(map(str,algebras( ((24, 5, 2),) ))))
106+
(2, 5, 1, 1, 1)
107+
(4, 5, 1, 2, 2)
108+
(8, 5, 2, 4, 2)
109+
(3, 5, 2, 2, 1)
110+
(6, 5, 2, 2, 1)
111+
(12, 5, 2, 4, 2)
112+
(24, 5, 2, 8, 4)
113+
"""
114+
# Generate the divisors for each max_m
115+
for m_max,p,d in m_maxs:
116+
factors = numth.factorize(m_max)
117+
for mFactors in numth.divisorsFactors(factors):
118+
d_real = d
119+
m = numth.calcDivisor(mFactors)
120+
if m == 1:
121+
continue
122+
123+
phi_m = numth.phi(mFactors)
124+
125+
# Correct for order of p in mod m a.k.a. d
126+
phimFactors = numth.factorize(phi_m)
127+
for e in sorted(numth.divisors(phimFactors)):
128+
if p**e % m == 1:
129+
d_real = e
130+
break
131+
132+
nSlots, r = divmod(phi_m, d_real)
133+
if r != 0:
134+
raise ArithmeticError("Fractional nslots should not exist.")
135+
136+
# SolnObj just a 5-tuple ( m, p, d, phi_m, nSlots )
137+
yield (m,p,d_real,phi_m,nSlots)
138+
139+
140+
def printTable( headers, data, colwidths=None ):
141+
"""
142+
Print a nice table of possible algebras.
143+
144+
Args:
145+
headers: A list/tuple of strings of column headers.
146+
data: An iterable of list/tuple to print as the row data.
147+
colwidths: A list/tuple of column widths to be used (forced min. of 8 spaces).
148+
Default 12 spaces if None given.
149+
Returns:
150+
None.
151+
Usage:
152+
>>> printTable(('m','p','d','phi(m)','nSlots'),([(24,5,2,8,4),(12,5,2,4,2)]), [1,1,1,1,1])
153+
m p d phi(m) nSlots
154+
24 5 2 8 4
155+
12 5 2 4 2
156+
"""
157+
if colwidths == None:
158+
colwidths = [12]*len(headers)
159+
else:
160+
colwidths = [ width+2 if width > 8 else 8 for width in colwidths ]
161+
162+
for header, width in zip(headers, colwidths):
163+
print('{:{align}{width}}'.format(header, align='^', width=width), end='')
164+
print()
165+
166+
for row in data:
167+
for datum, width in zip(row, colwidths):
168+
print('{:{align}{width}}'.format(datum, align='>', width=width), end='')
169+
print()
170+
171+
172+
if __name__ == "__main__":
173+
174+
parser = argparse.ArgumentParser(description="")
175+
parser.add_argument("-p", type=parsePrimeRange, default="2",
176+
help="Prime number for plaintext space.")
177+
parser.add_argument("-d", type=parseRange, default="1",
178+
help="How many coefficients in a slot (the order of p in ZmStar).")
179+
parser.add_argument("--self-test", action="store_true",
180+
help="Run doctest on itself.")
181+
args = parser.parse_args()
182+
183+
# Run doctest instead of normal operation
184+
if args.self_test:
185+
import doctest
186+
print(doctest.testmod())
187+
exit(0)
188+
189+
# Generator for m_max(s)
190+
m_maxs = [ (p**d - 1, p, d) for p in args.p for d in args.d ]
191+
192+
# SolnObj just a 5-tuple ( m, p, d, phi_m, nSlots )
193+
solns = sorted( algebras(m_maxs) )
194+
195+
m_max = max(m_maxs)
196+
minWidths = [ math.ceil(math.log10(t)) for t in m_max ]
197+
minWidths.append(
198+
math.ceil(math.log10(numth.phi(numth.factorize(m_max[0])))))
199+
minWidths.append(minWidths[-1])
200+
201+
printTable( ('m','p','d','phi(m)','nSlots'), solns, minWidths)

0 commit comments

Comments
 (0)