Package wyautl.rw

Source Code of wyautl.rw.IterativeRewriter

// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//    * Neither the name of the <organization> nor the
//      names of its contributors may be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL DAVID J. PEARCE BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package wyautl.rw;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import wyautl.core.Automata;
import wyautl.core.Automaton;
import wyautl.core.Schema;
import wyautl.core.Automaton.State;
import wyautl.rw.IterativeRewriter.Result;

/**
* An implementation of <code>wyrl.rw.Rewriter</code> which utilises the
* strategy pattern to support different rule selection heuristics.
* Specifically, it is parameterised by two strategies which (respectively)
* control the selection of inference rules and reduction rules.
*
* @author David J. Pearce
*
*/
public final class IterativeRewriter implements Rewriter {

  public enum Result {
    TRUE,FALSE,TIMEOUT
  }

  /**
   * The schema used by automata being reduced. This is primarily useful for
   * debugging purposes.
   */
  protected final Schema schema;

  /**
   * Used to count the number of unsuccessful inferences (i.e. those
   * successful inference rule activations which did not result in a changed
   * automaton after reduction).
   */
  private int numInferenceFailures;

  /**
   * Used to count the number of successful inferences (i.e. those successful
   * inference rule activations which did result in a changed automaton after
   * reduction).
   */
  private int numInferenceSuccesses;

  /**
   * Used to count the total number of activations made for inference rules.
   */
  private int numInferenceActivations;

  /**
   * Used to count the number of unsuccessful reductions (i.e. those
   * successful reduction rule activations which did not result in a changed
   * automaton after reduction).
   */
  private int numReductionFailures;

  /**
   * Used to count the number of successful reductions (i.e. those successful
   * reduction rule activations which did result in a changed automaton after
   * reduction).
   */
  private int numReductionSuccesses;

  /**
   * Used to count the total number of activations made for reduction rules.
   */
  private int numReductionActivations;

  /**
   * The inference strategy controls the order in which inference rules are
   * applied. This has a useful effect on performance, though it is currently
   * unclear which strategies are best.
   */
  protected final IterativeRewriter.Strategy<InferenceRule> inferenceStrategy;

  /**
   * The reduction strategy controls the order in which reduction rules are
   * applied. This has a significant effect on performance, though it is
   * currently unclear which strategies are best.
   */
  protected final IterativeRewriter.Strategy<ReductionRule> reductionStrategy;

  /**
   * The automaton being rewritten by this rewriter.
   */
  protected final Automaton automaton;

  /**
   * This is used to maintain information about which states in the current
   * automaton are reachable. This is necessary to ensure that rewrites are
   * not applied to multiple states more than once (as this can cause infinite
   * loops).
   */
  private boolean[] reachable;

  /**
   * The oneStepUndo provides a mapping from new automaton states to their
   * original states during a reduction. Using this map, every unreachable
   * state can be returned to its original form in "one step". In particular,
   * the oneStepUndo function maps reachable states above the pivot to
   * unreachable states below the pivot.
   */
  private int[] oneStepUndo;

  private int maxInferenceSteps = 100;

  private int maxReductionSteps = 500;

  /**
   * Construct a simple rewriter for a given automaton which uses the given
   * strategies for selecting inference and reduction tool.
   *
   * @param automaton
   *            Automaton to be rewritten
   * @param inferenceStrategy
   *            Strategy to use for selecting inference rules
   * @param reductionStrategy
   *            Strategy to use for selecting reduction rules
   * @param schema
   *            Schema used by automaton, which only used for debugging
   *            purposes.
   */
  public IterativeRewriter(Automaton automaton,
      IterativeRewriter.Strategy<InferenceRule> inferenceStrategy,
      IterativeRewriter.Strategy<ReductionRule> reductionStrategy, Schema schema) {
    this.automaton = automaton;
    this.schema = schema;
    this.reachable = new boolean[automaton.nStates() * 2];
    this.oneStepUndo = new int[automaton.nStates() * 2];
    this.inferenceStrategy = inferenceStrategy;
    this.reductionStrategy = reductionStrategy;
  }

  public void setMaxInferenceSteps(int maxInhaleSteps) {
    this.maxInferenceSteps = maxInhaleSteps;
  }

  public void setMaxReductionSteps(int maxExhaleSteps) {
    this.maxReductionSteps = maxExhaleSteps;
  }

