#! /usr/bin/less
#! -*- coding:utf-8; mode:text; -*-


This package contains the all code for the master's thesis "Aesthetic
Value of Graph Layouts: Investigation of Statistical Syndromes for
Automatic Quantification" of Moritz Klammler.  It is self-contained to
reproduce all experiments from scratch and even typeset the written
thesis and slides for the presentation automatically incorporating the
most recent results.


Dependencies
============

The project uses a CMake build system.  You need at least CMake 3.9.1 or
newer in order to build anything.  There are no install targets,
everything is built locally because the artifacts are not really meant
to be used stand-alone.

The heart of the graph algorithms is implemented in C++ using the OGDF
library, which will be automatically downloaded and (after verifying the
integrity of the downloaded file) built as part of building this
project.  It is therefore not considered as an external dependency.

The project uses (at the time of this writing) fairly cutting-edge C++17
technology so a modern C++ compiler is required.  GCC 8.1.0 was tested
and proven to work.

Besides, the Boost C++ libraries are required in version 1.66 or newer.
Apart from the header-only components, the following compiled Boost
libraries are required: `filesystem`, `iostreams`, `program_options` and
`system`.  If you don't have a Boost version recent enough installed,
you might find the script `maintainer/get-boost` handy, that will
download, build and install the required Boost libraries in a number of
configurations for you.  The integrity of the downloads is checked.  The
libraries will be installed locally for the current user so no root
privileges are required and existing system libraries are not messed
with.

The framework is glued together with a non-trivial amount of Python
code, referred to as the *driver*.  It requires Python 3.6.2 or newer.

The following Python packages are required:

 - `h5py`
 - `keras`
 - `numpy`

The following Python packages are optional:

 - `lxml`

The `keras` package will only function if you have TensorFlow installed
which has to be done via the usual procedures for your operating system.

The web front-end and other presentation targets will only work if
`gnuplot` is installed.

Further optional dependencies are Doxygen to generate API documentation
for the C++ code, and a TeX toolchain (in particular, `lualatex`,
`biber` or `bibtex` and `makeindex`) for typesetting the written thesis
and presentation slides.


Configuration, Building and Testing
===================================

Once all dependencies are available, the project can be built using the
usual CMake commands.  In the most simple case, running

    $ cmake .
    $ cmake --build .

should be sufficient.  It might be a better idea to use out-of-tree
builds, though.  Once the project is configured and built, the tests can
be run via

    $ ctest -T Test

with additional CTest flags added to your liking.

If you want to hack on the project, you might find it convenient to
build different configurations in which case the `maintainer/configure`
and `maintainer/build` script could be useful.


Additional Targets
------------------

The default CMake target will only build the C++ tools.  In order to run
any experiments, you have to build the respective targets explicitly.
The following targets might be useful.

 - `deploy` populates the database and runs the main experiment
 - `httpd` starts a HTTP server listening at port 8000
 - `eval` runs cross validation and other tests
 - `apidoc` builds Doxygen API reference documentation
 - `benchmark` runs some benchmarks (there are not many of them)
 - `report` typesets the written thesis in `report/graphstudy.pdf`
 - `slides` typesets the presentation slides in `slides/graphstudy.pdf`

The `deploy` target will take a *very long* time to build (probably
several days).  The `eval` target will take a long time, too (probably
several hours).  Building the `eval` target will run the experiments
again each time.  It is therefore not an explicit dependency of the
`report` and `slides` target although you have to build it once before
those targets can be built.

Before you go ahead building the `deploy` and `eval` targets, you might
want to read the section about configuration first.

The `httpd` target starts a local web server listening on port 8000 and
serves visualization and other useful insights into the current data
base.


Configuration
=============

This section is about setting up the experiment, not build system
configuration.

The configuration read by the driver script is found in the `config`
directory.  The files there might serve as a good starting point.  All
`*.cfg` files have in common that blank lines are ignored as well as
everything after a `#` character.

The file `graphs.cfg` controls the graphs that will be generated.  Its
format is a table where the first column lists the graph generator and
the subsequent columns the desired number of graphs per size class.  All
columns except the first (which must not have a title) must have a title
that specifies the size class.  For example, the following configuration

                        SMALL     MEDIUM
    LINDENMAYER         10        5
    ROME                20        *

