Package: gaussianprimes
gaussian-prime-sieveA program to generate primes in the Gaussian integers with the Sieve of Eratosthenes.Table of ContentsGaussian IntegersInstallCommand line usagePython APIAlgorithmC++ ImplementationApplicationsTestsLicenseGaussian IntegersTheGaussian integersare complex numbers of the form a + bi where a and b are integers and i is theimaginary unit. While the Gaussian integers are a two dimensional extension of therational integers(the usual integers we know and love), they possess nearly all of the same essential properties. Gaussian integers can be factored and the notion of primality is well-defined. Thenormof a Gaussian integer gives a measure of its size; the analogy in the rational integers is absolute value. Equipped with these tools, sieve methods can be used to compute primes in this extension of our usual integer system.The goal of this project is to build an efficient C++ implementation of a prime generating sieve in the Gaussian integers and to provide a library for deeper exploration. A natural starting point is theSieve of Eratosthenes, which generates primes in the rational integers. The 2-dimensional nature of the Gaussian integers requires overcoming additional hurdles which are not present in the naive Sieve of Eratosthenes. Despite this, nearly all of the basic computational sieve ideas can be extended to Gaussian integers. In particular, I extend both the wheel sieve and the segmented sieve.Theprimesieveproject implements the current state of the art prime generator for rational integers.primesieveincludes both algorithmic optimizations as well as the hardware considerations needed to improve runtime performance. In this repository, I draw inspiration fromprimesieveand hope to extend some of its algorithms and architectures to the realm of the Gaussian integers.The motivation behind this project is to develop an API to allow for easy access to the Gaussian integers, an abstract mathematical structure lacking native support in most programming languages. The Gaussian integers provide a rich source of mathematical phenomena which have been well studied in domain of the rational integers. While the essence of mathematics is theorem proving, mathematical examples play an extremely valuable role: they inspire further questions, they guide conjecture, and they inform insight. This project bridges the gap between theory and example in the context of Gaussian primes.To the best of my knowledge, a highly efficient and accessible sieve has never been implemented in the Gaussian integers. Moreover, tools to access applications, such as Gaussian prime races and the Gaussian moat problem, are not available. This project hopes to provide such resources.InstallDependenciesBuilding the source requires theclangcompiler and thelibc++library.On macOS, these dependencies can be obtained by installing the command line tools.xcode-select--installOn a Linux, these dependencies can be installed through apt.sudoaptinstallclanglibc++-devlibc++abi-devBuildWith these dependencies in place, you can build the C++ executables (this is not needed for Python installation). Clone or download this repository and call the Makefile.gitclonehttps://github.com/zebengberg/gaussian-integer-sievecdgaussian-integer-sieve
makeInstalling the Python APIAfter installing thedependencies, thegaussianprimesmodule can be installed through PyPI.pipinstallgaussianprimesThis approach has been tested on MacOS X and Ubuntu 20.04.ThePython APIcan also be installed from source withpip.gitclonehttps://github.com/zebengberg/gaussian-integer-sievecdgaussian-integer-sieve
makepyinstallAfter the module is installed, it can be imported into a python console or used in a python script withimport gaussianprimes.Command line usageAll sieving algorithms can be called from thegintsieveexecutable.
Command line options include:Usage: ./gintsieve x [y dx dy alpha beta] [option1] [option2] ...
Generate Gaussian primes with norm up to x using sieving methods.
    x                   Norm-bound of the generated primes
    y                   Coordinates (x, y) of SW-corner of array in block sieve mode.
    dx                  Horizontal side length in block sieve mode.
    dy                  Vertical side length in block sieve mode.
    alpha               Start angle in sector sieve mode.
    beta                Terminal angle in sector sieve mode.

Options:
    -h, --help          Print this help message.
    -v, --verbose       Display sieving progress.
    -p, --printprimes   Print the real and imag part of primes found by the sieve.
    -w, --write         Write primes to csv file in current directory.
    -a, --printarray    Print a text representation of the sieve array.
    -c, --count         Count the number of generated primes and exit program.