  @Override
  public final boolean apply() {
    // First, make sure the automaton is minimised and compacted.
    automaton.minimise();
    automaton.compact();

    // Need to update the reachability and undo information here: (1) after
    // a successful inference application; (2) the first time this is called
    // prior to any inference activations.
    reachable = updateReachable(automaton, reachable);

    // Now, perform initial reduction to ensure everything is compact as
    // possible.
    reduce(Automaton.K_VOID,Automaton.K_VOID,0);

    return infer();
  }

  private final boolean infer() {
    Activation activation;
    int step = 0;

    // Reset the strategy to ensure that we're starting fresh
    inferenceStrategy.reset();

    // Now, continue applying reductions until no more left.
    while (step < maxInferenceSteps && (activation = inferenceStrategy.next(reachable)) != null) {
      // Apply the activation
      numInferenceActivations++;

      int pivot = automaton.nStates();
      int from = activation.root();
      int target = activation.apply(automaton);

      if (target != Automaton.K_VOID && from != target) {

//        System.out.println("\nAUTOMATON(BEFORE): ");
        //wyrl.util.Runtime.debug(automaton, schema, "And","Or");
        // Update reachability status for nodes affected by this
        // activation. This is because such states could cause
        // an infinite loop of re-activations. More specifically, where
        // we activate on a state and rewrite it, but then it remains
        // and so we repeat.
        reachable = updateReachable(automaton, reachable);

        Result r = reduce(from,target,pivot);

        //System.out.println("\nAUTOMATON(AFTER): " + automaton);

        if(r == Result.TIMEOUT) {
          // Reduction timeout
          return false;
        } else if(r == Result.TRUE) {

//          System.out.println("*** INFERRED: " + activation.rule.name()
//              + ", " + activation.rule.getClass().getName() + " :: "
//              + activation.root() + " => " + target + " (" + automaton.nStates() + ")");

          //wyrl.util.Runtime.debug(automaton, schema, "And","Or");
          // Reset the strategy for the next time we use it.
          inferenceStrategy.reset();
          numInferenceSuccesses++;
        } else {
          numInferenceFailures++;
        }

        step = step + 1;

      } else {
        // In this case, the activation failed so we simply
        // continue on to try another activation.
        numInferenceActivations++;
      }
    }

    return step != maxInferenceSteps;
  }

  /**
   * <p>
   * Reduce the states of a given automaton as much as possible. The pivot
   * point indicates the portion of the automaton which is "new" (i.e. above
   * the pivot) versus that which is "old" (i.e. below the pivot). States
   * above the pivot are those which must be reduced, whilst those below the
   * pivot are (mostly) already fully reduced (and therefore do not need
   * further reducing). However, there may be states in the old region which
   * have changed (e.g. after an <code>Automaton.rewrite()</code> has been
   * applied) and should be reduce. See #382 for more on this process.
   * </p>
   *
   * <p>
   * This function is used primarily during the application of an inference
   * rule. An important aspect of this is that the function must indicate
   * whether or not <i>the original automaton was left after reduction</i>.
   * That is when, after reduction, all states above the <code>pivot</code>
   * have been eliminated, but no state below the pivot has. This indicates
   * that the new states introduced by the inference rule were reduced away
   * leaving an automaton identical to before the rule was applied. When this
   * happens, the inference rule has not been successfully applied and we
   * should continue to search for other rules which can be applied.
   * </p>
   *
   * <p>
   * The generally accepted strategy for checking whether the original
   * automaton remains is as follows: firstly, reductions are applied to all
   * states; secondly, reductions are applied only to reachable states (to
   * prevent against the continued re-application of a reduction rule);
   * thirdly, when the fixed-point is reached, the automaton is fully
   * compacted. If during the final compaction, any state below the pivot
   * becomes unreachable, then the original automaton was not retained;
   * likewise, if after compaction the number of states exceeds the pivot,
   * then it was not retained either.
   * </p>
   *
   * @param automaton
   *            The automaton to be reduced.
   * @param pivot
   *            The pivot point for the reduction. All states above this
   *            (including the pivot index itself) are eligible for reduction;
   *            all those below are not.
   * @param from
   *            Automaton state which was been rewritten from.
   * @param to
   *            Automaton state which was been rewritten to.
   * @return True if the original automaton was not retained (i.e. if some new
   *         information has been generated).
   */
  private final Result reduce(int from, int to, int pivot) {

    initOneStepUndo();

    if(from != Automaton.K_VOID) {
      oneStepUndo = applyUndo(oneStepUndo,from,to,pivot);
    }

    Activation activation;
    int step = 0;

    // Reset the reduction strategy to ensure we're fresh
    reductionStrategy.reset();

    // Now, continue applying reductions until no more left.
    while (step < maxReductionSteps && (activation = reductionStrategy.next(reachable)) != null) {
      // Apply the activation
      numReductionActivations++;

      from = activation.root();
      int target = activation.apply(automaton);

      if (target != Automaton.K_VOID && from != target) {

//        System.out.println("*** REDUCED: " + activation.rule.name()
//            + ", " + activation.rule.getClass().getName() + " :: "
//            + activation.root() + " => " + target + " (" + automaton.nStates() + ")");

        // Update reachability status for nodes affected by this
        // activation. This is because such states could cause
        // an infinite loop of re-activations. More specifically, where
        // we activate on a state and rewrite it, but then it remains
        // and so we repeat.
        reachable = updateReachable(automaton, reachable);

        // Revert all states below the pivot which are now unreachable.
        // This is essential to ensuring that the automaton will return
        // to its original state iff it is the unchanged. This must be
        // applied before compaction.
        oneStepUndo = applyUndo(oneStepUndo,activation.root(), target, pivot);

        // Compact all states above the pivot to eliminate unreachable
        // states and prevent the automaton from growing continually.
        // This is possible because automton.rewrite() can introduce
        // null states into the automaton.
        compact(automaton, pivot, reachable, oneStepUndo);

        //assertValidOneStepUndo(oneStepUndo,pivot);

        // Reset the strategy for the next time we use it.
        reductionStrategy.reset();
        numReductionSuccesses++;
        step = step + 1;
      } else {
        // In this case, the activation failed so we simply
        // continue on to try another activation.
        numReductionFailures++;
      }
    }

    //printAutomatonStats(automaton);

    if(step == maxReductionSteps) {
      return Result.TIMEOUT;
    } else if(completed(oneStepUndo,pivot)) {
      return Result.FALSE;
    } else {
      return Result.TRUE;
    }
  }

