Monday, January 2, 2012

Key logger (Kernel space)

I am done with the development of a new Linux key logger (Kernel Space)..

Simply its a Linux kernel module that sniffs key strokes and saves it in an in-memory buffer, and then any user space can read it from /dev/klg virtual device node.

You can download the source code and try it yourself from sourceforge.

Here is the video demo for it..



i will supply a link to download another user-space one soon :)

Sunday, January 1, 2012

Apache JMeter for Performance/Load Testing

In this post i will describe the benefits and usage of JMeter as well as the installation steps.

Agenda
  • Introduction
  • Features
  • Installing JMeter
  • Using JMeter
    • Test plan
    • Building a simple web test plan
  • References
Introduction

The Apache JMeter™ desktop application is open source software, a 100% pure Java application designed to load test functional behavior and measure performance. It was originally designed for testing Web Applications but has since expanded to other test functions. 

Apache JMeter may be used to test performance both on static and dynamic resources (files, Servlets, Perl scripts, Java Objects, Data Bases and Queries, FTP Servers and more). It can be used to simulate a heavy load on a server, network or object to test its strength or to analyze overall performance under different load types. You can use it to make a graphical analysis of performance or to test your server/script/object behavior under heavy concurrent load. 

Features
  • Can load and performance test many different server types:
    • Web - HTTP, HTTPS
    • SOAP
    • Database via JDBC
    • LDAP
    • JMS
    • Mail - POP3(S) and IMAP(S)
  • Complete portability and 100% Java purity .
  • Full multithreading framework allows concurrent sampling by many threads and simultaneous sampling of different functions by seperate thread groups.
  • Careful GUI design allows faster operation and more precise timings.
  • Caching and offline analysis/replaying of test results.
  • Highly Extensible:
    • Pluggable Samplers allow unlimited testing capabilities.
    • Several load statistics may be choosen with pluggable timers .
    • Data analysis and visualization plugins allow great extendibility as well as personalization.
    • Functions can be used to provide dynamic input to a test or provide data manipulation.
    • Scriptable Samplers (BeanShell is fully supported; and there is a sampler which supports BSF-compatible languages)

Installing JMeter
  • Download the appropriate jmeter package (Here)
  • Extract the package (e.g., Linux use tar xvf PACK_NAME)
  • Go to the bin directory (e.g. Linux cd PACK_PATH/bin)
  • Run the jmeter.sh (Linux) jmeter.bat (Windows) (e.g. Linux ./jmeter on the terminal)
  • The previous step will require JVM to be installed on your machine (Here)
  • Check the PACK_PATH/docs for information of how to use it
Using JMeter

Test Plan

A test plan describes a series of steps JMeter will execute when run. A complete test plan will consist of one or more Thread Groups, logic conrollers, sample generating controllers, listeners, timers, assertions, and configuration elements. For more information about Test plan see Building a Test Plan.

Building a simple web test plan 

See the Building a Web Test Plan tutorial on the apache web site, Also you may go and have a look at my latest article about Apache JMeter along with JSF.

References 

Hudson (For java geeks)

While searching for a java program like ccache and distcc i found this project that includes the features included in both ccache and distcc and much more features.

Note: Distcc is a program that distribute builds of C/C++ across several machines on a network, while ccache acts as a cache pre-processor to C/C++ compilers, which results in 5 to 10 times speedup in common compilations.

Agenda
  • Introduction
  • Hudson features
  • Hudson best practices
  • Hudson eclipse plugin
  • How to contribute?
  • References
Introduction

Hudson monitors executions of repeated jobs, such as building a software project or jobs run by cron. Among those things, current Hudson focuses on the following two jobs:

Building/testing software projects continuously, Hudson provides an easy-to-use so-called continuous integration system, making it easier for developers to integrate changes to the project, and making it easier for users to obtain a fresh build. The automated, continuous build increases the productivity. 

Monitoring executions of externally-run jobs, such as cron jobs and procmail jobs, even those that are run on a remote machine. For example, with cron, all you receive is regular e-mails that capture the output, and it is up to you to look at them diligently and notice when it broke. Hudson keeps those outputs and makes it easy for you to notice when something is wrong.


Hudson features

