Skip to content

Commit

Permalink
post merge branch tree_stripping
Browse files Browse the repository at this point in the history
  • Loading branch information
skramm committed Jul 29, 2023
2 parents ded9d18 + ca6afd7 commit 30ab7f0
Show file tree
Hide file tree
Showing 17 changed files with 1,176 additions and 140 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ These are sorted with the smallest vertex in first position, and such as the sec
<a name="s_stat"></a>

- Home page: https://github.com/skramm/udgcd
- beta, may produce a result, no guarantee at all (this is a stalled project)
- Author: Sebastien Kramm
- Author: Sebastien Kramm, [email protected]
- Latest news:
- 2023-07-17: (**major update**) Algorithm improvment, now much more efficient, see [details here](misc/tree_stripping.md)
- 2023-07-03: was reported to [build on Windows using VisualStudio](https://github.com/skramm/udgcd/issues/4#issuecomment-1611426339)
- 2020-06-09: experimental code and preliminar release, source is pretty messy, but it works fine, give it a try (instructions below).
- Released under the Boost licence.

### Features

- Single-file, header only, OS agnostic
- Single-file header only library, OS agnostic
- Works for graphs holding unconnected sub-graphs.
- Works for non-planar graphs
- Modern C++ design (RAII), basic C++11.
Expand Down Expand Up @@ -75,7 +75,7 @@ This will copy the file in `/usr/local/include/`
Some additional apps are included, that are build by the makefile:
- `read_graph.cpp`: reads graph from a text file given as argument, computes its cycles, prints them and generate the corresponding dot file.
- `random_test.cpp`: generates a random graph computes its cycles, prints them and generate the corresponding dot file.
- `sample_?.cpp`: c++ apps that build a graph and computes its cycles.
- `sample_?.cpp`: C++ apps that build a graph and computes its cycles.

#### Build options:
- The provided makefile is not requested to use the library, as it is "header-only".
Expand Down Expand Up @@ -145,10 +145,16 @@ This step is the most time-consuming.
- The third steps does some post-processing:
sort cycles by decreasing length, and do Gaussian Elimination to retain a Minimal Cycle Basis (MCB).

### Dependencies

- boost::graph - https://www.boost.org/doc/libs/1_82_0/libs/graph/doc/
(warning: boost graph itself has a lot of other boost dependencies)
- boost::dynamic_bitset https://www.boost.org/doc/libs/1_82_0/libs/dynamic_bitset/dynamic_bitset.html


### References
<a name="s_ref"></a>

- BGL: http://www.boost.org/doc/libs/1_59_0/libs/graph/doc
- https://en.wikipedia.org/wiki/Cycle_basis
- J. D. Horton, <i>A polynomial-time algorithm to find a shortest cycle basis of a graph</i>, SIAM Journal of Computing 16, 1987, pp. 359–366
[link](https://epubs.siam.org/doi/10.1137/0216026).
Expand Down
54 changes: 35 additions & 19 deletions demo/common_sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ Home page: https://github.com/skramm/udgcd
#include <string>
#include <numeric>

#ifdef UDGCD_USE_M4RI
#define HAS_M4RI "YES\n"
#else
#define HAS_M4RI "NO\n"
#endif

#define SHOW_INFO \
std::cout << "-START: " << __FILE__ \
<< "\n-built with Boost " << BOOST_LIB_VERSION << '\n'
<< "\n-built with Boost " << BOOST_LIB_VERSION \
<< "\n-build option: USE_M4RI: " << HAS_M4RI << "\n";

extern std::string prog_id; // allocated in samples files
int g_idx = 0;
Expand Down Expand Up @@ -473,7 +480,7 @@ The counterpart is that we do not read the vertices/edges properties that can be

\warning This is a minimal reader, don't expect any fancy features.

Limitation/features: as we do not handle labels, the index in input file will be the BGL vertex index.<br>
Limitation/features: as we do not handle labels, the index in input file will be the BGL vertex index.<br>
Drawback: if a node is missing in input file, it will be in the generated graph, because BGL indexes are always
consecutives.

Expand All @@ -497,7 +504,7 @@ will generate a graph of 6 vertices (0 to 5), with only 0 and 1 connected.
*/
template<typename graph_t>
graph_t
loadGraph_dot( const char* fname )
loadGraph_dot( std::string fname )
{
std::cout<< " - Reading file:" << fname << '\n';
std::ifstream f( fname );
Expand Down Expand Up @@ -641,7 +648,7 @@ loadGraph_dot( const char* fname )
/// Load graph from custom simple text format
template<typename graph_t>
graph_t
loadGraph_txt( const char* fname )
loadGraph_txt( std::string fname )
{
graph_t g;

Expand Down Expand Up @@ -734,42 +741,51 @@ The first value is 0 in case of success, -1 if incorrect cycles were found.
If other value, it is the absolute number of differences between:
- the \b computed number of cycles
- and the \b expected number of cycles

*/
template<typename graph_t,typename vertex_t>
std::pair<int,std::vector<std::vector<vertex_t>>>
processGraph( graph_t& g )
processGraph( graph_t& g, const udgcd::RunTimeOptions& rtOptions )
{
auto expected = printGraphInfo( g );

udgcd::UdgcdInfo info;
info.runTime = rtOptions;
auto cycles = udgcd::findCycles<graph_t,vertex_t>( g, info );
// udgcd::printPaths( std::cout, cycles, "final" );

if( expected != cycles.size() )
std::cout << "ERROR: computed nb of cycles is not what expected (expected=" << expected << ")\n";

// std::cout << "diff=" << (int)expected - (int)cycles.size() << "\n";

udgcd::priv::printStatus( std::cout, cycles, __LINE__ );

auto check = udgcd::priv::checkCycles( cycles, g );
if( check.first != 0 )
{
std::cout << "ERROR: " << check.first << " incorrect cycles found\n";
return std::make_pair(-1, cycles );
}
if( check.second != 0 )
if( rtOptions.printCycles )
udgcd::printPaths( std::cout, cycles, "final" );

if( rtOptions.doChecking )
{
std::cout << "Found: " << check.second << " non chordless cycles\n";
auto check = udgcd::priv::checkCycles( cycles, g );
if( check.first != 0 )
{
std::cout << "ERROR: " << check.first << " incorrect cycles found\n";
return std::make_pair(-1, cycles );
}
if( check.second != 0 )
{
std::cout << "Found: " << check.second << " non chordless cycles\n";
}
}

info.print( std::cout );
// info.printCSV( std::cerr );

std::cout << "Histogram of cycle sizes:\n";
auto histog = buildSizeHistogram( cycles );
for( size_t i=0; i<histog.size(); i++ )
std::cout << i+3 << ":" << histog[i] << "\n";
if( rtOptions.printHistogram )
{
std::cout << "Histogram of cycle sizes:\n";
auto histog = buildSizeHistogram( cycles );
for( size_t i=0; i<histog.size(); i++ )
std::cout << i+3 << ":" << histog[i] << "\n";
}

auto diff = (int)cycles.size() - (int)expected;
return std::make_pair(diff, cycles );
Expand Down
3 changes: 2 additions & 1 deletion demo/random_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ int main( int argc, const char** argv )
SaveGraph( g, std::to_string(current_time) );
sample::renderGraph( g, "gen_" + std::to_string(current_time) );

auto result = sample::processGraph<graph_t,vertex_t>( g );
udgcd::RunTimeOptions rtOptions;
auto result = sample::processGraph<graph_t,vertex_t>( g, rtOptions );

return result.first;
}
Expand Down
61 changes: 45 additions & 16 deletions demo/read_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
\file read_graph.cpp
\brief Read a graph in a file, searches for cycles, and make sure these are correct.

Also generates a .dot file that can be graphically rendered with Graphviz
Also generates a .dot file in the `out/` folder, that can be graphically rendered with Graphviz (done with `$ make svg`).

Has some runtime switches, see help()
*/

#include <iostream>
Expand All @@ -18,6 +20,17 @@ Also generates a .dot file that can be graphically rendered with Graphviz
std::string prog_id = "read_graph";
#include "common_sample.h"

/// help function
/// \sa RunTimeOptions
void help()
{
std::cout << "Options:" \
"\n -t: print cycles as trees" \
"\n -p: print produced cycles" \
"\n -h: print histogram of cycles length" \
"\n -c: does a checking of correctness of computed cycles" \
"\n";
}

//-------------------------------------------------------------------
/// see read_graph.cpp
Expand All @@ -34,55 +47,71 @@ int main( int argc, const char** argv )

using vertex_t = boost::graph_traits<graph_t>::vertex_descriptor;

#ifdef UDGCD_USE_M4RI
std::cout << "Build using libM4ri\n";
#endif

if( argc < 2 )
{
std::cout << "missing input filename, exit.\n";
std::cout << "missing input filename, exit\n";
help();
return 1;
}
auto vs = sample::splitString( argv[1], '.' );
std::string fname = argv[argc-1];
auto vs = sample::splitString( fname, '.' );
if( vs.size() < 2 )
{
std::cerr << "Error, input file '" << argv[1] << "' has no extension\n";
std::cerr << "Error, input file '" << fname << "' has no extension\n";
return 1;
}

graph_t gr;
if( vs.back() == "dot" )
{
gr = sample::loadGraph_dot<graph_t>( argv[1] );
gr = sample::loadGraph_dot<graph_t>( fname );
}
else
{
if( vs.back() == "txt" )
{
gr = sample::loadGraph_txt<graph_t>( argv[1] );
gr = sample::loadGraph_txt<graph_t>( fname );
}
else
{
std::cerr << "Error, input file '" << argv[1] << "' extension invalid\n";
std::cerr << "Error, input file '" << fname << "' extension invalid\n";
return 1;
}
}


auto vs1 = sample::splitString( argv[1], '/' ); // splits by '/', and keep the last one (filename)
auto vs1 = sample::splitString( fname, '/' ); // splits by '/', and keep the last one (filename)
auto vs2 = sample::splitString( vs1.back(), '.' ); // splits by the point (if any)
sample::renderGraph( gr, vs2[0] );

udgcd::RunTimeOptions rtOptions;

bool noProcess(false);
bool verbose(false);
if( argc > 2 )
{
std::string a( argv[2] );
if( a == "-n" )
noProcess = true;
for( int i=1;i<argc-1;i++ )
{
std::string a( argv[i] );
if( a == "-n" )
noProcess = true;
if( a == "-v" )
verbose = true;
if( a == "-t" )
rtOptions.printTrees = true;
if( a == "-p" )
rtOptions.printCycles = true;
if( a == "-h" )
rtOptions.printHistogram = true;
if( a == "-c" )
rtOptions.doChecking = true;

}
}
if( noProcess )
return 0;
auto result = sample::processGraph<graph_t,vertex_t>( gr );

auto result = sample::processGraph<graph_t,vertex_t>( gr, rtOptions );
sample::renderGraph2( gr, result.second, vs2[0]+"_color" );
return result.first;
}
Expand Down
29 changes: 21 additions & 8 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ APP=udgcd.hpp
HEADER=udgcd.hpp
HEADERS = $(wildcard *.h*)
HEADERS += $(wildcard demo/*.h*)

BIN_DIR=build/bin
OBJ_DIR=build/obj

Expand All @@ -76,7 +77,8 @@ GEN_SAMPLES_OUTPUT += $(patsubst samples/graph_%.dot,out/stdout_graph_%.txt,$(GE
#GEN_SAMPLES_PLOT = $(patsubst samples/%.txt,out/sample_%.svg,$(GEN_SAMPLE_FILES))

DOT_FILES=$(wildcard out/*.dot)
SVG_FILES=$(patsubst out/%.dot,out/svg/%.svg,$(DOT_FILES))
SVG_FILES=$(patsubst out/%.dot,out/svg/%_dot.svg,$(DOT_FILES))
SVG_FILES+=$(patsubst out/%.dot,out/svg/%_neato.svg,$(DOT_FILES))

# default target
all: $(EXEC_FILES)
Expand Down Expand Up @@ -126,11 +128,17 @@ out/stdout_graph_%.txt: samples/graph_%.dot $(BIN_DIR)/read_graph makefile
@-$(BIN_DIR)/read_graph $< > $@;\
STATUS=$$?; echo "file $<: exit with $$STATUS" >> build/runsam.log

out/svg/%.svg : out/%.dot
mkdir -p out/svg
echo "generating $@"
dot -Tsvg -Nfontsize=24 $< >out/svg/$(notdir $(basename $@))_dot.svg
neato -Tsvg -Nfontsize=24 -Elen=1.5 $< >out/svg/$(notdir $(basename $@))_neato.svg

create_svg:
mkdir -p out/svg/

out/svg/%_dot.svg: out/%.dot create_svg
@echo "generating $@"
@dot -Tsvg -Nfontsize=24 $< >out/svg/$(notdir $(basename $@))_dot.svg

out/svg/%_neato.svg: out/%.dot create_svg
@echo "generating $@"
@neato -Tsvg -Nfontsize=24 -Elen=2.0 $< >out/svg/$(notdir $(basename $@))_neato.svg

show: $(SRC_FILES)
@echo SRC_FILES=$(SRC_FILES)
Expand All @@ -151,6 +159,11 @@ doc:
doxygen misc/doxyfile 1>build/doxygen.stdout 2>build/doxygen.stderr
xdg-open build/html/index.html

doc2:
make runsam -j4
make svg -j4
make doc

clean:
@-rm $(OBJ_DIR)/*
@-rm $(BIN_DIR)/*
Expand Down Expand Up @@ -196,8 +209,8 @@ $(BIN_DIR)/%: $(OBJ_DIR)/%.o
# $(CXX) -o bin/test_catch $(OBJ_DIR)/test_catch.o -s
# @echo "done target $@"

$(BIN_DIR)/test_catch:
$(CXX) -o $(BIN_DIR)/test_catch test_catch.cpp -s
$(BIN_DIR)/test_catch: udgcd.hpp test_catch.cpp
$(CXX) -o $(BIN_DIR)/test_catch test_catch.cpp -s $(CFLAGS)
@echo "done target $@"

# -s option: also shows successful test results
Expand Down
4 changes: 2 additions & 2 deletions misc/doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT = ./ misc/
INPUT = ./ misc/ demo/

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
Expand Down Expand Up @@ -896,7 +896,7 @@ EXAMPLE_RECURSIVE = NO
# that contain images that are to be included in the documentation (see the
# \image command).

IMAGE_PATH = out/
IMAGE_PATH = out/ misc/

# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
Expand Down
File renamed without changes
File renamed without changes
Loading

0 comments on commit 30ab7f0

Please sign in to comment.