/** Construct the parallel composition of two PTAs. */
  public PTA compose(PTA pta1, PTA pta2) {
    Set<String> alpha1, alpha2, alpha1only, alpha2only, sync;
    Transition transition;
    Edge edge;
    double prob;
    IndexPair state;
    int src, dest;

    // Store PTAs locally and create new one to store parallel composition
    this.pta1 = pta1;
    this.pta2 = pta2;
    par = new PTA();

    // New set of clocks is union of sets for two PTAs
    for (String s : pta1.clockNames) {
      par.getOrAddClock(s);
    }
    for (String s : pta2.clockNames) {
      par.getOrAddClock(s);
    }

    // Get alphabets, compute intersection etc.
    alpha1 = pta1.getAlphabet();
    alpha2 = pta2.getAlphabet();
    // System.out.println("alpha1: " + alpha1);
    // System.out.println("alpha2: " + alpha2);
    sync = new LinkedHashSet<String>();
    alpha1only = new LinkedHashSet<String>();
    alpha2only = new LinkedHashSet<String>();
    for (String a : alpha1) {
      if (!("".equals(a)) && alpha2.contains(a)) {
        sync.add(a);
      } else {
        alpha1only.add(a);
      }
    }
    for (String a : alpha2) {
      if (!alpha1.contains(a)) {
        alpha2only.add(a);
      }
    }
    // Explicitly add tau to action lists
    alpha1only.add("");
    alpha2only.add("");
    // System.out.println("alpha1only: " + alpha1only);
    // System.out.println("alpha2only: " + alpha2only);
    // System.out.println("sync: " + sync);

    // Initialise states storage
    states = new IndexedSet<IndexPair>();
    explore = new LinkedList<IndexPair>();
    // Add initial location
    addState(0, 0);
    src = -1;
    while (!explore.isEmpty()) {
      // Pick next state to explore
      // (they are stored in order found so know index is src+1)
      state = explore.removeFirst();
      src++;
      // Go through asynchronous transitions of PTA 1
      for (String a : alpha1only) {
        for (Transition transition1 : pta1.getTransitionsByAction(state.i1, a)) {
          // Create new transition
          transition = par.addTransition(src, a);
          // Copy guard
          for (Constraint c : transition1.getGuardConstraints())
            transition.addGuardConstraint(c.deepCopy().renameClocks(pta1, par));
          // Combine edges
          for (Edge edge1 : transition1.getEdges()) {
            prob = edge1.getProbability();
            dest = addState(edge1.getDestination(), state.i2);
            edge = transition.addEdge(prob, dest);
            // Copy resets
            for (Map.Entry<Integer, Integer> e : edge1.getResets()) {
              edge.addReset(PTA.renameClock(pta1, par, e.getKey()), e.getValue());
            }
          }
        }
      }
      // Go through asynchronous transitions of PTA 2
      for (String a : alpha2only) {
        for (Transition transition2 : pta2.getTransitionsByAction(state.i2, a)) {
          // Create new transition
          transition = par.addTransition(src, a);
          // Copy guard
          for (Constraint c : transition2.getGuardConstraints())
            transition.addGuardConstraint(c.deepCopy().renameClocks(pta2, par));
          // Combine edges
          for (Edge edge2 : transition2.getEdges()) {
            prob = edge2.getProbability();
            dest = addState(state.i1, edge2.getDestination());
            edge = transition.addEdge(prob, dest);
            // Copy resets
            for (Map.Entry<Integer, Integer> e : edge2.getResets()) {
              edge.addReset(PTA.renameClock(pta2, par, e.getKey()), e.getValue());
            }
          }
        }
      }
      // Go through synchronous transitions
      for (String a : sync) {
        for (Transition transition1 : pta1.getTransitionsByAction(state.i1, a)) {
          for (Transition transition2 : pta2.getTransitionsByAction(state.i2, a)) {
            // Create new transition
            transition = par.addTransition(src, a);
            // Guard is conjunction of guards
            for (Constraint c : transition1.getGuardConstraints())
              transition.addGuardConstraint(c.deepCopy().renameClocks(pta1, par));
            for (Constraint c : transition2.getGuardConstraints())
              transition.addGuardConstraint(c.deepCopy().renameClocks(pta2, par));
            // Combine edges
            for (Edge edge1 : transition1.getEdges()) {
              for (Edge edge2 : transition2.getEdges()) {
                prob = edge1.getProbability() * edge2.getProbability();
                dest = addState(edge1.getDestination(), edge2.getDestination());
                edge = transition.addEdge(prob, dest);
                // Reset set is union of reset sets
                for (Map.Entry<Integer, Integer> e : edge1.getResets()) {
                  edge.addReset(PTA.renameClock(pta1, par, e.getKey()), e.getValue());
                }
                for (Map.Entry<Integer, Integer> e : edge2.getResets()) {
                  edge.addReset(PTA.renameClock(pta2, par, e.getKey()), e.getValue());
                }
              }
            }
          }
        }
      }
    }

    return par;
  }
  /** Build a time bounded reachability query into a PTA; return the new target location set. */
  private BitSet buildTimeBoundIntoPta(
      PTA pta, BitSet targetLocs, int timeBound, boolean timeBoundStrict) {
    String timerClock = null;
    int timerClockIndex, numLocs, newTargetLoc;
    String newTargetLocString;
    List<Transition> trNewList;
    Transition trNew;
    BitSet targetLocsNew;
    boolean toTarget;
    int i;

    // Add a timer clock
    timerClock = "time";
    while (pta.getClockIndex(timerClock) != -1) timerClock += "_";
    timerClockIndex = pta.addClock(timerClock);
    // Add a new target location
    numLocs = pta.getNumLocations();
    newTargetLocString = "target";
    while (pta.getLocationIndex(newTargetLocString) != -1) newTargetLocString += "_";
    newTargetLoc = pta.addLocation(newTargetLocString);
    // Go through old (on-target) locations
    for (i = 0; i < numLocs; i++) {
      trNewList = new ArrayList<Transition>();
      for (Transition tr : pta.getTransitions(i)) {
        // See if the transition can go to a target location
        toTarget = false;
        for (Edge e : tr.getEdges()) {
          if (targetLocs.get(e.getDestination())) {
            toTarget = true;
            break;
          }
        }
        // Copy transition, modify edges going to target and add guard
        if (toTarget) {
          trNew = new Transition(tr);
          for (Edge e : trNew.getEdges()) {
            if (targetLocs.get(e.getDestination())) {
              e.setDestination(newTargetLoc);
            }
          }
          if (timeBoundStrict)
            trNew.addGuardConstraint(Constraint.buildLt(timerClockIndex, timeBound));
          else trNew.addGuardConstraint(Constraint.buildLeq(timerClockIndex, timeBound));
          trNewList.add(trNew);
          // Modify guard of copied transition
          if (timeBoundStrict)
            tr.addGuardConstraint(Constraint.buildGeq(timerClockIndex, timeBound));
          else tr.addGuardConstraint(Constraint.buildGt(timerClockIndex, timeBound));
        }
      }
      // Add new transitions to PTA
      for (Transition tr : trNewList) {
        pta.addTransition(tr);
      }
    }
    // Re-generate set of target locations
    targetLocsNew = new BitSet(pta.getNumLocations());
    targetLocsNew.set(newTargetLoc);

    return targetLocsNew;
  }