Skip to content

Instantly share code, notes, and snippets.

@Thorium
Last active April 24, 2019 18:33
Show Gist options
  • Save Thorium/2c4dd55ab3ee3ba6ec74c765f7faee02 to your computer and use it in GitHub Desktop.
Save Thorium/2c4dd55ab3ee3ba6ec74c765f7faee02 to your computer and use it in GitHub Desktop.
Creating graphical visualisation of a Markov chain

Markov chain example from: https://github.com/mariuszwojcik/Presentations/blob/master/Introduction%20to%20Markov%20Chains%20in%20F%23/code/src/WeatherForecast.fsx

GrapViz Snipet taken from: http://fssnip.net/7Rf

Create new .NET Framework console application: GrapViz doesn't support .NET Standard and doesn't run in FSI.

  1. Install: http://www.graphviz.org/download/

  2. Add <appSettings> under <configuration> to app.config/web.config Add Nuget / Paket: GraphViz.NET

  3. Add under <configuration><appSettings>: <add key="graphVizLocation" value="C:\Program Files (x86)\Graphviz2.38\bin" />

Add references to System.Configuration.dll and System.Drawing.dll

sample

module GraphVizSample
open GraphVizWrapper
open GraphVizWrapper.Commands
open GraphVizWrapper.Queries
open System
open System.Configuration
open System.Drawing
open System.IO
/// Creates some pngs
let genereateGraphFile (path:string) graphVizImageData =
let procqry = GetStartProcessQuery()
let infoqry = GetProcessStartInfoQuery()
let wrapper = GraphGeneration(procqry, infoqry,
RegisterLayoutPluginCommand(infoqry, procqry))
// You probably don't need all 7:
//[0..7] // different layout of graphs, e.g.: Enums.RenderingEngine.Neato
[1] |> List.map(fun i ->
wrapper.RenderingEngine <- enum<Enums.RenderingEngine> i
//wrapper.GraphvizPath <- "C:/Program Files (x86)/Graphviz2.38/bin//"
let output = wrapper.GenerateGraph(graphVizImageData, Enums.GraphReturnType.Png)
use image = System.Drawing.Image.FromStream(new MemoryStream(output))
let filename = sprintf "%sgraph-%i-%i.png" path (output.GetHashCode()) i
do image.Save(filename, System.Drawing.Imaging.ImageFormat.Png)
let creationtime = File.GetLastWriteTime filename
filename, creationtime
)
/// Replace your version of data generation.
/// This is just a silly example to give some starting point.
/// Note: If you use Guid based Ids, it's better to use some text-prefix!
let sampleGraphData items1 items2 =
// shapes: box, diamond, circle, ...
// colors: lightgray, lightblue, goldenrod2, thistle2, ...
// Click on picture under http://www.graphviz.org/Gallery.php
// ...and picture again to get the sample txt-file.
let idPrefix = "item"
let nodes1 =
let items = items1 |> Array.map(fun (id,name,_) ->
sprintf "%s%s [label=\"%s\"]; " idPrefix id name)
"node [shape=box,style=filled,color=lightblue]; " +
(items |> String.Concat)
let nodes2 =
let printStr =
sprintf "%s%s [label=\"%s\",color=%s]; " idPrefix
"node [shape=circle,style=filled]; " +
(items2 |> Array.map(function
| id, name, true, _ -> printStr id name "goldenrod2"
| id, name, false, _ -> printStr id name "lightgray"
) |> String.Concat)
let graphPart =
let printArrow aFrom aTo =
sprintf "%s%s -> %s%s;" idPrefix aFrom idPrefix aTo
let arrows1 =
items1 |> Array.filter(fun (i,_,lnk) -> lnk<>"")
|> Array.map(fun (item,_,lnk) -> printArrow item lnk)
let arrows2 =
items2 |> Array.filter(fun (i,_,_,lnk) -> lnk<>"")
|> Array.map(fun (item,_,_,lnk) -> printArrow item lnk)
(String.Concat arrows1) + (String.Concat arrows2)
"digraph myDiagram { " + nodes1 + nodes2 + graphPart + "overlap=false}"
// Create some data.
let generateWeatherGraph (data:seq<char * seq<char * float>>) =
let genId() = Guid.NewGuid().ToString("N")
let probabilities = data |> Map.ofSeq
let states =
data |> Seq.toArray |> Array.map (fun k -> fst k, genId())
let statesMap = states |> Map.ofSeq
let stateItems, changeItems =
states |> Array.collect(fun (stateLabel, stateId) ->
probabilities.[stateLabel] |> Seq.toArray
|> Array.map(fun (toState, propabilityLabel) ->
let changeStateId = genId()
(stateId, stateLabel.ToString(), changeStateId),
(changeStateId, propabilityLabel.ToString(), false, statesMap.[toState])
)
) |> Array.unzip
let graphdata = sampleGraphData stateItems changeItems
genereateGraphFile AppDomain.CurrentDomain.BaseDirectory graphdata
// https://github.com/mariuszwojcik/Presentations/blob/ff095259ea75f790723ada0e6e0612150ead061d/Introduction%20to%20Markov%20Chains%20in%20F%23/code/src/WeatherForecast.fsx#L10
let calcTransitionProbability items =
let total = items |> Seq.length
items
|> Seq.groupBy(id)
|> Seq.map(fun (g,i) -> g, float (i |> Seq.length) / float total)
let data =
"RRRRSSSSRRRRRRRRRRRRRRRRRRRSSSSSRR"
|> Seq.windowed 2
|> Seq.groupBy(fun a -> a.[0])
|> Seq.map(fun (k, e) -> (k,e |> Seq.map(Seq.skip 1 >> Seq.head) |> calcTransitionProbability))
[<EntryPoint>]
let main argv =
let r = generateWeatherGraph data
printfn "%A" argv
0 // return an integer exit code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment