@@ -634,21 +634,41 @@ private final static boolean equivalent(Automaton automaton,
634
634
}
635
635
636
636
/**
637
+ * <p>
637
638
* Determine whether two bag states are equivalent. This is more challenging
638
- * than for either list or set states. As for sets we must identify that
639
- * every state in the first set has an equivalent state in the second set;
640
- * likewise, that every state in the second state has an equivalent state in
641
- * the first. However, we must also count the occurrences of a particular
642
- * state and its equivalents is the same in both as well.
639
+ * than for either list or set states. To do this safely, we need to
640
+ * determine whether there is a possible matching between the states of each
641
+ * bag. This turns out to be relatively tricky algorithmic problem which
642
+ * reduces to the problem of determining whether a perfect match exists in a
643
+ * bipartite graph.
644
+ * </p>
645
+ * <p>
646
+ * The algorithm presented here may be based on that of Kuhn, though I have
647
+ * so far failed to find a reference for it. The approach is to traverse the
648
+ * vertices in one bag from left to right. At each point, the algorithm
649
+ * maintains a perfect matching for all vertices seen thus far. Then, at
650
+ * each step, the goal is to extend this perfect matching by exactly two
651
+ * vertices. The extension is done greedily if possible (i.e. take any
652
+ * unmatched adjacent vertex if one exists). However, when we reach a
653
+ * situation where we cannot greedily match then we attempt to "reconfigure"
654
+ * the current match. This is done by searching for a reachable vertex
655
+ * adjacent to an unmatched vertex. In such case, we can immediately
656
+ * reconfigure the network and proceed to the next step.
657
+ * </p>
658
+ * <p>
659
+ * Unfortunately, the presence of negative states complicates the whole
660
+ * issue (as it often seems to do), and is one way in which this algorithm
661
+ * departs from the normal.
662
+ * </p>
643
663
*
644
664
* @param automaton
645
- * Automaton in which the two states reside
665
+ * --- Automaton in which the two states reside
646
666
* @param equivs
647
- * Binary equivalence matrix.
667
+ * --- Binary equivalence matrix.
648
668
* @param b1
649
- * First bag state
669
+ * --- First bag state
650
670
* @param b2
651
- * Second bag state
671
+ * --- Second bag state
652
672
* @return
653
673
*/
654
674
private final static boolean equivalent (Automaton automaton ,
@@ -666,49 +686,116 @@ private final static boolean equivalent(Automaton automaton,
666
686
667
687
int [] b1_children = b1 .children ;
668
688
int [] b2_children = b2 .children ;
669
-
689
+ // maps vertices in b2 to those they are matched against in b1
690
+ int [] matches = new int [b1_size ];
691
+ Arrays .fill (matches , -1 );
692
+ // Used to ensure no vertex is explored more than once during
693
+ // reconfiguration.
694
+ boolean [] visited = new boolean [b1_size ];
695
+
670
696
// For every state in s1
671
- for (int k = 0 ; k != b1_size ; ++k ) {
672
- int b1_child = b1_children [k ];
673
- int b1_count = 0 ;
674
- // First, count the number of equivalent states to this state in b1.
675
- // This is necessary so we can check the count is the same in b2.
676
- for (int l = 0 ; l != b1_size ; ++l ) {
677
- int b11_child = b1_children [l ];
678
- if (b1_child == b11_child
679
- || (b1_child >= 0 && b11_child >= 0 && equivs .get (
680
- b1_child , b11_child ))) {
681
- // TODO: We could current iteration of outer loop early
682
- // here, if first equivalent state is less than l. This
683
- // would mean we'd already checked this equivalence class.
684
- b1_count ++;
697
+ for (int i = 0 ; i != b1_size ; ++i ) {
698
+ int b1_child = b1_children [i ];
699
+ if (b1_child < 0 ) {
700
+ // In this case, we have to do something different.
701
+ if (!findNegativeMatch (i ,b1_children ,b2_children ,equivs ,matches )) {
702
+ return false ;
685
703
}
686
- }
687
- // Second, count the number of equivalent states to this in b2, such
688
- // that we can ensure the count is the same in both b1 and b2.
689
- int b2_count = 0 ;
690
- for (int l = 0 ; l != b2_size ; ++l ) {
691
- int b2_child = b2_children [l ];
692
- if (b1_child == b2_child
693
- || (b1_child >= 0 && b2_child >= 0 && equivs .get (
694
- b1_child , b2_child ))) {
695
- b2_count ++;
704
+ } else {
705
+ Arrays .fill (visited ,false );
706
+ if (!findMatch (i ,b1_children ,b2_children ,equivs ,matches ,visited )) {
707
+ // If we can't find a match, then it's game over and we know for
708
+ // sure these two states are not equivalent.
709
+ return false ;
696
710
}
697
711
}
698
- // Check that the count matches.
699
- if (b1_count != b2_count ) {
700
- return false ;
701
- }
702
712
}
703
-
704
- // NOTE: unlike the case for Set, we don't need to perform the same
705
- // calculation in the reverse direction. This is because the size of two
706
- // bags must be identical and, hence, if the above loop passes we have
707
- // checked all states in both directions already.
708
-
713
+
709
714
return true ;
710
715
}
711
716
717
+ /**
718
+ * In this case, we have to find a state equivalent to some state of
719
+ * negative kind. This can only happen if such states are identical and,
720
+ * hence, we can greedily look for such a state which has not already been
721
+ * matched.
722
+ *
723
+ * @param i
724
+ * --- index in b1_children of state being matched
725
+ * @param b1_children
726
+ * --- children of first state
727
+ * @param b2_children
728
+ * --- children of second state
729
+ * @param equivs
730
+ * --- current list of equivalences
731
+ * @param matches
732
+ * --- current list of matches
733
+ * @return
734
+ */
735
+ private static boolean findNegativeMatch (int i , int [] b1_children , int [] b2_children , BinaryMatrix equivs , int [] matches ) {
736
+ int b1_child = b1_children [i ];
737
+ for (int j =0 ;j !=b2_children .length ;++j ) {
738
+ int b2_child = b2_children [j ];
739
+ if (b1_child == b2_child && matches [j ] == -1 ) {
740
+ matches [j ] = i ;
741
+ return true ;
742
+ }
743
+ }
744
+ // Unable to find a suitable match
745
+ return false ;
746
+ }
747
+
748
+ /**
749
+ * Attempt to find a match for a given state. In the case that this state is
750
+ * adjacent to another which is unmatched, we can greedily match them
751
+ * together. Otherwise, we search for an "augmenting path" which corresponds
752
+ * (in this case) to a path from this state to another which is adjacent to
753
+ * an unmatched state. If such a state can be found then we can reconfigure
754
+ * the current matching to reflect this, hence freeing up an adjacent state
755
+ * for this state to be matched against. Got it?
756
+ *
757
+ * @param i
758
+ * --- index in b1_children of state being matched
759
+ * @param b1_children
760
+ * --- children of first state
761
+ * @param b2_children
762
+ * --- children of second state
763
+ * @param equivs
764
+ * --- current list of equivalences
765
+ * @param matches
766
+ * --- current list of matches
767
+ * @param visited
768
+ * --- current indices of b1_children which have been explored
769
+ * during current attempt to find augmenting path.
770
+ * @return
771
+ */
772
+ private static boolean findMatch (int i , int [] b1_children , int [] b2_children , BinaryMatrix equivs , int [] matches ,
773
+ boolean [] visited ) {
774
+ visited [i ] = true ;
775
+ int b1_child = b1_children [i ];
776
+ for (int j =0 ;j !=b2_children .length ;++j ) {
777
+ int b2_child = b2_children [j ];
778
+ if (b2_child >= 0 && equivs .get (b1_child , b2_child )) {
779
+ int match = matches [j ];
780
+ if (matches [j ] == -1 ) {
781
+ // This indicates that b2_child is not matched, so we
782
+ // greedily match it.
783
+ matches [j ] = i ;
784
+ return true ;
785
+ } else if (!visited [match ] && findMatch (match ,b1_children ,b2_children ,equivs ,matches ,visited )) {
786
+ // This is a vertex was not previously visited during the
787
+ // current search for an augmenting path, and we were able
788
+ // to find an augmenting path. Hence, we reconfigure the
789
+ // matches to reflect the inverted augmenting path.
790
+ matches [j ] = i ;
791
+ return true ;
792
+ }
793
+ }
794
+ }
795
+ // We failed to find an augmenting path
796
+ return false ;
797
+ }
798
+
712
799
/**
713
800
* <p>
714
801
* This algorithm extends all of the current morphisms by a single place.
0 commit comments