Show / Hide Table of Contents

    Create a Model-Class

    Prerequisites

    • NuGet Package is added to the project

    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

    NetworkDesignModel

    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)

    Edge.cs

    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}";
           
        }
    }
    

    Next step

    Go to Select the right Solver (for your project)

    Back to top Copyright © OPTANO GmbH generated with DocFX
    Privacy Policy | Impressum – Legal Notice