Optional sieve types:
    -o, --octant        Sieve array indexed by Gaussian integers in the first octant.
                        This is the default sieve method called.
    -s, --sector        Sieve array indexed by Gaussian integers in the sector
                        with start angle alpha and final angle beta.
    -b, --block         Sieve array indexed by Gaussian integers in the rectangle
                        defined by x <= real < x + dx and y <= imag < y + dy.
    -d, --donut         If a donut version of the sieve array exists, use it. In the
                        donut sieve, the sieve array consists of Gaussian integers
                        coprime to 2 and 5. This option can be used with --octant
                        and --block, and is often significantly faster.For example, to print the real and imaginary parts of the Gaussian primes up to norm 60 sorted by norm, run:$./gintsieve601121123032234114522561165445707227Totalnumberofprimesprinted:17Many of the sieving algorithms can be visualized by calling the-aoption which prints thesieve arrayafter sieving is complete. For example, below is a hexidecimal encoding of the donut sieve array for primes in the first octant up to norm 12000.$./gintsieve12000-a-dd34cdbffffffffec0aedf3ffc0120c78da730b2effffffef6fdcd7ff1f08ddf0eed55590d18b13c7ffeebdb2f65cffef70113512369847a336968225e82622d51149408bfffffffd43eedbfff8cba42e51413f89082e18b588face6d2a0c27225316d330fffff8440bffdfef0e47b6da25f0b9daf924571487cac0ea8894d8588f4c51d59602fc1aff54b816f3ceffefac979b9bbe4c61d394f6cea564332c6d660ea2b61196506d40fdb31f2a1e034ff5c0ac56