Hudson offers the following features and much more:
  •  Change set support: Hudson can be configured entirely from its friendly web GUI with extensive on-the-fly error checks and inline help. There's no need to tweak XML manually anymore, although if you'd like to do so, you can do that, too.
  •  Permanent links: Hudson gives you clean readable URLs for most of its pages, including some permalinks like "latest build"/"latest successful build", so that they can be easily linked from elsewhere.
  • RSS/E-mail/IM Integration: Monitor build results by RSS or e-mail to get real-time notifications on failures. 
  • After-the-fact tagging: Builds can be tagged long after builds are completed 
  • Distributed builds: Hudson can distribute build/test loads to multiple computers. This lets you get the most out of those idle workstations sitting beneath developers' desks. 
  • File fingerprinting: Hudson can keep track of which build produced which jars, and which build is using which version of jars, and so on. This works even for jars that are produced outside Hudson, and is ideal for projects to track dependency. 
  • Easy installation: Just java -jar hudson.war for testing. Use a native package or deploy it in a servlet container for production use. No additional install, no database.
  • Easy configuration: Hudson can be configured entirely from its friendly web GUI with extensive on-the-fly error checks and inline help. There's no need to tweak XML manually anymore, although if you'd like to do so, you can do that, too. 
Hudson best practices 

Continuous integration with automated test execution has seen board adoption in recent years. The ideas behind continuous integration have been changed how companies look at Build Management, Release Management, Deployment Automation, and Test Orchestration. This section provides a set of best practices for Hudson - A Continuous integration to provide executives, business managers, software developers and architects a better sense of the development progress and code quality of projects throughout the development lifecycle. (View Hudson Best Practices)

Hudson eclipse plugin 

Hudson integrates with Netbeans, Eclipse, and many other IDEs and tools, check this link for a list of tools integrates with hudson.

Install using the Update Manager or drop dk.contix.eclipse.hudson_x.x.x.jar in the plugins/ dir.
The update site url is URL.
 
After installation, the plugin must be configured. This is done in Preferences under Hudson. The important parameter is Hudson base url, which should point to the main Hudson page.

When the plugin is running, a health icon is displayed at the bottom of the Eclipse window. The icon is red on build failure and green on success. Double-click on the icon to open the Hudson view, where all projects in Hudson can be tracked. Please note that due to limitations in Eclipse, the Hudson view must be active before the icon is displayed. 

How to contribute? 

Check out this page.

References 

Wednesday, December 28, 2011

Minix 3 System event framework (SEF)

In this post i will give an introduction into the minix 3 OS System Event Framework (SEF), and its usage and the benefits behind it. If you are not familiar with the minix 3 OS you may need to have a look at this post.

Agenda
  • Introduction
  • Event types
  • Call-backs
  • References
Introduction

SEF is a component of the system library that deals with system events in a centralized and convenient way. In MINIX 3 every driver and system server has to deal with the System Event Framework (SEF) which handles live up-dates and failure recovery.

Every system service (server or device driver) must call sef_startup() at startup time to handle initialization and sef_receive() when receiving a message. The developer can register callbacks to provide handlers for each particular type of event. When no callback is registered for one particular event, the default behavior is assumed. The developer can also reuse some predefined callback implementations provided by SEF for each particular event type.

Event types

Initialization

Triggered by initialization messages sent by the Reincarnation Server when a service is started. The API and the predefined callback implementations are declared in <minix/sef.h> and defined in lib/syslib/sef_init.c.

Ping

Triggered by keep-a-live messages sent by the Reincarnation Server periodically to check the status of a system service. The API and the predefined callback implementations are declared in <minix/sef.h> and defined in lib/libsys/sef_ping.c.

Live update

Triggered by live update messages sent by the Reincarnation Server when an update is available for a particular system service. The API and the predefined callback implementations are declared in <minix/sef.h> and defined in lib/libsys/sef_liveupdate.c.


Signal (Not included in the minix wiki, i got it from the code)

Triggered to intercept process signals (e.g., SIGTERM). The API and the predefined callback implementations are declared in <minix/sef.h> and defined in lib/libsys/sef_signal.c.

Callbacks (This section is totally extracted (by me) from the component code)


Initialization callbacks

void sef_setcb_init_fresh(sef_cb_init_t cb)


Set the start up init callback, if cb is null then the default init callback is assumed.

void sef_setcb_init_lu(sef_cb_init_t cb)

Set the live update init callback, if cb is null then the default live update init callback is assumed.

