package org.matheclipse.core.reflection.system;

import static org.matheclipse.core.expression.F.Expand;
import static org.matheclipse.core.expression.F.Plus;
import static org.matheclipse.core.expression.F.Times;

import org.matheclipse.core.eval.exception.Validate;
import org.matheclipse.core.eval.exception.WrongArgumentType;
import org.matheclipse.core.eval.interfaces.AbstractFunctionEvaluator;
import org.matheclipse.core.expression.F;
import org.matheclipse.core.generic.Functors;
import org.matheclipse.core.generic.combinatoric.KPermutationsIterable;
import org.matheclipse.core.interfaces.IAST;
import org.matheclipse.core.interfaces.IExpr;
import org.matheclipse.core.interfaces.IInteger;

public class Expand extends AbstractFunctionEvaluator {
	private static class Expander {
		/**
		 * Pattern which may be null
		 */
		IExpr pattern;

		public Expander(IExpr pattern) {
			this.pattern = pattern;
		}

		/**
		 * Check if the given expression doesn't contain the pattern.
		 * 
		 * @param expression
		 * @return
		 */
		public boolean isPatternFree(IExpr expression) {
			return (pattern != null && expression.isFree(pattern, false));
		}

		public IExpr expand(final IAST ast) {
			if (isPatternFree(ast)) {
				return null;
			}
			if (ast.isPower()) {
				return expandPowerNull(ast);
			} else if (ast.isTimes()) {
				// (a+b)*(c+d)...

				IExpr[] temp = Apart.getFractionalPartsTimes(ast, false);
				if (temp[0].equals(F.C1)) {
					if (temp[1].isTimes()) {
						return F.Power(expandTimes((IAST) temp[1]), F.CN1);
					}
					if (temp[1].isPower() || temp[1].isPlus()) {
						IExpr denom = expand((IAST) temp[1]);
						if (denom != null) {
							return F.Power(denom, F.CN1);
						}
					}
					return null;
				}

				if (temp[1].equals(F.C1)) {
					return expandTimes(ast);
				}

				if (temp[0].isTimes()) {
					temp[0] = expandTimes((IAST) temp[0]);
				}
				if (temp[1].isTimes()) {
					temp[1] = expandTimes((IAST) temp[1]);
				} else {
					if (temp[1].isPower() || temp[1].isPlus()) {
						IExpr denom = expand((IAST) temp[1]);
						if (denom != null) {
							temp[1] = denom;
						}
					}
				}
				return F.Times(temp[0], F.Power(temp[1], F.CN1));
			} else if (ast.isPlus()) {
				return ast.map(Functors.replace1st(Expand(F.Null)));
			}
			return null;
		}

		public IExpr expandPowerNull(final IAST powerAST) {
			try {
				int exp = Validate.checkPowerExponent(powerAST);
				// (a+b)^exp
				if ((powerAST.arg1().isPlus())) {
					if (exp < 0) {
						exp *= (-1);
						return F.Power(expandPower((IAST) powerAST.arg1(), exp), F.CN1);
					}
					return expandPower((IAST) powerAST.arg1(), exp);
				}
			} catch (WrongArgumentType e) {
				return null;
			}
			return null;
		}

		/**
		 * Expand a polynomial power with the multinomial theorem. See Wikipedia - Multinomial theorem
		 * 
		 * @param plusAST
		 * @param n
		 *            n ≥ 0
		 * @return
		 */
		private IExpr expandPower(final IAST plusAST, final int n) {
			if (n == 1) {
				return plusAST;
			}
			if (n == 0) {
				return F.C1;
			}

			final IAST expandedResult = Plus();
			Expand.NumberPartititon part = new Expand.NumberPartititon(plusAST, n, expandedResult);
			part.partition();
			return expandedResult;
		}

		private IExpr expandTimes(final IAST timesAST) {
			IExpr result = timesAST.arg1();
			IExpr temp;
			if (result.isPower()) {
				temp = expandPowerNull((IAST) result);
				if (temp != null) {
					result = temp;
				}
			}
			for (int i = 2; i < timesAST.size(); i++) {
				temp = timesAST.get(i);
				if (temp.isPower()) {
					temp = expandPowerNull((IAST) temp);
					if (temp == null) {
						temp = timesAST.get(i);
					}
				}
				result = expandTimesBinary(result, temp);
			}
			return result;
		}

