>

>
1x
go to beginning previous frame pause play next frame go to end

A Matching in a graph G = (V, E) is a subset M of E edges in G such that no two of which meet at a common vertex.


Maximum Cardinality Matching (MCM) problem is a Graph Matching problem where we seek a matching M that contains the largest possible number of edges. A desirable but rarely possible result is Perfect Matching where all |V| vertices are matched (assuming |V| is even), i.e., the cardinality of M is |V|/2.


A Bipartite Graph is a graph whose vertices can be partitioned into two disjoint sets U and V such that every edge can only connect a vertex in U to a vertex in V.


Maximum Cardinality Bipartite Matching (MCBM) problem is the MCM problem in a Bipartite Graph, which is a lot easier than MCM problem in a General Graph.


Remarks: By default, we show e-Lecture Mode for first time (or non logged-in) visitor.
If you are an NUS student and a repeat visitor, please login.

🕑

Graph Matching problems (and its variants) arise in various applications, e.g.,

  1. The underlying reason on why Social Development Network exists in Singapore
  2. Matching job openings (one disjoint set) to job applicants (the other disjoint set)
  3. The weighted version of #2 is called the Assignment problem
  4. Special-case of some NP-hard optimization problems
    (e.g., MVC, MIS, MPC on DAG, etc)
  5. Deterministic 2-opt Approximation Algorithm for MVC
  6. Sub-routine of Christofides's 1.5-approximation algorithm for TSP, etc...

Pro-tip 1: Since you are not logged-in, you may be a first time visitor (or not an NUS student) who are not aware of the following keyboard shortcuts to navigate this e-Lecture mode: [PageDown]/[PageUp] to go to the next/previous slide, respectively, (and if the drop-down box is highlighted, you can also use [→ or ↓/← or ↑] to do the same),and [Esc] to toggle between this e-Lecture mode and exploration mode.

🕑

In some applications, the weights of edges are not uniform (1 unit) but varies, and we may then want to take MCBM or MCM with minimum (or even maximum) total weight.


However, this visualization is currently limited to unweighted graphs only. Thus, we currently do not support Graph Matching problem variants involving weighted graphs...


The plan is to add Hungarian (Kuhn-Munkres) algorithm for weighted MCBM problem by April 2024, stay tuned...


Pro-tip 2: We designed this visualization and this e-Lecture mode to look good on 1366x768 resolution or larger (typical modern laptop resolution in 2021). We recommend using Google Chrome to access VisuAlgo. Go to full screen mode (F11) to enjoy this setup. However, you can use zoom-in (Ctrl +) or zoom-out (Ctrl -) to calibrate this.

🕑

To switch between the unweighted MCBM (default, as it is much more popular) and unweighted MCM mode, click the respective header.


Here is an example of MCM mode. In MCM mode, one can draw a General, not necessarily Bipartite graphs. However, the graphs are unweighted (all edges have uniform weight 1).


The available algorithms are different in the two modes.


Pro-tip 3: Other than using the typical media UI at the bottom of the page, you can also control the animation playback using keyboard shortcuts (in Exploration Mode): Spacebar to play/pause/replay the animation, / to step the animation backwards/forwards, respectively, and -/+ to decrease/increase the animation speed, respectively.

🕑

You can view the visualisation here!


For Bipartite Graph visualization, we will mostly layout the vertices of the graph so that the two disjoint sets (U and V) are clearly visible as Left (U) and Right (V) sets. When you draw your input bipartite graph, you can choose to re-layout your bipartite graph into this easier-to-visualize form. However, you do not have to visualize Bipartite Graph in this form, e.g., you can click Grid Graph to load an example grid graph and notice that vertices {0,1,2,3} can form set U and vertices {4,5,6,7,8} can form set V. There is no odd-length cycle in this grid graph.


For General Graph, we do not (and usually cannot) relayout the vertices into this Left Set and Right Set form.


Initially, edges have grey color. Matched edges will have black color. Free/Matched edges along an augmenting path will have Orange/Light Blue colors, respectively.

🕑

There are three different sources for specifying an input graph:

  1. Edit Graph: You can draw any undirected unweighted graph as the input graph.
    However, due to the way we visualize our MCBM algorithms, we need to impose one additional graph drawing constraint that does not exist in the actual MCBM problems. That constraint is that vertices on the left set are numbered from [0, n), and vertices on the right set are numbered from [n, n+m). You do not have to visually draw them in left-right sets form, as shown in this Grid Graph example.
  2. Modeling: Several graph problems can be reduced into an MCBM problem. In this visualization, we have the modeling examples for the famous Rook Attack problem and standard MCBM problem (also valid in MCM mode).
  3. Example Graphs: You can select from the list of our example graphs to get you started. The list of examples is slightly different in the two MCBM vs MCM modes.
🕑

This slide is a stub and will be expanded with the explanation of this problem and how to interpret the bipartite graph created.

🕑

You can create any (small) bipartite graph with n/m vertices on the left set, respectively, and set the density of the edges, with 100% being a complete bipartite graph Kn,m and 0% being a bipartite graph with no edge.

🕑

There are several Max Cardinality Bipartite Matching (MCBM) algorithms in this visualization, plus one more in Max Flow visualization:

  1. By reducing MCBM problem into a Max-Flow problem in polynomial time,
    we can actually use any Max Flow algorithm to solve MCBM.
  2. O(VE) Augmenting Path Algorithm (without greedy pre-processing),
  3. O(√(V)E) Dinic's or Hopcroft-Karp Algorithm,
  4. O(kE) Augmenting Path Algorithm (with randomized greedy pre-processing),

PS1: Although possible, we will likely not use O(V3) Edmonds' Matching Algorithm if the input is guaranteed to be a Bipartite Graph (as it is much slower).

PS2: Although possible, we will also likely not use O(V3) Kuhn-Munkres Algorithm if the input is guaranteed to be an unweighted Bipartite Graph (again, as it is much slower).

🕑

The MCBM problem can be modeled (or reduced into) as a Max Flow problem in polynomial time.


Go to Max Flow visualization page and see the flow graph modeling of MCBM problem (select Modeling → Bipartite Matching → all 1). Basically, create a super source vertex s that connects to all vertices in the left set and also create a super sink vertex t where all vertices in the right set connect to t. Keep all edges in the flow graph directed from source to sink and with unit weight 1.


If we use one of the earliest Max Flow algorithm, i.e., a simple Ford-Fulkerson algorithm, the time complexity will be tighter than O(mf × E) as all edge weights in the flow graph are unit weight so mf ≤ V, i.e., so O(V × E) overall.


If we use one of the fastest Max Flow algorithm, i.e., Dinic's algorithm on this flow graph, we can find Max Flow = MCBM in O(√(V)E) time — the analysis is omitted for now. This allows us to solve MCBM problem with V ∈ [1000..1500] in a typical 1s allowed runtime in many programming competitions.


Discussion: The edges in the flow graph must be directed. Why?
Then prove that this reduction is correct.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

Actually, we can just stop here, i.e., when given any MCBM(-related) problem, we can simply reduce it into a Max-Flow problem and use (the fastest) Max Flow algorithm.


However, there is a far simpler Graph Matching algorithm that we will see in the next few slides. It is based on a crucial theorem and can be implemented as an easy variation of the standard Depth-First Search (DFS) algorithm.

🕑

Augmenting Path is a path that starts from a free (unmatched) vertex u in graph G (note that G does not necessarily has to be a bipartite graph although augmenting path, if any, is much easier to find in a bipartite graph), alternates through unmatched (or free/'f'), matched (or 'm'), ..., unmatched ('f') edges in G, until it ends at another free vertex v. The pattern of any Augmenting Path will be fmf...fmf and is of odd length.


If we flip the edge status along that augmenting path, i.e., fmf...fmf into mfm...mfm, we will increase the number of edges in the matching set M by exactly 1 unit and eliminates this augmenting path.


In 1957, Claude Berge proposes the following theorem:
A matching M in graph G is maximum iff there is no more augmenting path in G.


Discussion: In class, prove the correctness of Berge's theorem!
In practice, we can just use it verbatim.

🕑

The proof claims if and only if, thus it has two parts:
the forwards direction and the backwards direction.


The forwards proof is easier:
M∈G is maximum → there is no augmenting path in G w.r.t M.


The backwards proof is a bit harder:
M∈G is maximum ← there is no augmenting path in G w.r.t M.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

Recall: Berge's theorem states:
A matching M in graph G is maximum iff there is no more augmenting path in G.


The Augmenting Path Algorithm (on Bipartite Graph) is a simple O(V*(V+E)) = O(V2 + VE) = O(VE) implementation (a modification of DFS) of that theorem: Find and then eliminate augmenting paths in Bipartite Graph G.


Click Augmenting Path Algorithm Demo to visualize this algorithm on a special test case called X̄ (X-bar).


Basically, this Augmenting Path Algorithm scans through all vertices on the left set (that were initially free vertices) one by one. Suppose L on the left set is a free vertex, this algorithm will recursively (via modification of DFS) go to a vertex R on the right set:

  1. If R is another free vertex, we have found one augmenting path (e.g., Augmenting Path 0-2 initially), and
  2. If R is already matched (this information is stored at match[R]), we immediately return to the left set and recurse (e.g, path 1-2-immediately return to 0-then 0-3, to find the second Augmenting Path 1-2-0-3)
🕑
vi match, vis;           // global variables

int Aug(int L) { // notice similarities with DFS algorithm
if (vis[L]) return 0; // L visited, return 0
vis[L] = 1;
for (auto& R : AL[L])
if ((match[R] == -1) || Aug(match[R])) { // the key modification
match[R] = L; // flip status
return 1; // found 1 matching
}
return 0; // Augmenting Path is not found
}
🕑
// in int main(), build the bipartite graph
// use directed edges from left set (of size VLeft) to right set
int MCBM = 0;
match.assign(V, -1);
for (int L = 0; L < VLeft; ++L) { // try all left vertices
vis.assign(VLeft, 0);
MCBM += Aug(L); // find augmenting path starting from L
}
printf("Found %d matchings&bsol;n", MCBM);

Please see the full implementation at Competitive Programming book repository: mcbm.cpp | py | java | ml.

🕑

If we are given a Complete Bipartite Graph KN/2,N/2, i.e.,
V = N/2+N/2 = N and E = N/2×N/2 = N2/4 &approx; N2, then
the Augmenting Path Algorithm discussed earlier will run in O(VE) = O(N×N2) = O(N3).


This is only OK for V &in; [400..500] in a typical 1s allowed runtime in many programming competitions.


Try executing the standard Augmenting Path Algorithm on this Extreme Test Case, which is an almost complete K5,5 Bipartite Graph.


It feels bad, especially on the latter iterations...
So, should we avoid using this simple Augmenting Path algorithm?

🕑

The key idea of Hopcroft-Karp (HK) Algorithm (invented in 1973) is identical to Dinic's Max Flow Algorithm, i.e., prioritize shortest augmenting paths (in terms of number of edges used) first. That's it, augmenting paths with 1 edge are processed first before longer augmenting paths with 3 edges, 5 edges, 7 edges, etc (the length always increase by 2 due to the nature of augmenting path in a Bipartite Graph).


Hopcroft-Karp Algorithm has time complexity of O(√(V)E) — analysis omitted for now. This allows us to solve MCBM problem with V ∈ [1000..1500] in a typical 1s allowed runtime in many programming competitions — the similar range as with running Dinic's algorithm on Bipartite Matching flow graph.


Try HK Algorithm on the same Extreme Test Case earlier. You will notice that HK Algorithm can find the MCBM in a much faster time than the previous standard O(VE) Augmenting Path Algorithm.


Since Hopcroft-Karp algorithm is essentially also Dinic's algorithm, we treat both as 'approximately equal'.

🕑

However, we can actually make the easy-to-code Augmenting Path Algorithm discussed earlier to avoid its worst case O(VE) behavior by doing O(V+E) randomized (to avoid adversary test case) greedy pre-processing before running the actual algorithm.


This O(V+E) additional pre-processing step is simple: For every vertex on the left set, match it with a randomly chosen unmatched neighbouring vertex on the right set. This way, we eliminate many trivial (one-edge) Augmenting Paths that consist of a free vertex u, an unmatched edge (u, v), and a free vertex v.


Try Augmenting Path Algorithm Plus on the same Extreme Test Case earlier. Notice that the pre-processing step already eliminates many trivial 1-edge augmenting paths, making the actual Augmenting Path Algorithm only need to do little amount of additional work.

🕑

Quite often, on randomly generated Bipartite Graph, the randomized greedy pre-processing step has cleared most of the matchings.


However, we can construct test case like: Example Graphs, Corner Case, Rand Greedy AP Killer to make randomization as ineffective as possible. For every group of 4 vertices, there are 2 matchings. Random greedy processing has 50% chance of making mistake per group (but since each group has only short Augmenting Paths, the fixes are not 'long'). Try this Test Case with Multiple Components case to see for yourself.


The worst case time complexity is no longer O(VE) but now O(kE) where k is a small integer, much smaller than V, k can be as small as 0 and is at most V/2 (any maximal matching, as with this case, has size of at least half of the maximum matching). In our empirical experiments, we estimate k to be "about √(V)" too. This version of Augmenting Path Algorithm Plus also allows us to solve MCBM problem with V ∈ [1000..1500] in a typical 1s allowed runtime in many programming competitions.

🕑

So, when presented with an MCBM problem, which route should we take?

  1. Reduce the MCBM problem into Max-Flow and use Dinic's algorithm (essentially Hopcroft-Karp algorithm) and gets O(√(V)E) performance guarantee but with a much longer implementation?
  2. Use Augmenting Path algorithm with Randomized Greedy Processing with O(kE) performance with good empirical results and a much shorter implementation?

Discussion: Discuss these two routes!

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

The content of this interesting slide (the answer of the usually intriguing discussion point from the earlier slide) is hidden and only available for legitimate CS lecturer worldwide. This mechanism is used in the various flipped classrooms in NUS.


