From bc4396095e0efb683bcc579c45c4c7527574a4b0 Mon Sep 17 00:00:00 2001 From: Ludo Pulles Date: Fri, 14 Jun 2024 15:21:08 +0200 Subject: [PATCH 1/4] Change choice of q in qary-lattice generation Let random lattice generation use the methods changed in fplll/fplll#526. Note: adds support for values of 'q' above 2^31 when calling IntegerMatrix.random with methods 'ntrulike', 'ntrulike2' and 'qary'. --- src/fpylll/fplll/fplll.pxd | 10 +++++----- src/fpylll/fplll/integer_matrix.pyx | 29 +++++++++++++++++++---------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/fpylll/fplll/fplll.pxd b/src/fpylll/fplll/fplll.pxd index c5b7b1d7..907a4450 100644 --- a/src/fpylll/fplll/fplll.pxd +++ b/src/fpylll/fplll/fplll.pxd @@ -388,11 +388,11 @@ cdef extern from "fplll/nr/matrix.h" namespace "fplll": void gen_intrel(int bits) nogil void gen_simdioph(int bits, int bits2) nogil void gen_uniform(int bits) nogil - void gen_ntrulike(int bits) nogil - void gen_ntrulike_withq(int q) nogil - void gen_ntrulike2(int bits) nogil - void gen_ntrulike2_withq(int q) nogil - void gen_qary_withq(int k, int q) nogil + void gen_ntrulike(const Z_NR[T] &q) nogil + void gen_ntrulike_bits(int bits) nogil + void gen_ntrulike2(const Z_NR[T] &q) nogil + void gen_ntrulike2_bits(int bits) nogil + void gen_qary(int k, const Z_NR[T] &q) nogil void gen_qary_prime(int k, int bits) nogil void gen_trg(double alpha) nogil diff --git a/src/fpylll/fplll/integer_matrix.pyx b/src/fpylll/fplll/integer_matrix.pyx index b0682b8a..2f35fbeb 100644 --- a/src/fpylll/fplll/integer_matrix.pyx +++ b/src/fpylll/fplll/integer_matrix.pyx @@ -979,6 +979,9 @@ cdef class IntegerMatrix: :seealso: :func:`~IntegerMatrix.random` """ + cdef Z_NR[mpz_t] t_mpz + cdef Z_NR[long] t_long + if algorithm == "intrel": bits = int(kwds["bits"]) sig_on() @@ -1018,9 +1021,11 @@ cdef class IntegerMatrix: q = int(kwds["q"]) sig_on() if self._type == ZT_MPZ: - self._core.mpz.gen_ntrulike_withq(q) + assign_Z_NR_mpz(t_mpz, q) + self._core.mpz.gen_ntrulike(t_mpz) elif self._type == ZT_LONG: - self._core.long.gen_ntrulike_withq(q) + t_long = q + self._core.long.gen_ntrulike(t_long) else: raise RuntimeError("Integer type '%s' not understood."%self._type) sig_off() @@ -1028,9 +1033,9 @@ cdef class IntegerMatrix: bits = int(kwds["bits"]) sig_on() if self._type == ZT_MPZ: - self._core.mpz.gen_ntrulike(bits) + self._core.mpz.gen_ntrulike_bits(bits) elif self._type == ZT_LONG: - self._core.long.gen_ntrulike(bits) + self._core.long.gen_ntrulike_bits(bits) else: raise RuntimeError("Integer type '%s' not understood."%self._type) sig_off() @@ -1042,9 +1047,11 @@ cdef class IntegerMatrix: q = int(kwds["q"]) sig_on() if self._type == ZT_MPZ: - self._core.mpz.gen_ntrulike2_withq(q) + assign_Z_NR_mpz(t_mpz, q) + self._core.mpz.gen_ntrulike2(t_mpz) elif self._type == ZT_LONG: - self._core.long.gen_ntrulike2_withq(q) + t_long = q + self._core.long.gen_ntrulike2(t_long) else: raise RuntimeError("Integer type '%s' not understood."%self._type) sig_off() @@ -1052,9 +1059,9 @@ cdef class IntegerMatrix: bits = int(kwds["bits"]) sig_on() if self._type == ZT_MPZ: - self._core.mpz.gen_ntrulike2(bits) + self._core.mpz.gen_ntrulike2_bits(bits) elif self._type == ZT_LONG: - self._core.long.gen_ntrulike2(bits) + self._core.long.gen_ntrulike2_bits(bits) else: raise RuntimeError("Integer type '%s' not understood."%self._type) sig_off() @@ -1067,9 +1074,11 @@ cdef class IntegerMatrix: q = int(kwds["q"]) sig_on() if self._type == ZT_MPZ: - self._core.mpz.gen_qary_withq(k, q) + assign_Z_NR_mpz(t_mpz, q) + self._core.mpz.gen_qary(k, t_mpz) elif self._type == ZT_LONG: - self._core.long.gen_qary_withq(k, q) + t_long = q + self._core.long.gen_qary(k, t_long) else: raise RuntimeError("Integer type '%s' not understood."%self._type) sig_off() From 5e16a94815a6124937c0a26e355bbf7caddd39ad Mon Sep 17 00:00:00 2001 From: Ludo Pulles Date: Fri, 14 Jun 2024 16:30:59 +0200 Subject: [PATCH 2/4] Generate matrix using bits >= 1. - Setting bits >= 1 prevents a SignalError in IntegerMatrix.randomize. - Add assertion for the check introduced in commit b0d4ca43b8e66e7fcc3f63519c85b1b261f4defc . --- tests/test_gso.py | 23 +++++++++-------------- tests/test_random.py | 8 ++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/test_gso.py b/tests/test_gso.py index 8f881e31..3c68edd6 100644 --- a/tests/test_gso.py +++ b/tests/test_gso.py @@ -18,7 +18,7 @@ def make_integer_matrix(m, n, int_type="mpz"): A = IntegerMatrix(m, n, int_type=int_type) - A.randomize("qary", k=m//2, bits=m) + A.randomize("qary", k=m//2, bits=max(1, m)) return A @@ -94,9 +94,9 @@ def test_gso_update_gso(): g00.append(M.get_gram(0, 0)) for i in range(1, len(r00)): - assert r00[0] == pytest.approx(r00[i], rel=EPSILON) - assert re00[0] == pytest.approx(re00[i], rel=EPSILON) - assert g00[0] == pytest.approx(g00[i], rel=EPSILON) + assert r00[0] == pytest.approx(r00[i], rel=EPSILON) + assert re00[0] == pytest.approx(re00[i], rel=EPSILON) + assert g00[0] == pytest.approx(g00[i], rel=EPSILON) def test_gso_babai(): @@ -145,11 +145,10 @@ def test_gso_coherence_gram_matrix(): Test if the GSO is coherent if it is given a matrix A or its associated Gram matrix A*A^T """ - EPSILON = 0.0001 - for m, n in dimensions: - for int_type in int_types: + for int_type in int_types: + for m, n in dimensions: # long is not tested for high dimensions because of integer overflow if m > 20 and int_type == "long": continue @@ -171,12 +170,8 @@ def test_gso_coherence_gram_matrix(): # Check if computations coincide for i in range(m): - M_A.get_r(i, i) == pytest.approx(M_G.get_r(i, j), rel=EPSILON) + assert M_A.get_r(i, i) == pytest.approx(M_G.get_r(i, i), rel=EPSILON) for j in range(i): - assert ( - M_A.get_r(i, j) == pytest.approx(M_G.get_r(i, j), rel=EPSILON) - ) - assert ( - M_A.get_mu(i, j) == pytest.approx(M_G.get_mu(i, j), rel=EPSILON) - ) + assert M_A.get_r(i, j) == pytest.approx(M_G.get_r(i, j), rel=EPSILON) + assert M_A.get_mu(i, j) == pytest.approx(M_G.get_mu(i, j), rel=EPSILON) diff --git a/tests/test_random.py b/tests/test_random.py index d9dc3925..d3804576 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +from cysignals.signals import SignalError +from pytest import raises + from fpylll import IntegerMatrix, FPLLL @@ -9,6 +12,11 @@ def make_integer_matrix(m, n, int_type="mpz"): return A +def test_zero_bits(): + with raises(SignalError): + IntegerMatrix.random(10, "qary", k=5, bits=0) + + def test_randomize(): FPLLL.set_random_seed(1337) A0 = make_integer_matrix(20, 20) From c2c14797fc471677ad9a584a9662c71d7e354f94 Mon Sep 17 00:00:00 2001 From: Ludo Pulles Date: Fri, 14 Jun 2024 16:37:43 +0200 Subject: [PATCH 3/4] Ignore .so build files of Cython --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6f8c0cc5..6c216158 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,11 @@ target/ /.vagrant/ /Vagrantfile + +# Ignore any build files +/src/fpylll/*.so +/src/fpylll/*/*.so + /src/fpylll/config.pxi /.ipynb_checkpoints/*.ipynb /MANIFEST @@ -62,4 +67,4 @@ target/ /.pytest_cache/ /activate /fpylll-env -/fpylll-fplll \ No newline at end of file +/fpylll-fplll From 38fb8ed7a9dc8950bdd74d6c5c96b1341307a029 Mon Sep 17 00:00:00 2001 From: Ludo Pulles Date: Fri, 14 Jun 2024 17:24:25 +0200 Subject: [PATCH 4/4] Update doctest --- docs/tutorial.rst | 24 ++++++++++++------------ src/fpylll/fplll/enumeration.pyx | 2 +- src/fpylll/fplll/gso.pyx | 22 +++++++++++----------- src/fpylll/fplll/pruner.pyx | 6 ++++-- src/fpylll/tools/bkz_simulator.py | 18 +++++++++--------- src/fpylll/tools/quality.py | 8 ++++---- 6 files changed, 41 insertions(+), 39 deletions(-) diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 486d5534..27ee40de 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -138,10 +138,10 @@ To compute the LLL reduced matrix of :math:`{\bf A}` >>> A_original = copy(A) >>> A_lll = LLL.reduction(A) >>> print(A_lll) - [ -1 9 -5 -3 ] - [ 12 -2 7 -17 ] - [ -18 3 16 -1 ] - [ 4 17 20 12 ] + [ -3 21 -15 -23 ] + [ -10 70 -50 111 ] + [ -94 95 93 30 ] + [ 238 23 64 -52 ] To test if a matrix is LLL-reduced @@ -162,10 +162,10 @@ For the BKZ reduction of :math:`{\bf A}` with blocksize say 3 (without pruning), >>> A.randomize("qary", bits=10, k=3) >>> A_bkz = BKZ.reduction(A, BKZ.Param(block_size)) >>> print(A_bkz) - [ -1 9 -5 -3 ] - [ 12 -2 7 -17 ] - [ -18 3 16 -1 ] - [ 4 17 20 12 ] + [ -3 21 -15 -23 ] + [ -10 70 -50 111 ] + [ -94 95 93 30 ] + [ 238 23 64 -52 ] If we want to use pruning we can use the default pruning of fplll [GNR10]_. @@ -188,7 +188,7 @@ use it from the GSO tool detailed above >>> _ = M.update_gso() >>> w = M.babai([1, 17, -3, -75, 102]) >>> A.multiply_left(w) - (-4, 16, -5, -78, 97) + (-6, 12, -19, -71, 98) To compute the norm of a shortest vector of the lattice generated by the rows of the matrix :math:`{\bf A}` we use the ``shortest_vector`` method of the SVP class, and measure the first row of the resulting matrix :math:`{\bf A}` @@ -196,11 +196,11 @@ To compute the norm of a shortest vector of the lattice generated by the rows of >>> from fpylll import SVP >>> SVP.shortest_vector(A) - (2, -2, 7, 4, -1) + (-3, 0, 21, -15, -23) >>> print(A[0]) - (2, -2, 7, 4, -1) + (-3, 0, 21, -15, -23) >>> A[0].norm() - 8.602325267042627 + 34.698703... For the Closest Vector Problem, fplll (and so fpylll) uses enumeration:: diff --git a/src/fpylll/fplll/enumeration.pyx b/src/fpylll/fplll/enumeration.pyx index c14c4d61..9c2e96a7 100644 --- a/src/fpylll/fplll/enumeration.pyx +++ b/src/fpylll/fplll/enumeration.pyx @@ -535,7 +535,7 @@ cdef class Enumeration: >>> enum = Enumeration(M, strategy=EvaluatorStrategy.BEST_N_SOLUTIONS, sub_solutions=True) >>> _ = enum.enumerate(0, 30, 0.999*M.get_r(0, 0), 0, pruning=pruning.coefficients) >>> [int(round(a)) for a,b in enum.sub_solutions[:5]] - [5569754193, 5556022462, 5083806188, 5022873440, 4260865083] + [13018980230, 12980748618, 12469398480, 10737191842, 10723577014] """ cdef list sub_solutions = [] diff --git a/src/fpylll/fplll/gso.pyx b/src/fpylll/fplll/gso.pyx index e42a00b2..5a256ade 100644 --- a/src/fpylll/fplll/gso.pyx +++ b/src/fpylll/fplll/gso.pyx @@ -458,19 +458,19 @@ cdef class MatGSO: >>> M = GSO.Mat(A, flags=GSO.INT_GRAM); _ = M.update_gso() >>> G = M.G >>> print(G) - [ 2176 0 0 0 0 0 0 0 0 0 ] - [ 1818 4659 0 0 0 0 0 0 0 0 ] - [ 2695 5709 7416 0 0 0 0 0 0 0 ] - [ 2889 5221 7077 7399 0 0 0 0 0 0 ] - [ 2746 3508 4717 4772 4618 0 0 0 0 0 ] - [ 2332 1590 2279 2332 2597 2809 0 0 0 0 ] - [ 265 1749 2491 2438 0 0 2809 0 0 0 ] - [ 159 265 212 1219 318 0 0 2809 0 0 ] - [ 742 636 1537 2067 1802 0 0 0 2809 0 ] - [ 159 2650 2650 1908 1696 0 0 0 0 2809 ] + [ 822597 0 0 0 0 0 0 0 0 0 ] + [ 357490 391403 0 0 0 0 0 0 0 0 ] + [ 474377 396238 635122 0 0 0 0 0 0 0 ] + [ 503594 382116 396118 448816 0 0 0 0 0 0 ] + [ 555245 288280 463386 393750 495338 0 0 0 0 0 ] + [ 313028 6756 146380 121045 286004 316969 0 0 0 0 ] + [ 2815 136246 304583 104718 163270 0 316969 0 0 0 ] + [ 215629 24209 28150 106407 90080 0 0 316969 0 0 ] + [ 287693 210562 276996 164396 139061 0 0 0 316969 0 ] + [ 182975 246031 97962 279811 145254 0 0 0 0 316969 ] >>> A[0].norm()**2 - 2176.0 + 822597.000... >>> M = GSO.Mat(G, gram=True); _ = M.update_gso() >>> G == M.G diff --git a/src/fpylll/fplll/pruner.pyx b/src/fpylll/fplll/pruner.pyx index 0348ed20..64aa8800 100644 --- a/src/fpylll/fplll/pruner.pyx +++ b/src/fpylll/fplll/pruner.pyx @@ -12,10 +12,12 @@ EXAMPLE:: >>> radius = sum([m.get_r(0, 0) for m in M])/len(M) >>> pr = Pruning.run(radius, 10000, [m.r() for m in M], 0.4) >>> print(pr) # doctest: +ELLIPSIS - PruningParams<7.797437, (1.00,...,0.80), 0.6594> + PruningParams<1.397930, (1.00,...,0.43), 0.4055> + >>> print(Pruning.run(M[0].get_r(0, 0), 2**20, [m.r() for m in M], 0.9, pruning=pr)) - PruningParams<1.001130, (1.00,...,0.98), 0.9410> + PruningParams<1.437235, (1.00,...,0.98), 0.9410> + .. moduleauthor:: Martin R. Albrecht diff --git a/src/fpylll/tools/bkz_simulator.py b/src/fpylll/tools/bkz_simulator.py index bccbdca9..c4ec0376 100644 --- a/src/fpylll/tools/bkz_simulator.py +++ b/src/fpylll/tools/bkz_simulator.py @@ -108,10 +108,10 @@ def simulate(r, param): >>> M = GSO.Mat(A) >>> from fpylll.tools.bkz_simulator import simulate >>> _ = simulate(M, BKZ.Param(block_size=40, max_loops=4, flags=BKZ.VERBOSE)) - {"i": 0, "r_0": 2^33.3, "r_0/gh": 6.110565, "rhf": 1.018340, "/": -0.07013, "hv/hv": 2.424131} - {"i": 1, "r_0": 2^32.7, "r_0/gh": 4.018330, "rhf": 1.016208, "/": -0.06161, "hv/hv": 2.156298} - {"i": 2, "r_0": 2^32.3, "r_0/gh": 2.973172, "rhf": 1.014679, "/": -0.05745, "hv/hv": 2.047014} - {"i": 3, "r_0": 2^32.1, "r_0/gh": 2.583479, "rhf": 1.013966, "/": -0.05560, "hv/hv": 2.000296} + {"i": 0, "r_0": 2^34.7, "r_0/gh": 5.547855, "rhf": 1.017849, "/": -0.06928, "hv/hv": 2.406481} + {"i": 1, "r_0": 2^34.2, "r_0/gh": 3.894188, "rhf": 1.016049, "/": -0.06136, "hv/hv": 2.150078} + {"i": 2, "r_0": 2^33.8, "r_0/gh": 2.949459, "rhf": 1.014638, "/": -0.05735, "hv/hv": 2.044402} + {"i": 3, "r_0": 2^33.6, "r_0/gh": 2.574565, "rhf": 1.013949, "/": -0.05556, "hv/hv": 1.999163} """ r = _extract_log_norms(r) @@ -190,10 +190,10 @@ def simulate_prob(r, param, prng_seed=0xdeadbeef): >>> M = GSO.Mat(A) >>> from fpylll.tools.bkz_simulator import simulate_prob >>> _ = simulate_prob(M, BKZ.Param(block_size=40, max_loops=4, flags=BKZ.VERBOSE)) - {"i": 0, "r_0": 2^33.1, "r_0/gh": 5.193166, "rhf": 1.017512, "/": -0.07022, "hv/hv": 2.428125} - {"i": 1, "r_0": 2^32.7, "r_0/gh": 3.997766, "rhf": 1.016182, "/": -0.06214, "hv/hv": 2.168460} - {"i": 2, "r_0": 2^32.3, "r_0/gh": 3.020156, "rhf": 1.014759, "/": -0.05808, "hv/hv": 2.059562} - {"i": 3, "r_0": 2^32.2, "r_0/gh": 2.783102, "rhf": 1.014344, "/": -0.05603, "hv/hv": 2.013191} + {"i": 0, "r_0": 2^34.5, "r_0/gh": 4.714937, "rhf": 1.017021, "/": -0.06936, "hv/hv": 2.410445} + {"i": 1, "r_0": 2^34.2, "r_0/gh": 3.874259, "rhf": 1.016023, "/": -0.06189, "hv/hv": 2.162205} + {"i": 2, "r_0": 2^33.8, "r_0/gh": 2.996068, "rhf": 1.014718, "/": -0.05798, "hv/hv": 2.056934} + {"i": 3, "r_0": 2^33.7, "r_0/gh": 2.773499, "rhf": 1.014326, "/": -0.05598, "hv/hv": 2.012050} """ if param.block_size <= 2: @@ -301,7 +301,7 @@ def averaged_simulate_prob(L, param, tries=10): >>> from fpylll.tools.bkz_simulator import averaged_simulate_prob >>> _ = averaged_simulate_prob(M, BKZ.Param(block_size=40, max_loops=4)) >>> print(_[0][:3]) - [4663149828.487..., 4267813469.1884..., 4273411937.5775...] + [13371442256.252..., 12239031147.433..., 12256303707.863...] """ if tries < 1: raise ValueError("Need to average over positive number of tries.") diff --git a/src/fpylll/tools/quality.py b/src/fpylll/tools/quality.py index eee47a14..4ab056a2 100644 --- a/src/fpylll/tools/quality.py +++ b/src/fpylll/tools/quality.py @@ -19,9 +19,9 @@ def get_current_slope(r, start_row=0, stop_row=-1): >>> M = GSO.Mat(A); _ = M.update_gso() >>> from fpylll.tools.quality import get_current_slope >>> M.get_current_slope(0, 100) # doctest: +ELLIPSIS - -0.085500625... + -0.083173398... >>> get_current_slope(M.r(), 0, 100) # doctest: +ELLIPSIS - -0.085500625... + -0.083173398... """ x = [log(r[i]) for i in range(start_row, stop_row)] @@ -69,9 +69,9 @@ def basis_quality(M): >>> from fpylll.tools.quality import basis_quality >>> from fpylll.tools.bkz_stats import pretty_dict >>> str(pretty_dict(basis_quality(M))) - '{"r_0": 2^34.0, "r_0/gh": 9.389811, "rhf": 1.020530, "/": -0.08550, "hv/hv": 2.940943}' + '{"r_0": 2^35.3, "r_0/gh": 8.564671, "rhf": 1.020061, "/": -0.08317, "hv/hv": 2.832300}' >>> str(pretty_dict(basis_quality(M.r()))) - '{"r_0": 2^34.0, "r_0/gh": 9.389811, "rhf": 1.020530, "/": -0.08550, "hv/hv": 2.940943}' + '{"r_0": 2^35.3, "r_0/gh": 8.564671, "rhf": 1.020061, "/": -0.08317, "hv/hv": 2.832300}' """