specifies that 10 small and 5 medium-sized `LINDENMAYER` graphs are
desired as well as 20 small graphs from the `ROME` collection and any
medium graphs in there.  Using an asterisk makes only sense for
generators that import from a finite collection.

The file `layouts.cfg` specifies the desired layouts to compute.  Its
format is a list of layout algorithms followed by a list of graph
sizes for which the algorithm should be applied.  For example, the
configuration

    NATIVE              ...
    FMMM                ... LARGE
    STRESS              TINY SMALL

specifies that native layouts should be "computed" for graphs of all
sizes (the ellipsis) while `FMMM` layouts should be computed for graphs
up to and including large size and `STRESS` layouts should be computed
for tiny and small graphs only.  The ellipsis can be used in three
ways.  If it is used alone, it selects all size classes.  If it is used
as the first or last word in a row, it refers to all size classes up to
(and including) or all size classes from and above the following or
preceding class respectively.  If an ellipsis is used between two other
size classes, it selects all size classes in between.

The files `interpolation.cfg` and `worsening.cfg` control the
computation of interpolated and worsened layouts respectively.  Their
format is the same: a list of interpolation or worsening algorithms
followed by a list of numbers (between 0 and 1) that select the rates at
which interpolations or worsenings should be computed using that
algorithm.  For example, the configuration

    PERTURB             0.15      0.25
    MOVLSQ              0.10

specifies that worsened layouts using the `PERTURB` algorithm shall be
computed at rates of 0.15 and 0.25 while worsened layouts using the
`MOVLSQ` algorithm should be computed only for a rate of 0.10.

The `properties.cfg` file uses the same format as the `layouts.cfg` file
only that it specifies properties rather than layouts to be computed.
For example, the configuration file

    ANGULAR             SMALL     ...       HUGE
    EDGE_LENGTH         MEDIUM

specifies that the `ANGULAR` property shall be computed for all layouts
of graphs from small to huge size (both inclusive) and the `EDGE_LENGTH`
property shall be computed for layouts of medium size graphs only.