5fffffeebb73ba7fc97675271739d9f0c3a99523bf5e23a4199f8080f06afb52554124ac4f23726148e00f02To see the primality of an 80 x 20 block of Gaussian integers far out the in complex plane, we run:$./gintsieve1000000003000000008020-a-b
--------------------------------------------*-*-------------------*-------*-----
---------------*---------------------------------------------------*-------*----
--------------------------------*-----------------------------------------------
---------------------*-------------------------------------*-----*---*-*--------
------------------------------------*---------------*---------------------------
-----------------------------*---------------------------------------------*----
--------------------------------*-----------------------------------------------
-------------*-----------*-------------------------------*----------------------
--------------*-----------*-----------------------------*---*-------------------
---*-------*-----------*-------*-----------------*------------------------------
--------------------------------------------------------------------------------
-------------------------*-*----------------------------------------------------
------------------------------*-*-----*-----------------------------------------
---------*-----------------------------------*---*-------------------*----------
------*---------*-----*-------------------------*---------------------------*---
---------------*-----*---*---------------*-------------------------------------*
--------------------------------------------------------------------------------
---*---------*-----------------------------------------*-------*----------------
----------------------------------------------------------------*-----*---*-----
-*-----------------------------*-*---*-*---------------------------*------------Python APIOnce the python modulegaussianprimesisbuilt, we can import it into python file or notebook and use it as in the following examples.>>>importgaussianprimesasgp# Generate a numpy array containing coordinates of Gaussian primes up to norm 50 sorted by norm.>>>gp.gprimes(50)Gints([[1,2,1,3,3,2,4,1,5,2,6,1,5,4,7],[1,1,2,0,2,3,1,4,2,5,1,6,4,5,0]],dtype=int32)# Instead of returning an array, we can count the Gaussian primes.>>>gp.count(3141592653)CallingtheOctantSievetogeneratesmallPrimes...Buildingdonutsievearray...Sievearrayapproximatememoryuse:49MB.Sievearraytotalnumberofentries:394936384Startingtosieve.......................................................Donesieving.Totaltimeforsieving:12.538seconds.Countingprimesaftersieve...Totalnumberofprimes,includingassociates:603726004603726004# Plotting Gaussian primes in a rectangular block.>>>p=gp.gprimes_block(123456,67890,100,100)>>>p.plot()# Plot the Gaussian primes in the first octant with norm up to 10000.>>>p=gp.gprimes_sector(10000,0,3.14159/8)>>>p.plot()# Plot the Gaussian primes as well as their associates with norm up to 1000.>>>p=gp.gprimes(1000)>>>p.plot(full_disk=True)Seethis Jupyter notebookfor many more usage examples.AlgorithmPrime generating sieves often employ asieve arrayA and a set ofsmall primesP. For each prime p in P, sieving proceeds by crossing off multiples of p in the sieve array A. On a computer, a sieve array can be realized as container of booleans indexed by elements of A. The state of each boolean records if the underlying index has been crossed off by some prime p in P.In broad strokes, sieving in the Gaussian integers involves the following steps.Initialize a 2-dimensional array A with index (a, b) corresponding to the Gaussian integer a + bi. In this way, we can view this array as representing a finite subset of the Gaussian integers. Multiplication by i gives a 4-fold rotational symmetry in the complex plane and complex conjugation (in conjunction with multiplication by i) gives a reflectional symmetry about the line y = x in the complex plane. Such symmetries allow us to reduce the size of the sieving array if it contains redundancies encapsulated by these symmetries.Generate a set ofsmall primesP either through calling another sieve or by using the existing sieve array to locate uncrossed elements of small norm. The set P contains all primes with norm up to sqrt(B), where B is the norm of the largest Gaussian integer in A. In either case, for each prime a + bi in P, leave the sieve array A uncrossed at a + bi but mark off every Gaussian integer multiple of a + bi. Continue this process for each prime in P.The unmarked entries that remain in the array A are exactly the Gaussian primes in A.See herefor a discussion of different approaches to generating primes in the Gaussian integers and some motivation for our choice of algorithm.Crossing off multiplesGiven a Gaussian integer a + bi, we are tasked with crossing off multiples of a + bi from our sieve array. A multiple of a + bi is of the form (a + bi)(c + di) for c, d in Z. Expanding this, we seek indices of the form (u, v) where
u = ac - bd, and v = ad + bc.
This simply requires a double for-loop (once over c and once over d). Moreover, we can avoid costly multiplications by adding or subtracting c's or d's from the current index u or v while iterating. In the language of abstract algebra, we are crossing off elements of the Z-module generated by (a, b) and (-b, a).Donut SieveIn the rational integers Z, thewheel sieveallows for a more efficient way to manage and step through the sieve array. Primes other than 2, 3, and 5 must lie in one of the 8 distinct residue classes 1, 7, 11, 13, 17, 19, 23, 29 mod 30. In light of this observation, the wheel sieve (with a wheel size of 30) reduces both the memory and complexity of sieving by only tracking these 8 distinguished residue classes. If implemented carefully, a wheel sieve will not add significant overhead to the actual sieving process, and it will reduce to the total size of the sieve array to 8 / 30 = 27% of the original size.This project extends the idea of a wheel sieve to the Gaussian integers. Instead of a wheel corresponding to the quotient Z/30Z rolling over the integers, we consider the finite set of remainders in Z[i]/10Z[i]. Mathematically, this quotient is adonutor torus. Just as the primes 2, 3, 5 in the rational integers give rise to a mod-30 wheel, the mod-10 donut arises from the primes 1 + i, 1 + 2i and 2 + i. The first of these three primes sits above the ramifying prime 2, and the second and third both sit above the splitting prime 5.Every Gaussian integer z satisfying z = 0 (mod 1 + i) should be omitted from the sieve array. The prime 1 + i has norm 2, and so one out of every two Gaussian integers will be divisible by 1 + i. In this way, including 1 + i in the donut-base will reduce the sieve array size by 50%.We deal with the primes 1 + 2i and 2 + i together (by the Chinese Remainder Theorem, this is equivalent to working in Z[i]/5Z[i]). Every Gaussian integer satisfying z = 0 (mod 1 + 2i)orz = 0 (mod 2 + i) should be omitted from the sieve array. By the inclusion-exclusion principle and the CRT, there are exactly 9 solutions to these equations in Z[i]/5Z[i]. The Gaussian integer 5 has norm 25, and so 9 out of every 25 Gaussian integers will be removed from the sieve array. This provides a savings of 9/25 = 36%.Combining both of these local conditions, our donut allows us to only consider 32 of the 100 residue classes in Z[i]/10Z[i].In the wheel sieve, adding more primes to the wheel-base will allow more savings within the sieve array at the expense of increasing the combinatorial complexity of the sieving process itself. We stopped with a donut of size 10 for hardware-related reasons (seeimplementation), however, the following additional primes could be consider.The inert prime 3 in the donut would give a savings of 1/9 = 11% in sieve array size.The primes 3 + 2i and 3 + 2i, those dividing 13, would give a savings of 25/169 = 15% in sieve array size.In the classesOctantDonutSieveandBlockDonutSievewe implement the basic structure of the donut sieve. In particular, these classes contain variables and methods for accessing and dealing with the donut. The array below exemplifies how to deal with donut difficulties in a mod-10 donut. The entries in this array correspond to horizontal gaps between distinguished residue classes.{{0, 2, 0, 4, 0, 0, 0, 2, 0, 2},
 {4, 0, 0, 0, 2, 0, 4, 0, 0, 0},
 {0, 0, 0, 2, 0, 2, 0, 6, 0, 0},
 {2, 0, 6, 0, 0, 0, 0, 0, 2, 0},
 {0, 4, 0, 0, 0, 4, 0, 0, 0, 2},
 {0, 0, 2, 0, 2, 0, 2, 0, 4, 0},
 {0, 4, 0, 0, 0, 4, 0, 0, 0, 2},
 {2, 0, 6, 0, 0, 0, 0, 0, 2, 0},
 {0, 0, 0, 2, 0, 2, 0, 6, 0, 0},
 {4, 0, 0, 0, 2, 0, 4, 0, 0, 0}}Segmented SieveIn a segmented sieve, we break up the sieving array into smaller subsets calledsegments. With these segments in place, we perform sieving (crossing off multiples of small primes) segment by segment. There are advantages and disadvantages to this segmented approach.The main advantage of segmentation is that each segment can fit more readily into computer hardware such as the L2-cache. This will allow the inner for-loop in the snippet below to execute more quickly because each segment can be quickly accessed.The main disadvantage of segmentation is some additional overhead. Using code snippet below as an example, for some primespthere will be few or no calls to the functioncross_off_multiples. As a rule of thumb, each segment should contain at least one multiple of each small primep.forsegmentinsieve_array:forpinsmall_primes:cross_off_multiples(segment,p)In this project, segmentation can be achieved by calling instances of theBlockSieveclass. InVerticalMoatandSegmentedMoat, we take this approach to explore Gaussian primes.C++ ImplementationThe aforementioned algorithm is implemented in a C++ library.BaseSieveis an abstract base class with some basic sieving methods. Classes derived from this includeOctantSieve,OctantDonutSieve,SectorSieve,BlockSieve, andBlockDonutSieve. Each derived class has its own method for initiating and accessing the sieve array. See theusage examplesfor various text representations of these sieve arrays.In classesOctantSieve,SectorSieve, andBlockSieve, the 2-dimensional sieve array A is stored with avector<vector<bool>>container. This C++ object is memory-efficient: each boolean is stored as a single bit in memory (this in itself gives a 224-fold savings over python 3.7 which requires 28 bytes of memory to store a boolean value).In implementing the donut sieve in the classesOctantDonutSieveandBlockDonutSieve, each 10 x 10 block of Gaussian integers corresponds to a fulldonut roll. Thedonut sieverequires holding 32 residue classes for every 10 x 10 block of Gaussian integers. Said differently, every 10 x 10 block of Gaussian integers requires 32 bits of information to store its current state in the sieve process. Conveniently, a C++inttypically also requires 32 bits of memory space. In this way, in donut-based classes our sieve array is avector<vector<int>>container in C++.ApplicationsThis library can be used to generate data that sheds new light on several unsolved problems in number theory.Gaussian prime races in sectorsAngular distribution of Gaussian primesThe Gaussian moat problemAll three of these applications have implementations within thePython APIand can be found inside thegintsievepython module.This Jupyter notebookgives some examples.Gaussian prime races in sectorsPrime number races have been thoroughly studied beginning with an observation of Chebyshev in 1853. Odd prime numbers fall into two disjoint categories: primes of the form 4k + 1, and primes of the form 4k + 3. Chebyshev noted that when counting the number of primes in these two sets up to some threshold x, the primes of the form 4k + 3 often seemed to lead the race. This phenomenon has been extensively quantified and generalized in the realm of the rational integers, and is known asChebyshev bias.In the Gaussian integers, rather than group a prime by its remainder mod 4, we can categorize Gaussian primes by sector. Aclassical resultof Hecke states that the angles determined by Gaussian primes areequidistributed. In other words, given two sectors in the complex plane with equal central angles, Hecke proved that as the radius of the sector grows large, both have the same asymptotic number of primes. Just as Chebyshev raced the primes in the residue classes 4k + 1 and 4k + 3, one can hold a race among Gaussian primes in two sectors with equal central angle.TheSectorSieveclass performs sieving in a specified sector in the complex plane. We use this class to conduct races within sectors and calculate Chebyshev bias. To the best of my knowledge, these prime number races in sectors were previously unstudied. ThePython APIcontain a class for exploring these sector races.Angular distribution of Gaussian primesIn addition to two-way prime number races, one can consider races with many participants. In the rational primes, odd primes fall into one of the four categories 8k + 1, 8k + 3, 8k + 5, and 8k + 7. As a consequence, we can consider the four-way race between these four disjoint residue classes mod 8. This many-way race can just as easily be considered with Gaussian primes. In particular, for a collection of sectors having the same central angle, we consider the distribution of the counts of Gaussian primes within these sectors as the bound on the sectors' radii grows. This is data collection is implemented in thePython API.The Gaussian moat problemTo conceptualize the Gaussian moat problem, imagine a frog jumping between lily pads situated in the complex plane at exactly the Gaussian primes. The frog, having finite strength, can only take jumps of bounded size. A famous unsolved problems asks if such a frog could travel arbitrarily far from its starting point. If this frog fails to do so, it must have encountered someGaussian moatpreventing it from making progress.This problem, lying at the interface between number theory and percolation theory, has been extensively studied since it was first posed in 1962. In 2004, Tsuchimura computed the state of the art result by showing that a frog starting at the origin and taking jumps of distance at most 6 will encounter a Gaussian moat. It is a folklore conjecture that given any bounded jump size, a moat will always be encountered.Beginning with a bound on the frog's jump, form a graph whose vertices are Gaussian primes. Two vertices are adjacent if the distance between them is less than or equal to the jump bound. Three classes within this library explore themain connected componentof this graph, that is, the connected component containing the Gaussian prime 1 + i (the prime closest to the origin).In the classOctantMoat, the main connected component is explored using adepth-first searchapproach. Initially, all primes are generated and held in memory using theOctantSieveclass. Once the sieve array is larger than the memory capabilities of the computer calling this class, this approach fails.InSegmentedMoat, the same component is explored using a segmented sieving approach. Instances ofBlockSieveare called and explored; boundary data is passed from one instance to the next. This approach is to similar to the algorithm in Tsuchimura's paperComputational Results for Gaussian Moat Problem.InVerticalMoat, we consider a vertical strip in the complex plane running between the real-axis and the diagonal line y = x. As inSegmentedMoat, instances ofBlockSieveare called and boundary data is shared. This algorithm searches for a sequence of adjacent Gaussian primes spanning the vertical strip. If no such sequence is found, then a moat is present, and as a consequence, the main component is finite. A similar approach was taken in the paperA Stroll Through the Gaussian Primesby Gethner, Wagon, and Wick.All of these moat-exploring classes can be accessed from thegintmoatexecutable. Command line options include:Usage: ./gintmoat jumpSize [realPart] [option1] [option2] ...
