Exemple #1
0
        public static double ComputePortfolioMinimumVariance(LabeledMatrix <Security> matrix, double expectedReturn, out Dictionary <Security, double> weights, bool allowShortSelling = false)
        {
            SolverContext context = SolverContext.GetContext();
            Model         model   = context.CreateModel();

            // since the row securities are the same as the column securities. use row.
            var securities      = matrix.RowEntities;
            var weightDecisions = new Dictionary <Security, Decision>();
            var rangeOfWeights  = allowShortSelling ? Domain.RealRange(-10, 10) : Domain.RealRange(0, 10);

            Term t1 = 0d; // constraint 1, sum of weights = 1
            Term t2 = 0d; // constraint 2, expected portfolio return (sum of weight*price) = expectedReturn

            foreach (var security in securities)
            {
                var securityWeightDecision = new Decision(rangeOfWeights, security.Symbol); // -10 <= w <= 10
                weightDecisions[security] = securityWeightDecision;
                model.AddDecisions(securityWeightDecision);

                t1 += securityWeightDecision;
                t2 += (securityWeightDecision * security.MarketPrice);
            }

            model.AddConstraint("SumOfWeights", t1 == 1d);
            model.AddConstraint("ExpectedPortfolioReturn", t2 == expectedReturn);

            // goal 1, the ptf var to be minimized
            Term varianceTerm = 0d;

            foreach (Security rowEntity in matrix.RowEntities)
            {
                foreach (Security columnEntity in matrix.ColumnEntities)
                {
                    varianceTerm += (matrix.Get(rowEntity, columnEntity) * weightDecisions[rowEntity] * weightDecisions[columnEntity]);
                }
            }

            Goal goal = model.AddGoal("MeanVariance", GoalKind.Minimize, varianceTerm);

            var gurobiDirective = new GurobiDirective();

            //gurobiDirective.OutputFlag = true;

            context.Solve(gurobiDirective);
            //Report report = solution.GetReport();
            //Console.WriteLine("{0}", report);
            //Console.WriteLine(goal.ToDouble());
            //foreach (var weightDecision in weightDecisions)
            //{
            //    Console.WriteLine(weightDecision.Key.Symbol + ":" + weightDecision.Value.GetDouble());
            //}
            context.ClearModel();

            weights = weightDecisions.ToDictionary(p => p.Key, p => p.Value.GetDouble());
            return(goal.ToDouble());
        }
        public RawOptimizedState Optimize(
			Network net,
			OptimizationOptions options)
        {
            var context = SolverContext.GetContext();
            context.ClearModel();
            var model = context.CreateModel();

            // Load in nodes, links, orders as lists.
            var nodes = net.Nodes.ToList();
            var links = net.Links.ToList();
            var orders = net.Orders.ToList();

            // Make sure the existing optimization has default weights set.
            if(options.AllowLocomotiveCapacityExpansion)
            {
                if(net.OptimizationResult.DefaultLinkExpansion == null)
                {
                    net.OptimizationResult.DefaultLinkExpansion = new ExpansionParameters()
                    {
                        CapacityExpansionMaxPossible = 100,
                        CapacityExpansionCostPerUnit = 10000
                    };
                }
            }
            if(options.AllowNodeCapacityExpansion)
            {
                if(net.OptimizationResult.DefaultNodeExpansion == null)
                {
                    net.OptimizationResult.DefaultNodeExpansion = new ExpansionParameters()
                    {
                        CapacityExpansionMaxPossible = 10000,
                        CapacityExpansionCostPerUnit = 1000
                    };
                }
            }

            #region Decision variables, car costs
            var flowDecisions = new Dictionary<Node,Dictionary<Link,Decision>>();
            Term totalCarCosts = 0;

            var expandNodeDecisions = new Dictionary<NodeOptimized,Decision>();
            var expandLinkDecisions = new Dictionary<LinkOptimized,Decision>();
            Term totalExpandCosts = 0;

            // One per link for each node.
            foreach(var node in nodes)
            {
                flowDecisions[node] = new Dictionary<Link,Decision>();
                foreach(var link in links)
                {
                    flowDecisions[node][link] = new Decision(
                        Domain.IntegerNonnegative,
                        "Node_" + node.ID + "_cars_" +
                        link.From.ID + "_to_" + link.To.ID
                    );

                    model.AddDecision(flowDecisions[node][link]);

                    // Car cost for this link.
                    totalCarCosts += flowDecisions[node][link] * link.Distance * net.CarCostPerMile;
                }
            }
            // Create the decision vars for expansion, if necessary.
            if(options.AllowNodeCapacityExpansion)
            {
                foreach(var node in net.OptimizationResult.Nodes)
                {
                    expandNodeDecisions[node] = new Decision(
                        Domain.IntegerNonnegative,
                        "Node_" + node.Node.ID + "_expansion"
                    );

                    model.AddDecision(expandNodeDecisions[node]);

                    if(node.Expansion == null)
                        node.Expansion = new ExpansionParameters();

                    var perUnitCost = node.Expansion.CapacityExpansionCostPerUnit ??
                        (int)net.OptimizationResult.DefaultNodeExpansion.CapacityExpansionCostPerUnit;
                    int maxPossible = node.Expansion.CapacityExpansionMaxPossible ??
                        (int)net.OptimizationResult.DefaultNodeExpansion.CapacityExpansionMaxPossible;

                    totalExpandCosts += expandNodeDecisions[node] * perUnitCost;

                    model.AddConstraint("Node_exp_cap_" + node.Node.ID,
                        expandNodeDecisions[node] <= maxPossible);
                }
            }
            if(options.AllowLocomotiveCapacityExpansion)
            {
                foreach(var link in net.OptimizationResult.Links)
                {
                    expandLinkDecisions[link] = new Decision(
                        Domain.IntegerNonnegative,
                        "Link_" + link.Link.ID + "_expansion"
                    );

                    model.AddDecision(expandLinkDecisions[link]);

                    if(link.Expansion == null)
                        link.Expansion = new ExpansionParameters();

                    var perUnitCost = link.Expansion.CapacityExpansionCostPerUnit ??
                        (int)net.OptimizationResult.DefaultLinkExpansion.CapacityExpansionCostPerUnit;
                    int maxPossible = link.Expansion.CapacityExpansionMaxPossible ??
                        (int)net.OptimizationResult.DefaultLinkExpansion.CapacityExpansionMaxPossible;

                    totalExpandCosts += expandLinkDecisions[link] * perUnitCost;

                    model.AddConstraint("Link_exp_cap_" + link.Link.ID,
                        expandLinkDecisions[link] <= maxPossible);
                }
            }
            #endregion

            #region Locomotive decision variables, locomotive costs
            Term totalLocomotiveCosts = 0;
            var locomotiveDecisions = new Dictionary<Link, Decision>();
            foreach(var link in links)
            {
                locomotiveDecisions[link] = new Decision(Domain.IntegerNonnegative,
                    "Locomotives_" + link.From.ID + "_to_" + link.To.ID);
                model.AddDecision(locomotiveDecisions[link]);

                // Constraint on max locomotives.
                // If optimized, number of locomotives is allowed to be below the capacity.
                Term locos = locomotiveDecisions[link];
                if(options.AllowLocomotiveCapacityExpansion)
                {
                    Decision expand = expandLinkDecisions.FirstOrDefault(
                        l => l.Key.Link == link
                    ).Value;

                    model.AddConstraint("Maxloco_" + link.From.ID + "_to_" + link.To.ID,
                        locos <= link.MaxTrains + expand);
                }
                else
                {
                    model.AddConstraint("Maxloco_" + link.From.ID + "_to_" + link.To.ID,
                        locos <= link.MaxTrains);
                }

                Term locoCost = link.Distance * locomotiveDecisions[link]
                    * (net.NonFuelCostPerMile + net.FuelCostPerMile * link.FuelAdjustment);
                totalLocomotiveCosts += locoCost;
            }
            #endregion

            #region Constraint 1: Order Flow
            // For loop so that indices are kept.
            foreach(var fulfiller in nodes)
            {
                foreach(var fulfilled in nodes)
                {
                    var fulfilledOrders = orders.Where(o => o.Origin == fulfiller);
                    if(fulfilled != fulfiller)
                        fulfilledOrders = fulfilledOrders.Where(o => o.Destination == fulfilled);

                    // Required flow on nodeFulfilled to satisfy the order.
                    int requiredFlow = 0;
                    foreach(Order order in fulfilledOrders)
                    {
                        if(fulfilled == fulfiller)
                            requiredFlow += order.Cars;
                        else
                            requiredFlow -= order.Cars;
                    }

                    // Sum of decision variables that should match the required flow.
                    Term actualFlow = 0;
                    // Get links that go to and from fulfilled.
                    var fromLinks = links.Where(l => l.From == fulfilled);
                    var toLinks = links.Where(l => l.To == fulfilled);
                    // Go through related decision variables.
                    foreach(var link in fromLinks)
                    {
                        // Decision variable index.
                        actualFlow += flowDecisions[fulfiller][link];
                    }
                    foreach(var link in toLinks)
                    {
                        // Decision variable index.
                        actualFlow -= flowDecisions[fulfiller][link];
                    }

                    model.AddConstraint("_" + fulfiller.ID + "_fulfilling_" + fulfilled.ID,
                        actualFlow == requiredFlow);
                }
            }
            #endregion

            #region Constraint 2: Link Capacity
            foreach(var link in links)
            {
                // Total amount of flow over this link, summed for all car sources.
                Term totalFlow = 0;

                foreach(var node in nodes)
                {
                    totalFlow += flowDecisions[node][link];
                }

                Term linkCapacity = locomotiveDecisions[link] * net.MaxCarsPerTrain;
                if(options.AllowLocomotiveCapacityExpansion)
                {
                    LinkOptimized optNode = net.OptimizationResult.Links.Where(n => n.Link == link).First();
                    // If the user has set max possible expansions for flow in or out, use them.

                    model.AddConstraint("Link_" + link.ID + "_cap",
                        totalFlow <= linkCapacity + expandLinkDecisions[optNode]);
                }
                else
                {
                    model.AddConstraint("Link_" + link.ID + "_cap",
                        totalFlow <= linkCapacity);
                }
            }
            #endregion

            #region Constraint 3/4: Node Capacity
            foreach(var node in nodes)
            {
                // Get all in and out links.
                var outLinks = links.Where(l => l.From == node);
                var inLinks = links.Where(l => l.To == node);

                Term flowOut = 0;
                Term flowIn = 0;

                foreach(var nodeo in nodes)
                {
                    foreach(var link in outLinks)
                        flowOut += flowDecisions[nodeo][link];
                    foreach(var link in inLinks)
                        flowIn += flowDecisions[nodeo][link];
                }

                if(options.AllowNodeCapacityExpansion)
                {
                    NodeOptimized optNode = net.OptimizationResult.Nodes.Where(n => n.Node == node).First();
                    // If the user has set max possible expansions for flow in or out, use them.

                    model.AddConstraint("Node_" + node.ID + "_capacity_out",
                        flowOut <= node.CarCapacity + expandNodeDecisions[optNode]);
                    model.AddConstraint("Node_" + node.ID + "_capacity_in",
                        flowIn <= node.CarCapacity + expandNodeDecisions[optNode]);
                    // If there is no max, make no constraint.
                }
                else
                {
                    model.AddConstraint("Node_" + node.ID + "_capacity_out",
                        flowOut <= node.CarCapacity);
                    model.AddConstraint("Node_" + node.ID + "_capacity_in",
                        flowIn <= node.CarCapacity);
                }
            }
            #endregion

            #region Objective Function
            // Objective function. (Minimize)
            Term totalCost = totalCarCosts + totalLocomotiveCosts + totalExpandCosts;
            model.AddGoal("TotalCost", GoalKind.Minimize, totalCost);
            #endregion

            //Directive directive = new LpSolveDirective();
            //Directive directive = new SimplexDirective();
            Directive directive = new GurobiDirective();
            //Directive directive = new MixedIntegerProgrammingDirective();

            directive.TimeLimit = TIMEOUT_MAX_SECONDS * 1000;

            var solution = context.Solve(directive);

            var extractedFlowDec = new Dictionary<Node, Dictionary<Link, int>>();
            var extractedLocoDec = new Dictionary<Link, int>();

            foreach(var nvp in flowDecisions)
            {
                extractedFlowDec[nvp.Key] = new Dictionary<Link,int>();
                foreach(var lvp in nvp.Value)
                {
                    extractedFlowDec[nvp.Key][lvp.Key] = (int)lvp.Value.ToDouble();
                }
            }
            foreach(var lvp in locomotiveDecisions)
            {
                extractedLocoDec[lvp.Key] = (int)lvp.Value.ToDouble();
            }

            foreach(var nvp in expandNodeDecisions)
            {
                nvp.Key.ExpansionSuggested = (int)nvp.Value.ToDouble();
            }
            foreach(var lvp in expandLinkDecisions)
            {
                lvp.Key.ExpansionSuggested = (int)lvp.Value.ToDouble();
            }

            // Extract the suggestion cost. (Don't know a better way to do this.)
            double suggestionCapitalCost = 0.0;

            if(options.AllowNodeCapacityExpansion)
            {
                foreach(var node in net.OptimizationResult.Nodes)
                {
                    var perUnitCost = node.Expansion.CapacityExpansionCostPerUnit ??
                        (int)net.OptimizationResult.DefaultNodeExpansion.CapacityExpansionCostPerUnit;

                    suggestionCapitalCost += expandNodeDecisions[node].ToDouble() * perUnitCost;
                }
            }
            if(options.AllowLocomotiveCapacityExpansion)
            {
                foreach(var link in net.OptimizationResult.Links)
                {
                    var perUnitCost = link.Expansion.CapacityExpansionCostPerUnit ??
                        (int)net.OptimizationResult.DefaultLinkExpansion.CapacityExpansionCostPerUnit;

                    suggestionCapitalCost += expandLinkDecisions[link].ToDouble() * perUnitCost;
                }
            }

            RawOptimizedState.RawQuality q;
            switch(solution.Quality)
            {
                case SolverQuality.Optimal:
                    q = RawOptimizedState.RawQuality.Solved;
                    break;

                case SolverQuality.Infeasible:
                    q = RawOptimizedState.RawQuality.Infeasible;
                    break;

                case SolverQuality.InfeasibleOrUnbounded:
                case SolverQuality.Unbounded:
                    q = RawOptimizedState.RawQuality.Unbounded;
                    break;

                case SolverQuality.Unknown:
                case SolverQuality.Feasible:
                default:
                    q = RawOptimizedState.RawQuality.TimedOut;
                    break;
            }
            var rawState = new RawOptimizedState()
            {
                solvedNetwork = net,
                flowDecisions = extractedFlowDec,
                locomotiveDecisions = extractedLocoDec,
                totalCost = (int)(model.Goals.First().ToDouble() - suggestionCapitalCost),
                suggestionCapitalCost = (int)suggestionCapitalCost,
                Quality = q
            };

            return rawState;
        }