If you are really a CS lecturer (or an IT teacher) (outside of NUS) and are interested to know the answers, please drop an email to stevenhalim at gmail dot com (show your University staff profile/relevant proof to Steven) for Steven to manually activate this CS lecturer-only feature for you.


FAQ: This feature will NOT be given to anyone else who is not a CS lecturer.

🕑

Unfortunately there is no weighted MCBM algorithm (e.g., Min-Cost-Max-Flow (mcmf) or Hungarian/Kuhn-Munkres) visualization in VisuAlgo yet. But the plan is to have this visualization eventually.


For this section, please refer to CP4 Book 2 Chapter 9.25 and 9.27.

🕑

When Graph Matching is posed on general graphs (the MCM problem), it is (much) harder to find Augmenting Path. In fact, before Jack Edmonds published his famous paper titled "Paths, Trees, and Flowers" in 1965, this MCM problem was thought to be an (NP-)hard optimization problem.


There are two Max Cardinality Matching (MCM) algorithms in this visualization:

  1. O(V^3) Edmonds' Matching algorithm (without greedy pre-processing),
  2. O(V^3) Edmonds' Matching algorithm (with greedy pre-processing),
🕑

In General Graph (like the graph shown in the background that has |MCM| = 4), we may have Odd-Length cycle. Augmenting Path is not well defined in such a graph, hence we cannot easily implement Claude Berge's theorem like what we did with Bipartite Graph.


Jack Edmonds call a path that starts from a free vertex u, alternates between free, matched, ..., free edges, and returns to the same free vertex u as a Blossom. This situation is only possible if we have Odd-Length cycle, i.e., in a non-Bipartite Graph. For example, assume edge 1-2 has been matched in the graph shown in the background, then path 3-1=2-3 is a blossom.


Edmonds then proposed Blossom shrinking/contraction and expansion algorithm to solve this issue. For details on how this algorithm works, read CP4 Section 9.28 as the current visualization of Edmonds' matching algorithm in VisuAlgo is still 'a bit too hard too understand' for beginners, try Edmonds' Matching. In a live class in NUS, these steps will be explained verbally.


This algorithm can be implemented in O(V^3).

🕑
O(V^3) Edmonds' Matching Algorithm Plus

As with the Augmenting Path Algorithm Plus for the MCBM problem, we can also do randomized greedy pre-processing step to eliminate as many 'trivial matchings' as possible upfront. This reduces the amount of work of Edmonds' Matching Algorithm, thus resulting in a faster time complexity — analysis TBA.

🕑

We have not added the visualizations for weighted variant of MCBM and MCM problems. They are for future work. Among the two, weighted MCBM will likely be added earlier than the weighted MCM version.


One of the possible solution for weighted MCBM problem is the Hungarian algorithm. This algorithm also has another name: The Kuhn-Munkres algorithm. This algorithm relies on Berge's Augmenting Path Theorem too, but it uses the theorem slightly differently.

🕑

To strengthen your understanding about these Graph Matching problem, its variations, and the multiple possible solutions, please try solving as many of these programming competition problems listed below:

  1. Standard MCBM (but need a fast algorithm): Kattis - flippingcards
  2. Greedy Bipartite Matching: Kattis - froshweek2
    (you do not need a specific MCBM algorithm for this,
    in fact, it will be too slow if you use any algorithm discussed here)
  3. Special case of an NP-hard optimization problem: Kattis - bilateral
  4. Rather straightforward weighted MCBM: Kattis - engaging
🕑

To tackle those programming contest problems, you are allowed to use/modify our implementation code for Augmenting Path Algorithm (with Randomized Greedy Preprocessing): mcbm.cpp | py | java | ml


You have reached the last slide. Return to 'Exploration Mode' to start exploring!

Note that if you notice any bug in this visualization or if you want to request for a new visualization feature, do not hesitate to drop an email to the project leader: Dr Steven Halim via his email address: stevenhalim at gmail dot com.

🕑

Edit Graph

Input Graph

Modeling

Example Graphs

Augmenting Path

>

Rook Attack

Generate Random Bipartite Graph, specify n, m, and edge density

Generate Random Weighted Bipartite Graph, specify n

K2,2

F-mod

Corner Case

Special Case

Performance Test

Matching with Capacity

waif (WA)

CP4 3.11a*

Theorem

Sample Weighted Bipartite

Sample Weighted Bipartite TUM

Standard

With Randomized Greedy Preprocessing

Hopcroft Karp

Edmonds Blossom

Edmonds Blossom + Greedy

Hungarian

About Team Terms of use Privacy Policy

About

Initially conceived in 2011 by Associate Professor Steven Halim, VisuAlgo aimed to facilitate a deeper understanding of data structures and algorithms for his students by providing a self-paced, interactive learning platform.

Featuring numerous advanced algorithms discussed in Dr. Steven Halim's book, 'Competitive Programming' — co-authored with Dr. Felix Halim and Dr. Suhendry Effendy — VisuAlgo remains the exclusive platform for visualizing and animating several of these complex algorithms even after a decade.

While primarily designed for National University of Singapore (NUS) students enrolled in various data structure and algorithm courses (e.g., CS1010/equivalent, CS2040/equivalent (including IT5003), CS3230, CS3233, and CS4234), VisuAlgo also serves as a valuable resource for inquisitive minds worldwide, promoting online learning.

Initially, VisuAlgo was not designed for small touch screens like smartphones, as intricate algorithm visualizations required substantial pixel space and click-and-drag interactions. For an optimal user experience, a minimum screen resolution of 1366x768 is recommended. However, since April 2022, a mobile (lite) version of VisuAlgo has been made available, making it possible to use a subset of VisuAlgo features on smartphone screens.

VisuAlgo remains a work in progress, with the ongoing development of more complex visualizations. At present, the platform features 24 visualization modules.

Equipped with a built-in question generator and answer verifier, VisuAlgo's "online quiz system" enables students to test their knowledge of basic data structures and algorithms. Questions are randomly generated based on specific rules, and students' answers are automatically graded upon submission to our grading server. As more CS instructors adopt this online quiz system worldwide, it could effectively eliminate manual basic data structure and algorithm questions from standard Computer Science exams in many universities. By assigning a small (but non-zero) weight to passing the online quiz, CS instructors can significantly enhance their students' mastery of these basic concepts, as they have access to an almost unlimited number of practice questions that can be instantly verified before taking the online quiz. Each VisuAlgo visualization module now includes its own online quiz component.

VisuAlgo has been translated into three primary languages: English, Chinese, and Indonesian. Additionally, we have authored public notes about VisuAlgo in various languages, including Indonesian, Korean, Vietnamese, and Thai:

id, kr, vn, th.

Team

Project Leader & Advisor (Jul 2011-present)
Associate Professor Steven Halim, School of Computing (SoC), National University of Singapore (NUS)
Dr Felix Halim, Senior Software Engineer, Google (Mountain View)

Undergraduate Student Researchers 1
CDTL TEG 1: Jul 2011-Apr 2012: Koh Zi Chun, Victor Loh Bo Huai

Final Year Project/UROP students 1
Jul 2012-Dec 2013: Phan Thi Quynh Trang, Peter Phandi, Albert Millardo Tjindradinata, Nguyen Hoang Duy
Jun 2013-Apr 2014 Rose Marie Tan Zhao Yun, Ivan Reinaldo

Undergraduate Student Researchers 2
CDTL TEG 2: May 2014-Jul 2014: Jonathan Irvin Gunawan, Nathan Azaria, Ian Leow Tze Wei, Nguyen Viet Dung, Nguyen Khac Tung, Steven Kester Yuwono, Cao Shengze, Mohan Jishnu

Final Year Project/UROP students 2
Jun 2014-Apr 2015: Erin Teo Yi Ling, Wang Zi
Jun 2016-Dec 2017: Truong Ngoc Khanh, John Kevin Tjahjadi, Gabriella Michelle, Muhammad Rais Fathin Mudzakir
Aug 2021-Apr 2023: Liu Guangyuan, Manas Vegi, Sha Long, Vuong Hoang Long, Ting Xiao, Lim Dewen Aloysius

Undergraduate Student Researchers 3
Optiver: Aug 2023-Oct 2023: Bui Hong Duc, Oleh Naver, Tay Ngan Lin

Final Year Project/UROP students 3
Aug 2023-Apr 2024: Xiong Jingya, Radian Krisno, Ng Wee Han, Tan Chee Heng
Aug 2024-Apr 2025: Edbert Geraldy Cangdinata, Huang Xing Chen, Nicholas Patrick

List of translators who have contributed ≥ 100 translations can be found at statistics page.

Acknowledgements
NUS CDTL gave Teaching Enhancement Grant to kickstart this project.

For Academic Year 2023/24, a generous donation from Optiver will be used to further develop VisuAlgo.

Terms of use

VisuAlgo is generously offered at no cost to the global Computer Science community. If you appreciate VisuAlgo, we kindly request that you spread the word about its existence to fellow Computer Science students and instructors. You can share VisuAlgo through social media platforms (e.g., Facebook, YouTube, Instagram, TikTok, Twitter, etc), course webpages, blog reviews, emails, and more.

