Skip to content

Commit 724c550

Browse files
committed
1 parent 261d6b4 commit 724c550

23 files changed

+3711
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* Copyright 2022 Sander Verdonschot <sander.verdonschot at gmail.com>.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.github.mangara.diophantine;
15+
16+
import io.github.mangara.diophantine.iterators.IntegerIterator;
17+
import io.github.mangara.diophantine.iterators.MappingIterator;
18+
import io.github.mangara.diophantine.iterators.XYIterator;
19+
import java.math.BigInteger;
20+
import java.util.Collections;
21+
import java.util.Iterator;
22+
import java.util.function.Function;
23+
24+
/**
25+
* Solves linear Diophantine equations in two variables.
26+
* <p>
27+
* The method is based on https://www.alpertron.com.ar/METHODS.HTM#Linear
28+
*/
29+
public class LinearSolver {
30+
31+
/**
32+
* Solves the linear Diophantine equation d x + e y + f = 0.
33+
*
34+
* @param d
35+
* @param e
36+
* @param f
37+
* @return an iterator over all integer solutions (x, y)
38+
*/
39+
public static Iterator<XYPair> solve(long d, long e, long f) {
40+
return solve(BigInteger.valueOf(d), BigInteger.valueOf(e), BigInteger.valueOf(f));
41+
}
42+
43+
/**
44+
* Solves the linear Diophantine equation d x + e y + f = 0.
45+
*
46+
* @param d
47+
* @param e
48+
* @param f
49+
* @return an iterator over all integer solutions (x, y)
50+
*/
51+
public static Iterator<XYPair> solve(BigInteger d, BigInteger e, BigInteger f) {
52+
if (d.signum() == 0 && e.signum() == 0) {
53+
return solveTrivial(f);
54+
} else if (d.signum() == 0) {
55+
return solveSingle(e, f, true);
56+
} else if (e.signum() == 0) {
57+
return solveSingle(d, f, false);
58+
} else {
59+
return solveGeneral(d, e, f);
60+
}
61+
}
62+
63+
private static Iterator<XYPair> solveTrivial(BigInteger f) {
64+
if (f.signum() != 0) {
65+
return Collections.emptyIterator();
66+
}
67+
68+
return new XYIterator();
69+
}
70+
71+
/**
72+
* Solves the linear Diophantine equations g x + f = 0 or g y + f = 0.
73+
* <p>
74+
* If arbitraryX is true, the equation solved is g y + f = 0 and x ranges over all integers.
75+
* Otherwise, it is g x + f = 0, with y ranging over all integers.
76+
*
77+
* @param g
78+
* @param f
79+
* @param arbitraryX
80+
* @return an iterator over all integer solutions (x, y)
81+
*/
82+
private static Iterator<XYPair> solveSingle(BigInteger g, BigInteger f, boolean arbitraryX) {
83+
if (f.mod(g.abs()).signum() != 0) {
84+
return Collections.emptyIterator();
85+
}
86+
87+
BigInteger val = f.negate().divide(g);
88+
89+
Function<BigInteger, XYPair> map;
90+
if (arbitraryX) {
91+
map = k -> new XYPair(k, val);
92+
} else {
93+
map = k -> new XYPair(val, k);
94+
}
95+
96+
return new MappingIterator<>(new IntegerIterator(), map);
97+
}
98+
99+
// Pre: d != 0 && e != 0
100+
private static Iterator<XYPair> solveGeneral(BigInteger d, BigInteger e, BigInteger f) {
101+
Eq reduced = reduce(d, e, f);
102+
103+
if (reduced == null) {
104+
return Collections.emptyIterator();
105+
} else {
106+
return solveReduced(reduced);
107+
}
108+
}
109+
110+
private static Eq reduce(BigInteger d, BigInteger e, BigInteger f) {
111+
BigInteger gcd = d.gcd(e).abs();
112+
113+
if (f.mod(gcd).signum() != 0) {
114+
// No solutions, as d x + e y will always be a multiple of gcd for integer x and y
115+
return null;
116+
}
117+
118+
return new Eq(d.divide(gcd), e.divide(gcd), f.divide(gcd));
119+
}
120+
121+
// Pre: d != 0 && e != 0 && d and e are co-prime
122+
private static Iterator<XYPair> solveReduced(Eq eq) {
123+
XYPair solution = findAnySolution(eq);
124+
125+
final BigInteger dx = eq.e;
126+
final BigInteger dy = eq.d.negate();
127+
128+
return new MappingIterator<>(new IntegerIterator(),
129+
(k) -> new XYPair(solution.x.add(dx.multiply(k)), solution.y.add(dy.multiply(k))));
130+
}
131+
132+
private static XYPair findAnySolution(Eq eq) {
133+
// Run the extended Euclidean algorithm (
134+
// https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm )
135+
BigInteger prevR = eq.d;
136+
BigInteger r = eq.e;
137+
BigInteger prevS = BigInteger.ONE;
138+
BigInteger s = BigInteger.ZERO;
139+
BigInteger prevT = BigInteger.ZERO;
140+
BigInteger t = BigInteger.ONE;
141+
142+
while (r.signum() != 0) {
143+
BigInteger quotient = prevR.divide(r);
144+
BigInteger tempR = r;
145+
BigInteger tempS = s;
146+
BigInteger tempT = t;
147+
148+
r = prevR.subtract(quotient.multiply(r));
149+
s = prevS.subtract(quotient.multiply(s));
150+
t = prevT.subtract(quotient.multiply(t));
151+
152+
prevR = tempR;
153+
prevS = tempS;
154+
prevT = tempT;
155+
}
156+
157+
// Results are in prevR (which is the gcd = +/- 1), prevS, and prevT
158+
// Thus, d * prevS + e * prevT = +/- 1
159+
// We want d * x + e * y = -f, so we need to multiply by f or -f, depending on the sign of prevR
160+
BigInteger factor = eq.f.negate().multiply(prevR);
161+
BigInteger x = factor.multiply(prevS); // -/+ f * prevS
162+
BigInteger y = factor.multiply(prevT); // -/+ f * prevT
163+
164+
return new XYPair(x, y);
165+
}
166+
167+
private static class Eq {
168+
169+
BigInteger d, e, f;
170+
171+
Eq(BigInteger d, BigInteger e, BigInteger f) {
172+
this.d = d;
173+
this.e = e;
174+
this.f = f;
175+
}
176+
}
177+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2022 Sander Verdonschot <sander.verdonschot at gmail.com>.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.github.mangara.diophantine;
15+
16+
import java.math.BigInteger;
17+
import java.util.Collections;
18+
import java.util.Iterator;
19+
import io.github.mangara.diophantine.quadratic.EllipticalSolver;
20+
import io.github.mangara.diophantine.quadratic.HyperbolicSolver;
21+
import io.github.mangara.diophantine.quadratic.ParabolicSolver;
22+
import io.github.mangara.diophantine.quadratic.SquareDiscriminantSolver;
23+
24+
/**
25+
* Solves quadratic Diophantine equations in two variables.
26+
* <p>
27+
* The method is based on K. R. Matthews, "Solving the Diophantine equation \(ax^2 + bxy + cy^2 + dx
28+
* + ey + f = 0\)" http://www.numbertheory.org/PDFS/general_quadratic_solution.pdf
29+
*/
30+
public class QuadraticSolver {
31+
32+
/**
33+
* Solves the quadratic Diophantine equation a x^2 + b xy + c y^2 + d x + e y + f = 0.
34+
*
35+
* @param a
36+
* @param b
37+
* @param c
38+
* @param d
39+
* @param e
40+
* @param f
41+
* @return an iterator over all integer solutions (x, y)
42+
*/
43+
public static Iterator<XYPair> solve(long a, long b, long c, long d, long e, long f) {
44+
return solve(BigInteger.valueOf(a), BigInteger.valueOf(b), BigInteger.valueOf(c),
45+
BigInteger.valueOf(d), BigInteger.valueOf(e), BigInteger.valueOf(f));
46+
}
47+
48+
/**
49+
* Solves the quadratic Diophantine equation a x^2 + b xy + c y^2 + d x + e y + f = 0.
50+
*
51+
* @param a
52+
* @param b
53+
* @param c
54+
* @param d
55+
* @param e
56+
* @param f
57+
* @return an iterator over all integer solutions (x, y)
58+
*/
59+
public static Iterator<XYPair> solve(BigInteger a, BigInteger b, BigInteger c, BigInteger d,
60+
BigInteger e, BigInteger f) {
61+
if (a.signum() == 0 && b.signum() == 0 && c.signum() == 0) {
62+
return LinearSolver.solve(d, e, f);
63+
}
64+
65+
BigInteger D = Utils.discriminant(a, b, c);
66+
67+
if (D.signum() == 0) {
68+
return ParabolicSolver.solve(a, b, c, d, e, f);
69+
} else if (Utils.isSquare(D)) {
70+
return SquareDiscriminantSolver.solve(a, b, c, d, e, f);
71+
} else if (Utils.legendreConstant(a, b, c, d, e, f, D).signum() == 0) {
72+
return solveTrivialCase(a, b, c, d, e, f, D);
73+
} else if (D.signum() < 0) {
74+
return EllipticalSolver.solve(a, b, c, d, e, f);
75+
} else {
76+
return HyperbolicSolver.solve(a, b, c, d, e, f);
77+
}
78+
}
79+
80+
// Pre: D = b^2 - 4ac != 0 && D not a perfect square && Legendre's k = 0
81+
private static Iterator<XYPair> solveTrivialCase(BigInteger a, BigInteger b, BigInteger c,
82+
BigInteger d, BigInteger e, BigInteger f, BigInteger D) {
83+
BigInteger alpha = Utils.legendreAlpha(b, c, d, e);
84+
BigInteger beta = Utils.legendreBeta(a, b, d, e);
85+
86+
if (alpha.mod(D).signum() == 0 && beta.mod(D).signum() == 0) {
87+
return Collections.singletonList(new XYPair(alpha.divide(D), beta.divide(D))).iterator();
88+
} else {
89+
return Collections.emptyIterator();
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)