Explore the connected component at the origin in the Gaussian moat problem.
    jumpSize            The jump bound giving adjacency relation among primes.
    realPart            The real-part of the vertical strip to explore if in
                        vertical mode.

Exploration modes:
    --origin            Explore the connected component starting at the origin until
                        an impassable moat is encountered. Default exploration mode.
    --segmented         Use a segmented approach to explore the connected component.
                        Algorithm is similar to that in Tsuchimura paper. This approach
                        only counts the size of the component.
    --vertical          Search for a Gaussian moat along a thin vertical strip starting
                        at real-part x. Used to show a component is finite.

Options:
    -h, --help          Print this help message.
    -v, --verbose       Display progress.
    -p, --printprimes   Print the real and imag part of primes in the connected component
                        if in origin mode.For example, to print all primes that can be reach with jumps up to distance 1.5, we run:$./gintmoat1.5-p112130324152617072839485103114Themainconnectedcomponenthassize:14Thefarthestoutprimeincomponenthascoordinates:114To get the size of the component with jumps up to distance 4.5, we could run the following (and wait nearly an hour):$./gintmoat4.5--segmented


Themainconnectedcomponenthassize:273791623Many other graph theoretic statistics could be gathered using a similar implementation. Examples of interesting data to consider include:The distribution of the vertex degrees in the main componentNumber and location ofbridgesSize of other connected components (away from the origin)"Lakes" and other regions void of primesThis project could readily be adapted to explore such phenomena.TestsPythonTest thegaussianprimesPython package by runningpython python/test_gaussianprimes.py.C++Results of this repository can be verified withhttp://oeis.org/A091100. The programginttest.cppautomates this.maketestMarkdown-formatted output of this is pasted below.Testing and timing OctantSieve and OctantDonutSievenorm bound# of primes including associatesOctantSieve timeOctantDonutSieve time2^203279720.039 s0.017 s2^216227000.073 s0.032 s2^2211841280.147 s0.065 s2^2322561400.295 s0.13 s2^2443112560.622 s0.283 s2^2582547122.247 s0.551 s2^26158300202.8 s0.953 s2^27304158168.317 s3.281 s2^285852560412.048 s3.927 s2^2911277028426.179 s8.516 s2^3021759982456.611 s18.362 sTesting and timing BlockSieve and BlockDonutSieve with random rectanglesblock# of primesBlockSieve timeBlockDonutSieve time[50000000, 50000700) x [80000000, 80000600)145998.787 s4.456 s[80000000, 80000300) x [20000000, 20000100)10696.239 s4.261 s[30000000, 30000600) x [40000000, 40000100)21525.006 s2.783 s[50000000, 50000100) x [80000000, 80000900)31379.424 s5.808 s[90000000, 90000200) x [90000000, 90000800)543211.09 s5.532 s[20000000, 20000100) x [90000000, 90000100)3526.33 s3.875 s[30000000, 30000300) x [50000000, 50000500)53703.868 s2.502 s[40000000, 40000500) x [20000000, 20000700)126613.215 s2.008 s[10000000, 10000200) x [80000000, 80000600)42095.864 s3.595 s[30000000, 30000200) x [20000000, 20000400)29152.609 s1.714 sTesting and timing SectorSieve with random sectorsalphabetabeta - alphanorm bound# of primestime0.2284570.753730.5252732^20274100.0410.541190.7632060.2220162^21220080.0380.7686520.7739410.005289182^229710.0140.5080.6656470.1576472^23565790.1010.01761640.6366020.6189862^244246310.6910.2573150.580760.3234452^254249790.7590.6001250.7643350.164212^264136330.7930.0990270.5602950.4612682^2722330835.2420.2542820.6225130.3682312^2834299428.6110.6714020.7661830.09478142^2917009254.6880.03799320.7319880.6939952^302403485589.962Timing SectorSieve with random thin sectorsalphabetabeta - alphanorm bound# of primestime0.5425570.5435332^-102^30340140.5480.1658160.1663042^-112^31327290.8660.3210850.321332^-122^32314911.3190.4194260.4195482^-132^33306262.2270.3413360.3413972^-142^34295333.3610.652820.6528512^-152^35287985.7280.2585260.2585412^-162^36277999.0220.4157010.4157082^-172^372711713.8970.3005030.3005072^-182^382663023.1020.2474650.2474662^-192^392572637.0140.4229510.4229522^-202^402498154.84Testing OctantMoat and SegmentedMoatjumpSizesize of main componentfarthest prime encountered29242 17338084 413.531221976 31143476383297 27804.32386129not computedLicenseMIT License