Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions PSGraph.Common/Model/PSCentralityRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using PSGraph.Model;

namespace PSGraph.Common.Model;

public class PSCentralityRecord
{
public required PSVertex Vertex;
public double Centrality;
}
9 changes: 9 additions & 0 deletions PSGraph.Common/Model/PSConnectedComponentRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using PSGraph.Model;

namespace PSGraph.Common.Model;

public class PSConnectedComponentRecord
{
public required PSVertex Vertex;
public int ComponentId;
}
9 changes: 9 additions & 0 deletions PSGraph.Common/Model/PSCycleRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;
using PSGraph.Model;

namespace PSGraph.Common.Model;

public class PSCycleRecord
{
public required IList<PSVertex> Vertices;
}
52 changes: 52 additions & 0 deletions PSGraph.Tests/FindGraphCycleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Xunit;
using System;
using System.Management.Automation;
using PSGraph.Model;
using FluentAssertions;
using System.Linq;
using PSGraph.Common.Model;

namespace PSGraph.Tests
{
public class FindGraphCycleCmdletTests : IDisposable
{
private readonly PowerShell _powershell;

public FindGraphCycleCmdletTests()
{
_powershell = PowerShell.Create();
_powershell.AddCommand("Import-Module").AddParameter("Assembly", typeof(PSGraph.Cmdlets.FindGraphCycle).Assembly);
_powershell.Invoke();
_powershell.Commands.Clear();
}

public void Dispose()
{
_powershell.Dispose();
}

[Fact]
public void FindGraphCycle_DetectsCycle()
{
_powershell.AddCommand("New-Graph");
var graphResults = _powershell.Invoke();
var graph = graphResults[0].BaseObject as PsBidirectionalGraph;
_powershell.Commands.Clear();

var a = new PSVertex("A");
var b = new PSVertex("B");
var cVertex = new PSVertex("C");
var d = new PSVertex("D");
graph.AddVertexRange(new[] { a, b, cVertex, d });
graph.AddEdge(new PSEdge(a, b, new PSEdgeTag()));
graph.AddEdge(new PSEdge(b, cVertex, new PSEdgeTag()));
graph.AddEdge(new PSEdge(cVertex, a, new PSEdgeTag()));

_powershell.AddCommand("Find-GraphCycle").AddParameter("Graph", graph);
var results = _powershell.Invoke();
var cycles = results.Select(r => r.BaseObject as PSCycleRecord).ToList();
cycles.Should().NotBeEmpty();
cycles.Any(cycle => cycle.Vertices.Contains(a) && cycle.Vertices.Contains(b) && cycle.Vertices.Contains(cVertex)).Should().BeTrue();
}
}
}
52 changes: 52 additions & 0 deletions PSGraph.Tests/GetGraphCentralityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Xunit;
using System;
using System.Management.Automation;
using PSGraph.Model;
using FluentAssertions;
using System.Linq;
using PSGraph.Common.Model;

namespace PSGraph.Tests
{
public class GetGraphCentralityCmdletTests : IDisposable
{
private readonly PowerShell _powershell;

public GetGraphCentralityCmdletTests()
{
_powershell = PowerShell.Create();
_powershell.AddCommand("Import-Module").AddParameter("Assembly", typeof(PSGraph.Cmdlets.GetGraphCentrality).Assembly);
_powershell.Invoke();
_powershell.Commands.Clear();
}

public void Dispose()
{
_powershell.Dispose();
}

[Fact]
public void GetGraphCentrality_ComputesBetweenness()
{
_powershell.AddCommand("New-Graph");
var graphResults = _powershell.Invoke();
var graph = graphResults[0].BaseObject as PsBidirectionalGraph;
_powershell.Commands.Clear();

var a = new PSVertex("A");
var b = new PSVertex("B");
var c = new PSVertex("C");
graph.AddVertexRange(new[] { a, b, c });
graph.AddEdge(new PSEdge(a, b, new PSEdgeTag()));
graph.AddEdge(new PSEdge(b, c, new PSEdgeTag()));

_powershell.AddCommand("Get-GraphCentrality").AddParameter("Graph", graph);
var results = _powershell.Invoke();
var records = results.Select(r => r.BaseObject as PSCentralityRecord).ToList();

records.Should().HaveCount(3);
records.Single(r => r.Vertex == b).Centrality.Should().BeGreaterThan(records.Single(r => r.Vertex == a).Centrality);
records.Single(r => r.Vertex == b).Centrality.Should().BeGreaterThan(records.Single(r => r.Vertex == c).Centrality);
}
}
}
57 changes: 57 additions & 0 deletions PSGraph.Tests/GetGraphConnectedComponentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Xunit;
using System;
using System.Management.Automation;
using PSGraph.Model;
using FluentAssertions;
using System.Linq;
using PSGraph.Common.Model;