		private IExpr expandTimesBinary(final IExpr expr0, final IExpr expr1) {
			if (expr0.isPlus()) {
				if (!expr1.isPlus()) {
					return F.eval(expandTimesPlus(expr1, (IAST) expr0));
				}
				final IAST ast1 = assurePlus(expr1);
				return F.eval(expandTimesPlus((IAST) expr0, ast1));
			}
			if (expr1.isPlus()) {
				if (!expr0.isPlus()) {
					return F.eval(expandTimesPlus(expr0, (IAST) expr1));
				}
				final IAST ast0 = assurePlus(expr0);
				return F.eval(expandTimesPlus(ast0, (IAST) expr1));
			}
			return F.eval(F.Times(expr0, expr1));
		}

		private IAST expandTimesPlus(final IAST plusAST0, final IAST plusAST1) {
			// (a+b)*(c+d) -> a*c+a*d+b*c+b*d
			final IAST pList = Plus();
			for (int i = 1; i < plusAST0.size(); i++) {
				plusAST1.args().map(pList, Functors.replace2nd(Times(plusAST0.get(i), F.Null)));
			}
			return pList;
		}

		private IAST expandTimesPlus(final IExpr expr1, final IAST plusAST) {
			// expr*(a+b+c) -> expr*a+expr*b+expr*c
			final IAST pList = Plus();
			for (int i = 1; i < plusAST.size(); i++) {
				pList.add(F.Times(expr1, plusAST.get(i)));
			}
			return pList;
		}

		private IAST assurePlus(final IExpr expr) {
			if (expr.isPlus()) {
				return (IAST) expr;
			}
			// if expr is not of the form Plus[...], generate Plus[expr]
			return F.Plus(expr);
		}

	}

	static class NumberPartititon {
		IAST expandedResult;
		int m;
		int n;
		int[] parts;
		IAST precalculatedPowerASTs;

		public NumberPartititon(IAST plusAST, int n, IAST expandedResult) {

			this.expandedResult = expandedResult;
			this.n = n;
			this.m = plusAST.size() - 1;
			// System.out.println(m+"/"+n);
			this.parts = new int[m];
			// precalculate all Power[] ASTs:
			this.precalculatedPowerASTs = F.List();
			for (IExpr expr : plusAST) {
				precalculatedPowerASTs.add(F.Power(expr, F.Null));
			}
		}

		private void addFactor(int[] j) {
			final KPermutationsIterable perm = new KPermutationsIterable(j, m, m);
			IInteger multinomial = F.integer(Multinomial.multinomial(j, n));
			final IAST times = Times();
			IAST temp;
			for (int[] indices : perm) {
				final IAST timesAST = times.clone();
				if (!multinomial.isOne()) {
					timesAST.add(multinomial);
				}
				for (int k = 0; k < m; k++) {
					if (indices[k] != 0) {
						temp = precalculatedPowerASTs.getAST(k + 1).clone();
						if (indices[k] == 1) {
							timesAST.add(temp.arg1());
						} else {
							temp.set(2, F.integer(indices[k]));
							timesAST.add(temp);
						}

					}
				}
				expandedResult.add(timesAST);
			}
		}

		public void partition() {
			partition(n, n, 0);
		}

		private void partition(int n, int max, int currentIndex) {
			if (n == 0) {
				addFactor(parts);
				return;
			}
			if (currentIndex >= m) {
				return;
			}
			int old;
			old = parts[currentIndex];
			int min = Math.min(max, n);

			for (int i = min; i >= 1; i--) {
				parts[currentIndex] = i;
				partition(n - i, i, currentIndex + 1);
			}
			parts[currentIndex] = old;
		}
	}

	public static IExpr expand(final IAST ast, IExpr patt) {
		Expander expander = new Expander(patt);
		return expander.expand(ast);
	}

	public Expand() {
		super();
	}

	@Override
	public IExpr evaluate(final IAST ast) {
		Validate.checkRange(ast, 2, 3);

		if (ast.arg1().isAST()) {
			IExpr patt = null;
			if (ast.size() > 2) {
				patt = ast.arg2();
			}
			IExpr temp = expand((IAST) ast.arg1(), patt);
			if (temp != null) {
				return temp;
			}
		}

		return ast.arg1();
	}
}
 

Ads help maintain this website.