diff --git a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/basic/ToggleFeature.java b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/basic/ToggleFeature.java index def8e3fa24..38e2259b89 100644 --- a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/basic/ToggleFeature.java +++ b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/basic/ToggleFeature.java @@ -58,5 +58,11 @@ public class ToggleFeature { */ public static boolean SERIES_DENOMINATOR = false; + /** + * If true, enable solvers from package io.github.mangara.diophantine to + * find some solutions in {@link S#FindInstance} + */ + public static boolean SOLVE_DIOPHANTINE = true; + public static boolean SHOW_STEPS = true; } diff --git a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/NumberTheory.java b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/NumberTheory.java index 45896f6c55..a6a55c01b5 100644 --- a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/NumberTheory.java +++ b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/NumberTheory.java @@ -16,6 +16,7 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedMap; @@ -79,6 +80,7 @@ import org.matheclipse.core.interfaces.ISymbol; import org.matheclipse.core.numbertheory.GaussianInteger; import org.matheclipse.core.numbertheory.Primality; +import org.matheclipse.core.polynomials.QuarticSolver; import org.matheclipse.core.sympy.series.Sequences; import org.matheclipse.core.visit.VisitorExpr; import com.google.common.math.BigIntegerMath; @@ -88,8 +90,14 @@ import edu.jas.arith.ModInteger; import edu.jas.arith.ModIntegerRing; import edu.jas.poly.GenPolynomial; +import edu.jas.poly.Monomial; import edu.jas.ufd.FactorAbstract; import edu.jas.ufd.FactorFactory; +import io.github.mangara.diophantine.QuadraticSolver; +import io.github.mangara.diophantine.Utils; +import io.github.mangara.diophantine.XYPair; +import io.github.mangara.diophantine.quadratic.ParabolicSolver; +import io.github.mangara.diophantine.quadratic.PellsSolver; public final class NumberTheory { @@ -6616,4 +6624,123 @@ private static IAST quadraticIrrationalPlus(IAST plusAST, IASTMutable resultList } return F.NIL; } + + public static IAST diophantinePolynomial(final IExpr expr, IAST varList, + int maximumNumberOfResults) { + VariablesSet varSet = new VariablesSet(varList); + IASTMutable result = F.NIL; + try { + // try to generate a common expression polynomial + JASConvert jas = new JASConvert( + varSet.getArrayList(), edu.jas.arith.BigInteger.ZERO); + GenPolynomial ePoly = jas.expr2JAS(expr, false); + result = diophantinePolynomial(ePoly, varList, maximumNumberOfResults); + result = QuarticSolver.sortASTArguments(result); + return result; + } catch (JASConversionException e2) { + e2.printStackTrace(); + } + return result; + } + + private static IASTAppendable diophantinePolynomial( + GenPolynomial polynomial, IAST varList, + int maximumNumberOfResults) { + long varDegree = polynomial.degree(0); + + if (polynomial.isConstant()) { + return F.ListAlloc(1); + } + // a*x^2 + b*x*y + c*y^2 + d*x + e*y + f = 0 + BigInteger a = BigInteger.ZERO; + BigInteger b = BigInteger.ZERO; + BigInteger c = BigInteger.ZERO; + BigInteger d = BigInteger.ZERO; + BigInteger e = BigInteger.ZERO; + BigInteger f = BigInteger.ZERO; + try { + if (varDegree <= 2) { + if (varList.argSize() == 1) { + // x is only variable => b=0;c=0;e=0; + for (Monomial monomial : polynomial) { + edu.jas.arith.BigInteger coeff = monomial.coefficient(); + BigInteger zz = coeff.val; + long xExp = monomial.exponent().getVal(0); + if (xExp == 2) { + a = zz; + } else if (xExp == 1) { + d = zz; + } else if (xExp == 2) { + f = zz; + } else { + throw new ArithmeticException( + "diophantinePolynomial::Unexpected exponent value: " + xExp); + } + } + } else if (varList.argSize() == 2) { + // x and y are both variables + // a*x^2 + b*x*y + c*y^2 + d*x + e*y + f = 0 + for (Monomial monomial : polynomial) { + edu.jas.arith.BigInteger coeff = monomial.coefficient(); + BigInteger zz = coeff.val; + int xi = monomial.exponent().varIndex(0); + int yi = monomial.exponent().varIndex(1); + long xExp = monomial.exponent().getVal(xi); + long yExp = monomial.exponent().getVal(yi); + if (xExp == 2 && yExp == 0) { + a = zz; + } else if (xExp == 1 && yExp == 1) { + b = zz; + } else if (xExp == 0 && yExp == 2) { + c = zz; + } else if (xExp == 1 && yExp == 0) { + d = zz; + } else if (xExp == 0 && yExp == 1) { + e = zz; + } else if (xExp == 0 && yExp == 0) { + f = zz; + } else { + throw new ArithmeticException( + "diophantinePolynomial::Unexpected exponent value: " + xExp); + } + } + } + Iterator diophantineSolver = null; + IASTAppendable result = F.ListAlloc(); + if (maximumNumberOfResults == 1 && c.signum() < 0 && f.equals(BigInteger.valueOf(-4)) + && b.signum() == 0 && d.signum() == 0 && e.signum() == 0) { + BigInteger cNegate = c.negate(); + if (!Utils.isSquare(cNegate)) { + // use Pell's equation if -c is not a perfect square + XYPair xyPair = PellsSolver.leastPositivePellsFourSolution(cNegate); + result.append(F.List(F.Rule(varList.arg1(), F.ZZ(xyPair.x)), + F.Rule(varList.arg2(), F.ZZ(xyPair.y)))); + return result; + } + } + if (a.signum() != 0 || b.signum() != 0 || c.signum() != 0) { + // D := b^2 - 4ac && D == 0 + BigInteger D = b.multiply(b).subtract(a.multiply(c).multiply(BigInteger.valueOf(4))); + if (D.signum() == 0) { + diophantineSolver = ParabolicSolver.solve(a, b, c, d, e, f); + } + } + if (diophantineSolver == null) { + diophantineSolver = QuadraticSolver.solve(a, b, c, d, e, f); + } + + int n = 0; + while (diophantineSolver.hasNext() && n++ < maximumNumberOfResults) { + XYPair xyPair = diophantineSolver.next(); + result.append(F.List(F.Rule(varList.arg1(), F.ZZ(xyPair.x)), + F.Rule(varList.arg2(), F.ZZ(xyPair.y)))); + } + return result; + } + } catch (ArithmeticException aex) { + // + } + + return F.NIL; + } } diff --git a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/convert/JASConvert.java b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/convert/JASConvert.java index 78d46b7ac7..8cdd503ced 100644 --- a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/convert/JASConvert.java +++ b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/convert/JASConvert.java @@ -356,7 +356,14 @@ private GenPolynomial expr2Poly(final IExpr exprPoly, boolean numeric2Rationa // "JASConvert:expr2Poly - invalid exponent: " + ast.arg2().toString()); } try { - return fPolyFactory.univariate(base.getSymbolName(), exponent); + GenPolynomial v = fPolyFactory.univariate(base.getSymbolName(), 1L); + return v.power(exponent); + // int indexOf = fVariables.indexOf(base); + // if (indexOf >= 0) { + // ExpVectorLong v = new ExpVectorLong(fVariables.size(), indexOf, exponent); + // return fPolyFactory.valueOf(fRingFactory.getONE(), v); + // } + // return fPolyFactory.univariate(base.getSymbolName(), exponent); } catch (IllegalArgumentException iae) { // fall through } diff --git a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/reflection/system/Solve.java b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/reflection/system/Solve.java index 71f15da7a7..da0a8c9b67 100644 --- a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/reflection/system/Solve.java +++ b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/reflection/system/Solve.java @@ -9,9 +9,11 @@ import org.apache.logging.log4j.Logger; import org.hipparchus.linear.FieldMatrix; import org.matheclipse.core.basic.Config; +import org.matheclipse.core.basic.ToggleFeature; import org.matheclipse.core.builtin.Algebra; import org.matheclipse.core.builtin.BooleanFunctions; import org.matheclipse.core.builtin.LinearAlgebra; +import org.matheclipse.core.builtin.NumberTheory; import org.matheclipse.core.builtin.PolynomialFunctions; import org.matheclipse.core.builtin.RootsFunctions; import org.matheclipse.core.convert.ChocoConvert; @@ -1289,6 +1291,19 @@ public static IExpr solveIntegers(final IAST ast, IAST equationVariables, return F.NIL; } try { + if (ToggleFeature.SOLVE_DIOPHANTINE) { + if (equationsAndInequations.argSize() == 1) { + IExpr eq1 = equationsAndInequations.arg1(); + if (eq1.isEqual() && eq1.second().isZero()) { + IAST diophantineResult = NumberTheory.diophantinePolynomial(eq1.first(), + equationVariables, maximumNumberOfResults); + if (diophantineResult.isPresent()) { + return diophantineResult; + } + } + } + } + if (equationsAndInequations.isFreeAST(x -> chocoSolver(x))) { // choco-solver doesn't handle Power() expressions very well at the moment! try { diff --git a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/FindInstanceTest.java b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/FindInstanceTest.java index f6ec16a896..95f713eba8 100644 --- a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/FindInstanceTest.java +++ b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/FindInstanceTest.java @@ -1,16 +1,43 @@ package org.matheclipse.core.system; import org.junit.Test; +import org.matheclipse.core.basic.ToggleFeature; /** Tests for FindInstance function */ public class FindInstanceTest extends ExprEvaluatorTestCase { @Test - public void testDiophantine() { + public void testDiophantine001() { + if (ToggleFeature.SOLVE_DIOPHANTINE) { + // ParabolicSolver + check("FindInstance(9*x^2 - 12*x*y + 4*y^2 + 3*x + 2*y == 12,{x,y},Integers, 3)", // + "{{x->0,y->-2},{x->1,y->0},{x->2,y->3}}"); + // QuadraticSolver + check("FindInstance(3*x^2 - 8*x*y + 7*y^2 - 4*x + 2*y - 109 == 0,{x,y},Integers, 3)", // + "{{x->2,y->-3},{x->2,y->5},{x->14,y->9}}"); + // Pell 4 + check("FindInstance(x^2-29986*y^2-4 ==0,{x,y},Integers,1)", // + "{{x->135915148103491619905402044543098,y->784889635731418443294120995460}}"); + + check("FindInstance(x^2-29986*y^2-4 ==0,{x,y},Integers,3)", // + "{{x->-135915148103491619905402044543098,y->784889635731418443294120995460},{x->-\n" // + + "2,y->0},{x->135915148103491619905402044543098,y->-784889635731418443294120995460}}"); + } + } + + @Test + public void testDiophantine002() { // TODO return condition with extra variable C1 - check("FindInstance(13*x+51*y==0, {x,y}, Integers, 6)", // - "{{x->-153,y->39},{x->-102,y->26},{x->-51,y->13},{x->0,y->0},{x->51,y->-13},{x->\n" - + "102,y->-26}}"); + if (ToggleFeature.SOLVE_DIOPHANTINE) { + check("FindInstance(13*x+51*y==0, {x,y}, Integers, 6)", // + "{{x->-102,y->26},{x->-51,y->13},{x->0,y->0},{x->51,y->-13},{x->102,y->-26},{x->\n" // + + "153,y->-39}}"); + } else { + // choco-solver solutions: + check("FindInstance(13*x+51*y==0, {x,y}, Integers, 6)", // + "{{x->-153,y->39},{x->-102,y->26},{x->-51,y->13},{x->0,y->0},{x->51,y->-13},{x->\n" // + + "102,y->-26}}"); + } } @Test