void sef_setcb_init_restart(sef_cb_init_t cb)

Set the restart init callback, if cb is null then the default restart init callback is assumed.

sef_setcb_init_response(sef_cb_init_response_t cb)

Set the init response callback for example (notify the Reincarnation Server that we completed init)

Ping callbacks

void sef_setcb_ping_reply(sef_cb_ping_reply_t cb)

Set the ping reply callback, two default implementations introduced either do nothing (sef_cb_ping_reply_null), or ack (sef_cb_ping_reply_pong).

Live update callbacks

void sef_setcb_lu_prepare(sef_cb_lu_prepare_t cb)

Set the live update prepare callback, if cb is null default callback is assumed.

void sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_t cb)

Set the live update is valid state callback, when a live update encountered the RS must assure that the driver/server is in a valid state for live update.

void sef_setcb_lu_state_changed(sef_cb_lu_state_changed_t cb)

Set the live update changed state callback.

void sef_setcb_lu_state_dump(sef_cb_lu_state_dump_t cb)

Set the live update state dump callback -> I think its for debugging information or something like that.

void sef_setcb_lu_state_save(sef_cb_lu_state_save_t cb)

Set the live update stat save callback.

void sef_setcb_lu_response(sef_cb_lu_response_t cb)

Set the live update response callback, for example telling the RS that we are up and running.

Signal callbacks

void sef_setcb_signal_handler(sef_cb_signal_handler_t cb) 

Set the signal handler callback. Default implementation is either to ignore all signals, or handling the term signal (Terminate in this case), Also it provide a case to handle posix signals.

void sef_setcb_signal_manager(sef_cb_signal_manager_t cb)

Still can't know its purpose till now.

References
Feed backs are more than welcomed.

Tuesday, December 27, 2011

Problem solving techniques (Example problems) (Cont.)

In this post i will show how to use the divide and conquer technique to solve problems, you may need to check previous post.


Problem 3 (Binary search)

Given a list of sorted integer numbers and an integer, find the occurrence of this number into the list or -1 if it doesn't exist.


Discussion 

We can linear search for this list O(N), but with the fact that the list is sorted we can at each time get the middle element of the array and minimize the search space into either the left half part (if the searched element is less than the mid element), or the right half (if the searched element is greater than the mid element). O(lg N) where N is number of elements.

Code

int binary_search(vector<int> vec, int src, int dest, int elem) {
    if(dest < src) return -1;
    int mid = (src+dest+1)/2;
  
    if(elem == vec[mid]) return mid;
    else if(elem < vec[mid]) return binary_search(vec, src, mid-1, elem);
    else return binary_search(vec, mid+1, dest, elem);
}

Problem 4 (LargestSubsequence)
Topcoder SRM 518 (DIV 1 level 1, DIV 2 level 2)

For Strings x and y, we say y is a subsequence of x if y can be obtained from x by erasing some (possibly all or none) of the letters in x. For example, "tpcdr" is a subsequence of "topcoder", while "rt" is not.
Given a String s, return the lexicographically largest subsequence of s.

Discussion

For example if the passed string is "test" then all subsequences listed in lexicographical order are "" (empty string), "e", "es", "est", "et", "s", "st", "t", "te", "tes", "test", "tet", "ts", "tst" and "tt". So return "tt".

Code

string getLargest(string s) {
    if(s.size() == 1)
        return s;
    string tmp = getLargest(s.substr(1));
    if(tmp[0] <= s[0])
        return s[0]+tmp;
    return tmp;
}

Problem 5 (Merge sort)

Merge sort is based on the divide-and-conquer paradigm. Its worst-case running time has a lower order of growth than insertion sort. Since we are dealing with subproblems, we state each subproblem as sorting a subarray A[p .. r]. Initially, p = 1 and r = n, but these values change as we recurse through subproblems.


To sort A[p .. r]:

1. Divide Step
If a given array A has zero or one element, simply return; it is already sorted. Otherwise, split A[p .. r] into two subarrays A[p .. q] and A[q + 1 .. r], each containing about half of the elements of A[p .. r]. That is, q is the halfway point of A[p .. r].
2. Conquer Step
Conquer by recursively sorting the two subarrays A[p .. q] and A[q + 1 .. r].
3. Combine Step
Combine the elements back in A[p .. r] by merging the two sorted subarrays A[p .. q] and A[q + 1 .. r] into a sorted sequence. To accomplish this step, we will define a procedure MERGE (A, p, q, r).
 Code