There is one last configuration file, `imports.json` that, as its name
suggests, is in JSON format.  This file specifies where to find import
sources for graphs (for the `IMPORT` generator).  Its format is either a
single JSON object or a JSON array of JSON objects.  Using a single
object is just a convenience for using an array with only one element.
Each object specifies one archive which will be considered in turn.  It
is therefore most useful to specify multiple import sources only in
conjunction with selecting to import all available graphs (using the `*`
in the `graphs.cfg` file).  An import source must always have a *type*.
The remaining fields permissible (and some required) in the JSON object
depend on the type of the archive.  The following types are defined and
accept or require the attributes mentioned as nested items.  Items with
no default value mentioned are mandatory.

 * `DIR` refers to a local directory

    - `type` must be `DIR` (string)
    - `directory` specifies the directory (path) to scan for graph files
      (string)
    - `format` specifies the file format used by the archive (string)
    - `compression` specifies the compression (if any) applied to the
      files in the archive (string, default: `none`)
    - `pattern` specifies a POSIX globing expression by which to select
      files in the directory (string, default: `*`)
    - `recursive` selects whether or not the directory shall be scanned
      recursively for graph files to import (boolean, default: `false`)
    - `layout` specifies whether or not the graph files have an
      associated native layout (boolean, default: `false`)
    - `simplify` specifies whether or not to "simplify" the imported
      graphs by pruning multiple edges, loops and making the graph
      undirected (boolean, default: `false`)

 * `TAR` refers to a tarball specified via a URL

    - `type` must be `TAR` (string)
    - `url` specifies the URL of the tarball (string)
    - `format` specifies the file format used by the archive (string)
    - `compression` specifies the compression (if any) applied to the
      files in the archive (string, default: `none`)
    - `cache` specifies a file name (not a full path) to use for caching
      the tarball locally which is useful if the URL is not a `file://`
      (string, default: don't cache)
    - `checksum` specifies the cryptographic hash algorithm and hex
      digest to expect for the tarball in the format `ALGO:HASH` where
      `ALGO` is one of the strings understood by the `hashlib` module
      from the Python standard library (string, default: don't verify
      checksum)
    - `pattern` specifies a POSIX globing expression by which to select
      files in the directory (string, default: `*`)
    - `layout` specifies whether or not the graph files have an
      associated native layout (boolean, default: `false`)
    - `simplify` specifies whether or not to "simplify" the imported
      graphs by pruning multiple edges, loops and making the graph
      undirected (boolean, default: `false`)

 * `URL` refers to a collection of URLs of individual graphs

    - `type` must be `URL` (string)
    - `urls` lists the URLs of the graph files to consider (array of
      strings)
    - `format` specifies the file format used by the archive (string)
    - `compression` specifies the compression (if any) applied to the
      files in the archive (string, default: `none`)
    - `layout` specifies whether or not the graph files have an
      associated native layout (boolean, default: `false`)
    - `simplify` specifies whether or not to "simplify" the imported
      graphs by pruning multiple edges, loops and making the graph
      undirected (boolean, default: `false`)
    - `name` specifies an informal name for the archive and should be a
      valid identifier (string, default: `www`)
    - `cache` specifies whether or not to cache the downloaded files
      locally in a database (boolean, default: `false`)

 * `NULL` is a dummy archive that contains no graphs

    - `type` must be `NULL` (string)

Acceptable values for the `format` attribute may be found by running the
`import` tool (see below) with the `--show-formats` option.  Acceptable
values for the `compression` attribute are `gzip`, `bzip2` and `none`.


Command Line Tools
==================

After building the project, the `src` directory will (in sub
directories) contain a number of command line tools that are
subsequently invoked by the driver script but may also have merits on
their own.  Each executable can be run with the `--help` option which
will make it print a summary of the arguments and options it accepts.


File Names
----------

Whenever a tool accepts a file name, it will also accept a decimal file
descriptor.  This is especially useful if you invoke a tool from another
process and need more than one pipe to communicate all the data back and
forth.  Furthermore, the strings `NULL` and `STDIO` receive special
treatment.  The former will be understood as a request to perform no I/O
at all (somewhat like using `/dev/null`) and the latter causes standard
input or output to be used.  The empty string has the same effect as
`NULL` and the string `-` has the same effect as using the string
`STDIO`.  The reason the more verbose alternatives are provided as well
(and are, in fact, recommended) is that the Boost `program_options`
library that is used for parsing the command line arguments will behave
erratically in some cases when confronted with the empty string or the
string `-`.  It follows that if you should ever want to refer to a
regular file by such a name, you will have to use a construct like
`./NULL` or `./42` to circumvent this special treatment.

Apart from the remarks above, file names also support a special syntax
to enable transparent compression.  If the file name contains a colon,
the portion after the last colon will be interpreted as a compression
algorithm.  For example, the "file name" `file.dat:gzip` refers to the
file name `file.dat` which should be accessed using gzip compression.
Beware that some operating systems use colons in regular file names.  In
that case, you must always append another colon at the end.  For
example, `A:graphs\mansion.dat` will cause confusion while
`A:graphs\mansion.dat:` will work fine.  Using the empty string
otherwise has the same effect as not specifying any compression at all
and will cause the compression to be inferred from the file name.  If it
ends in `.gz` it will be assumed that the file is `gzip` compressed and
if it ends in `.bz2` then `bzip2` compression will be assumed.  If the
compression is specified explicitly, the strings `gzip`, `bzip2` and
`none` are accepted and have the obvious meaning.  If you like being
verbose, the string `automatic` may also be used instead of the empty
string to the same effect.


Environment Variables
---------------------

Some environment variables are accepted by all command line tools and
control the resource limits.  They all have the form `MSC_LIMIT_${RES}`
where `${RES}` is one of the constants defined by the POSIX rlimit
interface.  Consult the man page for the `getrlimit` or `setrlimit`
system calls for that purpose.  For example, running a command line tool
with `MSC_LIMIT_STACK=33554432` will cause the process to use a maximum
stack size of 32 MiB, unless a lower limit is already imposed in which
case that limit will take effect.

All command line tools that may use non-determinism also recognize an
environment variable `MSC_RANDOM_SEED` which, if set, will be used to
act as a deterministic seed for the pseudo random number generator.  It
may be any sequence of bytes.