  /**
   * Complete the reduction process. This is the most difficult aspect of the
   * process because this must efficiently and precisely determine whether or
   * not the automaton has changed.
   *
   * @param pivot
   * @return
   */
  private final boolean completed(int[] oneStepUndo, int pivot) {
    // Exploit reachability information to determine how many states remain
    // above the pivot, and how many free states there are below the pivot.
    // An invariant is that if countAbove == 0 then countBelow == 0. In the
    // case that no new states remain (i.e. countAbove == 0) then we know
    // the automaton has not changed.

    int countBelow = 0;
    for (int i = 0; i < pivot; ++i) {
      if (reachable[i]) {
        countBelow++;
      }
    }
    int countAbove = 0;
    for (int i = pivot; i < automaton.nStates(); ++i) {
      if (reachable[i]) {
        countAbove++;
      }
    }

    //System.out.println("\n *** ABOVE = " + countAbove + ", BELOW = " + countBelow + ", PIVOT = " + pivot);

    // Finally, determine whether the automaton has actually changed or not.
    if (countAbove == 0 && countBelow == pivot) {
      // Indicates no reachable states remain above the pivot and, hence,
      // the automaton has not changed. We must now eliminate these states
      // to ensure the automaton remains identical as before.

      automaton.resize(pivot);

      return true;
    } else {
      // Otherwise, the automaton has definitely changed. Therefore, we
      // compact the automaton down by eliminating all unreachable states.
      compact(automaton, 0, reachable, oneStepUndo);

      return false;
    }
  }

  /**
   * Update the reachability information associated with the automaton after
   * some change has occurred. This information is currently recomputed from
   * scratch, though in principle it could be updated incrementally.
   */
  private static boolean[] updateReachable(Automaton automaton,
      boolean[] reachable) {

    // TODO: update reachability information incrementally

    if (reachable.length < automaton.nStates()) {
      reachable = new boolean[automaton.nStates() * 2];
    } else {
      Arrays.fill(reachable, false);
    }
    // first, visit all nodes
    for (int i = 0; i != automaton.nRoots(); ++i) {
      int root = automaton.getRoot(i);
      if (root >= 0) {
        findReachable(automaton, reachable, root);
      }
    }

    return reachable;
  }