vector<int> merge(vector<int> a, vector<int> b) {
    vector<int> res;
    int sz = (a.size()+b.size()), i = 0, j = 0;
  
    a.push_back(INT_MAX);
    b.push_back(INT_MAX);
 
    while(res.size() < sz) {
        if(a[i] <= b[j]) {
            res.push_back(a[i]);
            ++i;
        }
        else {
            res.push_back(b[j]);
            ++j;
        }
    }
 
    return res;
}


vector<int> merge_sort(vector<int> A) {
    if(A.size() <= 1) return A; // Already sorted
    vector<int> left, right, res;
 
    int i, mid = A.size()/2;
  
    for(i = 0; i < mid; i++) left.push_back(A[i]);
    for(i = mid; i < A.size(); i++) right.push_back(A[i]);
 
    left = merge_sort(left);
    right = merge_sort(right);
    res = merge(left, right);
  
    return res;
}



In the next post i will show sample problems solved using the Dynamic programming technique.

Problem solving techniques (Cont.) Example problems

In this post and the up coming future posts i will show how to use the techniques discussed in my earlier posts problem solving techniques (part 1 and part 2) to solve problems.

Complete search (brute force) examples

Problem 1 (Building a House)
Code jam (Qualification Round Africa and Arabia 2011)

You have just bought land and want to plant the largest rectangular field possible. In surveying your land, you find a number of obstacles and decide to draw a map. You indicate in each square of the map whether it contains grass (G), rock (R), water (W), shrubs (S), or trees (T). While the grass can be mowed and the shrubs dug from the ground, the water, rocks, and trees cannot be removed. Given these obstacles, determine the area of the largest rectangular field.

Input

The first line of input gives the number of cases, N.
N test cases follow. For each test case there will be:
  • One line containing two space-separated integers indicating the length (L) and width (W) of your land.
  • Followed by, W lines, each containing L characters where each indicates the conditions for that square of land (one of G, R, W, S, or T).

Output

For each test case, output one line containing "Case #x: " followed by the maximum area of the largest rectangle that can be cleared.

Limits:
1 ≤ L ≤ 50
1 ≤ W ≤ 50

N ≤ 30
Fewer than 20 obstacles in each test case. 

Discussion

From the constraints above we can check that a brute-force solution could survive. With the mathematical fact that we can represent a rectangle with only two points, we can iterate all possible combination's of two points and check if the rectangle represented with those two points include an obstacle or not. O(L^2*W^2*C) where c is the number of obstacles in the grid.

Code

vector<point> obstacles;
int res = -1;

for (i = 0; i < l; i++)
for (j = 0; j < w; j++)
if(grid[i][j] != 'G' || grid[i][j] != 'S') obstacles.push_back(point(i, j));

for(i = 0; i < l; i++)
for(j = 0; j < w; j++)
for(a = i; a < l; a++)
for(b = j; b < w; b++)
{
for(c = 0; c < obstacles.size(); c++)
if(obstacles[c].x >= i && obstacles[c].x <= a && obstacles[c].y >= j && obstacles[c].y <= b) break;
if(c >= obstacles.size()) res = max(res, (a-i+1)*(b-j+1));
}

cout << res << endl;

Problem 2 (SRMCodingPhase)
Topcoder (SRM 520 Div 2 level 2, Div 1 level 1)

Mr. Dengklek introduced you to an online programming contest called SRM (Special Round Match)!

You are now in the coding phase of the contest. There are 3 problems in the contest. You have practiced a lot before in practice rooms, so you are sure that you can solve the first problem in skills[0] minutes, the second problem in skills[1] minutes and the third problem is skills[2] minutes.

You have exactly 75 minutes to solve the problems. Before submitting a solution to a problem, you must first open the problem. If you submit a solution to a problem t minutes after you open the problem, you will receive:
  • (points[0] - 2t) points for the first problem, or
  • (points[1] - 4t) points for the second problem, or
  • (points[2] - 8t) points for the third problem.

In your strategy, you only submit a solution to a problem after you solve the problem. If you don't submit a solution to a problem, you will receive zero points for the problem.

