Skip to content

Commit 1acae61

Browse files
committed
Merge branch 'develop'
2 parents a4b577b + 4751e0b commit 1acae61

File tree

2 files changed

+132
-45
lines changed

2 files changed

+132
-45
lines changed

config.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<project name="BuildConfig">
2-
<property name="version" value="0.4.3"/>
2+
<property name="version" value="0.4.4"/>
33
<property name="WYBS_JAR" value="lib/wybs-v0.3.34.jar"/>
44
</project>

src/wyautl/core/Automata.java

Lines changed: 131 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -634,21 +634,41 @@ private final static boolean equivalent(Automaton automaton,
634634
}
635635

636636
/**
637+
* <p>
637638
* 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>
643663
*
644664
* @param automaton
645-
* Automaton in which the two states reside
665+
* --- Automaton in which the two states reside
646666
* @param equivs
647-
* Binary equivalence matrix.
667+
* --- Binary equivalence matrix.
648668
* @param b1
649-
* First bag state
669+
* --- First bag state
650670
* @param b2
651-
* Second bag state
671+
* --- Second bag state
652672
* @return
653673
*/
654674
private final static boolean equivalent(Automaton automaton,
@@ -666,49 +686,116 @@ private final static boolean equivalent(Automaton automaton,
666686

667687
int[] b1_children = b1.children;
668688
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+
670696
// 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;
685703
}
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;
696710
}
697711
}
698-
// Check that the count matches.
699-
if (b1_count != b2_count) {
700-
return false;
701-
}
702712
}
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+
709714
return true;
710715
}
711716

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+
712799
/**
713800
* <p>
714801
* This algorithm extends all of the current morphisms by a single place.

0 commit comments

Comments
 (0)