Business Objects & Instantiation
Visualization
The image below visualizes the data created below. In this example we have five cities and ten edges. We want to know the optimal tour, which has to be determined by the solver according to the constraints in the model we will define in the following step.
Node (Business Object)
For convenience we use an implementation with nodes and edges even if the nodes only have a name.
Node.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TSP
{
/// <summary>
/// A node of the traveling salesman problem network
/// </summary>
public class Node : INode
{
/// <summary>
/// Initializes a new instance of the <see cref="Node"/> class, representing a node of the traveling salesman problem
/// </summary>
/// <param name="name">
/// Name of the node
/// </param>
/// <param name="isStartingNode">
/// Is this node a starting node?
/// </param>
public Node(string name, bool isStartingNode)
{
this.Name = name;
this.IsStartingNode = isStartingNode;
}
/// <summary>
/// Gets the name of the node
/// </summary>
public string Name { get; }
/// <summary>
/// Indicates whether this node is a starting node (true) or not (false)
/// </summary>
public bool IsStartingNode { get; set; }
/// <summary>
/// Name of the node
/// </summary>
/// <returns>
/// The name of this node (<see cref="string"/>).
/// </returns>
public override string ToString() => this.Name;
}
}
Edge (Business Object)
Each edge connects exactly two nodes and has a set distance.
Edge.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TSP
{
/// <summary>
/// An edge between two nodes of the network
/// </summary>
public class Edge : IEdge
{
/// <summary>
/// Initializes a new instance of the <see cref="Edge"/> class
/// </summary>
/// <param name="fromNode">
/// The departing node
/// </param>
/// <param name="toNode">
/// The arrival node
/// </param>
/// <param name="distance">
/// The length of the edge
/// </param>
public Edge(INode fromNode, INode toNode, double? distance)
{
// set the parameter information
this.FromNode = fromNode;
this.ToNode = toNode;
this.Distance = distance;
}
/// <summary>
/// Gets the capacity of the edge
/// </summary>
public double? Distance { get; }
/// <summary>
/// Gets the end node of the edge
/// </summary>
public INode ToNode { get; }
/// <summary>
/// Gets the start node of the edge
/// </summary>
public INode FromNode { get; }
/// <summary>
/// Gets or sets a value indicating whether the edge is used in the solution
/// </summary>
public bool IsUsed { get; set; }
/// <summary>
/// Name of the edge
/// </summary>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
public override string ToString() => $"Edge {this.FromNode} to {this.ToNode}";
}
}
Program instance
Instantiation of the different Nodes and Edges, the Model, as well as the Solver and an empty Solution.
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TSP
{
using System.Configuration;
using OPTANO.Modeling.Common;
using OPTANO.Modeling.Optimization;
using OPTANO.Modeling.Optimization.Configuration;
using OPTANO.Modeling.Optimization.Solver.Gurobi810;
using System.IO;
/// <summary>
/// Demo program solving a traveling salesman problem
/// </summary>
class Program
{
/// <summary>
/// The main method
/// </summary>
/// <param name="args">
/// no arguments required
/// </param>
static void Main(string[] args)
{
INode pb = new Node("Paderborn",true);
INode ny = new Node("New York",false);
INode b = new Node("Beijing",false);
INode sp = new Node("São Paulo",false);
INode sf = new Node("San Francisco",false);
var nodes = new List<INode> { pb, ny, b, sp, sf };
var edges = new List<IEdge>
{
new Edge(pb, ny, 6100),
new Edge(pb, b, 7600),
new Edge(pb, sp, 9900),
new Edge(ny, sp, 7600),
new Edge(ny, b, 11000),
new Edge(b, sf, 9500),
new Edge(b, ny, 11000),
new Edge(sp, pb, 9900),
new Edge(sf, sp, 10500),
new Edge(sf, ny, 4100)
};
// Use long names for easier debugging/model understanding.
var config = new Configuration();
config.NameHandling = NameHandlingStyle.UniqueLongNames;
config.ComputeRemovedVariables = true;
using (var scope = new ModelScope(config))
{
// create a model, based on given data and the model scope
var travelingSalesmanModel = new TravelingSalesmanModel(nodes, edges);
// Get a solver instance, change your solver
using (var solver = new GurobiSolver())
{
// solve the model
var solution = solver.Solve(travelingSalesmanModel.Model);
// import the results back into the model
travelingSalesmanModel.Model.VariableCollections.ForEach(vc => vc.SetVariableValues(solution.VariableValues));
// print objective and variable decisions
Console.WriteLine($"{solution.ObjectiveValues.Single()}");
travelingSalesmanModel.y.Variables.ForEach(x => Console.WriteLine($"{x.ToString().PadRight(36)}: {x.Value}"));
travelingSalesmanModel.Model.VariableStatistics.WriteCSV(AppDomain.CurrentDomain.BaseDirectory);
Console.ReadLine();
}
}
}
}
}
Next Step
- Creating the Model