It is well-known that luck plays an important role in a contest. A fortune-teller told you that you have luck points of luck. You may use these points to decrease the amount of time you need to solve the problems, in minutes. Of course, you don't have to use all the points. Each point is worth one minute per problem. So, if you initially can solve a problem in t minutes, by using x points of luck (where x is a positive integer and 0 < x < t), you can solve the problem in (t - x) minutes (it is impossible to use t or more points of luck on the problem).

Arrange your strategy in this coding phase. Return the maximum total score you can achieve in this coding phase.

Constraints

-points will contain exactly 3 elements.
-points[0] will be between 250 and 300, inclusive.
-points[1] will be between 450 and 600, inclusive.
-points[2] will be between 900 and 1100, inclusive.
-skills will contain exactly 3 elements.
-Each element of skills will be between 1 and 100, inclusive.
-luck will be between 0 and 100, inclusive.

Discussion

From the problem constraints we can loop through all possible combination's of solved problems and compute the maximum points gained each time,  caution about the tricky luck part, we can use greedy to pass it.

Code

int countScore(vector <int> points, vector <int> skills, int luck) {
   
        int res = 0;
      
        for(int i = 0; i < 2; i++) // 1 solve, 0 not solve
        for(int j = 0; j < 2; j++) // 1 solve, 0 not solve
        for(int s = 0; s < 2; s++) // 1 solve, 0 not solve
        {
            vector<int> lucky(3, 0);
            int minutes = 0, tmp = 0;
          
            if(s == 1) { // Solving problem 3
                lucky[2] = min(skills[2]-1, luck); // Compute the lucky for prob 3
                minutes += skills[2]; // Add the time for solving prob 3
                tmp += points[2]-8*(skills[2]-lucky[2]); // Add the points gained from solving prob 3
            }
            if(j == 1) { // solving problem 2
                lucky[1] = min(skills[1]-1, luck-lucky[2]); // Compute the lucky of solving prob 2
                minutes += skills[1]; // Add the time for solving prob 2
                tmp += points[1]-4*(skills[1]-lucky[1]); // Add the points gained from solving prob 2
            }
            if(i == 1) {
                lucky[0] = min(skills[0]-1, luck-lucky[2]-lucky[1]); // Compute the lucky for prob 1
                minutes += skills[0]; // Add the time for solving prob 1
                tmp += points[0]-2*(skills[0]-lucky[0]); // Add the points gained from solving prob 1
            }  
          
            if(minutes-luck <= 75) { // Check that we can solve the probs within time
                res = max(res, tmp); // Check the max num of points
            }
        }
      
        return res;
    }


The next post show how to solve specific types of problems using Divide and Conquer technique.

Sunday, December 25, 2011

Problem solving techniques (Cont.)

In this post i will complete the post Problem solving techniques

Backtracking

Backtracking is a refinement of the brute force approach, which systematically searches for a solution to a problem among all available options. It does so by assuming that the solutions are represented by vectors (v1, ..., vm) of values and by traversing, in a depth first manner, the domains of the vectors until the solutions are found. 

When invoked, the algorithm starts with an empty vector. At each stage it extends the partial vector with a new value. Upon reaching a partial vector (v1, ..., vi) which can’t represent a partial solution, the algorithm backtracks by removing the trailing value from the vector, and then proceeds by trying to extend the vector with alternative values. 

Graph Algorithms

Representing a graph

Array representation

The basic concept is to have a 2 dimensional array of integers, where the element in row i, at column j represents the edge cost from node i to j. If the connection from i to j is not possible, we use some sort of sentinel value (usually a very large or small value, like -1 or the maximum integer). Another nice thing about this type of structure is that we can represent directed or undirected edges very easily.

So for example, the following connection matrix:

    A  B  C
A   0  1  5
B  -1  0  1
C  -1 -1  0

Would mean that node A has a 0 weight connection to itself, a 1 weight connection to node B and 5 weight connection to node C. Node B on the other hand has no connection to node A, a 0 weight connection to itself, and a 1 weight connection to C. Node C is connected to nobody. This graph would look like this if you were to draw it:



This representation is very convenient for graphs that do not have multiple edges between each node, and allows us to simplify working with the graph.


Adjacency list

In this representation the graph can be represented as a big list of nodes, each node can be represented as a structure that holds data section and a list of indicies (Indicies of the childs).

structure node
   vector<int> childs;
   [data]
end

vector<node> graph;


Searching a graph

Depth first search

The depth first search is well geared towards problems where we want to find any solution to the problem (not necessarily the shortest path), or to visit all of the nodes in the graph.

The basic concept is to visit a node, then push all of the nodes to be visited onto the stack. To find the next node to visit we simply pop a node of the stack, and then push all the nodes connected to that one onto the stack as well and we continue doing this until all nodes are visited. It is a key property of the Depth First search that we not visit the same node more than once, otherwise it is quite possible that we will recurse infinitely. We do this by marking the node as we visit it, then unmarking it after we have finished our recursions. This action allows us to visit all the paths that exist in a graph; however for large graphs this is mostly infeasible so we sometimes omit the marking the node as not visited step to just find one valid path through the graph (which is good enough most of the time).

So the basic structure will look something like this:

dfs(node start) {
 stack s;
 s.push(start);
 while (s.empty() == false) {
  top = s.top();
  s.pop();
  mark top as visited;

  check for termination condition

  add all of top's unvisited neighbors to the stack.
  mark top as not visited;
 }
}
 
Alternatively we can define the function recursively as follows:
 
dfs(node current) {
 mark current as visited;
 visit all of current's unvisited neighbors by calling dfs(neighbor)
 mark current as not visited;
}

Breadth First Search

The Breadth First search is an extremely useful searching technique. It differs from the depth-first search in that it uses a queue to perform the search, so the order in which the nodes are visited is quite different. It has the extremely useful property that if all of the edges in a graph are unweighted (or the same weight) then the first time a node is visited is the shortest path to that node from the source node. You can verify this by thinking about what using a queue means to the search order. When we visit a node and add all the neighbors into the queue, then pop the next thing off of the queue, we will get the neighbors of the first node as the first elements in the queue. This comes about naturally from the FIFO property of the queue and ends up being an extremely useful property. One thing that we have to be careful about in a Breadth First search is that we do not want to visit the same node twice, or we will lose the property that when a node is visited it is the quickest path to that node from the source.

The basic structure of a breadth first search will look this:
void bfs(node start) {
 queue s;
 s.push(start);
 while (s.empty() == false) {
  top = s.front();
  s.pop();
  mark top as visited;
 }
}

Shortest path

Dijkstra algorithm

It essentially allows you to write a Breadth First search, and instead of using a Queue you use a Priority Queue and define a sorting function on the nodes such that the node with the lowest cost is at the top of the Priority Queue. This allows us to find the best path through a graph in O(m * log(n)) time where n is the number of vertices and m is the number of edges in the graph. 

Graph algorithms

Maximum flow, minimum  cut problem

In this problem we are given a directed graph, in which every edge has a certain capacity c associated with it, a starting vertex (the source, X in the example above), and an ending vertex (the sink). We are asked to associate another value f satisfying f ≤ c for each edge such that for every vertex other than the source and the sink, the sum of the values associated to the edges that enter it must equal the sum of the values associated to the edges that leave it. We will call f the flow along that edge. Furthermore, we are asked to maximize the sum of the values associated to the arcs leaving the source, which is the total flow in the network. 

Now how do we actually solve the problem? First, let us define two basic concepts for understanding flow networks: residual networks and augmenting paths. Consider an arbitrary flow in a network. The residual network has the same vertices as the original network, and one or two edges for each edge in the original. More specifically, if the flow along the edge x-y is less than the capacity there is a forward edge x-y with a capacity equal to the difference between the capacity and the flow (this is called the residual capacity), and if the flow is positive there is a backward edge y-x with a capacity equal to the flow on x-y. An augmenting path is simply a path from the source to the sink in the residual network, whose purpose is to increase the flow in the original one. It is important to understand that the edges in this path can point the "wrong way" according to the original network. The path capacity of a path is the minimum capacity of an edge along that path.

The algorithm starts with no flow everywhere and increase the total flow in the network while there is an augmenting path from the source to the sink with no full forward edges or empty backward edges - a path in the residual network. The algorithm (known as the Ford-Fulkerson method) is guaranteed to terminate: due to the capacities and flows of the edges being integers and the path-capacity being positive, at each step we get a new flow that is closer to the maximum. As a side note, the algorithm isn't guaranteed to even terminate if the capacities are irrationals.

Now we are done with the theoretical part of studying algorithms. Example problems