SimplificationRules.java

package net.bmahe.genetics4j.gp.math;

import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import net.bmahe.genetics4j.core.chromosomes.TreeNode;
import net.bmahe.genetics4j.gp.InputSpec;
import net.bmahe.genetics4j.gp.Operation;
import net.bmahe.genetics4j.gp.OperationFactories;
import net.bmahe.genetics4j.gp.OperationFactory;
import net.bmahe.genetics4j.gp.spec.mutation.ImmutableRule;
import net.bmahe.genetics4j.gp.spec.mutation.Rule;
import net.bmahe.genetics4j.gp.utils.TreeNodeUtils;

public class SimplificationRules {
	final static public Logger logger = LogManager.getLogger(SimplificationRules.class);

	public final static double DEFAULT_EPSILON = 0.0001;

	protected static boolean isOperation(final TreeNode<Operation<?>> node, final String name) {
		Validate.notNull(node);
		Validate.notBlank(name);
		return name.equals(node.getData().getName());
	}

	protected static boolean hasChildOperation(final TreeNode<Operation<?>> node, final int childIndex,
			final String name) {
		Validate.notNull(node);
		Validate.isTrue(childIndex >= 0);
		Validate.notBlank(name);

		if (node.getChildren().size() <= childIndex) {
			return false;
		}

		final TreeNode<Operation<?>> child = node.getChild(childIndex);
		return name.equals(child.getData().getName());
	}

	protected static <T> T getChildAs(final TreeNode<Operation<?>> node, final int childIndex, final Class<T> clazz) {
		final TreeNode<Operation<?>> child = node.getChild(childIndex);
		final Operation<?> operation = child.getData();
		return (T) operation;
	}

	protected static boolean isEqual(final double v1, final double v2, final double epsilon) {
		Validate.isTrue(epsilon >= 0);

		return Math.abs(v2 - v1) < epsilon;
	}

	protected static boolean isEqual(final double v1, final double v2) {
		return isEqual(v1, v2, DEFAULT_EPSILON);
	}

