├── .gitignore ├── LICENSE ├── README.md ├── _test ├── __init__.py ├── __main__.py ├── algorithm_test.py ├── elliptic_curves_group_test.py ├── finite_fields_test.py ├── fraction_fields_test.py ├── polynomial_rings_test.py ├── quotient_rings_test.py └── support_test.py ├── doc ├── Doxyfile ├── doxypy-0.4.2 │ ├── PKG-INFO │ ├── doxypy.py │ └── setup.py └── quick_tags ├── elliptic_curves ├── __init__.py ├── division_polynomials │ ├── __init__.py │ └── naive.py ├── l_torsion_group │ ├── __init__.py │ └── naive.py ├── naive.py └── polynomials │ ├── __init__.py │ └── naive.py ├── fields ├── __init__.py ├── finite │ ├── __init__.py │ └── naive.py └── fraction │ ├── __init__.py │ └── naive.py ├── naive_schoof.py ├── reduced_computation_schoof.py ├── rings ├── __init__.py ├── integers │ ├── __init__.py │ └── naive.py ├── polynomials │ ├── __init__.py │ └── naive.py └── quotients │ ├── __init__.py │ └── naive.py ├── support ├── __init__.py ├── operators.py ├── primes.py ├── profiling.py ├── quotients.py ├── rings.py ├── running.py └── types.py └── tools ├── _test └── callgraph_test.py ├── callgraph.py ├── callgraph_operations.py ├── paramter_generator.py ├── primes.txt ├── profile_to_callgrind.py ├── profile_to_dot.py └── profile_to_table.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /doc/html 3 | *.pyc 4 | *.pyo 5 | *~ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python3 Implementation of Schoof's Algorithm 2 | ============================================ 3 | 4 | This is an implementation of Schoof's algorithm for counting the 5 | points on elliptic curves over finite fields (Schoof, René. Elliptic 6 | curves over finite fields and the computation of square roots mod 7 | p. *Mathematics of Computation*, 44(170):483–494, 1985). 8 | 9 | Elliptic curve cryptographic algorithms base their security guarantees 10 | on the number of points on the used elliptic curve. Because naive 11 | point counting is infeasible, having a fast counting algorithm is 12 | important to swiftly decide whether a curve is safe to use in 13 | cryptography or not. René Schoof's algorithm for counting the points 14 | on an elliptic curve over a finite field is the foundation for the 15 | (asymptotically) fastest [Schoof–Elkies–Atkin][sea-algorithm] counting 16 | algorithm. 17 | 18 | 19 | **Implementation.** 20 | The implementation is written in [Python 3][python] and is kept as 21 | simple as possible. Its goal is to give insight into the mathematics 22 | of the algorithm without the use of (too) high-level concepts. For a 23 | (pretty) gentle introduction into why and how Schoof's algorithm 24 | works, please read my diploma thesis titled 25 | [*An Elementary Derivation and Implementation of Schoof's Algorithm for Counting Points on Elliptic Curves*][thesis]. 26 | In the thesis, I try to explain *how one might get the idea for the 27 | algorithm*. This understanding of *why* things look the way they do 28 | is often neglected in mathematics. 29 | 30 | Schoof's algorithm uses arithmetic on elliptic curves, finite fields, 31 | rings of polynomials, and quotient rings. This repository therefore 32 | contains Python modules for all these concepts that can be used on 33 | their own. 34 | 35 | 36 | System Requirements 37 | ------------------- 38 | 39 | The algorithm is implemented in version 3.1 of [Python][python], an 40 | open licensed dynamic programming language available on all common 41 | platforms. To find out whether a compatible version of Python is 42 | already installed on your system, execute `python --version` in a 43 | terminal. The command will return the version number if Python is 44 | available. Note that the version 3 interpreter could also be named 45 | `python3`. Please see the *Using Python* part of Python's 46 | [documentation][python-using] for system installation instructions; 47 | follow the steps below to set up Python locally in your account. 48 | 49 | 50 | ### Local Installation from Source Code 51 | 52 | Installing Python from source code requires a C compiler; on Linux and 53 | Unix systems, one is almost always available. The following steps 54 | install Python on a Linux system: 55 | 56 | * **Download.** Download the source tar ball of version 3.1 or later 57 | from the Python website at 58 | . 59 | * **Compile.** Open a terminal and create a temporary directory, say 60 | `${HOME}/tmp/`, by executing `mkdir ${HOME}/tmp/`. Change into the 61 | temporary directory and extract the source tar ball: `cd 62 | ${HOME}/tmp/` and then `tar xzvf Python-3.1.2.tgz`; adjust the path 63 | and file name accordingly. If you downloaded the bzipped source tar 64 | ball, use `tar xjvf Python-3.1.2.tar.bz2` instead. 65 | 66 | Next, change into the directory that contains the extracted source 67 | code, for instance `${HOME}/tmp/Python-3.1.2/`. Configure the build 68 | system by executing `./configure --prefix=${HOME}/python3`. The 69 | prefix is the path that will be the root of the Python installation, 70 | so adjust it to taste. In case required components are missing, the 71 | command will exit with an error message. In this case, please 72 | install the components and re-execute `configure`. 73 | 74 | If everything worked, then the last line of output by `configure` 75 | will be `creating Makefile`. To start the compilation, execute 76 | `make`. 77 | * **Install.** Use `make install` to install Python after the 78 | compilation finished. 79 | * **Set Up Environment.** To enable the local Python installation, add 80 | its interpreter and modules to the respective search paths: execute 81 | `export PATH=${HOME}/python3/bin:${PATH}` to tell the shell where to 82 | find the `python3` interpreter; adjust the path to your prefix for 83 | `configure`. Likewise, execute `export 84 | PYTHONPATH=${HOME}/python3/lib/python3.1` to tell Python where to 85 | find its modules. 86 | 87 | Note that the scope of `export` is the current shell. Thus you have 88 | to issue both commands in every freshly opened terminal you wish to 89 | use for Python 3.1 programs. 90 | 91 | 92 | Program Execution 93 | ----------------- 94 | 95 | The implementations work without any installation; they may be 96 | executed directly from the checked out repository. However, they 97 | expect a set up Python 3.1 run-time environment as explained above. 98 | 99 | The root directory contains the point counting programs: the file 100 | `naive_schoof.py` is the implementation discussed in 101 | \autoref{sec:implementation}; the file `reduced_computation_schoof.py` 102 | is the version with better constants using a smarter computation order 103 | and Hasse's theorem. Curves for counting are specified as 104 | space-separated triples *p*, *A*, and *B*: *p* is the prime size of 105 | the galois field *GF[p]*, and *A* and *B* are the curve parameters. 106 | 107 | ### Example 108 | 109 | Suppose you want to count the number of points on the elliptic curve 110 | over *GF[23]* with parameters *A=4* and *B=2*. If the current 111 | directory in the terminal is the repository root, then 112 | executing `python3 naive_schoof.py 23 4 2` yields the output 113 | 114 | ~~~~~ 115 | Counting points on y^2 = x^3 + 4x + 2 over GF<23>: 21 116 | ~~~~~ 117 | 118 | 119 | ### Command Line Parameters 120 | 121 | The program supports the command line options described below, which 122 | for instance allow to read the curves from a file, or to create 123 | execution profiles for performance analysis. 124 | 125 | * `--version`: Show program's version number and exit. 126 | * `-h`, `--help`: Show a help message and exit. 127 | * `-i` *f*, `--input-file=`*f*: Read the curve parameters from file 128 | *f*. Lines in *f* must contain either a comment, or a 129 | space-separated triple of curve parameters *p*, *A*, and *B*. 130 | Comment lines start with a hash (`#`); these and empty lines are 131 | skipped. 132 | * `-o` *f*, `--output-file=`*f*: Write the results to file *f* instead 133 | of showing them on the terminal. Each line of input generates one 134 | corresponding line of output. 135 | * `-t` *s*, `--timelimit=`*s*: Terminate the program if processing an 136 | input triple takes longer than *s* seconds. The program ends if the 137 | time limit expires; no subsequent lines will be read from an input 138 | file. Thus, sort the parameters in input files ascending in length 139 | of the prime *p* to avoid triggering the time limit too early. 140 | * `-p`, `--create-profile`: Create an execution profile for each 141 | processed input triple. The profile consists of a call profile 142 | generated with the `cProfile` Python module, and a file with data 143 | about elapsed time and used memory. 144 | * `-d` *d*, `--profile-directory=`*d*: If profiling is enabled with 145 | the `-p` flag, then the collected data is written to the directory 146 | *d*. The default value is the current directory. 147 | 148 | 149 | Profiling Tools 150 | --------------- 151 | 152 | The `tools/` directory contains several programs to post-process 153 | profiles resulting from a set `-p` flag. For example, it includes a 154 | call profile converter that outputs [Callgrind][callgrind] files. Use 155 | [KCacheGrind][kcachegrind] to interactively inspect Callgrind files. 156 | 157 | 158 | Further Documentation 159 | --------------------- 160 | 161 | The implementation comes with extensive [API documentation][api] that 162 | explains the purpose and usage of all classes. 163 | 164 | Furthermore, the directory `_test` contains unit tests to verify 165 | correct arithmetic. The unit tests are designed to be easily extended 166 | to further implementations of the same objects. Thus, mistakes in new 167 | designs should be simpler to locate. To run the unit tests, execute 168 | `python3 -m _test` in the repository root directory. 169 | 170 | 171 | License 172 | ------- 173 | 174 | Copyright (c) 2010--2012 Peter Dinges . 175 | 176 | The software in this repository is free software: you can redistribute 177 | it and/or modify it under the terms of the GNU General Public License 178 | as published by the Free Software Foundation, either version 3 of the 179 | License, or (at your option) any later version. 180 | 181 | The software is distributed in the hope that it will be useful, but 182 | WITHOUT ANY WARRANTY; without even the implied warranty of 183 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 184 | General Public License for more details. 185 | 186 | You should have received a copy of the [GNU General Public License][gpl3] 187 | along with this program. If not, see . 188 | 189 | 190 | [api]: http://pdinges.github.com/python-schoof 191 | [callgrind]: http://valgrind.org/info/tools.html "Callgrind is part of the Valgrind tool suite" 192 | [gpl3]: http://opensource.org/licenses/GPL-3.0 "GNU General Public License, version 3" 193 | [kcachegrind]: http://kcachegrind.sourceforge.net/html/Home.html "Interactive viewer for Callgrind files." 194 | [python]: http://python.org "Python Programming Language" 195 | [python-using]: http://docs.python.org/py3k/using/index.html "Python Setup and Usage" 196 | [sea-algorithm]: http://en.wikipedia.org/wiki/Schoof%E2%80%93Elkies%E2%80%93Atkin_algorithm "Schoof-Elkies-Atkin algorithm for counting points on elliptic curves over finite fields." 197 | [thesis]: http://pdinges.github.com/python-schoof/dinges-elementary_schoof_derivation-thesis.pdf 198 | -------------------------------------------------------------------------------- /_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/_test/__init__.py -------------------------------------------------------------------------------- /_test/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | import unittest 19 | 20 | all_suites = [] 21 | 22 | #=============================================================================== 23 | # Collect all test suites 24 | #=============================================================================== 25 | 26 | #- Rings ---------------------------------------------------------------------- 27 | from . import polynomial_rings_test 28 | all_suites.extend( polynomial_rings_test.all_suites ) 29 | 30 | from . import quotient_rings_test 31 | all_suites.extend( quotient_rings_test.all_suites ) 32 | 33 | 34 | #- Fields --------------------------------------------------------------------- 35 | from . import finite_fields_test 36 | all_suites.extend( finite_fields_test.all_suites ) 37 | 38 | from . import fraction_fields_test 39 | all_suites.extend( fraction_fields_test.all_suites ) 40 | 41 | 42 | #- Elliptic Curves------------------------------------------------------------- 43 | from . import elliptic_curves_group_test 44 | all_suites.extend( elliptic_curves_group_test.all_suites ) 45 | 46 | 47 | #- Support -------------------------------------------------------------------- 48 | from . import support_test 49 | all_suites.extend( support_test.all_suites ) 50 | 51 | 52 | #- Algorithms ----------------------------------------------------------------- 53 | from . import algorithm_test 54 | all_suites.extend( algorithm_test.all_suites ) 55 | 56 | #=============================================================================== 57 | # Run all tests 58 | #=============================================================================== 59 | all_tests = unittest.TestSuite( all_suites ) 60 | unittest.TextTestRunner().run( all_tests ) 61 | -------------------------------------------------------------------------------- /_test/algorithm_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | import unittest 19 | 20 | from fields.finite.naive import FiniteField 21 | from elliptic_curves.naive import EllipticCurve 22 | 23 | def generate_test_suites(frobenius_trace_implementation, name_prefix): 24 | """ 25 | Generate and combine TestCase classes for the given algorithm for computing 26 | the trace of the Frobenius endomorphism of elliptic curves over finite 27 | fields. This groups the tests by implementation and category (instead of 28 | category alone) and allows flexible addition and removal of implementations. 29 | """ 30 | 31 | class FrobeniusTraceTest(unittest.TestCase): 32 | """ 33 | Test cases for computing the trace of the Frobenius endomorphism on 34 | elliptic curves over finite fields 35 | 36 | This class assumes correct EllipticCurve and FiniteField implementations. 37 | """ 38 | 39 | def test_5_1_1(self): 40 | """y^2 = x^3 + x + 1 over GF<5>""" 41 | # From section 4.1 in Washington, L. C., 42 | # "Elliptic Curve --- Number Theory and Cryptography", second edition, 43 | # CRC Press, 2008 44 | self.assert_( self._count_points(5, 1, 1) == 9 ) 45 | 46 | def test_7_0_2(self): 47 | """y^2 = x^3 + 2 over GF<7>""" 48 | # From section 4.1 in Washington, L. C., 49 | # "Elliptic Curve --- Number Theory and Cryptography", second edition, 50 | # CRC Press, 2008 51 | self.assert_( self._count_points(7, 0, 2) == 9 ) 52 | 53 | def test_23_7_16(self): 54 | """y^2 = x^3 + 7x + 16 over GF<23>""" 55 | # From http://www.certicom.com/ecc_tutorial/ecc_twopoints.html 56 | self.assert_( self._count_points( 23, 7, 16 ) == 22 ) 57 | 58 | def _count_points(self, p, A, B): 59 | curve = EllipticCurve( FiniteField(p), A, B ) 60 | trace = frobenius_trace_implementation( curve ) 61 | return p + 1 - trace 62 | 63 | 64 | suites = [] 65 | for test_class in [ FrobeniusTraceTest ]: 66 | test_class.__name__ = "{0}_{1}".format( name_prefix, test_class.__name__ ) 67 | suites.append( unittest.TestLoader().loadTestsFromTestCase( test_class ) ) 68 | return suites 69 | 70 | 71 | #=============================================================================== 72 | # Implementation importing and TestSuites generation 73 | #=============================================================================== 74 | 75 | import naive_schoof 76 | import reduced_computation_schoof 77 | 78 | implementations = [ 79 | (naive_schoof.frobenius_trace, "Naive"), 80 | (reduced_computation_schoof.frobenius_trace, "Reduced"), 81 | ] 82 | 83 | all_suites = [] 84 | for implementation, prefix in implementations: 85 | all_suites.extend( generate_test_suites( implementation, prefix ) ) 86 | 87 | 88 | if __name__ == "__main__": 89 | all_tests = unittest.TestSuite( all_suites ) 90 | unittest.TextTestRunner().run( all_tests ) 91 | -------------------------------------------------------------------------------- /_test/elliptic_curves_group_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | import unittest 19 | 20 | from fields.finite.naive import FiniteField 21 | 22 | def generate_test_suites(curve_implementation, infinity_implementation, \ 23 | name_prefix): 24 | """ 25 | Generate TestCase classes for the given implementations of elliptic curves 26 | and the point at infinity; combine them to TestSuites. This groups the tests 27 | by implementation and category (instead of category alone) and allows 28 | flexible addition and removal of implementations. 29 | """ 30 | 31 | # Test the implementation for one small and one large field. Large 32 | # means that operands require more than 64 bit; the tenth Mersenne 33 | # prime will do as field size. 34 | F = FiniteField( 23 ) 35 | G = FiniteField( 2**89 - 1 ) 36 | 37 | # TODO: Add more test cases, especially larger ones 38 | E1 = curve_implementation(F, 1, 0) 39 | O = infinity_implementation() 40 | 41 | 42 | # TODO: Add test cases for point creation (on the curve or not) 43 | 44 | 45 | class PointsTest(unittest.TestCase): 46 | """ 47 | Test cases for creating and comparing points on elliptic curves 48 | """ 49 | #- Equality --------------------------------------------------------------- 50 | def test_eq_true(self): 51 | """Equality: true statement""" 52 | self.assert_( E1(9, 5) == E1(9, 5) ) 53 | 54 | def test_eq_false(self): 55 | """Equality: false statement""" 56 | self.failIf( E1(9, 5) == E1(13, 5) ) 57 | 58 | 59 | #- Inequality ------------------------------------------------------------- 60 | def test_neq_true(self): 61 | """Inequality: true statement""" 62 | self.failIf( E1(9, 5) != E1(9, 5) ) 63 | 64 | def test_neq_false(self): 65 | """Inequality: false statement""" 66 | self.assert_( E1(9, 5) != E1(13, 5) ) 67 | 68 | 69 | #- Finiteness ------------------------------------------------------------- 70 | def test_infinite(self): 71 | """Test for infinity""" 72 | self.failIf( E1(9, 5).is_infinite() ) 73 | 74 | 75 | class GroupOperationTest(unittest.TestCase): 76 | """ 77 | Test cases for the group operation on elliptic curves 78 | """ 79 | #- Addition --------------------------------------------------------------- 80 | def test_add_base(self): 81 | """Addition base case""" 82 | self.assert_( E1(9,5) + E1(13, 5) == E1(1, 18) ) 83 | 84 | def test_add_infinity(self): 85 | """Addition of neutral element (point at infinity)""" 86 | self.assert_( E1(11, 10) + O == E1(11, 10) ) 87 | 88 | def test_add_double(self): 89 | """Point duplication""" 90 | self.assert_( E1(11, 10) + E1(11, 10) == E1(13, 18) ) 91 | 92 | 93 | #- Additive inverse (unary minus) ----------------------------------------- 94 | def test_neg_base(self): 95 | """Negation (additive inverse) base case""" 96 | self.assert_( -E1(9, 5) == E1(9, -5) ) 97 | self.assert_( E1(9, 5) + (-E1(9, 5)) == O) 98 | 99 | def test_neg_double(self): 100 | """Double negation""" 101 | self.assert_( -(-E1(9, 5)) == E1(9, 5) ) 102 | 103 | 104 | #- Subtraction ------------------------------------------------------------ 105 | def test_sub_base(self): 106 | """Subtraction base case""" 107 | self.assert_( E1(9, 5) - E1(13, -5) == E1(1, 18) ) 108 | 109 | 110 | #- Multiplication with integers (repeated addition) ----------------------- 111 | def test_mul_base(self): 112 | """Multiplication with integers (point as left factor) base case""" 113 | P = E1(11, 10) 114 | self.assert_( P * 2 == P + P ) 115 | self.assert_( P * 5 == P + P + P + P + P ) 116 | self.assert_( P * 6 == P + P + P + P + P + P ) 117 | 118 | def test_mul_zero(self): 119 | """Multiplication with zero (point as left factor)""" 120 | self.assert_( E1(11, 10) * 0 == O ) 121 | 122 | def test_rmul_base(self): 123 | """Multiplication with integers (point as right factor)""" 124 | P = E1(11, 10) 125 | self.assert_( 2 * P == P + P ) 126 | self.assert_( 5 * P == P + P + P + P + P ) 127 | self.assert_( 6 * P == P + P + P + P + P + P ) 128 | 129 | def test_rmul_zero(self): 130 | """Multiplication with zero (point as right factor)""" 131 | self.assert_( 0 * E1(11, 10) == O ) 132 | 133 | 134 | class PointAtInfinityTest(unittest.TestCase): 135 | """ 136 | Test cases for the point at infinity 137 | """ 138 | def test_eq(self): 139 | """Equality""" 140 | self.assert_( O == O ) 141 | self.failIf( O == E1(9, 5) ) 142 | 143 | def test_neq(self): 144 | """Inequality""" 145 | self.failIf( O != O ) 146 | self.assert_( O != E1(9, 5) ) 147 | 148 | def test_infinite(self): 149 | """Test for infinity""" 150 | self.assert_( O.is_infinite() ) 151 | 152 | def test_add(self): 153 | """Addition""" 154 | self.assert_( O + E1(11, 10) == E1(11, 10) ) 155 | self.assert_( O + O == O) 156 | 157 | def test_neg(self): 158 | """Negation (additive inverse)""" 159 | self.assert_( O == (-O) ) 160 | 161 | def test_mul(self): 162 | """Multiplication with integers""" 163 | self.assert_( O * 7 == O ) 164 | self.assert_( 7 * O == O) 165 | 166 | 167 | 168 | suites = [] 169 | for test_class in [ PointsTest, GroupOperationTest, PointAtInfinityTest ]: 170 | test_class.__name__ = "{0}_{1}".format( name_prefix, test_class.__name__ ) 171 | suites.append( unittest.TestLoader().loadTestsFromTestCase( test_class ) ) 172 | return suites 173 | 174 | #=============================================================================== 175 | # Implementation importing and TestSuites generation 176 | #=============================================================================== 177 | 178 | import elliptic_curves.naive 179 | 180 | implementations = [ 181 | ( elliptic_curves.naive.EllipticCurve, 182 | elliptic_curves.naive.PointAtInfinity, 183 | "Naive" ), 184 | ] 185 | 186 | all_suites = [] 187 | for curve_implementation, infinity_implementation, prefix in implementations: 188 | all_suites.extend( 189 | generate_test_suites( 190 | curve_implementation, 191 | infinity_implementation, 192 | prefix 193 | ) 194 | ) 195 | 196 | 197 | if __name__ == "__main__": 198 | all_tests = unittest.TestSuite( all_suites ) 199 | unittest.TextTestRunner().run( all_tests ) 200 | -------------------------------------------------------------------------------- /_test/finite_fields_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | import unittest 19 | 20 | def generate_test_suites(finitefield_implementation, name_prefix): 21 | """ 22 | Generate TestCase classes for the given finite field implementation and 23 | combine all tests to TestSuites. This groups the tests by implementation 24 | and category (instead of category alone) and allows flexible addition 25 | and removal of implementations. 26 | """ 27 | 28 | # Test the implementation for one small and one large field. Large 29 | # means that operands require more than 64 bit; the tenth Mersenne 30 | # prime will do as field size. 31 | F = finitefield_implementation( 17 ) 32 | G = finitefield_implementation( 2**89 - 1 ) 33 | 34 | 35 | class ElementsTest(unittest.TestCase): 36 | """ 37 | Test cases concerning the creation and comparison of 38 | elements of finite fields 39 | """ 40 | #- Creation --------------------------------------------------------------- 41 | def test_create(self): 42 | """Element creation""" 43 | self.assertIsNotNone( F(0) ) 44 | self.assertIsNotNone( G(0) ) 45 | 46 | def test_create_uncastable(self): 47 | """Element creation raises TypeError if uncastable""" 48 | def f(): 49 | return G(F(1)) 50 | def g(): 51 | return F(G(4)) 52 | self.assertRaises( TypeError, f ) 53 | self.assertRaises( TypeError, g ) 54 | 55 | def test_create_idempotent(self): 56 | """Element creation accepts elements of the same field""" 57 | self.assert_( F(F(1)) == F(1) ) 58 | self.assert_( G(G(1)) == G(1) ) 59 | self.assert_( F(F(7)) == F(7) ) 60 | self.assert_( G(G(7)) == G(7) ) 61 | 62 | 63 | #- Equality --------------------------------------------------------------- 64 | def test_eq_true(self): 65 | """Equality: true statement""" 66 | self.assert_( F(3) == F(3) ) 67 | self.assert_( G(3) == G(3) ) 68 | 69 | def test_eq_false(self): 70 | """Equality: false statement""" 71 | self.failIf( F(3) == F(5) ) 72 | self.failIf( G(3) == G(15) ) 73 | 74 | def test_eq_casting_integers(self): 75 | """Equality: automatic casting of integers on the right hand side""" 76 | self.assert_( F(1) == 1 ) 77 | self.assert_( G(6) == 6 ) 78 | 79 | def test_eq_casting_integers_reversed(self): 80 | """Equality: automatic casting of integers on the left hand side""" 81 | self.assert_( 3 == F(3) ) 82 | self.assert_( 5 == G(5) ) 83 | 84 | def test_eq_uncastable(self): 85 | """Equality: uncastable resolves to false""" 86 | self.failIf( F(1) == G(1) ) 87 | 88 | 89 | #- Inequality ------------------------------------------------------------- 90 | def test_ne_true(self): 91 | """Inequality: true statement""" 92 | self.failIf( F(3) != F(3) ) 93 | self.failIf( G(3) != G(3) ) 94 | 95 | def test_ne_false(self): 96 | """Inequality: false statement""" 97 | self.assert_( F(3) != F(5) ) 98 | self.assert_( G(3) != G(5) ) 99 | 100 | def test_ne_casting_integers(self): 101 | """Inequality: automatic casting of integers on the right hand side""" 102 | self.assert_( F(1) != 2 ) 103 | self.assert_( G(1) != 2 ) 104 | 105 | def test_ne_casting_integers_reversed(self): 106 | """Inequality: automatic casting of integers on the left hand side""" 107 | self.assert_( 2 != F(1) ) 108 | self.assert_( 2 != G(1) ) 109 | 110 | def test_ne_uncastable(self): 111 | """Inequality: uncastable resolves to false""" 112 | self.assert_( F(1) != G(1) ) 113 | 114 | 115 | #- Test for zero ---------------------------------------------------------- 116 | def test_zero_true(self): 117 | """Test for zero: true""" 118 | self.failIf( F(0) ) 119 | self.failIf( G(0) ) 120 | 121 | def test_zero_false(self): 122 | """Test for zero: false""" 123 | self.assert_( F(23) ) 124 | self.assert_( G(42) ) 125 | 126 | 127 | class ArithmeticTest(unittest.TestCase): 128 | """Test cases for arithmetic operations over finite fields""" 129 | # These tests rely on working equality comparison and 130 | # not all elements being zero. 131 | 132 | #- Addition --------------------------------------------------------------- 133 | def test_add_base(self): 134 | """Addition base case""" 135 | self.assert_( F(3) + F(5) == F(8) ) 136 | self.assert_( G(3) + G(5) == G(8) ) 137 | 138 | def test_add_wrap(self): 139 | """Addition with wrap-around""" 140 | self.assert_( F(7) + F(13) == F(3) ) 141 | self.assert_( G(2**88) + G(2**88 + 1) == G(2) ) 142 | 143 | def test_add_casting(self): 144 | """Addition: automatic casting of right summand""" 145 | self.assert_( F(7) + 2 == F(9) ) 146 | self.assert_( G(7) + 2 == G(9) ) 147 | 148 | def test_add_casting_reversed(self): 149 | """Addition: automatic casting of left summand""" 150 | self.assert_( 4 + F(12) == F(16) ) 151 | self.assert_( 4 + G(12) == G(16) ) 152 | 153 | 154 | #- Negation (unary minus) ------------------------------------------------- 155 | def test_neg_base(self): 156 | """Negation (additive inverse) base case""" 157 | self.assert_( -F(5) == F(-5) ) 158 | self.assert_( F(13) + (-F(13)) == F(0) ) 159 | self.assert_( -G(5) == G(-5) ) 160 | self.assert_( G(13) + (-G(13)) == G(0) ) 161 | 162 | def test_neg_double(self): 163 | """Double negation""" 164 | self.assert_( -(-(F(12))) == F(12) ) 165 | self.assert_( -(-(G(12))) == G(12) ) 166 | 167 | 168 | #- Subtraction ------------------------------------------------------------ 169 | def test_sub_base(self): 170 | """Subtraction without wrap-around""" 171 | self.assert_( F(8) - F(5) == F(3) ) 172 | self.assert_( G(8) - G(5) == G(3) ) 173 | 174 | def test_sub_wrap(self): 175 | """Subtraction with wrap-around""" 176 | self.assert_( F(3) - F(13) == F(7) ) 177 | self.assert_( G(2) - G(2**88 + 1) == G(2**88) ) 178 | 179 | def test_sub_as_add(self): 180 | """Subtraction as addition of negative""" 181 | self.assert_( F(5) + (-F(13)) == F(5) - F(13) ) 182 | self.assert_( G(5) + (-G(13)) == G(5) - G(13) ) 183 | 184 | def test_sub_casting(self): 185 | """Subtraction: automatic casting of subtrahend""" 186 | self.assert_( F(13) - 2 == F(11) ) 187 | self.assert_( G(13) - 2 == G(11) ) 188 | 189 | def test_sub_casting_reversed(self): 190 | """Subtraction: automatic casting of minuend""" 191 | self.assert_( 6 - F(4) == F(2) ) 192 | self.assert_( 6 - G(4) == G(2) ) 193 | 194 | 195 | #- Multiplication --------------------------------------------------------- 196 | def test_mul_base(self): 197 | """Multiplication base case""" 198 | self.assert_( F(3) * F(5) == F(15) ) 199 | self.assert_( G(3) * G(5) == G(15) ) 200 | 201 | def test_mul_wrap(self): 202 | """Multiplication with wrap-around""" 203 | self.assert_( F(5) * F(7) == F(1) ) 204 | self.assert_( G(2**88) * G(2) == G(1) ) 205 | 206 | def test_mul_inverse(self): 207 | """Multiplicative inverse base case""" 208 | self.assert_( F(5).multiplicative_inverse() == F(7) ) 209 | self.assert_( G(2**45).multiplicative_inverse() == G(2**44) ) 210 | 211 | def test_mul_inverse_zero(self): 212 | """Multiplicative inverse of zero raises exception""" 213 | def f(): 214 | return F(0).multiplicative_inverse() 215 | def g(): 216 | return G(0).multiplicative_inverse() 217 | self.assertRaises( ZeroDivisionError, f ) 218 | self.assertRaises( ZeroDivisionError, g ) 219 | 220 | def test_mul_casting(self): 221 | """Multiplication: automatic casting of right factor""" 222 | self.assert_( F(4) * 3 == F(12) ) 223 | self.assert_( G(4) * 3 == G(12) ) 224 | 225 | def test_mul_casting_reversed(self): 226 | """Multiplication: automatic casting of left factor""" 227 | self.assert_( 4 * F(3) == F(12) ) 228 | self.assert_( 4 * G(3) == G(12) ) 229 | 230 | 231 | #- Division --------------------------------------------------------------- 232 | def test_truediv_base(self): 233 | """Division base case""" 234 | self.assert_( F(1) / F(13) == F(13).multiplicative_inverse() ) 235 | self.assert_( G(1) / G(13) == G(13).multiplicative_inverse() ) 236 | 237 | def test_truediv_zero(self): 238 | """Division by zero raises exception""" 239 | def f(): 240 | return F(1) / F(0) 241 | def g(): 242 | return G(127) / G(0) 243 | self.assertRaises( ZeroDivisionError, f ) 244 | self.assertRaises( ZeroDivisionError, g ) 245 | 246 | def test_truediv_casting(self): 247 | """Division: automatic casting of divisor""" 248 | self.assert_( F(12) / 3 == F(4) ) 249 | self.assert_( G(12) / 3 == G(4) ) 250 | 251 | def test_truediv_casting_reversed(self): 252 | """Division: automatic casting of dividend""" 253 | self.assert_( 12 / F(4) == F(3) ) 254 | self.assert_( 12 / G(4) == G(3) ) 255 | 256 | 257 | #- Exponentiation --------------------------------------------------------- 258 | def test_pow_base(self): 259 | """Integer power base case""" 260 | self.assert_( F(2)**3 == F(8) ) 261 | self.assert_( G(2)**3 == G(8) ) 262 | 263 | def test_pow_wrap(self): 264 | """Integer power with wrap-around""" 265 | self.assert_( F(15)**2 == F(4) ) 266 | self.assert_( G(2**45)**2 == G(2) ) 267 | 268 | def test_pow_non_casting(self): 269 | """Integer power: no casting of exponent""" 270 | def f(): 271 | return F(12) ** F(3) 272 | def g(): 273 | return G(12) ** G(3) 274 | self.assertRaises( TypeError, f ) 275 | self.assertRaises( TypeError, g ) 276 | 277 | 278 | suites = [] 279 | for test_class in [ ElementsTest, ArithmeticTest ]: 280 | test_class.__name__ = "{0}_{1}".format( name_prefix, test_class.__name__ ) 281 | suites.append( unittest.TestLoader().loadTestsFromTestCase( test_class ) ) 282 | return suites 283 | 284 | 285 | #=============================================================================== 286 | # Implementation importing and TestSuites generation 287 | #=============================================================================== 288 | 289 | import fields.finite.naive 290 | 291 | implementations = [ 292 | (fields.finite.naive.FiniteField, "Naive"), 293 | ] 294 | 295 | all_suites = [] 296 | for implementation, prefix in implementations: 297 | all_suites.extend( generate_test_suites( implementation, prefix ) ) 298 | 299 | 300 | if __name__ == "__main__": 301 | all_tests = unittest.TestSuite( all_suites ) 302 | unittest.TextTestRunner().run( all_tests ) 303 | -------------------------------------------------------------------------------- /_test/support_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | import unittest 19 | 20 | #- Primes --------------------------------------------------------------------- 21 | 22 | from support.primes import primes_range 23 | 24 | class PrimesRangeTest(unittest.TestCase): 25 | """Test cases for the function @c support.primes.primes_range""" 26 | 27 | def test_results(self): 28 | """Sample results""" 29 | self.assert_( list(primes_range(1, 10)) == [2, 3, 5, 7] ) 30 | 31 | def test_lower_bound(self): 32 | """Inclusive lower bound""" 33 | self.assert_( list(primes_range(3, 10)) == [3, 5, 7] ) 34 | 35 | def test_upper_bound(self): 36 | """Exclusive upper bound""" 37 | self.assert_( list(primes_range(1, 7)) == [2, 3, 5] ) 38 | 39 | def test_empty(self): 40 | """Empty primes interval""" 41 | self.assert_( not primes_range(3, 2) ) 42 | 43 | def test_iterable(self): 44 | """Iterable return type""" 45 | self.assert_( [2, 3, 5] == [ p for p in primes_range(2, 6) ] ) 46 | 47 | 48 | from support.primes import inverse_primorial 49 | 50 | class InversePrimorialTest(unittest.TestCase): 51 | """Test cases for the function @c support.primes.inverse_primorial""" 52 | 53 | def test_results(self): 54 | """Sample results""" 55 | self.assert_( inverse_primorial(30) == 5 ) 56 | self.assert_( inverse_primorial(31) == 7 ) 57 | 58 | def test_empty(self): 59 | """Empty (too small) input""" 60 | self.assert_( inverse_primorial(-1) == 2 ) 61 | 62 | 63 | #- Quotients ------------------------------------------------------------------ 64 | 65 | from rings.integers.naive import Integers 66 | from rings.quotients.naive import QuotientRing 67 | from support.quotients import solve_congruence_equations 68 | 69 | class CongruenceEquationTest(unittest.TestCase): 70 | """ 71 | Test cases for the Chinese Remainder Theorem function 72 | @c support.quotients.solve_congruence_equations 73 | """ 74 | def setUp(self): 75 | Z3 = QuotientRing( Integers, 3 ) 76 | Z5 = QuotientRing( Integers, 5 ) 77 | self._remainders = [ Z3(2), Z5(1) ] 78 | 79 | def test_result_type(self): 80 | """Solution is congruence""" 81 | solution = solve_congruence_equations( self._remainders ) 82 | self.assert_( hasattr( solution, "remainder" ) ) 83 | self.assert_( hasattr( solution, "modulus" ) ) 84 | 85 | def test_result_modulus(self): 86 | """Sample solution: modulus""" 87 | solution = solve_congruence_equations( self._remainders ) 88 | self.assert_( solution.modulus() == 15 ) 89 | 90 | def test_result_remainder(self): 91 | """Sample solution: remainder""" 92 | solution = solve_congruence_equations( self._remainders ) 93 | self.assert_( solution.remainder() == 11 ) 94 | 95 | def test_empty_equations(self): 96 | """Empty set of congruence equations""" 97 | def f(): 98 | solve_congruence_equations( [] ) 99 | self.assertRaises( ValueError , f ) 100 | 101 | 102 | from support.quotients import inverse_modulo 103 | 104 | class InverseModuloTest(unittest.TestCase): 105 | """Test cases for the computation of inverses of quotient classes""" 106 | 107 | def test_result(self): 108 | """Sample results""" 109 | self.assert_( (inverse_modulo(3, 5) - 2) % 5 == 0 ) 110 | self.assert_( (inverse_modulo(3, 7) - 5) % 7 == 0 ) 111 | 112 | def test_non_unit(self): 113 | """Input without inverse""" 114 | def f(): 115 | inverse_modulo(2, 6) 116 | self.assertRaises( ValueError, f ) 117 | 118 | def test_zero_modulus(self): 119 | """Zero modulus""" 120 | def f(): 121 | inverse_modulo(2, 0) 122 | self.assertRaises( ZeroDivisionError, f ) 123 | 124 | 125 | #- Rings ---------------------------------------------------------------------- 126 | 127 | from fields.finite.naive import FiniteField 128 | from rings.polynomials.naive import Polynomials 129 | from support.rings import extended_euclidean_algorithm as eeuc 130 | 131 | class ExtendedEuclideanAlgorithmTest(unittest.TestCase): 132 | """Test cases for the extended Euclidean algorithm""" 133 | 134 | def test_integer_gcd(self): 135 | """Integer GCD""" 136 | self.assert_( eeuc( 17, 25 )[2] == 1 ) 137 | self.assert_( eeuc( 24, 27 )[2] == 3 ) 138 | 139 | def test_integer_linear_combination(self): 140 | """Integer GCD as linear combination""" 141 | x, y, d = eeuc( 17, 25 ) 142 | self.assert_( x*17 + y*25 == d ) 143 | 144 | def test_polynomial_gcd(self): 145 | """Polynomial GCD""" 146 | # gcd( (x+1)^2, (x+1)(x-1) ) == (x+1) 147 | R = Polynomials( FiniteField(7) ) 148 | gcd = eeuc( R(1, 2, 1), R(-1, 0, 1) )[2] # Make monic 149 | self.assert_( gcd // gcd.leading_coefficient() == R(1, 1) ) 150 | 151 | def test_polynomial_linear_combination(self): 152 | """Polynomial GCD as linear combination""" 153 | R = Polynomials( FiniteField(7) ) 154 | p, q = R(1, 3, 1, 2, 4), R(1, 0, 1, 1) 155 | x, y, d = eeuc( p, q ) 156 | self.assert_( x*p + y*q == d ) 157 | 158 | def test_zero(self): 159 | """GCD of zero""" 160 | def f(): 161 | eeuc( 3, 0 ) 162 | def g(): 163 | eeuc( 0, 3 ) 164 | self.assertRaises( ZeroDivisionError, f ) 165 | self.assertRaises( ZeroDivisionError, g ) 166 | 167 | 168 | from fields.finite.naive import FiniteField 169 | from rings.polynomials.naive import Polynomials 170 | from support.rings import gcd 171 | 172 | class GcdTest(unittest.TestCase): 173 | """Test cases for the gcd function""" 174 | 175 | def test_integer_gcd(self): 176 | """Integer GCD""" 177 | self.assert_( gcd( 17, 25 ) == 1 ) 178 | self.assert_( gcd( 24, 27 ) == 3 ) 179 | 180 | def test_integer_linear_combination(self): 181 | """Integer GCD as linear combination""" 182 | x, y, d = eeuc( 17, 25 ) 183 | self.assert_( x*17 + y*25 == d ) 184 | 185 | def test_polynomial_gcd(self): 186 | """Polynomial GCD""" 187 | # gcd( (x+1)^2, (x+1)(x-1) ) == (x+1) 188 | R = Polynomials( FiniteField(7) ) 189 | gcd = eeuc( R(1, 2, 1), R(-1, 0, 1) )[2] # Make monic 190 | self.assert_( gcd // gcd.leading_coefficient() == R(1, 1) ) 191 | 192 | def test_polynomial_linear_combination(self): 193 | """Polynomial GCD as linear combination""" 194 | R = Polynomials( FiniteField(7) ) 195 | p, q = R(1, 3, 1, 2, 4), R(1, 0, 1, 1) 196 | x, y, d = eeuc( p, q ) 197 | self.assert_( x*p + y*q == d ) 198 | 199 | def test_polynomial_gcd_relatively_prime(self): 200 | """GCD of relatively prime elements""" 201 | R = Polynomials( FiniteField(7) ) 202 | self.assert_( gcd( R(-1, 0, 1), R(0, 1) ).degree() == 0 ) 203 | 204 | def test_zero(self): 205 | """GCD of zero""" 206 | def f(): 207 | eeuc( 3, 0 ) 208 | def g(): 209 | eeuc( 0, 3 ) 210 | self.assertRaises( ZeroDivisionError, f ) 211 | self.assertRaises( ZeroDivisionError, g ) 212 | 213 | 214 | #=============================================================================== 215 | # TestSuites generation 216 | #=============================================================================== 217 | 218 | all_suites = [] 219 | for test_class in [ 220 | PrimesRangeTest, 221 | InversePrimorialTest, 222 | CongruenceEquationTest, 223 | InverseModuloTest, 224 | ExtendedEuclideanAlgorithmTest, 225 | ]: 226 | all_suites.append( unittest.TestLoader().loadTestsFromTestCase( test_class ) ) 227 | 228 | 229 | if __name__ == "__main__": 230 | all_tests = unittest.TestSuite( all_suites ) 231 | unittest.TextTestRunner().run( all_tests ) 232 | -------------------------------------------------------------------------------- /doc/doxypy-0.4.2/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: doxypy 3 | Version: 0.4.2 4 | Summary: 5 | doxypy is an input filter for Doxygen. It preprocesses python 6 | files so that docstrings of classes and functions are reformatted 7 | into Doxygen-conform documentation blocks. 8 | 9 | Home-page: http://code.foosel.org/doxypy 10 | Author: Philippe Neumann & Gina Haeussge 11 | Author-email: doxypy@demod.org 12 | License: GPL v2 13 | Description: UNKNOWN 14 | Platform: UNKNOWN 15 | Classifier: Programming Language :: Python 16 | Classifier: Intended Audience :: Developers 17 | Classifier: License :: OSI Approved :: GNU General Public License (GPL) 18 | Classifier: Operating System :: OS Independent 19 | Classifier: Topic :: Software Development :: Documentation 20 | -------------------------------------------------------------------------------- /doc/doxypy-0.4.2/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | 6 | from glob import glob 7 | from distutils.core import setup 8 | from distutils.command.install import INSTALL_SCHEMES 9 | 10 | import doxypy 11 | 12 | setup( 13 | name=doxypy.__applicationName__, 14 | version=doxypy.__version__, 15 | author='Philippe Neumann & Gina Haeussge', 16 | author_email='doxypy@demod.org', 17 | url=doxypy.__website__, 18 | description=doxypy.__blurb__, 19 | license=doxypy.__licenseName__, 20 | 21 | classifiers=[ 22 | "Programming Language :: Python", 23 | "Intended Audience :: Developers", 24 | "License :: OSI Approved :: GNU General Public License (GPL)", 25 | "Operating System :: OS Independent", 26 | "Topic :: Software Development :: Documentation" 27 | ], 28 | scripts=['doxypy.py'] 29 | ) 30 | -------------------------------------------------------------------------------- /doc/quick_tags: -------------------------------------------------------------------------------- 1 | @author 2 | @brief 3 | @bug 4 | @code and @endcode 5 | @date 6 | @file 7 | @package 8 | @param 9 | @return 10 | @see 11 | @todo 12 | @version 13 | @warning 14 | -------------------------------------------------------------------------------- /elliptic_curves/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/elliptic_curves/__init__.py -------------------------------------------------------------------------------- /elliptic_curves/division_polynomials/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/elliptic_curves/division_polynomials/__init__.py -------------------------------------------------------------------------------- /elliptic_curves/division_polynomials/naive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | An indexed list of division polynomials. 20 | 21 | @package elliptic_curves.division_polynomials.naive 22 | @author Peter Dinges 23 | """ 24 | 25 | class DivisionPolynomialsList: 26 | """ 27 | An indexed list of division polynomials over an elliptic curve. 28 | 29 | Use it, for example, as follows: 30 | @code 31 | # Create the underlying elliptic curve 32 | E = elliptic_curves.naive.EllipticCurve( FiniteField(7), 3, 4 ) 33 | R = CurvePolynomials( E ) 34 | # Instantiate the list 35 | psi = DivisionPolynomialsList( R ) 36 | 37 | # Retrieve some division polynomials 38 | p = psi[2] # The second is '2y' 39 | q = psi[3] # The third is already longer: 3x^4 + 6Ax^2 + 12Bx -A^2 40 | 41 | # Use division polynomials for arithmetic on l-torsion points 42 | Q = QuotientRing( R, psi[3] ) 43 | # Do something with Q... 44 | @endcode 45 | 46 | The division polynomials are specific polynomials over an elliptic curve. 47 | The l-th divison polynomial @f$ \psi_l @f$ has zeros of multiplicity 1 48 | precisely at the finite l-torsion points 49 | @f$ E[l]\setminus\{\mathcal{O}\} @f$. Modular polynomial arithmetic then 50 | allows computations with the l-torsion points without knowing them 51 | explicitly. (Their coordinates come from the algebraic closure of the 52 | curve's field; thus they might be badly suited for computer representations.) 53 | 54 | An implementation of l-torsion groups using division polynomials is available from 55 | @c elliptic_curves.l_torsion_group.naive.LTorsionGroup. 56 | 57 | @note The implementation lazily constructs the polynomials: the l-th polynomial 58 | will be instantiated only if index @c l is accessed; the polynomial will 59 | then be cached. 60 | 61 | @see elliptic_curves.polynomials.naive.CurvePolynomials; 62 | elliptic_curves.l_torsion_group.naive.LTorsionGroup; and 63 | Charlap, Leonard S. and Robbins, David P., "CRD Expositroy Report 31: 64 | An Elementary Introduction to Elliptic Curves", 1988, chapter 9 65 | """ 66 | 67 | def __init__(self, curve_polynomials): 68 | """ 69 | Construct a new list of division polynomials for the given ring of 70 | @p curve_polynomials. 71 | 72 | @param curve_polynomials The ring of polynomials over an elliptic 73 | curve from which the polynomials will come. 74 | Note that no type checks will be performed 75 | to guarantee maximum freedom in experiments. 76 | If the given object supports the interface 77 | of @c elliptic_curves.polynomials.naive.CurvePolynomials, 78 | then everything will be fine. 79 | """ 80 | class DivisionPolynomials( curve_polynomials ): 81 | """ 82 | DivisionPolynomials are specific instances of polynomials on the 83 | curve. They grow quadratically in degree, which clutters their 84 | string representation. Instead of printing the coefficients, 85 | print the customary 'psi[l]' where l is the polynomial's index. 86 | 87 | @see elliptic_curves.polynomials.naive.CurvePolynomials 88 | """ 89 | def __str__(self): 90 | # The leading coefficient equals the torsion. Using quotient 91 | # classes is not a problem: first of all, they should not 92 | # appear in realistic use cases. 93 | # Second, adding a point m-times to itself for m > p, where p 94 | # denotes the field characteristic, is the same as adding it 95 | # m % p times to itself. Therefore, the m-th division 96 | # polynomial must equal the (m % p)-th division polynomial. 97 | return "psi[{0}]".format( 98 | self.leading_coefficient().remainder() 99 | ) 100 | self.__curve_polynomials = DivisionPolynomials 101 | 102 | # __psi is the cache of division polynomials. 103 | self.__psi = None 104 | 105 | 106 | def __getitem__(self, index): 107 | """ 108 | Retrieve the division polynomial with the given @p index; an index 109 | of @f$ l @f$ will return @f$ \psi_l @f$. 110 | 111 | The polynomial will be an instance of @c curve_polynomials(). 112 | """ 113 | index = int(index) 114 | if index < 0: 115 | raise IndexError 116 | 117 | self.__generate_up_to( index ) 118 | return self.__psi[ index ] 119 | 120 | 121 | def curve_polynomials(self): 122 | """ 123 | Return the ring of polynomials from which the division polynomials come. 124 | 125 | This is the ring of polynomials over an elliptic curve used to 126 | construct the DivisionPolynomialsList. 127 | 128 | @see __init__() 129 | """ 130 | return self.__curve_polynomials 131 | 132 | 133 | def __generate_up_to( self, l ): 134 | """ 135 | Ascertain that all division polynomials up to (including) 'l' 136 | exist in the cache self.__psi. 137 | 138 | Calling this method has no effect if the polynomials already exist. 139 | """ 140 | # See Charlap, Leonard S. and Robbins, David P., "CRD Expositroy 141 | # Report 31: An Elementary Introduction to Elliptic Curves", 1988, 142 | # Definition 9.8 for the recurrence 143 | if not self.__psi: 144 | self.__psi = 5 * [ None ] 145 | 146 | # R = F[x,y] / (y**2 - x**3 - A*x - B) 147 | R = self.__curve_polynomials 148 | # The polynomial y (used in the recursion scheme) 149 | self.__y = R( 0, 1 ) 150 | 151 | A, B = self.__curve_polynomials.curve().parameters() 152 | 153 | # Lists are copied by reference; assigning to psi modifies self.__psi 154 | psi = self.__psi 155 | psi[0] = R( 0, 0 ) 156 | psi[1] = R( 1, 0 ) 157 | psi[2] = R( 0, 2 ) 158 | psi[3] = R( (-(A**2), 12*B, 6*A, 0, 3), 0 ) 159 | psi[4] = R( 160 | 0, 161 | ( -4*( 8*(B**2) + A**3 ), -16*A*B, -20*(A**2), 80*B, 20*A, 0, 4 ) 162 | ) 163 | 164 | psi = self.__psi 165 | y = self.__y 166 | for j in range( len(self.__psi), l+1 ): 167 | k, m = divmod(j, 2) 168 | if m: 169 | # j is odd 170 | psi.append( psi[k+2] * psi[k]**3 - psi[k+1]**3 * psi[k-1] ) 171 | else: 172 | if k % 2 == 0: 173 | psi.append( 174 | ( psi[k].y_factor() // 2 ) \ 175 | * ( psi[k+2] * psi[k-1]**2 - psi[k-2] * psi[k+1]**2 ) 176 | ) 177 | else: 178 | psi.append( 179 | y * ( psi[k].x_factor() // 2 ) \ 180 | * ( psi[k+2] * psi[k-1].y_factor()**2 \ 181 | - psi[k-2] * psi[k+1].y_factor()**2 ) 182 | ) 183 | -------------------------------------------------------------------------------- /elliptic_curves/l_torsion_group/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/elliptic_curves/l_torsion_group/__init__.py -------------------------------------------------------------------------------- /elliptic_curves/l_torsion_group/naive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A naive implementation of an implicitly represented l-torsion group 20 | of an elliptic curve. 21 | 22 | @package elliptic_curves.l_torsion_group.naive 23 | @author Peter Dinges 24 | """ 25 | 26 | from rings.quotients.naive import QuotientRing 27 | from fields.fraction.naive import FractionField 28 | from elliptic_curves.naive import EllipticCurve 29 | from elliptic_curves.polynomials.naive import CurvePolynomials 30 | from elliptic_curves.division_polynomials.naive import DivisionPolynomialsList 31 | 32 | from support.types import template 33 | 34 | class LTorsionGroup( metaclass=template("_elliptic_curve") ): 35 | """ 36 | An l-torsion group of an elliptic curve for odd l; use the @c elements() 37 | method to have intuitive syntax when working with l-torsion points. 38 | 39 | This is a template class that must be instantiated with the elliptic curve. 40 | Use it, for example, as follows: 41 | @code 42 | # Create the underlying elliptic curve 43 | E = elliptic_curves.naive.EllipticCurve( FiniteField(7), 3, 4 ) 44 | # Instantiate the template; El is a class (here: l-torsion groups of E) 45 | El = LTorsionGroup( E ) 46 | 47 | # Construct the group of 3-torsion points and do something with its points 48 | for P in El[3].elements(): 49 | do_something(P) 50 | @endcode 51 | 52 | The l-Torsion points of an elliptic curve are the points of order l. Their 53 | coordinates come from the algebraic closure of the elliptic curve's field, 54 | which makes them badly suited for computer representation. (Extending the 55 | coordinate domain to the algebraic closure makes sure that all points of 56 | order l are available; there are always @f$ l^2 @f$ points of order l if 57 | l is prime to the field characteristic.) 58 | 59 | @note The l-torsion points are represented implicitly through polynomial 60 | arithmetic. Therefore, the list of points in returned by 61 | @c elements() contains only a single entry: the point @f$ (x, y) \in 62 | E\bigl(\mathbb{F}_{p}(E) / \psi_l\bigr) @f$, where 63 | @f$ \mathbb{F}_{p}(E)/\psi_l @f$ denotes the field of rational 64 | functions over the elliptic curve @f$ E @f$ with numerator and 65 | denominator polynomials taken modulo the l-th division polynomial 66 | @f$ \psi_l @f$. 67 | 68 | @note The class supports only odd torsions greater one because 69 | multivariate polynomial arithmetic is unavailable 70 | 71 | @see Silverman, Joseph H., "The Arithmetic of Elliptic Curves", 72 | second edition, Springer, 2009, p. 373 73 | """ 74 | 75 | #- Instance Methods ------------------------------------------------------- 76 | 77 | def __init__(self, torsion): 78 | """ 79 | Construct a new l-torsion group for the given @p torsion. 80 | 81 | @param torsion The @p torsion is the order of the points in the group; 82 | the group represents all points of order @p torsion. It 83 | must be an odd integer greater than 1 and prime to the 84 | field characteristic; the limitation comes from the 85 | implicit representation, see above. 86 | """ 87 | torsion = int( torsion ) 88 | if torsion < 3 or torsion % 2 == 0 or self.curve().field()(torsion) == 0: 89 | raise ValueError( "only odd torsions greater than 1 are supported" ) 90 | 91 | self.__torsion = torsion 92 | self.__point = None 93 | 94 | 95 | def elements(self): 96 | """ 97 | Return a list containing the one point that implicitly represents 98 | the whole group. 99 | 100 | Use it, for example, to elegantly perform an operation on all l-torsion 101 | points: 102 | @code 103 | for P in torsion_group.elements(): 104 | do_something(P) 105 | @endcode 106 | 107 | The one point in the list is @f$ (x, y) \in 108 | E\bigl(\mathbb{F}_{p}(E) / \psi_l\bigr) @f$, where 109 | @f$ \mathbb{F}_{p}(E)/\psi_l @f$ denotes the field of rational functions 110 | over the elliptic curve @f$ E @f$ with numerator and denominator 111 | polynomials taken modulo the l-th division polynomial @f$ \psi_l @f$. 112 | """ 113 | if not self.__point: 114 | self.__init_point() 115 | return [ self.__point ] 116 | 117 | 118 | def torsion(self): 119 | """ 120 | Return the group's torsion, that is, l for the l-torsion group. 121 | """ 122 | return self.__torsion 123 | 124 | 125 | def __init_point(self): 126 | """ 127 | Create the point that implicitly represents the whole l-torsion group 128 | and store it in the cache. 129 | 130 | Calling the method has no effect if the point already exists. 131 | """ 132 | if self.__point: 133 | return 134 | 135 | # R = F[x] / (y**2 - x**3 - A*x - B) 136 | R = self._division_polynomial_list().curve_polynomials() 137 | # The n-th division polynomial 138 | psi = self._division_polynomial_list()[ self.__torsion ] 139 | 140 | # T = ( F[x] / (y**2 - x**3 - A*x - B) ) / psi(l) 141 | S = QuotientRing( R, psi ) 142 | T = FractionField( S ) 143 | 144 | A, B = R.curve().parameters() 145 | 146 | # Polynomials x and y on the curve interpreted 147 | # as elements of the field of fractions 148 | x = T( R( (0, 1), 0 ) ) 149 | y = T( R( 0 , 1 ) ) 150 | 151 | self.__point = EllipticCurve( T, A, B )( x, y ) 152 | 153 | 154 | #- Class Methods----------------------------------------------------------- 155 | 156 | @classmethod 157 | def curve(cls): 158 | """ 159 | Return the elliptic curve that was used to initialize the template. 160 | """ 161 | return cls._elliptic_curve 162 | 163 | @classmethod 164 | def _division_polynomial_list(cls): 165 | """ 166 | Return the list of division polynomials that supplies the modulus for 167 | the implicit polynomial representation of the l-torsion points. 168 | """ 169 | try: 170 | return cls.__division_polynomial_list 171 | except AttributeError: 172 | # R = F[x] / (y**2 - x**3 - A*x - B) 173 | # where F, A, B are the elliptic curve's field and its parameters. 174 | R = CurvePolynomials( cls._elliptic_curve ) 175 | cls.__division_polynomial_list = DivisionPolynomialsList( R ) 176 | 177 | return cls.__division_polynomial_list 178 | -------------------------------------------------------------------------------- /elliptic_curves/polynomials/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/elliptic_curves/polynomials/__init__.py -------------------------------------------------------------------------------- /fields/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | Field related classes and functions (field as in rational, real, or 20 | complex numbers). 21 | 22 | @package fields 23 | @author Peter Dinges 24 | """ 25 | 26 | class Field: 27 | """ 28 | A base class for field elements that provides default operator overloading. 29 | 30 | For example, subtraction is defined as addition of the additive inverse. 31 | Derived classes therefore only have to implement the following operations 32 | to support the complete operator set: 33 | - __bool__(): Zero testing (@c True if not zero) 34 | - __eq__(): Equality testing with the @c == operator (@c True if equal) 35 | - __add__(): Addition with the @c + operator; @c self is the left summand 36 | - __neg__(): Negation with the @c - unary minus operator (the additive inverse) 37 | - __mul__(): Multiplication with the @c * operator; @c self is the 38 | left factor 39 | - multiplicative_inverse(): Reciprocal (the multiplicative inverse) 40 | 41 | The following operations are implemented in terms of above operations using 42 | the field axioms: 43 | - __neq__(): Inequality testing with the @c != operator 44 | - __radd__(): Addition with the @c + operator; @c self is the right summand 45 | - __sub__(): Subtraction with the @c - operator; @c self is the minuend 46 | (left element) 47 | - __rsub__(): Subtraction with the @c - operator; @c self is the 48 | subtrahend (right element) 49 | - __rmul__(): Multiplication with the @c * operator; @c self is the 50 | right factor 51 | - __truediv__(): Division with the @c / operator; @c self is the 52 | dividend (left element) 53 | - __rtruediv__(): Division with the @c / operator; @c self is the 54 | divisor (right element) 55 | - __pow__(): Exponentiation with integers 56 | """ 57 | 58 | #- Template Operations ---------------------------------------------------- 59 | 60 | def __neq__(self, other): 61 | """ 62 | Test whether another element @p other is different from @p self; return 63 | @c True if that is the case. The infix operator @c != calls this 64 | method; for example: 65 | @code 66 | if self != other: 67 | do_something() 68 | @endcode 69 | """ 70 | return not self.__eq__( other ) 71 | 72 | def __radd__(self, other): 73 | """ 74 | Return the sum of @p self and @p other. The infix operator @c + calls 75 | this method if @p self is the right summand and @p other.__add__() 76 | returns @c NotImplemented. For example: 77 | @code 78 | result = other + self 79 | @endcode 80 | """ 81 | return self.__add__( other ) 82 | 83 | def __sub__(self, other): 84 | """ 85 | Return the difference of @p self and @p other. The infix operator @c - 86 | calls this method if @p self is the minuend (left element); for example: 87 | @code 88 | result = self - other 89 | @endcode 90 | """ 91 | return self.__add__( -other ) 92 | 93 | def __rsub__(self, other): 94 | """ 95 | Return the difference of @p other and @p self. The infix operator @c - 96 | calls this method if @p self is the subtrahend (right element) and 97 | @c other.__sub__() returns @c NotImplemented. For example: 98 | @code 99 | result = other - self 100 | @endcode 101 | """ 102 | # other - self == -(self - other) 103 | return -self.__sub__( other ) 104 | 105 | def __rmul__(self, other): 106 | """ 107 | Return the product of @p self and @p other. The infix operator @c * 108 | calls this method if @p self is the right factor and @c other.__mul__() 109 | returns @c NotImplemented. For example: 110 | @code 111 | result = other * self 112 | @endcode 113 | """ 114 | return self.__mul__( other ) 115 | 116 | def __truediv__(self, other): 117 | """ 118 | Return the quotient of @p self and @p other. The infix operator @c / 119 | calls this method if @p self is the dividend; for example: 120 | @code 121 | result = self / other 122 | @endcode 123 | 124 | @exception ZeroDivisionError if @p other is zero. 125 | @exception TypeError if @p other lacks the 126 | multiplicative_inverse() method and 127 | cannot be cast to @p self's class. 128 | """ 129 | if not other: 130 | raise ZeroDivisionError 131 | 132 | try: 133 | other = self.__class__(other) 134 | return self.__mul__( other.multiplicative_inverse() ) 135 | 136 | except TypeError: 137 | return NotImplemented 138 | 139 | def __rtruediv__(self, other): 140 | """ 141 | Return the quotient of @p other and @p self. The infix operator @c / 142 | calls this method if @p self is the divisor and @c other.__truediv__() 143 | returns @c NotImplemented. For example 144 | @code 145 | result = other / self 146 | @endcode 147 | 148 | @exception ZeroDivisionError if @p self is zero. 149 | """ 150 | return self.multiplicative_inverse() * other 151 | 152 | def __pow__(self, n): 153 | """ 154 | Return @p self taken to the @p n-th power. The infix operator @c ** 155 | calls this method; for example: 156 | @code 157 | result = self ** n 158 | @endcode 159 | 160 | @note The implementation uses the most naive by-the-book method for 161 | exponentiation: the element is multiplied @p n-1 times with 162 | itself. This is slow (and might skin your cat!). However, the 163 | purpose of this code is to be easy to understand, not fast. 164 | 165 | @param n The exponent; it is expected to be a non-negative integer 166 | type. Negative integers and floats are unsupported. 167 | """ 168 | # This only makes sense for integer arguments. 169 | result = self 170 | for i in range(1, int(n)): 171 | result = result * self 172 | 173 | return result 174 | 175 | 176 | #- Base Operations (Defined in Derived Classes) --------------------------- 177 | 178 | def __bool__(self): 179 | """ 180 | Test whether the element is non-zero: return @c True if, and only if, 181 | it is non-zero. Otherwise return @c False. Implicit conversions to 182 | boolean (truth) values use this method; for example when @c x is an 183 | element of a Field: 184 | @code 185 | if x: 186 | do_something() 187 | @endcode 188 | 189 | @exception NotImplementedError if this method is called; subclasses 190 | must implement this operation. 191 | """ 192 | raise NotImplementedError 193 | 194 | def __eq__(self, other): 195 | """ 196 | Test whether another element @p other is equal to @p self; return 197 | @c True if that is the case. The infix operator @c == calls this 198 | method; for example: 199 | @code 200 | if self == other: 201 | do_something() 202 | @endcode 203 | 204 | @exception NotImplementedError if this method is called; subclasses 205 | must implement this operation. 206 | """ 207 | raise NotImplementedError 208 | 209 | def __add__(self, other): 210 | """ 211 | Return the sum of @p self and @p other. The infix operator @c + calls 212 | this method if @p self is the left summand; for example: 213 | @code 214 | result = self + other 215 | @endcode 216 | 217 | @exception NotImplementedError if this method is called; subclasses 218 | must implement this operation. 219 | """ 220 | raise NotImplementedError 221 | 222 | def __neg__(self): 223 | """ 224 | Return the additive inverse of @p self. The unary minus operator @c -x 225 | calls this method; for example: 226 | @code 227 | negated = -self 228 | @endcode 229 | 230 | @exception NotImplementedError if this method is called; subclasses 231 | must implement this operation. 232 | """ 233 | raise NotImplementedError 234 | 235 | def __mul__(self, other): 236 | """ 237 | Return the product of @p self and @p other. The infix operator @c + calls 238 | this method if @p self is the left factor; for example: 239 | @code 240 | result = self * other 241 | @endcode 242 | 243 | @exception NotImplementedError if this method is called; subclasses 244 | must implement this operation. 245 | """ 246 | raise NotImplementedError 247 | 248 | def multiplicative_inverse(self): 249 | """ 250 | Return the multiplicative inverse of @p self. 251 | 252 | @exception NotImplementedError if this method is called; subclasses 253 | must implement this operation. 254 | """ 255 | raise NotImplementedError 256 | -------------------------------------------------------------------------------- /fields/finite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/fields/finite/__init__.py -------------------------------------------------------------------------------- /fields/finite/naive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A naive implementation of finite fields. 20 | 21 | @package fields.finite.naive 22 | @author Peter Dinges 23 | """ 24 | 25 | from rings.quotients.naive import QuotientRing 26 | from rings.integers.naive import Integers 27 | from support.profiling import profiling_name 28 | 29 | @profiling_name("GF<{_modulus}>") 30 | class FiniteField(QuotientRing, _ring=Integers): 31 | """ 32 | The finite field with @f$ p^k @f$ elements; the field operations on the 33 | element instances support the natural infix syntax and allow mixing in 34 | integer arguments. 35 | 36 | This is a template class that must be instantiated with the field size. 37 | For example: 38 | @code 39 | # Instantiate the template; GF23 is a class. 40 | GF23 = FiniteField(23) 41 | x = GF23(3) # Create a field element: x is the quotient class (3 mod 23) 42 | y = GF23(-2) # Equivalently: y = GF23(21) because 21 == -2 (mod 23) 43 | z = x**2 + 2*y - x/y # z == 15 (mod 23); note that -2*11 == 1 (mod 23) 44 | type(x) is GF23 # This is True 45 | @endcode 46 | 47 | The implementation is held as simple as possible. Its focus lies on ease 48 | of understanding, not on performance. Thus it is badly suited for larger 49 | computations. The template inherits from 50 | @c rings.quotients.naive.QuotientRing and specializes the @c _ring template 51 | parameter to @c rings.integers.naive.Integers 52 | 53 | @note The field size must be a prime. The template will, however, 54 | accept compound numbers without hesitation. In this case, the 55 | class behaves like the quotient ring 56 | @f$ \mathbb{Z}/n\mathbb{Z} @f$. 57 | 58 | @see rings.quotients.naive.QuotientRing 59 | 60 | @author Peter Dinges 61 | """ 62 | 63 | @classmethod 64 | def characteristic(cls): 65 | """ 66 | Return the field characteristic @f$ p @f$. 67 | """ 68 | return cls._modulus 69 | 70 | @classmethod 71 | def power(cls): 72 | """ 73 | Return the power @f$ k @f$ of the characteristic that yields the field 74 | size: @f$ q = p^k @f$, where @f$ p @f$ is the field characteristic. 75 | 76 | @note In the current implementation, @f$ k @f$ is always 1. 77 | """ 78 | return 1 79 | 80 | @classmethod 81 | def size(cls): 82 | """ 83 | Return the number of elements in the field. 84 | """ 85 | return cls.characteristic() ** cls.power() 86 | 87 | @classmethod 88 | def elements(cls): 89 | """ 90 | Return a list of all field elements in ascending order 91 | @f$ (0, 1, 2, ..., p-1) @f$ (mod p). 92 | 93 | @note The method populates the complete list, so this operation might 94 | be expensive for large field characteristics. 95 | """ 96 | return [ cls(i) for i in range(0, cls.size()) ] 97 | -------------------------------------------------------------------------------- /fields/fraction/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/fields/fraction/__init__.py -------------------------------------------------------------------------------- /fields/fraction/naive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A naive implementation of fraction fields (rational fields over an 20 | integral domain). 21 | 22 | @package fields.fraction.naive 23 | @author Peter Dinges 24 | """ 25 | 26 | from fields import Field 27 | 28 | from support.types import template 29 | from support.operators import operand_casting 30 | from support.profiling import profiling_name, local_method_names 31 | 32 | @operand_casting 33 | @local_method_names 34 | @profiling_name( "Q<{_integral_domain}>" ) 35 | class FractionField( Field, metaclass=template("_integral_domain") ): 36 | """ 37 | A field of formal quotients (fractions) 38 | @f$ \frac{\mathrm{numerator}}{\mathrm{denominator}} @f$ over an integral 39 | domain; the field elements support infix notation for field operations, 40 | and mixed argument types. 41 | 42 | This is a template class that must be instantiated with the underlying 43 | integral domain. For example: 44 | @code 45 | # Instantiate the template; Q is a class (here: the rational numbers) 46 | Q = FractionField( rings.integers.naive.Integers ) 47 | x = Q(1, 2) # Create a field element: one over two 48 | y = Q(2) # Another field element: two, which is two over one 49 | z = x*y # z is two over two; no canceling takes place 50 | z == Q(4, 4) # This true because 4*2 == 2*4 51 | type(z) is Q # This is also true 52 | @endcode 53 | 54 | A formal quotient is an equivalence class of pairs (numerator, denominator) 55 | whose entries come from an integral domain (a commutative ring with 56 | identity and no zero divisors). Two pairs @f$ \frac{u}{v} @f$ and 57 | @f$ \frac{s}{t} @f$ are considered equivalent if, and only if, @f$ u\cdot t 58 | = s\cdot v @f$. Observe that this comparison uses multiplication only; 59 | since the elements come from a ring and might have no multiplicative 60 | inverses, division does not work. 61 | 62 | @note A consequence of omitted canceling is that the elements might grow 63 | large. 64 | 65 | @note The class uses the operand_casting() decorator: @c other operands in 66 | binary operations will first be treated as FractionField elements. 67 | If that fails, the operation will be repeated after @p other was fed 68 | to the constructor __init__(). If that fails, too, then the 69 | operation returns @c NotImplemented (so that @p other.__rop__() 70 | might be invoked). 71 | 72 | @see For example, Robinson, Derek J. S., 73 | "An Introduction to Abstract Algebra", p. 113. 74 | """ 75 | def __init__(self, numerator, denominator=None): 76 | """ 77 | Construct a new formal quotient (@p numerator, @p denominator). 78 | 79 | If the @p numerator is an element of this FractionField class, then 80 | the new element is a copy of @p numerator; @p denominator is ignored. 81 | Otherwise, a @c None denominator (the default) is interpreted as the 82 | multiplicative neutral element (one) in the underlying integral domain. 83 | 84 | @exception ZeroDivisonError if the denominator is zero (and not 85 | @c None, see above). 86 | """ 87 | if isinstance(numerator, self.__class__): 88 | # Copy an instance 89 | self.__numerator = numerator.__numerator 90 | self.__denominator = numerator.__denominator 91 | 92 | else: 93 | if denominator is None: 94 | denominator = self._integral_domain.one() 95 | if not denominator: 96 | raise ZeroDivisionError 97 | self.__numerator = self._integral_domain( numerator ) 98 | self.__denominator = self._integral_domain( denominator ) 99 | 100 | 101 | def __bool__(self): 102 | """ 103 | Test whether the formal quotient is non-zero: return @c True if, and 104 | only if, the numerator is non-zero. Return @c False if the numerator 105 | is zero. 106 | 107 | Implicit conversions to boolean (truth) values use this method, for 108 | example when @c x is an element of a FractionField: 109 | @code 110 | if x: 111 | do_something() 112 | @endcode 113 | """ 114 | return bool( self.__numerator ) 115 | 116 | 117 | def __eq__(self, other): 118 | """ 119 | Test whether another formal quotient @p other is equivalent to @p self; 120 | return @c True if that is the case. The infix operator @c == calls 121 | this method. 122 | 123 | Two fractions @f$ \frac{u}{v} @f$ and @f$ \frac{s}{t} @f$ are equivalent 124 | if, and only if, @f$ u\cdot t = s\cdot v @f$. 125 | 126 | @note Comparison may be expensive: it requires two multiplications in 127 | the underlying integral domain. 128 | """ 129 | # Use the basic definition of equivalence for comparison. 130 | return self.__numerator * other.__denominator \ 131 | == other.__numerator * self.__denominator 132 | 133 | 134 | def __add__(self, other): 135 | """ 136 | Return the sum of @p self and @p other. The infix operator @c + calls 137 | this method. 138 | 139 | As usual, the sum of two fractions @f$ \frac{u}{v} @f$ and 140 | @f$ \frac{s}{t} @f$ is @f$ \frac{ u\cdot t + s\cdot v }{ v\cdot t } @f$. 141 | 142 | @note Canceling of common factors is impossible, for the underlying 143 | integral domain contains non-units. Therefore, repeated addition 144 | results in large elements. 145 | """ 146 | numerator = self.__numerator * other.__denominator \ 147 | + self.__denominator * other.__numerator 148 | denominator = self.__denominator * other.__denominator 149 | return self.__class__( numerator, denominator ) 150 | 151 | 152 | def __neg__(self): 153 | """ 154 | Return the additive inverse of @p self, which is @f$ \frac{-u}{v} @f$ 155 | for a fraction @f$ \frac{u}{v} @f$. The negation operator @c -x 156 | (unary minus) calls this method. 157 | """ 158 | return self.__class__( 159 | -self.__numerator, 160 | self.__denominator 161 | ) 162 | 163 | 164 | def __mul__(self, other): 165 | """ 166 | Return the product of @p self and @p other. The @c * infix operator 167 | calls this method. 168 | 169 | As usual, the product of two fractions @f$ \frac{u}{v} @f$ and 170 | @f$ \frac{s}{t} @f$ is @f$ \frac{ u\cdot s }{ v\cdot t } @f$. 171 | 172 | @note Canceling of common factors is impossible, for the underlying 173 | integral domain contains non-units. Therefore, repeated 174 | multiplication results in large elements. 175 | """ 176 | numerator = self.__numerator * other.__numerator 177 | denominator = self.__denominator * other.__denominator 178 | return self.__class__( numerator, denominator ) 179 | 180 | 181 | def multiplicative_inverse(self): 182 | """ 183 | Return the multiplicative inverse of @p self, which is 184 | @f$ \frac{v}{u} @f$ for a fraction @f$ \frac{u}{v} @f$. 185 | 186 | @exception ZeroDivisionError if @p self is zero (has a zero numerator) 187 | 188 | @see __bool__() 189 | """ 190 | if not self: 191 | raise ZeroDivisionError 192 | 193 | return self.__class__( 194 | self.__denominator, 195 | self.__numerator 196 | ) 197 | 198 | 199 | @classmethod 200 | def zero(cls): 201 | """ 202 | Return the field's neutral element of addition (zero). 203 | 204 | Zero in a FractionField is a fraction @f$ \frac{0}{1} @f$, where 205 | @f$ 0 @f$ and @f$ 1 @f$ denote the zero and one of the underlying 206 | integral domain. 207 | """ 208 | return cls( cls._integral_domain.zero(), cls._integral_domain.one() ) 209 | 210 | 211 | @classmethod 212 | def one(cls): 213 | """ 214 | Return the field's neutral element of multiplication (one). 215 | 216 | One (or unity) in a FractionField is a fraction @f$ \frac{1}{1} @f$, 217 | where @f$ 1 @f$ denotes the one of the underlying integral domain. 218 | """ 219 | return cls( cls._integral_domain.one(), cls._integral_domain.one() ) 220 | -------------------------------------------------------------------------------- /naive_schoof.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A naive implementation of Schoof's algorithm for counting the points of 20 | elliptic curves over finite fields. 21 | 22 | The implementation is meant for instructional purposes, not for actual counting 23 | of, say, curves for cryptographic settings. It uses naive arithmetic and 24 | emphasizes ease of understanding over performance; all speed-ups (such as 25 | symmetry of search ranges, Hasse's theorem, etc.) are excluded. 26 | 27 | @see Schoof, R. 28 | "Elliptic Curves Over Finite Fields and the Computation of Square Roots mod p" 29 | in Mathematics of Computation, Vol. 44, No. 170 (Apr. 1985), pp. 483--494. 30 | 31 | @package schoof.naive 32 | @author Peter Dinges 33 | """ 34 | 35 | from elliptic_curves.l_torsion_group.naive import LTorsionGroup 36 | from support.primes import inverse_primorial, primes_range 37 | from support.quotients import solve_congruence_equations, representative_in_range 38 | 39 | def frobenius_trace(curve): 40 | """ 41 | Compute the trace of the Frobenius endomorphism for the given EllpiticCurve 42 | @p curve. 43 | 44 | This is an implementation of Schoof's original algorithm for counting the 45 | points of an elliptic curve over a finite field. 46 | 47 | @return The trace @f$ t @f$ of the Frobenius endomorphism. The number of 48 | points on the curve then is @f$ q + 1 - t @f$, where @f$ q @f$ 49 | is the size of the finite field over which the curve was defined. 50 | """ 51 | # Initialize variables and parameters 52 | trace_congruences = [] 53 | search_range = possible_frobenius_trace_range( curve.field() ) 54 | upper_prime_bound = inverse_primorial( 55 | len(search_range), 56 | shunned = curve.field().characteristic() 57 | ) 58 | 59 | # Collect the congruence equations (avoid multivariate 60 | # polynomial arithmetic by handling 2-torsion separately) 61 | trace_congruences.append( frobenius_trace_mod_2( curve ) ) 62 | 63 | torsion_group = LTorsionGroup( curve ) 64 | for prime in primes_range( 3, upper_prime_bound+1 ): 65 | if prime != curve.field().characteristic(): 66 | trace_congruences.append( 67 | frobenius_trace_mod_l( torsion_group( prime ) ) 68 | ) 69 | 70 | # Recover the unique valid trace representative 71 | trace_congruence = solve_congruence_equations( 72 | trace_congruences 73 | ) 74 | return representative_in_range( trace_congruence, search_range ) 75 | 76 | 77 | from rings.integers.naive import Integers 78 | from rings.polynomials.naive import Polynomials 79 | from rings.quotients.naive import QuotientRing 80 | from support.rings import gcd 81 | 82 | def frobenius_trace_mod_2(curve): 83 | """ 84 | Compute the trace of the Frobenius endomorphism modulo 2. 85 | 86 | We cannot use the implicit torsion group representation of 87 | frobenius_trace_mod_l() in the case @f$ l=2 @f$ because the second division 88 | polynomial is @f$ y @f$, and multivariate polynomial arithmetic is 89 | unavailable. Implementing it for this single case would be overkill. 90 | 91 | Instead, we test whether there are any rational 2-torsion points. If so, 92 | then @f$ E[2] @f$ is a subgroup of the @p curve and its order divides the 93 | order of the curve (which is the number of rational points). Now, the 94 | number of rational points in @f$ E[2] @f$ can only be 2 or 4 in this case, so 95 | we know that the number of rational points is even. Hence, the Frobenius 96 | trace must then be 0 modulo 2. 97 | 98 | @return 0 if the number of points on the curve is even, 1 otherwise. 99 | The result is a congruence class modulo 2, that is, an element 100 | of @c QuotientRing( Integers, 2 ). 101 | """ 102 | R = Polynomials( curve.field() ) 103 | 104 | x = R(0, 1) 105 | A, B = curve.parameters() 106 | 107 | defining_polynomial = x**3 + A*x + B 108 | rational_characteristic = x**curve.field().size() - x 109 | 110 | # gcd() has an arbitrary unit as leading coefficient; 111 | # relatively prime polynomials have a constant gcd. 112 | d = gcd( rational_characteristic, defining_polynomial ) 113 | if d.degree() == 0: 114 | # The rational characteristic and the defining polynomial 115 | # are relatively prime: no rational point of order 2 exists 116 | # and the Frobenius trace must be odd. 117 | return QuotientRing( Integers, 2 )(1) 118 | else: 119 | return QuotientRing( Integers, 2 )(0) 120 | 121 | 122 | from rings.integers.naive import Integers 123 | from rings.quotients.naive import QuotientRing 124 | 125 | def frobenius_trace_mod_l(torsion_group): 126 | """ 127 | Compute the trace of the Frobenius endomorphism modulo @f$ l @f$, where 128 | @f$ l @f$ is the torsion of @p torsion_group. 129 | 130 | The function guesses candidates and verifies the frobenius_equation() for 131 | every point in the @p torsion_group. 132 | 133 | @note A torsion of 2 requires multivariate polynomial arithmetic, 134 | which is unavailable. Therefore @f$ l @f$ must be greater than 2. 135 | Use frobenius_trace_mod_2() to get the trace modulo 2. 136 | 137 | @return The congruence class of the trace of the Frobenius endomorphism. 138 | This is an element of QuotientRing( Integers, l ). 139 | """ 140 | assert torsion_group.torsion() > 2, \ 141 | "torsion 2 requires multivariate polynomial arithmetic" 142 | 143 | ints_mod_torsion = QuotientRing( Integers, 144 | torsion_group.torsion() ) 145 | field_size = torsion_group.curve().field().size() 146 | 147 | for trace_candidate in range( 0, torsion_group.torsion() ): 148 | candidate_congruence = ints_mod_torsion( trace_candidate ) 149 | for point in torsion_group.elements(): 150 | if not frobenius_equation( candidate_congruence, 151 | field_size, 152 | point ): 153 | # Exit inner loop and skip the 'else' clause. 154 | break 155 | else: 156 | # Execute after the iteration completed; skip upon break. 157 | return candidate_congruence 158 | 159 | message = "Frobenius equation held for no trace candidate" 160 | raise ArithmeticError( message ) 161 | 162 | 163 | def frobenius_equation(trace, size, point): 164 | """ 165 | Check whether @p trace could be the trace of the Frobenius endomorphism 166 | @f$ \phi @f$ on the l-torsion group. To test the candidate, use it in 167 | the function that results from applying the characteristic polynomial 168 | @f$ \chi_\phi @f$ to @f$ \phi @f$. This function must then map @p point 169 | onto the point at infinity. 170 | 171 | @param trace The candidate congruence class for the remainder of the 172 | trace of @f$ \phi \mod l @f$. 173 | @param size The size of the field over which the curve was defined. 174 | This is the exponent of the Frobenius endomorphism. 175 | @param point The l-torsion point that will be inserted into 176 | @f$ \chi_{\phi}(phi) @f$. 177 | 178 | @return @c True, if @f$ (\chi_{\phi}(\phi))(\mathtt{point}) @f$ is the 179 | point at infinity. 180 | """ 181 | size_remainder = size % trace.modulus() 182 | result = frobenius( frobenius(point, size), size ) \ 183 | - trace.remainder() * frobenius(point, size) \ 184 | + size_remainder * point 185 | return result.is_infinite() 186 | 187 | 188 | def frobenius(point, q): 189 | """ 190 | The Frobenius endomorphism @f$ \phi @f$. 191 | 192 | @return The point @f$ (x^q, y^q) @f$ if @p point is @f$ (x, y) @f$. 193 | """ 194 | return point.__class__( point.x() ** q, point.y() ** q ) 195 | 196 | 197 | def possible_frobenius_trace_range(field): 198 | """ 199 | Return the interval in which the trace of the Frobenius endomorphism 200 | must reside. 201 | 202 | This is the naive estimation: a curve has at least one point (at infinity), 203 | and at most @f$ 2q + 1 @f$ points, where @f$ q @f$ is the field size. 204 | 205 | @return The interval that must contain the trace of the Frobenius 206 | endomorphism. 207 | """ 208 | # This only depends on the field and holds for any curve over it. 209 | return range( -field.size(), field.size()+1 ) 210 | 211 | 212 | #------------------------------------------------------------------------------ 213 | 214 | from fields.finite.naive import FiniteField 215 | from elliptic_curves.naive import EllipticCurve 216 | 217 | import sys 218 | from support.running import AlgorithmRunner 219 | 220 | def naive_schoof_algorithm( p, A, B, output=sys.stdout ): 221 | p, A, B = int(p), int(A), int(B) 222 | 223 | message = "Counting points on y^2 = x^3 + {A}x + {B} over GF<{p}>: " 224 | print( message.format( p=p, A=A, B=B ), end="", file=output ) 225 | output.flush() 226 | 227 | order = p + 1 - frobenius_trace( EllipticCurve( FiniteField(p), A, B ) ) 228 | print( order, file=output ) 229 | return order 230 | 231 | 232 | if __name__ == "__main__": 233 | runner = AlgorithmRunner( 234 | naive_schoof_algorithm, 235 | algorithm_version="$Rev$" 236 | ) 237 | runner.run() 238 | 239 | -------------------------------------------------------------------------------- /reduced_computation_schoof.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | An implementation of Schoof's algorithm for counting the points of 20 | elliptic curves over finite fields; the implementation avoids unnecessary 21 | computations. 22 | 23 | The implementation is faster than the naive version. Yet, it still uses naive 24 | arithmetic and is badly suited for actual counting tasks. It demonstrates the 25 | performance improvement from a smarter choice of moduli and reordered 26 | computations. 27 | 28 | @see Schoof, R. 29 | "Elliptic Curves Over Finite Fields and the Computation of Square Roots mod p" 30 | in Mathematics of Computation, Vol. 44, No. 170 (Apr. 1985), pp. 483--494. 31 | 32 | @package schoof.reduced_computation 33 | @author Peter Dinges 34 | """ 35 | 36 | from elliptic_curves.l_torsion_group.naive import LTorsionGroup 37 | from support.primes import inverse_primorial, primes_range 38 | from support.quotients import solve_congruence_equations, representative_in_range 39 | 40 | def frobenius_trace(curve): 41 | """ 42 | Compute the trace of the Frobenius endomorphism for the given EllpiticCurve 43 | @p curve. 44 | 45 | This is an implementation of Schoof's original algorithm for counting the 46 | points of an elliptic curve over a finite field. 47 | 48 | @return The trace @f$ t @f$ of the Frobenius endomorphism. The number of 49 | points on the curve then is @f$ q + 1 - t @f$, where @f$ q @f$ 50 | is the size of the finite field over which the curve was defined. 51 | """ 52 | trace_congruences = [] 53 | search_range = hasse_frobenius_trace_range( curve.field() ) 54 | torsion_primes = greedy_prime_factors( 55 | len(search_range), 56 | curve.field().characteristic() 57 | ) 58 | 59 | # To avoid multivariate polynomial arithmetic, make l=2 a special case. 60 | if 2 in torsion_primes: 61 | trace_congruences.append( frobenius_trace_mod_2( curve ) ) 62 | torsion_primes.remove( 2 ) 63 | 64 | torsion_group = LTorsionGroup( curve ) 65 | for prime in torsion_primes: 66 | trace_congruences.append( 67 | frobenius_trace_mod_l( torsion_group( prime ) ) 68 | ) 69 | 70 | trace_congruence = solve_congruence_equations( trace_congruences ) 71 | return representative_in_range( trace_congruence, search_range ) 72 | 73 | 74 | from rings.integers.naive import Integers 75 | from rings.polynomials.naive import Polynomials 76 | from rings.quotients.naive import QuotientRing 77 | from support.rings import gcd 78 | 79 | def frobenius_trace_mod_2(curve): 80 | """ 81 | Compute the trace of the Frobenius endomorphism modulo 2. 82 | 83 | We cannot use the implicit torsion group representation of 84 | frobenius_trace_mod_l() in the case @f$ l=2 @f$ because the second division 85 | polynomial is @f$ y @f$, and multivariate polynomial arithmetic is 86 | unavailable. Implementing it for this single case would be overkill. 87 | 88 | Instead, we test whether there are any rational 2-torsion points. If so, 89 | then @f$ E[2] @f$ is a subgroup of the @p curve and its order divides the 90 | order of the curve (which is the number of rational points). Now, the 91 | number of rational points in @f$ E[2] @f$ can only be 2 or 4 in this case, so 92 | we know that the number of rational points is even. Hence, the Frobenius 93 | trace must then be 0 modulo 2. 94 | 95 | @return 0 if the number of points on the curve is even, 1 otherwise. 96 | The result is a congruence class modulo 2, that is, an element 97 | of @c QuotientRing( Integers, 2 ). 98 | """ 99 | R = Polynomials( curve.field() ) 100 | 101 | x = R(0, 1) 102 | A, B = curve.parameters() 103 | 104 | defining_polynomial = x**3 + A*x + B 105 | rational_characteristic = x**curve.field().size() - x 106 | 107 | # gcd() returns a gcd, which may have any unit as leading coefficient. 108 | # For relatively prime polynomials, the gcd is constant. 109 | if gcd( rational_characteristic, defining_polynomial ).degree() == 0: 110 | # The rational characteristic and the defining polynomial are 111 | # relatively prime. Thus there is no rational point of order 2. 112 | return QuotientRing( Integers, 2 )(1) 113 | else: 114 | return QuotientRing( Integers, 2 )(0) 115 | 116 | 117 | from rings.integers.naive import Integers 118 | from rings.quotients.naive import QuotientRing 119 | 120 | def frobenius_trace_mod_l(torsion_group): 121 | """ 122 | Compute the trace of the Frobenius endomorphism modulo @f$ l @f$, where 123 | @f$ l @f$ is the torsion of @p torsion_group. 124 | 125 | The function guesses candidates and verifies whether the function that 126 | results from applying the characteristic polynomial @f$ \chi_\phi @f$ 127 | to @f$ \phi @f$ maps every point in the @p torsion_group onto the point 128 | at infinity. 129 | 130 | @note A torsion of 2 requires multivariate polynomial arithmetic, 131 | which is unavailable. Therefore @f$ l @f$ must be greater than 2. 132 | Use frobenius_trace_mod_2() to get the trace modulo 2. 133 | 134 | @return The congruence class of the trace of the Frobenius endomorphism. 135 | This is an element of @c QuotientRing( Integers, l ). 136 | """ 137 | assert torsion_group.torsion() > 2, \ 138 | "torsion 2 requires multivariate polynomial arithmetic" 139 | 140 | torsion_quotient_ring = QuotientRing( Integers, torsion_group.torsion() ) 141 | field_size = torsion_group.curve().field().size() 142 | 143 | # Note: Technically, there could be several points so we would have to 144 | # filter the one candidate that worked for all points in the end. 145 | # Luckily, there is only one point. 146 | for point in torsion_group.elements(): 147 | frobenius_point = frobenius( point, field_size ) 148 | frobenius2_point = frobenius( frobenius_point, field_size ) 149 | determinant_point = ( field_size % torsion_group.torsion() ) * point 150 | 151 | point_sum = frobenius2_point + determinant_point 152 | if point_sum.is_infinite(): 153 | return torsion_quotient_ring( 0 ) 154 | 155 | trace_point = frobenius_point 156 | for trace_candidate in range( 1, (torsion_group.torsion()+1) // 2 ): 157 | if point_sum.x() == trace_point.x(): 158 | if point_sum.y() == trace_point.y(): 159 | return torsion_quotient_ring( trace_candidate ) 160 | else: 161 | return torsion_quotient_ring( -trace_candidate ) 162 | else: 163 | trace_point += frobenius_point 164 | 165 | message = "Frobenius equation held for no trace candidate" 166 | raise ArithmeticError( message ) 167 | 168 | 169 | def frobenius(point, q): 170 | """ 171 | The Frobenius endomorphism @f$ \phi @f$. 172 | 173 | @return The point @f$ (x^q, y^q) @f$ if @p point is @f$ (x, y) @f$. 174 | """ 175 | return point.__class__( point.x() ** q, point.y() ** q ) 176 | 177 | 178 | from math import ceil, sqrt 179 | 180 | def hasse_frobenius_trace_range(field): 181 | """ 182 | Return the interval in which the trace of the Frobenius endomorphism 183 | must reside (using Hasse's theorem). 184 | 185 | Hasse's theorem gives a limit for the trace @f$ t @f$ of the Frobenius 186 | endomorphism on an elliptic curve over a field with @f$ q @f$ elements: 187 | @f$ \left|t\right| \leq 2\sqrt{q} @f$. 188 | 189 | @return The interval that must contain the trace of the Frobenius 190 | endomorphism according to Hasse's theorem. 191 | """ 192 | # This only depends on the field and holds for any curve over it. 193 | l = 2 * ceil( sqrt( field.size() ) ) 194 | return range( -l, l+1 ) 195 | 196 | 197 | from support.primes import primes_range, inverse_primorial 198 | 199 | def greedy_prime_factors(n, shunned=0): 200 | """ 201 | Return a list of the first primes whose product is greater than, or equal 202 | to @p n, but do not use @p shunned. 203 | 204 | For example, if @p n is 14, then the returned list will consist of 3 and 205 | 5, but not 2, because 3 times 5 is greater than 14. The function behaves 206 | like inverse_primorial() except that it removes unnecessary smaller primes. 207 | 208 | @note Canceling of unnecessary primes follows a greedy algorithm. 209 | Therefore the choice of primes might be suboptimal; perfect 210 | choice, however, is an NP-complete problem (KNAPSACK). 211 | 212 | @note This function uses primes_range() to obtain a list of primes. 213 | See the notes there for use case limitations. 214 | """ 215 | primes = primes_range( 2, n+1 ) 216 | 217 | # Find the smallest product of primes that is at least n, but don't use 218 | # the shunned prime. 219 | product = 1 220 | for index, prime in enumerate( primes ): 221 | if prime != shunned: 222 | product *= prime 223 | if product >= n: 224 | break 225 | 226 | # Throw away excess primes 227 | primes = primes[ : index+1 ] 228 | if shunned in primes: 229 | primes.remove( shunned ) 230 | 231 | # Try to cancel unnecessary primes, largest first. 232 | # (This greedy search is not optimal; however, we did not set out to solve 233 | # the KNAPSACK problem, did we?) 234 | for index, prime in enumerate( reversed( primes ) ): 235 | canceled_product = product / prime 236 | if canceled_product >= n: 237 | product = canceled_product 238 | primes[ -(index+1) ] = 0 239 | 240 | return list( filter( None, primes ) ) 241 | 242 | 243 | #------------------------------------------------------------------------------ 244 | 245 | from fields.finite.naive import FiniteField 246 | from elliptic_curves.naive import EllipticCurve 247 | 248 | import sys 249 | from support.running import AlgorithmRunner 250 | 251 | def reduced_computation_schoof_algorithm( p, A, B, output=sys.stdout ): 252 | p, A, B = int(p), int(A), int(B) 253 | 254 | message = "Counting points of y^2 = x^3 + {A}x + {B} over GF<{p}>: " 255 | print( message.format( p=p, A=A, B=B ), end="", file=output ) 256 | output.flush() 257 | 258 | order = p + 1 - frobenius_trace( EllipticCurve( FiniteField(p), A, B ) ) 259 | print( order, file=output ) 260 | return order 261 | 262 | 263 | if __name__ == "__main__": 264 | runner = AlgorithmRunner( 265 | reduced_computation_schoof_algorithm, 266 | algorithm_version="$Rev$" 267 | ) 268 | runner.run() 269 | 270 | -------------------------------------------------------------------------------- /rings/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | Ring related classes and functions (ring as in integers). 20 | 21 | @package rings 22 | @author Peter Dinges 23 | """ 24 | 25 | class CommutativeRing: 26 | """ 27 | A base class for elements of commutative rings that provides default 28 | operator overloading. 29 | 30 | For example, subtraction is defined as addition of the additive inverse. 31 | Derived classes therefore only have to implement the following operations 32 | to support the complete operator set: 33 | - __bool__(): Zero testing (@c True if not zero) 34 | - __eq__(): Equality testing with the @c == operator (@c True if equal) 35 | - __add__(): Addition with the @c + operator; @c self is the left summand 36 | - __neg__(): Negation with the @c - unary minus operator (the additive inverse) 37 | - __mul__(): Multiplication with the @c * operator; @c self is the 38 | left factor 39 | - __divmod__(): Division with remainder; @c self is the dividend (left element) 40 | 41 | The following operations are implemented in terms of above operations using 42 | the field axioms: 43 | - __neq__(): Inequality testing with the @c != operator 44 | - __radd__(): Addition with the @c + operator; @c self is the right summand 45 | - __sub__(): Subtraction with the @c - operator; @c self is the minuend 46 | (left element) 47 | - __rsub__(): Subtraction with the @c - operator; @c self is the 48 | subtrahend (right element) 49 | - __rmul__(): Multiplication with the @c * operator; @c self is the 50 | right factor 51 | - __rdivmod__(): Division with remainder using @c divmod(); @c self is the 52 | divisor (right element) 53 | - __floordiv__(): Remainder ignoring division with the @c // operator; 54 | @c self is the dividend (left element) 55 | - __rfloordiv__(): Remainder ignoring division with the @c // operator; 56 | @c self is the divisor (right element) 57 | - __mod__(): Remainder of @c self modulo @c other with the @c % operator 58 | - __rmod__(): Remainder of @c other modulo @c self with the @c % operator 59 | - __pow__(): Exponentiation with integers 60 | """ 61 | 62 | #- Template Operations ---------------------------------------------------- 63 | 64 | def __neq__(self, other): 65 | """ 66 | Test whether another element @p other is different from @p self; return 67 | @c True if that is the case. The infix operator @c != calls this 68 | method; for example: 69 | @code 70 | if self != other: 71 | do_something() 72 | @endcode 73 | """ 74 | return not self.__eq__( other ) 75 | 76 | def __radd__(self, other): 77 | """ 78 | Return the sum of @p self and @p other. The infix operator @c + calls 79 | this method if @p self is the right summand and @p other.__add__() 80 | returns @c NotImplemented. For example: 81 | @code 82 | result = other + self 83 | @endcode 84 | """ 85 | # Additive subgroup is commutative: 86 | # other + self == self + other 87 | return self.__add__( other ) 88 | 89 | def __sub__(self, other): 90 | """ 91 | Return the difference of @p self and @p other. The infix operator @c - 92 | calls this method if @p self is the minuend (left element); for example: 93 | @code 94 | result = self - other 95 | @endcode 96 | """ 97 | return self.__add__( -other ) 98 | 99 | def __rsub__(self, other): 100 | """ 101 | Return the difference of @p other and @p self. The infix operator @c - 102 | calls this method if @p self is the subtrahend (right element) and 103 | @c other.__sub__() returns @c NotImplemented. For example: 104 | @code 105 | result = other - self 106 | @endcode 107 | """ 108 | # Additive subgroup is commutative: 109 | # other - self == -( self - other ) 110 | return -self.__sub__( other ) 111 | 112 | def __rmul__(self, other): 113 | """ 114 | Return the product of @p self and @p other. The infix operator @c * 115 | calls this method if @p self is the right factor and @c other.__mul__() 116 | returns @c NotImplemented. For example: 117 | @code 118 | result = other * self 119 | @endcode 120 | """ 121 | # Commutative ring: 122 | # other * self == self * other 123 | return self.__mul__( other ) 124 | 125 | def __rdivmod__(self, other): 126 | """ 127 | Return quotient and remainder of @p other divided by @p self. The 128 | @c divmod() built-in function calls this method if @p self is the 129 | divisor and @c other.__divmod__() returns @c NotImplemented. 130 | For example: 131 | @code 132 | quotient, remainder = divmod( other, self ) 133 | @endcode 134 | """ 135 | return divmod( self.__class__( other ), self ) 136 | 137 | def __floordiv__(self, other): 138 | """ 139 | Return the quotient of @p self divided by @p other and ignore the 140 | remainder. The @c // operator calls this method; for example: 141 | @code 142 | quotient = self // other 143 | @endcode 144 | """ 145 | return divmod( self, other )[0] 146 | 147 | def __rfloordiv__(self, other): 148 | """ 149 | Return the quotient of @p self divided by @p other and ignore the 150 | remainder. The @c // operator calls this method if @p self is the 151 | divisor and @c other.__floordiv__() returns @c NotImplemented. 152 | For example: 153 | @code 154 | quotient = other // self 155 | @endcode 156 | """ 157 | return divmod( other, self )[0] 158 | 159 | def __mod__(self, other): 160 | """ 161 | Return @p self modulo @p other, that is, the remainder of @p self 162 | divided by @p other. The @c % operator calls this method; for example: 163 | @code 164 | remainder = self % other 165 | @endcode 166 | """ 167 | return divmod( self, other )[1] 168 | 169 | def __rmod__(self, other): 170 | """ 171 | Return @p other modulo @p self, that is, the remainder of @p other 172 | divided by @p self. The @c % operator calls this method if @p self is 173 | the divisor and @c other.__mod__() returns @c NotImplemented. 174 | For example: 175 | @code 176 | remainder = other % self 177 | @endcode 178 | """ 179 | return divmod( other, self )[1] 180 | 181 | def __pow__(self, n): 182 | """ 183 | Return @p self taken to the @p n-th power. The infix operator @c ** 184 | calls this method; for example: 185 | @code 186 | result = self ** n 187 | @endcode 188 | 189 | @note The implementation uses the most naive by-the-book method for 190 | exponentiation: the element is multiplied @p n-1 times with 191 | itself. This is slow (and might skin your cat!). However, the 192 | purpose of this code is to be easy to understand, not fast. 193 | 194 | @param n The exponent; it is expected to be a non-negative integer 195 | type. Negative integers and floats are unsupported. 196 | """ 197 | # This only makes sense for integer arguments. 198 | result = self 199 | for i in range(1, int(n)): 200 | result = result * self 201 | 202 | return result 203 | 204 | 205 | #- Base Operations (Defined in Derived Classes) --------------------------- 206 | 207 | def __bool__(self): 208 | """ 209 | Test whether the element is non-zero: return @c True if, and only if, 210 | it is non-zero. Otherwise return @c False. Implicit conversions to 211 | boolean (truth) values use this method; for example when @c x is an 212 | element of a CommutativeRing: 213 | @code 214 | if x: 215 | do_something() 216 | @endcode 217 | 218 | @exception NotImplementedError if this method is called; subclasses 219 | must implement this operation. 220 | """ 221 | raise NotImplementedError 222 | 223 | def __eq__(self, other): 224 | """ 225 | Test whether another element @p other is equal to @p self; return 226 | @c True if that is the case. The infix operator @c == calls this 227 | method; for example: 228 | @code 229 | if self == other: 230 | do_something() 231 | @endcode 232 | 233 | @exception NotImplementedError if this method is called; subclasses 234 | must implement this operation. 235 | """ 236 | raise NotImplementedError 237 | 238 | def __add__(self, other): 239 | """ 240 | Return the sum of @p self and @p other. The infix operator @c + calls 241 | this method if @p self is the left summand; for example: 242 | @code 243 | result = self + other 244 | @endcode 245 | 246 | @exception NotImplementedError if this method is called; subclasses 247 | must implement this operation. 248 | """ 249 | raise NotImplementedError 250 | 251 | def __neg__(self): 252 | """ 253 | Return the additive inverse of @p self. The unary minus operator @c -x 254 | calls this method; for example: 255 | @code 256 | negated = -self 257 | @endcode 258 | 259 | @exception NotImplementedError if this method is called; subclasses 260 | must implement this operation. 261 | """ 262 | raise NotImplementedError 263 | 264 | def __mul__(self, other): 265 | """ 266 | Return the product of @p self and @p other. The infix operator @c + calls 267 | this method if @p self is the left factor; for example: 268 | @code 269 | result = self * other 270 | @endcode 271 | 272 | @exception NotImplementedError if this method is called; subclasses 273 | must implement this operation. 274 | """ 275 | raise NotImplementedError 276 | 277 | def __divmod__(self, other): 278 | """ 279 | Return quotient and remainder of @p self divided by @p other. The 280 | @c divmod() built-in function calls this method; for example: 281 | @code 282 | quotient, remainder = divmod( self, other ) 283 | @endcode 284 | 285 | @exception NotImplementedError if this method is called; subclasses 286 | must implement this operation. 287 | """ 288 | raise NotImplementedError 289 | -------------------------------------------------------------------------------- /rings/integers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/rings/integers/__init__.py -------------------------------------------------------------------------------- /rings/integers/naive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A wrapper that extends Python's built-in integer type @c int with the expected 20 | ring interface. 21 | 22 | @package rings.integers.naive 23 | @author Peter Dinges 24 | """ 25 | 26 | from support.profiling import profiling_name 27 | 28 | @profiling_name("Z") 29 | class Integers(int): 30 | """ 31 | The ring of integers. This wrapper class extends Python's built-in integer 32 | type @c int with the interface provided by the other ring classes. 33 | 34 | @see rings.polynomials.naive.Polynomials, 35 | rings.quotients.naive.QuotientRing 36 | """ 37 | 38 | @staticmethod 39 | def zero(): 40 | """ 41 | Return @c 0, the neutral element of integer addition. 42 | """ 43 | return 0 44 | 45 | @staticmethod 46 | def one(): 47 | """ 48 | Return @c 1, the neutral element of integer multiplication. 49 | """ 50 | return 1 51 | -------------------------------------------------------------------------------- /rings/polynomials/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/rings/polynomials/__init__.py -------------------------------------------------------------------------------- /rings/quotients/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/rings/quotients/__init__.py -------------------------------------------------------------------------------- /rings/quotients/naive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A naive implementation of quotient rings (factor rings, residue class rings). 20 | 21 | @package rings.quotients.naive 22 | @author Peter Dinges 23 | """ 24 | 25 | from rings import CommutativeRing 26 | 27 | from support.types import template 28 | from support.operators import operand_casting 29 | from support.profiling import profiling_name, local_method_names 30 | from support.rings import extended_euclidean_algorithm 31 | 32 | # FIXME: Having ring and modulus as parameters is redundant. Obviously we have 33 | # ring == modulus.__class__ 34 | 35 | @operand_casting 36 | @local_method_names 37 | @profiling_name( "{_ring}/{_modulus}" ) 38 | class QuotientRing( CommutativeRing, metaclass=template( "_ring", "_modulus" ) ): 39 | """ 40 | A ring of residue classes of ring elements modulo a fixed ring element; the 41 | residue classes (quotient ring elements) support infix notation for ring 42 | operations, as well as mixed argument types. 43 | 44 | This is a template class that must be instantiated with the source ring 45 | and modulus. Use it, for example, as follows: 46 | @code 47 | # Instantiate the template; Z4 is a class (here: integer congruence classes) 48 | Z4 = QuotientRing( rings.integers.naive.Integers, 4 ) 49 | x = Z4(1) # Create a residue class: (1 mod 4) 50 | y = Z4(3) # Another residue class: (2 mod 4) 51 | z = x+y # z is (0 mod 4) 52 | z == Z4(2) * 2 # This true because 4 == 0 modulo 4 53 | type(z) is Z4 # This is also true 54 | @endcode 55 | 56 | Other examples of QuotientRing template instances are: 57 | @code 58 | Z17 = QuotientRing( rings.integers.naive.Integers, 17 ) # Z/17Z 59 | 60 | # The ring (Z/2Z[x]) / (x^3 + x + 1) is isomorphic to GF8, 61 | # the field with 8 elements 62 | GF2 = fields.finite.naive.FiniteField( 2 ) 63 | GF2x = rings.polynomials.naive.Polynomials( GF2 ) 64 | GF8 = QuotientRing( GF2x, GF2x(1, 1, 0, 1) ) 65 | @endcode 66 | 67 | A quotient ring, factor ring, or residue class ring, is a ring 68 | @f$ R/(m) @f$, where @f$ R @f$ is the source ring, and @f$ (m) @f$ 69 | denotes the ideal generated by the element @f$ m\in R @f$. The ring 70 | operations carry over to quotient classes in a natural way: if 71 | @f$ x + y = z @f$ in @f$ R @f$, then @f$ [x] + [y] = [z] @f$ 72 | in @f$ R/(m) @f$ (by the canonical homomorphism). 73 | 74 | With the quotient ring operations being defined in terms of source ring 75 | operations, elements of the source ring must implement: 76 | - __bool__(): Zero testing (@c True if not zero) 77 | - __eq__(): Equality testing with the @c == operator (@c True if equal) 78 | - __add__(): Addition with the @c + operator; @c self is the left summand 79 | - __neg__(): Negation with the @c - unary minus operator (the additive inverse) 80 | - __mul__(): Multiplication with the @c * operator; @c self is the 81 | left factor 82 | - __divmod__(): Division with remainder; @c self is the dividend (left element) 83 | 84 | @note The implementation emphasizes simplicity over speed; it omits 85 | possible optimizations. 86 | 87 | @note The class uses the operand_casting() decorator: @c other operands in 88 | binary operations will first be treated as QuotientRing elements. 89 | If that fails, the operation will be repeated after @p other was fed 90 | to the constructor __init__(). If that fails, too, then the 91 | operation returns @c NotImplemented (so that @p other.__rop__() 92 | might be invoked). 93 | 94 | @see For example, Robinson, Derek J. S., 95 | "An Introduction to Abstract Algebra", p. 106. 96 | """ 97 | 98 | #- Instance Methods ----------------------------------------------------------- 99 | 100 | def __init__(self, representative): 101 | """ 102 | Construct a new residue class @p representative modulo modulus(). 103 | 104 | If the @p representative already is an element of this QuotientRing 105 | class, then the new element is a copy of @p representative. 106 | """ 107 | if isinstance( representative, self.__class__ ): 108 | self.__remainder = representative.__remainder 109 | elif isinstance( representative, self._modulus.__class__ ): 110 | self.__remainder = representative % self._modulus 111 | else: 112 | m = self._modulus 113 | self.__remainder = m.__class__( representative ) % m 114 | 115 | 116 | def remainder(self): 117 | """ 118 | Return the remainder of the residue class (QuotientRing element) 119 | @p self. This is an element of the source ring(), not a residue class. 120 | """ 121 | return self.__remainder 122 | 123 | 124 | def __bool__(self): 125 | """ 126 | Test whether the residue class (QuotientRing element) is non-zero: 127 | return @c True if, and only if, the remainder modulo modulus() is 128 | non-zero. Return @c False if the remainder is zero. 129 | 130 | Implicit conversions to boolean (truth) values use this method, for 131 | example when @c x is an element of a QuotientRing: 132 | @code 133 | if x: 134 | do_something() 135 | @endcode 136 | """ 137 | return bool( self.__remainder ) 138 | 139 | 140 | def __eq__(self, other): 141 | """ 142 | Test whether another residue class (QuotientRing element) @p other is 143 | equivalent to @p self; return @c True if that is the case. The infix 144 | operator @c == calls this method. 145 | 146 | Two residue classes @f$ [x], [y] @f$ are equal if, and only if, the 147 | difference of two representatives is a multiple of the modulus(): 148 | @f$ x-y = m\cdot z @f$. 149 | """ 150 | # The representative is always the remainder; an equality test suffices 151 | return self.__remainder == other.remainder() 152 | 153 | 154 | def __add__(self, other): 155 | """ 156 | Return the sum of @p self and @p other. The infix operator @c + calls 157 | this method. 158 | 159 | The sum of two residue classes (QuotientRing elements) @f$ [x], [y] @f$ 160 | is the residue class @f$ [x + y] @f$. 161 | """ 162 | return self.__class__( 163 | self.__remainder + other.remainder() 164 | ) 165 | 166 | 167 | def __neg__(self): 168 | """ 169 | Return the additive inverse of @p self, which is @f$ [-x] @f$ 170 | for a residue class (QuotientRing element) @f$ [x] @f$. The negation 171 | operator @c -x (unary minus) calls this method. 172 | """ 173 | return self.__class__( -self.__remainder ) 174 | 175 | 176 | def __mul__(self, other): 177 | """ 178 | Return the product of @p self and @p other. The infix operator @c * 179 | calls this method. 180 | 181 | The product of two residue classes (QuotientRing elements) 182 | @f$ [x], [y] @f$ is the residue class @f$ [x \cdot y] @f$. 183 | """ 184 | return self.__class__( 185 | self.__remainder * other.remainder() 186 | ) 187 | 188 | 189 | def __truediv__(self, other): 190 | """ 191 | Return the quotient of @p self and @p other: multiply @p self with 192 | @c other.multiplicative_inverse(), so that 193 | @code 194 | (self / other) * other == self 195 | @endcode 196 | The infix operator @c / calls this method. 197 | 198 | @exception ZeroDivisionError if @p other is not a unit, that is, has 199 | no multiplicative inverse. 200 | """ 201 | return self * other.multiplicative_inverse() 202 | 203 | def __rtruediv__(self, other): 204 | """ 205 | Return the quotient of @p other and @p self: multiply @p other with 206 | @c self.multiplicative_inverse(), so that 207 | @code 208 | (other / self) * self == other 209 | @endcode 210 | The infix operator @c / calls this method if other.__truediv__() 211 | returned @c NotImplemented. 212 | 213 | @exception ZeroDivisionError if @p other is not a unit, that is, has 214 | no multiplicative inverse. 215 | """ 216 | return other * self.multiplicative_inverse() 217 | 218 | 219 | def multiplicative_inverse(self): 220 | """ 221 | Return an residue class (QuotientRing element) @c n such that 222 | @c n * self is one(). 223 | 224 | @exception ZeroDivisionError if @p self is not a unit, that is, has 225 | no multiplicative inverse. 226 | """ 227 | if not self.__remainder: 228 | raise ZeroDivisionError 229 | 230 | inverse, ignore, gcd = \ 231 | extended_euclidean_algorithm( self.__remainder, self._modulus ) 232 | 233 | if gcd == self._ring.one(): 234 | return self.__class__( inverse ) 235 | else: 236 | message = "element has no inverse: representative and modulus " \ 237 | "are not relatively prime" 238 | raise ZeroDivisionError( message ) 239 | 240 | 241 | #- Class Methods----------------------------------------------------------- 242 | 243 | @classmethod 244 | def modulus(cls): 245 | """ 246 | Return the quotient ring's modulus; this is an element of the 247 | source ring(), not a residue class (QuotientRing element). 248 | """ 249 | return cls._modulus 250 | 251 | 252 | @classmethod 253 | def ring(cls): 254 | """ 255 | Return the source ring. 256 | """ 257 | return cls._ring 258 | 259 | 260 | @classmethod 261 | def zero(cls): 262 | """ 263 | Return the quotient ring's neutral element of addition: the residue 264 | class (QuotientRing element) of ring().zero() 265 | """ 266 | return cls( cls._ring.zero() ) 267 | 268 | 269 | @classmethod 270 | def one(cls): 271 | """ 272 | Return the quotient ring's neutral element of multiplication: the 273 | residue class (QuotientRing element) of ring().one() 274 | """ 275 | return cls( cls._ring.one() ) 276 | -------------------------------------------------------------------------------- /support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pdinges/python-schoof/d5f720ba5f11f3dc6761a2ba9bb61e6deaa342d9/support/__init__.py -------------------------------------------------------------------------------- /support/operators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A class decorator for operand casting in binary operations. Use it to support 20 | mixed mode operations in arithmetic objects, for example, to be able to add 21 | regular integers to finite field elements. 22 | 23 | @package support.operators 24 | @author Peter Dinges 25 | """ 26 | 27 | def operand_casting(cls): 28 | """ 29 | Class decorator that adds operand casting to binary operations. 30 | 31 | The decorator offers an easy way to support mixed mode operations in 32 | arithmetic objects. For example, it allows polynomials to interpret 33 | integer constants as constant polynomials without adding overhead to the 34 | operator methods like __add__() etc. 35 | 36 | @note The decorator assumes that operation definitions remain constant 37 | during runtime, that is, will not be re-assigned after 38 | template specialization. 39 | 40 | @see The sources of rings.polynomials.naive.Polynomials 41 | for a usage example. 42 | """ 43 | # @f( arg ) 44 | # class A: pass 45 | # resolves to 46 | # class A: pass 47 | # A = f( arg )( A ) 48 | # See section "Function definitions" in the Python language reference. 49 | if getattr( cls.__class__, "__operand_casting__", False ): 50 | return cls 51 | 52 | # Simply wrap the meta-class's type creation method. On type creation, 53 | # wrap the new type object's methods. 54 | original_new = cls.__class__.__new__ 55 | 56 | def operation_casting_new(meta_class, class_name, bases, class_dict, **kw_arguments): 57 | class_object = original_new( meta_class, class_name, bases, class_dict, **kw_arguments ) 58 | 59 | # Modify only fully specialized templates. 60 | if getattr( class_object.__class__, "__unbound_parameters__", False ): 61 | return class_object 62 | 63 | for operation_name in [ "__{0}__".format( op ) for op in __binary_operation_names ]: 64 | # Only wrap operations in the actual class object, not in its bases. 65 | # (getattr() would retrieve attributes from base classes.) 66 | if operation_name in class_object.__dict__: 67 | operation = class_object.__dict__[ operation_name ] 68 | setattr( class_object, operation_name, __casting_wrapped( operation ) ) 69 | 70 | return class_object 71 | 72 | cls.__class__.__new__ = operation_casting_new 73 | setattr( cls.__class__, "__operand_casting__", True ) 74 | return cls 75 | 76 | 77 | # Wrap these operations 78 | __binary_operation_names = [ 79 | "eq", "neq", 80 | "add", "radd", "sub", "rsub", 81 | "mul", "rmul", "truediv", "rtruediv", 82 | "divmod", "rdivmod", "floordiv", "rfloordiv", "mod", "rmod" 83 | 84 | ] 85 | 86 | 87 | from .profiling import rename_function 88 | 89 | def __casting_wrapped( operation ): 90 | """ 91 | Return the wrapped @p operation (without nesting the wrappers). 92 | 93 | This function is not intended for direct use; rather, employ 94 | @c operand_casting() 95 | """ 96 | if hasattr( operation, "__wrapped_method__" ): 97 | return operation 98 | 99 | # A function outside operation_casting_new()'s scope is required 100 | # to avoid variable binding problems. 101 | # (If defined inside the scope, 'operation' points at the 102 | # last used value for _all_ wrappers.) 103 | def casting_wrapper( self, other ): 104 | if self.__class__ is other.__class__: 105 | return operation( self, other ) 106 | 107 | try: 108 | return operation( self, self.__class__( other ) ) 109 | except TypeError: 110 | return NotImplemented 111 | 112 | # Rename the code object to reflect the wrapped operation's name. 113 | # Otherwise, the profile lists all wrapped operations 114 | # as 'wrapped_operation'. 115 | rename_function( 116 | casting_wrapper, 117 | "{op}_casting_wrapper".format( op = operation.__name__ ) 118 | ) 119 | setattr( casting_wrapper, "__wrapped_method__", operation ) 120 | return casting_wrapper 121 | -------------------------------------------------------------------------------- /support/primes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | Auxiliary functions for computations related to prime numbers. 20 | 21 | The algorithms and implementations are kept as simple as possible. They are 22 | not meant for high performance computing, but for instructive purposes. 23 | 24 | @package support.primes 25 | @author Peter Dinges 26 | """ 27 | 28 | from math import ceil, sqrt 29 | 30 | def primes_range(lower_bound, upper_bound): 31 | """ 32 | Return a list of all primes in the range [@p lower_bound, @p upper_bound). 33 | 34 | @note The function returns an actual list, not a proper range object. 35 | Furthermore, it computes the complete list of primes up to 36 | @p upper_bound on each call. These characteristics make it badly 37 | suited for anything involving large ranges or large primes. 38 | 39 | @param lower_bound The minimum size for returned primes; typically 40 | this is an integer, but any number object with 41 | an integer interpretation works. 42 | @param upper_bound The strict upper bound for returned primes: all 43 | returned primes are strictly less than this 44 | number. 45 | """ 46 | # The sieve of Eratosthenes 47 | primes = set( range(2, upper_bound) ) 48 | sieve_bound = ceil( sqrt(upper_bound) ) 49 | for i in range(2, sieve_bound): 50 | if i in primes: 51 | multiples = [ i*j for j in range(2, ceil(upper_bound / i)) ] 52 | primes -= set(multiples) 53 | return sorted(list( primes - set(range(2, lower_bound)) )) 54 | 55 | 56 | def inverse_primorial(n, shunned = 0): 57 | """ 58 | Return the smallest prime @c p such that the product of all primes 59 | up to @c p (inclusive) is at least @p n. For example, 60 | @c inverse_primorial(30) is 5, and @c inverse_primorial(31) is 7. 61 | 62 | @note This function uses primes_range() to obtain a list of primes. 63 | See the notes there for use case limitations. 64 | 65 | @param n The number object that the product must exceed in size. 66 | The object must have an integer interpretation. 67 | 68 | @return The prime @c p such that the product of all primes up to @c p 69 | (inclusive) is at least @p n; if @p n is too small (less than 2) 70 | the result is 2. 71 | """ 72 | product = 1 73 | # A much smaller upper bound should suffice; however, we play it safe. 74 | for prime in primes_range(2, int(n)+1): 75 | if prime != shunned: 76 | product *= prime 77 | if product >= n: 78 | return prime 79 | # Return the smallest prime if n is too small 80 | return 2 81 | -------------------------------------------------------------------------------- /support/profiling.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | Class decorators for more expressive call profiles. 20 | 21 | The Python profiler in the @c cProfile module registers methods as elements 22 | of the class that contains their source code. Thus, @c cProfile merges calling 23 | statistics of the defining class and subclasses that do not re-implement the 24 | methods (such as template class specializations). The decorator 25 | @c local_method_names() addresses this problem. Also, in case of nested template 26 | classes (say, elliptic_curves.polynomials.naive.CurvePolynomials), 27 | the template parameters bloat the type name; the decorator @c profiling_name() 28 | alleviates the issue. 29 | 30 | @package support.profiling 31 | @author Peter Dinges 32 | """ 33 | 34 | def profiling_name(name): 35 | """ 36 | A class decorator that sets the @p name that will show up in profiles 37 | generated with the @c cProfile module. 38 | 39 | Usage example: 40 | @code 41 | @local_method_names 42 | @profiling_name( "GF<{_size}>" ) 43 | class FiniteField( Field, metaclass=template( "_size" ) ): 44 | ... 45 | @endcode 46 | 47 | @param name A string that contains the class name (which is usually 48 | shorter than the original name). For template classes, the 49 | string may contain format-string variables for the 50 | parameters. 51 | 52 | @note The function requires the @c local_method_name() decorator to show 53 | an effect. 54 | 55 | @see The sources of rings.quotients.naive.QuotientRing for a usage 56 | example and support.types.template() for information about template 57 | classes. 58 | """ 59 | def class_wrapper(cls): 60 | setattr(cls, "__profiling_name__", name) 61 | return cls 62 | return class_wrapper 63 | 64 | 65 | from .types import is_incomplete 66 | 67 | def local_method_names(cls): 68 | """ 69 | A class decorator that makes the function names used by the @c cProfile 70 | module local to the class of use (rather than the class of definition). 71 | 72 | The @c cProfile module uses the name of a function's code object as the 73 | profile name. Therefore, calls to methods in subclasses that do not 74 | re-implement the method are counted as calls to the parent class. In 75 | template classes, this makes the call profile too coarse. 76 | 77 | Use profiling_name() to get shorter class names. 78 | 79 | @note The decorator copies the code objects; it "flattens" the class. 80 | Therefore, re-assigning methods will annihilate the decorator 81 | effects for the method. 82 | 83 | @see The sources of rings.quotients.naive.QuotientRing for a usage 84 | example and support.types.template() for information about template 85 | classes. 86 | """ 87 | if getattr( cls.__class__, "__operation_renaming__", False ): 88 | return cls 89 | 90 | original_new = cls.__class__.__new__ 91 | 92 | def prefixing_new(meta_class, class_name, bases, class_dict, **kw_arguments): 93 | class_object = original_new( meta_class, class_name, bases, class_dict, **kw_arguments ) 94 | 95 | if is_incomplete( class_object ): 96 | return class_object 97 | 98 | if "__operation_renaming__" in class_object.__dict__: 99 | return class_object 100 | 101 | profiling_name = __profiling_str( class_object ) 102 | __localize_method_names( 103 | class_object.__class__, 104 | [ "__new__", "__call__" ], 105 | "{cls}::{{meth}}".format( cls = profiling_name ) 106 | ) 107 | 108 | __flatten_methods( class_object ) 109 | __localize_method_names( 110 | class_object, 111 | __method_names( class_object ), 112 | "{cls}::{{meth}}".format( cls = profiling_name ) 113 | ) 114 | 115 | 116 | setattr( class_object, "__operation_renaming__", True ) 117 | return class_object 118 | 119 | cls.__class__.__new__ = prefixing_new 120 | setattr( cls.__class__, "__operation_renaming__", True ) 121 | 122 | return cls 123 | 124 | 125 | 126 | def rename_function( function, name, filename=None, firstlineno=-1 ): 127 | """ 128 | Rename a function and its associated code object. 129 | 130 | This is handy when using the @c profile and @c cProfile modules: 131 | both retrieve the function names from the code object 132 | (the @c co_name attribute); @c __name__ is ignored. 133 | """ 134 | # Renaming a function in the profile thus requires generating a new 135 | # code object. As CodeType.__doc__ notes, this is not for the 136 | # faint of heart. 137 | 138 | # Modify the unbound function object of methods 139 | if hasattr( function, "__func__" ): 140 | function = function.__func__ 141 | 142 | try: 143 | code = function.__code__ 144 | 145 | except AttributeError: 146 | message = "expected '{func}' to have an associated code object ('__code__' attribute)" 147 | raise ValueError( message.format( function ) ) 148 | 149 | # Copy old values if unspecified 150 | if filename is None: 151 | filename = code.co_filename 152 | if firstlineno == -1: 153 | firstlineno = code.co_firstlineno 154 | 155 | renamed_code = types.CodeType( 156 | code.co_argcount, 157 | code.co_kwonlyargcount, 158 | code.co_nlocals, 159 | code.co_stacksize, 160 | code.co_flags, 161 | code.co_code, 162 | code.co_consts, 163 | code.co_names, 164 | code.co_varnames, 165 | str( filename ), 166 | str( name ), 167 | int( firstlineno ), 168 | code.co_lnotab, 169 | code.co_freevars, 170 | code.co_cellvars 171 | ) 172 | 173 | function.__name__ = str( name ) 174 | function.__code__ = renamed_code 175 | 176 | 177 | def __method_names( class_object ): 178 | """ 179 | Return a dictionary with the methods defined in the class (not counting 180 | inherited methods). 181 | 182 | This function is not intended for direct use. 183 | """ 184 | return [ key for key, value in class_object.__dict__.items() \ 185 | if type( value ) in function_types ] 186 | 187 | 188 | def __localize_method_names( class_object, method_names, format_string ): 189 | """ 190 | Make all inherited (and not re-implemented) methods local to the class 191 | and rename them accordingly. That way, the @c cProfile module 192 | distinguishes between calls the original and the inherited implementation. 193 | 194 | This function is not intended for direct use. 195 | """ 196 | for method_name in method_names: 197 | method = __get_dict_item( class_object, method_name ) 198 | method_copy = __copy_function( method ) 199 | 200 | new_name = format_string.format( meth = method_name ) 201 | rename_function( method_copy, new_name ) 202 | 203 | setattr( class_object, method_name, method_copy ) 204 | 205 | 206 | def __flatten_methods( class_object ): 207 | """ 208 | Copy all inherited (and not re-implemented) methods to the local 209 | class dictionary. 210 | 211 | This function is not intended for direct use. 212 | """ 213 | for attribute_name in dir( class_object ): 214 | # Skip local attributes 215 | if attribute_name in class_object.__dict__: 216 | continue 217 | 218 | # Skip non-method attributes (for example class variables) 219 | method = __get_dict_item( class_object, attribute_name ) 220 | if type( method ) not in function_types: 221 | continue 222 | 223 | method_copy = __copy_function( __unwrap( method ) ) 224 | setattr(class_object, attribute_name, method_copy ) 225 | 226 | 227 | def __get_dict_item( class_object, key ): 228 | """ 229 | Return the class dictionary entry with key @p key; traverse the parent 230 | classes until a matching entry was found. Otherwise raise an 231 | @c AttributeError. 232 | 233 | This function is not intended for direct use. 234 | """ 235 | for cls in class_object.__mro__: 236 | if key in cls.__dict__: 237 | return cls.__dict__[ key ] 238 | 239 | message = "object '{name}' has no attribute '{key}'" 240 | raise AttributeError( 241 | message.format( name = class_object.__name__, key = key ) 242 | ) 243 | 244 | 245 | def __copy_function( function ): 246 | """ 247 | Create a completely independent copy of @p function. 248 | 249 | The function also copies the code object of @p function. 250 | 251 | This function is not intended for direct use. 252 | """ 253 | if type( function ) in [ staticmethod, classmethod ]: 254 | return type( function )( __copy_function( function.__func__ ) ) 255 | 256 | if type( function ) not in [ types.FunctionType, types.MethodType ]: 257 | message = "expected function or method type (got {0})" 258 | raise ValueError( message.format( function ) ) 259 | 260 | if type( function ) is types.MethodType: 261 | function = function.__func__ 262 | 263 | code = function.__code__ 264 | code_copy = types.CodeType( 265 | code.co_argcount, 266 | code.co_kwonlyargcount, 267 | code.co_nlocals, 268 | code.co_stacksize, 269 | code.co_flags, 270 | code.co_code, 271 | code.co_consts, 272 | code.co_names, 273 | code.co_varnames, 274 | code.co_filename, 275 | code.co_name, 276 | code.co_firstlineno, 277 | code.co_lnotab, 278 | code.co_freevars, 279 | code.co_cellvars 280 | ) 281 | 282 | function_copy = types.FunctionType( 283 | code_copy, 284 | function.__globals__, 285 | function.__name__, 286 | function.__defaults__, 287 | function.__closure__ 288 | ) 289 | 290 | # Re-bind methods to their instance 291 | if type( function ) is types.MethodType: 292 | return types.MethodType( function_copy, function.__self__) 293 | 294 | return function_copy 295 | 296 | 297 | def __unwrap( method ): 298 | """ 299 | Return the original function inside a method wrapper. 300 | 301 | This function is not intended for direct use. 302 | 303 | @see support.operators.operand_casting() 304 | """ 305 | if hasattr( method, "__wrapped_method__" ): 306 | return __unwrap( getattr( method, "__wrapped_method__" ) ) 307 | 308 | return method 309 | 310 | 311 | def __profiling_str(obj): 312 | """ 313 | Return the formatted name of @p obj as set by the @c profiling_name() 314 | decorator. Fall back to __str__() if necessary. 315 | 316 | This function is not intended for direct use. 317 | """ 318 | # FIXME: Endless recursion for cyclic dependencies. 319 | if isinstance(obj, type) and hasattr(obj, "__profiling_name__"): 320 | if hasattr( obj.__class__, "__parameter_map__" ): 321 | args = [ (k, __profiling_str(v)) for k,v in obj.__class__.__parameter_map__.items() ] 322 | else: 323 | args = [] 324 | 325 | try: 326 | return obj.__profiling_name__.format( **dict( args ) ) 327 | except KeyError: 328 | pass 329 | 330 | return str(obj) 331 | 332 | 333 | import types 334 | 335 | function_types = [ 336 | types.FunctionType, 337 | types.MethodType, 338 | staticmethod, 339 | classmethod, 340 | ] 341 | -------------------------------------------------------------------------------- /support/quotients.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | Auxiliary functions to handle congruences and quotient classes. 20 | 21 | The algorithms and implementations are kept as simple as possible. They are 22 | not meant for high performance computing, but for instructive purposes. 23 | 24 | @package support.quotients 25 | @author Peter Dinges 26 | """ 27 | 28 | def representative_in_range( quotient_class, valid_range ): 29 | """ 30 | Return the unique representative of @p quotient_class in @p valid_range. 31 | For example, the representative of @c (3 mod 7) in @c range(13, 19) is 17. 32 | 33 | @param quotient_class A QuotientClass over the Integers. 34 | @param valid_range The range() object describing the interval in 35 | which to the returned representative must lie. 36 | 37 | @return An integer @c x in @p valid_range with @c x % m == r, where 38 | @c m is the modulus, and @c r the remainder of @p quotient_class. 39 | 40 | @exception ValueError either if there is no representative in 41 | @p valid_range, or if there are multiple. 42 | """ 43 | if len(valid_range) > quotient_class.modulus(): 44 | raise ValueError("solution not unique") 45 | 46 | # range[0] is the first element, that is, the lower bound 47 | q, r = divmod( valid_range[0], quotient_class.modulus() ) 48 | shifted_range = range(r, r + len(valid_range)) 49 | 50 | if quotient_class.remainder() in shifted_range: 51 | return q * quotient_class.modulus() + quotient_class.remainder() 52 | elif quotient_class.remainder() + quotient_class.modulus() in shifted_range: 53 | return (q+1) * quotient_class.modulus() + quotient_class.remainder() 54 | else: 55 | # FIXME: Really use exceptions? 56 | raise ValueError("no solution") 57 | 58 | 59 | #------------------------------------------------------------------------------ 60 | 61 | from functools import reduce 62 | from operator import mul 63 | 64 | from rings.integers.naive import Integers 65 | from rings.quotients.naive import QuotientRing 66 | 67 | from support.rings import gcd 68 | 69 | def solve_congruence_equations( congruences ): 70 | """ 71 | Return a quotient class that simultaneously solves the list of 72 | @p congruences; this is the Chinese Remainder Theorem. 73 | 74 | All representatives of the returned congruence have the remainders of 75 | @p congruences when taken modulo the respective moduli. The result is a 76 | congruence modulo the product of all moduli. Thus the function returns a 77 | number @f$ z \mod \prod_{i} m_i @f$ such that 78 | @f{align*}{ 79 | z &\equiv a_1 \mod m_1 \\ 80 | \vdots \\ 81 | z &\equiv a_k \mod m_k 82 | @f} 83 | 84 | @note The moduli @f$ m_i @f$ of all congruences must be relatively prime. 85 | 86 | @exception ValueError if @p congruences is empty. 87 | 88 | @param congruences An iterable of objects of QuotientClass over the 89 | Integers. Every pair of two different moduli 90 | must have a greatest common divisor (gcd()) of 1. 91 | 92 | @return An instance of rings.quotients.naive.QuotientRing over the 93 | rings.integers.naive.Integers solving the @p congruences. 94 | 95 | @see Robinson, D. J. S., "An Introduction to Abstract Algebra", p. 27 96 | """ 97 | # The Chinese remainder theorem 98 | if not congruences: 99 | raise ValueError( "cannot solve empty equation system" ) 100 | 101 | if __debug__: 102 | # This test is expensive; remove it in optimized execution 103 | pairs = [ (c1, c2) for c1 in congruences for c2 in congruences if c1 != c2 ] 104 | pairwise_gcds = [ gcd( c1.modulus(), c2.modulus() ) for c1, c2 in pairs ] 105 | 106 | assert set( pairwise_gcds ) == set([ 1 ]), \ 107 | "the Chinese Remainder Theorem requires relatively prime moduli" 108 | 109 | common_modulus = reduce( mul, [ c.modulus() for c in congruences ] ) 110 | common_representative = 0 111 | for c in congruences: 112 | neutralizer = common_modulus // c.modulus() 113 | common_representative += c.remainder() * neutralizer \ 114 | * inverse_modulo( neutralizer, c.modulus() ) 115 | 116 | quotient_ring = QuotientRing( Integers, common_modulus ) 117 | return quotient_ring( common_representative ) 118 | 119 | 120 | from support.rings import extended_euclidean_algorithm 121 | 122 | def inverse_modulo(representative, modulus): 123 | """ 124 | Return an element @c n such that @c n * representative has remainder one 125 | if divided by @p modulus. 126 | 127 | In residue class rings, this is the multiplicative inverse. 128 | 129 | @exception ValueError if @p representative and @p modulus are not 130 | relatively prime. 131 | """ 132 | inverse, ignore, gcd = extended_euclidean_algorithm( representative, modulus ) 133 | try: 134 | relatively_prime = ( gcd == representative.__class__.one() ) 135 | except Exception: 136 | relatively_prime = ( gcd == 1 ) 137 | 138 | if relatively_prime: 139 | return inverse 140 | else: 141 | raise ValueError( "representative and modulus must be relatively prime" ) 142 | -------------------------------------------------------------------------------- /support/rings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | Auxiliary functions and algorithms for rings and their elements. 20 | 21 | The algorithms and implementations are kept as simple as possible. They are 22 | not meant for high performance computing, but for instructive purposes. 23 | 24 | @package support.rings 25 | @author Peter Dinges 26 | """ 27 | 28 | def extended_euclidean_algorithm(u, v): 29 | """ 30 | Return a tuple @c (m, n, d) such that @c u*m + v*n = d, where @c d is the 31 | greatest common divisor of @p u and @p v. 32 | 33 | The implementation does not enforce the use of integers for @p u and @p v. 34 | Thus, elements from other rings that support the @c divmod() operations as 35 | well as the operators @c //, @c * and @c - should work as well. 36 | 37 | @note If @p u and @p v are Polynomials, then the returned gcd 38 | polynomial is not necessarily a monic polynomial; that means the 39 | gcd may have a leading coefficient other than one. To check 40 | whether two polynomials are relatively prime, test whether the 41 | result has degree() zero. 42 | 43 | @exception ZeroDivisionError if one of @p u or @p v is zero. 44 | 45 | @see Knuth, D. E., "The Art of Computer Programming", 46 | volume 2, second edition, p. 342 47 | """ 48 | if not (u and v): 49 | raise ZeroDivisionError( "cannot determine the gcd of zero" ) 50 | 51 | # It does not matter which one is larger: if v > u, then the first shift 52 | # switches u and v. 53 | larger_remainder, lesser_remainder = u, v 54 | 55 | try: 56 | # u and v might not be integers after all, so ask for the right scalars 57 | larger_scalar, lesser_scalar = u.__class__.one(), u.__class__.zero() 58 | except AttributeError: 59 | larger_scalar, lesser_scalar = 1, 0 60 | 61 | while lesser_remainder: 62 | quotient, next_remainder = divmod( larger_remainder, lesser_remainder ) 63 | next_scalar = larger_scalar - quotient * lesser_scalar 64 | 65 | # Shift roles 66 | larger_remainder, larger_scalar = lesser_remainder, lesser_scalar 67 | lesser_remainder, lesser_scalar = next_remainder, next_scalar 68 | 69 | # larger_remainder is the gcd; 70 | # larger_scalar is the factor for the linear combination 71 | other_scalar = ( larger_remainder - u * larger_scalar ) // v 72 | return ( larger_scalar, other_scalar, larger_remainder ) 73 | 74 | 75 | def gcd(u, v): 76 | """ 77 | Determine the greatest common divisor of @p u and @p v. 78 | 79 | @note If @p u and @p v are Polynomials, then the returned polynomial 80 | is not necessarily monic; that means it may have a leading 81 | coefficient other than one. To check whether two polynomials are 82 | relatively prime, test whether the result has degree() zero. 83 | 84 | @see extended_euclidean_algorithm() for a description of working 85 | input types. 86 | """ 87 | return extended_euclidean_algorithm( u, v )[2] 88 | 89 | -------------------------------------------------------------------------------- /support/types.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | """ 19 | A template meta-class system. 20 | 21 | Template meta-classes offer high modularity and natural syntax for 22 | mathematical objects; for instance, see rings.polynomials.naive.Polynomials 23 | or fields.finite.naive.FiniteField 24 | 25 | @package support.types 26 | @author Peter Dinges 27 | """ 28 | 29 | def template( *parameters ): 30 | """ 31 | Factory function to create template meta-classes. The function arguments 32 | are the names of the template parameters. After specialization, the bound 33 | parameters are available as class attributes of the given names. 34 | 35 | Usage example: 36 | @code 37 | # Create a class template C with parameters "a" and "b" 38 | class C( metaclass=template("a", "b") ): 39 | pass 40 | 41 | # Instantiate the template with values 23 and 42; Cp is a class object. 42 | Cp = C(23, 42) 43 | isinstance(Cp, type) == True # This holds 44 | Cp.a == 23 45 | Cp.b == 42 46 | 47 | # Construct Cp objects 48 | x = Cp() 49 | type(x) is Cp # This also holds 50 | @endcode 51 | 52 | @see TypeTemplate 53 | """ 54 | template_name = "TypeTemplate<{0}>".format( ", ".join( parameters ) ) 55 | template_bases = tuple( [ TypeTemplate ] ) 56 | template_dict = { "__parameters__": parameters, 57 | "__unbound_parameters__": parameters, 58 | "__parameter_map__": {} 59 | } 60 | return type( template_name, template_bases, template_dict ) 61 | 62 | 63 | def is_incomplete( class_object ): 64 | """ 65 | Test whether the template class @p class_object has any unbound parameters. 66 | 67 | @return @c False if the template class is fully specialized; @c True 68 | otheriwse (that is, if it has unbound parameters). 69 | """ 70 | return getattr( class_object.__class__, "__unbound_parameters__", False ) 71 | 72 | 73 | class TypeTemplate(type): 74 | """ 75 | A template meta-class prototype; use the function @c template() to create 76 | actual meta-class types. 77 | 78 | Outline of usage and effects: 79 | @code 80 | # Create a template meta-class 'T' with parameters "a" and "b". 81 | # The meta-class can be used for _any_ class. 82 | T = template("a", "b") 83 | 84 | # Create a class template with parameters "a" and "b" (these come from T) 85 | # The type of C is T 86 | class C(metaclass=T): 87 | def __str__(self): return "C" 88 | 89 | # Partially specialize C by setting parameter a; this yields a class 90 | # template D with the single parameter "b"; type(D) is T and 91 | # is a subtype of T 92 | D = C(4) 93 | 94 | # Create a subclass template E with parameters "a" and "b"; use this to 95 | # re-implement methods from the class template C 96 | class E(C): 97 | def __str__(self): return "E" 98 | # Parameters can be bound while subclassing template classes 99 | class F(C, b=2): 100 | def __str__(self): return "F with b=2" 101 | @endcode 102 | 103 | @see The sources of rings.quotients.naive.QuotientRing and 104 | fields.finite.naive.FiniteField for examples. 105 | """ 106 | 107 | # Note: type objects are their own meta-classes. 108 | # Both, __init__() and __new__(), behave like class methods; 109 | # they are called in order __new__(), __init__() and must 110 | # have identical signatures (even though __init__() ignores 111 | # the keyword arguments). 112 | def __init__(meta_class, class_name, bases, class_dict, **kw_arguments): 113 | """ 114 | Initialize a new type object. 115 | 116 | Behaves like a class method because type objects are their own 117 | meta-classes. 118 | """ 119 | type.__init__(meta_class, class_name, bases, class_dict) 120 | 121 | 122 | def __new__(meta_class, class_name, bases, class_dict, **kw_arguments): 123 | """ 124 | Create a new type object, for example through a 'class' statement. 125 | 126 | Behaves like a class method and is called before __init__(). 127 | """ 128 | if kw_arguments: 129 | # Assigning values to the parameters means specializing the 130 | # template. Therefore, derive a subclass from this meta-class 131 | # and make it create the actual type object. 132 | specialized_meta_class = meta_class.__specialize( kw_arguments ) 133 | # Base classes must have the same specialized meta-class. 134 | specialized_bases = [] 135 | for base in bases: 136 | if base.__class__ is meta_class: 137 | specialized_bases.append( 138 | specialized_meta_class.__new__( 139 | specialized_meta_class, 140 | base.__plain_name__, 141 | base.__bases__, 142 | base.__dict__ 143 | ) 144 | ) 145 | else: 146 | specialized_bases.append( base ) 147 | 148 | return specialized_meta_class.__new__( 149 | specialized_meta_class, 150 | class_name, 151 | tuple( specialized_bases ), 152 | class_dict 153 | ) 154 | else: 155 | # No specialization. Create a type object. 156 | extended_name = meta_class.__template_name( 157 | class_name, 158 | meta_class.__parameters__, 159 | meta_class.__parameter_map__ 160 | ) 161 | extended_dict = meta_class.__parameter_map__.copy() 162 | extended_dict.update( class_dict ) 163 | extended_dict[ "__plain_name__" ] = class_name 164 | return type.__new__( 165 | meta_class, 166 | extended_name, 167 | bases, 168 | extended_dict 169 | ) 170 | 171 | 172 | def __call__(self, *arguments, **kw_arguments): 173 | """ 174 | Specialize the template or create an instance. 175 | 176 | Note that this bends python's usual semantics: calling a template 177 | class may return types and instances, rather than always returning 178 | instances. 179 | """ 180 | unbound_parameters = self.__unbound_parameters__ 181 | if unbound_parameters: 182 | # There still are unbound parameters, so calling the type object 183 | # means specializing it. 184 | if len(arguments) > len(unbound_parameters): 185 | message = "{0} takes at most {1} arguments ({2} given)".format( 186 | self.__name__, 187 | len( unbound_parameters ), 188 | len( arguments ) 189 | ) 190 | raise TypeError( message ) 191 | 192 | pos_parameters = unbound_parameters[ : len(arguments) ] 193 | ambiguous_parameters = set( pos_parameters ).intersection( set( kw_arguments.keys() ) ) 194 | if ambiguous_parameters: 195 | message = "multiple values for parameters {0}" 196 | raise TypeError( message.format( tuple( ambiguous_parameters ) ) ) 197 | 198 | parameter_map = kw_arguments.copy() 199 | parameter_map.update( zip( pos_parameters, arguments ) ) 200 | 201 | return self.__class__.__new__( 202 | self.__class__, 203 | self.__plain_name__, 204 | self.__bases__, 205 | self.__dict__, 206 | **parameter_map 207 | ) 208 | else: 209 | # All parameters are set; create an instance. 210 | return type.__call__( self, *arguments, **kw_arguments ) 211 | 212 | 213 | @classmethod 214 | def __specialize(meta_class, kw_arguments): 215 | """ 216 | Derive a sub-template of the given template meta-class by setting 217 | template parameters. 218 | """ 219 | unbound_parameters = meta_class.__unbound_parameters__ 220 | if set( kw_arguments.keys() ) <= set( unbound_parameters ): 221 | specialization_dict = meta_class.__dict__.copy() 222 | specialization_dict[ "__parameters__" ] = getattr( meta_class, "__parameters__" ) 223 | remaining_parameters = [ p for p in unbound_parameters if p not in kw_arguments ] 224 | specialization_dict[ "__unbound_parameters__" ] = tuple( remaining_parameters ) 225 | specialization_dict[ "__parameter_map__" ] = getattr( meta_class, "__parameter_map__" ).copy() 226 | specialization_dict[ "__parameter_map__" ].update( kw_arguments ) 227 | 228 | specialization_bases = tuple( [ meta_class ] ) 229 | specialization_name = meta_class.__template_name( 230 | "TypeTemplate", 231 | specialization_dict[ "__parameters__" ], 232 | specialization_dict[ "__parameter_map__" ] 233 | ) 234 | return type( specialization_name, specialization_bases, specialization_dict ) 235 | 236 | # TODO: Add detail to messages 237 | elif set( kw_arguments.keys() ).intersection( meta_class.__parameter_map__.keys() ): 238 | message = "template parameter already set" 239 | raise TypeError( message ) 240 | else: 241 | message = "not a template parameter" 242 | raise TypeError( message ) 243 | 244 | 245 | @staticmethod 246 | def __template_name(class_name, parameters, parameter_map): 247 | """ 248 | Return a C++ style template name, for example 'C'. 249 | """ 250 | parameter_strings = [] 251 | for p in parameters: 252 | if p in parameter_map: 253 | parameter_strings.append( "{0}={1}".format( p, str( parameter_map[p] ) ) ) 254 | else: 255 | parameter_strings.append( p ) 256 | 257 | return "{0}<{1}>".format( class_name, ", ".join( parameter_strings ) ) 258 | -------------------------------------------------------------------------------- /tools/callgraph_operations.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | def merge_casting_wrappers( callgraph ): 19 | """Merge wrappers and wrapped functions""" 20 | wrapper_functions = [ f for f in callgraph if f.name().endswith("_casting_wrapper") ] 21 | for wrapper in wrapper_functions: 22 | wrapped_function_name = wrapper.name()[: -len("_casting_wrapper") ] 23 | for call in wrapper.outgoing_calls(): 24 | if call.callee().name() == wrapped_function_name: 25 | wrapped_function = call.callee() 26 | break 27 | assert( wrapped_function ) 28 | callgraph.treat_as_inlined( wrapped_function ) 29 | del wrapped_function 30 | 31 | 32 | def apply_threshold( callgraph, threshold ): 33 | """Prune functions with less than @p threshold percent total cost""" 34 | threshold = int( threshold * callgraph.total_time() ) 35 | insignificant_functions = [ f for f in callgraph if f.cumulative_time() < threshold ] 36 | for function in insignificant_functions: 37 | callgraph.treat_as_builtin( function ) 38 | assert( not function.valid() ) 39 | 40 | 41 | import re 42 | 43 | def merge_by_division_polynomials( callgraph ): 44 | """ 45 | Merge all namespaces containing specific division polynomials 46 | into one generic namespace. 47 | 48 | For example, 'psi[3]' and 'psi[7]' will be unified into 'psi[l]'. 49 | """ 50 | division_polynomial_namespaces = [ 51 | ( "^E>\[x,y\]/psi\[(?P[l0-9]+)\]>>$", 52 | "E>[x,y]/psi[l]>>" ), 53 | 54 | ( "^Q>\[x,y\]/psi\[(?P[l0-9]+)\]>$", 55 | "Q>[x,y]/psi[l]>" ), 56 | 57 | ( "^E>\[x,y\]/psi\[(?P[l0-9]+)\]$", 58 | "E>[x,y]/psi[l]" ), 59 | ] 60 | 61 | # Compile a list of all occurring fields 62 | fields_re = re.compile( "GF<(?P[q0-9]+)>" ) 63 | fields = set() 64 | for namespace in callgraph.namespaces(): 65 | m = fields_re.search( namespace ) 66 | if m: 67 | fields.add( m.groupdict()[ "q" ] ) 68 | 69 | # Merge division polynomials grouped by underlying fields 70 | for q in fields: 71 | for namespace_re, merged_namespace in division_polynomial_namespaces: 72 | namespace_re = namespace_re.format( q = q ) 73 | merged_namespace = merged_namespace.format( q = q ) 74 | callgraph.merge_namespaces( namespace_re, merged_namespace ) 75 | 76 | 77 | import re 78 | 79 | def merge_by_fields( callgraph ): 80 | """ 81 | Merge all namespaces containing specific finite fields 82 | into one generic namespace. 83 | 84 | For example, 'GF<3>[x]' and 'GF<5>[x]' will be unified into 'GF[x]'. 85 | """ 86 | field_namespaces_with_divpoly = [ 87 | ( "^E[q0-9]+)>>\[x,y\]/psi\[{l}\]>>$", 88 | "E>[x,y]/psi[{l}]>>" ), 89 | 90 | ( "^Q[q0-9]+)>>\[x,y\]/psi\[{l}\]>$", 91 | "Q>[x,y]/psi[{l}]>" ), 92 | 93 | ( "^E[q0-9]+)>>\[x,y\]/psi\[{l}\]$", 94 | "E>[x,y]/psi[{l}]" ), 95 | ] 96 | field_namespaces = [ 97 | ( "^E[q0-9]+)>>\[x,y\]$", 98 | "E>[x,y]" ), 99 | 100 | ( "^GF<(?P[q0-9]+)>\[x\]$", 101 | "GF[x]" ), 102 | 103 | ( "^GF<(?P[q0-9]+)>$", 104 | "GF" ), 105 | ] 106 | 107 | # Compile a list of all occuring division polynomials 108 | divpoly_re = re.compile( "psi\[(?P[l0-9]+)\]" ) 109 | divpolys = set() 110 | for namespace in callgraph.namespaces(): 111 | m = divpoly_re.search( namespace ) 112 | if m: 113 | divpolys.add( m.groupdict()[ "l" ] ) 114 | 115 | # Merge fields grouped by division polynomials 116 | for l in divpolys: 117 | for namespace_re, merged_namespace in field_namespaces_with_divpoly: 118 | namespace_re = namespace_re.format( l = l ) 119 | merged_namespace = merged_namespace.format( l = l ) 120 | callgraph.merge_namespaces( namespace_re, merged_namespace ) 121 | 122 | for namespace_re, merged_namespace in field_namespaces: 123 | callgraph.merge_namespaces( namespace_re, merged_namespace ) 124 | 125 | 126 | def plain_name( function ): 127 | """ 128 | Return the @p function name, free from clutter such as wrapper postfixes. 129 | """ 130 | if function.name().endswith( "_casting_wrapper" ): 131 | return function.name()[ : -len("_casting_wrapper") ] 132 | 133 | return function.name() 134 | 135 | 136 | def tex_name( function ): 137 | """Return the @p function name as it should appear in the thesis.""" 138 | if function.namespace(): 139 | return "{0}::{1}".format( function.namespace(), plain_name( function ) ) 140 | else: 141 | return plain_name( function ) 142 | 143 | 144 | def tex_description( function ): 145 | """Return a TeX string that describes, what @p function does.""" 146 | description = _tex_method_descriptions.get( plain_name( function ), "" ) 147 | 148 | for namespace_re, tex_name in _tex_namespace_names: 149 | match = namespace_re.match( function.namespace() ) 150 | if match: 151 | tex_name = tex_name.format( **match.groupdict() ) 152 | # Make the plain l a script l (\ell in TeX) 153 | tex_name = tex_name.replace( "\divpoly[l]", r"\divpoly[\l]" ) 154 | return description.format( ns = tex_name ) 155 | else: 156 | return description.format( ns = function.namespace() ) 157 | 158 | 159 | #------------------------------------------------------------------------------ 160 | # These tables are ugly. However, the effort that proper parsing takes would 161 | # never pay off. 162 | 163 | import re 164 | 165 | _tex_namespace_names = [ (re.compile(r), n) for r, n in [ 166 | ( "^E[q0-9]+)>>\[x,y\]/psi\[(?P[l0-9]+)\]>>$", 167 | r"$\ecE\bigl(\ratField{{\fieldF_{{{q}}}}}{{\ecE}}/\divpoly[{l}]\bigr)$" ), 168 | 169 | ( "^Q[q0-9]+)>>\[x,y\]/psi\[(?P[l0-9]+)\]>$", 170 | r"$\ratField{{\fieldF_{{{q}}}}}{{\ecE}}/\divpoly[{l}]$" ), 171 | 172 | ( "^E[q0-9]+)>>\[x,y\]/psi\[(?P[l0-9]+)\]$", 173 | r"$\polyRing{{\fieldF_{{{q}}}}}{{\ecE}}/\divpoly[{l}]$" ), 174 | 175 | ( "^E[q0-9]+)>>\[x,y\]$", 176 | r"$\polyRing{{\fieldF_{{{q}}}}}{{\ecE}}$" ), 177 | 178 | ( "^E[q0-9]+)>>$", 179 | r"$\ecE$" ), 180 | 181 | ( "^GF<(?P[q0-9]+)>\[x\]$", 182 | r"$\polyRing{{\fieldF_{{{q}}}}}{{\x}}$" ), 183 | 184 | ( "^GF<(?P[q0-9]+)>$", 185 | r"$\fieldF_{{{q}}}$" ), ] 186 | ] 187 | 188 | _tex_method_descriptions = { 189 | "__add__" : "Addition in {ns}", 190 | "__radd__" : "Addition in {ns}", 191 | "__sub__" : "Subtraction in {ns}", 192 | "__rsub__" : "Subtraction in {ns}", 193 | "__mul__" : "Multiplication in {ns}", 194 | "__rmul__" : "Multiplication in {ns}", 195 | "__truediv__" : "Division in {ns}", 196 | "__rtruediv__" : "Division in {ns}", 197 | "__divmod__" : "Division with remainder in {ns}", 198 | "__rdivmod__" : "Division with remainder in {ns}", 199 | "__mod__" : "Taking the remainder in {ns}", 200 | "__rmod__" : "Taking the remainder in {ns}", 201 | "__pow__" : "Exponentiation in {ns}", 202 | "__rpow__" : "Exponentiation in {ns}", 203 | "__neg__" : "Negation in {ns}", 204 | "frobenius" : "Frobenius endomorphism", 205 | "frobenius_trace" : r"Computation of $\trace({{\frob}})$", 206 | "frobenius_trace_mod_l": r"Computation of $\trace({{\frob}}) \mod \l$", 207 | "frobenius_trace_mod_2": r"Computation of $\trace({{\frob}}) \mod 2$", 208 | "frobenius_equation": r"Test trace candidate on $\charpoly{{\frob}}(\frob) = \rmapId$", 209 | "__call__" : "Object creation", 210 | "__new__" : "Object creation", 211 | "__init__" : "Object initialization", 212 | "naive_schoof_algorithm": "Main function", 213 | "remainder" : r"Get the representative of $\felA \in$ {ns}", 214 | } 215 | -------------------------------------------------------------------------------- /tools/paramter_generator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | __description = \ 19 | """ 20 | Generate a list of random parameters of non-singular elliptic curves 21 | over the finite fields with @c p elements. The primes @c p are computed from 22 | pairs "n, k" as p = 2^n - k; the pairs are read one per line from the given 23 | input file. 24 | """ 25 | __doc__ = __description 26 | 27 | 28 | def non_singular(p, A, B): 29 | """ 30 | Test whether the elliptic curve with parameters @p A and @p B is 31 | non-singular over the finite field with @p p elements. 32 | """ 33 | assert p > 3, \ 34 | "discriminant test for non-singular curves requires characteristic > 3" 35 | return (4 * A**3 + 27 * B**2) % p != 0 36 | 37 | 38 | import random 39 | 40 | def generate_parameters(p, number): 41 | """ 42 | Return a set of @p number parameter pairs @c (A,B) of non-singular 43 | elliptic curves over the finite field with @p p elements. 44 | """ 45 | parameters = set() 46 | 47 | # Too many pairs to guess and test; use systematic approach 48 | if number > 0.8 * (p**2 - 1): 49 | pairs = [ (i, j) for i in range(0, p) for j in range(0, p) ] 50 | 51 | while pairs and len( parameters ) < number: 52 | index = random.randrange( 0, len(pairs) ) 53 | A, B = pairs[ index ] 54 | del pairs[ index ] 55 | if non_singular(p, A, B): 56 | parameters.add( (A, B) ) 57 | 58 | if len( parameters ) < number: 59 | warning = "WARNING: Could find only {n} parameter pairs for p = {p}" 60 | print( warning.format( n=len( parameters ), p=p ), file=sys.stderr ) 61 | 62 | else: 63 | while len( parameters ) < number: 64 | A = random.randrange(0, p) 65 | B = random.randrange(0, p) 66 | if (A, B) not in parameters and non_singular(p, A, B): 67 | parameters.add( (A, B) ) 68 | 69 | return parameters 70 | 71 | 72 | import optparse 73 | import os.path 74 | import sys 75 | 76 | from contextlib import closing 77 | 78 | def main(arguments): 79 | usage_string = "%prog " 80 | parser = optparse.OptionParser( 81 | usage=usage_string, 82 | description=__description.strip() 83 | ) 84 | 85 | parser.add_option( "-o", 86 | "--output-name", 87 | metavar="FILE", 88 | dest="output_name", 89 | help="Write output to FILE instead of console", 90 | default=None 91 | ) 92 | 93 | parser.add_option( "-n", 94 | "--curves-per-prime", 95 | metavar="N", 96 | dest="curves_per_prime", 97 | help="Generate N sets of curve parameters per prime", 98 | default=11 99 | ) 100 | 101 | options, arguments = parser.parse_args( arguments ) 102 | 103 | if len( arguments ) != 1: 104 | parser.print_usage() 105 | return 2 106 | 107 | primes_list_name = arguments[ 0 ] 108 | 109 | if options.output_name and os.path.exists( options.output_name ): 110 | message = "ERROR: Output file '{0}' already exists. Aborting." 111 | print( message.format( options.output_name ), file=sys.stderr ) 112 | return 1 113 | 114 | try: 115 | primes_list = open( primes_list_name, "rt" ) 116 | 117 | except IOError as error: 118 | message = "ERROR: Could not open primes list.\nReason: {0}" 119 | print( message.format( error ), file=sys.stderr ) 120 | return 1 121 | 122 | 123 | if options.output_name: 124 | try: 125 | output_file = open( options.output_name, "wt" ) 126 | except IOError as error: 127 | message = "ERROR: Could not store the output.\nReason: {0}" 128 | print( message.format( error ), file=sys.stderr ) 129 | return 1 130 | else: 131 | output_file = sys.stdout 132 | 133 | 134 | with closing( output_file ) as output: 135 | for line in primes_list: 136 | # Ignore empty lines and comments (starting with #) 137 | if not line.strip() or line.strip().startswith("#"): 138 | continue 139 | n, k = map( int, line.split(",") ) 140 | p = 2**n - k 141 | 142 | parameters = generate_parameters( p, int(options.curves_per_prime) ) 143 | 144 | comment = "# {n}-bit prime {p} = 2^{n} - {k}".format( n=n, k=k, p=p ) 145 | print( comment, file=output ) 146 | 147 | for A, B in parameters: 148 | print(p, A, B, file=output) 149 | print( file=output ) 150 | 151 | return 0 152 | 153 | 154 | if __name__ == '__main__': 155 | sys.exit( main( sys.argv[ 1: ] ) ) 156 | -------------------------------------------------------------------------------- /tools/primes.txt: -------------------------------------------------------------------------------- 1 | # (n, k) such that 2^n - k is prime 2 | # From: http://primes.utm.edu/lists/2small/0bit.html 3 | 3, 1 4 | 4, 3 5 | 5, 1 6 | 6, 3 7 | 7, 1 8 | 8, 5 9 | 12, 3 10 | 16, 15 11 | 20, 3 12 | 24, 3 13 | 28, 57 14 | 32, 5 15 | 36, 5 16 | 40, 87 17 | 44, 17 18 | 48, 59 19 | 52, 47 20 | 56, 5 21 | 60, 93 22 | 64, 59 23 | 68, 23 24 | 72, 93 25 | 76, 15 26 | 80, 65 27 | 84, 35 28 | 88, 299 29 | 92, 83 30 | 96, 17 31 | 100, 15 32 | 104, 17 33 | 108, 59 34 | 112, 75 35 | 116, 3 36 | 120, 119 37 | 124, 59 38 | 128, 159 39 | 132, 347 40 | 136, 113 41 | 140, 27 42 | 144, 83 43 | 148, 167 44 | 152, 17 45 | 156, 143 46 | 160, 47 47 | 164, 63 48 | 168, 257 49 | 172, 95 50 | 176, 233 51 | 180, 47 52 | 184, 33 53 | 188, 125 54 | 192, 237 55 | 196, 15 56 | 200, 75 -------------------------------------------------------------------------------- /tools/profile_to_callgrind.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | # Docstrings will be empty if optimization (-OO command line flag) is enabled. 19 | # However, the description has to be available regardless of this. Thus put 20 | # it in an extra variable. 21 | __description = \ 22 | """ 23 | Convert execution profile dumps generated with the cProfile module to a file 24 | in the Callgrind format (typically named 'callgrind.out...'). 25 | 26 | The Callgrind format is widely supported in profiling tools. For example, 27 | the excellent profile inspection tool KCacheGrind supports Callgrind files. 28 | 29 | This script accepts several profile dumps as input and allows some aggregation 30 | and grouping of the data. 31 | """ 32 | __doc__ = __description 33 | 34 | 35 | import callgraph_operations 36 | from contextlib import closing 37 | import sys 38 | 39 | def main(arguments): 40 | options, arguments = parse_arguments( arguments ) 41 | 42 | output = get_output( arguments[0], options ) 43 | callgraph = get_callgraph( arguments ) 44 | 45 | #- Merge ------------------------------------------------------------------ 46 | if not options.preserve_wrappers: 47 | callgraph_operations.merge_casting_wrappers( callgraph ) 48 | 49 | if options.merge_divpolys: 50 | callgraph_operations.merge_by_division_polynomials( callgraph ) 51 | 52 | if options.merge_fields: 53 | callgraph_operations.merge_by_fields( callgraph ) 54 | 55 | #- Output ----------------------------------------------------------------- 56 | try: 57 | with closing( output ): 58 | callgrind_profile = CallgrindProfile( callgraph ) 59 | callgrind_profile.dump( output ) 60 | 61 | sys.exit(0) 62 | 63 | except IOError as error: 64 | message = "ERROR: Could not store the output.\nReason: {0}" 65 | print( message.format( error ), file=sys.stderr ) 66 | sys.exit(1) 67 | 68 | 69 | from functools import reduce 70 | 71 | class CallgrindProfile: 72 | """ 73 | A callgrind profile generated from a callgraph.CallGraph. 74 | 75 | See "Callgrind Format Specification" in the Valgrind documentation at 76 | http://www.valgrind.org/docs/manual/cl-format.html (Jan. 26, 2010) 77 | 78 | Inspired by the 'lsprofcalltree.py' script by David Allouche et al. 79 | """ 80 | def __init__(self, callgraph): 81 | self.__callgraph = callgraph 82 | 83 | 84 | def dump(self, file): 85 | print( "events: Ticks", file=file ) 86 | self._print_summary( file ) 87 | 88 | for function in self.__callgraph: 89 | self._print_function( function, file ) 90 | 91 | 92 | def _print_summary(self, file): 93 | total_time = int( self.__callgraph.total_time() * 1000 ) 94 | print( "summary: {0:d}".format( total_time ), file=file ) 95 | 96 | 97 | def _print_function(self, function, file): 98 | print( "fi={0:s}".format( function.filename() ), file=file ) 99 | print( "fn={0:s}".format( self._absolute_name( function ) ), file=file ) 100 | 101 | inline_time = int( function.inline_time() * 1000 ) 102 | cost_data = ( function.line_number(), inline_time ) 103 | print( "{0:d} {1:d}".format( *cost_data ), file=file) 104 | 105 | for call in function.outgoing_calls(): 106 | self._print_call( call, file ) 107 | 108 | 109 | def _print_call(self, call, file): 110 | callee = call.callee() 111 | print( "cfn={0:s}".format( self._absolute_name( callee ) ), file=file ) 112 | print( "cfi={0:s}".format( callee.filename() ), file=file ) 113 | 114 | calls_data = ( call.total_callcount(), callee.line_number() ) 115 | print( "calls={0:d} {1:d}".format( *calls_data ), file=file ) 116 | 117 | cumulative_time = int( call.cumulative_time() * 1000 ) 118 | cost_data = ( call.caller().line_number(), cumulative_time ) 119 | print( "{0:d} {1:d}".format( *cost_data ), file=file ) 120 | 121 | @staticmethod 122 | def _absolute_name(function): 123 | if function.namespace(): 124 | return "{0:s}::{1:s}".format( function.namespace(), function.name() ) 125 | else: 126 | return function.name() 127 | 128 | 129 | import optparse 130 | 131 | def parse_arguments( arguments ): 132 | usage_string = "%prog " 133 | parser = optparse.OptionParser( 134 | usage=usage_string, 135 | description=__description.strip() 136 | ) 137 | 138 | parser.add_option( "-o", 139 | "--output-name", 140 | dest="output_name", 141 | default=None, 142 | metavar="FILE", 143 | help="Write output to FILE instead of " 144 | "callgrind.out.FIRST_INPUT_FILE Use '-' to have the" 145 | "output written to the terminal (stdout)." 146 | ) 147 | 148 | parser.add_option( "-w", 149 | "--overwrite", 150 | dest="overwrite", 151 | action="store_true", 152 | default=False, 153 | help="Overwrite the output file if it already exists." 154 | ) 155 | 156 | parser.add_option( "-c", 157 | "--preserve-wrappers", 158 | dest="preserve_wrappers", 159 | action="store_true", 160 | default=False, 161 | help="Preserve casting wrappers instead of merging " 162 | "them with the wrapped function" 163 | ) 164 | parser.add_option( "-d", 165 | "--merge-division-polynomials", 166 | dest="merge_divpolys", 167 | action="store_true", 168 | default=False, 169 | help="Merge namespaces with division polynomials into " 170 | "a generic namespace" 171 | ) 172 | parser.add_option( "-f", 173 | "--merge-fields", 174 | dest="merge_fields", 175 | action="store_true", 176 | default=False, 177 | help="Merge namespaces of fields with different element " 178 | "count into a generic namespace" 179 | ) 180 | 181 | options, arguments = parser.parse_args( arguments ) 182 | 183 | if len( arguments ) < 1: 184 | parser.print_usage() 185 | sys.exit(2) 186 | 187 | return options, arguments 188 | 189 | 190 | import os.path 191 | import sys 192 | 193 | def get_output( first_profile_name, options ): 194 | if options.output_name == "-": 195 | return sys.stdout 196 | 197 | elif not options.output_name: 198 | base_name = os.path.splitext( os.path.basename( first_profile_name ) )[0] 199 | file_name = "callgrind.out.{0}".format( base_name ) 200 | directory = os.path.dirname( first_profile_name ) 201 | options.output_name = os.path.join( directory, file_name) 202 | 203 | if not options.overwrite and os.path.exists( options.output_name ): 204 | message = "ERROR: Output file '{0}' already exists. Aborting." 205 | print( message.format( options.output_name ), file=sys.stderr ) 206 | sys.exit(1) 207 | 208 | return open( options.output_name, "wt" ) 209 | 210 | 211 | import callgraph 212 | import pstats 213 | import sys 214 | 215 | def get_callgraph( profile_names ): 216 | callgraph_ = callgraph.CallGraph() 217 | 218 | for profile_name in profile_names: 219 | try: 220 | stats = pstats.Stats( profile_name ) 221 | callgraph_.add( stats ) 222 | except IOError as error: 223 | message = "ERROR: Could not open profile file.\nReason: {0}" 224 | print( message.format( error), file=sys.stderr ) 225 | sys.exit(1) 226 | 227 | return callgraph_ 228 | 229 | 230 | if __name__ == '__main__': 231 | main( sys.argv[ 1: ] ) 232 | -------------------------------------------------------------------------------- /tools/profile_to_dot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | # Docstrings will be empty if optimization (-OO command line flag) is enabled. 19 | # However, the description has to be available regardless of this. Thus put 20 | # it in an extra variable. 21 | __description = \ 22 | """ 23 | Create a call graph in the DOT format from execution profile dumps generated 24 | with the cProfile module. The DOT file format is common for graph 25 | visualization tools; it comes from the Graphviz package. 26 | 27 | This script accepts several profile dumps as input and allows some aggregation 28 | and grouping of the data. 29 | """ 30 | __doc__ = __description 31 | 32 | 33 | import sys 34 | import callgraph_operations 35 | from datetime import datetime 36 | from callgraph import CallGraph 37 | from contextlib import closing 38 | 39 | def main(arguments): 40 | options, arguments = parse_arguments( arguments ) 41 | 42 | output = get_output( arguments[0], options ) 43 | callgraph = get_callgraph( arguments ) 44 | 45 | #- Merge ------------------------------------------------------------------ 46 | if not options.preserve_wrappers: 47 | callgraph_operations.merge_casting_wrappers( callgraph ) 48 | 49 | if options.merge_divpolys: 50 | callgraph_operations.merge_by_division_polynomials( callgraph ) 51 | 52 | if options.merge_fields: 53 | callgraph_operations.merge_by_fields( callgraph ) 54 | 55 | #- Prune ------------------------------------------------------------------ 56 | if options.threshold: 57 | callgraph_operations.apply_threshold( callgraph, options.threshold / 100 ) 58 | 59 | #- Print ------------------------------------------------------------------ 60 | try: 61 | with closing( output ): 62 | header = "// Call Graph with\n" \ 63 | "// Cost threshold: {threshold}%\n" \ 64 | "// Merged: {merged}\n" \ 65 | "// Generated on {date} from:\n" \ 66 | "// {input}" 67 | merge_options = [ 68 | (options.preserve_wrappers, "preserved casting wrappers"), 69 | (options.merge_divpolys, "division polynomials namespaces"), 70 | (options.merge_fields, "fields namespaces" ) 71 | ] 72 | header = header.format( 73 | threshold = int( options.threshold ), 74 | merged = ", ".join( [ m[1] for m in merge_options if m[0] ] ), 75 | date = datetime.now().strftime("%Y/%m/%d %H:%M:%S"), 76 | input = "\n// ".join( arguments ), 77 | ) 78 | print( header, file=output ) 79 | 80 | dot_callgraph = DotCallgraph( callgraph ) 81 | dot_callgraph.dump( output ) 82 | 83 | sys.exit(0) 84 | 85 | except IOError as error: 86 | message = "ERROR: Could not store the output.\nReason: {0}" 87 | print( message.format( error), file=sys.stderr ) 88 | sys.exit(1) 89 | 90 | 91 | class DotCallgraph: 92 | def __init__(self, callgraph): 93 | self.__callgraph = callgraph 94 | 95 | def dump(self, file): 96 | print( "digraph callgraph {", file=file ) 97 | print( "node [shape = box];", file=file ) 98 | 99 | for i, namespace in enumerate( self.__callgraph.namespaces() ): 100 | print( "subgraph cluster_namespace{0} {{".format( i ), file=file ) 101 | print( "label = \"{0}\";".format( namespace ), file=file ) 102 | for function in self.__callgraph.namespace( namespace ): 103 | self._print_node( function, file ) 104 | print( "}", file=file ) 105 | print( file=file ) 106 | 107 | print( file=file ) 108 | for function in self.__callgraph: 109 | self._print_calls( function, file ) 110 | 111 | print( "}", file=file ) 112 | 113 | def _print_node(self, function, file): 114 | node_string = "{id} [label = \"{label}\"]".format( 115 | id = id(function), 116 | label = function.name() 117 | ) 118 | print( node_string, file=file ) 119 | 120 | def _print_calls(self, function, file): 121 | for call in function.outgoing_calls(): 122 | edge_string = "\"{caller}\":s -> \"{callee}\":n;".format( 123 | caller = id( call.caller() ), 124 | callee = id( call.callee() ) 125 | ) 126 | print( edge_string, file=file ) 127 | 128 | 129 | import optparse 130 | import sys 131 | 132 | def parse_arguments( arguments ): 133 | usage_string = "%prog " 134 | parser = optparse.OptionParser( 135 | usage=usage_string, 136 | description=__description.strip() 137 | ) 138 | 139 | parser.add_option( "-o", 140 | "--output-name", 141 | dest="output_name", 142 | default=None, 143 | metavar="FILE", 144 | help="Write output to FILE instead of " 145 | "FIRST_INPUT_FILE.dot Use '-' to have the output " 146 | "written to the terminal (stdout)." 147 | ) 148 | 149 | parser.add_option( "-w", 150 | "--overwrite", 151 | dest="overwrite", 152 | action="store_true", 153 | default=False, 154 | help="Overwrite the output file if it already exists." 155 | ) 156 | 157 | parser.add_option( "-t", 158 | "--threshold", 159 | dest="threshold", 160 | type="int", 161 | default=2, 162 | metavar="PERCENT", 163 | help="Ignore all functions with less than PERCENT " 164 | "part in the total execution time" 165 | ) 166 | 167 | parser.add_option( "-c", 168 | "--preserve-wrappers", 169 | dest="preserve_wrappers", 170 | action="store_true", 171 | default=False, 172 | help="Preserve casting wrappers instead of merging " 173 | "them with the wrapped function" 174 | ) 175 | parser.add_option( "-d", 176 | "--merge-division-polynomials", 177 | dest="merge_divpolys", 178 | action="store_true", 179 | default=False, 180 | help="Merge namespaces with division polynomials into " 181 | "a generic namespace" 182 | ) 183 | parser.add_option( "-f", 184 | "--merge-fields", 185 | dest="merge_fields", 186 | action="store_true", 187 | default=False, 188 | help="Merge namespaces of fields with different element " 189 | "count into a generic namespace" 190 | ) 191 | 192 | options, arguments = parser.parse_args( arguments ) 193 | 194 | if len( arguments ) < 1: 195 | parser.print_usage() 196 | sys.exit(2) 197 | 198 | return options, arguments 199 | 200 | 201 | import os.path 202 | import sys 203 | 204 | def get_output( first_profile_name, options ): 205 | if options.output_name == "-": 206 | return sys.stdout 207 | 208 | elif not options.output_name: 209 | base_name = os.path.splitext( os.path.basename( first_profile_name ) )[0] 210 | file_name = "{0}.tex".format( base_name ) 211 | directory = os.path.dirname( first_profile_name ) 212 | options.output_name = os.path.join( directory, file_name) 213 | 214 | if not options.overwrite and os.path.exists( options.output_name ): 215 | message = "ERROR: Output file '{0}' already exists. Aborting." 216 | print( message.format( options.output_name ), file=sys.stderr ) 217 | sys.exit(1) 218 | 219 | return open( options.output_name, "wt" ) 220 | 221 | 222 | import callgraph 223 | import pstats 224 | import sys 225 | 226 | def get_callgraph( profile_names ): 227 | callgraph_ = callgraph.CallGraph() 228 | 229 | for profile_name in profile_names: 230 | try: 231 | stats = pstats.Stats( profile_name ) 232 | callgraph_.add( stats ) 233 | except IOError as error: 234 | message = "ERROR: Could not open profile file.\nReason: {0}" 235 | print( message.format( error), file=sys.stderr ) 236 | sys.exit(1) 237 | 238 | return callgraph_ 239 | 240 | 241 | if __name__ == '__main__': 242 | main( sys.argv[ 1: ] ) 243 | -------------------------------------------------------------------------------- /tools/profile_to_table.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2010--2012 Peter Dinges 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | 18 | # Docstrings will be empty if optimization (-OO command line flag) is enabled. 19 | # However, the description has to be available regardless of this. Thus put 20 | # it in an extra variable. 21 | __description = \ 22 | r""" 23 | Create a table of the most expensive functions in execution profile dumps 24 | generated with the cProfile module. The output is in LaTeX table format: 25 | columns separated with '&', rows separated with '\\'. It has four columns: 26 | cumulative time, inline time, function name, function description. The script 27 | uses the cumulative time as primary sort key; the inline time is the secondary 28 | sort key. 29 | 30 | This script accepts several profile dumps as input and allows some aggregation, 31 | grouping, and pruning of the data. 32 | """ 33 | __doc__ = __description 34 | 35 | 36 | import callgraph_operations 37 | from contextlib import closing 38 | from datetime import datetime 39 | import sys 40 | 41 | def main(arguments): 42 | options, arguments = parse_arguments( arguments ) 43 | 44 | output = get_output( arguments[0], options ) 45 | callgraph = get_callgraph( arguments ) 46 | 47 | #- Merge ------------------------------------------------------------------ 48 | if not options.preserve_wrappers: 49 | callgraph_operations.merge_casting_wrappers( callgraph ) 50 | 51 | if options.merge_divpolys: 52 | callgraph_operations.merge_by_division_polynomials( callgraph ) 53 | 54 | if options.merge_fields: 55 | callgraph_operations.merge_by_fields( callgraph ) 56 | 57 | #- Prune ------------------------------------------------------------------ 58 | if options.threshold: 59 | callgraph_operations.apply_threshold( callgraph, options.threshold / 100 ) 60 | 61 | #- Print ------------------------------------------------------------------ 62 | try: 63 | with closing( output ): 64 | header = "%% Table of the {limit} most expensive functions " \ 65 | "(first sort key: cumulative time; second key: inline time)\n" \ 66 | "%% Cost threshold: {threshold}%\n" \ 67 | "%% Merged: {merged}\n" \ 68 | "%% Generated on {date} from:\n" \ 69 | "%% {input}" 70 | merge_options = [ 71 | (options.preserve_wrappers, "preserved casting wrappers"), 72 | (options.merge_divpolys, "division polynomials namespaces"), 73 | (options.merge_fields, "fields namespaces" ) 74 | ] 75 | header = header.format( 76 | limit = options.limit, 77 | threshold = int( options.threshold ), 78 | merged = ", ".join( [ m[1] for m in merge_options if m[0] ] ), 79 | date = datetime.now().strftime("%Y/%m/%d %H:%M:%S"), 80 | input = "\n%% ".join( arguments ), 81 | ) 82 | print( header, file=output ) 83 | 84 | functions = sorted( callgraph, key=function_sort_key, reverse=True ) 85 | rows = [ prepare_row( f, callgraph.total_time() ) 86 | for f in functions[ : options.limit ] ] 87 | 88 | # Sort again to prevent (seemingly) wrong order 89 | # from rounding to the same value 90 | for row in sorted( rows, reverse=True ): 91 | print( format_row( row ), file=output ) 92 | 93 | sys.exit(0) 94 | 95 | except IOError as error: 96 | message = "ERROR: Could not store the output.\nReason: {0}" 97 | print( message.format( error), file=sys.stderr ) 98 | sys.exit(1) 99 | 100 | 101 | import optparse 102 | import sys 103 | 104 | def parse_arguments( arguments ): 105 | usage_string = "%prog " 106 | parser = optparse.OptionParser( 107 | usage=usage_string, 108 | description=__description.strip() 109 | ) 110 | 111 | parser.add_option( "-o", 112 | "--output-name", 113 | dest="output_name", 114 | default=None, 115 | metavar="FILE", 116 | help="Write output to FILE instead of " 117 | "FIRST_INPUT_FILE.tex Use '-' to have the output " 118 | "written to the terminal (stdout)." 119 | ) 120 | 121 | parser.add_option( "-w", 122 | "--overwrite", 123 | dest="overwrite", 124 | action="store_true", 125 | default=False, 126 | help="Overwrite the output file if it already exists." 127 | ) 128 | 129 | parser.add_option( "-t", 130 | "--threshold", 131 | dest="threshold", 132 | type="int", 133 | default=2, 134 | metavar="PERCENT", 135 | help="Ignore all functions with less than PERCENT " 136 | "part in the total execution time" 137 | ) 138 | parser.add_option( "-l", 139 | "--limit", 140 | dest="limit", 141 | type="int", 142 | default=15, 143 | metavar="NUMBER", 144 | help="Return the top NUMBER functions" 145 | ) 146 | 147 | parser.add_option( "-c", 148 | "--preserve-wrappers", 149 | dest="preserve_wrappers", 150 | action="store_true", 151 | default=False, 152 | help="Preserve casting wrappers instead of merging " 153 | "them with the wrapped function" 154 | ) 155 | parser.add_option( "-d", 156 | "--merge-division-polynomials", 157 | dest="merge_divpolys", 158 | action="store_true", 159 | default=False, 160 | help="Merge namespaces with division polynomials into " 161 | "a generic namespace" 162 | ) 163 | parser.add_option( "-f", 164 | "--merge-fields", 165 | dest="merge_fields", 166 | action="store_true", 167 | default=False, 168 | help="Merge namespaces of fields with different element " 169 | "count into a generic namespace" 170 | ) 171 | 172 | options, arguments = parser.parse_args( arguments ) 173 | 174 | if len( arguments ) < 1: 175 | parser.print_usage() 176 | sys.exit(2) 177 | 178 | return options, arguments 179 | 180 | 181 | import os.path 182 | import sys 183 | 184 | def get_output( first_profile_name, options ): 185 | if options.output_name == "-": 186 | return sys.stdout 187 | 188 | elif not options.output_name: 189 | base_name = os.path.splitext( os.path.basename( first_profile_name ) )[0] 190 | file_name = "{0}.tex".format( base_name ) 191 | directory = os.path.dirname( first_profile_name ) 192 | options.output_name = os.path.join( directory, file_name) 193 | 194 | if not options.overwrite and os.path.exists( options.output_name ): 195 | message = "ERROR: Output file '{0}' already exists. Aborting." 196 | print( message.format( options.output_name ), file=sys.stderr ) 197 | sys.exit(1) 198 | 199 | return open( options.output_name, "wt" ) 200 | 201 | 202 | import callgraph 203 | import pstats 204 | import sys 205 | 206 | def get_callgraph( profile_names ): 207 | callgraph_ = callgraph.CallGraph() 208 | 209 | for profile_name in profile_names: 210 | try: 211 | stats = pstats.Stats( profile_name ) 212 | callgraph_.add( stats ) 213 | except IOError as error: 214 | message = "ERROR: Could not open profile file.\nReason: {0}" 215 | print( message.format( error), file=sys.stderr ) 216 | sys.exit(1) 217 | 218 | return callgraph_ 219 | 220 | 221 | def function_sort_key( function ): 222 | # TODO Use locale.strxform() to have locale aware string keys 223 | return ( function.cumulative_time(), 224 | function.inline_time(), 225 | function.namespace().lower(), 226 | function.name().lower() 227 | ) 228 | 229 | 230 | import callgraph_operations 231 | 232 | def prepare_row( function, total_time ): 233 | total_percent = int( round( 100 * function.cumulative_time() / total_time ) ) 234 | inline_percent = int( round( 100 * function.inline_time() / total_time ) ) 235 | name = callgraph_operations.tex_name( function ) 236 | description = callgraph_operations.tex_description( function ) 237 | 238 | return ( total_percent, inline_percent, name, description ) 239 | 240 | 241 | def format_row( row ): 242 | if row[3]: 243 | return r"{0} & {1} & !{2}! \hfill\hbox{{\hskip 1.5em ({3})}} \\".format( *row ) 244 | else: 245 | return r"{0} & {1} & !{2}! \\".format( *row ) 246 | 247 | 248 | if __name__ == '__main__': 249 | main( sys.argv[ 1: ] ) 250 | --------------------------------------------------------------------------------