README.md

## ![image](https://cdn.rawgit.com/faber-lotto/garph/master/logo.svg)  Garph

_Graphical algorithm result processing helper_

Garph is a simple way to implement complex decision trees by using graphs.

### Features

* Simplifies decision trees by breaking them down into single function calls.
* Easy documentation of complex decision graphs through DOT file export.

### Installation

Add garph [hex package](https://hex.pm/packages/garph) to your list of dependencies in `mix.exs`:

```
def deps do
  [{:garph, "~> 0.0.1"}]
end
```

Alternatively you can also require it directiy from github, of course:

```
def deps do
  [{:garph, git: "https://github.com/faber-lotto/garph"}]
end
```

### Usage

Garph can be enabled by importing the Garph module:

```
defmodule YourModule do
  import  Garph
  ...
end
```

#### Defining graph structure

First we need to describe the graph itself, that should be used by garph to call functions. We can achieve this by creating a nested keyword list with 2 levels as a module variable.

```
defmodule Sports do
  import  Garph

  @play_tennis [
                 outlook:
                   [
                     rainy: :wind,
                     overcast: "yes",
                     sunny: :humidity
                   ],
                 humidity:
                   [
                     normal: "yes",
                     high: :temperature
                   ],
                 wind:
                   [
                     strong: "no",
                     weak: "yes"
                   ],
                 temperature:
                   [
                     low: "yes",
                     high: "no"
                   ]
               ]
```
This will result in the following graph:

![image](https://cdn.rawgit.com/faber-lotto/garph/master/play_tennis.svg)

Please note, that there are two kinds of keys here. The keys on the first level represent the name of the functions wich may be called according to the graphs instructions. The first key is identical to the first function called, when traversing the graph. In this case this would be the function _outlook()_.

The second level keys represent the possible results of a certain function. If the value for that key is an atom _eg. :rainy_ and matches the result tuple coming from _outlook()_ , _eg. {:rainy, attributes}_, another function named _wind(*attributes)_ will be called. If the value is a string no further functions will be called and it will be just used as an informational tag, when the graph is plotted.

#### Defining functions

As mentioned before the first level keys of _@play_tennis_ represent the minimum set of functions we need to define in order to get the graph up and running.

```
def outlook(outlook, humidity, wind, temperature) do
  cond do
    outlook >= 0 && outlook <= 0.25 -> {:rainy, [wind]}
    outlook > 0.25 && outlook <= 0.75 -> {:result, true}
    outlook > 0.75 && outlook <= 1 -> {:sunny, [humidity, temperature]}
  end
end

```

#### Results and attributes

A function used by garph always needs to return a result tuple, wich allows it to decide if the end of the graph has been reached, or if a further function call is required. In the latter case the first tuple element contains the name of that function as an atom. The second tuple element is a list of attributes that are passed as the functions parameters. So the whole result tuple has the form _{:function_name, [arg1, arg2, ..., argn]}_.


```
...
outlook > 0.25 && outlook <= 0.75 -> {:result, true}
outlook > 0.75 && outlook <= 1 -> {:sunny, [humidity, temperature]}
...
```

In the example above the result _:sunny_ combined with the graph instructions from @play_tennis leads to calling _humidity(humidity, temperature)_.

Please note, that _:result_ is a reserved key for garph result tuples. Using it will cause garph to assume that the end of the graph has been reached and return the given payload. In this case _true_.

#### Putting it all together

```
defmodule Sports do
  use Garph

  @play_tennis [
                 outlook:
                   [
                     rainy: :wind,
                     overcast: "yes",
                     sunny: :humidity
                   ],
                 humidity:
                   [
                     normal: "yes",
                     high: :temperature
                   ],
                 wind:
                   [
                     strong: "no",
                     weak: "yes"
                   ],
                 temperature:
                   [
                     low: "yes",
                     high: "no"
                   ]
               ]

  def play_tennis?(outlook, humidity, wind, temperature) do
    evaluate @play_tennis, [outlook, humidity, wind, temperature]
  end

  def export_dot do
    {:ok, file} = File.open "play_tennis.dot", [:write]
    IO.binwrite file, Testmod.to_dot(@play_tennis)
  end

  def outlook(outlook, humidity, wind, temperature) do
    cond do
      outlook >= 0 && outlook <= 0.25 -> {:rainy, [wind]}
      outlook > 0.25 && outlook <= 0.75 -> {:result, true}
      outlook > 0.75 && outlook <= 1 -> {:sunny, [humidity, temperature]}
    end
  end

  def humidity(humidity, temperature) do
    cond do
      humidity >= 0 && humidity <= 0.6 -> {:result, true}
      humidity > 0.6 && humidity <= 1 -> {:high, [temperature]}
    end
  end

  def wind(value) do
    cond do
      value >= 0 && value < 7 -> {:result, true}
      value >= 7 -> {:result, false}
    end
  end

  def temperature(value) do
    cond do
      value <= 22 -> {:result, true}
      value > 22 -> {:result, false}
    end
  end
end

```

In this example all necessary functions have been implemented. 
To start the traversal process garph provides _evaluate(graph, attributes)_. It uses a graphs description and initial parameters to begin the traversal process starting with the graphs first node. You may encapsulate the function call in order to make it easily accessible.

```
def play_tennis?(outlook, humidity, wind, temperature) do
  evaluate @play_tennis, [outlook, humidity, wind, temperature]
end
```

Now we are able to ask the graph if we should play tennis just by calling

```
Sports.play_tennis?(outlook, humidity, wind, temperature)
```

### Documentation / Plotting

Sometimes, you may want to plot the graph, eg. for documentational purposes. To achieve this you may just implement a function that exports the graphs data to the dot format:

```
def export_dot do
  {:ok, file} = File.open "play_tennis.dot", [:write]
  IO.binwrite file, Sports.to_dot(@play_tennis)
end
```

Running

```
Sports.export_dot()
```

will result in a file play_tennis.dot which can be used with [graphviz]() to plot it through the console command:

```
dot -Tpng play_tennis.dot > play_tennis.png
```

#### Credits

Special thanks to:

### Copyright and License

Copyright (c) 2016, Faber Lotto GmbH.

Garph source code is licensed under the [MIT License](https://github.com/faber-lotto/garph/blob/master/LICENSE).