/**
   * Compute invariants of a graph g by mining invariants from the transitive closure using {@code
   * extractInvariantsUsingTC}, which returns an over-approximation of the invariants that hold
   * (i.e. it may return invariants that do not hold, but may not fail to return an invariant that
   * does not hold)
   *
   * @param g the graph of nodes of type LogEvent
   * @param mineConcurrencyInvariants whether or not to also mine concurrency invariants
   * @return the set of temporal invariants the graph satisfies
   */
  public TemporalInvariantSet computeTransClosureInvariants(
      TraceGraph<?> g, boolean mineConcurrencyInvariants) {

    TimedTask mineInvariants = PerformanceMetrics.createTask("mineInvariants", false);
    Set<ITemporalInvariant> overapproximatedInvariantsSet;

    AbstractMain main = AbstractMain.getInstance();

    // Compute the over-approximated set of invariants for the input graph.
    try {

      TimedTask itc = PerformanceMetrics.createTask("invariants_transitive_closure", false);

      // Compute the transitive closure.
      AllRelationsTransitiveClosure transitiveClosure = new AllRelationsTransitiveClosure(g);

      // Get the over-approximation.
      itc.stop();
      if (main.options.doBenchmarking) {
        logger.info("BENCHM: " + itc);
      }
      TimedTask io = PerformanceMetrics.createTask("invariants_approximation", false);

      // Extract invariants for all relations, iteratively. Since we are
      // not considering invariants over multiple relations, this is
      // sufficient.
      overapproximatedInvariantsSet = new LinkedHashSet<ITemporalInvariant>();
      for (String relation : g.getRelations()) {
        overapproximatedInvariantsSet.addAll(
            extractInvariantsFromTC(
                g, transitiveClosure.get(relation), relation, mineConcurrencyInvariants));
      }

      io.stop();
      if (main.options.doBenchmarking) {
        logger.info("BENCHM: " + io);
      }
      // logger.info("Over-approx set: "
      // + overapproximatedInvariantsSet.toString());
    } finally {
      mineInvariants.stop();
    }

    return new TemporalInvariantSet(overapproximatedInvariantsSet);
  }
 public AllRelationsTransitiveClosure(TraceGraph<?> g) {
   for (String relation : g.getRelations()) {
     tcs.put(relation, g.getTransitiveClosure(relation));
   }
 }
  /**
   * Extract an over-approximated set of invariants from the transitive closure {@code tc} of the
   * graph {@code g}.
   *
   * @param g the graph over LogEvent
   * @param tc the transitive closure (of {@code g}) from which to mine invariants
   * @param relation the relation to consider for the invariants
   * @return the over-approximated set of invariants
   * @throws Exception
   */
  private Set<ITemporalInvariant> extractInvariantsFromTC(
      TraceGraph<?> g, TransitiveClosure tc, String relation, boolean mineConcurrencyInvariants) {

    // This maintains the mapping from event type to a map of trace ids ->
    // list of event instances in the trace. This is used to check
    // reachability only between event instances that are in the same trace.
    Map<EventType, Map<Integer, List<EventNode>>> etypeToTraceIdToENode =
        new LinkedHashMap<EventType, Map<Integer, List<EventNode>>>();

    // Initialize the partitions map: each unique label maps to a list of
    // nodes with that label.
    for (EventNode node : g.getNodes()) {
      if (node.getEType().isSpecialEventType()) {
        /**
         * The inclusion of INITIAL and TERMINAL states in the graphs generates the following types
         * of "tautological" invariants (for all event types X):
         *
         * <pre>
         * - x AP TERMINAL
         * - INITIAL AP x
         * - x AP TERMINAL
         * - x AFby TERMINAL
         * - TERMINAL NFby INITIAL
         * </pre>
         *
         * <p>NOTE: x AP TERMINAL is not actually a tautological invariant, but we do not
         * mine/include it because we instead use x AFby INITIAL.
         *
         * <p>We filter these out by simply ignoring any temporal invariants of the form x INV y
         * where x or y in {INITIAL, TERMINAL}. This is useful because it relieves us from
         * checking/reporting invariants which are true for all graphs produced with typical
         * construction.
         */
        continue;
      }
      Map<Integer, List<EventNode>> map;
      EventType etype = node.getEType();
      if (!etypeToTraceIdToENode.containsKey(etype)) {
        map = new LinkedHashMap<Integer, List<EventNode>>();
        etypeToTraceIdToENode.put(etype, map);
      } else {
        map = etypeToTraceIdToENode.get(etype);
      }

      List<EventNode> list;
      int tid = node.getTraceID();
      if (!map.containsKey(tid)) {
        list = new LinkedList<EventNode>();
        map.put(tid, list);
      } else {
        list = map.get(tid);
      }
      list.add(node);
    }

    Set<ITemporalInvariant> pathInvs = new LinkedHashSet<ITemporalInvariant>();
    Set<ITemporalInvariant> neverConcurInvs = new LinkedHashSet<ITemporalInvariant>();
    Set<ITemporalInvariant> alwaysConcurInvs = new LinkedHashSet<ITemporalInvariant>();

    Set<Pair<EventType, EventType>> observedPairs = new LinkedHashSet<Pair<EventType, EventType>>();
    int numTraces = g.getNumTraces();
    for (Entry<EventType, Map<Integer, List<EventNode>>> e1Entry :
        etypeToTraceIdToENode.entrySet()) {
      EventType e1 = e1Entry.getKey();
      // ///////////////// Determine if "INITIAL AFby e1" is true
      // Check if an e1 node appeared in every trace, if yes then inv
      // true.
      if (e1Entry.getValue().keySet().size() == numTraces) {
        pathInvs.add(
            new AlwaysFollowedInvariant(
                StringEventType.newInitialStringEventType(), e1, Event.defTimeRelationStr));
      }
      // /////////////////

      for (Entry<EventType, Map<Integer, List<EventNode>>> e2Entry :
          etypeToTraceIdToENode.entrySet()) {
        EventType e2 = e2Entry.getKey();
        // If we have done (e1,e2) then do not do (e2,e1) because for
        // pair (e1,e2) we derive orderings and invariants for both
        // (e1,e2) and (e2,e1) -- this is done so that we can do correct
        // subsumption of invariants (and concurrency invariants require
        // symmetrical information).
        if (observedPairs.contains(new Pair<EventType, EventType>(e2, e1))) {
          continue;
        }
        observedPairs.add(new Pair<EventType, EventType>(e1, e2));

        // ///////////////////////////////
        // Derive the ordering summary between each instance of e1 and
        // every instance of e2.
        EventOrderingSummary E1orderE2 =
            summarizeOrderings(e1Entry.getValue(), e2Entry.getValue(), tc);
        // Do same for e2,e1.
        EventOrderingSummary E2orderE1 =
            summarizeOrderings(e2Entry.getValue(), e1Entry.getValue(), tc);
        // ///////////////////////////////

        // Whether or not never ordered invariant was added --
        // determines whether or not NFby invariants are added
        // below.
        boolean addedNeverOrdered = false;

        if (mineConcurrencyInvariants) {
          // Ignore local versions of alwaysOrdered and
          // neverOrdered since they are trivially true and false
          // respectively.
          if (!((DistEventType) e1)
              .getProcessName()
              .equals(((DistEventType) e2).getProcessName())) {
            // Because lack of order is symmetric, it doesn't matter
            // if we use E1orderE2.neverOrdered or
            // E2orderE1.neverOrdered.
            if (E1orderE2.neverOrdered) {
              alwaysConcurInvs.add(
                  new AlwaysConcurrentInvariant((DistEventType) e2, (DistEventType) e1, relation));
              addedNeverOrdered = true;
            }
            // Because complete order is symmetric, it doesn't
            // matter if we use E1orderE2.alwaysOrdered or
            // E2orderE1.alwaysOrdered.
            if (E1orderE2.alwaysOrdered
                && !E1orderE2.alwaysPrecedes
                && !E1orderE2.alwaysFollowedBy
                && !E2orderE1.alwaysPrecedes
                && !E2orderE1.alwaysFollowedBy) {
              neverConcurInvs.add(
                  new NeverConcurrentInvariant((DistEventType) e2, (DistEventType) e1, relation));
            }
          }
        }

        if (!addedNeverOrdered) {
          // Note that ACWith subsumes NFby, which is why we add NFby
          // if not(ACWith).
          if (E1orderE2.neverFollowedBy) {
            pathInvs.add(new NeverFollowedInvariant(e1, e2, relation));
          }
          if (E2orderE1.neverFollowedBy) {
            pathInvs.add(new NeverFollowedInvariant(e2, e1, relation));
          }
        }

        if (E1orderE2.alwaysFollowedBy) {
          pathInvs.add(new AlwaysFollowedInvariant(e1, e2, relation));
        }
        if (E2orderE1.alwaysFollowedBy) {
          pathInvs.add(new AlwaysFollowedInvariant(e2, e1, relation));
        }

        if (E1orderE2.alwaysPrecedes) {
          pathInvs.add(new AlwaysPrecedesInvariant(e2, e1, relation));
        }
        if (E2orderE1.alwaysPrecedes) {
          pathInvs.add(new AlwaysPrecedesInvariant(e1, e2, relation));
        }
      }
    }

    // Merge the concurrency and path invariant sets.
    pathInvs.addAll(neverConcurInvs);
    pathInvs.addAll(alwaysConcurInvs);
    return pathInvs;
  }