namespace PSGraph.Tests
{
public class GetGraphConnectedComponentCmdletTests : IDisposable
{
private readonly PowerShell _powershell;

public GetGraphConnectedComponentCmdletTests()
{
_powershell = PowerShell.Create();
_powershell.AddCommand("Import-Module").AddParameter("Assembly", typeof(PSGraph.Cmdlets.GetGraphConnectedComponent).Assembly);
_powershell.Invoke();
_powershell.Commands.Clear();
}

public void Dispose()
{
_powershell.Dispose();
}

[Fact]
public void GetGraphConnectedComponent_ReturnsComponents()
{
_powershell.AddCommand("New-Graph");
var graphResults = _powershell.Invoke();
var graph = graphResults[0].BaseObject as PsBidirectionalGraph;

_powershell.Commands.Clear();

var a = new PSVertex("A");
var b = new PSVertex("B");
var c = new PSVertex("C");
var d = new PSVertex("D");
graph.AddVertexRange(new[] { a, b, c, d });
graph.AddEdge(new PSEdge(a, b, new PSEdgeTag()));
graph.AddEdge(new PSEdge(b, a, new PSEdgeTag()));
graph.AddEdge(new PSEdge(c, d, new PSEdgeTag()));
graph.AddEdge(new PSEdge(d, c, new PSEdgeTag()));

_powershell.AddCommand("Get-GraphConnectedComponent").AddParameter("Graph", graph);
var results = _powershell.Invoke();

var records = results.Select(r => r.BaseObject as PSConnectedComponentRecord).ToList();
records.Should().HaveCount(4);
var compAB = records.Where(r => r.Vertex == a || r.Vertex == b).Select(r => r.ComponentId).Distinct().Single();
var compCD = records.Where(r => r.Vertex == c || r.Vertex == d).Select(r => r.ComponentId).Distinct().Single();
compAB.Should().NotBe(compCD);
}
}
}
42 changes: 42 additions & 0 deletions PSGraph/cmdlets/graph/FindGraphCycle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Management.Automation;
using QuikGraph.Algorithms.Search;
using PSGraph.Model;
using PSGraph.Common.Model;

namespace PSGraph.Cmdlets
{
[Cmdlet("Find", "GraphCycle")]
public class FindGraphCycle : PSCmdlet
{
[Parameter(Mandatory = true)]
[ValidateNotNull]
public PsBidirectionalGraph Graph { get; set; } = null!;

protected override void EndProcessing()
{
var dfs = new DepthFirstSearchAlgorithm<PSVertex, PSEdge>(Graph);
var parents = new Dictionary<PSVertex, PSVertex>();
var cycles = new List<PSCycleRecord>();

dfs.TreeEdge += e => parents[e.Target] = e.Source;
dfs.BackEdge += e =>
{
var cycle = new List<PSVertex> { e.Target };
var current = e.Source;
while (!EqualityComparer<PSVertex>.Default.Equals(current, e.Target) && parents.TryGetValue(current, out var parent))
{
cycle.Add(current);
current = parent;
}
cycle.Add(e.Target);
cycle.Reverse();
cycles.Add(new PSCycleRecord { Vertices = cycle });
};

dfs.Compute();

WriteObject(cycles, enumerateCollection: true);
}
}
}
81 changes: 81 additions & 0 deletions PSGraph/cmdlets/graph/GetGraphCentrality.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using PSGraph.Common.Model;
using PSGraph.Model;

namespace PSGraph.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "GraphCentrality")]
public class GetGraphCentrality : PSCmdlet
{
[Parameter(Mandatory = true)]
[ValidateNotNull]
public PsBidirectionalGraph Graph { get; set; } = null!;

protected override void EndProcessing()
{
var centrality = ComputeBetweennessCentrality(Graph);
var results = new List<PSCentralityRecord>();
foreach (var kvp in centrality)
{
results.Add(new PSCentralityRecord { Vertex = kvp.Key, Centrality = kvp.Value });
}
WriteObject(results, enumerateCollection: true);
}

private static Dictionary<PSVertex, double> ComputeBetweennessCentrality(PsBidirectionalGraph graph)
{
var Cb = graph.Vertices.ToDictionary(v => v, v => 0.0);
foreach (var s in graph.Vertices)
{
var S = new Stack<PSVertex>();
var P = new Dictionary<PSVertex, List<PSVertex>>();
var sigma = new Dictionary<PSVertex, double>();
var dist = new Dictionary<PSVertex, int>();

foreach (var v in graph.Vertices)
{
P[v] = new List<PSVertex>();
sigma[v] = 0.0;
dist[v] = -1;
}
sigma[s] = 1.0;
dist[s] = 0;
var Q = new Queue<PSVertex>();
Q.Enqueue(s);
while (Q.Count > 0)
{
var v = Q.Dequeue();
S.Push(v);
foreach (var edge in graph.OutEdges(v))
{
var w = edge.Target;
if (dist[w] < 0)
{
Q.Enqueue(w);
dist[w] = dist[v] + 1;
}
if (dist[w] == dist[v] + 1)
{
sigma[w] += sigma[v];
P[w].Add(v);
}
}
}
var delta = graph.Vertices.ToDictionary(v => v, v => 0.0);
while (S.Count > 0)
{
var w = S.Pop();
foreach (var v in P[w])
{
delta[v] += (sigma[v] / sigma[w]) * (1.0 + delta[w]);
}
if (!EqualityComparer<PSVertex>.Default.Equals(w, s))
Cb[w] += delta[w];
}
}
return Cb;
}
}
}
30 changes: 30 additions & 0 deletions PSGraph/cmdlets/graph/GetGraphConnectedComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Management.Automation;
using QuikGraph.Algorithms.ConnectedComponents;
using PSGraph.Model;
using PSGraph.Common.Model;

namespace PSGraph.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "GraphConnectedComponent")]
public class GetGraphConnectedComponent : PSCmdlet
{
[Parameter(Mandatory = true)]
[ValidateNotNull]
public PsBidirectionalGraph Graph { get; set; } = null!;

protected override void EndProcessing()
{
var algorithm = new StronglyConnectedComponentsAlgorithm<PSVertex, PSEdge>(Graph);
algorithm.Compute();

var results = new List<PSConnectedComponentRecord>();
foreach (var pair in algorithm.Components)
{
results.Add(new PSConnectedComponentRecord { Vertex = pair.Key, ComponentId = pair.Value });
}

WriteObject(results, enumerateCollection: true);
}
}
}
Loading
Loading