COIN-OR::LEMON - Graph Library

Ticket #168: bp_benchmark_ea93dc149092.patch

File bp_benchmark_ea93dc149092.patch, 20.5 KB (added by Poroszkai Daniel, 9 years ago)

Benchmark tools

  • lemon/dimacs.h

    # HG changeset patch
    # User Daniel Poroszkai <poroszd@inf.elte.hu>
    # Date 1328397809 -3600
    # Node ID ea93dc1490922914aac7dbfaeb71e9cffa7eaf6d
    # Parent  55711f25c446935bd8fda1fa3c6b556d3679c041
    Tools for bipartite matching benchmarks
    
    diff --git a/lemon/dimacs.h b/lemon/dimacs.h
    a b  
    22 *
    33 * This file is a part of LEMON, a generic C++ optimization library.
    44 *
    5  * Copyright (C) 2003-2010
     5 * Copyright (C) 2003-2012
    66 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
    77 * (Egervary Research Group on Combinatorial Optimization, EGRES).
    88 *
     
    4545      MIN,   ///< DIMACS file type for minimum cost flow problems.
    4646      MAX,   ///< DIMACS file type for maximum flow problems.
    4747      SP,    ///< DIMACS file type for shostest path problems.
    48       MAT    ///< DIMACS file type for plain graphs and matching problems.
     48      MAT,   ///< DIMACS file type for plain graphs and matching problems.
     49      ASN,   ///< DIMACS file type for assignment problems.
     50      UBM,   ///< DIMACS file type for unweighted bipartite matching problems.
     51      WBM    ///< DIMACS file type for weighted bipartite matching problems.
    4952    };
    5053    ///The file type
    5154    Type type;
     
    8285              else if(problem=="max") r.type=DimacsDescriptor::MAX;
    8386              else if(problem=="sp") r.type=DimacsDescriptor::SP;
    8487              else if(problem=="mat") r.type=DimacsDescriptor::MAT;
     88              else if(problem=="asn") r.type=DimacsDescriptor::ASN;
     89              else if(problem=="ubm") r.type=DimacsDescriptor::UBM;
     90              else if(problem=="wbm") r.type=DimacsDescriptor::WBM;
    8591              else throw FormatError("Unknown problem type");
    8692              return r;
    8793            }
     
    101107  }
    102108
    103109
     110
     111  /// \brief DIMACS unweighted bipartite matching problem reader function.
     112  ///
     113  /// This function reads an unweighted bipartite matching problem instance
     114  /// from DIMACS format, i.e. from a DIMACS file having a line starting with
     115  /// \code
     116  ///   p ubm
     117  /// \endcode
     118  /// and creates the corresponding bipartite graph.
     119  ///
     120  /// At the beginning, \c g is cleared by \c g.clear().
     121  ///
     122  /// If the file type was previously evaluated by dimacsType(), then
     123  /// the descriptor struct should be given by the \c dest parameter.
     124  template <typename BpGraph>
     125  void readDimacsUbm(std::istream& is,
     126                     BpGraph &g,
     127                     DimacsDescriptor desc=DimacsDescriptor())
     128  {
     129    g.clear();
     130
     131    if(desc.type==DimacsDescriptor::NONE) desc=dimacsType(is);
     132    if(desc.type!=DimacsDescriptor::UBM)
     133      throw FormatError("Problem type mismatch");
     134
     135    std::vector<typename BpGraph::Node> nodes(desc.nodeNum + 1, INVALID);
     136    typename BpGraph::Edge e;
     137    std::string problem, str;
     138    char c;
     139    int i, j;
     140
     141    // eat up comment lines and problem line
     142    while (is >> c && (c=='c' || c=='p')) {
     143      getline(is, str);
     144    }
     145    is.unget();
     146
     147    // reading red nodes
     148    while (is >> c && c == 'n') {
     149      is >> i;
     150      getline(is, str);
     151      nodes[i] = g.addRedNode();
     152    }
     153    is.unget();
     154
     155    // remaining nodes are blue:
     156    for (int k = 1; k <= desc.nodeNum; ++k) {
     157      if (nodes[k] == INVALID) nodes[k] = g.addBlueNode();
     158    }
     159
     160    // reading edges
     161    while (is >> c && c == 'a') {
     162      is >> i >> j;
     163      getline(is, str);
     164      e = g.addEdge(g.asRedNode(nodes[i]), g.asBlueNode(nodes[j]));
     165    }
     166  }
     167
     168
     169  /// \brief DIMACS weighted bipartite matching problem reader function.
     170  ///
     171  /// This function reads a weighted bipartite matching problem instance from
     172  /// DIMACS format, i.e. from a DIMACS file having a line starting with
     173  /// \code
     174  ///   p wbm
     175  /// \endcode
     176  /// and creates the corresponding bipartite graph and weight map.
     177  ///
     178  /// At the beginning, \c g is cleared by \c g.clear().
     179  ///
     180  /// If the file type was previously evaluated by dimacsType(), then
     181  /// the descriptor struct should be given by the \c dest parameter.
     182  template <typename BpGraph, typename WeightMap>
     183  void readDimacsWbm(std::istream& is,
     184                     BpGraph &g,
     185                     WeightMap &m,
     186                     DimacsDescriptor desc=DimacsDescriptor())
     187  {
     188    g.clear();
     189
     190    if(desc.type==DimacsDescriptor::NONE) desc=dimacsType(is);
     191    if(desc.type!=DimacsDescriptor::WBM)
     192      throw FormatError("Problem type mismatch");
     193
     194    std::vector<typename BpGraph::Node> nodes(desc.nodeNum + 1, INVALID);
     195    typename BpGraph::Edge e;
     196    std::string problem, str;
     197    char c;
     198    int i, j;
     199    typename WeightMap::Value w;
     200
     201    // eat up comment lines and problem line
     202    while (is >> c && (c=='c' || c=='p')) {
     203      getline(is, str);
     204    }
     205    is.unget();
     206
     207    // reading red nodes
     208    while (is >> c && c == 'n') {
     209      is >> i;
     210      getline(is, str);
     211      nodes[i] = g.addRedNode();
     212    }
     213    is.unget();
     214
     215    // remaining nodes are blue:
     216    for (int k = 1; k <= desc.nodeNum; ++k) {
     217      if (nodes[k] == INVALID) nodes[k] = g.addBlueNode();
     218    }
     219
     220    // reading edges and weights
     221    while (is >> c && c == 'a') {
     222      is >> i >> j >> w;
     223      getline(is, str);
     224      e = g.addEdge(g.asRedNode(nodes[i]), g.asBlueNode(nodes[j]));
     225      m.set(e, w);
     226    }
     227  }
     228
     229
     230  /// \brief DIMACS assignment problem reader function.
     231  ///
     232  /// This function reads an assignment problem instance from DIMACS format,
     233  /// i.e. from a DIMACS file having a line starting with
     234  /// \code
     235  ///   p asn
     236  /// \endcode
     237  /// and creates the corresponding digraph, arc cost- and supply map.
     238  ///
     239  /// At the beginning, \c g is cleared by \c g.clear().
     240  /// Cost of the arcs are written to the cost map,
     241  /// and the supply map contains the supply of the nodes, which is
     242  /// 1 for sources and -1 for sink nodes.
     243  ///
     244  /// If the file type was previously evaluated by dimacsType(), then
     245  /// the descriptor struct should be given by the \c dest parameter.
     246  template <typename Digraph, typename CostMap>
     247  void readDimacsAsn(std::istream& is,
     248                     Digraph &g,
     249                     CostMap &cost,
     250                     typename Digraph::template NodeMap<int> &supply,
     251                     DimacsDescriptor desc=DimacsDescriptor())
     252  {
     253    g.clear();
     254
     255    if(desc.type==DimacsDescriptor::NONE) desc=dimacsType(is);
     256    if(desc.type!=DimacsDescriptor::ASN)
     257      throw FormatError("Problem type mismatch");
     258
     259    std::vector<typename Digraph::Node> nodes(desc.nodeNum + 1, INVALID);
     260    typename Digraph::Arc e;
     261    std::string problem, str;
     262    char c;
     263    int i, j;
     264    typename CostMap::Value co;
     265
     266    for (int k = 1; k <= desc.nodeNum; ++k) {
     267      nodes[k] = g.addNode();
     268      supply.set(nodes[k], -1);
     269    }
     270
     271    // eat up comment lines and problem line
     272    while (is >> c && (c=='c' || c=='p')) {
     273      getline(is, str);
     274    }
     275    is.unget();
     276
     277    // reading source nodes
     278    while (is >> c && c == 'n') {
     279      is >> i;
     280      getline(is, str);
     281      supply.set(nodes[i], 1);
     282    }
     283    is.unget();
     284
     285    // reading arcs
     286    while (is >> c && c == 'a') {
     287      is >> i >> j >> co;
     288      getline(is, str);
     289      e = g.addArc(nodes[i], nodes[j]);
     290      cost.set(e, co);
     291    }
     292  }
     293
    104294  /// \brief DIMACS minimum cost flow reader function.
    105295  ///
    106296  /// This function reads a minimum cost flow instance from DIMACS format,
  • tools/CMakeLists.txt

    diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
    a b  
    1616ADD_EXECUTABLE(dimacs-solver dimacs-solver.cc)
    1717TARGET_LINK_LIBRARIES(dimacs-solver lemon)
    1818
     19ADD_EXECUTABLE(weighted-bp-gen weighted-bp-gen.cc)
     20TARGET_LINK_LIBRARIES(weighted-bp-gen lemon)
     21
     22ADD_EXECUTABLE(unweighted-bp-gen unweighted-bp-gen.cc)
     23TARGET_LINK_LIBRARIES(unweighted-bp-gen lemon)
     24
     25ADD_EXECUTABLE(bp-matching-benchmark bp-matching-benchmark.cc)
     26TARGET_LINK_LIBRARIES(bp-matching-benchmark lemon)
     27
    1928INSTALL(
    2029  TARGETS lgf-gen dimacs-to-lgf dimacs-solver
    2130  RUNTIME DESTINATION bin
  • tools/Makefile.am

    diff --git a/tools/Makefile.am b/tools/Makefile.am
    a b  
    44if WANT_TOOLS
    55
    66bin_PROGRAMS += \
     7        tools/bp-matching-benchmark \
    78        tools/dimacs-solver \
    89        tools/dimacs-to-lgf \
    9         tools/lgf-gen
     10        tools/lgf-gen \
     11        tools/unweighted-bp-gen \
     12        tools/weighted-bp-gen
    1013
    1114dist_bin_SCRIPTS += tools/lemon-0.x-to-1.x.sh
    1215
    1316endif WANT_TOOLS
    1417
     18tools_bp_matching_benchmark_SOURCES = tools/bp-matching-benchmark.cc
    1519tools_dimacs_solver_SOURCES = tools/dimacs-solver.cc
    1620tools_dimacs_to_lgf_SOURCES = tools/dimacs-to-lgf.cc
    1721tools_lgf_gen_SOURCES = tools/lgf-gen.cc
     22tools_unweighted_bp_gen_SOURCES = tools/unweighted-bp-gen.cc
     23tools_weighted_bp_gen_SOURCES = tools/weighted-bp-gen.cc
     24 No newline at end of file
  • new file tools/bp-matching-benchmark.cc

    diff --git a/tools/bp-matching-benchmark.cc b/tools/bp-matching-benchmark.cc
    new file mode 100644
    - +  
     1/* -*- mode: C++; indent-tabs-mode: nil; -*-
     2 *
     3 * This file is a part of LEMON, a generic C++ optimization library.
     4 *
     5 * Copyright (C) 2003-2012
     6 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
     7 * (Egervary Research Group on Combinatorial Optimization, EGRES).
     8 *
     9 * Permission to use, modify and distribute this software is granted
     10 * provided that this copyright notice appears in all copies. For
     11 * precise terms see the accompanying LICENSE file.
     12 *
     13 * This software is provided "AS IS" with no warranty of any kind,
     14 * express or implied, and with no claim as to its suitability for any
     15 * purpose.
     16 *
     17 */
     18
     19#include <lemon/dimacs.h>
     20#include <lemon/maps.h>
     21#include <lemon/smart_graph.h>
     22#include <lemon/time_measure.h>
     23#include <lemon/hopcroft_karp.h>
     24#include <lemon/matching.h>
     25#include <lemon/preflow.h>
     26#include <lemon/bp_matching.h>
     27#include <iostream>
     28
     29using namespace std;
     30using namespace lemon;
     31
     32
     33template <typename BpGraph, typename Digraph>
     34void create_network(const BpGraph &bpg,
     35                    Digraph &network,
     36                    typename Digraph::Node &source,
     37                    typename Digraph::Node &sink)
     38{
     39  network.clear();
     40  source = network.addNode();
     41  sink = network.addNode();
     42
     43  typename BpGraph::template NodeMap<typename Digraph::Node> xref(bpg);
     44  for (typename BpGraph::RedNodeIt r(bpg); r!=INVALID; ++r) {
     45    xref.set(r, network.addNode());
     46    network.addArc(source, xref[r]);
     47  }
     48  for (typename BpGraph::BlueNodeIt b(bpg); b!=INVALID; ++b) {
     49    xref.set(b, network.addNode());
     50    network.addArc(xref[b], sink);
     51  }
     52
     53  for (typename BpGraph::EdgeIt e(bpg); e!=INVALID; ++e) {
     54    network.addArc(xref[bpg.redNode(e)], xref[bpg.blueNode(e)]);
     55  }
     56}
     57
     58typedef SmartDigraph Digraph;
     59typedef SmartBpGraph BpGraph;
     60
     61///\ingroup tools
     62///\file
     63///\brief Benchmark program for weighted and unweighted bipartite matching.
     64///
     65/// The program input is a Dimacs file with a problem line starting
     66/// \code
     67///   p ubm
     68/// \endcode
     69/// or
     70/// \code
     71///   p wbm
     72/// \endcode
     73///
     74/// Depending on the input, it runs the \ref lemon::HopcroftKarp
     75/// "Hopcroft-Karp", the \ref lemon::MaxMatching "general matching"
     76/// and the \ref lemon::Preflow "Preflow" algorithms for finding
     77/// a maximum cardinality bipartite matching, or the
     78/// \ref lemon::MaxWeightedBpMatching "max. weighted matching
     79/// for sparse bipartite graphs" and the \ref lemon::MaxWeightedMatching
     80/// "max. weighted matching for general graph" to find the maximum
     81/// weighted matching.
     82int main() {
     83  DimacsDescriptor desc = dimacsType(cin);
     84  if (desc.type == DimacsDescriptor::UBM) {
     85    // unweighted bipartite matching
     86    Timer hk_t(false), mm_t(false), pf_t(false);
     87
     88    BpGraph bpg;
     89    readDimacsUbm(cin, bpg, desc);
     90
     91    Digraph network;
     92    Digraph::Node source, target;
     93    create_network(bpg, network, source, target);
     94
     95    hk_t.start();
     96    HopcroftKarp<BpGraph> hk(bpg);
     97    hk.run();
     98    hk_t.stop();
     99
     100    mm_t.start();
     101    MaxMatching<BpGraph> mm(bpg);
     102    mm.run();
     103    mm_t.stop();
     104
     105    pf_t.start();
     106    Preflow<Digraph, ConstMap<Digraph::Arc, int> >
     107      pf(network, constMap<Digraph::Arc, int>(1), source, target);
     108    pf.run();
     109    pf_t.stop();
     110
     111    cout << "Benchmarking in unweighted case, using a bipartite graph\n"
     112         << "with " << countRedNodes(bpg) << " red, "
     113         << countBlueNodes(bpg) << " blue nodes, and "
     114         << desc.edgeNum << " edges.\n"
     115         << "--------------------------------------------------------\n"
     116         << "Algorithm used       Matching size       Time\n"
     117         << "Hopcroft-Karp            " << hk.matchingSize()
     118         << "         " << hk_t.realTime() << "\n"
     119         << "General matching         " << mm.matchingSize()
     120         << "         " << mm_t.realTime() << "\n"
     121         << "Preflow                  " << pf.flowValue()
     122         << "         " << pf_t.realTime() << endl;
     123
     124  } else if (desc.type == DimacsDescriptor::WBM) {
     125    // weighted bipartite matching
     126    Timer bm_t(false), gm_t(false);
     127
     128    BpGraph bpg;
     129    BpGraph::EdgeMap<int> weight(bpg);
     130    readDimacsWbm(cin, bpg, weight, desc);
     131
     132    bm_t.start();
     133    MaxWeightedBpMatching<BpGraph> bm(bpg, weight);
     134    bm.run();
     135    bm_t.stop();
     136
     137    gm_t.start();
     138    MaxWeightedMatching<BpGraph> gm(bpg, weight);
     139    gm.run();
     140    gm_t.stop();
     141
     142    cout << "Benchmarking on weighted bipartite matching problem on a "
     143         << "graph\nwith " << countRedNodes(bpg) << " red, "
     144         << countBlueNodes(bpg) << " blue nodes and "
     145         << desc.edgeNum << " edges\n"
     146         << "--------------------------------------------------------\n"
     147         << "Algorithm used       Maximum weight       Time\n"
     148         << "Bipartite matching       " << bm.matchingWeight()
     149         << "         " << bm_t.realTime() << "\n"
     150         << "General matching         " << gm.matchingWeight()
     151         << "         " << gm_t.realTime() << endl;
     152
     153  } else {
     154    cerr << "Wrong problem type." << endl;
     155    return 1;
     156  }
     157
     158  return 0;
     159}
  • new file tools/unweighted-bp-gen.cc

    diff --git a/tools/unweighted-bp-gen.cc b/tools/unweighted-bp-gen.cc
    new file mode 100644
    - +  
     1/* -*- mode: C++; indent-tabs-mode: nil; -*-
     2 *
     3 * This file is a part of LEMON, a generic C++ optimization library.
     4 *
     5 * Copyright (C) 2003-2012
     6 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
     7 * (Egervary Research Group on Combinatorial Optimization, EGRES).
     8 *
     9 * Permission to use, modify and distribute this software is granted
     10 * provided that this copyright notice appears in all copies. For
     11 * precise terms see the accompanying LICENSE file.
     12 *
     13 * This software is provided "AS IS" with no warranty of any kind,
     14 * express or implied, and with no claim as to its suitability for any
     15 * purpose.
     16 *
     17 */
     18
     19#include <lemon/arg_parser.h>
     20#include <lemon/random.h>
     21
     22using namespace lemon;
     23using namespace std;
     24
     25///\ingroup tools
     26///\file
     27///\brief Random bipartite graph generator.
     28///
     29/// This program generates an unweighted bipartite matching problem.
     30/// The output is in Dimacs format, with problem type of 'ubm'. The
     31/// enumerated nodes form one set of the bipartite graph, and
     32/// the edges do not have any label.
     33int main(int argc, char** argv) {
     34  int seed = 1,
     35    red = 5,
     36    blue = 5,
     37    density = 10;
     38
     39  ArgParser arg_p(argc, argv);
     40
     41  arg_p.refOption("seed",
     42                  "Seed of the random number generator (default: 1)",
     43                  seed)
     44    .refOption("red",
     45               "Number of red nodes (default: 5)",
     46               red)
     47    .refOption("blue",
     48               "Number of blue nodes (default: 5)",
     49               blue)
     50    .refOption("density",
     51               "Number of edges (default: 10)",
     52               density);
     53
     54  arg_p.synonym("s", "seed")
     55    .synonym("r", "red")
     56    .synonym("b", "blue")
     57    .synonym("d", "density");
     58
     59  arg_p.parse();
     60
     61  rnd.seed(seed);
     62
     63
     64  long max_edge = red*blue;
     65  long edge_left = density;
     66
     67  if (max_edge < density) {
     68    cerr << "Too many edges!\n"
     69         << "At most " << max_edge << " edges can be placed in the graph.\n";
     70    return 1;
     71  }
     72
     73  cout << "c Random bipartite graph for bipartite matching\n"
     74       << "c----------------------------------------------\n"
     75       << "c Input parameters:\n"
     76       << "c   Random seed: " << seed << "\n"
     77       << "c   Number of nodes: " << red + blue << "\n"
     78       << "c     Number of red nodes:  " << red << "\n"
     79       << "c     Number of blue nodes: " << blue << "\n"
     80       << "c   Number of edges: " << density << "\n"
     81       << "c----------------------------------------------\n";
     82  cout << "p ubm " << red + blue << " " << density << "\n";
     83
     84  // red nodes:
     85  for (int r=1; r <= red; ++r) {
     86    cout << "n " << r << "\n";
     87  }
     88
     89  // edges:
     90  for (int r = 1; r <= red; ++r) {
     91    for (int b = red+1; b <= red + blue; ++b) {
     92      if (rnd.boolean(static_cast<long double>(edge_left) / max_edge)) {
     93        --edge_left;
     94        cout << "a " << r << " " << b << "\n";
     95      }
     96      --max_edge;
     97    }
     98  }
     99
     100  return 0;
     101}
     102
  • new file tools/weighted-bp-gen.cc

    diff --git a/tools/weighted-bp-gen.cc b/tools/weighted-bp-gen.cc
    new file mode 100644
    - +  
     1/* -*- mode: C++; indent-tabs-mode: nil; -*-
     2 *
     3 * This file is a part of LEMON, a generic C++ optimization library.
     4 *
     5 * Copyright (C) 2003-2012
     6 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
     7 * (Egervary Research Group on Combinatorial Optimization, EGRES).
     8 *
     9 * Permission to use, modify and distribute this software is granted
     10 * provided that this copyright notice appears in all copies. For
     11 * precise terms see the accompanying LICENSE file.
     12 *
     13 * This software is provided "AS IS" with no warranty of any kind,
     14 * express or implied, and with no claim as to its suitability for any
     15 * purpose.
     16 *
     17 */
     18
     19#include <lemon/arg_parser.h>
     20#include <lemon/random.h>
     21
     22using namespace lemon;
     23using namespace std;
     24
     25///\ingroup tools
     26///\file
     27///\brief Random weighted bipartite graph generator.
     28///
     29/// This program generates a weighted bipartite matching problem.
     30/// The output is in Dimacs format, with problem type of 'wbm'. The
     31/// enumerated nodes form one set of the bipartite graph, and
     32/// the edge labels denote weights.
     33int main(int argc, char** argv) {
     34  int seed = 1,
     35    red = 5,
     36    blue = 5,
     37    density = 10,
     38    minweight = 10,
     39    maxweight = 100;
     40
     41  ArgParser arg_p(argc, argv);
     42
     43  arg_p.refOption("seed",
     44                  "Seed of the random number generator (default: 1)",
     45                  seed)
     46    .refOption("red",
     47               "Number of red nodes (default: 5)",
     48               red)
     49    .refOption("blue",
     50               "Number of blue nodes (default: 5)",
     51               blue)
     52    .refOption("density",
     53               "Number of edges (default: 10)",
     54               density)
     55    .refOption("minweight",
     56               "Minimal weight of edges (default: 10)",
     57               minweight)
     58    .refOption("maxweight",
     59               "Maximal weight of edges (default: 100",
     60               maxweight);
     61
     62  arg_p.synonym("s", "seed")
     63    .synonym("r", "red")
     64    .synonym("b", "blue")
     65    .synonym("d", "density")
     66    .synonym("min", "minweight")
     67    .synonym("max", "maxweight");
     68
     69  arg_p.parse();
     70
     71  rnd.seed(seed);
     72
     73
     74  long max_edge = red*blue;
     75  long edge_left = density;
     76
     77  if (max_edge < density) {
     78    cerr << "Too many edges!\n"
     79         << "At most " << max_edge << " edges can be placed in the graph.\n";
     80    return 1;
     81  }
     82
     83  cout << "c Random bipartite graph for weighted bipartite matching\n"
     84       << "c----------------------------------------------\n"
     85       << "c Input parameters:\n"
     86       << "c   Random seed: " << seed << "\n"
     87       << "c   Number of nodes: " << red + blue << "\n"
     88       << "c     Number of red nodes:  " << red << "\n"
     89       << "c     Number of blue nodes: " << blue << "\n"
     90       << "c   Number of edges: " << density << "\n"
     91       << "c   Minimal weight of edges: " << minweight << "\n"
     92       << "c   Maximal weight of edges: " << maxweight << "\n"
     93       << "c----------------------------------------------\n";
     94  cout << "p wbm " << red + blue << " " << density << "\n";
     95
     96  // red nodes:
     97  for (int r=1; r <= red; ++r) {
     98    cout << "n " << r << "\n";
     99  }
     100
     101  // edges:
     102  for (int r = 1; r <= red; ++r) {
     103    for (int b = red+1; b <= red + blue; ++b) {
     104      if (rnd.boolean(static_cast<long double>(edge_left) / max_edge)) {
     105        --edge_left;
     106        cout << "a " << r << " " << b << " "
     107             << rnd.integer(minweight, maxweight+1) << "\n";
     108      }
     109      --max_edge;
     110    }
     111  }
     112
     113  return 0;
     114}
     115