Create a Model-Class
Prerequisites
Create a Model-Class
In this example a Network Design Model is created. Two sets of Nodes and Edges will be used.
Creating a constraint with OPTANO.Modeling is just as easy as
y[edge] <= x[edge];
A few ideas:
- The custom model shall not derive from Model. Just create a field with your Model instance (see the class NetworkDesignModel from the example)
- Use a VariableCollection as a group of variables, which are usually named by the same letter (x, y, ...). A VariableCollection may have no to several indices, this article about Variable Collections shows details.
- The constructor will create the required VariableCollection s, one for the x (flow Variable ) and one for the y (design decision Variable ). Usually, it is a good idea to extract the creation of a VariableCollection, Constraints and IndexSets in separate methods.
- Always use a generic VariableCollection (like @OPTANO.Modeling.Optimization.VariableCollection-2). It's much easier to refer to the variables afterwards.
- In default configuration, OPTANO.Modeling will create the required Variable automatically. It will get all required information from the VariableCollection.
- The use of lambda-Expressions for the lower and upper bounds and the names are a pretty mighty tool. Even if they look weird at the beginning, you'll love them soon.
Model Generator Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NDP
{
using OPTANO.Modeling.Optimization;
using OPTANO.Modeling.Optimization.Operators;
using OPTANO.Modeling.Optimization.Enums;
/// <summary>
/// A network design model
/// </summary>
public class NetworkDesignModel
{
/// <summary>
/// Initializes a new instance of the <see cref="NetworkDesignModel"/> class and initializes all fields.
/// </summary>
/// <param name="nodes">
/// The network nodes of the model
/// </param>
/// <param name="edges">
/// The edges of the model
/// </param>
public NetworkDesignModel(List<INode> nodes, List<IEdge> edges)
{
this.Nodes = nodes;
this.Edges = edges;
this.Model = new Model();
// Flow-Variables
this.x = new VariableCollection<IEdge>(
// register with this.Model
this.Model,
// Index is just the edge (might have time period in a more advanced problem)
this.Edges,
// the name of the variable collection
"x",
// Label-Generator
edge => $"Flow {edge.FromNode} to {edge.ToNode}",
// Lower Bound, 0 is default
edge => 0,
// if Capacity is set, use as upper bound (otherwise double.PositiveInfinity for unbounded)
edge => edge.Capacity ?? double.PositiveInfinity,
// Continuous Variable (is default)
edge => VariableType.Continuous);
// Design-Variables
this.y = new VariableCollection<IEdge>(
this.Model, // register this.Model
this.Edges, // Index is just the edge (might have time period in a more advanced problem)
"y", // the name of the variable collection
edge => $"Design {edge.FromNode} to {edge.ToNode}", // Label-Generator
edge => 0, // Lower Bound, 0 is default
edge => (edge.Capacity ?? double.PositiveInfinity) > 0 ? 1d : 0d, // if capacity is set and greater than 0, set 1 as bound for binary, otherwise 0. e
edge => VariableType.Binary); // it is a binary! only bounds of {0;1} are valid.
// Create Constraints
// Add flow-balance for every node
foreach (var node in this.Nodes)
{
// Add Constraint to model
this.Model.AddConstraint(
// equal flow of every edge arriving at the node
Expression.Sum(this.Edges.Where(e => e.ToNode == node).Select(edge => this.x[edge]))
// flows of every edge departing from the node
== Expression.Sum(this.Edges.Where(e => e.FromNode == node).Select(edge => this.x[edge]))
// plus the demand to fulfill
+ node.Demand,
$"FlowBalance, {node}"); // name of the constraint for debug use.
}
// if any edge is unbounded, take the sum of all demands as Big M
var bigM = this.Nodes.Where(node => node.Demand > 0).Sum(node => node.Demand);
foreach (var edge in this.Edges)
{
// Add Constraint to model
this.Model.AddConstraint(
// Set y greater or equal to edge's usage ratio, use Big M is capacity is unbounded
this.y[edge] >= this.x[edge] / (edge.Capacity ?? bigM),
// name of the constraint for debug use.
$"Desing_LowerBound {edge}");
this.Model.AddConstraint(
// Bound y to zero, if edge is not used at all (only required if design cost may be negative).
this.y[edge] <= this.x[edge],
// name of the constraint for debug use.
$"Desing_UpperBound {edge}");
}
// Add the objective
// Sum of all flows times the flow-unit-cost plus all design decisions and their respective costs.
// \sum_{edge \in Edges} \{ x_{edge} * costPerFlowUnit_{edge} + y_{edge} * designCost_{edge} \}
this.Model.AddObjective(
new Objective(Expression.Sum(this.Edges.Select(edge => (x[edge] * edge.CostPerFlowUnit) + (y[edge] * edge.DesignCost))),
"sum of all cost", // name
ObjectiveSense.Minimize)
); // minimize
}
/// <summary>
/// Gets the Model instance
/// </summary>
public Model Model { get; private set; }
/// <summary>
/// Gets the edges of this network
/// </summary>
public List<IEdge> Edges { get; }
/// <summary>
/// Gets the nodes of this network
/// </summary>
public List<INode> Nodes { get; }
/// <summary>
/// Gets the Collection of all flow variables
/// </summary>
public VariableCollection<IEdge> x { get; }
/// <summary>
/// Gets the Collection of all design variables
/// </summary>
public VariableCollection<IEdge> y { get; }
}
}
Visualization
The image below visualizes the data created below. In this example we have five cities and six edges. We want to create a cost optimal network of the nodes.
Node (Business Object)
The Business Layer in this example is just a class of nodes and edges: Node.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NDP
{
/// <summary>
/// A node of the network
/// </summary>
public class Node : INode
{
/// <summary>
/// Initializes a new instance of the <see cref="Node"/> class, representing a node of the network
/// </summary>
/// <param name="name">
/// Name of the node
/// </param>
/// <param name="demand">
/// The demand of this node. A negative demand is supply.
/// </param>
public Node(string name, double demand)
{
this.Name = name;
this.Demand = demand;
}
/// <summary>
/// Gets the demand of this node. A negative demand is supply.
/// </summary>
public double Demand { get; }
/// <summary>
/// Gets the name of the node
/// </summary>
public string Name { get; }
/// <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)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NDP
{
/// <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="capacity">
/// The capacity of the edge
/// </param>
/// <param name="costPerFlowUnit">
/// The cost per flow unit
/// </param>
/// <param name="designCost">
/// The design cost, applied if the edge is used at all.
/// </param>
public Edge(INode fromNode, INode toNode, double? capacity, double costPerFlowUnit, double designCost)
{
// set the parameter information
this.FromNode = fromNode;
this.ToNode = toNode;
this.Capacity = capacity;
this.CostPerFlowUnit = costPerFlowUnit;
this.DesignCost = designCost;
}
/// <summary>
/// Gets the design cost of this edge, which are applied if the edge is used at all.
/// </summary>
public double DesignCost { get; }
/// <summary>
/// Gets the cost per flow unit on this edge
/// </summary>
public double CostPerFlowUnit { get; }
/// <summary>
/// Gets the capacity of the edge
/// </summary>
public double? Capacity { 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>
/// Gets or sets the flow of the optimal solution
/// </summary>
public double Flow { get; set; }
/// <summary>
/// Name of the edge
/// </summary>
/// <returns>
/// The <see cref="string"/>.
/// </returns>
public override string ToString() => $"Edge {this.FromNode} to {this.ToNode}";
}
}