Data Structures and Algorithms (DSA) students and instructors are welcome to use this website directly for their classes. If you capture screenshots or videos from this site, feel free to use them elsewhere, provided that you cite the URL of this website (https://visualgo.net) and/or the list of publications below as references. However, please refrain from downloading VisuAlgo's client-side files and hosting them on your website, as this constitutes plagiarism. At this time, we do not permit others to fork this project or create VisuAlgo variants. Personal use of an offline copy of the client-side VisuAlgo is acceptable.

Please note that VisuAlgo's online quiz component has a substantial server-side element, and it is not easy to save server-side scripts and databases locally. Currently, the general public can access the online quiz system only through the 'training mode.' The 'test mode' offers a more controlled environment for using randomly generated questions and automatic verification in real examinations at NUS.

List of Publications

This work has been presented at the CLI Workshop at the ICPC World Finals 2012 (Poland, Warsaw) and at the IOI Conference at IOI 2012 (Sirmione-Montichiari, Italy). You can click this link to read our 2012 paper about this system (it was not yet called VisuAlgo back in 2012) and this link for the short update in 2015 (to link VisuAlgo name with the previous project).

Bug Reports or Request for New Features

VisuAlgo is not a finished project. Associate Professor Steven Halim is still actively improving VisuAlgo. If you are using VisuAlgo and spot a bug in any of our visualization page/online quiz tool or if you want to request for new features, please contact Associate Professor Steven Halim. His contact is the concatenation of his name and add gmail dot com.

Privacy Policy

Version 1.2 (Updated Fri, 18 Aug 2023).

Since Fri, 18 Aug 2023, we no longer use Google Analytics. Thus, all cookies that we use now are solely for the operations of this website. The annoying cookie-consent popup is now turned off even for first-time visitors.

Since Fri, 07 Jun 2023, thanks to a generous donation by Optiver, anyone in the world can self-create a VisuAlgo account to store a few customization settings (e.g., layout mode, default language, playback speed, etc).

Additionally, for NUS students, by using a VisuAlgo account (a tuple of NUS official email address, student name as in the class roster, and a password that is encrypted on the server side — no other personal data is stored), you are giving a consent for your course lecturer to keep track of your e-lecture slides reading and online quiz training progresses that is needed to run the course smoothly. Your VisuAlgo account will also be needed for taking NUS official VisuAlgo Online Quizzes and thus passing your account credentials to another person to do the Online Quiz on your behalf constitutes an academic offense. Your user account will be purged after the conclusion of the course unless you choose to keep your account (OPT-IN). Access to the full VisuAlgo database (with encrypted passwords) is limited to Prof Halim himself.

For other CS lecturers worldwide who have written to Steven, a VisuAlgo account (your (non-NUS) email address, you can use any display name, and encrypted password) is needed to distinguish your online credential versus the rest of the world. Your account will have CS lecturer specific features, namely the ability to see the hidden slides that contain (interesting) answers to the questions presented in the preceding slides before the hidden slides. You can also access Hard setting of the VisuAlgo Online Quizzes. You can freely use the material to enhance your data structures and algorithm classes. Note that there can be other CS lecturer specific features in the future.

For anyone with VisuAlgo account, you can remove your own account by yourself should you wish to no longer be associated with VisuAlgo tool.

\n'; $('#rookattack-board').html(toWrite); $('#rookattack-board').show("slow"); } this.modeling = function(modelingType) { iEL = {}; iVL = {}; if (modelingType == "rookattack") this.rookattack(); if (modelingType == "baseball") this.baseball(); return true; } this.hopcroftKarpViz = function(callback) { var graph = new HopcroftKarp.Graph(amountVertex); for (var key in iEL) { var u = +iEL[key]['u']; var v = +iEL[key]['v']; graph.adj[u][v] = graph.adj[v][u] = 1; } function findEdgeId(u, v) { for (var key in iEL) { if (+iEL[key]['u'] === u && +iEL[key]['v'] === v) { return +key; } if (+iEL[key]['v'] === u && +iEL[key]['u'] === v) { return +key; } } return -1; } var cs, stateList = []; var vizInfo = []; var result = HopcroftKarp.HopcroftKarp(graph, amountLeftSet, vizInfo); // console.log(JSON.stringify(vizInfo)); var matchingSize = 0; var edgeHighlighted = {}; for (var i = 0; i < vizInfo.length; ++i) { var info = vizInfo[i]; edgeHighlighted = {}; for (var j = 0; j < graph.n; j++) { if (info.curMatching[j] !== -1) { var edgeId = findEdgeId(j, info.curMatching[j]); edgeHighlighted[edgeId] = true; } } switch (info.payload.kind) { case "BFSDistance": var maxDistance = -1; for (var j = 0; j < graph.n; j++) { iVL[j]["extratext"] = info.payload.distance[j] < 0 ? "INF" : info.payload.distance[j]; if (info.payload.distance[j] > maxDistance) { maxDistance = info.payload.distance[j]; } } cs = createState(iVL, iEL, {}, edgeHighlighted); if (maxDistance >= 0) { cs["status"] = "Partition the graph into layers based on augmenting path."; cs["lineNo"] = [1, 2]; stateList.push(cs); } else { cs["status"] = "No more augmenting paths."; cs["lineNo"] = 1; stateList.push(cs); } break; case "NewMatching": var edgeTraversed = {}; // !matched && selected var edgeTraversed2 = {}; // matched && selected for (var j = 0; j < info.payload.newMatchings.length - 1; j++) { var edgeId = findEdgeId(info.payload.newMatchings[j][1], info.payload.newMatchings[j + 1][0]); if (edgeId === -1) { alert("W"); } delete edgeHighlighted[edgeId]; edgeTraversed2[edgeId] = true; } ++matchingSize; var augmentingPath = []; for (var j = 0; j < info.payload.newMatchings.length; ++j) { augmentingPath.push(info.payload.newMatchings[j][0]); augmentingPath.push(info.payload.newMatchings[j][1]); var edgeId = findEdgeId(info.payload.newMatchings[j][0], info.payload.newMatchings[j][1]); if (edgeId === -1) { alert("W"); } edgeTraversed[edgeId] = true; } cs = createState(iVL, iEL, {}, edgeHighlighted, {}, edgeTraversed, {}, edgeTraversed2); cs['status'] = 'Found new matching from the following augmenting path: ' + augmentingPath.join(', '); cs['lineNo'] = [3, 4]; stateList.push(cs); break; default: } } for (var i = 0; i < graph.n; i++) { iVL[i]['extratext'] = ''; } cs = createState(iVL, iEL, {}, edgeHighlighted); cs["status"] = "Found matchings of size " + matchingSize; cs["lineNo"] = []; stateList.push(cs); populatePseudocode(1); gw.startAnimation(stateList, callback); return true; } this.edmondsBlossomViz = function(performGreedyAssignment, callback) { var graph = new EdmondsBlossom.Graph(amountVertex); for (var key in iEL) { var u = +iEL[key]['u']; var v = +iEL[key]['v']; graph.adj[u][v] = graph.adj[v][u] = 1; } var vizInfo = []; var result = EdmondsBlossom.EdmondsBlossom(graph, performGreedyAssignment, vizInfo); var canRemove = {}; // never hide vertices with degree 0 { var degree = []; for (var i = 0; i < amountVertex; i++) { degree[i] = 0; } for (var key in iEL) { degree[+iEL[key]['u']]++; degree[+iEL[key]['v']]++; } for (var i = 0; i < amountVertex; i++) { canRemove[i] = degree[i] === 0 ? false : true; } } // console.log(JSON.stringify(vizInfo)); var supMapper = { "0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹", } var lastRoot = -1; function createVL(el, blossom = []) { var degree = []; for (var i = 0; i < amountVertex; i++) { degree[i] = 0; } for (var key in el) { degree[+el[key]['u']]++; degree[+el[key]['v']]++; } var newVL = {}; for (var i = 0; i < amountVertex; i++) { newVL[i] = $.extend({}, iVL[i]); if (blossom.length != 0 && i == blossom[0]) { var blossomR = blossom[0]; newVL[blossomR]['text'] = i; newVL[blossomR]['radius'] = 16; if (blossomLength >= 1) { var toAdd = `${blossomLength}` for (var ch of toAdd) { newVL[blossomR]['text'] += supMapper[ch] } } newVL[blossomR]['radius'] += 4 * blossomLength; } if (degree[i] === 0 && i !== lastRoot && canRemove[i]) { // hide this // newVL[i]['x'] = 1000; // newVL[i]['y'] = 1000; if (blossom.length != 0) { var blossomR = blossom[0]; newVL[i]['x'] = iVL[blossomR]['x']; newVL[i]['y'] = iVL[blossomR]['y']; newVL[i]['text'] = ''; newVL[i]['radius'] = 16; for (var blossomL = 0; blossomL < blossomLength; blossomL++) { newVL[i]['radius'] += 4; } } } } return newVL; } function createEL(graph) { var newEL = {}; var edgeCount = 0; for (var i = 0; i < graph.n; i++) { for (var j = i + 1; j < graph.n; j++) { if (graph.adj[i][j]) { newEL[edgeCount++] = {'u': i, 'v': j}; } } } return newEL; } function findEdgeIndex(el, from, to) { for (var key in el) { if (el[key]['u'] === from && el[key]['v'] === to) { return +key; } if (el[key]['v'] === from && el[key]['u'] === to) { return +key; } } return -1; } var lastMatching = []; var lastVL = iVL; var lastEL = iEL; var cs, stateList = []; var matchingSize = 0; var edgeHighlighted = {}; // matched && !selected var blossomLength = 0; var blossoms = [[]]; for (var i = 0; i < vizInfo.length; i++) { if (lastEL === undefined) { alert("ERROR!" + i); } var info = vizInfo[i]; var status = ""; var vertexHighlighted = {}; edgeHighlighted = {}; var edgeTraversed = {}; // !matched && selected var edgeTraversed2 = {}; // matched && selected var vertexHighlighted = {}; // for blossom var vertexTraversed = {}; var newEL = undefined; for (var j = 0; j < graph.n; j++) { var edgeIndex = findEdgeIndex(lastEL, j, info.curMatching[j]); if (edgeIndex === -1) { // hidden continue; } edgeHighlighted[edgeIndex] = true; } switch (info.payload.kind) { case "AddGreedyMatching": status = "Randomly match " + info.payload.newMatching[0] + " and " + info.payload.newMatching[1]; cs = createState(iVL, iEL, {}, edgeHighlighted); cs["status"] = status; cs["lineNo"] = 1; stateList.push(cs); matchingSize++; break; case "FoundAugmentingPath": var path = info.payload.path; var newPath = []; for (var p = 0; p < path.length; p++) { if (path[p] != blossoms[blossoms.length - 1][0]) { newPath.push(path[p]); } else { var tempString = `${path[p]}`; if (blossoms.length - 1 >= 1) { var toAdd = `${blossoms.length - 1}` for (var ch of toAdd) { tempString += supMapper[ch] } } newPath.push(tempString); } } status = "Found an augmenting path " + newPath.join(" -> "); for (var j = 1; j < info.payload.path.length; j++) { var prev = info.payload.path[j - 1]; var cur = info.payload.path[j]; var edgeIndex = findEdgeIndex(lastEL, info.payload.path[j - 1], info.payload.path[j]); if (edgeIndex === -1) { // error alert("what?"); return false; } if (info.curMatching[prev] === cur) { delete edgeHighlighted[edgeIndex]; edgeTraversed2[edgeIndex] = true; } else { edgeTraversed[edgeIndex] = true; } } cs = createState(createVL(lastEL, blossoms[blossoms.length - 1]), lastEL, {}, edgeHighlighted, {}, edgeTraversed, {}, edgeTraversed2); cs["status"] = status; if (performGreedyAssignment) { cs["lineNo"] = [2, 3]; } else { cs["lineNo"] = [1, 2]; } stateList.push(cs); break; case "NoAugmentingPath": status = "No augmenting path from " + info.payload.start + " found"; cs = createState(createVL(lastEL), lastEL); cs["status"] = status; cs["lineNo"] = 1; stateList.push(cs); break; case "IncreaseMatching": status = "Increased the number of matching by one."; // do nothing cs = createState(createVL(lastEL), lastEL, {}, edgeHighlighted, {}, edgeTraversed, {}, edgeTraversed2); cs["status"] = status; if (performGreedyAssignment) { cs["lineNo"] = [2, 4, 5] } else { cs["lineNo"] = [1, 2, 4]; } lastMatching = info.curMatching.slice(0); stateList.push(cs); matchingSize++; break; case "ContractBlossom": var path = info.payload.blossom.concat([info.payload.blossom[0]]); var newPath = []; for (var p = 0; p < path.length; p++) { if (path[p] != blossoms[blossoms.length - 1][0]) { newPath.push(path[p]); } else { var tempString = `${path[p]}`; if (blossoms.length - 1 >= 1) { var toAdd = `${blossoms.length - 1}` for (var ch of toAdd) { tempString += supMapper[ch] } } newPath.push(tempString); } } status = "Found an odd-length cycle " + (newPath.join(" -> ")); lastRoot = info.payload.root; for (var j = 0; j < info.payload.blossom.length; j++) { vertexHighlighted[info.payload.blossom[j]] = true; } for (var j = 0; j < info.payload.pathToBlossom.length; j++) { var cur = info.payload.pathToBlossom[j]; var next = j === info.payload.pathToBlossom.length - 1 ? info.payload.root : info.payload.pathToBlossom[j + 1]; var edgeIndex = findEdgeIndex(lastEL, cur, next); if (edgeIndex === -1) { // error alert("WHAT? " + JSON.stringify(lastEL) + " " + cur + " " + next); return false; } if (info.curMatching[cur] === next) { delete edgeHighlighted[edgeIndex]; edgeTraversed2[edgeIndex] = true; } else { edgeTraversed[edgeIndex] = true; } } cs = createState(createVL(lastEL, blossoms[blossoms.length - 1]), lastEL, {}, edgeHighlighted, {}, edgeTraversed, {}, edgeTraversed2); cs["status"] = status; if (performGreedyAssignment) { cs["lineNo"] = [2, 6, 7]; } else { cs["lineNo"] = [1, 5, 6]; } stateList.push(cs); //INTERMEDIARY STEPS tempBlossom = info.payload.blossom var path = info.payload.blossom; var newPath = []; for (var p = 0; p < path.length; p++) { if (path[p] != blossoms[blossoms.length - 1][0]) { newPath.push(path[p]); } else { var tempString = `${path[p]}`; if (blossoms.length - 1 >= 1) { var toAdd = `${blossoms.length - 1}` for (var ch of toAdd) { tempString += supMapper[ch] } } newPath.push(tempString); } } var blossomMap = {} for (var index = 0; index < tempBlossom.length; index++) { blossomMap[tempBlossom[index]] = tempBlossom[index] } tempEL = JSON.parse(JSON.stringify(lastEL)) for (var e in tempEL) { if (tempBlossom.includes(tempEL[e]["u"]) && tempBlossom.includes(tempEL[e]["v"])) { delete tempEL[e] } } cs = createState(createVL(lastEL, blossoms[blossoms.length - 1]), tempEL, blossomMap, edgeHighlighted); cs["status"] = `Contracting blossom rooted at ${newPath[0]}: [${newPath}]. Deleting edges within the blossom.` stateList.push(cs) for (var e in tempEL) { if (tempBlossom.includes(tempEL[e]["u"]) || tempBlossom.includes(tempEL[e]["v"])) { delete tempEL[e] } } cs = createState(createVL(lastEL, blossoms[blossoms.length - 1]), tempEL, blossomMap, edgeHighlighted); cs["status"] = `Contracting blossom rooted at ${newPath[0]}: [${newPath}]. Deleting edges that are connected to the blossom.` stateList.push(cs) newEL = createEL(info.payload.newGraph); edgeHighlighted = {}; for (var j = 0; j < graph.n; j++) { var k = info.curMatching[j]; if (k !== -1) { var edgeIndex = findEdgeIndex(newEL, j, k); if (edgeIndex !== -1) { edgeHighlighted[edgeIndex] = true; } } } blossoms.push(info.payload.blossom); blossomLength++; cs = createState(createVL(newEL, blossoms[blossoms.length - 1]), newEL, {}, edgeHighlighted); cs["status"] = `Contracted blossom to ${info.payload.blossom[0]}`; if (blossoms.length - 1 >= 1) { var toAdd = `${blossoms.length - 1}` for (var ch of toAdd) { cs["status"] += supMapper[ch] } } if (performGreedyAssignment) { cs["lineNo"] = [2, 6, 7]; } else { cs["lineNo"] = [1, 5, 6]; } stateList.push(cs); break; case "ExpandBlossom": var refBlossom = blossoms[blossoms.length - 1]; var rootBlossom = `${info.payload.root}`; if (refBlossom[0] == info.payload.root) { if (blossoms.length - 2 >= 1) { var toAdd = `${blossoms.length - 2}` for (var ch of toAdd) { rootBlossom += supMapper[ch] } } } status = `Expanded blossom rooted at ${rootBlossom}. `; var rootIndexInPath = -1; for (var j = 0; j < info.payload.highlightedPath.length; j++) { if (info.payload.highlightedPath[j] === info.payload.root) { rootIndexInPath = j; break; } } newEL = createEL(info.payload.newGraph); edgeHighlighted = {}; for (var j = 0; j < graph.n; j++) { var k = info.curMatching[j]; if (k === -1) { continue; } var edgeIndex = findEdgeIndex(newEL, j, k); if (edgeIndex === -1) { // ignore continue; } edgeHighlighted[edgeIndex] = true; } for (var j = 1; j < info.payload.highlightedPath.length; j++) { var prev = info.payload.highlightedPath[j - 1]; var cur = info.payload.highlightedPath[j]; if (j - 1 === rootIndexInPath) { continue; } var edgeIndex = findEdgeIndex(newEL, prev, cur); if (edgeIndex === -1) { // error alert("NANI?"); return false; } if (info.curMatching[prev] === cur) { delete edgeHighlighted[edgeIndex]; edgeTraversed2[edgeIndex] = true; } else { edgeTraversed[edgeIndex] = true; } } for (var j = 0; j < info.payload.pathTakenInside.length; j++) { var prev = j == 0 ? info.payload.root : info.payload.pathTakenInside[j - 1]; var cur = info.payload.pathTakenInside[j]; vertexTraversed[cur] = true; var edgeIndex = findEdgeIndex(newEL, prev, cur); if (edgeIndex === -1) { // error alert("?? " + prev + " " + cur + " " + JSON.stringify(newEL)); return false; } if (info.curMatching[prev] === cur) { delete edgeHighlighted[edgeIndex]; edgeTraversed2[edgeIndex] = true; } else { edgeTraversed[edgeIndex] = true; } } if (rootIndexInPath !== -1) { var prev = info.payload.blossomFrom; var cur = info.payload.highlightedPath[rootIndexInPath + 1]; var edgeIndex = findEdgeIndex(newEL, prev, cur); if (info.curMatching[prev] === cur) { delete edgeHighlighted[edgeIndex]; edgeTraversed2[edgeIndex] = true; } else { edgeTraversed[edgeIndex] = true; } } if (info.payload.pathTakenInside.length > 0) { var path = [info.payload.blossomRoot].concat(info.payload.pathTakenInside).concat([info.payload.highlightedPath[rootIndexInPath + 1]]); var newPath = []; for (var p = 0; p < path.length; p++) { if (path[p] != blossoms[blossoms.length - 1][0]) { newPath.push(path[p]); } else { var tempString = `${path[p]}`; if (blossoms.length - 1 >= 1) { var toAdd = `${blossoms.length - 1}` for (var ch of toAdd) { tempString += supMapper[ch] } } newPath.push(tempString); } } status += info.payload.highlightedPath[rootIndexInPath + 1] + " is connected to " + info.payload.blossomFrom + " from the blossom. "; status += "Taking the following path " + rootBlossom + newPath.join(" -> ") + " inside the blossom."; } //INTERMEDIARY STEPS tempEL1 = JSON.parse(JSON.stringify(newEL)); for (var e in tempEL1) { if (blossoms[blossoms.length - 1].includes(tempEL1[e]["u"]) || blossoms[blossoms.length - 1].includes(tempEL1[e]["v"])) { delete tempEL1[e] } } tempEL2 = JSON.parse(JSON.stringify(newEL)); for (var e in tempEL2) { if (blossoms[blossoms.length - 1].includes(tempEL2[e]["u"]) && blossoms[blossoms.length - 1].includes(tempEL2[e]["v"])) { delete tempEL2[e] } } var lastBlossom = blossoms.pop(); var path = lastBlossom; var newPath = []; for (var p = 0; p < path.length; p++) { if (path[p] != blossoms[blossoms.length - 1][0]) { newPath.push(path[p]); } else { var tempString = `${path[p]}`; if (blossoms.length - 1 >= 2) { var toAdd = `${blossoms.length - 1}` for (var ch of toAdd) { tempString += supMapper[ch] } } newPath.push(tempString); } } var blossomMap = {}; for (var index = 0; index < lastBlossom.length; index++) { blossomMap[lastBlossom[index]] = lastBlossom[index]; } blossomLength--; cs = createState(createVL(newEL, blossoms[blossoms.length - 1]), tempEL1, blossomMap); cs["status"] = `Expanding blossom rooted at ${newPath[0]}: [${newPath}].`; stateList.push(cs); cs = createState(createVL(newEL, blossoms[blossoms.length - 1]), tempEL2, blossomMap); cs["status"] = `Expanding blossom rooted at ${newPath[0]}: [${newPath}]. Drawing edges that are connected to the blossom.`; stateList.push(cs); cs = createState(createVL(newEL, blossoms[blossoms.length - 1]), newEL, blossomMap); cs["status"] = `Expanding blossom rooted at ${newPath[0]}: [${newPath}]. Drawing edges that are connected within the blossom.`; stateList.push(cs); cs = createState(createVL(newEL, blossoms[blossoms.length - 1]), newEL, {}, edgeHighlighted, vertexTraversed, edgeTraversed, {}, edgeTraversed2); cs["status"] = status; if (performGreedyAssignment) { cs["lineNo"] = [2, 3, 4]; } else { cs["lineNo"] = [1, 2, 3]; // TODO(raisfathin) } stateList.push(cs); break; default: alert("UNHANDLED " + info.kind); return false; } if (newEL !== undefined) { lastEL = newEL; } } var edgeHighlighted = {}; for (var i = 0; i < graph.n; i++) { if (lastMatching[i] !== -1) { var edgeId = findEdgeIndex(iEL, i, lastMatching[i]); if (edgeId !== -1) { edgeHighlighted[edgeId] = true; } } } cs = createState(iVL, iEL, {}, edgeHighlighted); cs['status'] = 'Found matchings of size ' + matchingSize; cs['lineNo'] = []; stateList.push(cs); populatePseudocode(performGreedyAssignment ? 5 : 4); gw.startAnimation(stateList, callback); return true; } this.hungarian = function(callback) { populatePseudocode(6); function findEdgeId(u, v) { for (var key in iEL) { if (+iEL[key]['u'] == u && +iEL[key]['v'] == v) { return +key; } if (+iEL[key]['v'] == u && +iEL[key]['u'] == v) { return +key; } } return -1; } var vertexHighlighted = {}, edgeHighlighted = {}, vertexTraversed = {}, edgeTraversed = {}, vertexTraversed2 = {}, edgeTraversed2 = {}; var stateList = []; var cs; var iELTemp = {}; var toSet = [], toUnSet = []; var LeftToRightEdge = [], RightToLeftEdge = []; var edgeDict = {}; for (key in iEL) { var edgeDictKeyOne = []; var edgeDictKeyTwo = []; edgeDictKeyOne.push(iEL[key]["u"]); edgeDictKeyOne.push(iEL[key]["v"]); edgeDictKeyTwo.push(iEL[key]["v"]); edgeDictKeyTwo.push(iEL[key]["u"]); edgeDict[edgeDictKeyOne] = key; edgeDict[edgeDictKeyTwo] = key; } var n = 0; for (var key in iVL) { if (iVL[key]["x"] == 300) { n++; } } var l = Array(n * 2).fill(0); // Find the first matching var leftToRightTemp = {}; for (var key in iEL) { if (iEL[key]['w'] > l[iEL[key]['u']]) { leftToRightTemp[iEL[key]['u']] = iEL[key]['v']; } l[iEL[key]['u']] = Math.max(l[iEL[key]['u']], iEL[key]['w']); } for (var left in leftToRightTemp) { iELTemp[findEdgeId(left, leftToRightTemp[left])] = iEL[findEdgeId(left, leftToRightTemp[left])]; } for (var key in iELTemp) { edgeHighlighted[key] = true; } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = 'an edge exist in the subgraph iff l(u) + l(v) = w(u, v), initially l(u) = max weight among all u\'s outgoing edges'; cs['lineNo'] = 1; stateList.push(cs); // Augmenting step match = {}, vis = {}; path = []; path2 = []; path3 = []; paths = []; for (key in iVL) match[key] = -1; for (var i = 0; i < n; i++) { for (key in iVL) vis[key] = 0; path = []; path2 = []; path3 = []; LeftToRightEdge = []; RightToLeftEdge = []; if (match[i] === -1) { var res = Aug(i); var flag = false; // Visualize iteration for (var j in iELTemp) { var edge = iELTemp[j]; var e, r; var pathVisited = new Set(); var cleanPath = []; for (var node of path) { if (pathVisited.has(node)) { continue; } cleanPath.push(node); pathVisited.add(node); } var uniquePath = []; for (var node of path2) { var insert = true; for (var unique of uniquePath) { if (unique[0] == node[0] && unique[1] == node[1]) { insert = false; } if (unique[0] == node[1] && unique[1] == node[0]) { insert = false; } } if (insert) { uniquePath.push([node[0], node[1]]); } } if (i == edge["u"]) { r = edge["v"]; edgeTraversed[j] = true; vertexHighlighted[i] = true; vertexHighlighted[r] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `applying berge\'s lemma from vertex ${i}`; cs['lineNo'] = 3; stateList.push(cs); if (match[i] == r || match[r] == i) { var augmentingPath = `${i} -> ${r}`; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `found augmenting path ${augmentingPath}`; cs['lineNo'] = 4; stateList.push(cs); } else { } delete edgeTraversed[j]; delete vertexHighlighted[i]; delete vertexHighlighted[r]; if (match[i] == r || match[r] == i) { edgeTraversed2[j] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `flip the status of the edges`; cs['lineNo'] = 5; stateList.push(cs); } else { console.log('UNIQUE PATH', path2, uniquePath, path3); // for (var index = 0; index < uniquePath.length; index++) { // vertexHighlighted[uniquePath[index][0]] = true; // vertexHighlighted[uniquePath[index][1]] = true; // edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] = true; // cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); // cs['status'] = `extend augmenting path from ${uniquePath[index][0]} to ${uniquePath[index][1]}`; // cs['lineNo'] = 3; // stateList.push(cs); // } var uniquePathCounter = 0; var path3Visited = new Set(); var path3Counter = new Map(); for (var index = 0; index < path3.length; index++) { if ((path3[index][0] == 'add')) { if (!path3Counter.has(findEdgeId(path3[index][1], path3[index][2]))) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), 1); } else { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) + 1); continue; } } if ((path3[index][0] == 'add') && (!path3Visited.has(findEdgeId(path3[index][1], path3[index][2])))) { if ((path3[index][1] == uniquePath[uniquePathCounter][0]) && (path3[index][2] == uniquePath[uniquePathCounter][1])) { uniquePathCounter++; } path3Visited.add(findEdgeId(path3[index][1], path3[index][2])); vertexHighlighted[path3[index][1]] = true; vertexHighlighted[path3[index][2]] = true; edgeTraversed[findEdgeId(path3[index][1], path3[index][2])] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `extend augmenting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } if (uniquePathCounter == uniquePath.length) { break; } if ((path3[index][0] == 'del')) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) - 1); if (path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) > 0) { continue; } } if ((path3[index][0] == 'del') && (path3Visited.has(findEdgeId(path3[index][1], path3[index][2]))) && (uniquePathCounter < uniquePath.length)) { path3Visited.delete(findEdgeId(path3[index][1], path3[index][2])); delete vertexHighlighted[path3[index][2]]; delete edgeTraversed[findEdgeId(path3[index][1], path3[index][2])]; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `backtrack, deleting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `stuck, need to relabel equality graph`; cs['lineNo'] = 6; stateList.push(cs); for (var index = 0; index < uniquePath.length; index++) { delete vertexHighlighted[uniquePath[index][0]]; delete vertexHighlighted[uniquePath[index][1]]; delete edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] } } } else if (i == edge["v"]) { r = edge["u"]; edgeTraversed[j] = true; vertexHighlighted[i] = true; vertexHighlighted[r] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `applying berge\'s lemma from vertex ${i}`; cs['lineNo'] = 3; stateList.push(cs); if (match[i] == r || match[r] == i) { var augmentingPath = `${i} -> ${r}`; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `found augmenting path ${augmentingPath}`; cs['lineNo'] = 4; stateList.push(cs); } else { } delete edgeTraversed[j]; delete vertexHighlighted[i]; delete vertexHighlighted[r]; if (match[i] == r || match[r] == i) { edgeTraversed2[j] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `flip the status of the edges`; cs['lineNo'] = 5; stateList.push(cs); } else { console.log('UNIQUE PATH', path2, uniquePath, path3); // for (var index = 0; index < uniquePath.length; index++) { // vertexHighlighted[uniquePath[index][0]] = true; // vertexHighlighted[uniquePath[index][1]] = true; // edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] = true; // cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); // cs['status'] = `extend augmenting path from ${uniquePath[index][0]} to ${uniquePath[index][1]}`; // cs['lineNo'] = 3; // stateList.push(cs); // } var uniquePathCounter = 0; var path3Visited = new Set(); var path3Counter = new Map(); for (var index = 0; index < path3.length; index++) { if ((path3[index][0] == 'add')) { if (!path3Counter.has(findEdgeId(path3[index][1], path3[index][2]))) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), 1); } else { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) + 1); continue; } } if ((path3[index][0] == 'add') && (!path3Visited.has(findEdgeId(path3[index][1], path3[index][2])))) { if ((path3[index][1] == uniquePath[uniquePathCounter][0]) && (path3[index][2] == uniquePath[uniquePathCounter][1])) { uniquePathCounter++; } path3Visited.add(findEdgeId(path3[index][1], path3[index][2])); vertexHighlighted[path3[index][1]] = true; vertexHighlighted[path3[index][2]] = true; edgeTraversed[findEdgeId(path3[index][1], path3[index][2])] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `extend augmenting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } if (uniquePathCounter == uniquePath.length) { break; } if ((path3[index][0] == 'del')) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) - 1); if (path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) > 0) { continue; } } if ((path3[index][0] == 'del') && (path3Visited.has(findEdgeId(path3[index][1], path3[index][2]))) && (uniquePathCounter < uniquePath.length)) { path3Visited.delete(findEdgeId(path3[index][1], path3[index][2])); delete vertexHighlighted[path3[index][2]]; delete edgeTraversed[findEdgeId(path3[index][1], path3[index][2])]; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `backtrack, deleting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `stuck, need to relabel equality graph`; cs['lineNo'] = 6; stateList.push(cs); for (var index = 0; index < uniquePath.length; index++) { delete vertexHighlighted[uniquePath[index][0]]; delete vertexHighlighted[uniquePath[index][1]]; delete edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] } } delete edgeTraversed[j]; delete vertexHighlighted[i]; delete vertexHighlighted[r]; if (match[i] == r || match[r] == i) { edgeTraversed2[j] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `flip the status of the edges in the augmenting path`; cs['lineNo'] = 5; stateList.push(cs); } } } while (res == 0) { // Extend graph var leftSet = new Set(); var rightSet = new Set(); for (var node in path) { if (path[node] < n) { leftSet.add(path[node]); } else { rightSet.add(path[node]); } } var delta = Infinity; var edgeIdToAdd = []; for (var j in iEL) { var edge = iEL[j]; if (leftSet.has(edge["u"]) && !rightSet.has(edge["v"])) { var curr = l[edge["u"]] + l[edge["v"]] - edge["w"]; if (curr < delta) { delta = curr; edgeIdToAdd = [j]; } else if (curr == delta) { edgeIdToAdd.push(j); } } } // Do relabel operations var leftItems = ""; var rightItems = ""; var edgeItems = ""; for (var node of leftSet) { l[node] -= delta; leftItems += `${node},`; vertexHighlighted[node] = true; } for (var node of rightSet) { l[node] += delta; rightItems += `${node},`; vertexHighlighted[node] = true; } for (var edgeId of edgeIdToAdd) { iELTemp[edgeId] = iEL[edgeId]; edgeHighlighted[edgeId] = true; edgeItems += `${iEL[edgeId]["u"]}-${iEL[edgeId]["v"]},`; } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `S = {${leftItems.slice(0, -1)}}, T = {${rightItems.slice(0, -1)}}, delta = ${delta}\nadding edge = {${edgeItems.slice(0, -1)}}`; cs['lineNo'] = 7; stateList.push(cs); for (var node of leftSet) { delete vertexHighlighted[node]; } for (var node of rightSet) { delete vertexHighlighted[node]; } for (key in iVL) vis[key] = 0; path = []; path2 = []; path3 = []; res = Aug(i); if (res == 0) { var pathVisited = new Set(); var cleanPath = []; for (var node of path) { if (pathVisited.has(node)) { continue; } cleanPath.push(node); pathVisited.add(node); } var uniquePath = []; for (var node of path2) { var insert = true; for (var unique of uniquePath) { if (unique[0] == node[0] && unique[1] == node[1]) { insert = false; } if (unique[0] == node[1] && unique[1] == node[0]) { insert = false; } } if (insert) { uniquePath.push([node[0], node[1]]); } } vertexHighlighted[uniquePath[0][0]] = true; vertexHighlighted[uniquePath[0][1]] = true; edgeTraversed[findEdgeId(uniquePath[0][0], uniquePath[0][1])] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `applying berge\'s lemma from vertex ${i}`; cs['lineNo'] = 3; stateList.push(cs); console.log('UNIQUE PATH', path2, uniquePath, path3); // for (var index = 0; index < uniquePath.length; index++) { // vertexHighlighted[uniquePath[index][0]] = true; // vertexHighlighted[uniquePath[index][1]] = true; // edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] = true; // cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); // cs['status'] = `extend augmenting path from ${uniquePath[index][0]} to ${uniquePath[index][1]}`; // cs['lineNo'] = 3; // stateList.push(cs); // } var uniquePathCounter = 0; var path3Visited = new Set(); var path3Counter = new Map(); for (var index = 0; index < path3.length; index++) { if ((path3[index][0] == 'add')) { if (!path3Counter.has(findEdgeId(path3[index][1], path3[index][2]))) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), 1); } else { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) + 1); continue; } } if ((path3[index][0] == 'add') && (!path3Visited.has(findEdgeId(path3[index][1], path3[index][2])))) { if ((path3[index][1] == uniquePath[uniquePathCounter][0]) && (path3[index][2] == uniquePath[uniquePathCounter][1])) { uniquePathCounter++; } path3Visited.add(findEdgeId(path3[index][1], path3[index][2])); vertexHighlighted[path3[index][1]] = true; vertexHighlighted[path3[index][2]] = true; edgeTraversed[findEdgeId(path3[index][1], path3[index][2])] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `extend augmenting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } if (uniquePathCounter == uniquePath.length) { break; } if ((path3[index][0] == 'del')) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) - 1); if (path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) > 0) { continue; } } if ((path3[index][0] == 'del') && (path3Visited.has(findEdgeId(path3[index][1], path3[index][2]))) && (uniquePathCounter < uniquePath.length)) { path3Visited.delete(findEdgeId(path3[index][1], path3[index][2])); delete vertexHighlighted[path3[index][2]]; delete edgeTraversed[findEdgeId(path3[index][1], path3[index][2])]; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `backtrack, deleting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `stuck, need to relabel equality graph`; cs['lineNo'] = 6; stateList.push(cs); for (var index = 0; index < uniquePath.length; index++) { delete vertexHighlighted[uniquePath[index][0]]; delete vertexHighlighted[uniquePath[index][1]]; delete edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] } } flag = true; } if (flag) { // Try last time var pathVisited = new Set(); var cleanPath = []; for (var node of path) { if (pathVisited.has(node)) { continue; } cleanPath.push(node); pathVisited.add(node); } var uniquePath = []; for (var node of path2) { var insert = true; for (var unique of uniquePath) { if (unique[0] == node[0] && unique[1] == node[1]) { insert = false; } if (unique[0] == node[1] && unique[1] == node[0]) { insert = false; } } if (insert) { uniquePath.push([node[0], node[1]]); } } console.log('UNIQUE PATH', path2, uniquePath, path3); // for (var index = 0; index < uniquePath.length; index++) { // vertexHighlighted[uniquePath[index][0]] = true; // vertexHighlighted[uniquePath[index][1]] = true; // edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] = true; // cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); // cs['status'] = `extend augmenting path from ${uniquePath[index][0]} to ${uniquePath[index][1]}`; // cs['lineNo'] = 3; // stateList.push(cs); // } var uniquePathCounter = 0; var path3Visited = new Set(); var path3Counter = new Map(); for (var index = 0; index < path3.length; index++) { if ((path3[index][0] == 'add')) { if (!path3Counter.has(findEdgeId(path3[index][1], path3[index][2]))) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), 1); } else { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) + 1); continue; } } if ((path3[index][0] == 'add') && (!path3Visited.has(findEdgeId(path3[index][1], path3[index][2])))) { if ((path3[index][1] == uniquePath[uniquePathCounter][0]) && (path3[index][2] == uniquePath[uniquePathCounter][1])) { uniquePathCounter++; } path3Visited.add(findEdgeId(path3[index][1], path3[index][2])); vertexHighlighted[path3[index][1]] = true; vertexHighlighted[path3[index][2]] = true; edgeTraversed[findEdgeId(path3[index][1], path3[index][2])] = true; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `extend augmenting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } if (uniquePathCounter == uniquePath.length) { break; } if ((path3[index][0] == 'del')) { path3Counter.set(findEdgeId(path3[index][1], path3[index][2]), path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) - 1); if (path3Counter.get(findEdgeId(path3[index][1], path3[index][2])) > 0) { continue; } } if ((path3[index][0] == 'del') && (path3Visited.has(findEdgeId(path3[index][1], path3[index][2]))) && (uniquePathCounter < uniquePath.length)) { path3Visited.delete(findEdgeId(path3[index][1], path3[index][2])); delete vertexHighlighted[path3[index][2]]; delete edgeTraversed[findEdgeId(path3[index][1], path3[index][2])]; cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `backtrack, deleting path from ${path3[index][1]} to ${path3[index][2]}`; cs['lineNo'] = 3; stateList.push(cs); } } // FLIPPING for (var index = 0; index < uniquePath.length; index++) { delete edgeTraversed2[findEdgeId(uniquePath[index][0], uniquePath[index][1])]; } for (var left in match) { right = match[left]; if (right != -1) { edgeTraversed2[findEdgeId(left, right)] = true; } } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = 'flip the status of the edges in the augmenting path'; cs['lineNo'] = 5; stateList.push(cs); for (var index = 0; index < uniquePath.length; index++) { delete vertexHighlighted[uniquePath[index][0]]; delete vertexHighlighted[uniquePath[index][1]]; delete edgeTraversed[findEdgeId(uniquePath[index][0], uniquePath[index][1])] } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = 'continue iterating on the left vertex'; cs['lineNo'] = 2; stateList.push(cs); } } // LeftToRightEdge.reverse(); // RightToLeftEdge.reverse(); } console.log(match); var value = 0; var dummyCount = 0; for (var right in match) { var left = match[right]; if (left == -1) { continue; } var edgeWeight = iEL[findEdgeId(left, right)]['w']; if (edgeWeight == -1000) { dummyCount++; } else { value += edgeWeight; } } cs = createStateWithExtraText(iVL, iEL, vertexHighlighted, {}, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l); cs['status'] = `found the max weighted perfect matching of ${value}, with ${dummyCount} dummy edge (represented by -1000)`; stateList.push(cs); function Aug(l, prev, order=[]) { if (vis[l] !== 0) return 0; if (prev) { path.push(prev); order.push(prev); } path.push(l); order.push(l); vis[l] = 1; for (var j in iELTemp) { var edge = iELTemp[j]; var e, r; if (l === edge["u"]) { r = edge["v"]; if (match[r] === -1) { path.push(r); path2.push([l, r]); path3.push(['add', l, r]) e = edgeDict[[l, r]]; LeftToRightEdge.push([e,l,r]); if (match[r] !== -1) { e = edgeDict[[r, match[r]]]; RightToLeftEdge.push([e,r,match[r]]); } match[r] = l; return 1; } else { path2.push([l, r]); path2.push([r, match[r]]); path3.push(['add', l, r]); path3.push(['add', r, match[r]]); if (Aug(match[r], r, order.slice())) { e = edgeDict[[l, r]]; LeftToRightEdge.push([e,l,r]); if (match[r] !== -1) { e = edgeDict[[r, match[r]]]; RightToLeftEdge.push([e,r,match[r]]); } match[r] = l; return 1; } path3.push(['del', r, match[r]]); path3.push(['del', l, r]); } } else if (l === edge["v"]) { r = edge["u"]; if (match[r] === -1) { path.push(r); e = edgeDict[[l, r]]; LeftToRightEdge.push([e,l,r]); if (match[r] !== -1) { e = edgeDict[[r,match[r]]]; RightToLeftEdge.push([e,r,match[r]]); } match[r] = l; return 1; } else if (Aug(match[r], r, order.slice())) { e = edgeDict[[l, r]]; LeftToRightEdge.push([e,l,r]); if (match[r] !== -1) { e = edgeDict[[r,match[r]]]; RightToLeftEdge.push([e,r,match[r]]); } match[r] = l; return 1; } } } return 0; } // var indexToRowMap = {}; // var indexToColMap = {}; // var rowToIndexMap = {}; // var colToIndexMap = {}; // var left = 0; // var right = 0; // var A = []; // for (var key in iVL) { // if (iVL[key]["x"] == 300) { // indexToRowMap[key] = left; // rowToIndexMap[left] = key; // left++; // } else { // indexToColMap[key] = right; // colToIndexMap[right] = key; // right++; // } // } // for (var i = 0; i < left; i++) { // A.push([]); // for (var j = 0; j < right; j++) { // A[i].push(0); // } // } // for (var key in iEL) { // var u = indexToRowMap[iEL[key]["u"]]; // var v = indexToColMap[iEL[key]["v"]]; // A[u][v] = iEL[key]["w"] // } // console.log('INI A', A); // console.log(indexToRowMap, indexToColMap); // Hungarian algorithm // var n = left - 1; // var m = right - 1; // var u = Array(left).fill(0); // use 0 to n // var v = Array(right).fill(0); // use 0 to m // var p = Array(right).fill(0); // use 0 to m // var way = Array(right).fill(0); // use 1 to m // for (var i = 1; i <= n; i++) { // p[0] = i; // var j0 = 0; // var minv = Array(right).fill(Infinity); // use 1 to m // var used = Array(right).fill(false); // use 1 to m // do { // used[j0] = true; // var i0 = p[j0]; // var delta = Infinity; // var j1; // for (var j = 1; j <= m; j++) { // if (!used[j]) { // var cur = A[i0][j] - u[i0] - v[j]; // if (cur < minv[j]) { // minv[j] = cur; // way[j] = j0; // } // if (minv[j] < delta) { // delta = minv[j]; // j1 = j; // } // } // } // for (var j = 0; j <= m; j++) { // if (used[j]) { // u[p[j]] += delta; // v[j] -= delta; // } else { // minv[j] -= delta; // } // } // j0 = j1; // } while (p[j0] != 0); // do { // var j1 = way[j0]; // p[j0] = p[j1]; // j0 = j1; // } while (j0); // } // var ans = Array(left).fill(0); // for (var j = 1; j <= m; j++) { // ans[p[j]] = j; // } gw.startAnimation(stateList, callback); return true; } this.generalRandom = function() { // var id = CS4234_TUTORIAL_THREE; var id = HOUSE_OF_CARDS; // not really random leh iVL = getExampleGraph(id, VL); iEL = getExampleGraph(id, EL); fixJSON(); // console.log(JSON.stringify(iVL)); // console.log(JSON.stringify(iEL)); var newState = createState(iVL, iEL); gw.updateGraph(newState, 500); return true; } this.bipartiteRandom = function(n, m, d) { // now this is properly done (10 Sep 2022) // note on 11 Sep, these error messages not yet displayed correctly... just use carefully first if ((n < 1) || (n > 7)) { // Please enter a valid index between [1..{limit}]. // $('#modeling-err').html('Please enter a valid n between [1..{limit}]'.replace("{limit}", 7)); $('#modeling-err').html("Please enter a valid n between [1..7]"); console.log('debug this ' + n); return false; } if ((m < 1) || (m > 7)) { $('#modeling-err').html("Please enter a valid m between [1..7]"); console.log('debug this ' + m); return false; } if ((d < 0) || (d > 100)) { $('#modeling-err').html("Please enter a valid d between [0..100]"); console.log('debug this ' + d); return false; } amountVertex = n+m; amountLeftSet = n; // to help with the AugmentingPath algorithm too iVL = new Object(); iEL = new Object(); for (var i = 0; i < n; ++i) { iVL[i] = { "x": 300, "y": 25 + i * (n == 1 ? 0 : 240/(n-1)), "text": i, } } for (var j = 0; j < m; ++j) { iVL[n+j] = { "x": 400, "y": 25 + j * (m == 1 ? 0 : 240/(m-1)), "text": (n+j), } } amountEdge = 0; for (var i = 0; i < n; ++i) for (var j = 0; j < m; ++j) if (Math.floor(Math.random()*100) < d) { // this edge (i, n+j) has d% chance to be created, random between [0..99], when d = 100, K_{n,m} created, when d = 0, no edge is created iEL[amountEdge] = { "u": i, "v": n+j, "w": 1, } ++amountEdge; } var newState = createState(iVL, iEL); gw.updateGraph(newState, 500); return true; } this.weightedBipartiteRandom = function(n) { // now this is properly done (10 Sep 2022) // note on 11 Sep, these error messages not yet displayed correctly... just use carefully first var m = n; if ((n < 1) || (n > 7)) { // Please enter a valid index between [1..{limit}]. // $('#modeling-err').html('Please enter a valid n between [1..{limit}]'.replace("{limit}", 7)); $('#modeling-err').html("Please enter a valid n between [1..7]"); console.log('debug this ' + n); return false; } if ((m < 1) || (m > 7)) { $('#modeling-err').html("Please enter a valid m between [1..7]"); console.log('debug this ' + m); return false; } amountVertex = n+m; amountLeftSet = n; // to help with the AugmentingPath algorithm too iVL = new Object(); iEL = new Object(); for (var i = 0; i < n; ++i) { iVL[i] = { "x": 300, "y": 25 + i * (n == 1 ? 0 : 240/(n-1)), "text": i, } } for (var j = 0; j < m; ++j) { iVL[n+j] = { "x": j%2 == 0 ? 700 : 800, "y": 25 + j * (m == 1 ? 0 : 240/(m-1)), "text": (n+j), } } amountEdge = 0; for (var i = 0; i < n; ++i) { for (var j = 0; j < m; ++j) { iEL[amountEdge] = { "u": i, "v": n+j, "w": Math.random() < 0.9 ? Math.floor(Math.random()*19) + 1: -1000, } ++amountEdge; } } var newState = createState(iVL, iEL); gw.updateGraph(newState, 500); return true; } this.examples = function(id) { // quick patch on 8 Sep 2022 to add a few example graphs without touching graph library yet if (id == 'xbar') { // the smallest test case to illustrate Augmenting Path algorithm iVL = { 0: { "x": 300, "y": 25 }, 1: { "x": 300, "y": 265 }, 2: { "x": 400, "y": 25 }, 3: { "x": 400, "y": 265 }, }; iEL = { 0: { "u": 0, "v": 2, "w": 1 }, 1: { "u": 0, "v": 3, "w": 1 }, 2: { "u": 1, "v": 2, "w": 1 }, }; } else if (id == 'OddLineBipartite') { // an odd-length line graph (to help show Berge's theorem proof) iVL = { 0: { "x": 300, "y": 25 }, 1: { "x": 400, "y": 25 }, 2: { "x": 500, "y": 25 }, 3: { "x": 600, "y": 25 }, 4: { "x": 350, "y": 25 }, 5: { "x": 450, "y": 25 }, 6: { "x": 550, "y": 25 }, 7: { "x": 250, "y": 25 }, }; iEL = { 0: { "u": 0, "v": 4, "w": 1 }, 1: { "u": 4, "v": 1, "w": 1 }, 2: { "u": 1, "v": 5, "w": 1 }, 3: { "u": 5, "v": 2, "w": 1 }, 4: { "u": 2, "v": 6, "w": 1 }, 5: { "u": 6, "v": 3, "w": 1 }, 6: { "u": 7, "v": 0, "w": 1 }, }; } else if (id == 'EvenLineBipartite') { // an even-length line graph (to help show Berge's theorem proof) iVL = { 0: { "x": 300, "y": 25 }, 1: { "x": 400, "y": 25 }, 2: { "x": 500, "y": 25 }, 3: { "x": 600, "y": 25 }, 4: { "x": 350, "y": 25 }, 5: { "x": 450, "y": 25 }, 6: { "x": 550, "y": 25 }, }; iEL = { 0: { "u": 0, "v": 4, "w": 1 }, 1: { "u": 4, "v": 1, "w": 1 }, 2: { "u": 1, "v": 5, "w": 1 }, 3: { "u": 5, "v": 2, "w": 1 }, 4: { "u": 2, "v": 6, "w": 1 }, 5: { "u": 6, "v": 3, "w": 1 }, }; } else if (id == 'EvenCycleBipartite') { // can be used for Christofides weighted perfect matching on even number of odd-degree vertices of the MST of the input graph iVL = { // suppose there are 8 (even) vertices with odd-degree, must be numbered this way in this visualization... 0: { "x": 300, "y": 25 }, 1: { "x": 500, "y": 25 }, 2: { "x": 300, "y": 225 }, 3: { "x": 500, "y": 225 }, 4: { "x": 400, "y": 25 }, 5: { "x": 300, "y": 125 }, 6: { "x": 500, "y": 125 }, 7: { "x": 400, "y": 225 }, }; iEL = { // we can construct a ring out of these 8 0: { "u": 0, "v": 4, "w": 1 }, 1: { "u": 4, "v": 1, "w": 1 }, 2: { "u": 1, "v": 6, "w": 1 }, 3: { "u": 6, "v": 3, "w": 1 }, 4: { "u": 3, "v": 7, "w": 1 }, 5: { "u": 7, "v": 2, "w": 1 }, 6: { "u": 2, "v": 5, "w": 1 }, 7: { "u": 5, "v": 0, "w": 1 }, }; } else if (id == 'TreeBipartite') { // visually a tree, but with special labels for this visualization to work iVL = { 0: { "x": 400, "y": 185 }, 1: { "x": 400, "y": 25 }, 2: { "x": 500, "y": 185 }, 3: { "x": 400, "y": 105 }, 4: { "x": 500, "y": 105 }, 5: { "x": 350, "y": 265 }, 6: { "x": 450, "y": 265 }, 7: { "x": 300, "y": 105 }, }; iEL = { 0: { "u": 0, "v": 3, "w": 1 }, 1: { "u": 0, "v": 5, "w": 1 }, 2: { "u": 0, "v": 6, "w": 1 }, 3: { "u": 1, "v": 3, "w": 1 }, 4: { "u": 1, "v": 4, "w": 1 }, 5: { "u": 1, "v": 7, "w": 1 }, 6: { "u": 2, "v": 4, "w": 1 }, }; } else if (id == 'GridBipartite') { // visually a grid, but with special labels for this visualization to work iVL = { 0: { "x": 400, "y": 25 }, 1: { "x": 300, "y": 125 }, 2: { "x": 500, "y": 125 }, 3: { "x": 400, "y": 225 }, 4: { "x": 300, "y": 25 }, 5: { "x": 500, "y": 25 }, 6: { "x": 400, "y": 125 }, 7: { "x": 300, "y": 225 }, 8: { "x": 500, "y": 225 }, }; iEL = { 0: { "u": 0, "v": 4, "w": 1 }, 1: { "u": 0, "v": 5, "w": 1 }, 2: { "u": 0, "v": 6, "w": 1 }, 3: { "u": 1, "v": 4, "w": 1 }, 4: { "u": 1, "v": 6, "w": 1 }, 5: { "u": 1, "v": 7, "w": 1 }, 6: { "u": 2, "v": 5, "w": 1 }, 7: { "u": 2, "v": 6, "w": 1 }, 8: { "u": 2, "v": 8, "w": 1 }, 9: { "u": 3, "v": 6, "w": 1 }, 10: { "u": 3, "v": 7, "w": 1 }, 11: { "u": 3, "v": 8, "w": 1 }, }; } else if (id == 'MatchingWithCapacity') { iVL = { 0: { "x": 300, "y": 265 }, 1: { "x": 300, "y": 25 }, 2: { "x": 300, "y": 125, "text": "2a" }, 3: { "x": 300, "y": 175, "text": "2b" }, 4: { "x": 300, "y": 225, "text": "2c" }, 5: { "x": 400, "y": 25, "text": "3a" }, 6: { "x": 400, "y": 75, "text": "3b" }, 7: { "x": 400, "y": 175, "text": "4a" }, 8: { "x": 400, "y": 225, "text": "4b" }, 9: { "x": 400, "y": 265 }, }; iEL = { 0: { "u": 1, "v": 5, "w": 1 }, 1: { "u": 1, "v": 6, "w": 1 }, 2: { "u": 1, "v": 7, "w": 1 }, 3: { "u": 1, "v": 8, "w": 1 }, 4: { "u": 2, "v": 7, "w": 1 }, 5: { "u": 3, "v": 7, "w": 1 }, 6: { "u": 4, "v": 7, "w": 1 }, 7: { "u": 2, "v": 8, "w": 1 }, 8: { "u": 3, "v": 8, "w": 1 }, 9: { "u": 4, "v": 8, "w": 1 }, }; } else if (id == 'waif_WA') { iVL = { 0: { "x": 300, "y": 265 }, 1: { "x": 300, "y": 25, "text": "c1" }, 2: { "x": 300, "y": 92, "text": "c2" }, 3: { "x": 300, "y": 159, "text": "c3" }, 4: { "x": 300, "y": 225, "text": "c4" }, 5: { "x": 400, "y": 25, "text": "t1" }, 6: { "x": 400, "y": 125, "text": "t2" }, 7: { "x": 400, "y": 225, "text": "t3" }, 8: { "x": 400, "y": 265 }, }; iEL = { 0: { "u": 1, "v": 5, "w": 1 }, 1: { "u": 1, "v": 6, "w": 1 }, 2: { "u": 2, "v": 5, "w": 1 }, 3: { "u": 2, "v": 6, "w": 1 }, 4: { "u": 3, "v": 7, "w": 1 }, 5: { "u": 4, "v": 7, "w": 1 }, }; } else if (id == 'greedy_raw') { iVL = { 0: { "x": 300, "y": 25, "text": "5" }, 1: { "x": 300, "y": 145, "text": "4" }, 2: { "x": 300, "y": 265, "text": "8" }, 3: { "x": 400, "y": 25, "text": "7" }, 4: { "x": 400, "y": 145, "text": "8" }, 5: { "x": 400, "y": 265, "text": "4" }, }; iEL = { 0: { "u": 0, "v": 3, "w": 1 }, 1: { "u": 0, "v": 4, "w": 1 }, 2: { "u": 1, "v": 3, "w": 1 }, 3: { "u": 1, "v": 4, "w": 1 }, 4: { "u": 1, "v": 5, "w": 1 }, 5: { "u": 2, "v": 4, "w": 1 }, }; } else if (id == 'greedy_sorted') { iVL = { 0: { "x": 300, "y": 25, "text": "4" }, 1: { "x": 300, "y": 145, "text": "5" }, 2: { "x": 300, "y": 265, "text": "8" }, 3: { "x": 400, "y": 25, "text": "4" }, 4: { "x": 400, "y": 145, "text": "7" }, 5: { "x": 400, "y": 265, "text": "8" }, }; iEL = { 0: { "u": 0, "v": 3, "w": 1 }, 1: { "u": 0, "v": 4, "w": 1 }, 2: { "u": 0, "v": 5, "w": 1 }, 3: { "u": 1, "v": 4, "w": 1 }, 4: { "u": 1, "v": 5, "w": 1 }, 5: { "u": 2, "v": 5, "w": 1 }, }; } else if (id == 'Undirected_MF_Killer') { // must be consistent with /maxflow version iVL = { 0: { "x": 300, "y": 265 }, 1: { "x": 300, "y": 25 }, 2: { "x": 300, "y": 105 }, 3: { "x": 300, "y": 185 }, 4: { "x": 400, "y": 25 }, 5: { "x": 400, "y": 105 }, 6: { "x": 400, "y": 185 }, 7: { "x": 400, "y": 265 }, }; iEL = { 0: { "u": 1, "v": 4, "w": 1 }, 1: { "u": 1, "v": 5, "w": 1 }, 2: { "u": 1, "v": 6, "w": 1 }, 3: { "u": 2, "v": 5, "w": 1 }, 4: { "u": 3, "v": 5, "w": 1 }, }; } else if (id == 'Rand_Greedy_AP_Killer') { // multiple copies of xbars iVL = { 0: { "x": 300, "y": 25 }, 1: { "x": 300, "y": 58 }, 2: { "x": 300, "y": 94 }, 3: { "x": 300, "y": 127 }, 4: { "x": 300, "y": 163 }, 5: { "x": 300, "y": 196 }, 6: { "x": 300, "y": 232 }, 7: { "x": 300, "y": 265 }, 8: { "x": 400, "y": 25 }, 9: { "x": 400, "y": 58 }, 10: { "x": 400, "y": 94 }, 11: { "x": 400, "y": 127 }, 12: { "x": 400, "y": 163 }, 13: { "x": 400, "y": 196 }, 14: { "x": 400, "y": 232 }, 15: { "x": 400, "y": 265 }, }; iEL = { 0: { "u": 0, "v": 8, "w": 1 }, 1: { "u": 0, "v": 9, "w": 1 }, 2: { "u": 1, "v": 8, "w": 1 }, 3: { "u": 2, "v": 10, "w": 1 }, 4: { "u": 2, "v": 11, "w": 1 }, 5: { "u": 3, "v": 10, "w": 1 }, 6: { "u": 4, "v": 12, "w": 1 }, 7: { "u": 4, "v": 13, "w": 1 }, 8: { "u": 5, "v": 12, "w": 1 }, 9: { "u": 6, "v": 14, "w": 1 }, 10: { "u": 6, "v": 15, "w": 1 }, 11: { "u": 7, "v": 14, "w": 1 }, }; } else if (id == 'HMT_1') { // Hall's Marriage Theorem using Steven's 2006 case iVL = { 0: { "x": 300, "y": 25, "text": "Steven" }, 1: { "x": 300, "y": 145 }, 2: { "x": 300, "y": 265 }, 3: { "x": 400, "y": 25 }, 4: { "x": 400, "y": 145 }, 5: { "x": 400, "y": 265 }, }; iEL = { 0: { "u": 0, "v": 3, "w": 1 }, // Steven-A 1: { "u": 1, "v": 3, "w": 1 }, // X-A too 2: { "u": 2, "v": 3, "w": 1 }, // Y-A again 3: { "u": 2, "v": 4, "w": 1 }, // Y-B }; } else if (id == 'HMT_2') { // Hall's Marriage Theorem using Steven's 2007 case (Grace appeared) iVL = { 0: { "x": 300, "y": 25, "text": "Steven" }, 1: { "x": 300, "y": 145 }, 2: { "x": 300, "y": 265 }, 3: { "x": 400, "y": 25 }, 4: { "x": 400, "y": 105 }, 5: { "x": 400, "y": 185 }, 6: { "x": 400, "y": 265, "text": "Grace" }, }; iEL = { 0: { "u": 0, "v": 6, "w": 1 }, // Steven-Grace 1: { "u": 1, "v": 3, "w": 1 }, // X-A too (actually never happened) 2: { "u": 2, "v": 3, "w": 1 }, // Y-A again (also never happened) 3: { "u": 2, "v": 4, "w": 1 }, // Y-B }; } else if (id == 'SMT_2') { // Steven's Marriage Theorem 2 = "If there is a vertex x in {U, V} with (out-)degree 0 in G, then x will not be part of any MCBM" iVL = { 0: { "x": 300, "y": 25 }, 1: { "x": 300, "y": 65 }, 2: { "x": 300, "y": 105 }, 3: { "x": 300, "y": 145 }, 4: { "x": 300, "y": 185 }, 5: { "x": 300, "y": 225 }, 6: { "x": 300, "y": 265 }, 7: { "x": 400, "y": 25 }, 8: { "x": 400, "y": 65 }, 9: { "x": 400, "y": 105 }, 10: { "x": 400, "y": 145 }, 11: { "x": 400, "y": 185 }, 12: { "x": 400, "y": 225 }, 13: { "x": 400, "y": 265 }, }; iEL = { // there is no edge involving 0 (left) and 10 (right) 0: { "u": 1, "v": 7, "w": 1 }, 1: { "u": 1, "v": 8, "w": 1 }, 2: { "u": 1, "v": 9, "w": 1 }, 3: { "u": 1, "v": 11, "w": 1 }, 4: { "u": 1, "v": 12, "w": 1 }, 5: { "u": 1, "v": 13, "w": 1 }, 6: { "u": 2, "v": 7, "w": 1 }, 7: { "u": 2, "v": 8, "w": 1 }, 8: { "u": 2, "v": 9, "w": 1 }, 9: { "u": 2, "v": 11, "w": 1 }, 10: { "u": 2, "v": 12, "w": 1 }, 11: { "u": 2, "v": 13, "w": 1 }, 12: { "u": 3, "v": 7, "w": 1 }, 13: { "u": 3, "v": 8, "w": 1 }, 14: { "u": 3, "v": 9, "w": 1 }, 15: { "u": 3, "v": 11, "w": 1 }, 16: { "u": 3, "v": 12, "w": 1 }, 17: { "u": 3, "v": 13, "w": 1 }, 18: { "u": 4, "v": 7, "w": 1 }, 19: { "u": 4, "v": 8, "w": 1 }, 20: { "u": 4, "v": 9, "w": 1 }, 21: { "u": 4, "v": 11, "w": 1 }, 22: { "u": 4, "v": 12, "w": 1 }, 23: { "u": 4, "v": 13, "w": 1 }, 24: { "u": 5, "v": 7, "w": 1 }, 25: { "u": 5, "v": 8, "w": 1 }, 26: { "u": 5, "v": 9, "w": 1 }, 27: { "u": 5, "v": 11, "w": 1 }, 28: { "u": 5, "v": 12, "w": 1 }, 29: { "u": 5, "v": 13, "w": 1 }, 30: { "u": 6, "v": 7, "w": 1 }, 31: { "u": 6, "v": 8, "w": 1 }, 32: { "u": 6, "v": 9, "w": 1 }, 33: { "u": 6, "v": 11, "w": 1 }, 34: { "u": 6, "v": 12, "w": 1 }, 35: { "u": 6, "v": 13, "w": 1 }, }; } else if (id == 'SMT_4') { // Steven's Marriage Theorem 4 (2022) = "If E contains |E| disjoint edges, then |MCBM| = |E|. Moreover, MCBM is also a perfect matching" iVL = { // 14 vertices 0: { "x": 300, "y": 25 }, 1: { "x": 300, "y": 65 }, 2: { "x": 300, "y": 105 }, 3: { "x": 300, "y": 145 }, 4: { "x": 300, "y": 185 }, 5: { "x": 300, "y": 225 }, 6: { "x": 300, "y": 265 }, 7: { "x": 400, "y": 25 }, 8: { "x": 400, "y": 65 }, 9: { "x": 400, "y": 105 }, 10: { "x": 400, "y": 145 }, 11: { "x": 400, "y": 185 }, 12: { "x": 400, "y": 225 }, 13: { "x": 400, "y": 265 }, }; iEL = { // 7 disjoint edges, so |MCBM| = |E| = 7 0: { "u": 0, "v": 8, "w": 1 }, 1: { "u": 1, "v": 7, "w": 1 }, 2: { "u": 2, "v": 12, "w": 1 }, 3: { "u": 3, "v": 9, "w": 1 }, 4: { "u": 4, "v": 13, "w": 1 }, 5: { "u": 5, "v": 11, "w": 1 }, 6: { "u": 6, "v": 10, "w": 1 }, }; } else if (id == 'SAMPLE_WEIGHTED_BIPARTITE_TUM') { iVL = { 0: { "x": 300, "y": 25 }, 1: { "x": 300, "y": 105 }, 2: { "x": 300, "y": 185 }, 3: { "x": 300, "y": 265 }, 4: { "x": 700, "y": 25 }, 5: { "x": 800, "y": 105 }, 6: { "x": 700, "y": 185 }, 7: { "x": 800, "y": 265 }, }; iEL = { 0: { "u": 0, "v": 4, "w": 7 }, 1: { "u": 0, "v": 5, "w": 4 }, 2: { "u": 0, "v": 6, "w": 3 }, 3: { "u": 0, "v": 7, "w": 5 }, 4: { "u": 1, "v": 4, "w": 6 }, 5: { "u": 1, "v": 5, "w": 8 }, 6: { "u": 1, "v": 6, "w": 5 }, 7: { "u": 1, "v": 7, "w": 9 }, 8: { "u": 2, "v": 4, "w": 9 }, 9: { "u": 2, "v": 5, "w": 4 }, 10: { "u": 2, "v": 6, "w": 4 }, 11: { "u": 2, "v": 7, "w": 2 }, 12: { "u": 3, "v": 4, "w": 3 }, 13: { "u": 3, "v": 5, "w": 8 }, 14: { "u": 3, "v": 6, "w": 7 }, 15: { "u": 3, "v": 7, "w": 4 }, } } else if (id == 'SAMPLE_WEIGHTED_BIPARTITE') { iVL = { 0: { "x": 300, "y": 25 }, 1: { "x": 300, "y": 145 }, 2: { "x": 300, "y": 265 }, 3: { "x": 700, "y": 25 }, 4: { "x": 800, "y": 145 }, 5: { "x": 700, "y": 265 }, }; iEL = { 0: { "u": 0, "v": 3, "w": -1000 }, 1: { "u": 0, "v": 4, "w": 8 }, 2: { "u": 0, "v": 5, "w": 6 }, 3: { "u": 1, "v": 3, "w": 1 }, 4: { "u": 1, "v": 4, "w": 4 }, 5: { "u": 1, "v": 5, "w": -1000 }, 6: { "u": 2, "v": 3, "w": 4 }, 7: { "u": 2, "v": 4, "w": -1000 }, 8: { "u": 2, "v": 5, "w": 1 }, } } else { iVL = getExampleGraph(id, VL); iEL = getExampleGraph(id, EL); } amountVertex = 0; amountEdge = 0; for (var key in iVL) ++amountVertex; for (var key in iEL) ++amountEdge; for (var key in iVL) if (iVL[key]['text'] == null) // add this special check on 10 Sep so that some label (Steven/Grace) can be custom... iVL[key]['text'] = key; //amountLeftSet = templateToUse[4]; // FIX THIS... I don't have this value yet for (var i = 0; i < amountVertex; ++i) { var validAmountLeftSet = true; for (var key in iEL) { var u = +iEL[key]['u']; var v = +iEL[key]['v']; if ((u <= i) == (v <= i)) { validAmountLeftSet = false; } } if (validAmountLeftSet) { amountLeftSet = i+1; break; } } var newState = createState(iVL, iEL); gw.updateGraph(newState, 500); return true; } } // function createState(iVLObject, iELObject, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2){ // if (vertexHighlighted == null) vertexHighlighted = {}; // if (edgeHighlighted == null) edgeHighlighted = {}; // if (vertexTraversed == null) vertexTraversed = {}; // if (edgeTraversed == null) edgeTraversed = {}; // if (vertexTraversed2 == null) vertexTraversed2 = {}; // if (edgeTraversed2 == null) edgeTraversed2 = {}; // var key; // var state = { // "vl":{}, // "el":{} // }; // for (key in iVLObject) { // state["vl"][key] = {}; // state["vl"][key]["cx"] = iVLObject[key]["x"]; // state["vl"][key]["cy"] = iVLObject[key]["y"]; // if (iVLObject[key]["text"] == null) // state["vl"][key]["text"] = +key; // else // state["vl"][key]["text"] = iVLObject[key]["text"]; // if (iVLObject[key]["state"] == OBJ_HIDDEN) // state["vl"][key]["state"] = OBJ_HIDDEN; // else // state["vl"][key]["state"] = VERTEX_DEFAULT; // state["vl"][key]["extratext"] = iVLObject[key]["extratext"]; // } // for (key in iELObject) { // state["el"][key] = {}; // state["el"][key]["vertexA"] = iELObject[key]["u"]; // state["el"][key]["vertexB"] = iELObject[key]["v"]; // state["el"][key]["type"] = EDGE_TYPE_UDE; // state["el"][key]["weight"] = iELObject[key]["w"]; // if (iELObject[key]["state"] == OBJ_HIDDEN) // state["el"][key]["state"] = OBJ_HIDDEN; // else // state["el"][key]["state"] = EDGE_GREY; // state["el"][key]["displayWeight"] = false; // state["el"][key]["animateHighlighted"] = false; // } // for (key in vertexHighlighted) // state["vl"][key]["state"] = VERTEX_HIGHLIGHTED; // for (key in edgeHighlighted) // state["el"][key]["state"] = EDGE_DEFAULT; // for (key in vertexTraversed) // state["vl"][key]["state"] = VERTEX_TRAVERSED; // for (key in edgeTraversed) // state["el"][key]["state"] = EDGE_TRAVERSED; // for (key in vertexTraversed2) // state["vl"][key]["state"] = VERTEX_BLUE_OUTLINE; // for (key in edgeTraversed2) // state["el"][key]["state"] = EDGE_BLUE; // return state; // } function createState(iVLObject, iELObject, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2){ if (vertexHighlighted == null) vertexHighlighted = {}; if (edgeHighlighted == null) edgeHighlighted = {}; if (vertexTraversed == null) vertexTraversed = {}; if (edgeTraversed == null) edgeTraversed = {}; if (vertexTraversed2 == null) vertexTraversed2 = {}; if (edgeTraversed2 == null) edgeTraversed2 = {}; var key; var state = { "vl":{}, "el":{} }; for (key in iVLObject) { state["vl"][key] = {}; state["vl"][key]["cx"] = iVLObject[key]["x"]; state["vl"][key]["cy"] = iVLObject[key]["y"]; if (iVLObject[key]["text"] == null) state["vl"][key]["text"] = +key; else state["vl"][key]["text"] = iVLObject[key]["text"]; if (iVLObject[key]["state"] == OBJ_HIDDEN) state["vl"][key]["state"] = OBJ_HIDDEN; else state["vl"][key]["state"] = VERTEX_DEFAULT; if (iVLObject[key]["radius"] != null) { state["vl"][key]['inner-r'] = iVLObject[key]["radius"] - 2; state["vl"][key]['outer-r'] = iVLObject[key]["radius"]; state["vl"][key]['inner-w'] = iVLObject[key]["radius"] * 2 - 2; state["vl"][key]['outer-w'] = iVLObject[key]["radius"] * 2; state["vl"][key]['inner-h'] = iVLObject[key]["radius"] * 2 - 2; state["vl"][key]['outer-h'] = iVLObject[key]["radius"] * 2; } state["vl"][key]["extratext"] = iVLObject[key]["extratext"]; } for (i in iELObject) { small = Math.min(iELObject[i]["u"], iELObject[i]["v"]) big = Math.max(iELObject[i]["u"], iELObject[i]["v"]) key = `${small}_${big}` state["el"][key] = {}; state["el"][key]["vertexA"] = small; state["el"][key]["vertexB"] = big; state["el"][key]["type"] = EDGE_TYPE_UDE; state["el"][key]["weight"] = iELObject[i]["w"]; if (iELObject[i]["state"] == OBJ_HIDDEN) state["el"][key]["state"] = OBJ_HIDDEN; else state["el"][key]["state"] = EDGE_GREY; state["el"][key]["displayWeight"] = selectedState == WEIGHTED_BIPARTITE_MATCHING; state["el"][key]["animateHighlighted"] = false; } for (key in vertexHighlighted) state["vl"][key]["state"] = VERTEX_HIGHLIGHTED; for (i in edgeHighlighted) { small = Math.min(iELObject[i]["u"], iELObject[i]["v"]) big = Math.max(iELObject[i]["u"], iELObject[i]["v"]) key = `${small}_${big}` state["el"][key]["state"] = EDGE_DEFAULT; } for (key in vertexTraversed) state["vl"][key]["state"] = VERTEX_TRAVERSED; for (i in edgeTraversed) { small = Math.min(iELObject[i]["u"], iELObject[i]["v"]) big = Math.max(iELObject[i]["u"], iELObject[i]["v"]) key = `${small}_${big}` state["el"][key]["state"] = EDGE_TRAVERSED; } for (key in vertexTraversed2) state["vl"][key]["state"] = VERTEX_BLUE_OUTLINE; for (i in edgeTraversed2) { small = Math.min(iELObject[i]["u"], iELObject[i]["v"]) big = Math.max(iELObject[i]["u"], iELObject[i]["v"]) key = `${small}_${big}` state["el"][key]["state"] = EDGE_BLUE; } return state; } function createStateWithExtraText(iVLObject, iELObject, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2, l){ var state = createState(iVLObject, iELObject, vertexHighlighted, edgeHighlighted, vertexTraversed, edgeTraversed, vertexTraversed2, edgeTraversed2); for (var i = 0; i < l.length; i++) { state["vl"][i]["extratext"] = l[i]; } return state; } function populatePseudocode(act) { switch (act) { case 0: // Augmenting Path $('#code1').html('for each vertex in the left set'); $('#code2').html('&nbsp&nbspif ∃ an augmenting path of 1 (or more) edge(s)'); $('#code3').html('&nbsp&nbsp  flip edge status along augmenting path'); $('#code4').html('return'); $('#code5').html(''); $('#code6').html(''); $('#code7').html(''); break; case 1: // Hopcroft-Karp $('#code1').html('while ∃ some augmenting path(s)'); $('#code2').html(' run BFS to partition the graph into layers'); $('#code3').html(' while ∃ an augmenting path in this layer'); $('#code4').html('  flip edge status along augmenting path'); $('#code5').html(''); $('#code6').html(''); $('#code7').html(''); break; case 2: // Rook Attack modeling $('#code1').html('create source and sink vertex'); $('#code2').html('create one vertex Ri for each row i'); $('#code3').html('create one vertex Cj for each column j'); $('#code4').html('for each rook-placable cell (i, j)'); $('#code5').html('  add an edge from Ri to Cj'); $('#code6').html('run any MCBM algorithm'); $('#code7').html(''); break; case 3: // Augmenting Path with randomized greedy preprocessing $('#code1').html('for each vertex v in the left set'); $('#code2').html('&nbsp&nbsp(randomly) choose unmatched neighbour x'); $('#code3').html('&nbsp&nbspmatch v (left set) with x (right set)'); $('#code4').html('Run the standard Augmenting Path Algorithm'); $('#code5').html(''); $('#code6').html(''); $('#code7').html(''); break; case 4: $('#code1').html('while ∃ an unprocessed vertex'); $('#code2').html(' if ∃ an augmenting path'); $('#code3').html('  expand the blossom'); $('#code4').html('  flip the status of edges in the augmenting path'); $('#code5').html(' else if there is an odd-length cycle'); $('#code6').html('  contract the odd cycle and update the graph'); $('#code7').html(''); break; case 5: $('#code1').html('go through each edge and pair them randomly'); $('#code2').html('while ∃ an unprocessed vertex'); $('#code3').html(' if ∃ an augmenting path'); $('#code4').html('  expand the blossom'); $('#code5').html('  flip the status of edges in the augmenting path'); $('#code6').html(' else if there is an odd-length cycle'); $('#code7').html('  contract the odd cycle and update the graph'); break; case 6: // Hungarian algorithm $('#code1').html('find the initial equality subgraph'); $('#code2').html('while ∃ a free vertex on the left'); $('#code3').html(' apply berge\'s lemma on the current left vertex'); $('#code4').html(' if ∃ an augmenting path'); $('#code5').html('  flip the status of the edges in the aug path'); $('#code6').html(' else'); $('#code7').html('  relabel the equality graph'); break; } } // matching_action.js var actionsWidth = 150; var statusCodetraceWidth = 410; // local var bmw, gw; var BIPARTITE_MATCHING = 1 << 0; var WEIGHTED_GRAPH = 1 << 1; var WEIGHTED_BIPARTITE_MATCHING = 1 << 2; var selectedState = -1; $(function() { write(true, true); $('#play').hide(); $('#drawgraph-form').append('
Relayout'); $('#relayout').removeAttr('checked'); bmw = new MCBM(); gw = bmw.getGraphWidget(); gw.setAnimationDuration(700 / speedVal); // randomize first $('#bipartite-n').val(1 + Math.floor(Math.random()*7)); // [1..7] $('#bipartite-m').val(1 + Math.floor(Math.random()*7)); // [1..7] $('#bipartite-d').val(Math.floor(Math.random()*101)); // [0..100] bipartiteRandom(); $("#menu-unweighted-bipartite").on("click", function() { selectedState = BIPARTITE_MATCHING; $(".unweighted-general").hide(); $(".weighted-bipartite").hide(); $(".unweighted-bipartite").show(); $("#menu-unweighted-bipartite").removeClass("selected-viz"); $("#menu-unweighted-general").removeClass("selected-viz"); $("#menu-weighted-bipartite").removeClass("selected-viz"); $("#menu-unweighted-general").html("U/G"); $("#menu-weighted-bipartite").html("W/B"); $("#menu-unweighted-bipartite").addClass("selected-viz"); $("#menu-unweighted-bipartite").html("(Unweighted Bipartite) Graph Matching"); $("#example-houseofcards").hide(); $("#example-fmod").hide(); //$("#example4").hide(); $("#augpathnormal").show(); $("#augpathgreedy").show(); $("#hopcraftkarp").show(); $("#edmondsblossom").hide(); $("#edmondsblossomplusgreedy").hide(); $("#rookattack").show(); $("#weightedbipartite").hide(); $("#bipartite").show(); $("#example-k22").show(); $("#example-xbar").show(); $("#example-fmod").show(); $("#example-corner-case").show(); $("#example-special-case").show(); $("#example-performance").show(); $("#example-capacity").show(); $("#example-waif").show(); $("#example-greedyable").show(); $("#example-theorem").show(); $("#example-weighted-bipartite").hide(); $("#example-weighted-bipartite_tum").hide(); bipartiteRandom(); // always different every time }); $("#menu-weighted-bipartite").on("click", function() { selectedState = WEIGHTED_BIPARTITE_MATCHING; $(".unweighted-general").hide(); $(".unweighted-bipartite").hide(); $(".weighted-bipartite").show(); $("#menu-unweighted-bipartite").removeClass("selected-viz"); $("#menu-unweighted-general").removeClass("selected-viz"); $("#menu-weighted-bipartite").removeClass("selected-viz"); $("#menu-unweighted-bipartite").html("U/B"); $("#menu-unweighted-general").html("U/G"); $("#menu-weighted-bipartite").addClass("selected-viz"); $("#menu-weighted-bipartite").html("(Weighted Bipartite) Graph Matching"); $("#augpathnormal").hide(); $("#augpathgreedy").hide(); $("#hopcraftkarp").hide(); $("#edmondsblossom").hide(); $("#edmondsblossomplusgreedy").hide(); $("#rookattack").hide(); $("#weightedbipartite").show(); $("#bipartite").hide(); $("#example-k22").hide(); $("#example-xbar").hide(); $("#example-fmod").hide(); $("#example-corner-case").hide(); $("#example-special-case").hide(); $("#example-performance").hide(); $("#example-capacity").hide(); $("#example-waif").hide(); $("#example-greedyable").hide(); $("#example-theorem").hide(); $("#example-weighted-bipartite").show(); $("#example-weighted-bipartite_tum").show(); weightedBipartiteRandom(); // always different every time }); $("#menu-unweighted-general").on("click", function() { selectedState = 0; $(".unweighted-bipartite").hide(); $(".weighted-bipartite").hide(); $(".unweighted-general").show(); $("#menu-unweighted-bipartite").removeClass("selected-viz"); $("#menu-unweighted-general").removeClass("selected-viz"); $("#menu-weighted-bipartite").removeClass("selected-viz"); $("#menu-unweighted-bipartite").html("U/B"); $("#menu-weighted-bipartite").html("W/B"); $("#menu-unweighted-general").addClass("selected-viz"); $("#menu-unweighted-general").html("(Unweighted General) Graph Matching"); $("#example-houseofcards").show(); $("#example-fmod").show(); //$("#example4").show(); $("#augpathnormal").hide(); $("#augpathgreedy").hide(); $("#hopcraftkarp").hide(); $("#edmondsblossom").show(); $("#edmondsblossomplusgreedy").show(); $("#rookattack").show(); $("#weightedbipartite").hide(); $("#bipartite").show(); $("#example-k22").show(); $("#example-xbar").show(); $("#example-fmod").show(); $("#example-corner-case").show(); $("#example-special-case").show(); $("#example-performance").show(); $("#example-capacity").show(); $("#example-waif").show(); $("#example-greedyable").show(); $("#example-theorem").show(); $("#example-weighted-bipartite").hide(); $("#example-weighted-bipartite_tum").hide(); bmw.generalRandom(); }); $("#menu-unweighted-bipartite").click(); var graphJSON = getQueryVariable("create"); if (graphJSON.length > 0) { importjson(graphJSON); window.history.pushState("object or string", "Title", window.location.href.split('?')[0]); } // userGraph = bmw.getGraph(); // save the current graph }); function closeAugPath() { closeAction('augpath'); } function importjson(text) { if(isPlaying) { stop(); } if (mode=="exploration") { bmw.importjson(text); isPlaying = false; } } function drawGraph() { if(isPlaying) { stop(); } if (mode=="exploration") { const [newiVL, newiEL] = representationConvert(bmw.getiVL(), bmw.getiEL()); if (selectedState == WEIGHTED_BIPARTITE_MATCHING) { currentGraphVisu = new GraphVisu(true, false, true, newiVL, newiEL, true); } else { currentGraphVisu = new GraphVisu(true, true, true, newiVL, newiEL, true); } $('#dark-overlay').fadeIn(function(){ $('#drawgraph').fadeIn(); }); bmw.startLoop(); isPlaying = false; } } function inputGraph() { if(isPlaying) { stop(); } if (mode=="exploration") { const [newiVL, newiEL] = representationConvert(bmw.getiVL(), bmw.getiEL()); if (selectedState == WEIGHTED_BIPARTITE_MATCHING) { currentGraphVisu = new GraphVisu(true, false, true, newiVL, newiEL, true); } else { currentGraphVisu = new GraphVisu(true, true, true, newiVL, newiEL, true); } $('#dark-overlay').fadeIn(function() { $('#drawgraph').fadeIn(function() { $('#custom-graph-input').fadeIn(); showGraphInput(); }); }); bmw.startLoop(); isPlaying = false; } } function drawDone() { if (!bmw.draw()) return false; bmw.stopLoop(); $('#drawgraph').fadeOut(); $('#dark-overlay').fadeOut(); } function drawCancel() { bmw.stopLoop(); $('#drawgraph').fadeOut(); $('#dark-overlay').fadeOut(); } function createRandom() { return; // this function is temporarily disabled if(isPlaying) { stop(); } if (mode=="exploration") { var n = Math.floor(5 + Math.random()*6); $.ajax({ url: PHP_DOMAIN + "/php/Graph.php?mode=" + MODE_GET_RANDOM_SUBMITTED_GRAPH + "&directed=" + 0 + "&connected=" + 1 + "&bipartite=" + 1 + "&separatable=" + 1 }).done(function(data){ data = JSON.parse(data); var graph = extractQnGraph(data.graph); randomGraphID = data.graphID; bmw.initRandom(graph); $('#rate-sample-graph').show(); }) setTimeout(function(){ $('#progress-bar').slider( "option", "max", 0); isPlaying = false; },500); } } function example(id) { if (isPlaying) stop(); setTimeout(function() { if (bmw.examples(id)) { $('#progress-bar').slider("option", "max", 0); isPlaying = false; } }, 500); } function augmentingPath(callback) { if (isPlaying) stop(); commonAction(bmw.augmentingPath(false, callback), "Augmenting Path Algorithm"); } function greedyAug(callback) { if (isPlaying) stop(); commonAction(bmw.augmentingPath(true, callback), "Augmenting Path Algorithm Plus"); } function hopcroftKarp(callback) { if (isPlaying) stop(); commonAction(bmw.hopcroftKarpViz(callback), "Hopcroft-Karp Algorithm"); } // function modelingOpen(modelingType) { // $(".modeling").css("bottom","117px"); // if (modelingType != "rookattack") $('#rookattack-input').fadeOut('fast'); // if (modelingType != "baseball") $('#baseball-input').fadeOut('fast'); // if (modelingType != "bipartite") $('#bipartite-input').fadeOut('fast'); // $('#' + modelingType + '-input').fadeIn('fast'); // } function modeling(modelingType) { /* if(isPlaying) { stop(); } setTimeout( function() { if (mode != "exploration") return; if (!bmw.modeling(modelingType)) return; }, 500); */ commonAction(bmw.modeling(modelingType), "Modeling a Bipartite Graph"); } function bipartiteRandom() { var n = parseInt($('#bipartite-n').val()); var m = parseInt($('#bipartite-m').val()); var d = parseInt($('#bipartite-d').val()); bmw.bipartiteRandom(n, m, d); closeAction('modeling'); setTimeout(function() { $('#bipartite-n').val(1 + Math.floor(Math.random()*7)); // [1..7] $('#bipartite-m').val(1 + Math.floor(Math.random()*7)); // [1..7] $('#bipartite-d').val(Math.floor(Math.random()*101)); // [0..100] }, 500); } function weightedBipartiteRandom() { var n = parseInt($('#weighted-bipartite-n').val()); bmw.weightedBipartiteRandom(n); closeAction('modeling'); setTimeout(function() { var a = 2 + Math.floor(Math.random()*4); $('#weighted-bipartite-n').val(a); // [1..7] }, 500); } /* function edmondsBlossomViz(performGreedyAssignment) { if (isPlaying) {stop();} setTimeout( function() { if ((mode=="exploration")&&bmw.edmondsBlossomViz(performGreedyAssignment)) { $('#current-action').show(); $('#current-action p').html("Greedy + Augmenting Path MCBM"); $('#progress-bar').slider("option","max",gw.getTotalIteration()-1); triggerRightPanels(); isPlaying = true; } }, 500); } */ function edmondsBlossomViz(performGreedyAssignment, callback) { if (isPlaying) stop(); commonAction(bmw.edmondsBlossomViz(performGreedyAssignment, callback), "Edmonds' Matching Algorithm" + (performGreedyAssignment ? " Plus" : "")); } function hungarian(callback) { if (isPlaying) stop(); commonAction(bmw.hungarian(callback), 'Hungarian Algorithm'); } // Implement these functions in each visualisation var userGraph = { 'vl': {}, 'el': {}, }; // This function will be called before entering E-Lecture Mode function ENTER_LECTURE_MODE() { //if (bmw) userGraph = bmw.getGraph(); } // This function will be called before returning to Explore Mode function ENTER_EXPLORE_MODE() { //loadGraph(userGraph); } // Lecture action functions function CUSTOM_ACTION(action, data, mode) { if (action == 'augmentingpath') { hideSlide(function() { augmentingPath(showSlide); }); } else if (action == 'hopcroftkarp') { hideSlide(function() { hopcroftKarp(showSlide); }); } else if (action == 'greedyaug') { hideSlide(function() { greedyAug(showSlide); }); } else if (action == 'edmonds') { hideSlide(function() { edmondsBlossomViz(false, showSlide); }); } }