  /**
   * The purpose of this method is to ensure that states below the pivot which
   * are now unreachable (if any) are reverted to their original state. This
   * is import to ensure that the automaton will always return to its original
   * state iff it is equivalent.
   *
   * @param activation
   * @param pivot
   */
  private int[] applyUndo(int[] oneStepUndo, int from, int to, int pivot) {
    // Update the oneStepUndo map with the new information.
    int nStates = automaton.nStates();
    int oStates = oneStepUndo.length;

    // First, ensure enough memory allocated for undo function.
    if (oStates < nStates) {
      // First, copy and update undo information
      int[] tmpUndo = new int[nStates * 2];
      System.arraycopy(oneStepUndo, 0, tmpUndo, 0, oStates);
      for (int i = oStates; i != tmpUndo.length; ++i) {
        tmpUndo[i] = i;
      }
      oneStepUndo = tmpUndo;
    }

    // Second, apply the oneStepUndo map to all unreachable vertices
    boolean changed = false;
    for (int i = 0; i != pivot; ++i) {
      if (!reachable[i]) {
        State state = automaton.get(i);
        if (state != null) {
          changed |= state.remap(oneStepUndo);
        }
      }
    }

    // Third, minimise automaton (if applicable)
    if(changed) {
      // At this point, the automaton is not necessarily minimised and,
      // hence, we must minimise it.
      automaton.minimise();
      reachable = updateReachable(automaton, reachable);
    }

    // Finally, update the oneStepUndo information. This has to be done last
    // since unreachable states to utilise the previous oneStepUndo
    // information.  See #382.
    if (to >= pivot) {
      if(from < pivot) {
        // In this case, we need to initialise the oneStepUndo
        // information.
        oneStepUndo[to] = from;
      } else if (from != oneStepUndo[from]){
        // In this case, we need to transfer the oneStepUndo
        // information.
        oneStepUndo[to] = oneStepUndo[from];
        // Reset undo information.
        oneStepUndo[from] = from;
      }
    }

    return oneStepUndo;
  }


  /**
   * Initialise the oneStepUndo map by assigning every state to itself, and
   * ensuring that enough space was allocated.
   */
  private int[] initOneStepUndo() {
    int nStates = automaton.nStates();

    // Ensure capacity for undo and binding space
    if(oneStepUndo.length < nStates) {
      oneStepUndo = new int[nStates * 2];
    }

    // Initialise undo information
    for (int i = 0; i != oneStepUndo.length; ++i) {
      oneStepUndo[i] = i;
    }

    return oneStepUndo;
  }

  /**
   * Visit all states reachable from a given starting state in the given
   * automaton. In doing this, states which are visited are marked and,
   * furthermore, those which are "headers" are additionally identified. A
   * header state is one which is the target of a back-edge in the directed
   * graph reachable from the start state.
   *
   * @param automaton
   *            --- automaton to traverse.
   * @param reachable
   *            --- states marked with false are those which have not been
   *            visited.
   * @param index
   *            --- state to begin traversal from.
   * @return
   */
  public static void findReachable(Automaton automaton, boolean[] reachable,
      int index) {
    if (index < 0) {
      return;
    } else if (reachable[index]) {
      // Already visited, so terminate here
      return;
    } else {
      // Not previously visited, so mark now and traverse any children
      reachable[index] = true;
      Automaton.State state = automaton.get(index);
      if (state instanceof Automaton.Term) {
        Automaton.Term term = (Automaton.Term) state;
        if (term.contents != Automaton.K_VOID) {
          findReachable(automaton, reachable, term.contents);
        }
      } else if (state instanceof Automaton.Collection) {
        Automaton.Collection compound = (Automaton.Collection) state;
        for (int i = 0; i != compound.size(); ++i) {
          findReachable(automaton, reachable, compound.get(i));
        }
      }
    }
  }

  private static void compact(Automaton automaton, int pivot,
      boolean[] reachable, int[] oneStepUndo) {
    int nStates = automaton.nStates();
    int nRoots = automaton.nRoots();
    int[] binding = new int[nStates];

    // First, initialise binding for all states upto start state. This
    // ensure that they are subsequently mapped to themselves.
    for (int i = 0; i < nStates; ++i) {
      binding[i] = i;
    }

    // Second, go through and eliminate all unreachable states and compact
    // the automaton down, whilst updating reachable one oneStepUndo
    // information accordingly.
    int j = pivot;

    for (int i = pivot; i < nStates; ++i) {
      if (reachable[i]) {
        State ith = automaton.get(i);
        binding[i] = j;
        reachable[i] = false;
        reachable[j] = true;
        oneStepUndo[j] = oneStepUndo[i];
        automaton.set(j++, ith);
      }
    }

    if (j < nStates) {
      // Update the oneStepUndo relation to ensure it remains
      // sound. The invariant it maintains is that all states above the
      // pivot map to themselves or to a state below the pivot.
      for(int i=j;i<nStates;++i) {
        oneStepUndo[i] = i;
      }

      // Ok, some compaction actually occurred; therefore follow through
      // and update all states accordingly.
      nStates = j;
      automaton.resize(nStates); // will nullify all deleted states

      // Update mapping and oneStepUndo for *all* states
      for (int i = 0; i != nStates; ++i) {
        Automaton.State state = automaton.get(i);
        if(state != null) {
          state.remap(binding);
        }
        oneStepUndo[i] = binding[oneStepUndo[i]];
      }

      // Update mapping for all roots
      for (int i = 0; i != nRoots; ++i) {
        int root = automaton.getRoot(i);
        if (root >= 0) {
          automaton.setRoot(i, binding[root]);
        }
      }
    }
  }

  public void printAutomatonStats(Automaton automaton) {
    HashMap<Integer,Integer> data = new HashMap<Integer,Integer>();

    for(int i=0;i!=automaton.nStates();++i) {
      if(reachable[i]) {
        Automaton.State state = automaton.get(i);
        Integer value = data.get(state.kind);
        if(value == null) {
          value = 1;
        } else {
          value = value + 1;
        }
        data.put(state.kind,value);
      }
    }

    boolean firstTime = true;
    for (Map.Entry<Integer, Integer> e : data.entrySet()) {
      if(!firstTime) {
        System.out.print(", ");
      }
      firstTime=false;
      int kind = e.getKey();
      if(kind >= 0) {
        System.out.print(schema.get(kind).name + " => "
            + e.getValue());
      } else {
        String key = null;
        switch(kind) {
        case Automaton.K_BOOL:
          key = "bool";
          break;
        case Automaton.K_INT:
          key = "int";
          break;
        case Automaton.K_REAL:
          key = "real";
          break;
        case Automaton.K_STRING:
          key = "string";
          break;
        case Automaton.K_SET:
          key = "set";
          break;
        case Automaton.K_BAG:
          key = "bag";
          break;
        case Automaton.K_LIST:
          key = "list";
          break;

        }
        System.out.print(key + " => "
            + e.getValue());
      }
    }

    System.out.println();
  }

  /**
   * A useful helper function for enforcing the oneStepUndo invariants. In
   * particular, that the oneStepUndo array maps reachable vertices which are
   * above the pivot to vertices below the pivot. All other vertices map to
   * themselves.
   *
   * @param oneStepUndo
   */
  private final void assertValidOneStepUndo(int[] oneStepUndo, int pivot) {
    for (int i = 0; i != automaton.nStates(); ++i) {
      if (reachable[i]) {
        int j = oneStepUndo[i];
        if (j != i) {
          if (i < pivot) {
            throw new RuntimeException(
                "Invalid One Step Undo (something below pivot considerered)");
          } else if (j >= pivot) {
            throw new RuntimeException(
                "Invalid One Step Undo (something above pivot mapping to above pivot)");
          }
        }
      }
    }
  }


  @Override
  public Rewriter.Stats getStats() {
    int numProbes = inferenceStrategy.numProbes()
        + reductionStrategy.numProbes();
    return new Stats(numProbes, numReductionActivations,
        numReductionFailures, numReductionSuccesses,
        numInferenceActivations, numInferenceFailures,
        numInferenceSuccesses);
  }

  @Override
  public void resetStats() {
    this.numReductionActivations = 0;
    this.numReductionFailures = 0;
    this.numReductionSuccesses = 0;
    this.numInferenceActivations = 0;
    this.numInferenceFailures = 0;
    this.numInferenceSuccesses = 0;
  }


  public static abstract class Strategy<T extends RewriteRule> {

    /**
     * Get the next activation according to this strategy, or null if none
     * available.
     *
     * @return
     */
    protected abstract Activation next(boolean[] reachable);

    /**
     * Reset strategy so that all reachable states and rewrite rules will be
     * considered again.
     */
    protected abstract void reset();

    /**
     * Return the number of probes performed by this strategy.
     *
     * @return
     */
    protected abstract int numProbes();
  }
}
TOP

Related Classes of wyautl.rw.IterativeRewriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.