/* * Copyright 2015 S. Webber * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.oakgp; import static org.oakgp.Arguments.createArguments; import static org.oakgp.node.NodeType.isConstant; import static org.oakgp.node.NodeType.isFunction; import java.util.HashSet; import java.util.Set; import org.oakgp.node.ConstantNode; import org.oakgp.node.FunctionNode; import org.oakgp.node.Node; /** * Attempts to reduce the size of tree structures without altering their functionality. * <p> * This can be done by replacing expressions with constant values or removing redundant branches. e.g. The expression: * * <pre> * (+ 7 (* 3 6)) * </pre> * * can be simplified to the value: * * <pre> * 25 * </pre> * * <b>Note:</b> relies on {@link org.oakgp.function.Function#isPure()} to identify if a function is referentially transparent and therefore suitable for * replacement with the result of evaluating it. */ public final class NodeSimplifier { private static final int MAX_RETRIES = 100; /** Private constructor as all methods are static. */ private NodeSimplifier() { // do nothing } /** * Attempts to reduce the size of the specified tree structures without altering its functionality. * <p> * Simplification can occur by replacing expressions with constant values (e.g. replacing {@code (+ 1 1)} with {@code 2}) or removing redundant branches * (e.g. replacing {@code (if (< 2 3) (+ v0 v1) (* v0 v1)) with {@code (+ v0 v1)}. * * @param input * the node to attempt to simplify. * @return the result of attempting to simplify {@code input}. * @see org.oakgp.function.Function#simplify(Arguments) */ public static Node simplify(Node input) { int ctr = 0; Set<Node> s = new HashSet<>(); Node previous; Node output = input; do { previous = output; output = simplifyOnce(output); // To avoid getting stuck in an infinite loop: // 1. exit if the result of an attempt to simplify equals the result of an earlier simplify if (!output.equals(previous) && !s.add(output)) { return output; } // 2. if the number of simplifies exceeds a defined limit then throw an exception if (ctr++ > MAX_RETRIES) { throw new IllegalArgumentException(input.toString()); } } while (isFunction(output) && !output.equals(previous)); return output; } private static Node simplifyOnce(Node input) { if (isFunction(input)) { return simplifyFunctionNode((FunctionNode) input); } else { return input; } } private static Node simplifyFunctionNode(final FunctionNode input) { // TODO it may be beneficial to add a "isSimplified" flag to FunctionNode to indicate that if it has already been simplified (to avoid trying again here) // try to simplify each of the arguments Arguments inputArgs = input.getArguments(); Node[] simplifiedArgs = new Node[inputArgs.getArgCount()]; boolean haveAnyArgumentsBeenSimplified = false; boolean areAllArgumentsConstants = true; for (int i = 0; i < simplifiedArgs.length; i++) { Node originalArg = inputArgs.getArg(i); simplifiedArgs[i] = simplifyOnce(originalArg); if (originalArg != simplifiedArgs[i]) { haveAnyArgumentsBeenSimplified = true; } if (!isConstant(simplifiedArgs[i])) { areAllArgumentsConstants = false; } } // if could simplify arguments then use simplified version to create new FunctionNode Arguments arguments; FunctionNode output; if (haveAnyArgumentsBeenSimplified) { arguments = createArguments(simplifiedArgs); output = new FunctionNode(input.getFunction(), arguments); } else { arguments = inputArgs; output = input; } // if a function is pure and all its arguments are constants then // the result of evaluating it will always be the same - // so, to avoid unnecessary computation and to reduce bloat, // the function node can be replaced with the result of evaluating it if (areAllArgumentsConstants && input.getFunction().isPure()) { return new ConstantNode(output.evaluate(null), output.getType()); } // try to simplify using function specific logic Node simplifiedByFunctionVersion = input.getFunction().simplify(arguments); if (simplifiedByFunctionVersion == null) { return output; } else { return simplifiedByFunctionVersion; } } }