modular_forms 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +152 -0
- data/lib/modular_forms/core/dedekind_eta_functions.rb +68 -0
- data/lib/modular_forms/core/dirichlet_characters.rb +57 -0
- data/lib/modular_forms/core/eisenstein_series.rb +45 -0
- data/lib/modular_forms/core/elliptic_curves_fp.rb +127 -0
- data/lib/modular_forms/core/elliptic_curves_q.rb +128 -0
- data/lib/modular_forms/core/hecke_operators.rb +48 -0
- data/lib/modular_forms/core/klein_j_invariant.rb +20 -0
- data/lib/modular_forms/core/newform_invariants.rb +39 -0
- data/lib/modular_forms/core/numeric_helpers/numeric_helpers.rb +122 -0
- data/lib/modular_forms/core/ramanujan_tau_function.rb +26 -0
- data/lib/modular_forms/core/sl2z_groups.rb +79 -0
- data/lib/modular_forms/core/theta_functions.rb +35 -0
- data/lib/modular_forms/core.rb +13 -0
- data/lib/modular_forms/version.rb +5 -0
- data/lib/modular_forms.rb +195 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c2b8536ed2846f289afd967456cdd892858ad3af312acffab3e312c57b367a38
|
4
|
+
data.tar.gz: ea900a9133578ce1278141bda388d85fc222419ac2de207c50a10c243176166f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7542e5fa3105f3008bdf192c2471e950898567aca9c133ebb50c7e0d65ec1bd0c3b1765c944257c59782bd26f2f849f15d7653652d14844d844a91ab1174b7b9
|
7
|
+
data.tar.gz: 6878c24ff277884ff52aeb3ad54073ae50001b3f0f792b317b1d4ed7caf40151a867124a394eef0731846f1d3732b096f741e4e8c6e2473f9b172eddaed0d237
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# Modular Forms
|
2
|
+
|
3
|
+
A creative toolkit for exploring modular forms and elliptic curves through [Sonic Pi](https://sonic-pi.net/).
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
## Project Status
|
8
|
+
|
9
|
+
This is a pre-alpha release of `modular_forms`. At this stage, only a subset of core mathematical definitions and operations is implemented.
|
10
|
+
Future updates might include a DSL, depending on how the library is used and the interest from the community.
|
11
|
+
|
12
|
+
Contributions are welcome! Feel free to fork, make changes, and submit a pull request.
|
13
|
+
|
14
|
+
## Features
|
15
|
+
|
16
|
+
- **Accessible to both musicians and coders**: No math expertise required. Create musical patterns, rhythms, timbres, and harmonies by experimenting with mathematical ideas and turning them into sound and effects intuitively.
|
17
|
+
- **Interactive Educational Resource**: Use **Sonic Pi** to discover introductory number theory in a hands-on, immersive way, gaining insights into abstract concepts through math in action.
|
18
|
+
|
19
|
+
## Purpose and Scope
|
20
|
+
|
21
|
+
Given the vastness of the field, this tool intentionally focuses on a limited subset of definitions, without covering all aspects of each. Below is a list of the implemented modules:
|
22
|
+
|
23
|
+
- [Eisenstein Series](#eisenstein-series)
|
24
|
+
- [Eta Functions and Eta Quotients](#eta-functions-and-eta-quotients)
|
25
|
+
- [Theta functions](#theta-functions)
|
26
|
+
- [Ramanujan Tau Function](#ramanujan-tau-function)
|
27
|
+
- [J-Function](#j-function)
|
28
|
+
- [Hecke Operators](#hecke-operators)
|
29
|
+
- [SL(2,Z) Group](#sl2z-group)
|
30
|
+
- [Dirichlet Characters](#dirichlet-characters)
|
31
|
+
- [Elliptic Curves over Rationals](#elliptic-curves-over-rationals)
|
32
|
+
- [Elliptic Curves over Finite Fields](#elliptic-curves-over-finite-fields)
|
33
|
+
- [Newforms Invariants](#newforms-invariants)
|
34
|
+
|
35
|
+
### Not Optimized for Computational Efficiency
|
36
|
+
|
37
|
+
This library is designed for creative exploration rather than maximum computational efficiency. It is not intended to replace advanced mathematical software. Instead, it draws inspiration from tools like SageMath, Pari/GP, and the LMFDB database.
|
38
|
+
|
39
|
+
### Goal
|
40
|
+
The goal is simple: to provide an accessible and creative starting point for those who wish to explore, learn, and uncover new ideas, regardless of their mathematical background.
|
41
|
+
|
42
|
+
## Installation
|
43
|
+
|
44
|
+
You can install the `modular_forms` gem directly from **RubyGems** or clone it from GitHub.
|
45
|
+
|
46
|
+
```bash
|
47
|
+
gem install modular_forms
|
48
|
+
```
|
49
|
+
|
50
|
+
## How to use?
|
51
|
+
|
52
|
+
You can dive into the beauty of math, both in Ruby and Sonic Pi, creating music in real-time. Here is a simple example of how to use **modular_forms** to generate a basic musical pattern:
|
53
|
+
|
54
|
+
```rb
|
55
|
+
# If you are using Ruby, simply load the gem with its name
|
56
|
+
# after installing it via 'gem install modular_forms'.
|
57
|
+
require 'modular_forms'
|
58
|
+
|
59
|
+
# If you are using Sonic Pi, replace <PATH>
|
60
|
+
# with the full path to the 'modular_forms.rb' file
|
61
|
+
# inside the gem installation on your system.
|
62
|
+
require "<PATH>/modular_forms.rb"
|
63
|
+
|
64
|
+
# Calculate the Eisenstein series of weight k = 4
|
65
|
+
eisenstein_melody = ModularForms.eisenstein_serie(4)
|
66
|
+
|
67
|
+
# Play the melody in a loop with a mathematical transformation
|
68
|
+
120.times do
|
69
|
+
play eisenstein_melody.next % 12 * 7
|
70
|
+
sleep 0.5
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
## Implemented Modular Forms, Elliptic Curves, and Related Definitions
|
75
|
+
|
76
|
+
### Eisenstein Series
|
77
|
+
|
78
|
+
1. `ModularForms.eisenstein_series(weight_k, galois_field = nil)`
|
79
|
+
2. `ModularForms.eisenstein_series_product(weight_k1, weight_k2, prec)`
|
80
|
+
3. `ModularForms.eisenstein_series_pow(weight_k, power, prec)`
|
81
|
+
|
82
|
+
### Eta Functions and Eta Quotients
|
83
|
+
|
84
|
+
4. `ModularForms.dedekind_eta_function(m_scale = 1, pentagonal_coefs = false)`
|
85
|
+
5. `ModularForms.dedekind_eta_pow(power, prec, m_scale = 1)`
|
86
|
+
6. `ModularForms.dedekind_sum(h, k)`
|
87
|
+
7. `ModularForms.eta_product(eta1, eta2, prec = nil)`
|
88
|
+
8. `ModularForms.eta_quotient(num_eta, den_eta, prec)`
|
89
|
+
|
90
|
+
### Theta functions
|
91
|
+
|
92
|
+
9. `ModularForms.jacobi_theta_function(jacobi_index = 3, square_coefs = false)`
|
93
|
+
10. `ModularForms.jacobi_theta_function_pow(jacobi_index, power, prec)`
|
94
|
+
|
95
|
+
### Ramanujan Tau Function
|
96
|
+
|
97
|
+
11. `ModularForms.ramanujan_tau_function`
|
98
|
+
|
99
|
+
### J-Function
|
100
|
+
|
101
|
+
12. `ModularForms.j_function(prec)`
|
102
|
+
|
103
|
+
### Hecke Operators
|
104
|
+
|
105
|
+
13. `ModularForms.hecke_operator_prime_non_cusp(non_cusp_form_arr, prime, weight_k, prec)`
|
106
|
+
14. `ModularForms.hecke_operator_prime_cusp(cusp_form_arr, prime, weight_k, prec)`
|
107
|
+
|
108
|
+
### SL(2,Z) Group
|
109
|
+
|
110
|
+
15. `ModularForms.t_gen_matrix(n_power)`
|
111
|
+
16. `ModularForms.s_gen_matrix(n_power)`
|
112
|
+
17. `ModularForms.u_gen_matrix(mod_n)`
|
113
|
+
18. `ModularForms.st_gen_matrix(n_power)`
|
114
|
+
19. `ModularForms.product_gen_mats(gen_mat_a, gen_mat_b)`
|
115
|
+
20. `ModularForms.gamma0_index(n)`
|
116
|
+
21. `ModularForms.gamma1_index(n)`
|
117
|
+
|
118
|
+
### Dirichlet Characters
|
119
|
+
|
120
|
+
22. `ModularForms.dirichlet_trivchar(modq, a)`
|
121
|
+
23. `ModularForms.conrey_p_pminus1(modp, a)`
|
122
|
+
24. `ModularForms.gauss_sum_triv(dirichlet_q, a)`
|
123
|
+
25. `ModularForms.gauss_sum_conrey_p_minus1(dirichlet_q, a, parity)`
|
124
|
+
|
125
|
+
### Elliptic Curves over Rationals
|
126
|
+
|
127
|
+
26. `ModularForms.elliptic_curve_q(coefs)`
|
128
|
+
27. `ModularForms.discriminant_q(curve)`
|
129
|
+
28. `ModularForms.j_invariant_q(curve)`
|
130
|
+
29. `ModularForms.point_on_curve_q?(curve, point)`
|
131
|
+
30. `ModularForms.point_addition_q(curve, p, q)`
|
132
|
+
31. `ModularForms.scalar_mul_point_q(curve, n, point)`
|
133
|
+
32. `ModularForms.isogeny_2deg_q(curve, point_2tor)`
|
134
|
+
33. `ModularForms.isogeny_ndeg_q(curve, point_ntor, order)`
|
135
|
+
34. `ModularForms.weil_height(x_point)`
|
136
|
+
35. `ModularForms.canonical_height(curve, point, prec = 64)`
|
137
|
+
|
138
|
+
### Elliptic Curves over Finite Fields
|
139
|
+
|
140
|
+
36. `ModularForms.elliptic_curve_fp(p, coefs)`
|
141
|
+
37. `ModularForms.point_on_curve_modp?(curve, point)`
|
142
|
+
38. `ModularForms.discriminant_modp(curve)`
|
143
|
+
39. `ModularForms.j_invariant_modp(curve)`
|
144
|
+
40. `ModularForms.point_addition_modp(curve, p_point, q_point)`
|
145
|
+
41. `ModularForms.scalar_mul_point_modp(curve, n, point)`
|
146
|
+
42. `ModularForms.points_fp(curve, point_at_infinity = false)`
|
147
|
+
43. `ModularForms.cardinality_fp(curve)`
|
148
|
+
44. `ModularForms.quadratic_twist_fp(curve)`
|
149
|
+
|
150
|
+
### Newforms Invariants
|
151
|
+
45. `ModularForms.analytic_conductor(level_n, weight_k)`
|
152
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::DedekindEtaFunctions
|
8
|
+
#
|
9
|
+
# This module provides a generator for Dedekind eta functions.
|
10
|
+
module DedekindEtaFunctions
|
11
|
+
def self.eta_function(m_scale = 1, pent_coefs = false) # rubocop:disable Style/OptionalBooleanParameter,Metrics/MethodLength,Metrics/AbcSize
|
12
|
+
Enumerator.new do |q|
|
13
|
+
q << 1
|
14
|
+
(m_scale - 1).times do
|
15
|
+
q << 0
|
16
|
+
end
|
17
|
+
(1..Float::INFINITY).each do |tau|
|
18
|
+
pentagonal_num_minus = ((3 * tau**2 - tau) / 2) * m_scale
|
19
|
+
pentagonal_num_plus = ((3 * tau**2 + tau) / 2) * m_scale
|
20
|
+
min_next_pent = ((3 * (tau + 1)**2 - (tau + 1)) / 2) * m_scale
|
21
|
+
sign = (-1)**tau
|
22
|
+
q << (pent_coefs == false ? sign : sign * pentagonal_num_minus)
|
23
|
+
(pentagonal_num_plus - pentagonal_num_minus - 1).abs.times do
|
24
|
+
q << 0
|
25
|
+
end
|
26
|
+
q << (pent_coefs == false ? sign : sign * pentagonal_num_plus)
|
27
|
+
(min_next_pent - pentagonal_num_plus - 1).abs.times do
|
28
|
+
q << 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.eta_function_pow(power, precision, m_scale = 1)
|
35
|
+
vec = eta_function(m_scale).take(precision)
|
36
|
+
eta_q_coefs = [1]
|
37
|
+
power.times do
|
38
|
+
eta_q_coefs = NumericHelpers.linear_convolve(eta_q_coefs, vec, precision)
|
39
|
+
end
|
40
|
+
eta_q_coefs
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.eta_product(eta1, eta2, prec = nil)
|
44
|
+
NumericHelpers.linear_convolve(eta1, eta2, prec)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.eta_quotient(num_eta, den_eta, prec)
|
48
|
+
NumericHelpers.quotient_of_series(num_eta, den_eta, prec)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.centered_fractional_part(num, den)
|
52
|
+
return 0 if num % den == 0 # rubocop:disable Style/NumericPredicate
|
53
|
+
|
54
|
+
Rational(num, den) - (num / den).floor - Rational(1, 2)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.dedekind_sum(h, k)
|
58
|
+
raise "#{k} parameter must be greater than 0" if k < 1
|
59
|
+
|
60
|
+
s = 0
|
61
|
+
(1..k - 1).each do |n|
|
62
|
+
s += centered_fractional_part(n, k) * centered_fractional_part(h * n, k)
|
63
|
+
end
|
64
|
+
s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
require 'bigdecimal'
|
5
|
+
|
6
|
+
module ModularForms
|
7
|
+
module Core
|
8
|
+
# ModularForms::Core::DirichletCharacters
|
9
|
+
#
|
10
|
+
# This module provides generators for Dirichlet characters and related topics.
|
11
|
+
module DirichletCharacters
|
12
|
+
PI2 = 2 * Math::PI
|
13
|
+
|
14
|
+
PARITY = {
|
15
|
+
EVEN: 'even',
|
16
|
+
ODD: 'odd'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def self.dirichlet_trivchar(modq, a)
|
20
|
+
NumericHelpers.gcd(a, modq) == 1 ? 1 : 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.conrey_p_pminus1(modp, a)
|
24
|
+
raise "#{modp} must be a prime number" if !NumericHelpers.prime_number?(modp) # rubocop:disable Style/NegatedIf
|
25
|
+
return 0 if a % modp == 0 # rubocop:disable Style/NumericPredicate
|
26
|
+
return 1 if NumericHelpers.euler_criterion(a, modp)
|
27
|
+
|
28
|
+
-1 if !NumericHelpers.euler_criterion(a, modp) # rubocop:disable Style/NegatedIf
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.exponential(a, r, q)
|
32
|
+
Complex.polar(1, PI2 * (a * r) / q)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.gauss_sum_triv(q_char, a)
|
36
|
+
t_a = 0 + 0i
|
37
|
+
(0..q_char - 1).each do |r|
|
38
|
+
t_a += dirichlet_trivchar(q_char, r) * exponential(a, r, q_char)
|
39
|
+
end
|
40
|
+
t_a.real.to_f.round(2)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.gauss_sum_conrey_p_minus1(q_char, a, parity) # rubocop:disable Metrics/AbcSize
|
44
|
+
raise "Parity '#{parity}' must be either 'even' or 'odd'" if parity != PARITY[:ODD] && parity != PARITY[:EVEN]
|
45
|
+
|
46
|
+
t_a = 0 + 0i
|
47
|
+
(0..q_char - 1).each do |r|
|
48
|
+
t_a += conrey_p_pminus1(q_char, r) * exponential(a, r, q_char)
|
49
|
+
end
|
50
|
+
|
51
|
+
return t_a.real.round(10) if parity == PARITY[:EVEN]
|
52
|
+
|
53
|
+
Complex(0, t_a.imaginary.round(10)) if parity == PARITY[:ODD]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::EisensteinSeries
|
8
|
+
#
|
9
|
+
# This module provides a generator for Eisenstein series.
|
10
|
+
module EisensteinSeries
|
11
|
+
def self.eisenstein_series(k, gal_f = nil) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
12
|
+
raise ArgumentError, 'k must be even and >= 2' if k < 2 || k % 2 != 0 # rubocop:disable Style/EvenOdd
|
13
|
+
|
14
|
+
bernoulli_k = NumericHelpers.bernoulli_numbers_arr(k)[k]
|
15
|
+
normalization_factor = - Rational(2 * k, bernoulli_k)
|
16
|
+
Enumerator.new do |q|
|
17
|
+
q << 1
|
18
|
+
(1..Float::INFINITY).each do |tau|
|
19
|
+
nth_term = (normalization_factor * NumericHelpers.divisor_function_sigma(tau, k - 1)).to_i
|
20
|
+
if gal_f != nil # rubocop:disable Style/ConditionalAssignment,Style/NonNilCheck
|
21
|
+
q << NumericHelpers.galois_field(nth_term, gal_f)
|
22
|
+
else
|
23
|
+
q << nth_term
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.eisenstein_series_product(weight_k1, weight_k2, precision)
|
30
|
+
vec1 = eisenstein_series(weight_k1).take(precision)
|
31
|
+
vec2 = eisenstein_series(weight_k2).take(precision)
|
32
|
+
NumericHelpers.linear_convolve(vec1, vec2, precision)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.eisenstein_series_pow(weight_k, power, precision)
|
36
|
+
vec = eisenstein_series(weight_k).take(precision)
|
37
|
+
eisenstein_q_coefs = [1]
|
38
|
+
power.times do
|
39
|
+
eisenstein_q_coefs = NumericHelpers.linear_convolve(eisenstein_q_coefs, vec, precision)
|
40
|
+
end
|
41
|
+
eisenstein_q_coefs
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
require_relative './elliptic_curves_q'
|
5
|
+
|
6
|
+
module ModularForms
|
7
|
+
module Core
|
8
|
+
# ModularForms::Core::EllipticCurvesFp
|
9
|
+
#
|
10
|
+
# This module provides methods for generating points on Elliptic Curves over Finite Fields (short Weierstrass form)
|
11
|
+
module EllipticCurvesFp
|
12
|
+
def self.reduction_modp(n, modp)
|
13
|
+
((n % modp) + modp) % modp
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.elliptic_curve_fp(p, coefs)
|
17
|
+
a, b = coefs
|
18
|
+
raise "#{p} is not a prime number" if NumericHelpers.prime_number?(p) == false
|
19
|
+
|
20
|
+
d = reduction_modp(EllipticCurvesQ.discriminant(a, b), p)
|
21
|
+
|
22
|
+
a_modp = reduction_modp(a, p)
|
23
|
+
b_modp = reduction_modp(b, p)
|
24
|
+
raise "y^2=x^3 #{a_modp}x #{b_modp} defines a singular curve" if d == 0 # rubocop:disable Style/NumericPredicate
|
25
|
+
|
26
|
+
puts "y^2 = x^3 #{a_modp}x #{b_modp} over Finite Field #{p}"
|
27
|
+
{ a: a, b: b, p: p }
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.point_on_curve_modp?(curve, point)
|
31
|
+
return true if point == nil # rubocop:disable Style/NilComparison
|
32
|
+
|
33
|
+
a, b, p = curve.values_at(:a, :b, :p)
|
34
|
+
x, y = point
|
35
|
+
x_modp = reduction_modp(x, p)
|
36
|
+
y_modp = reduction_modp(y, p)
|
37
|
+
coordinates = reduction_modp(y**2, p) == reduction_modp(x**3 + x * a + b, p)
|
38
|
+
|
39
|
+
raise "Coordinates [#{x_modp},#{y_modp}] do not define a point on curve" unless coordinates
|
40
|
+
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.discriminant_modp(curve)
|
45
|
+
a, b, p = curve.values_at(:a, :b, :p)
|
46
|
+
reduction_modp(-16 * (4 * a**3 + 27 * b**2), p)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.fermat_inverse_modp(a, p)
|
50
|
+
a.pow(p - 2, p)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.j_invariant_modp(curve)
|
54
|
+
a, b, p = curve.values_at(:a, :b, :p)
|
55
|
+
reduction_modp(1728 * (4 * a**3) * fermat_inverse_modp(4 * a**3 + 27 * b**2, p), p)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.point_addition_modp(curve, p_point, q_point) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
59
|
+
a, _, p = curve.values_at(:a, _, :p)
|
60
|
+
return p_point if q_point == nil # rubocop:disable Style/NilComparison
|
61
|
+
return q_point if p_point == nil # rubocop:disable Style/NilComparison
|
62
|
+
|
63
|
+
x1, y1 = p_point
|
64
|
+
x2, y2 = q_point
|
65
|
+
return nil if x1 == x2 && reduction_modp(y1 + y2, p) == 0 # rubocop:disable Style/NumericPredicate
|
66
|
+
|
67
|
+
if p_point != q_point
|
68
|
+
num1 = y2 - y1
|
69
|
+
den1 = x2 - x1
|
70
|
+
lambda_m = num1 * fermat_inverse_modp(den1, p)
|
71
|
+
else
|
72
|
+
num2 = 3 * x1**2 + a
|
73
|
+
den2 = 2 * y1
|
74
|
+
lambda_m = num2 * fermat_inverse_modp(den2, p)
|
75
|
+
end
|
76
|
+
|
77
|
+
x3 = lambda_m**2 - x1 - x2
|
78
|
+
y3 = lambda_m * (x1 - x3) - y1
|
79
|
+
[reduction_modp(x3, p), reduction_modp(y3, p)]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.scalar_mul_point_modp(curve, n, point)
|
83
|
+
n_times_point = nil
|
84
|
+
addend = point
|
85
|
+
|
86
|
+
while n > 0 # rubocop:disable Style/NumericPredicate
|
87
|
+
n_times_point = point_addition_modp(curve, n_times_point, addend) if n.odd?
|
88
|
+
addend = point_addition_modp(curve, addend, addend)
|
89
|
+
n >>= 1
|
90
|
+
end
|
91
|
+
|
92
|
+
n_times_point
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.points(curve, point_at_infinity = false) # rubocop:disable Metrics/MethodLength,Style/OptionalBooleanParameter,Metrics/AbcSize
|
96
|
+
a, b, p = curve.values_at(:a, :b, :p)
|
97
|
+
|
98
|
+
list_of_points = point_at_infinity ? [nil] : []
|
99
|
+
(0..(p - 1)).each do |x|
|
100
|
+
eq_rh = (x**3 + a * x + b) % p
|
101
|
+
(0..((p - 1) / 2)).each do |y|
|
102
|
+
if ((y**2) % p) == eq_rh
|
103
|
+
list_of_points << [x, y]
|
104
|
+
list_of_points << [x, p - y] if p - y < p
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
list_of_points
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.cardinality(curve)
|
113
|
+
points(curve, true).length
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.quadratic_twist(curve)
|
117
|
+
a, b, p = curve.values_at(:a, :b, :p)
|
118
|
+
|
119
|
+
d_non_square = NumericHelpers.square_modp_list(p, 2)[1]
|
120
|
+
index = rand(d_non_square.length)
|
121
|
+
d = d_non_square[index]
|
122
|
+
|
123
|
+
elliptic_curve_fp(p, [(a * d**2) % p, (b * d**3) % p])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::EllipticCurves
|
8
|
+
#
|
9
|
+
# This module provides methods for generating points on Elliptic Curves (short Weierstrass form) over Q
|
10
|
+
module EllipticCurvesQ
|
11
|
+
def self.discriminant(a, b)
|
12
|
+
-16 * (4 * a**3 + 27 * b**2)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.elliptic_curve_q(coefs)
|
16
|
+
a, b = coefs
|
17
|
+
raise "y^2=x^3 #{a}x #{b} defines a singular curve" if discriminant(a, b) == 0 # rubocop:disable Style/NumericPredicate
|
18
|
+
|
19
|
+
puts "y^2 = x^3 #{a}x #{b}"
|
20
|
+
{ a: a, b: b }
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.point_on_curve?(curve, point)
|
24
|
+
return true if point == nil # rubocop:disable Style/NilComparison
|
25
|
+
|
26
|
+
a, b = curve.values_at(:a, :b)
|
27
|
+
x, y = point
|
28
|
+
coordinates = y**2 == x**3 + x * a + b
|
29
|
+
raise "Coordinates [#{x},#{y}] do not define a point on curve" unless coordinates
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.negate_p(point)
|
35
|
+
raise ArgumentError, 'Expected a point with two coordinates [x, y]' unless point.length == 2
|
36
|
+
|
37
|
+
x, y = point
|
38
|
+
[x, -y]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.discriminant_qq(curve)
|
42
|
+
a, b = curve.values_at(:a, :b)
|
43
|
+
-16 * (4 * a**3 + 27 * b**2)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.j_invariant(curve)
|
47
|
+
a, b = curve.values_at(:a, :b)
|
48
|
+
return 0 if a == 0 # rubocop:disable Style/NumericPredicate
|
49
|
+
return 1728 if b == 0 # rubocop:disable Style/NumericPredicate
|
50
|
+
|
51
|
+
1728 * Rational(4 * a**3, 4 * a**3 + 27 * b**2)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.point_addition(curve, p, q) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
55
|
+
return p if q == nil # rubocop:disable Style/NilComparison
|
56
|
+
return q if p == nil # rubocop:disable Style/NilComparison
|
57
|
+
|
58
|
+
x1, y1 = p
|
59
|
+
x2, y2 = q
|
60
|
+
return nil if x1 == x2 && y1 + y2 == 0 # rubocop:disable Style/NumericPredicate
|
61
|
+
|
62
|
+
a, = curve.values_at(:a)
|
63
|
+
if p != q # rubocop:disable Style/ConditionalAssignment
|
64
|
+
lambda_m = Rational(y2 - y1, x2 - x1)
|
65
|
+
else
|
66
|
+
lambda_m = Rational(3 * x1**2 + a, 2 * y1)
|
67
|
+
end
|
68
|
+
x3 = lambda_m**2 - x1 - x2
|
69
|
+
y3 = lambda_m * (x1 - x3) - y1
|
70
|
+
[x3, y3]
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.scalar_mul_point(curve, n, point)
|
74
|
+
n_times_point = nil
|
75
|
+
addend = point
|
76
|
+
|
77
|
+
while n > 0 # rubocop:disable Style/NumericPredicate
|
78
|
+
n_times_point = point_addition(curve, n_times_point, addend) if n.odd?
|
79
|
+
addend = point_addition(curve, addend, addend)
|
80
|
+
n >>= 1
|
81
|
+
end
|
82
|
+
|
83
|
+
n_times_point
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.isogeny_2deg(curve, point_2tor)
|
87
|
+
a, b = curve.values_at(:a, :b)
|
88
|
+
x0 = point_2tor[0]
|
89
|
+
t = 3 * x0**2 + a
|
90
|
+
w = x0 * t
|
91
|
+
a_isog = a - 5 * t
|
92
|
+
b_isog = b - 7 * w
|
93
|
+
elliptic_curve_q([a_isog, b_isog])
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.isogeny_ndeg(curve, point_ntor, order) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
97
|
+
a, b = curve.values_at(:a, :b)
|
98
|
+
t = 0
|
99
|
+
w = 0
|
100
|
+
(1..order - 1).each do |n|
|
101
|
+
x0, y0 = scalar_mul_point(curve, n, point_ntor)
|
102
|
+
t_q = 3 * x0**2 + a
|
103
|
+
u_q = 2 * y0**2
|
104
|
+
w_q = u_q + t_q * x0
|
105
|
+
t += t_q
|
106
|
+
w += w_q
|
107
|
+
end
|
108
|
+
a_isog = a - 5 * t
|
109
|
+
b_isog = b - 7 * w
|
110
|
+
elliptic_curve_q([a_isog, b_isog])
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.weil_height(x_point)
|
114
|
+
return 0 if x_point == nil # rubocop:disable Style/NilComparison
|
115
|
+
|
116
|
+
Math.log(NumericHelpers.q_height(x_point))
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.canonical_height(curve, point, prec = 64)
|
120
|
+
n = prec
|
121
|
+
dim = 1 # To be implemented in a new module for ECurves over extensions of the rationals
|
122
|
+
two_pow_n = scalar_mul_point(curve, n, point)[0]
|
123
|
+
Rational(1, dim) * (weil_height([two_pow_n.numerator,
|
124
|
+
two_pow_n.denominator]) * n**-2)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::HeckeOperators
|
8
|
+
#
|
9
|
+
# This module provides methods for applying Hecke operators to modular forms.
|
10
|
+
module HeckeOperators
|
11
|
+
def self.hecke_prime_non_cusp(non_cusp_form, p, k, precision) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
12
|
+
raise ArgumentError, 'p must be a prime number' unless NumericHelpers.prime_number?(p)
|
13
|
+
|
14
|
+
q_size = non_cusp_form.length
|
15
|
+
minimum_q_size = (precision - 1) * p + 1
|
16
|
+
raise ArgumentError, "size of q-expansion must be at least #{minimum_q_size}" if q_size < minimum_q_size
|
17
|
+
|
18
|
+
normalization_factor = p**(k - 1)
|
19
|
+
(0..precision - 1).map do |n|
|
20
|
+
if n % p != 0
|
21
|
+
non_cusp_form[p * n]
|
22
|
+
else
|
23
|
+
non_cusp_form[p * n] + normalization_factor * non_cusp_form[n / p]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.hecke_prime_cusp(cusp_form, p, k, precision) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
29
|
+
raise ArgumentError, 'p must be a prime number' unless NumericHelpers.prime_number?(p)
|
30
|
+
|
31
|
+
q_size = cusp_form.length
|
32
|
+
minimum_q_size = precision * p
|
33
|
+
raise ArgumentError, "size of q-expansion must be at least #{minimum_q_size}" if q_size < minimum_q_size
|
34
|
+
|
35
|
+
cusp_form.unshift(0)
|
36
|
+
normalization_factor = p**(k - 1)
|
37
|
+
|
38
|
+
(1..precision).map do |n|
|
39
|
+
if n % p != 0
|
40
|
+
cusp_form[p * n]
|
41
|
+
else
|
42
|
+
cusp_form[p * n] + normalization_factor * cusp_form[n / p]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
require_relative 'eisenstein_series'
|
5
|
+
require_relative 'dedekind_eta_functions'
|
6
|
+
|
7
|
+
module ModularForms
|
8
|
+
module Core
|
9
|
+
# ModularForms::Core::KleinJInvariant
|
10
|
+
#
|
11
|
+
# This module provides an array of values for the j-invariant function.
|
12
|
+
module KleinJInvariant
|
13
|
+
def self.j_function(precision)
|
14
|
+
e4_cubed = EisensteinSeries.eisenstein_series_pow(4, 3, precision + 1)
|
15
|
+
delta_discriminant = DedekindEtaFunctions.eta_function_pow(24, precision + 1)
|
16
|
+
NumericHelpers.quotient_of_series(e4_cubed, delta_discriminant, precision)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::NewFormInvariants
|
8
|
+
#
|
9
|
+
# This module provides methods for NewForm Invariants
|
10
|
+
module NewFormInvariants
|
11
|
+
BERNOULLI = NumericHelpers.bernoulli_numbers_arr(16).reject { |bm| bm == 0 } # rubocop:disable Style/NumericPredicate
|
12
|
+
BERNOULLI_B_2m = BERNOULLI[2..16].each_with_index.map do |b2m, index|
|
13
|
+
Rational(b2m, (2 * (index + 1))).abs # abs => all B2m positive
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.digamma_func(z) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
17
|
+
partial_sum = 0.0
|
18
|
+
while z < 10
|
19
|
+
partial_sum -= Rational(1, z)
|
20
|
+
z += 1
|
21
|
+
end
|
22
|
+
inv_z = Rational(1 / z)
|
23
|
+
inv_z2 = inv_z * inv_z
|
24
|
+
partial_sum += Math.log(z) - Rational(1, 2) * inv_z
|
25
|
+
BERNOULLI_B_2m.each_with_index do |b2m, index|
|
26
|
+
partial_sum += (-1)**(index + 1) * b2m * (inv_z2**(index + 1))
|
27
|
+
end
|
28
|
+
partial_sum
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.analytic_conductor(level_n, weight_k)
|
32
|
+
psi = digamma_func(Rational(weight_k, 2))
|
33
|
+
num = Math.exp(psi)
|
34
|
+
den = 2 * Math::PI
|
35
|
+
(level_n * (num / den)**2).round(12)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularForms
|
4
|
+
module Core
|
5
|
+
# ModularForms::Core::NumericHelpers
|
6
|
+
#
|
7
|
+
# Contains functions used in the definitions of modular forms,
|
8
|
+
# as well as other related numerical operations.
|
9
|
+
module NumericHelpers
|
10
|
+
def self.factorial_iter(num)
|
11
|
+
t = 1
|
12
|
+
(1..num).each do |i|
|
13
|
+
t *= i
|
14
|
+
end
|
15
|
+
t
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.binomial_coefficient(n, k)
|
19
|
+
factorial_iter(n) / (factorial_iter(k) * factorial_iter(n - k))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.bernoulli_numbers_arr(n, bernoulli_numbers = [1])
|
23
|
+
return bernoulli_numbers[n] if n < bernoulli_numbers.length
|
24
|
+
|
25
|
+
(bernoulli_numbers.length..n).each do |k|
|
26
|
+
sum = Rational(0, 1)
|
27
|
+
(0...k).each do |j|
|
28
|
+
sum += binomial_coefficient(k + 1, j) * bernoulli_numbers[j]
|
29
|
+
end
|
30
|
+
bernoulli_numbers[k] = -sum / (k + 1)
|
31
|
+
end
|
32
|
+
bernoulli_numbers
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.divisor_function_sigma(n, z)
|
36
|
+
total_sum = 0
|
37
|
+
(1..n).each do |d|
|
38
|
+
if n % d == 0 # rubocop:disable Style/NumericPredicate,Style/IfUnlessModifier
|
39
|
+
total_sum += d**z
|
40
|
+
end
|
41
|
+
end
|
42
|
+
total_sum
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.prime_number?(n)
|
46
|
+
if !n.is_a?(Integer) # rubocop:disable Style/IfUnlessModifier,Style/NegatedIf
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
return false if n < 2
|
50
|
+
|
51
|
+
(2..Math.sqrt(n)).each do |i|
|
52
|
+
if (n % i) == 0 # rubocop:disable Style/NumericPredicate,Style/IfUnlessModifier
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.galois_field(n, modp)
|
60
|
+
if prime_number?(modp) == false # rubocop:disable Style/IfUnlessModifier
|
61
|
+
raise ArgumentError, 'GF must be a prime number'
|
62
|
+
end
|
63
|
+
|
64
|
+
((n % modp) + modp) % modp
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.linear_convolve(vec1, vec2, max_degree = nil) # rubocop:disable Metrics/AbcSize
|
68
|
+
max_degree == nil ? max_degree = (vec1.size + vec2.size) / 2 : max_degree # rubocop:disable Style/NilComparison
|
69
|
+
arr_product = Array.new(max_degree, 0)
|
70
|
+
(0..max_degree - 1).each do |n|
|
71
|
+
(0..n).each do |m|
|
72
|
+
if m < vec1.size && (n - m) < vec2.size # rubocop:disable Style/IfUnlessModifier
|
73
|
+
arr_product[n] += vec1[m] * vec2[n - m]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
arr_product
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.quotient_of_series(num, den, max_degree)
|
81
|
+
arr_coefs = []
|
82
|
+
(0..max_degree).each do |n|
|
83
|
+
sum = num[n] || 0
|
84
|
+
(1..n).each do |k|
|
85
|
+
sum -= (den[k] || 0) * (arr_coefs[n - k] || 0)
|
86
|
+
end
|
87
|
+
arr_coefs << sum / den[0].to_i
|
88
|
+
end
|
89
|
+
arr_coefs
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.euler_criterion(x, pmod)
|
93
|
+
(x**((pmod - 1) / 2)) % pmod == 1
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.square_modp_list(pmod, init = 1)
|
97
|
+
squares_fp = []
|
98
|
+
non_squares_fp = []
|
99
|
+
|
100
|
+
(init..pmod - 1).each do |x|
|
101
|
+
if euler_criterion(x, pmod)
|
102
|
+
squares_fp << x
|
103
|
+
else
|
104
|
+
non_squares_fp << x
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
[squares_fp, non_squares_fp]
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.gcd(a, m)
|
112
|
+
a, m = m, a % m while m != 0
|
113
|
+
a.abs
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.q_height(q_number)
|
117
|
+
x, y = q_number
|
118
|
+
[x.abs, y.abs].max
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::RamanujanTauFunction
|
8
|
+
#
|
9
|
+
# This module provides a generator for Ramanujan tau functions.
|
10
|
+
module RamanujanTauFunction
|
11
|
+
def self.niebur_sigma_formula # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
12
|
+
Enumerator.new do |q|
|
13
|
+
(1..Float::INFINITY).each do |n|
|
14
|
+
sum_term = 0
|
15
|
+
(1..(n - 1)).each do |i|
|
16
|
+
sum_term += i**2 * (35 * i**2 - 52 * i * n + 18 * n**2) *
|
17
|
+
NumericHelpers.divisor_function_sigma(i, 1) *
|
18
|
+
NumericHelpers.divisor_function_sigma(n - i, 1)
|
19
|
+
end
|
20
|
+
q << n**4 * NumericHelpers.divisor_function_sigma(n, 1) - 24 * sum_term
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::SL2Zgroups
|
8
|
+
#
|
9
|
+
# This module provides matrices from SL_2(Z) and related algebraic structures.
|
10
|
+
module SL2Zgroups
|
11
|
+
T_MATRIX = [[1, 1], [0, 1]].freeze
|
12
|
+
S_MATRIX = [[0, -1], [1, 0]].freeze
|
13
|
+
NEGATIVE_S_MATRIX = [[0, 1], [-1, 0]].freeze
|
14
|
+
I_MATRIX = [[1, 0], [0, 1]].freeze
|
15
|
+
NEGATIVE_I_MATRIX = [[-1, 0], [0, -1]].freeze
|
16
|
+
ST_MATRIX = [[0, -1], [1, 1]].freeze
|
17
|
+
U_MATRIX = [[1, 0], [1, 1]].freeze
|
18
|
+
|
19
|
+
def self.t_matrix(n_power)
|
20
|
+
[[1, n_power], [0, 1]]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.s_matrix(n_power)
|
24
|
+
matrix_s_power_map = {
|
25
|
+
1 => S_MATRIX,
|
26
|
+
2 => NEGATIVE_I_MATRIX,
|
27
|
+
3 => NEGATIVE_S_MATRIX,
|
28
|
+
0 => I_MATRIX
|
29
|
+
}
|
30
|
+
matrix_s_power_map[n_power % 4]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.st_matrix(n_power)
|
34
|
+
matrix_st_power_map = {
|
35
|
+
1 => ST_MATRIX,
|
36
|
+
2 => [[-1, -1], [1, 0]],
|
37
|
+
3 => NEGATIVE_I_MATRIX,
|
38
|
+
4 => [[0, 1], [-1, -1]],
|
39
|
+
5 => [[1, 1], [-1, 0]],
|
40
|
+
0 => I_MATRIX
|
41
|
+
}
|
42
|
+
matrix_st_power_map[n_power % 6]
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.u_matrix(n_power_mod)
|
46
|
+
[[1, 0], [n_power_mod, 1]]
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.product_gen_matrices(gen_mat_a, gen_mat_b) # rubocop:disable Metrics/AbcSize
|
50
|
+
[
|
51
|
+
[gen_mat_a[0][0] * gen_mat_b[0][0] + gen_mat_a[0][1] * gen_mat_b[1][0],
|
52
|
+
gen_mat_a[0][0] * gen_mat_b[0][1] + gen_mat_a[0][1] * gen_mat_b[1][1]],
|
53
|
+
[gen_mat_a[1][0] * gen_mat_b[0][0] + gen_mat_a[1][1] * gen_mat_b[1][0],
|
54
|
+
gen_mat_a[1][0] * gen_mat_b[0][1] + gen_mat_a[1][1] * gen_mat_b[1][1]]
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.index_gamma0(n)
|
59
|
+
product = 1
|
60
|
+
(2..n).each do |p|
|
61
|
+
if NumericHelpers.prime_number?(p) && n % p == 0 # rubocop:disable Style/NumericPredicate
|
62
|
+
product *= 1 + Rational(1, p)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
(n * product).to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.index_gamma1(n)
|
69
|
+
product = 1
|
70
|
+
(2..n).each do |p|
|
71
|
+
if NumericHelpers.prime_number?(p) && n % p == 0 # rubocop:disable Style/NumericPredicate
|
72
|
+
product *= 1 - Rational(1, p * p)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
(n * n * product).to_i
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/numeric_helpers/numeric_helpers'
|
4
|
+
|
5
|
+
module ModularForms
|
6
|
+
module Core
|
7
|
+
# ModularForms::Core::ThetaFunctions
|
8
|
+
# This module provides a generator for Theta functions.
|
9
|
+
module ThetaFunctions
|
10
|
+
def self.jacobi_theta_function(jacobi_index = 3, square_coefs = false) # rubocop:disable Style/OptionalBooleanParameter,Metrics/MethodLength,Metrics/AbcSize
|
11
|
+
Enumerator.new do |q|
|
12
|
+
q << 1
|
13
|
+
(1..Float::INFINITY).each do |tau|
|
14
|
+
square_num = tau**2
|
15
|
+
square_next_num = (tau + 1)**2
|
16
|
+
sign = jacobi_index == 3 ? 1**tau : (-1)**tau
|
17
|
+
q << (square_coefs == false ? sign * 2 : sign * square_num)
|
18
|
+
(square_next_num - square_num - 1).abs.times do
|
19
|
+
q << 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.jacobi_theta_function_pow(jacobi_index, power, precision)
|
26
|
+
vec = jacobi_theta_function(jacobi_index).take(precision)
|
27
|
+
theta_q_coefs = [1]
|
28
|
+
power.times do
|
29
|
+
theta_q_coefs = NumericHelpers.linear_convolve(theta_q_coefs, vec, precision)
|
30
|
+
end
|
31
|
+
theta_q_coefs
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'core/eisenstein_series'
|
4
|
+
require_relative 'core/elliptic_curves_q'
|
5
|
+
require_relative 'core/dedekind_eta_functions'
|
6
|
+
require_relative 'core/ramanujan_tau_function'
|
7
|
+
require_relative 'core/theta_functions'
|
8
|
+
require_relative 'core/klein_j_invariant'
|
9
|
+
require_relative 'core/hecke_operators'
|
10
|
+
require_relative 'core/sl2z_groups'
|
11
|
+
require_relative 'core/elliptic_curves_fp'
|
12
|
+
require_relative 'core/dirichlet_characters'
|
13
|
+
require_relative 'core/newform_invariants'
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './modular_forms/core'
|
4
|
+
|
5
|
+
# ModularForms
|
6
|
+
#
|
7
|
+
# This module provides a set of tools for working with modular forms and elliptic curves.
|
8
|
+
# It is designed for use with Sonic Pi, enabling creative exploration
|
9
|
+
# of these mathematical structures and their operations through sound.
|
10
|
+
|
11
|
+
# NOTE: While the module is designed for creative exploration, it is not optimized for high
|
12
|
+
# performance and should not be considered a replacement for specialized mathematical software.
|
13
|
+
module ModularForms # rubocop:disable Metrics/ModuleLength
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def eisenstein_series(weight_k, gal_f = nil)
|
17
|
+
Core::EisensteinSeries.eisenstein_series(weight_k, gal_f)
|
18
|
+
end
|
19
|
+
|
20
|
+
def eisenstein_series_product(weight_k1, weight_k2, prec)
|
21
|
+
Core::EisensteinSeries.eisenstein_series_product(weight_k1, weight_k2, prec)
|
22
|
+
end
|
23
|
+
|
24
|
+
def eisenstein_series_pow(weight_k, power, prec)
|
25
|
+
Core::EisensteinSeries.eisenstein_series_pow(weight_k, power, prec)
|
26
|
+
end
|
27
|
+
|
28
|
+
def dedekind_eta_function(m_scale = 1, pentagonal_coefs = false) # rubocop:disable Style/OptionalBooleanParameter
|
29
|
+
Core::DedekindEtaFunctions.eta_function(m_scale, pentagonal_coefs)
|
30
|
+
end
|
31
|
+
|
32
|
+
def dedekind_eta_pow(power, prec, m_scale = 1)
|
33
|
+
Core::DedekindEtaFunctions.eta_function_pow(power, prec, m_scale)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dedekind_sum(h, k)
|
37
|
+
Core::DedekindEtaFunctions.dedekind_sum(h, k)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ramanujan_tau_function
|
41
|
+
Core::RamanujanTauFunction.niebur_sigma_formula
|
42
|
+
end
|
43
|
+
|
44
|
+
def jacobi_theta_function(jacobi_index = 3, square_coefs = false) # rubocop:disable Style/OptionalBooleanParameter
|
45
|
+
Core::ThetaFunctions.jacobi_theta_function(jacobi_index, square_coefs)
|
46
|
+
end
|
47
|
+
|
48
|
+
def jacobi_theta_function_pow(jacobi_index, power, prec)
|
49
|
+
Core::ThetaFunctions.jacobi_theta_function_pow(jacobi_index, power, prec)
|
50
|
+
end
|
51
|
+
|
52
|
+
def j_function(prec)
|
53
|
+
Core::KleinJInvariant.j_function(prec)
|
54
|
+
end
|
55
|
+
|
56
|
+
def hecke_operator_prime_non_cusp(non_cusp_form_arr, prime, weight_k, prec)
|
57
|
+
Core::HeckeOperators.hecke_prime_non_cusp(non_cusp_form_arr, prime, weight_k, prec)
|
58
|
+
end
|
59
|
+
|
60
|
+
def hecke_operator_prime_cusp(cusp_form_arr, prime, weight_k, prec)
|
61
|
+
Core::HeckeOperators.hecke_prime_cusp(cusp_form_arr, prime, weight_k, prec)
|
62
|
+
end
|
63
|
+
|
64
|
+
def gamma0_index(n)
|
65
|
+
Core::SL2Zgroups.index_gamma0(n)
|
66
|
+
end
|
67
|
+
|
68
|
+
def gamma1_index(n)
|
69
|
+
Core::SL2Zgroups.index_gamma1(n)
|
70
|
+
end
|
71
|
+
|
72
|
+
def t_gen_matrix(n_power)
|
73
|
+
Core::SL2Zgroups.t_matrix(n_power)
|
74
|
+
end
|
75
|
+
|
76
|
+
def s_gen_matrix(n_power)
|
77
|
+
Core::SL2Zgroups.s_matrix(n_power)
|
78
|
+
end
|
79
|
+
|
80
|
+
def u_gen_matrix(mod_n)
|
81
|
+
Core::SL2Zgroups.u_matrix(mod_n)
|
82
|
+
end
|
83
|
+
|
84
|
+
def st_gen_matrix(n_power)
|
85
|
+
Core::SL2Zgroups.st_matrix(n_power)
|
86
|
+
end
|
87
|
+
|
88
|
+
def product_gen_mats(gen_mat_a, gen_mat_b)
|
89
|
+
Core::SL2Zgroups.product_gen_matrices(gen_mat_a, gen_mat_b)
|
90
|
+
end
|
91
|
+
|
92
|
+
def dirichlet_trivchar(modq, a)
|
93
|
+
Core::DirichletCharacters.dirichlet_trivchar(modq, a)
|
94
|
+
end
|
95
|
+
|
96
|
+
def conrey_p_pminus1(modp, a)
|
97
|
+
Core::DirichletCharacters.conrey_p_pminus1(modp, a)
|
98
|
+
end
|
99
|
+
|
100
|
+
def gauss_sum_triv(dirichlet_q, a)
|
101
|
+
Core::DirichletCharacters.gauss_sum_triv(dirichlet_q, a)
|
102
|
+
end
|
103
|
+
|
104
|
+
def gauss_sum_conrey_p_minus1(dirichlet_q, a, parity)
|
105
|
+
Core::DirichletCharacters.gauss_sum_conrey_p_minus1(dirichlet_q, a, parity)
|
106
|
+
end
|
107
|
+
|
108
|
+
def elliptic_curve_q(coefs)
|
109
|
+
Core::EllipticCurvesQ.elliptic_curve_q(coefs)
|
110
|
+
end
|
111
|
+
|
112
|
+
def discriminant_q(curve)
|
113
|
+
Core::EllipticCurvesQ.discriminant_qq(curve)
|
114
|
+
end
|
115
|
+
|
116
|
+
def j_invariant_q(curve)
|
117
|
+
Core::EllipticCurvesQ.j_invariant(curve)
|
118
|
+
end
|
119
|
+
|
120
|
+
def point_on_curve_q?(curve, point)
|
121
|
+
Core::EllipticCurvesQ.point_on_curve?(curve, point)
|
122
|
+
end
|
123
|
+
|
124
|
+
def point_addition_q(curve, p, q)
|
125
|
+
Core::EllipticCurvesQ.point_addition(curve, p, q)
|
126
|
+
end
|
127
|
+
|
128
|
+
def scalar_mul_point_q(curve, n, point)
|
129
|
+
Core::EllipticCurvesQ.scalar_mul_point(curve, n, point)
|
130
|
+
end
|
131
|
+
|
132
|
+
def isogeny_2deg_q(curve, point_2tor)
|
133
|
+
Core::EllipticCurvesQ.isogeny_2deg(curve, point_2tor)
|
134
|
+
end
|
135
|
+
|
136
|
+
def isogeny_ndeg_q(curve, point_ntor, order)
|
137
|
+
Core::EllipticCurvesQ.isogeny_ndeg(curve, point_ntor, order)
|
138
|
+
end
|
139
|
+
|
140
|
+
def weil_height(x_point)
|
141
|
+
Core::EllipticCurvesQ.weil_height(x_point)
|
142
|
+
end
|
143
|
+
|
144
|
+
def canonical_height(curve, point, prec = 64)
|
145
|
+
Core::EllipticCurvesQ.canonical_height(curve, point, prec)
|
146
|
+
end
|
147
|
+
|
148
|
+
def elliptic_curve_fp(p, coefs)
|
149
|
+
Core::EllipticCurvesFp.elliptic_curve_fp(p, coefs)
|
150
|
+
end
|
151
|
+
|
152
|
+
def point_on_curve_modp?(curve, point)
|
153
|
+
Core::EllipticCurvesFp.point_on_curve_modp?(curve, point)
|
154
|
+
end
|
155
|
+
|
156
|
+
def discriminant_modp(curve)
|
157
|
+
Core::EllipticCurvesFp.discriminant_modp(curve)
|
158
|
+
end
|
159
|
+
|
160
|
+
def j_invariant_modp(curve)
|
161
|
+
Core::EllipticCurvesFp.j_invariant_modp(curve)
|
162
|
+
end
|
163
|
+
|
164
|
+
def point_addition_modp(curve, p_point, q_point)
|
165
|
+
Core::EllipticCurvesFp.point_addition_modp(curve, p_point, q_point)
|
166
|
+
end
|
167
|
+
|
168
|
+
def scalar_mul_point_modp(curve, n, point)
|
169
|
+
Core::EllipticCurvesFp.scalar_mul_point_modp(curve, n, point)
|
170
|
+
end
|
171
|
+
|
172
|
+
def points_fp(curve, point_at_infinity = false) # rubocop:disable Style/OptionalBooleanParameter
|
173
|
+
Core::EllipticCurvesFp.points(curve, point_at_infinity)
|
174
|
+
end
|
175
|
+
|
176
|
+
def cardinality_fp(curve)
|
177
|
+
Core::EllipticCurvesFp.cardinality(curve)
|
178
|
+
end
|
179
|
+
|
180
|
+
def quadratic_twist_fp(curve)
|
181
|
+
Core::EllipticCurvesFp.quadratic_twist(curve)
|
182
|
+
end
|
183
|
+
|
184
|
+
def eta_product(eta1, eta2, prec = nil)
|
185
|
+
Core::DedekindEtaFunctions.eta_product(eta1, eta2, prec)
|
186
|
+
end
|
187
|
+
|
188
|
+
def eta_quotient(num_eta, den_eta, prec)
|
189
|
+
Core::DedekindEtaFunctions.eta_quotient(num_eta, den_eta, prec)
|
190
|
+
end
|
191
|
+
|
192
|
+
def analytic_conductor(level_n, weight_k)
|
193
|
+
Core::NewFormInvariants.analytic_conductor(level_n, weight_k)
|
194
|
+
end
|
195
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: modular_forms
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Edgar Armando Delgado Vega
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Modular Forms is an accessible interface for experimenting with modular
|
13
|
+
forms and elliptic curves through algorithmic composition and live coding using
|
14
|
+
Sonic Pi.
|
15
|
+
email:
|
16
|
+
- edelve91@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.md
|
21
|
+
files:
|
22
|
+
- README.md
|
23
|
+
- lib/modular_forms.rb
|
24
|
+
- lib/modular_forms/core.rb
|
25
|
+
- lib/modular_forms/core/dedekind_eta_functions.rb
|
26
|
+
- lib/modular_forms/core/dirichlet_characters.rb
|
27
|
+
- lib/modular_forms/core/eisenstein_series.rb
|
28
|
+
- lib/modular_forms/core/elliptic_curves_fp.rb
|
29
|
+
- lib/modular_forms/core/elliptic_curves_q.rb
|
30
|
+
- lib/modular_forms/core/hecke_operators.rb
|
31
|
+
- lib/modular_forms/core/klein_j_invariant.rb
|
32
|
+
- lib/modular_forms/core/newform_invariants.rb
|
33
|
+
- lib/modular_forms/core/numeric_helpers/numeric_helpers.rb
|
34
|
+
- lib/modular_forms/core/ramanujan_tau_function.rb
|
35
|
+
- lib/modular_forms/core/sl2z_groups.rb
|
36
|
+
- lib/modular_forms/core/theta_functions.rb
|
37
|
+
- lib/modular_forms/version.rb
|
38
|
+
homepage: https://github.com/edelveart/modular_forms
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata:
|
42
|
+
homepage_uri: https://github.com/edelveart/modular_forms
|
43
|
+
source_code_uri: https://github.com/edelveart/modular_forms/
|
44
|
+
rdoc_options:
|
45
|
+
- "--main"
|
46
|
+
- README.md
|
47
|
+
- "--line-numbers"
|
48
|
+
- "--inline-source"
|
49
|
+
- "--quiet"
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 3.0.0
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubygems_version: 3.6.9
|
64
|
+
specification_version: 4
|
65
|
+
summary: A creative toolkit for exploring modular forms and elliptic curves through
|
66
|
+
Sonic Pi.
|
67
|
+
test_files: []
|