diff --git a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/Algebra.java b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/Algebra.java index 0328e91d85..5d8a7a5607 100644 --- a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/Algebra.java +++ b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/Algebra.java @@ -4795,8 +4795,10 @@ private static IAST cancelCommonFactors(IExpr part0, IExpr part1) { } } if (p0.isTimes() || p1.isTimes()) { - final IAST p0Times = p0.isTimes() ? (IAST) p0 : F.Times(p0); - final IAST p1Times = p1.isTimes() ? (IAST) p1 : F.Times(p1); + IAST p0Times = AbstractFunctionEvaluator.getNegativePlusInTimes(p0); + IAST p1Times = AbstractFunctionEvaluator.getNegativePlusInTimes(p1); + // IAST p0Times = p0.isTimes() ? (IAST) p0 : F.Times(p0); + // IAST p1Times = p1.isTimes() ? (IAST) p1 : F.Times(p0); IASTAppendable t0 = p0Times.copyAppendable(); IASTAppendable t1 = p1Times.copyAppendable(); int i = 1; @@ -4840,8 +4842,7 @@ private static IAST cancelCommonFactors(IExpr part0, IExpr part1) { t1Base = t1Arg.base(); t1Exponent = t1Arg.exponent(); } - - if (t0Base.equals(t1Base) && t0Exponent.isReal() && t1Exponent.isReal()) { + if (t0Exponent.isReal() && t1Exponent.isReal() && t0Base.equals(t1Base)) { IReal exp0 = (IReal) t0Exponent; IReal exp1 = (IReal) t1Exponent; final IReal subtracted; @@ -4862,6 +4863,7 @@ private static IAST cancelCommonFactors(IExpr part0, IExpr part1) { subtracted = exp1.subtractFrom(exp0); t0.remove(i); t1.set(j, F.Power(t1Base, subtracted)); + j++; if (Config.TRACE_BASIC_ARITHMETIC) { if (EvalEngine.get().isTraceMode()) { if (commonFactors.isNIL()) { @@ -4900,6 +4902,7 @@ private static IAST cancelCommonFactors(IExpr part0, IExpr part1) { } } return F.NIL; + } /** @@ -5315,17 +5318,23 @@ private static IRational factorTermsGCD(IAST plusAST, EvalEngine engine) { */ public static IExpr[] getNumeratorDenominator(IAST ast, EvalEngine engine, boolean together) { if (together) { - IExpr[] result = new IExpr[3]; - result[2] = together(ast, engine); - // split expr into numerator and denominator - result[1] = engine.evaluate(F.Denominator(result[2])); - if (!result[1].isOne()) { - // search roots for the numerator expression - result[0] = engine.evaluate(F.Numerator(result[2])); - } else { - result[0] = ast; // result[2]; + boolean noSimplifyMode = engine.isNoSimplifyMode(); + try { + engine.setNoSimplifyMode(true); + IExpr[] result = new IExpr[3]; + result[2] = together(ast, engine); + // split expr into numerator and denominator + result[1] = engine.evaluate(F.Denominator(result[2])); + if (!result[1].isOne()) { + // search roots for the numerator expression + result[0] = engine.evaluate(F.Numerator(result[2])); + } else { + result[0] = ast; // result[2]; + } + return result; + } finally { + engine.setNoSimplifyMode(noSimplifyMode); } - return result; } IExpr[] result = new IExpr[2]; diff --git a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/EvalEngine.java b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/EvalEngine.java index 846f0d6f77..2349fefc58 100644 --- a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/EvalEngine.java +++ b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/EvalEngine.java @@ -227,11 +227,16 @@ public static void setReset(final EvalEngine engine) { transient boolean fNumericMode; /** - * if true the engine evaluates in "F.Together(expr)" in IExpr#times() - * method. + * if true the engine evaluates "F.Together(expr)" in IExpr#times() method. */ transient boolean fTogetherMode; + /** + * If true the engine does no simplification of "negative {@link S#Plus} + * expressions" inside {@link S#Times} expressions in common subexpression determining. + */ + transient boolean fNoSimplifyMode; + transient boolean fEvalLHSMode; transient boolean fEvalRHSMode; @@ -601,6 +606,7 @@ public synchronized EvalEngine copy() { engine.fSessionID = fSessionID; engine.fStopRequested = false; engine.fTogetherMode = fTogetherMode; + engine.fNoSimplifyMode = fNoSimplifyMode; engine.fTraceMode = fTraceMode; engine.fTraceStack = fTraceStack; engine.f$Input = f$Input; @@ -2982,6 +2988,7 @@ public final void init() { fRecursionCounter = 0; fNumericMode = false; fTogetherMode = false; + fNoSimplifyMode = false; fEvalLHSMode = false; fEvalRHSMode = false; fDisabledTrigRules = false; @@ -3146,6 +3153,15 @@ public final boolean isTogetherMode() { return fTogetherMode; } + /** + * If true the engine does no simplification of "negative {@link S#Plus} + * expressions" inside {@link S#Times} expressions in common subexpression determining. + * + */ + public final boolean isNoSimplifyMode() { + return fNoSimplifyMode; + } + /** * If the trace mode is set the system writes an evaluation trace list or if additionally the * stop after evaluation mode is set returns the first evaluated result. @@ -3213,6 +3229,7 @@ private void reset() { fDisabledTrigRules = false; fRecursionCounter = 0; fTogetherMode = false; + fNoSimplifyMode = false; fTraceMode = false; fTraceStack = null; fStopRequested = false; @@ -3495,12 +3512,22 @@ public void setStopRequested(final boolean stopRequested) { * {@link S#Together} command during the evaluation of the multiplication if the parameter is set * to true. * - * @param fTogetherMode if true the evaluation will be wrapped by a - * {@link S#Together} function in basic multiplication + * @param togetherMode if true the evaluation will be wrapped by a {@link S#Together} + * function in basic multiplication * @see #isTogetherMode() */ - public void setTogetherMode(boolean fTogetherMode) { - this.fTogetherMode = fTogetherMode; + public void setTogetherMode(boolean togetherMode) { + this.fTogetherMode = togetherMode; + } + + /** + * If set to true the engine does no simplification of "negative {@link S#Plus} + * expressions" inside {@link S#Times} expressions in common subexpression determining. + * + * @param noSimplifyMode + */ + public void setNoSimplifyMode(boolean noSimplifyMode) { + this.fNoSimplifyMode = noSimplifyMode; } /** @param b */ diff --git a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/interfaces/AbstractFunctionEvaluator.java b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/interfaces/AbstractFunctionEvaluator.java index efe62dbf70..ffa9f828a0 100644 --- a/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/interfaces/AbstractFunctionEvaluator.java +++ b/symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/eval/interfaces/AbstractFunctionEvaluator.java @@ -31,6 +31,127 @@ public abstract class AbstractFunctionEvaluator extends AbstractEvaluator { private static final Logger LOGGER = LogManager.getLogger(); + /** + * Determine the options only from the last arguments of ast. Possibly additional + * options are null if no matching option is found in the arguments of the ast + * . + * + * @param options + * @param ast + * @param argSize + * @param expectedArgSize + * @param optionSymbol + * @return + */ + private static int determineArgumentOptions(IExpr[] options, IAST ast, int argSize, + int[] expectedArgSize, IBuiltInSymbol[] optionSymbol) { + int minNumberOfArgs = 1; + if (expectedArgSize != null) { + // the ast function must at least contain the minimum number of arguments + minNumberOfArgs = expectedArgSize[0]; + if (minNumberOfArgs < 1) { + minNumberOfArgs = 1; + } + } + + int counter = 0; + boolean evaled = true; + while (argSize > minNumberOfArgs && evaled && counter < optionSymbol.length) { + IExpr arg = ast.get(argSize); + + // check that arg has the correct options format: + if (arg.isRule()) { + evaled = false; + for (int i = 0; i < optionSymbol.length; i++) { + if (optionSymbol[i].equals(arg.first())) { + options[i] = arg.second(); + argSize--; + counter++; + evaled = true; + break; + } + } + } else if (arg.isListOfRules(true) && !arg.isEmptyList()) { + IAST listOfRules = (IAST) arg; + for (int j = 1; j < listOfRules.size(); j++) { + IAST rule = (IAST) listOfRules.get(j); + evaled = false; + for (int i = 0; i < optionSymbol.length; i++) { + if (optionSymbol[i].equals(rule.first())) { + options[i] = rule.second(); + argSize--; + counter++; + evaled = true; + break; + } + } + } + } else { + evaled = false; + break; + } + } + return argSize; + } + + /** + * Replace the options which are null only from the default options of the head + * symbol. + * + * @param options + * @param ast + * @param optionSymbol + * @param engine + */ + private static void determineDefaultOptions(IExpr[] options, IAST ast, + IBuiltInSymbol[] optionSymbol, EvalEngine engine) { + int optionNullStart = -1; + for (int i = 0; i < options.length; i++) { + if (options[i] == null) { + optionNullStart = i; + break; + } + } + if (optionNullStart >= 0) { + final IExpr temp = OptionsPattern.optionsList(ast.topHead(), false); + // final IExpr temp = engine.evaluate(F.Options(ast.topHead())); + if (temp.isList() && temp.size() > 1) { + IAST list = (IAST) temp; + for (int i = optionNullStart; i < options.length; i++) { + if (options[i] == null) { + options[i] = S.None; + for (int j = 1; j < list.size(); j++) { + IAST rule = (IAST) list.get(j); + if (optionSymbol[i].equals(rule.first())) { + options[i] = rule.second(); + break; + } + } + } + } + } + } + } + + /** + * Determine the options from the last arguments of ast or from the default options. + * + * @param options the result array of options and their (possibly default) values. + * @param ast + * @param argSize + * @param expectedArgSize + * @param optionSymbol + * @param engine + * @return + */ + public static int determineOptions(IExpr[] options, IAST ast, int argSize, int[] expectedArgSize, + IBuiltInSymbol[] optionSymbol, EvalEngine engine) { + argSize = determineArgumentOptions(options, ast, argSize, expectedArgSize, optionSymbol); + + determineDefaultOptions(options, ast, optionSymbol, engine); + return argSize; + } + /** * Check if the expression has a complex number factor I (imaginary unit). * @@ -79,6 +200,33 @@ public static IExpr extractImaginaryUnit(final IExpr expression, boolean checkTi return F.NIL; } + public static IExpr getComplexExpr(final IExpr expr, IExpr factor) { + if (expr.isComplex() && (expr.re().isZero() || expr.re().isNegative())) { + return F.Times(factor, expr); + } else { + if (expr.isTimes() && expr.first().isComplex()) { + IComplex arg1 = (IComplex) expr.first(); + if (arg1.re().isZero() || arg1.re().isNegative()) { + return F.Times(factor, expr); + } + } else if (expr.isPlus()) { + IExpr arg1 = expr.first(); + if (arg1.isComplex() && (arg1.re().isZero() || arg1.re().isNegative())) { + // distribute the factor over the Plus() args + return F.Distribute(F.Times(factor, expr)); + } + if (arg1.isTimes() && arg1.first().isComplex()) { + arg1 = arg1.first(); + if (arg1.re().isZero() || arg1.re().isNegative()) { + // distribute the factor over the Plus() args + return F.Distribute(F.Times(factor, expr)); + } + } + } + } + return F.NIL; + } + /** * Check if the expression is canonical negative. * @@ -127,56 +275,7 @@ public static IExpr getNormalizedNegativeExpression(final IExpr expression, return timesAST.setAtCopy(index, F.CInfinity); } } else if (checkTimesPlus && expression.isPlus()) { - final IAST plusAST = ((IAST) expression); - IExpr arg1 = plusAST.arg1(); - if (arg1.isNumber()) { - if (((INumber) arg1).complexSign() < 0) { - result = plusAST.copy(); - result.set(1, arg1.negate()); - for (int i = 2; i < plusAST.size(); i++) { - result.set(i, plusAST.get(i).negate()); - } - return result; - } - } else if (arg1.isTimes()) { - IExpr arg1Negated = getNormalizedNegativeExpression(arg1, checkTimesPlus); - if (arg1Negated.isPresent()) { - // int positiveElementsCounter = 0; - result = plusAST.copy(); - result.set(1, arg1Negated); - for (int i = 2; i < plusAST.size(); i++) { - IExpr temp = plusAST.get(i); - // if (!temp.isTimes() && !temp.isPower()) { - // return F.NIL; - // } - - // arg1Negated = getNormalizedNegativeExpression(temp, checkTimesPlus); - // if (arg1Negated.isPresent()) { - // result.set(i, arg1Negated); - // } else { - - // positiveElementsCounter++; - // if (positiveElementsCounter * 2 > plusAST.argSize()) { - // number of positive elements is greater - // than number of negative elements - // return F.NIL; - // } - result.set(i, temp.negate()); - - // } - } - return result; - } else if (arg1.isNegativeInfinity()) { - result = plusAST.copy(); - result.set(1, F.CInfinity); - for (int i = 2; i < plusAST.size(); i++) { - result.set(i, plusAST.get(i).negate()); - } - return result; - } - } - // } else if (expression.isNegativeInfinity()) { - // return F.CInfinity; + return getNormalizedNegativePlus((IAST) expression, checkTimesPlus); } else if (expression.isDirectedInfinity() && expression.isAST1()) { IExpr arg1 = expression.first(); if (arg1.isMinusOne()) { @@ -194,82 +293,109 @@ public static IExpr getNormalizedNegativeExpression(final IExpr expression, return F.NIL; } - /** - * Return true if the number of negative terms of the ast expression - * countWeight is greater than the half of the number of the arguments of ast - * . I.e. int halfSize = ast.size() / 2; return countWeight > halfSize; - * - * @param ast - * @param checkTimesPlus check Times(...) and Plus(...) expressions - * @return - */ - public static boolean isNegativeWeighted(final IAST ast, boolean checkTimesPlus) { - return isNegativeWeighted(ast, checkTimesPlus, ast.size() / 2); - } - - /** - * Return the number of negative terms of the ast expression. - * - * @param ast - * @param checkTimesPlus check Times(...) and Plus(...) expressions - * @param maxNegativeExpr maximum number of negative valued terms which have to be found before - * returning true - * @return - */ - public static boolean isNegativeWeighted(final IAST ast, boolean checkTimesPlus, - int maxNegativeExpr) { - int count = maxNegativeExpr - 1; - for (int i = 1; i < ast.size(); i++) { - if (isNegativeValued(ast.get(i), checkTimesPlus)) { - if (--count < 0) { - return true; + private static IExpr getNormalizedNegativePlus(final IAST plusAST, boolean checkTimesPlus) { + IASTMutable result; + IExpr arg1 = plusAST.arg1(); + if (arg1.isNumber()) { + if (((INumber) arg1).complexSign() < 0) { + result = plusAST.copy(); + result.set(1, arg1.negate()); + for (int i = 2; i < plusAST.size(); i++) { + result.set(i, plusAST.get(i).negate()); + } + return result; + } + } else if (arg1.isTimes()) { + IExpr arg1Negated = getNormalizedNegativeExpression(arg1, checkTimesPlus); + if (arg1Negated.isPresent()) { + // int positiveElementsCounter = 0; + result = plusAST.copy(); + result.set(1, arg1Negated); + for (int i = 2; i < plusAST.size(); i++) { + IExpr temp = plusAST.get(i); + // if (!temp.isTimes() && !temp.isPower()) { + // return F.NIL; + // } + + // arg1Negated = getNormalizedNegativeExpression(temp, checkTimesPlus); + // if (arg1Negated.isPresent()) { + // result.set(i, arg1Negated); + // } else { + + // positiveElementsCounter++; + // if (positiveElementsCounter * 2 > plusAST.argSize()) { + // number of positive elements is greater + // than number of negative elements + // return F.NIL; + // } + result.set(i, temp.negate()); + + // } + } + return result; + } else if (arg1.isNegativeInfinity()) { + result = plusAST.copy(); + result.set(1, F.CInfinity); + for (int i = 2; i < plusAST.size(); i++) { + result.set(i, plusAST.get(i).negate()); } + return result; } } - return false; + return F.NIL; } /** - * Return true if the expression is considered having a negative - * value - * - * @param expression - * @param checkTimesPlus check Times(...) and Plus(...) expressions - * @return + * Create a {@link S#Times} form from expr and replace all occurrences of + * "negative" {@link S#Plus} expressions. Collect the "minus one" factors if + * necessary and insert the result at index 1. + * + * @param expr + * @return the original expr in {@link S#Times} form, if no "negative" {@link S#Plus} + * expression could b found or a new {@link S#Times} form, which contains normalized + * {@link S#Plus} expressions */ - private static boolean isNegativeValued(final IExpr expression, boolean checkTimesPlus) { - if (expression.isNumber()) { - return ((INumber) expression).complexSign() < 0; - } else if (expression.isAST()) { - if (checkTimesPlus && expression.isTimes()) { - IExpr arg1 = expression.first(); - // see github #110: checking for arg1.isNegative() will trigger infinite recursion! - if (arg1.isNumber()) { - if (((INumber) arg1).complexSign() < 0) { - return true; + public static IAST getNegativePlusInTimes(IExpr expr) { + final IAST timesAST = expr.isTimes() ? (IAST) expr : F.Times(expr); + IASTAppendable timesAppendable = F.NIL; + if (EvalEngine.get().isNoSimplifyMode()) { + return timesAST; + } + INumber neg = F.C1; + for (int i = 1; i < timesAST.size(); i++) { + IExpr arg = timesAST.get(i); + if (arg.isPlus()) { + IExpr negativeExpr = getNormalizedNegativePlus((IAST) arg, true); + if (negativeExpr.isPresent()) { + if (timesAppendable.isNIL()) { + timesAppendable = timesAST.copyAppendable(); } - } else if (arg1.isNegativeInfinity()) { - return true; + timesAppendable.set(i, negativeExpr); + neg = neg.negate(); } - } else if (checkTimesPlus && expression.isPlus()) { - IAST plusAST = ((IAST) expression); - IExpr arg1 = plusAST.arg1(); - if (arg1.isNumber()) { - if (((INumber) arg1).complexSign() < 0) { - return true; + } else if (arg.isPower() && arg.base().isPlus() && arg.exponent().isInteger()) { + long exponent = arg.exponent().toLongDefault(); + if (exponent != Long.MIN_VALUE) { + IExpr negativeExpr = getNormalizedNegativePlus((IAST) arg.base(), true); + if (negativeExpr.isPresent()) { + if (timesAppendable.isNIL()) { + timesAppendable = timesAST.copyAppendable(); + } + timesAppendable.set(i, F.Power(negativeExpr, arg.exponent())); + neg = neg.times(F.CN1.powerRational(exponent)); } - } else if (arg1.isNegativeInfinity() - || (arg1.isTimes() && isNegativeValued(arg1, checkTimesPlus))) { - return true; - } - } else if (expression.isDirectedInfinity() && expression.isAST1()) { - IExpr arg1 = expression.first(); - if (arg1.isMinusOne() || arg1.isNegativeImaginaryUnit()) { - return true; } } } - return false; + if (neg.isMinusOne()) { + if (timesAppendable.arg1().isNumber()) { + timesAppendable.set(1, timesAppendable.arg1().negate()); + } else { + timesAppendable.append(1, neg); + } + } + + return timesAppendable.orElse(timesAST); } /** @@ -315,140 +441,7 @@ public static IAST getPeriodicParts(final IExpr expr, final IExpr period) { } /** - * Split plusAST into two parts, a "rest" and a multiple of Pi/2. This assumes plusAST to be an - * Plus() expression. The multiple of Pi/2 returned in the second position is always a IRational - * number. - * - * @param plusAST - * @param engine - * @return F.NIL if no multiple is found. - */ - public static IAST peelOff(final IAST plusAST, final EvalEngine engine) { - IRational k = null; - for (int i = 1; i < plusAST.size(); i++) { - IExpr temp = plusAST.get(i); - if (temp.equals(S.Pi)) { - k = F.C1; - break; - } - if (temp.isTimes2()) { - if (temp.first().isRational() && temp.second().equals(S.Pi)) { - k = (IRational) temp.first(); - break; - } - } - } - if (k != null) { - IASTMutable result = F.binaryAST2(S.List, plusAST, F.C0); - IExpr m1 = F.Times(k.mod(F.C1D2), S.Pi); - IExpr m2 = S.Subtract.of(engine, F.Times(k, S.Pi), m1); - result.set(1, S.Subtract.of(plusAST, m2)); - result.set(2, m2); - return result; - } - return F.NIL; - } - - /** - * This method assumes plusAST to be a Plus() expression. The multiple of Pi returned is a - * IRational number or assumed to be an expression with Integer result. - * - * @param plusAST - * @param engine - * @return F.NIL if no multiple is found. - */ - public static IExpr peelOffPlusRational(final IAST plusAST, final EvalEngine engine) { - IExpr k = null; - for (int i = 1; i < plusAST.size(); i++) { - IExpr temp = plusAST.get(i); - if (temp.equals(S.Pi)) { - k = F.C1; - return k; - } - if (temp.isTimes()) { - IExpr peeled = peelOfTimes((IAST) temp, S.Pi); - if (peeled.isPresent()) { - if (peeled.isRational() || peeled.isIntegerResult()) { - // k = temp.first(); - return peeled; - } - } - } - } - return null; - } - - /** - * This assumes plusAST to be an Plus() expression. The multiple of Pi returned is a IRational - * number or assumed to be an expression with Integer result. - * - * @param plusAST - * @param engine - * @return F.NIL if no multiple is found, otherwise return - * List(rational-number, argument) - */ - public static IAST peelOffPlusI(final IAST plusAST, final EvalEngine engine) { - for (int i = 1; i < plusAST.size(); i++) { - final IExpr arg = plusAST.get(i); - if (arg.isTimes()) { - IExpr peeled = peelOfTimes((IAST) arg, S.Pi); - if (peeled.isPresent()) { - IExpr x = S.Times.of(F.CNI, peeled); - if (x.isRational() || x.isIntegerResult()) { - return F.list(x, arg); - } - } - } - } - return F.NIL; - } - - /** - * Try to split a periodic part from the Times() expression: result == timesAST / period - * - * - * @param astTimes - * @param period - * @return F.NIL if no periodicity was found - */ - public static IExpr peelOfTimes(final IAST astTimes, final IExpr period) { - for (int i = 1; i < astTimes.size(); i++) { - if (astTimes.get(i).equals(period)) { - return astTimes.splice(i).oneIdentity1(); - } - } - return F.NIL; - } - - /** - * Try to split a periodic part from the Times() expression: result == timesAST / period - * - * - * @param astTimes - * @param period1 - * @param period2 - * @return F.NIL if no periodicity was found - */ - public static IExpr peelOfTimes(final IAST astTimes, final IExpr period1, final IExpr period2) { - IASTAppendable result = F.NIL; - for (int i = 1; i < astTimes.size(); i++) { - if (astTimes.get(i).equals(period1)) { - result = astTimes.copyAppendable(); - result.remove(i); - for (int j = 1; j < result.size(); j++) { - if (result.get(j).equals(period2)) { - result.remove(j); - return result; - } - } - return F.NIL; - } - } - return F.NIL; - } - - /** - * Check if the expression is canonical negative. + * Check if the expression is canonical negative. * * @param expression * @param checkTimesPlus check Times(...) and Plus(...) expressions @@ -572,33 +565,6 @@ public static IExpr getPureImaginaryPart(final IExpr expr) { return F.NIL; } - public static IExpr getComplexExpr(final IExpr expr, IExpr factor) { - if (expr.isComplex() && (expr.re().isZero() || expr.re().isNegative())) { - return F.Times(factor, expr); - } else { - if (expr.isTimes() && expr.first().isComplex()) { - IComplex arg1 = (IComplex) expr.first(); - if (arg1.re().isZero() || arg1.re().isNegative()) { - return F.Times(factor, expr); - } - } else if (expr.isPlus()) { - IExpr arg1 = expr.first(); - if (arg1.isComplex() && (arg1.re().isZero() || arg1.re().isNegative())) { - // distribute the factor over the Plus() args - return F.Distribute(F.Times(factor, expr)); - } - if (arg1.isTimes() && arg1.first().isComplex()) { - arg1 = arg1.first(); - if (arg1.re().isZero() || arg1.re().isNegative()) { - // distribute the factor over the Plus() args - return F.Distribute(F.Times(factor, expr)); - } - } - } - } - return F.NIL; - } - public static IExpr imaginaryPart(final IExpr expr, boolean unequalsZero) { IExpr imPart = S.Im.of(expr); if (unequalsZero) { @@ -612,19 +578,6 @@ public static IExpr imaginaryPart(final IExpr expr, boolean unequalsZero) { return F.NIL; } - public static IExpr realPart(final IExpr expr, boolean unequalsZero) { - IExpr rePart = S.Re.of(expr); - if (unequalsZero) { - if (rePart.isZero()) { - return F.NIL; - } - } - if (rePart.isNumber() || rePart.isFree(S.Re)) { - return rePart; - } - return F.NIL; - } - /** * Initialize the serialized Rubi integration rules from ressource /ser/integrate.ser * . @@ -656,171 +609,274 @@ public static void initSerializedRules(final ISymbol symbol) { } } - private static IExpr pureImaginaryPart(final IExpr expr) { - if (expr.isComplex() && ((IComplex) expr).re().isZero()) { - IComplex compl = (IComplex) expr; - return compl.im(); - } - if (expr.isTimes()) { - IAST times = ((IAST) expr); - IExpr arg1 = times.arg1(); - if (arg1.isComplex() && ((IComplex) arg1).re().isZero()) { - return times.setAtCopy(1, ((IComplex) arg1).im()); - } - } - return F.NIL; - } - /** - * Create a rule which invokes the method name in this class instance. + * Return true if the expression is considered having a negative + * value * - * @param symbol - * @param patternString - * @param methodName + * @param expression + * @param checkTimesPlus check Times(...) and Plus(...) expressions + * @return */ - public void createRuleFromMethod(ISymbol symbol, String patternString, String methodName) { - PatternMatcherAndInvoker pm = new PatternMatcherAndInvoker(patternString, this, methodName); - symbol.putDownRule(pm); - } - - /** {@inheritDoc} */ - @Override - public abstract IExpr evaluate(final IAST ast, final EvalEngine engine); - - /** {@inheritDoc} */ - @Override - public void setUp(final ISymbol newSymbol) { - - // F.SYMBOL_OBSERVER.createPredefinedSymbol(newSymbol.toString()); - if (Config.SERIALIZE_SYMBOLS && newSymbol.containsRules()) { - try ( - FileOutputStream out = - new FileOutputStream("c:\\temp\\ser\\" + newSymbol.getSymbolName() + ".ser"); - ObjectOutputStream oos = new ObjectOutputStream(out);) { - newSymbol.writeRules(oos); - } catch (IOException e) { - LOGGER.error("AbstractFunctionEvaluator.setUp() failed", e); + private static boolean isNegativeValued(final IExpr expression, boolean checkTimesPlus) { + if (expression.isNumber()) { + return ((INumber) expression).complexSign() < 0; + } else if (expression.isAST()) { + if (checkTimesPlus && expression.isTimes()) { + IExpr arg1 = expression.first(); + // see github #110: checking for arg1.isNegative() will trigger infinite recursion! + if (arg1.isNumber()) { + if (((INumber) arg1).complexSign() < 0) { + return true; + } + } else if (arg1.isNegativeInfinity()) { + return true; + } + } else if (checkTimesPlus && expression.isPlus()) { + IAST plusAST = ((IAST) expression); + IExpr arg1 = plusAST.arg1(); + if (arg1.isNumber()) { + if (((INumber) arg1).complexSign() < 0) { + return true; + } + } else if (arg1.isNegativeInfinity() + || (arg1.isTimes() && isNegativeValued(arg1, checkTimesPlus))) { + return true; + } + } else if (expression.isDirectedInfinity() && expression.isAST1()) { + IExpr arg1 = expression.first(); + if (arg1.isMinusOne() || arg1.isNegativeImaginaryUnit()) { + return true; + } } } + return false; } /** - * Determine the options from the last arguments of ast or from the default options. + * Return true if the number of negative terms of the ast expression + * countWeight is greater than the half of the number of the arguments of ast + * . I.e. int halfSize = ast.size() / 2; return countWeight > halfSize; * - * @param options the result array of options and their (possibly default) values. * @param ast - * @param argSize - * @param expectedArgSize - * @param optionSymbol - * @param engine + * @param checkTimesPlus check Times(...) and Plus(...) expressions * @return */ - public static int determineOptions(IExpr[] options, IAST ast, int argSize, int[] expectedArgSize, - IBuiltInSymbol[] optionSymbol, EvalEngine engine) { - argSize = determineArgumentOptions(options, ast, argSize, expectedArgSize, optionSymbol); - - determineDefaultOptions(options, ast, optionSymbol, engine); - return argSize; + public static boolean isNegativeWeighted(final IAST ast, boolean checkTimesPlus) { + return isNegativeWeighted(ast, checkTimesPlus, ast.size() / 2); } /** - * Determine the options only from the last arguments of ast. Possibly additional - * options are null if no matching option is found in the arguments of the ast - * . + * Return the number of negative terms of the ast expression. * - * @param options * @param ast - * @param argSize - * @param expectedArgSize - * @param optionSymbol + * @param checkTimesPlus check Times(...) and Plus(...) expressions + * @param maxNegativeExpr maximum number of negative valued terms which have to be found before + * returning true * @return */ - private static int determineArgumentOptions(IExpr[] options, IAST ast, int argSize, - int[] expectedArgSize, IBuiltInSymbol[] optionSymbol) { - int minNumberOfArgs = 1; - if (expectedArgSize != null) { - // the ast function must at least contain the minimum number of arguments - minNumberOfArgs = expectedArgSize[0]; - if (minNumberOfArgs < 1) { - minNumberOfArgs = 1; + public static boolean isNegativeWeighted(final IAST ast, boolean checkTimesPlus, + int maxNegativeExpr) { + int count = maxNegativeExpr - 1; + for (int i = 1; i < ast.size(); i++) { + if (isNegativeValued(ast.get(i), checkTimesPlus)) { + if (--count < 0) { + return true; + } } } + return false; + } - int counter = 0; - boolean evaled = true; - while (argSize > minNumberOfArgs && evaled && counter < optionSymbol.length) { - IExpr arg = ast.get(argSize); + /** + * Split plusAST into two parts, a "rest" and a multiple of Pi/2. This assumes plusAST to be an + * Plus() expression. The multiple of Pi/2 returned in the second position is always a IRational + * number. + * + * @param plusAST + * @param engine + * @return F.NIL if no multiple is found. + */ + public static IAST peelOff(final IAST plusAST, final EvalEngine engine) { + IRational k = null; + for (int i = 1; i < plusAST.size(); i++) { + IExpr temp = plusAST.get(i); + if (temp.equals(S.Pi)) { + k = F.C1; + break; + } + if (temp.isTimes2()) { + if (temp.first().isRational() && temp.second().equals(S.Pi)) { + k = (IRational) temp.first(); + break; + } + } + } + if (k != null) { + IASTMutable result = F.binaryAST2(S.List, plusAST, F.C0); + IExpr m1 = F.Times(k.mod(F.C1D2), S.Pi); + IExpr m2 = S.Subtract.of(engine, F.Times(k, S.Pi), m1); + result.set(1, S.Subtract.of(plusAST, m2)); + result.set(2, m2); + return result; + } + return F.NIL; + } - // check that arg has the correct options format: - if (arg.isRule()) { - evaled = false; - for (int i = 0; i < optionSymbol.length; i++) { - if (optionSymbol[i].equals(arg.first())) { - options[i] = arg.second(); - argSize--; - counter++; - evaled = true; - break; + /** + * This assumes plusAST to be an Plus() expression. The multiple of Pi returned is a IRational + * number or assumed to be an expression with Integer result. + * + * @param plusAST + * @param engine + * @return F.NIL if no multiple is found, otherwise return + * List(rational-number, argument) + */ + public static IAST peelOffPlusI(final IAST plusAST, final EvalEngine engine) { + for (int i = 1; i < plusAST.size(); i++) { + final IExpr arg = plusAST.get(i); + if (arg.isTimes()) { + IExpr peeled = peelOfTimes((IAST) arg, S.Pi); + if (peeled.isPresent()) { + IExpr x = S.Times.of(F.CNI, peeled); + if (x.isRational() || x.isIntegerResult()) { + return F.list(x, arg); } } - } else if (arg.isListOfRules(true) && !arg.isEmptyList()) { - IAST listOfRules = (IAST) arg; - for (int j = 1; j < listOfRules.size(); j++) { - IAST rule = (IAST) listOfRules.get(j); - evaled = false; - for (int i = 0; i < optionSymbol.length; i++) { - if (optionSymbol[i].equals(rule.first())) { - options[i] = rule.second(); - argSize--; - counter++; - evaled = true; - break; - } + } + } + return F.NIL; + } + + /** + * This method assumes plusAST to be a Plus() expression. The multiple of Pi returned is a + * IRational number or assumed to be an expression with Integer result. + * + * @param plusAST + * @param engine + * @return F.NIL if no multiple is found. + */ + public static IExpr peelOffPlusRational(final IAST plusAST, final EvalEngine engine) { + IExpr k = null; + for (int i = 1; i < plusAST.size(); i++) { + IExpr temp = plusAST.get(i); + if (temp.equals(S.Pi)) { + k = F.C1; + return k; + } + if (temp.isTimes()) { + IExpr peeled = peelOfTimes((IAST) temp, S.Pi); + if (peeled.isPresent()) { + if (peeled.isRational() || peeled.isIntegerResult()) { + // k = temp.first(); + return peeled; } } - } else { - evaled = false; - break; } } - return argSize; + return null; } /** - * Replace the options which are null only from the default options of the head - * symbol. + * Try to split a periodic part from the Times() expression: result == timesAST / period + * * - * @param options - * @param ast - * @param optionSymbol - * @param engine + * @param astTimes + * @param period + * @return F.NIL if no periodicity was found */ - private static void determineDefaultOptions(IExpr[] options, IAST ast, - IBuiltInSymbol[] optionSymbol, EvalEngine engine) { - int optionNullStart = -1; - for (int i = 0; i < options.length; i++) { - if (options[i] == null) { - optionNullStart = i; - break; + public static IExpr peelOfTimes(final IAST astTimes, final IExpr period) { + for (int i = 1; i < astTimes.size(); i++) { + if (astTimes.get(i).equals(period)) { + return astTimes.splice(i).oneIdentity1(); } } - if (optionNullStart >= 0) { - final IExpr temp = OptionsPattern.optionsList(ast.topHead(), false); - // final IExpr temp = engine.evaluate(F.Options(ast.topHead())); - if (temp.isList() && temp.size() > 1) { - IAST list = (IAST) temp; - for (int i = optionNullStart; i < options.length; i++) { - if (options[i] == null) { - options[i] = S.None; - for (int j = 1; j < list.size(); j++) { - IAST rule = (IAST) list.get(j); - if (optionSymbol[i].equals(rule.first())) { - options[i] = rule.second(); - break; - } - } + return F.NIL; + } + + /** + * Try to split a periodic part from the Times() expression: result == timesAST / period + * + * + * @param astTimes + * @param period1 + * @param period2 + * @return F.NIL if no periodicity was found + */ + public static IExpr peelOfTimes(final IAST astTimes, final IExpr period1, final IExpr period2) { + IASTAppendable result = F.NIL; + for (int i = 1; i < astTimes.size(); i++) { + if (astTimes.get(i).equals(period1)) { + result = astTimes.copyAppendable(); + result.remove(i); + for (int j = 1; j < result.size(); j++) { + if (result.get(j).equals(period2)) { + result.remove(j); + return result; } } + return F.NIL; + } + } + return F.NIL; + } + + private static IExpr pureImaginaryPart(final IExpr expr) { + if (expr.isComplex() && ((IComplex) expr).re().isZero()) { + IComplex compl = (IComplex) expr; + return compl.im(); + } + if (expr.isTimes()) { + IAST times = ((IAST) expr); + IExpr arg1 = times.arg1(); + if (arg1.isComplex() && ((IComplex) arg1).re().isZero()) { + return times.setAtCopy(1, ((IComplex) arg1).im()); + } + } + return F.NIL; + } + + public static IExpr realPart(final IExpr expr, boolean unequalsZero) { + IExpr rePart = S.Re.of(expr); + if (unequalsZero) { + if (rePart.isZero()) { + return F.NIL; + } + } + if (rePart.isNumber() || rePart.isFree(S.Re)) { + return rePart; + } + return F.NIL; + } + + /** + * Create a rule which invokes the method name in this class instance. + * + * @param symbol + * @param patternString + * @param methodName + */ + public void createRuleFromMethod(ISymbol symbol, String patternString, String methodName) { + PatternMatcherAndInvoker pm = new PatternMatcherAndInvoker(patternString, this, methodName); + symbol.putDownRule(pm); + } + + /** {@inheritDoc} */ + @Override + public abstract IExpr evaluate(final IAST ast, final EvalEngine engine); + + /** {@inheritDoc} */ + @Override + public void setUp(final ISymbol newSymbol) { + + // F.SYMBOL_OBSERVER.createPredefinedSymbol(newSymbol.toString()); + if (Config.SERIALIZE_SYMBOLS && newSymbol.containsRules()) { + try ( + FileOutputStream out = + new FileOutputStream("c:\\temp\\ser\\" + newSymbol.getSymbolName() + ".ser"); + ObjectOutputStream oos = new ObjectOutputStream(out);) { + newSymbol.writeRules(oos); + } catch (IOException e) { + LOGGER.error("AbstractFunctionEvaluator.setUp() failed", e); } } } diff --git a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LinearAlgebraTestCase.java b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LinearAlgebraTestCase.java index a9b1ae46e4..563182f05e 100644 --- a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LinearAlgebraTestCase.java +++ b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LinearAlgebraTestCase.java @@ -17,7 +17,7 @@ public LinearAlgebraTestCase(String name) { public void testAdjugate() { check("Adjugate({{E^5, 1, 3 - 2*I}, {1 + I, Pi/2, 5}, {0, 1, -4}})", // - "{{2*(-5/2-Pi),7-I*2,(-3/2+I)*(-30/13-I*20/13+Pi)},\n" // + "{{2*(-5/2-Pi),7-I*2,(3/2-I)*(30/13+I*20/13-Pi)},\n" // + " {4+I*4,-4*E^5,5*(1+I*1/5-E^5)},\n" // + " {1+I,-E^5,1/2*(-2-I*2+E^5*Pi)}}"); // https://en.wikipedia.org/wiki/Adjugate_matrix @@ -931,10 +931,9 @@ public void testIdentityMatrix() { public void testInverse() { check("Inverse(s*{{1,0,0},{0,1,0},{0,0,1}}-{{-1,1,1},{-4,-4,1},{1,1,1}})", // - "{{(-5+3*s+s^2)/(-10+s+4*s^2+s^3),s/(-10+s+4*s^2+s^3),(5+s)/(-10+s+4*s^2+s^3)},\n"// - + " {((1+s)*(-5+4*s))/((-1-s)*(-10+s+4*s^2+s^3)),(-2+s^2)/(-10+s+4*s^2+s^3),(-3+s)/(-\n"// - + "10+s+4*s^2+s^3)},\n"// - + " {s/(-10+s+4*s^2+s^3),(-2-s)/(10-s-4*s^2-s^3),(8+5*s+s^2)/(-10+s+4*s^2+s^3)}}"); + "{{(5-3*s-s^2)/(10-s-4*s^2-s^3),s/(-10+s+4*s^2+s^3),(5+s)/(-10+s+4*s^2+s^3)},\n" // + + " {(5-4*s)/(-10+s+4*s^2+s^3),(2-s^2)/(10-s-4*s^2-s^3),(3-s)/(10-s-4*s^2-s^3)},\n" // + + " {-s/(10-s-4*s^2-s^3),-(2+s)/(10-s-4*s^2-s^3),(-8-5*s-s^2)/(10-s-4*s^2-s^3)}}"); check("N(Inverse({{1,2.0},{3,4}}),50)", // "{{-2.0,1.0},{1.5,-0.5}}"); @@ -1034,7 +1033,7 @@ public void testLinearSolve() { check("LinearSolve({{a, b,c,d,e}, {f,g,h,i,j}}, {x, y})", // "{(g*x-b*y)/(-b*f+a*g),(-f*x+a*y)/(-b*f+a*g),0,0,0}"); check("LinearSolve({{a,b,c,d,e}, {f,g,h,i,j}, {k,l,m,n,o}}, {x,y,z})", // - "{(-h*l*x+g*m*x+c*l*y-b*m*y-c*g*z+b*h*z)/(-c*g*k+b*h*k+c*f*l-a*h*l-b*f*m+a*g*m),(h*k*x-f*m*x-c*k*y+a*m*y+c*f*z-a*h*z)/(-c*g*k+b*h*k+c*f*l-a*h*l-b*f*m+a*g*m),(-g*k*x+f*l*x+b*k*y-a*l*y-b*f*z+a*g*z)/(-c*g*k+b*h*k+c*f*l-a*h*l-b*f*m+a*g*m),\n" + "{(h*l*x-g*m*x-c*l*y+b*m*y+c*g*z-b*h*z)/(c*g*k-b*h*k-c*f*l+a*h*l+b*f*m-a*g*m),(-h*k*x+f*m*x+c*k*y-a*m*y-c*f*z+a*h*z)/(c*g*k-b*h*k-c*f*l+a*h*l+b*f*m-a*g*m),(g*k*x-f*l*x-b*k*y+a*l*y+b*f*z-a*g*z)/(c*g*k-b*h*k-c*f*l+a*h*l+b*f*m-a*g*m),\n" // + "0,0}"); // underdetermined system: check("LinearSolve({{1, 2, 3}, {4, 5, 6}}, {6, 15})", // diff --git a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LowercaseTestCase.java b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LowercaseTestCase.java index a1fca7ec33..07010a1b77 100644 --- a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LowercaseTestCase.java +++ b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/LowercaseTestCase.java @@ -22017,6 +22017,9 @@ public void testSign() { // } public void testSimplify() { + check("Simplify((1/x-1/3)/(x-3))", // + "-1/(3*x)"); + check("Simplify(Abs(Sign(z)),z!=-1&& z!=0&&z!=1)", // "1"); check("Simplify(Sign(x), x<0)", // @@ -24336,6 +24339,10 @@ public void testTimeValue() { } public void testTogether() { + check("Together((1/x-1/3)^3/(x-3)^2)", // + "(3-x)/(27*x^3)"); + check("Together((1/x-1/3)/(x-3))", // + "-1/(3*x)"); check("Together(1/2+(1/3+I*1/2)*Sqrt(3))", // "1/6*(3+(2+I*3)*Sqrt(3))"); check("Together(1/2+I*1/2*Sqrt(3))", // @@ -24370,7 +24377,7 @@ public void testTogether() { check("Together((x+2*Sqrt(x)+1)/(1+Sqrt(x)))", // "1+Sqrt(x)"); check("Together(-a/(-(b-a*c)))", // - "-a/(-b+a*c)"); + "a/(b-a*c)"); check("Together(Simplify(Together(-a/(-(b-a*c)))))", // "a/(b-a*c)"); check("Together(1/2+I/3 + 3*a^(-1))", // diff --git a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SolveTest.java b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SolveTest.java index 4b4e3e91cd..b71051a0ef 100644 --- a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SolveTest.java +++ b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SolveTest.java @@ -216,7 +216,6 @@ public void testSolveX7_15002() { } public void testSolve001() { - check("Solve((5*x^4-2)/(x+1)/(x^2-1)==0,x)", // "{{x->-(2/5)^(1/4)},{x->-I*(2/5)^(1/4)},{x->I*(2/5)^(1/4)},{x->(2/5)^(1/4)}}"); } diff --git a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SumTest.java b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SumTest.java index 1549d295f5..baba139e0b 100644 --- a/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SumTest.java +++ b/symja_android_library/matheclipse-core/src/test/java/org/matheclipse/core/system/SumTest.java @@ -242,7 +242,7 @@ public void testSum007() { check("Sum(a^i,{i,0,1})", // "1+a"); check("Sum(a^i,{i,0,Infinity})", // - "-1/(-1+a)"); + "1/(1-a)"); check("Sum(a^i,{i,1,Infinity})", // "-a/(-1+a)"); check("Sum(a^i,{i,3,Infinity})", //