	@SuppressWarnings("unchecked")
	final public static Rule ADD_TWO_COEFFCIENTS = ImmutableRule
			.of((t) -> isOperation(t, Functions.NAME_ADD) && hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT)
					&& hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT), (program, t) -> {

						final InputSpec inputSpec = program.inputSpec();

						final CoefficientOperation<Double> firstCoefficient = getChildAs(t,
								0,
								CoefficientOperation.class);
						final Double firstValue = firstCoefficient.value();

						final CoefficientOperation<Double> secondCoefficient = getChildAs(t,
								1,
								CoefficientOperation.class);
						final Double secondValue = secondCoefficient.value();

						final OperationFactory coefficientFactory = OperationFactories
								.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, firstValue + secondValue);

						final Operation<?> newOperation = coefficientFactory.build(inputSpec);

						return new TreeNode<>(newOperation);
					});

	@SuppressWarnings("unchecked")
	final public static Rule MUL_TWO_COEFFICIENTS = ImmutableRule
			.of((t) -> isOperation(t, Functions.NAME_MUL) && hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT)
					&& hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT), (program, t) -> {

						final InputSpec inputSpec = program.inputSpec();

						final CoefficientOperation<Double> firstCoefficient = getChildAs(t,
								0,
								CoefficientOperation.class);
						final Double firstValue = firstCoefficient.value();

						final CoefficientOperation<Double> secondCoefficient = getChildAs(t,
								1,
								CoefficientOperation.class);
						final Double secondValue = secondCoefficient.value();

						final OperationFactory coefficientFactory = OperationFactories
								.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, firstValue * secondValue);

						final Operation<?> newOperation = coefficientFactory.build(inputSpec);

						return new TreeNode<>(newOperation);
					});

	@SuppressWarnings("unchecked")
	final public static Rule SUB_TWO_COEFFICIENTS = ImmutableRule
			.of((t) -> isOperation(t, Functions.NAME_SUB) && hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT)
					&& hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT), (program, t) -> {

						final InputSpec inputSpec = program.inputSpec();

						final CoefficientOperation<Double> firstCoefficient = getChildAs(t,
								0,
								CoefficientOperation.class);
						final Double firstValue = firstCoefficient.value();

						final CoefficientOperation<Double> secondCoefficient = getChildAs(t,
								1,
								CoefficientOperation.class);
						final Double secondValue = secondCoefficient.value();

						final OperationFactory coefficientFactory = OperationFactories
								.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, firstValue - secondValue);

						final Operation<?> newOperation = coefficientFactory.build(inputSpec);

						return new TreeNode<>(newOperation);
					});

	@SuppressWarnings("unchecked")
	final public static Rule SUB_INPUT_FROM_SAME_INPUT = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_SUB) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_INPUT) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_INPUT) == false) {
			return false;
		}

		final InputOperation<?> firstInput = (InputOperation<Double>) t.getChild(0).getData();

		final InputOperation<?> secondInput = (InputOperation<Double>) t.getChild(1).getData();

		return firstInput.index() == secondInput.index();
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory coefficientFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 0.0d);

		final Operation<?> newOperation = coefficientFactory.build(inputSpec);

		return new TreeNode<>(newOperation);
	});

	@SuppressWarnings("unchecked")
	final public static Rule SUB_ZERO_FROM_INPUT = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_SUB) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_INPUT) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_INPUT) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> firstCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		return isEqual(firstCoefficient.value(), 0.0d);
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory coefficientFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 0.0d);

		final Operation<?> newOperation = coefficientFactory.build(inputSpec);

		return new TreeNode<>(newOperation);
	});

	@SuppressWarnings("unchecked")
	final public static Rule DIV_TWO_COEFFICIENT_FINITE = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_DIV) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> firstCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		final CoefficientOperation<Double> secondCoefficient = (CoefficientOperation<Double>) t.getChild(1).getData();

		return Double.isFinite(firstCoefficient.value() / secondCoefficient.value());
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final CoefficientOperation<Double> firstCoefficient = getChildAs(t, 0, CoefficientOperation.class);
		final Double firstValue = firstCoefficient.value();

		final CoefficientOperation<Double> secondCoefficient = getChildAs(t, 1, CoefficientOperation.class);
		final Double secondValue = secondCoefficient.value();

		final OperationFactory coefficientFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, firstValue / secondValue);

		final Operation<?> newOperation = coefficientFactory.build(inputSpec);

		return new TreeNode<>(newOperation);
	});

	@SuppressWarnings("unchecked")
	final public static Rule ADD_INPUT_TO_SAME_INPUT = ImmutableRule.of((t) -> {
		boolean result = isOperation(t, Functions.NAME_ADD) && hasChildOperation(t, 0, Terminals.TYPE_INPUT)
				&& hasChildOperation(t, 1, Terminals.TYPE_INPUT);

		if (result == false) {
			return false;
		}

		final InputOperation<?> firstInput = getChildAs(t, 0, InputOperation.class);
		final InputOperation<?> secondInput = getChildAs(t, 1, InputOperation.class);

		return firstInput.index() == secondInput.index();
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final TreeNode<Operation<?>> multBaseTreeNode = new TreeNode<Operation<?>>(Functions.MUL.build(inputSpec));

		final OperationFactory coefficientFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 2.0d);
		final TreeNode<Operation<?>> timesTwoTreeNode = new TreeNode<Operation<?>>(coefficientFactory.build(inputSpec));
		multBaseTreeNode.addChild(timesTwoTreeNode);

		final InputOperation<?> firstInput = (InputOperation<Double>) t.getChild(0).getData();
		final TreeNode<Operation<?>> firstInputTreeNode = new TreeNode<Operation<?>>(firstInput);
		multBaseTreeNode.addChild(firstInputTreeNode);

		return multBaseTreeNode;
	});

	@SuppressWarnings("unchecked")
	final public static Rule MULTIPLY_INPUT_WITH_SAME_INPUT = ImmutableRule.of((t) -> {

		if (isOperation(t, Functions.NAME_MUL) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_INPUT) == false) {
			return false;
		}
		if (hasChildOperation(t, 1, Terminals.TYPE_INPUT) == false) {
			return false;
		}

		final InputOperation<?> firstInput = (InputOperation<Double>) t.getChild(0).getData();

		final InputOperation<?> secondInput = (InputOperation<Double>) t.getChild(1).getData();

		return firstInput.index() == secondInput.index();
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final TreeNode<Operation<?>> expBaseTreeNode = new TreeNode<Operation<?>>(Functions.EXP.build(inputSpec));

		final InputOperation<?> firstInput = (InputOperation<Double>) t.getChild(0).getData();
		final TreeNode<Operation<?>> firstInputTreeNode = new TreeNode<Operation<?>>(firstInput);
		expBaseTreeNode.addChild(firstInputTreeNode);

		final OperationFactory coefficientFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 2.0d);
		final TreeNode<Operation<?>> twoTreeNode = new TreeNode<Operation<?>>(coefficientFactory.build(inputSpec));
		expBaseTreeNode.addChild(twoTreeNode);

		return expBaseTreeNode;
	});

	@SuppressWarnings("unchecked")
	final public static Rule MULTIPLY_INPUT_WITH_EXP_SAME_INPUT_COEFF = ImmutableRule.of((t) -> {
		// ex: MULT( EXP( INPUT[0], 3), INPUT[0])
		// ==> EXP( INPUT[0], 4)

		if (isOperation(t, Functions.NAME_MUL) == false) {
			return false;
		}
		if (hasChildOperation(t, 0, Functions.NAME_EXP) == false) {
			return false;
		}
		if (hasChildOperation(t, 1, Terminals.TYPE_INPUT) == false) {
			return false;
		}

		final TreeNode<Operation<?>> expTreeNode = t.getChild(0);
		if (hasChildOperation(expTreeNode, 0, Terminals.TYPE_INPUT) == false) {
			return false;
		}
		if (hasChildOperation(expTreeNode, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final InputOperation<?> expInput = getChildAs(expTreeNode, 0, InputOperation.class);
		final InputOperation<?> secondInput = getChildAs(t, 1, InputOperation.class);

		return expInput.index() == secondInput.index();
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();
		final TreeNode<Operation<?>> originalExpTreeNode = t.getChild(0);
		final CoefficientOperation<Double> originalCoefficientExp = getChildAs(originalExpTreeNode,
				1,
				CoefficientOperation.class);

		final TreeNode<Operation<?>> expBaseTreeNode = new TreeNode<Operation<?>>(Functions.EXP.build(inputSpec));

		final InputOperation<?> firstInput = (InputOperation<Double>) t.getChild(0).getData();
		final TreeNode<Operation<?>> firstInputTreeNode = new TreeNode<Operation<?>>(firstInput);
		expBaseTreeNode.addChild(firstInputTreeNode);

		final OperationFactory coefficientFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, originalCoefficientExp.value() + 1.0d);
		final TreeNode<Operation<?>> newCoeffTreeNode = new TreeNode<Operation<?>>(coefficientFactory.build(inputSpec));
		expBaseTreeNode.addChild(newCoeffTreeNode);

		return expBaseTreeNode;
	});

	@SuppressWarnings("unchecked")
	final public static Rule MUL_1_WITH_ANYTHING = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_MUL) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> firstCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		return firstCoefficient.value() < 1 + 0.0001 && firstCoefficient.value() > 1 - .0001;
	}, (program, t) -> {

		return t.getChild(1);
	});

	@SuppressWarnings("unchecked")
	final public static Rule MUL_ANYTHING_WITH_1 = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_MUL) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> secondCoefficient = (CoefficientOperation<Double>) t.getChild(1).getData();

		return isEqual(secondCoefficient.value(), 1);
	}, (program, t) -> {

		return t.getChild(0);
	});

	@SuppressWarnings("unchecked")
	final public static Rule ADD_0_WITH_ANYTHING = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_ADD) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> firstCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		return isEqual(firstCoefficient.value(), 0.0d);
	}, (program, t) -> {

		return t.getChild(1);
	});

	@SuppressWarnings("unchecked")
	final public static Rule ADD_ANYTHING_WITH_0 = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_ADD) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> secondCoefficient = (CoefficientOperation<Double>) t.getChild(1).getData();

		return isEqual(secondCoefficient.value(), 0.0d);
	}, (program, t) -> {

		return t.getChild(0);
	});

	@SuppressWarnings("unchecked")
	final public static Rule MUL_0_WITH_ANYTHING = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_MUL) == false) {
			return false;
		}

		if (hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> firstCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		return isEqual(firstCoefficient.value(), 0.0d);
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory zeroFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 0.0d);

		return new TreeNode<>(zeroFactory.build(inputSpec));
	});

	@SuppressWarnings("unchecked")
	final public static Rule MUL_ANYTHING_WITH_0 = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_MUL) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> secondCoefficient = (CoefficientOperation<Double>) t.getChild(1).getData();

		return secondCoefficient.value() < 1 + 0.0001 && secondCoefficient.value() > 1 - .0001;
	}, (program, t) -> {
		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory zeroFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 0.0d);

		return new TreeNode<>(zeroFactory.build(inputSpec));
	});

	@SuppressWarnings("unchecked")
	final public static Rule POW_0 = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_POW) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> secondCoefficient = (CoefficientOperation<Double>) t.getChild(1).getData();

		return isEqual(secondCoefficient.value(), 0);
	}, (program, t) -> {
		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory oneFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 1.0d);

		return new TreeNode<>(oneFactory.build(inputSpec));
	});

	@SuppressWarnings("unchecked")
	final public static Rule POW_1 = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_POW) == false) {
			return false;
		}

		if (hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT) == false) {
			return false;
		}

		final CoefficientOperation<Double> secondCoefficient = (CoefficientOperation<Double>) t.getChild(1).getData();

		return isEqual(secondCoefficient.value(), 1);
	}, (program, t) -> {

		return t.getChild(0);
	});

	@SuppressWarnings("unchecked")
	final public static Rule COS_OF_COEFFICIENT = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_COS) == false) {
			return false;
		}

		return hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT);
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final CoefficientOperation<Double> cosCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		final double cosValue = Math.cos(cosCoefficient.value());

		final OperationFactory cosValueOperationFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, cosValue);

		return new TreeNode<>(cosValueOperationFactory.build(inputSpec));
	});

	@SuppressWarnings("unchecked")
	final public static Rule SIN_OF_COEFFICIENT = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_SIN) == false) {
			return false;
		}

		return hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT);
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final CoefficientOperation<Double> sinCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		final double sinValue = Math.sin(sinCoefficient.value());

		final OperationFactory sinValueOperationFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, sinValue);

		return new TreeNode<>(sinValueOperationFactory.build(inputSpec));
	});

	final public static Rule SUB_SAME_BRANCHES = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_SUB) == false) {
			return false;
		}

		return TreeNodeUtils.areSame(t.getChild(0), t.getChild(1));
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory zeroFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 0.0d);

		return new TreeNode<>(zeroFactory.build(inputSpec));
	});

	final public static Rule ADD_SAME_BRANCHES = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_ADD) == false) {
			return false;
		}

		return TreeNodeUtils.areSame(t.getChild(0), t.getChild(1));
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final TreeNode<Operation<?>> baseAdd = new TreeNode<>(Functions.ADD.build(inputSpec));

		final OperationFactory twoFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 2.0d);
		baseAdd.addChild(new TreeNode<>(twoFactory.build(inputSpec)));

		baseAdd.addChild(t.getChild(0)); // TODO copy it instead?

		return baseAdd;
	});

	final public static Rule DIV_SAME_BRANCHES = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_DIV) == false) {
			return false;
		}

		return TreeNodeUtils.areSame(t.getChild(0), t.getChild(1));
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory oneFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 1.0d);

		return new TreeNode<>(oneFactory.build(inputSpec));
	});

	@SuppressWarnings("unchecked")
	final public static Rule EXP_OF_COEFFICIENT = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_EXP) == false) {
			return false;
		}

		return hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT);
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final CoefficientOperation<Double> expCoefficient = (CoefficientOperation<Double>) t.getChild(0).getData();

		final double expValue = Math.exp(expCoefficient.value());

		final OperationFactory expValueOperationFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, expValue);

		return new TreeNode<>(expValueOperationFactory.build(inputSpec));
	});

	@SuppressWarnings("unchecked")
	final public static Rule POW_TWO_COEFFICIENTS = ImmutableRule
			.of((t) -> isOperation(t, Functions.NAME_POW) && hasChildOperation(t, 0, Terminals.TYPE_COEFFICIENT)
					&& hasChildOperation(t, 1, Terminals.TYPE_COEFFICIENT), (program, t) -> {

						final InputSpec inputSpec = program.inputSpec();

						final CoefficientOperation<Double> firstCoefficient = getChildAs(t,
								0,
								CoefficientOperation.class);
						final Double firstValue = firstCoefficient.value();

						final CoefficientOperation<Double> secondCoefficient = getChildAs(t,
								1,
								CoefficientOperation.class);
						final Double secondValue = secondCoefficient.value();

						final OperationFactory coefficientFactory = OperationFactories.ofCoefficient(
								Terminals.TYPE_COEFFICIENT,
								Double.class,
								Math.pow(firstValue, secondValue));

						final Operation<?> newOperation = coefficientFactory.build(inputSpec);

						return new TreeNode<>(newOperation);
					});

	/**
	 * multiplication of the same branch -> square of the first branch
	 */
	final public static Rule MUL_SAME_BRANCHES = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_MUL) == false) {
			return false;
		}

		return TreeNodeUtils.areSame(t.getChild(0), t.getChild(1));
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final TreeNode<Operation<?>> powNode = new TreeNode<>(Functions.POW.build(inputSpec));

		powNode.addChild(t.getChild(0));
		powNode.addChild(new TreeNode<>(
				OperationFactories.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, 2.0d).build(inputSpec)));

		return powNode;
	});

	final public static Rule COS_PI = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_COS) == false) {
			return false;
		}

		return hasChildOperation(t, 0, Terminals.NAME_PI);
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory minusOneFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, -1.0d);

		return new TreeNode<>(minusOneFactory.build(inputSpec));
	});

	final public static Rule SIN_PI = ImmutableRule.of((t) -> {
		if (isOperation(t, Functions.NAME_SIN) == false) {
			return false;
		}

		return hasChildOperation(t, 0, Terminals.NAME_PI);
	}, (program, t) -> {

		final InputSpec inputSpec = program.inputSpec();

		final OperationFactory zeroFactory = OperationFactories
				.ofCoefficient(Terminals.TYPE_COEFFICIENT, Double.class, -0.0d);

		return new TreeNode<>(zeroFactory.build(inputSpec));
	});

	final public static List<Rule> SIMPLIFY_RULES = Arrays.asList(MUL_SAME_BRANCHES,
			ADD_TWO_COEFFCIENTS,
			MUL_TWO_COEFFICIENTS,
			SUB_TWO_COEFFICIENTS,
			SUB_INPUT_FROM_SAME_INPUT,
			SUB_ZERO_FROM_INPUT,
			DIV_TWO_COEFFICIENT_FINITE,
			ADD_INPUT_TO_SAME_INPUT,
			MULTIPLY_INPUT_WITH_SAME_INPUT,
			MUL_1_WITH_ANYTHING,
			MUL_ANYTHING_WITH_1,
			MUL_0_WITH_ANYTHING,
			MUL_ANYTHING_WITH_0,
			POW_0,
			POW_1,
			COS_OF_COEFFICIENT,
			SIN_OF_COEFFICIENT,
			SUB_SAME_BRANCHES,
			ADD_SAME_BRANCHES,
			DIV_SAME_BRANCHES,
			EXP_OF_COEFFICIENT,
			POW_TWO_COEFFICIENTS,
			ADD_0_WITH_ANYTHING,
			ADD_ANYTHING_WITH_0,
			COS_PI,
			SIN_PI);

}