From 17fdd2d4329b78889cf1ef5c285a2f3b88f13238 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Tue, 25 Nov 2025 13:31:48 -0800 Subject: [PATCH 01/11] Consolidate CLI entry points - add CliArgs, TERunner, FailureAnalysisRunner --- .stylecop/StyleCop.props | 2 +- Directory.Build.props | 5 + Directory.Packages.props | 49 +++ MetaOptimize.Cli/BPRunner.cs | 84 ++++ MetaOptimize.Cli/CliArgs.cs | 259 ++++++++++++ MetaOptimize.Cli/FailureAnalysisRunner.cs | 175 ++++++++ .../{MainEntry.cs => MainEntry_old.cs} | 56 +-- MetaOptimize.Cli/MetaOptimize.Cli.csproj | 30 +- MetaOptimize.Cli/PIFORunner.cs | 145 +++++++ MetaOptimize.Cli/Program.cs | 81 ++++ MetaOptimize.Cli/TERunner.cs | 374 ++++++++++++++++++ MetaOptimize.Cli/TESimpleRunner.cs | 169 ++++++++ .../FailureAnalysisBasicTests.cs | 6 +- MetaOptimize.Test/MetaOptimize.Test.csproj | 36 +- MetaOptimize/MetaOptimize.csproj | 26 +- MetaOptimize/SolverZen.cs | 4 +- Topologies/simple.json | 17 + 17 files changed, 1400 insertions(+), 118 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props create mode 100644 MetaOptimize.Cli/BPRunner.cs create mode 100644 MetaOptimize.Cli/CliArgs.cs create mode 100644 MetaOptimize.Cli/FailureAnalysisRunner.cs rename MetaOptimize.Cli/{MainEntry.cs => MainEntry_old.cs} (92%) create mode 100644 MetaOptimize.Cli/PIFORunner.cs create mode 100644 MetaOptimize.Cli/Program.cs create mode 100644 MetaOptimize.Cli/TERunner.cs create mode 100644 MetaOptimize.Cli/TESimpleRunner.cs create mode 100644 Topologies/simple.json diff --git a/.stylecop/StyleCop.props b/.stylecop/StyleCop.props index 6e01fabb9..294439ffd 100644 --- a/.stylecop/StyleCop.props +++ b/.stylecop/StyleCop.props @@ -4,7 +4,7 @@ False - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..193280892 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..42388488b --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,49 @@ + + + + strict + disable + 9999 + preview + enable + net8.0 + True + true + + + $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) + $(Platforms) + + Microsoft + © Microsoft Corporation. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MetaOptimize.Cli/BPRunner.cs b/MetaOptimize.Cli/BPRunner.cs new file mode 100644 index 000000000..febff70c4 --- /dev/null +++ b/MetaOptimize.Cli/BPRunner.cs @@ -0,0 +1,84 @@ +using System.Diagnostics; +using Gurobi; +using ZenLib; + +namespace MetaOptimize.Cli +{ + /// + /// Main entry point for traffic engineering experiments. + /// Executes adversarial optimization to find worst-case demand patterns for routing heuristics. + /// + /// + /// Flow: Load topology → Generate demand levels → Run adversarial optimization → Validate results. + /// Finds demand patterns where heuristic routes significantly less than optimal. + /// + public sealed class BPRunner + { + /// + /// Runs Bin Packing optimization. + /// Uses MainVBP logic (more complete than vbMain). + /// + public static void Run(CliArgs args) + { + var numBins = args.GetInt("--numBins", 6); + var numDemands = args.GetInt("--numDemands", 9); + var numDimensions = args.GetInt("--numDimensions", 2); + var binCapacityStr = args.Get("--binCapacity", "1.00001,1.00001"); + var optimalBins = args.GetInt("--optimalBins", 3); + var ffdMethod = args.Get("--ffdMethod", "FFDSum"); + var breakSymmetry = args.GetBool("--breakSymmetry", false); + var timeout = args.GetDouble("--timeout", 1000); + var verbose = args.GetBool("--verbose", false); + + Console.WriteLine($"Bins: {numBins}, Items: {numDemands}, Dimensions: {numDimensions}"); + Console.WriteLine($"Target optimal bins: {optimalBins}"); + + // Parse bin capacity + var binSize = binCapacityStr.Split(',').Select(double.Parse).ToList(); + if (binSize.Count != numDimensions) + { + throw new Exception($"Bin capacity dimensions ({binSize.Count}) must match numDimensions ({numDimensions})"); + } + + var bins = new Bins(numBins, binSize); + var ffdMethodChoice = ffdMethod switch + { + "FF" => FFDMethodChoice.FF, + "FFDProd" => FFDMethodChoice.FFDProd, + "FFDDiv" => FFDMethodChoice.FFDDiv, + _ => FFDMethodChoice.FFDSum, + }; + var solver = new GurobiSOS(timeout: timeout, verbose: Convert.ToInt32(verbose)); + var optimalEncoder = new VBPOptimalEncoder( + solver, numDemands, numDimensions, BreakSymmetry: breakSymmetry); + var ffdEncoder = new FFDItemCentricEncoder( + solver, numDemands, numDimensions); + var adversarialGenerator = new VBPAdversarialInputGenerator( + bins, numDemands, numDimensions); + + var timer = Stopwatch.StartNew(); + var (optimalSolution, ffdSolution) = adversarialGenerator.MaximizeOptimalityGapFFD( + optimalEncoder, ffdEncoder, optimalBins, + ffdMethod: ffdMethodChoice, itemList: null, verbose: verbose); + timer.Stop(); + + Console.WriteLine("\n" + new string('=', 60)); + Console.WriteLine("RESULTS:"); + Console.WriteLine($"Optimal bins used: {optimalSolution.TotalNumBinsUsed}"); + Console.WriteLine($"FFD bins used: {ffdSolution.TotalNumBinsUsed}"); + Console.WriteLine($"Gap: {ffdSolution.TotalNumBinsUsed - optimalSolution.TotalNumBinsUsed}"); + Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); + Console.WriteLine(new string('=', 60)); + + if (verbose) + { + Console.WriteLine("\nOptimal Solution:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( + optimalSolution, Newtonsoft.Json.Formatting.Indented)); + Console.WriteLine("\nFFD Solution:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( + ffdSolution, Newtonsoft.Json.Formatting.Indented)); + } + } + } +} \ No newline at end of file diff --git a/MetaOptimize.Cli/CliArgs.cs b/MetaOptimize.Cli/CliArgs.cs new file mode 100644 index 000000000..81ebf3e8f --- /dev/null +++ b/MetaOptimize.Cli/CliArgs.cs @@ -0,0 +1,259 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +namespace MetaOptimize.Cli +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Command-line argument parser and help system for MetaOptimize. + /// Provides a unified interface for all problem types with sensible defaults. + /// + public class CliArgs + { + private readonly Dictionary arguments = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// Command-line arguments array. + public CliArgs(string[] args) + { + this.ParseArguments(args); + } + + /// + /// Gets the problem type being solved. + /// + public string ProblemType => this.Get("--problemType", "FailureAnalysis"); + + /// + /// Gets a value indicating whether to show help. + /// + public bool ShowHelp => this.Has("--help") || this.Has("-h"); + + /// + /// Gets argument value or returns default if not present. + /// + /// Argument name (e.g., "--timeout"). + /// Default value if not specified. + /// Argument value or default. + public string Get(string key, string defaultValue = "") + { + return this.arguments.ContainsKey(key) ? this.arguments[key] : defaultValue; + } + + /// + /// Checks if argument is present. + /// + /// Argument name. + /// True if argument exists. + public bool Has(string key) + { + return this.arguments.ContainsKey(key); + } + + /// + /// Gets integer argument value. + /// + /// Argument name. + /// Default value. + /// Integer value. + public int GetInt(string key, int defaultValue = 0) + { + return int.TryParse(this.Get(key), out var value) ? value : defaultValue; + } + + /// + /// Gets double argument value. + /// + /// Argument name. + /// Default value. + /// Double value. + public double GetDouble(string key, double defaultValue = 0.0) + { + return double.TryParse(this.Get(key), out var value) ? value : defaultValue; + } + + /// + /// Gets boolean argument value. + /// + /// Argument name. + /// Default value. + /// Boolean value. + public bool GetBool(string key, bool defaultValue = false) + { + if (!this.Has(key)) + { + return defaultValue; + } + + var value = this.Get(key).ToLower(); + return value == "true" || value == "1" || value == "yes" || string.IsNullOrEmpty(value); + } + + /// + /// Displays comprehensive help information for all problem types. + /// + public void ShowHelpMessage() + { + Console.WriteLine("MetaOptimize - Adversarial Input Generation Framework"); + Console.WriteLine("======================================================\n"); + Console.WriteLine("USAGE:"); + Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- [OPTIONS]\n"); + Console.WriteLine("PROBLEM TYPES:"); + Console.WriteLine(" --problemType Problem to solve (required)"); + Console.WriteLine(" - TrafficEngineering: Network flow optimization"); + Console.WriteLine(" - BinPacking: Multi-dimensional bin packing"); + Console.WriteLine(" - PIFO: Packet scheduling optimization"); + Console.WriteLine(" - FailureAnalysis: Network failure scenario analysis\n"); + + Console.WriteLine("COMMON OPTIONS:"); + Console.WriteLine(" --verbose <0|1> Enable detailed output (default: 0)"); + Console.WriteLine(" --timeout Solver timeout in seconds (default: 1000)"); + Console.WriteLine(" --solver Solver choice: Gurobi, Zen (default: Gurobi)"); + Console.WriteLine(" --help, -h Show this help message\n"); + + this.ShowTrafficEngineeringHelp(); + this.ShowBinPackingHelp(); + this.ShowPIFOHelp(); + this.ShowFailureAnalysisHelp(); + + Console.WriteLine("\nEXAMPLES:"); + Console.WriteLine(" # Traffic Engineering with demand pinning"); + Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType TrafficEngineering \\"); + Console.WriteLine(" --topologyFile topology.json --heuristic DemandPinning --paths 2\n"); + Console.WriteLine(" # Bin Packing adversarial generation"); + Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType BinPacking \\"); + Console.WriteLine(" --numBins 6 --numDemands 9 --numDimensions 2 --optimalBins 3\n"); + Console.WriteLine(" # PIFO packet scheduling"); + Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType PIFO \\"); + Console.WriteLine(" --numPackets 18 --maxRank 8 --numQueues 4\n"); + Console.WriteLine(" # Failure Analysis"); + Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType FailureAnalysis \\"); + Console.WriteLine(" --maxNumFailures 2 --numExtraPaths 1 --failureProbThreshold 0.1\n"); + } + + private void ShowTrafficEngineeringHelp() + { + Console.WriteLine("TRAFFIC ENGINEERING OPTIONS:"); + Console.WriteLine(" --teMode Mode: simple (TEMain), advanced (ssMain) (default: simple)"); + Console.WriteLine(" --topologyFile Network topology JSON file (default: simple.json)"); + Console.WriteLine(" --pathFile Path configuration file"); + Console.WriteLine(" --heuristic Heuristic: DemandPinning, Pop, ExpectedPop, PopDp"); + Console.WriteLine(" (default: DemandPinning)"); + Console.WriteLine(" --paths Number of paths per source-dest pair (default: 2)"); + Console.WriteLine(" --method Optimization method: Direct, Search, FindFeas,"); + Console.WriteLine(" Random, HillClimber, SimulatedAnnealing (default: Direct)"); + Console.WriteLine(" --demandUB Upper bound for demands (default: 100.0)"); + Console.WriteLine(" --demandList Comma-separated demand quantization levels"); + Console.WriteLine(" (default: \"0,5,10,15,20\")"); + Console.WriteLine(" --dpThreshold Demand pinning threshold (default: 0.5)"); + Console.WriteLine(" --innerEncoding Inner encoding: PrimalDual, KKT (default: PrimalDual)"); + Console.WriteLine(" --downscale Topology downscale factor (default: 1.0)"); + Console.WriteLine(" --numThreads Number of Gurobi threads (default: 1)"); + Console.WriteLine(" --enableClustering Enable topology clustering"); + Console.WriteLine(" --numClusters Number of clusters (default: 2)"); + Console.WriteLine(" --clusterDir Directory for cluster files"); + Console.WriteLine(" --logFile Path to log file\n"); + } + + private void ShowBinPackingHelp() + { + Console.WriteLine("BIN PACKING OPTIONS:"); + Console.WriteLine(" --numBins Number of bins (default: 6)"); + Console.WriteLine(" --numDemands Number of items to pack (default: 9)"); + Console.WriteLine(" --numDimensions Number of dimensions (default: 2)"); + Console.WriteLine(" --binCapacity Comma-separated bin capacities per dimension"); + Console.WriteLine(" (default: \"1.00001,1.00001\")"); + Console.WriteLine(" --optimalBins Expected optimal number of bins (default: 3)"); + Console.WriteLine(" --ffdMethod FFD method: FFDSum, FFDDimension (default: FFDSum)"); + Console.WriteLine(" --breakSymmetry Enable symmetry breaking (default: false)\n"); + } + + private void ShowPIFOHelp() + { + Console.WriteLine("PIFO OPTIONS:"); + Console.WriteLine(" --numPackets Number of packets (default: 18)"); + Console.WriteLine(" --maxRank Maximum rank value (default: 8)"); + Console.WriteLine(" --numQueues Number of queues for SP-PIFO (default: 4)"); + Console.WriteLine(" --maxQueueSize Maximum queue size (default: 12)"); + Console.WriteLine(" --windowSize AIFO window size (default: 12)"); + Console.WriteLine(" --burstParam Burst parameter for AIFO (default: 0.1)\n"); + } + + private void ShowFailureAnalysisHelp() + { + Console.WriteLine("FAILURE ANALYSIS OPTIONS:"); + Console.WriteLine(" --topologyFile Network topology JSON file (required for custom topology)"); + Console.WriteLine(" --useDefaultTopology Use built-in test topology (default: true)"); + Console.WriteLine(" --maxNumFailures Maximum number of link failures (default: 1)"); + Console.WriteLine(" --numExtraPaths Number of extra paths (default: 1)"); + Console.WriteLine(" --demandList Comma-separated demand levels (default: \"0,5,10\")"); + Console.WriteLine(" --failureProbThreshold Failure probability threshold (default: 0.25)"); + Console.WriteLine(" --scenarioProbThreshold Scenario probability threshold"); + Console.WriteLine(" --innerEncoding Inner encoding: PrimalDual, KKT (default: PrimalDual)\n"); + } + + private void ParseArguments(string[] args) + { + for (int i = 0; i < args.Length; i++) + { + if (args[i].StartsWith("--") || args[i].StartsWith("-")) + { + string key = args[i]; + string value = string.Empty; + + // Check if next argument is a value (doesn't start with --) + if (i + 1 < args.Length && !args[i + 1].StartsWith("--") && !args[i + 1].StartsWith("-")) + { + value = args[i + 1]; + i++; // Skip next argument since we consumed it + } + + this.arguments[key] = value; + } + } + } + + /// + /// Validates that required arguments are present for the selected problem type. + /// + /// True if valid, false otherwise. + public bool Validate() + { + if (this.ShowHelp) + { + return true; + } + + var problemType = this.ProblemType; + var validProblemTypes = new[] { "TrafficEngineering", "BinPacking", "PIFO", "FailureAnalysis" }; + + if (!validProblemTypes.Contains(problemType)) + { + Console.WriteLine($"ERROR: Invalid problem type '{problemType}'"); + Console.WriteLine($"Valid types: {string.Join(", ", validProblemTypes)}"); + Console.WriteLine("Use --help for more information."); + return false; + } + + // Problem-specific validation + switch (problemType) + { + case "FailureAnalysis": + if (!this.GetBool("--useDefaultTopology", true) && !this.Has("--topologyFile")) + { + Console.WriteLine("ERROR: --topologyFile is required when not using default topology"); + return false; + } + break; + } + + return true; + } + } +} diff --git a/MetaOptimize.Cli/FailureAnalysisRunner.cs b/MetaOptimize.Cli/FailureAnalysisRunner.cs new file mode 100644 index 000000000..2760d0c72 --- /dev/null +++ b/MetaOptimize.Cli/FailureAnalysisRunner.cs @@ -0,0 +1,175 @@ +using System.Diagnostics; +using Gurobi; +using MetaOptimize.FailureAnalysis; +using ZenLib; +using ZenLib.ModelChecking; + +namespace MetaOptimize.Cli +{ + /// + /// Non-generic entry point that dispatches to the correct generic implementation. + /// + public static class FailureAnalysisRunner + { + /// + /// Generic implementation of Failure Analysis optimization. + /// + public static void Run(CliArgs args) + { + var solverChoice = args.Get("--solver", "Gurobi"); + var verbose = args.GetBool("--verbose", false); + var timeout = args.GetDouble("--timeout", 1000); + + switch (solverChoice.ToLower()) + { + case "gurobi": + FailureAnalysisRunnerImpl.CreateSolver = + () => new GurobiSOS(verbose: Convert.ToInt32(verbose), timeout: timeout); + FailureAnalysisRunnerImpl.Run(args); + break; + case "zen": + FailureAnalysisRunnerImpl, ZenSolution>.CreateSolver = + () => new SolverZen(); + FailureAnalysisRunnerImpl, ZenSolution>.Run(args); + break; + default: + throw new Exception($"Unsupported solver: {solverChoice}"); + } + } + } + + /// + /// Generic implementation of Failure Analysis optimization. + /// + internal sealed class FailureAnalysisRunnerImpl + { + internal static Func> CreateSolver = null; + + /// + /// Creates default topology for failure analysis testing. + /// + private static Topology CreateDefaultFailureTopology() + { + var topology = new Topology(); + topology.AddNode("a"); + topology.AddNode("b"); + topology.AddNode("c"); + topology.AddNode("d"); + topology.AddEdge("a", "b", capacity: 10); + topology.AddEdge("a", "c", capacity: 10); + topology.AddEdge("b", "d", capacity: 10); + topology.AddEdge("c", "d", capacity: 10); + topology.AddEdge("a", "d", capacity: 5); + topology.AddEdge("b", "c", capacity: 3); + return topology; + } + + /// + /// Reads topology from JSON file. + /// + private static Topology ReadTopologyFromFile(string filePath) + { + Console.WriteLine($"Loading topology from: {filePath}"); + // TODO: Implement proper JSON topology loading + var topology = new Topology(); + return topology; + } + + /// + /// Runs Failure Analysis optimization. + /// + internal static void Run(CliArgs args) + { + var useDefaultTopology = args.GetBool("--useDefaultTopology", true); + var maxNumFailures = args.GetInt("--maxNumFailures", 1); + var numExtraPaths = args.GetInt("--numExtraPaths", 1); + var demandListStr = args.Get("--demandList", "0,5,10"); + var failureProbThreshold = args.GetDouble("--failureProbThreshold", 0.25); + var scenarioProbThreshold = args.GetDouble("--scenarioProbThreshold", 0.0); + var innerEncoding = args.Get("--innerEncoding", "PrimalDual"); + var verbose = args.GetBool("--verbose", false); + + Console.WriteLine($"Max Failures: {maxNumFailures}, Extra Paths: {numExtraPaths}"); + Console.WriteLine($"Failure Prob Threshold: {failureProbThreshold}"); + + Topology topology; + if (useDefaultTopology) + { + topology = CreateDefaultFailureTopology(); + Console.WriteLine("Using default test topology"); + } + else + { + var topologyFile = args.Get("--topologyFile"); + topology = ReadTopologyFromFile(topologyFile); + Console.WriteLine($"Loaded topology from: {topologyFile}"); + } + + var demands = new Dictionary<(string, string), double> + { + { ("a", "d"), 10 }, + { ("b", "d"), 5 }, + { ("a", "c"), 5 }, + { ("c", "d"), 0 }, + { ("a", "b"), 0 }, + { ("b", "c"), 0 }, + }; + + var demandSet = new HashSet( + demandListStr.Split(',').Select(double.Parse)); + var demandList = new GenericList(demandSet); + + var probs = new Dictionary<(string, string), double> + { + { ("a", "d"), 0.3 }, + { ("b", "d"), 0.2 }, + { ("a", "c"), 0 }, + { ("a", "b"), 0 }, + { ("c", "d"), 0 }, + { ("b", "c"), 0 }, + }; + + var solver = CreateSolver(); + + var innerEncodingType = innerEncoding == "KKT" + ? InnerRewriteMethodChoice.KKT + : InnerRewriteMethodChoice.PrimalDual; + + var timer = Stopwatch.StartNew(); + + var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, 2); + var optimalCutEncoder = new FailureAnalysisEncoder(solver, 2); + var adversarialGenerator = new FailureAnalysisAdversarialGenerator(topology, 2); + + var (optimalSol, failureSol) = adversarialGenerator.MaximizeOptimalityGap( + optimalEncoder, optimalCutEncoder, + innerEncoding: innerEncodingType, + constrainedDemands: demands, + maxNumFailures: maxNumFailures, + demandList: demandList, + numExtraPaths: numExtraPaths, + lagFailureProbabilities: probs, + failureProbThreshold: failureProbThreshold); + + timer.Stop(); + + Console.WriteLine("\n" + new string('=', 60)); + Console.WriteLine("RESULTS:"); + Console.WriteLine($"Optimal objective: {optimalSol.MaxObjective}"); + Console.WriteLine($"Failure scenario objective: {failureSol.MaxObjective}"); + Console.WriteLine($"Gap: {optimalSol.MaxObjective - failureSol.MaxObjective}"); + Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); + Console.WriteLine(new string('=', 60)); + + if (verbose) + { + Console.WriteLine("\nOptimal Solution:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( + optimalSol, Newtonsoft.Json.Formatting.Indented)); + Console.WriteLine("\nFailure Scenario Solution:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( + failureSol, Newtonsoft.Json.Formatting.Indented)); + } + } + } +} \ No newline at end of file diff --git a/MetaOptimize.Cli/MainEntry.cs b/MetaOptimize.Cli/MainEntry_old.cs similarity index 92% rename from MetaOptimize.Cli/MainEntry.cs rename to MetaOptimize.Cli/MainEntry_old.cs index 0d93893bb..6dac6b34d 100644 --- a/MetaOptimize.Cli/MainEntry.cs +++ b/MetaOptimize.Cli/MainEntry_old.cs @@ -10,17 +10,18 @@ namespace MetaOptimize.Cli using Gurobi; using MetaOptimize; using ZenLib; + using ZenLib.ModelChecking; /// /// Main entry point for the program. /// - public class MainEntry + public class MainEntry_old { /// /// checks whether we get the solution we expect after running the solvers. /// /// - public static void TEExampleMain(string[] args) + public static void MainOld(string[] args) { var topology = new Topology(); topology.AddNode("a"); @@ -63,7 +64,7 @@ public static void TEExampleMain(string[] args) /// Use this function to test our theorem for VBP. /// (see theorem 1 in our NSDI24 Paper). /// - public static void vbpMain(string[] args) + public static void vbMain(string[] args) { // OPT = 2m + 3n // HUE = 4m + 6n @@ -188,7 +189,7 @@ public static void vbpMain(string[] args) /// test MetaOpt on VBP. /// /// TODO: specify how this function is different from the previous. - public static void Main(string[] args) + public static void MainVBP(string[] args) { var binSize = new List(); binSize.Add(1.00001); @@ -352,53 +353,6 @@ private static int ComputeInversionNum(PIFOOptimizationSolution optimalSolutionG return numInvOpt; } - /// - /// Experiments for NSDI. - /// - public static void NSDIMain(string[] args) - { - // NSDIExp.compareGapDelayDiffMethodsDP(); - // NSDIExp.compareLargeScaleGapDelayDiffMethodsDP(); - // NSDIExp.compareGapDelayDiffMethodsPop(); - // NSDIExp.AblationStudyClusteringOnDP(); - // NSDIExp.BlackBoxParameterTunning(); - NSDIExp.AddRealisticConstraintsDP(); - // NSDIExp.gapThresholdDemandPinningForDifferentTopologies(); - // NSDIExp.ImpactNumPathsPartitionsExpectedPop(); - // NSDIExp.AblationStudyClusteringOnDP(); - // NSDIExp.BlackBoxParameterTunning(); - // NSDIExp.AnalyzeModifiedDP(); - // NSDIExp.ImpactNumNodesRadixSmallWordTopoDemandPinning(); - // NSDIExp.ImpactNumSamplesExpectedPop(); - // NSDIExp.AnalyzeParallelHeuristics(); - } - - /// - /// Experiments for hotnets. - /// - public static void hotnetsMain(string[] args) - { - // var topology = Topology.RandomRegularGraph(8, 7, 1, seed: 0); - // var topology = Topology.SmallWordGraph(5, 4, 1); - // foreach (var edge in topology.GetAllEdges()) { - // Console.WriteLine(edge.Source + "_" + edge.Target); - // } - // foreach (var pair in topology.GetNodePairs()) { - // if (!topology.ContaintsEdge(pair.Item1, pair.Item2, 1)) { - // Console.WriteLine("missing link " + pair.Item1 + " " + pair.Item2); - // } - // } - // Experiment.printPaths(); - // HotNetsExperiment.impactOfDPThresholdOnGap(); - // Experiment.ImpactNumPathsDemandPinning(); - // Experiment.ImpactNumNodesRadixRandomRegularGraphDemandPinning(); - HotNetsExperiment.impactSmallWordGraphParamsDP(); - // Experiment.ImpactNumPathsPartitionsPop(); - // Experiment.compareGapDelayDiffMethodsPop(); - // Experiment.compareGapDelayDiffMethodsDP(); - // Experiment.compareTopoSizeLatency(); - } - /// /// Main entry point for the program. /// The function takes the command line arguments and stores them in a diff --git a/MetaOptimize.Cli/MetaOptimize.Cli.csproj b/MetaOptimize.Cli/MetaOptimize.Cli.csproj index ebeae9c2e..84841dcf4 100644 --- a/MetaOptimize.Cli/MetaOptimize.Cli.csproj +++ b/MetaOptimize.Cli/MetaOptimize.Cli.csproj @@ -1,31 +1,23 @@  - + Exe - net6.0 - enable - disable - x64 - x64 - MetaOptimize.Cli.MainEntry + MetaOptimize.Cli.Program - - - - - - - - - - - + + + + + + + + - + \ No newline at end of file diff --git a/MetaOptimize.Cli/PIFORunner.cs b/MetaOptimize.Cli/PIFORunner.cs new file mode 100644 index 000000000..c6b20c1c5 --- /dev/null +++ b/MetaOptimize.Cli/PIFORunner.cs @@ -0,0 +1,145 @@ +using System.Diagnostics; +using Gurobi; + +namespace MetaOptimize.Cli +{ + /// + /// Main entry point for traffic engineering experiments. + /// Executes adversarial optimization to find worst-case demand patterns for routing heuristics. + /// + /// + /// Flow: Load topology → Generate demand levels → Run adversarial optimization → Validate results. + /// Finds demand patterns where heuristic routes significantly less than optimal. + /// + public sealed class PIFORunner + { + /// + /// Computes inversion count for a single packet. + /// + private static int ComputeInversionNum( + PIFOOptimizationSolution solution, + Dictionary orderToRank, + int pid) + { + int numInv = 0; + + if (solution.Admit[pid] >= 0.98) + { + int currOrder = solution.Order[pid]; + for (int prev = 0; prev < currOrder; prev++) + { + if (orderToRank[prev] > solution.Ranks[pid]) + { + numInv += 1; + } + } + } + else + { + foreach (var (order, rank) in orderToRank) + { + if (rank > solution.Ranks[pid]) + { + numInv += 1; + } + } + } + + return numInv; + } + + /// + /// Computes inversion count for PIFO solutions. + /// + private static (int optimal, int heuristic) ComputeInversions( + PIFOOptimizationSolution optimalSol, + PIFOOptimizationSolution heuristicSol, + int numPackets) + { + var orderToRankOpt = new Dictionary(); + var orderToRankHeu = new Dictionary(); + + for (int pid = 0; pid < numPackets; pid++) + { + if (optimalSol.Admit[pid] == 1) + { + orderToRankOpt[optimalSol.Order[pid]] = optimalSol.Ranks[pid]; + } + + if (heuristicSol.Admit[pid] == 1) + { + orderToRankHeu[heuristicSol.Order[pid]] = heuristicSol.Ranks[pid]; + } + } + + int numInvOpt = 0; + int numInvHeu = 0; + + for (int pid = 0; pid < numPackets; pid++) + { + numInvOpt += ComputeInversionNum(optimalSol, orderToRankOpt, pid); + numInvHeu += ComputeInversionNum(heuristicSol, orderToRankHeu, pid); + } + + return (numInvOpt, numInvHeu); + } + + /// + /// Runs Bin Packing optimization. + /// Uses MainVBP logic (more complete than vbMain). + /// + public static void Run(CliArgs args) + { + var maxRank = args.GetInt("--maxRank", 8); + var numPackets = args.GetInt("--numPackets", 18); + var numQueues = args.GetInt("--numQueues", 4); + var maxQueueSize = args.GetInt("--maxQueueSize", 12); + var windowSize = args.GetInt("--windowSize", 12); + var burstParam = args.GetDouble("--burstParam", 0.1); + var timeout = args.GetDouble("--timeout", 1000); + var verbose = args.GetBool("--verbose", false); + + Console.WriteLine($"Packets: {numPackets}, Max Rank: {maxRank}, Queues: {numQueues}"); + Console.WriteLine($"Max Queue Size: {maxQueueSize}, Window Size: {windowSize}"); + + var solver = new GurobiSOS(verbose: Convert.ToInt32(verbose), timeout: timeout); + + // Create encoders - comparing SP-PIFO with drop vs AIFO + var h1 = new SPPIFOWithDropAvgDelayEncoder( + solver, numPackets, numQueues, maxRank, maxQueueSize); + var h2 = new AIFOAvgDelayEncoder( + solver, numPackets, maxRank, maxQueueSize, windowSize, burstParam); + + var adversarialGenerator = new PIFOAdversarialInputGenerator( + numPackets, maxRank); + + var timer = Stopwatch.StartNew(); + var (optimalSolution, heuristicSolution) = adversarialGenerator.MaximizeOptimalityGap( + h1, h2, verbose: verbose); + timer.Stop(); + + // Compute inversions + var (numInvOpt, numInvHeu) = ComputeInversions(optimalSolution, heuristicSolution, numPackets); + + Console.WriteLine("\n" + new string('=', 60)); + Console.WriteLine("RESULTS:"); + Console.WriteLine($"Optimal cost: {optimalSolution.Cost}"); + Console.WriteLine($"Heuristic cost: {heuristicSolution.Cost}"); + Console.WriteLine($"Gap: {heuristicSolution.Cost - optimalSolution.Cost}"); + Console.WriteLine($"Inversions (Optimal): {numInvOpt}"); + Console.WriteLine($"Inversions (Heuristic): {numInvHeu}"); + Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); + Console.WriteLine(new string('=', 60)); + + if (verbose) + { + Console.WriteLine("\nOptimal Solution:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( + optimalSolution, Newtonsoft.Json.Formatting.Indented)); + Console.WriteLine("\nHeuristic Solution:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( + heuristicSolution, Newtonsoft.Json.Formatting.Indented)); + } + } + } +} \ No newline at end of file diff --git a/MetaOptimize.Cli/Program.cs b/MetaOptimize.Cli/Program.cs new file mode 100644 index 000000000..3ad13e2f3 --- /dev/null +++ b/MetaOptimize.Cli/Program.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +namespace MetaOptimize.Cli +{ + /// + /// Entry point for MetaOptimize CLI. + /// Routes to different problem solvers based on command-line arguments. + /// + public class Program + { + /// + /// Main entry point for the program. + /// + /// Command-line arguments. + public static void Main(string[] args) + { + var cliArgs = new CliArgs(args); + + if (cliArgs.ShowHelp) + { + cliArgs.ShowHelpMessage(); + return; + } + + if (!cliArgs.Validate()) + { + Environment.Exit(1); + } + + try + { + var problemType = cliArgs.ProblemType; + Console.WriteLine($"Starting MetaOptimize - Problem Type: {problemType}"); + Console.WriteLine(new string('=', 60)); + + switch (problemType) + { + case "TrafficEngineering": + var mode = cliArgs.Get("--teMode", "advanced"); + if (mode.ToLower() == "advanced") + { + TERunner.RunAdvanced(args); + } + else + { + TERunner.RunSimple(cliArgs); + } + break; + + case "BinPacking": + BPRunner.Run(cliArgs); + break; + + case "PIFO": + PIFORunner.Run(cliArgs); + break; + + case "FailureAnalysis": + FailureAnalysisRunner.Run(cliArgs); + break; + + default: + Console.WriteLine($"ERROR: Unknown problem type '{problemType}'"); + Environment.Exit(1); + break; + } + + Console.WriteLine(new string('=', 60)); + Console.WriteLine("Execution completed successfully."); + } + catch (Exception ex) + { + Console.WriteLine($"\nERROR: {ex.Message}"); + Console.WriteLine($"Stack Trace:\n{ex.StackTrace}"); + Environment.Exit(1); + } + } + } +} diff --git a/MetaOptimize.Cli/TERunner.cs b/MetaOptimize.Cli/TERunner.cs new file mode 100644 index 000000000..316093917 --- /dev/null +++ b/MetaOptimize.Cli/TERunner.cs @@ -0,0 +1,374 @@ +using System.Diagnostics; +using CommandLine; +using Gurobi; +using ZenLib; +using ZenLib.ModelChecking; + +namespace MetaOptimize.Cli +{ + /// + /// Main entry point for traffic engineering experiments. + /// Executes adversarial optimization to find worst-case demand patterns for routing heuristics. + /// + public static class TERunner + { + /// + /// Runs Traffic Engineering optimization. + /// Uses CliUtils for topology loading and heuristic setup (same as original ssMain). + /// + public static void RunAdvanced(string[] args) + { + var opts = CommandLine.Parser.Default.ParseArguments(args).MapResult(o => o, e => null); + CliOptions.Instance = opts; + + if (opts == null) + { + Environment.Exit(0); + } + + // read the topology and clusters. + var (topology, clusters) = CliUtils.getTopology(opts.TopologyFile, opts.PathFile, opts.DownScaleFactor, opts.EnableClustering, + opts.NumClusters, opts.ClusterDir, opts.Verbose); + + getSolverAndRunNetwork(topology, clusters); + } + + // TODO: this function is missing proper commenting + private static void getSolverAndRunNetwork(Topology topology, List clusters) + { + var opts = CliOptions.Instance; + // use the Z3 solver via the Zen wrapper library. + switch (opts.SolverChoice) + { + case SolverChoice.Zen: + // run the zen optimizer. + RunNetwork(new SolverZen(), topology, clusters); + break; + case SolverChoice.Gurobi: + var storeProgress = opts.StoreProgress & (opts.Method == MethodChoice.Direct); + if (opts.Heuristic == Heuristic.DemandPinning) + { + RunNetwork(new GurobiSOS(opts.Timeout, Convert.ToInt32(opts.Verbose), + timeToTerminateNoImprovement: opts.TimeToTerminateIfNoImprovement, + numThreads: opts.NumGurobiThreads, + recordProgress: storeProgress, + logPath: opts.LogFile), + topology, clusters); + } + else + { + RunNetwork(new GurobiSOS(opts.Timeout, Convert.ToInt32(opts.Verbose), + timeToTerminateNoImprovement: opts.TimeToTerminateIfNoImprovement, + numThreads: opts.NumGurobiThreads, + recordProgress: storeProgress, + logPath: opts.LogFile), + topology, clusters); + } + break; + default: + throw new Exception("Other solvers are currently invalid."); + } + } + + // TODO: this function is missing proper commenting + private static void RunNetwork(ISolver solver, + Topology topology, List clusters) + { + var opts = CliOptions.Instance; + + // setup the optimal encoder and adversarial input generator. + var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, opts.Paths); + TEAdversarialInputGenerator adversarialInputGenerator; + adversarialInputGenerator = new TEAdversarialInputGenerator(topology, opts.Paths, opts.NumProcesses); + + // setup the heuristic encoder and partitions. + var heuristicSolver = solver; + var (heuristicEncoder, partitioning, partitionList) = CliUtils.getHeuristic(heuristicSolver, topology, opts.Heuristic, opts.Paths, opts.PopSlices, + opts.DemandPinningThreshold * opts.DownScaleFactor, numSamples: opts.NumRandom, partitionSensitivity: opts.PartitionSensitivity, + scaleFactor: opts.DownScaleFactor, InnerEncoding: opts.InnerEncoding, maxShortestPathLen: opts.MaxShortestPathLen); + + // find an adversarial example and show the time taken. + var demandList = new GenericList((opts.DemandList.Split(",")).Select(x => double.Parse(x) * opts.DownScaleFactor).ToHashSet()); + Utils.logger( + string.Format("Demand List:{0}", Newtonsoft.Json.JsonConvert.SerializeObject(demandList.List, Newtonsoft.Json.Formatting.Indented)), + opts.Verbose); + var timer = System.Diagnostics.Stopwatch.StartNew(); + Utils.logger("Starting setup", opts.Verbose); + (TEOptimizationSolution, TEOptimizationSolution) result; + switch (opts.Method) + { + case MethodChoice.Direct: + result = CliUtils.getMetaOptResult(adversarialInputGenerator, optimalEncoder, heuristicEncoder, opts.DemandUB, opts.InnerEncoding, + demandList, opts.EnableClustering, opts.ClusterVersion, clusters, opts.NumInterClusterSamples, opts.NumNodesPerCluster, + opts.NumInterClusterQuantizations, opts.Simplify, opts.Verbose, opts.MaxDensity, opts.LargeDemandLB, opts.maxLargeDistance, + opts.maxSmallDistance, false, null); + break; + case MethodChoice.Search: + Utils.logger("Going to use search to find a desirable gap", opts.Verbose); + result = adversarialInputGenerator.FindMaximumGapInterval(optimalEncoder, heuristicEncoder, opts.Confidencelvl, opts.StartingGap, opts.DemandUB, + demandList: demandList); + break; + case MethodChoice.FindFeas: + Utils.logger("Going to find one feasible solution with the specified gap", opts.Verbose); + result = adversarialInputGenerator.FindOptimalityGapAtLeast(optimalEncoder, heuristicEncoder, opts.StartingGap, opts.DemandUB, + demandList: demandList, simplify: opts.Simplify); + break; + case MethodChoice.Random: + Utils.logger("Going to do random search to find some advers inputs", opts.Verbose); + result = adversarialInputGenerator.RandomAdversarialGenerator(optimalEncoder, heuristicEncoder, opts.NumRandom, opts.DemandUB, seed: opts.Seed, + verbose: opts.Verbose, storeProgress: opts.StoreProgress, logPath: opts.LogFile, timeout: opts.Timeout); + break; + case MethodChoice.HillClimber: + Utils.logger("Going to use HillClimber to find some advers inputs", opts.Verbose); + result = adversarialInputGenerator.HillClimbingAdversarialGenerator(optimalEncoder, heuristicEncoder, opts.NumRandom, + opts.NumNeighbors, opts.DemandUB, opts.StdDev, seed: opts.Seed, verbose: opts.Verbose, storeProgress: opts.StoreProgress, + logPath: opts.LogFile, timeout: opts.Timeout); + break; + case MethodChoice.SimulatedAnnealing: + Utils.logger("Going to use Simulated Annealing to find some advers inputs", opts.Verbose); + Utils.logger(opts.LogFile, opts.Verbose); + result = adversarialInputGenerator.SimulatedAnnealing(optimalEncoder, heuristicEncoder, opts.NumRandom, opts.NumNeighbors, + opts.DemandUB, opts.StdDev, opts.InitTmp, opts.TmpDecreaseFactor, seed: opts.Seed, verbose: opts.Verbose, storeProgress: opts.StoreProgress, + logPath: opts.LogFile, timeout: opts.Timeout); + break; + default: + throw new Exception("Wrong Method, please choose between available methods!!"); + } + + if (opts.FullOpt) + { + if (!opts.EnableClustering) + { + throw new Exception("does not need to be enable for non-clustering method"); + } + if (opts.InnerEncoding != InnerRewriteMethodChoice.PrimalDual) + { + throw new Exception("inner encoding should be primal dual"); + } + optimalEncoder.Solver.CleanAll(timeout: opts.FullOptTimer); + var currDemands = new Dictionary<(string, string), double>(result.Item1.Demands); + Utils.setEmptyPairsToZero(topology, currDemands); + result = adversarialInputGenerator.MaximizeOptimalityGap(optimalEncoder, heuristicEncoder, opts.DemandUB, innerEncoding: opts.InnerEncoding, + demandList: demandList, simplify: opts.Simplify, verbose: opts.Verbose, demandInits: currDemands); + optimalEncoder.Solver.CleanAll(focusBstBd: false, timeout: opts.Timeout); + } + + if (opts.UBFocus) + { + var currDemands = new Dictionary<(string, string), double>(result.Item1.Demands); + optimalEncoder.Solver.CleanAll(focusBstBd: true, timeout: opts.UBFocusTimer); + Utils.setEmptyPairsToZero(topology, currDemands); + result = adversarialInputGenerator.MaximizeOptimalityGap(optimalEncoder, heuristicEncoder, opts.DemandUB, innerEncoding: opts.InnerEncoding, + demandList: demandList, simplify: opts.Simplify, verbose: opts.Verbose, demandInits: currDemands); + optimalEncoder.Solver.CleanAll(focusBstBd: false, timeout: opts.Timeout); + } + var optimal = result.Item1.MaxObjective; + var heuristic = result.Item2.MaxObjective; + var demands = new Dictionary<(string, string), double>(result.Item1.Demands); + Utils.setEmptyPairsToZero(topology, demands); + Console.WriteLine("##############################################"); + Console.WriteLine("##############################################"); + Console.WriteLine("##############################################"); + Console.WriteLine($"optimal={optimal}, heuristic={heuristic}, time={timer.ElapsedMilliseconds}ms"); + if (opts.Heuristic == Heuristic.ExpectedPop) + { + CliUtils.findGapExpectedPopAdversarialDemandOnIndependentPartitions(opts, topology, demands, optimal); + } + Console.WriteLine("##############################################"); + Console.WriteLine("##############################################"); + Console.WriteLine("##############################################"); + var optGSolver = new GurobiBinary(); + var optimalEncoderG = new TEMaxFlowOptimalEncoder(optGSolver, maxNumPaths: opts.Paths); + var optZSolver = new SolverZen(); + var optimalEncoderZen = new TEMaxFlowOptimalEncoder, ZenSolution>(optZSolver, maxNumPaths: opts.Paths); + + var gSolver = new GurobiBinary(); + var zSolver = new SolverZen(); + IEncoder heuristicEncoderG; + IEncoder, ZenSolution> heuristicEncoderZ; + switch (opts.Heuristic) + { + case Heuristic.Pop: + Console.WriteLine("Starting exploring pop heuristic"); + heuristicEncoderG = new PopEncoder(gSolver, maxNumPaths: opts.Paths, numPartitions: opts.PopSlices, demandPartitions: partitioning); + heuristicEncoderZ = new PopEncoder, ZenSolution>(zSolver, maxNumPaths: opts.Paths, numPartitions: opts.PopSlices, demandPartitions: partitioning); + break; + case Heuristic.DemandPinning: + Console.WriteLine("Starting exploring demand pinning heuristic"); + heuristicEncoderG = new DirectDemandPinningEncoder(gSolver, k: opts.Paths, threshold: opts.DemandPinningThreshold * opts.DownScaleFactor); + heuristicEncoderZ = new DirectDemandPinningEncoder, ZenSolution>(zSolver, k: opts.Paths, threshold: opts.DemandPinningThreshold * opts.DownScaleFactor); + break; + case Heuristic.ExpectedPop: + Console.WriteLine("Starting to explore expected pop heuristic"); + heuristicEncoderG = new ExpectedPopEncoder(gSolver, k: opts.Paths, numSamples: opts.NumRandom, + numPartitionsPerSample: opts.PopSlices, demandPartitionsList: partitionList); + heuristicEncoderZ = new ExpectedPopEncoder, ZenSolution>(zSolver, k: opts.Paths, numSamples: opts.NumRandom, + numPartitionsPerSample: opts.PopSlices, demandPartitionsList: partitionList); + break; + case Heuristic.PopDp: + throw new Exception("Not Implemented Yet."); + default: + throw new Exception("No heuristic selected."); + } + Utils.checkSolution(topology, heuristicEncoderG, optimalEncoderG, heuristic, optimal, demands, "gurobiCheck"); + } + + /// + /// Runs Traffic Engineering optimization. + /// Uses CliUtils for topology loading and heuristic setup (same as original ssMain). + /// + public static void RunSimple(CliArgs args) + { + var topologyFile = args.Get("--topologyFile", "simple.json"); + var heuristic = args.Get("--heuristic", "Pop"); + var paths = args.GetInt("--paths", 1); + var verbose = args.GetBool("--verbose", false); + var timeout = args.GetDouble("--timeout", 1000); + var numThreads = args.GetInt("--numThreads", 1); + var popSlices = args.GetInt("--popSlices", 2); + + Console.WriteLine($"Topology File: {topologyFile}"); + Console.WriteLine($"Heuristic: {heuristic}"); + Console.WriteLine($"Paths per pair: {paths}"); + + // Load topology from JSON (simple loading, not CliUtils) + var topology = ReadTopologyFromFile(topologyFile); + + // Create solver + var solver = new GurobiSOS( + timeout: timeout, + verbose: Convert.ToInt32(verbose), + numThreads: numThreads); + + // Create optimal encoder + var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, maxNumPaths: paths); + + // Create heuristic encoder + IEncoder heuristicEncoder; + IDictionary<(string, string), int> partition = null; + + switch (heuristic) + { + case "Pop": + partition = topology.RandomPartition(popSlices); + heuristicEncoder = new PopEncoder( + solver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); + break; + case "DemandPinning": + var dpThreshold = args.GetDouble("--dpThreshold", 0.5); + heuristicEncoder = new DirectDemandPinningEncoder( + solver, k: paths, threshold: dpThreshold); + break; + default: + throw new Exception($"Unsupported heuristic: {heuristic}"); + } + + // Create adversarial generator + var adversarialGenerator = new TEAdversarialInputGenerator(topology, maxNumPaths: paths); + + // Run optimization - SIMPLE call like original TEMain + var timer = Stopwatch.StartNew(); + var (optimalSolution, heuristicSolution) = adversarialGenerator.MaximizeOptimalityGap( + optimalEncoder, heuristicEncoder); + timer.Stop(); + + // Display results + Console.WriteLine("Optimal:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(optimalSolution, Newtonsoft.Json.Formatting.Indented)); + Console.WriteLine("****"); + Console.WriteLine("Heuristic:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(heuristicSolution, Newtonsoft.Json.Formatting.Indented)); + Console.WriteLine("****"); + + var optimal = optimalSolution.MaxObjective; + var heuristicObj = heuristicSolution.MaxObjective; + Console.WriteLine($"optimalG={optimal}, heuristicG={heuristicObj}, time={timer.ElapsedMilliseconds}ms"); + + // Validation - like original TEMain + var demands = new Dictionary<(string, string), double>(optimalSolution.Demands); + + var optGSolver = new GurobiSOS(); + var optimalEncoderG = new TEMaxFlowOptimalEncoder(optGSolver, maxNumPaths: paths); + + var popGSolver = new GurobiSOS(); + IEncoder heuristicEncoderG; + + switch (heuristic) + { + case "Pop": + heuristicEncoderG = new PopEncoder( + popGSolver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); + break; + case "DemandPinning": + var dpThreshold = args.GetDouble("--dpThreshold", 0.5); + heuristicEncoderG = new DirectDemandPinningEncoder( + popGSolver, k: paths, threshold: dpThreshold); + break; + default: + throw new Exception($"Unsupported heuristic: {heuristic}"); + } + + Utils.checkSolution(topology, heuristicEncoderG, optimalEncoderG, heuristicObj, optimal, demands, "gurobiCheck"); + } + + private static Topology ReadTopologyFromFile(string fileName) + { + var baseDir = AppDomain.CurrentDomain.BaseDirectory; + var topologiesDir = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", "Topologies")); + var filePath = Path.Combine(topologiesDir, fileName); + + if (!File.Exists(filePath)) + { + topologiesDir = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "Topologies")); + filePath = Path.Combine(topologiesDir, fileName); + } + + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"Topology file not found: {fileName}"); + } + + Console.WriteLine($"Loading topology from: {filePath}"); + + var json = File.ReadAllText(filePath); + var data = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + + var topology = new Topology(); + + foreach (var node in data.Nodes) + { + topology.AddNode(node.Id.ToString()); + } + + foreach (var link in data.Links) + { + topology.AddEdge(link.Source.ToString(), link.Target.ToString(), capacity: link.Capacity); + } + + Console.WriteLine($"Loaded: {data.Nodes.Count} nodes, {data.Links.Count} edges"); + return topology; + } + + private class TopologyJson + { + public List Nodes { get; set; } = new (); + public List Links { get; set; } = new (); + } + + private class NodeJson + { + [Newtonsoft.Json.JsonProperty("id")] + public object Id { get; set; } + } + + private class LinkJson + { + [Newtonsoft.Json.JsonProperty("source")] + public object Source { get; set; } + [Newtonsoft.Json.JsonProperty("target")] + public object Target { get; set; } + [Newtonsoft.Json.JsonProperty("capacity")] + public double Capacity { get; set; } + } + } +} \ No newline at end of file diff --git a/MetaOptimize.Cli/TESimpleRunner.cs b/MetaOptimize.Cli/TESimpleRunner.cs new file mode 100644 index 000000000..5e48a3efb --- /dev/null +++ b/MetaOptimize.Cli/TESimpleRunner.cs @@ -0,0 +1,169 @@ +using System.Diagnostics; +using Gurobi; + +namespace MetaOptimize.Cli +{ + /// + /// Traffic Engineering runner - matches original TEMain behavior. + /// + public static class TESimpleRunner + { + /// + /// Runs Traffic Engineering optimization. + /// Uses CliUtils for topology loading and heuristic setup (same as original ssMain). + /// + public static void Run(CliArgs args) + { + var topologyFile = args.Get("--topologyFile", "simple.json"); + var heuristic = args.Get("--heuristic", "Pop"); + var paths = args.GetInt("--paths", 1); + var verbose = args.GetBool("--verbose", false); + var timeout = args.GetDouble("--timeout", 1000); + var numThreads = args.GetInt("--numThreads", 1); + var popSlices = args.GetInt("--popSlices", 2); + + Console.WriteLine($"Topology File: {topologyFile}"); + Console.WriteLine($"Heuristic: {heuristic}"); + Console.WriteLine($"Paths per pair: {paths}"); + + // Load topology from JSON (simple loading, not CliUtils) + var topology = ReadTopologyFromFile(topologyFile); + + // Create solver + var solver = new GurobiSOS( + timeout: timeout, + verbose: Convert.ToInt32(verbose), + numThreads: numThreads); + + // Create optimal encoder + var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, maxNumPaths: paths); + + // Create heuristic encoder + IEncoder heuristicEncoder; + IDictionary<(string, string), int> partition = null; + + switch (heuristic) + { + case "Pop": + partition = topology.RandomPartition(popSlices); + heuristicEncoder = new PopEncoder( + solver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); + break; + case "DemandPinning": + var dpThreshold = args.GetDouble("--dpThreshold", 0.5); + heuristicEncoder = new DirectDemandPinningEncoder( + solver, k: paths, threshold: dpThreshold); + break; + default: + throw new Exception($"Unsupported heuristic: {heuristic}"); + } + + // Create adversarial generator + var adversarialGenerator = new TEAdversarialInputGenerator(topology, maxNumPaths: paths); + + // Run optimization - SIMPLE call like original TEMain + var timer = Stopwatch.StartNew(); + var (optimalSolution, heuristicSolution) = adversarialGenerator.MaximizeOptimalityGap( + optimalEncoder, heuristicEncoder); + timer.Stop(); + + // Display results + Console.WriteLine("Optimal:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(optimalSolution, Newtonsoft.Json.Formatting.Indented)); + Console.WriteLine("****"); + Console.WriteLine("Heuristic:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(heuristicSolution, Newtonsoft.Json.Formatting.Indented)); + Console.WriteLine("****"); + + var optimal = optimalSolution.MaxObjective; + var heuristicObj = heuristicSolution.MaxObjective; + Console.WriteLine($"optimalG={optimal}, heuristicG={heuristicObj}, time={timer.ElapsedMilliseconds}ms"); + + // Validation - like original TEMain + var demands = new Dictionary<(string, string), double>(optimalSolution.Demands); + + var optGSolver = new GurobiSOS(); + var optimalEncoderG = new TEMaxFlowOptimalEncoder(optGSolver, maxNumPaths: paths); + + var popGSolver = new GurobiSOS(); + IEncoder heuristicEncoderG; + + switch (heuristic) + { + case "Pop": + heuristicEncoderG = new PopEncoder( + popGSolver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); + break; + case "DemandPinning": + var dpThreshold = args.GetDouble("--dpThreshold", 0.5); + heuristicEncoderG = new DirectDemandPinningEncoder( + popGSolver, k: paths, threshold: dpThreshold); + break; + default: + throw new Exception($"Unsupported heuristic: {heuristic}"); + } + + Utils.checkSolution(topology, heuristicEncoderG, optimalEncoderG, heuristicObj, optimal, demands, "gurobiCheck"); + } + + private static Topology ReadTopologyFromFile(string fileName) + { + var baseDir = AppDomain.CurrentDomain.BaseDirectory; + var topologiesDir = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", "Topologies")); + var filePath = Path.Combine(topologiesDir, fileName); + + if (!File.Exists(filePath)) + { + topologiesDir = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "Topologies")); + filePath = Path.Combine(topologiesDir, fileName); + } + + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"Topology file not found: {fileName}"); + } + + Console.WriteLine($"Loading topology from: {filePath}"); + + var json = File.ReadAllText(filePath); + var data = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + + var topology = new Topology(); + + foreach (var node in data.Nodes) + { + topology.AddNode(node.Id.ToString()); + } + + foreach (var link in data.Links) + { + topology.AddEdge(link.Source.ToString(), link.Target.ToString(), capacity: link.Capacity); + } + + Console.WriteLine($"Loaded: {data.Nodes.Count} nodes, {data.Links.Count} edges"); + return topology; + } + + private class TopologyJson + { + public List Nodes { get; set; } = new (); + public List Links { get; set; } = new (); + } + + private class NodeJson + { + [Newtonsoft.Json.JsonProperty("id")] + public object Id { get; set; } + } + + private class LinkJson + { + [Newtonsoft.Json.JsonProperty("source")] + public object Source { get; set; } + [Newtonsoft.Json.JsonProperty("target")] + public object Target { get; set; } + [Newtonsoft.Json.JsonProperty("capacity")] + public double Capacity { get; set; } + } + } +} \ No newline at end of file diff --git a/MetaOptimize.Test/FailureAnalysisBasicTests.cs b/MetaOptimize.Test/FailureAnalysisBasicTests.cs index ad820cda9..1928e01fd 100644 --- a/MetaOptimize.Test/FailureAnalysisBasicTests.cs +++ b/MetaOptimize.Test/FailureAnalysisBasicTests.cs @@ -2,14 +2,11 @@ namespace MetaOptimize.Test { using System; using System.Collections.Generic; - using System.Configuration.Assemblies; - using System.Diagnostics.CodeAnalysis; using System.Linq; - using System.Runtime.InteropServices; using MetaOptimize; using MetaOptimize.FailureAnalysis; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Microsoft.Z3; + /// /// The tests for the basic failure analysis class. /// @@ -410,6 +407,7 @@ public void adversarialGeneratorWithProbabilityThreshold() (optimalSol, failureSol) = adversarialGenerator.MaximizeOptimalityGap(optimalEncoder, optimalCutEncoder, innerEncoding: InnerRewriteMethodChoice.PrimalDual, constrainedDemands: demands, maxNumFailures: 2, demandList: demandList, numExtraPaths: 1, lagFailureProbabilities: probs, failureProbThreshold: 0.1); Assert.IsTrue(Utils.IsApproximately(2, optimalSol.MaxObjective - failureSol.MaxObjective)); } + /// /// Tests that the instance based gap generator is correct. /// diff --git a/MetaOptimize.Test/MetaOptimize.Test.csproj b/MetaOptimize.Test/MetaOptimize.Test.csproj index 79fe2b92d..341171474 100644 --- a/MetaOptimize.Test/MetaOptimize.Test.csproj +++ b/MetaOptimize.Test/MetaOptimize.Test.csproj @@ -1,29 +1,19 @@  - - - net6.0 - disable - - false - - x64 - x64 - - - - - - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + - - - + \ No newline at end of file diff --git a/MetaOptimize/MetaOptimize.csproj b/MetaOptimize/MetaOptimize.csproj index 13f478f8f..d1e1d65eb 100644 --- a/MetaOptimize/MetaOptimize.csproj +++ b/MetaOptimize/MetaOptimize.csproj @@ -1,26 +1,14 @@  - - - Library - net6.0 - © Microsoft Corporation. All rights reserved. - Microsoft - x64 - x64 - - - - - - - - - - + + + + + + + - <_Parameter1>$(MSBuildProjectName)Tests diff --git a/MetaOptimize/SolverZen.cs b/MetaOptimize/SolverZen.cs index 9f476eddc..2ccc6424f 100644 --- a/MetaOptimize/SolverZen.cs +++ b/MetaOptimize/SolverZen.cs @@ -10,6 +10,8 @@ namespace MetaOptimize using System.Linq; using Gurobi; using ZenLib; + using ZenLib.ModelChecking; + /// /// An interface for an optimization solver. /// @@ -36,7 +38,7 @@ public class SolverZen : ISolver, ZenSolution> /// static SolverZen() { - ZenLib.Settings.UseLargeStack = true; + ZenSettings.UseLargeStack = true; } /// diff --git a/Topologies/simple.json b/Topologies/simple.json new file mode 100644 index 000000000..ad4231c91 --- /dev/null +++ b/Topologies/simple.json @@ -0,0 +1,17 @@ +{ + "directed": true, + "multigraph": false, + "graph": {}, + "nodes": [ + { "id": "a" }, + { "id": "b" }, + { "id": "c" }, + { "id": "d" } + ], + "links": [ + { "source": "a", "target": "b", "capacity": 10 }, + { "source": "a", "target": "c", "capacity": 10 }, + { "source": "b", "target": "d", "capacity": 10 }, + { "source": "c", "target": "d", "capacity": 10 } + ] +} \ No newline at end of file From 82eb3804641d050253ba1bb65a15126c944fb88e Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Tue, 25 Nov 2025 13:44:59 -0800 Subject: [PATCH 02/11] setting problem default --- MetaOptimize.Cli/CliArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetaOptimize.Cli/CliArgs.cs b/MetaOptimize.Cli/CliArgs.cs index 81ebf3e8f..8b6a8fe4b 100644 --- a/MetaOptimize.Cli/CliArgs.cs +++ b/MetaOptimize.Cli/CliArgs.cs @@ -28,7 +28,7 @@ public CliArgs(string[] args) /// /// Gets the problem type being solved. /// - public string ProblemType => this.Get("--problemType", "FailureAnalysis"); + public string ProblemType => this.Get("--problemType", "BinPacking"); /// /// Gets a value indicating whether to show help. From 58dc812d23f4ca8adbd8cb3bb4da47e1a2997923 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Tue, 25 Nov 2025 13:55:18 -0800 Subject: [PATCH 03/11] default for te mode --- .vscode/launch.json | 27 ++++++++++++++++++++++++ .vscode/settings.json | 4 ++++ .vscode/tasks.json | 41 +++++++++++++++++++++++++++++++++++++ MetaOptimize.Cli/Program.cs | 2 +- 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..eaa976bc7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + // "program": "${workspaceFolder}/MetaOptimize.Test/bin/Debug/net8.0/MetaOptimize.Test.dll", + "program": "${workspaceFolder}/MetaOptimize.Cli/bin/Debug/net8.0/MetaOptimize.Cli.exe", + "args": [], + "cwd": "${workspaceFolder}/MetaOptimize.Cli", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..b51f72738 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "dotnet.defaultSolution": "MetaOptimize.sln", + "python.formatting.provider": "none" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..a14aa16e2 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/MetaOptimize.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/MetaOptimize.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/MetaOptimize.sln" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/MetaOptimize.Cli/Program.cs b/MetaOptimize.Cli/Program.cs index 3ad13e2f3..859720e12 100644 --- a/MetaOptimize.Cli/Program.cs +++ b/MetaOptimize.Cli/Program.cs @@ -38,7 +38,7 @@ public static void Main(string[] args) switch (problemType) { case "TrafficEngineering": - var mode = cliArgs.Get("--teMode", "advanced"); + var mode = cliArgs.Get("--teMode", "simple"); if (mode.ToLower() == "advanced") { TERunner.RunAdvanced(args); From 8f99d3bb38a5643e388992fb4de2d756c08c10a2 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Fri, 28 Nov 2025 13:31:01 -0800 Subject: [PATCH 04/11] Consolidate CLI: unified CliOptions for all problem types, remove CliArgs --- Directory.Packages.props | 11 +- MetaOptimize.Cli/BPRunner.cs | 140 +++-- MetaOptimize.Cli/CliArgs.cs | 259 -------- MetaOptimize.Cli/CliOptions.cs | 614 ++++++++++++++----- MetaOptimize.Cli/FailureAnalysisRunner.cs | 165 +++-- MetaOptimize.Cli/MetaOptimize.Cli.csproj | 1 - MetaOptimize.Cli/PIFORunner.cs | 124 ++-- MetaOptimize.Cli/Program.cs | 67 ++- MetaOptimize.Cli/TERunner.cs | 502 ++++++++-------- MetaOptimize.Cli/TESimpleRunner.cs | 169 ------ MetaOptimize.Test/CliOptionsTests.cs | 668 +++++++++++++++++++++ MetaOptimize.Test/MetaOptimize.Test.csproj | 9 + 12 files changed, 1701 insertions(+), 1028 deletions(-) delete mode 100644 MetaOptimize.Cli/CliArgs.cs delete mode 100644 MetaOptimize.Cli/TESimpleRunner.cs create mode 100644 MetaOptimize.Test/CliOptionsTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 42388488b..5079c9bf0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,9 +11,8 @@ true - $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) - $(Platforms) - + x64 + Microsoft © Microsoft Corporation. All rights reserved. @@ -40,10 +39,4 @@ - - - - - - \ No newline at end of file diff --git a/MetaOptimize.Cli/BPRunner.cs b/MetaOptimize.Cli/BPRunner.cs index febff70c4..cad2e011b 100644 --- a/MetaOptimize.Cli/BPRunner.cs +++ b/MetaOptimize.Cli/BPRunner.cs @@ -1,81 +1,129 @@ -using System.Diagnostics; -using Gurobi; -using ZenLib; +// +// Copyright (c) Microsoft. All rights reserved. +// namespace MetaOptimize.Cli { + using System.Diagnostics; + using Gurobi; + using ZenLib; + using ZenLib.ModelChecking; + /// - /// Main entry point for traffic engineering experiments. - /// Executes adversarial optimization to find worst-case demand patterns for routing heuristics. + /// Runner for Vector Bin Packing adversarial optimization. + /// Finds item sizes that maximize the gap between optimal packing and First-Fit heuristics. /// /// - /// Flow: Load topology → Generate demand levels → Run adversarial optimization → Validate results. - /// Finds demand patterns where heuristic routes significantly less than optimal. + /// Flow: Configure bins → Create encoders → Run adversarial optimization → Display gap. + /// + /// Compares optimal bin packing solution against First-Fit variants (FF, FFDSum, FFDProd, FFDDiv). + /// The adversarial generator finds item sizes where the heuristic uses significantly more bins + /// than the optimal solution. + /// + /// Supports both Gurobi (MIP) and Zen (SMT) solvers via generic implementation. /// - public sealed class BPRunner + public static class BPRunner { /// - /// Runs Bin Packing optimization. - /// Uses MainVBP logic (more complete than vbMain). + /// Runs Bin Packing adversarial optimization. + /// Dispatches to the appropriate solver-specific implementation. + /// + /// Command-line options containing bin packing parameters. + /// Thrown when an unsupported solver is specified. + public static void Run(CliOptions opts) + { + switch (opts.SolverChoice) + { + case SolverChoice.Zen: + RunBinPacking(new SolverZen(), opts); + break; + case SolverChoice.Gurobi: + RunBinPacking( + new GurobiSOS(timeout: opts.Timeout, verbose: Convert.ToInt32(opts.Verbose)), + opts); + break; + default: + throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: Gurobi, Zen"); + } + } + + /// + /// Generic implementation of bin packing adversarial optimization. /// - public static void Run(CliArgs args) + /// Solver variable type (GRBVar or Zen). + /// Solver solution type (GRBModel or ZenSolution). + /// The solver instance to use. + /// Command-line options containing bin packing parameters. + /// + /// Creates three components: + /// 1. VBPOptimalEncoder: Encodes the optimal bin packing problem + /// 2. FFDItemCentricEncoder: Encodes the First-Fit heuristic behavior + /// 3. VBPAdversarialInputGenerator: Finds item sizes maximizing the gap + /// + /// The optimization finds item sizes such that: + /// - Optimal solution uses exactly opts.OptimalBins bins + /// - FFD heuristic uses as many bins as possible + /// - Gap = FFD bins - Optimal bins is maximized. + /// + private static void RunBinPacking(ISolver solver, CliOptions opts) { - var numBins = args.GetInt("--numBins", 6); - var numDemands = args.GetInt("--numDemands", 9); - var numDimensions = args.GetInt("--numDimensions", 2); - var binCapacityStr = args.Get("--binCapacity", "1.00001,1.00001"); - var optimalBins = args.GetInt("--optimalBins", 3); - var ffdMethod = args.Get("--ffdMethod", "FFDSum"); - var breakSymmetry = args.GetBool("--breakSymmetry", false); - var timeout = args.GetDouble("--timeout", 1000); - var verbose = args.GetBool("--verbose", false); + Console.WriteLine($"Bins: {opts.NumBins}, Items: {opts.NumDemands}, Dimensions: {opts.NumDimensions}"); + Console.WriteLine($"Target optimal bins: {opts.OptimalBins}"); + Console.WriteLine($"FF Method: {opts.FFMethod}"); - Console.WriteLine($"Bins: {numBins}, Items: {numDemands}, Dimensions: {numDimensions}"); - Console.WriteLine($"Target optimal bins: {optimalBins}"); + // Parse bin capacities from comma-separated string + var binCapacities = opts.BinCapacity.Split(',').Select(double.Parse).ToList(); - // Parse bin capacity - var binSize = binCapacityStr.Split(',').Select(double.Parse).ToList(); - if (binSize.Count != numDimensions) + // Pad capacities to match number of dimensions + while (binCapacities.Count < opts.NumDimensions) { - throw new Exception($"Bin capacity dimensions ({binSize.Count}) must match numDimensions ({numDimensions})"); + binCapacities.Add(1.00001); } - var bins = new Bins(numBins, binSize); - var ffdMethodChoice = ffdMethod switch - { - "FF" => FFDMethodChoice.FF, - "FFDProd" => FFDMethodChoice.FFDProd, - "FFDDiv" => FFDMethodChoice.FFDDiv, - _ => FFDMethodChoice.FFDSum, - }; - var solver = new GurobiSOS(timeout: timeout, verbose: Convert.ToInt32(verbose)); - var optimalEncoder = new VBPOptimalEncoder( - solver, numDemands, numDimensions, BreakSymmetry: breakSymmetry); - var ffdEncoder = new FFDItemCentricEncoder( - solver, numDemands, numDimensions); - var adversarialGenerator = new VBPAdversarialInputGenerator( - bins, numDemands, numDimensions); + // Create bin configuration + var bins = new Bins(opts.NumBins, binCapacities); + + // Create optimal encoder - finds minimum bins needed + var optimalEncoder = new VBPOptimalEncoder( + solver, opts.NumDemands, opts.NumDimensions, BreakSymmetry: opts.BreakSymmetry); + + // Create FFD encoder - simulates First-Fit heuristic behavior + var ffdEncoder = new FFDItemCentricEncoder( + solver, opts.NumDemands, opts.NumDimensions); + + // Create adversarial generator - finds worst-case item sizes + var adversarialGenerator = new VBPAdversarialInputGenerator( + bins, opts.NumDemands, opts.NumDimensions); var timer = Stopwatch.StartNew(); + + // Run bilevel optimization to find adversarial inputs + List> demandList = null; var (optimalSolution, ffdSolution) = adversarialGenerator.MaximizeOptimalityGapFFD( - optimalEncoder, ffdEncoder, optimalBins, - ffdMethod: ffdMethodChoice, itemList: null, verbose: verbose); + optimalEncoder, ffdEncoder, + opts.OptimalBins, + ffdMethod: opts.FFMethod, + itemList: demandList, + verbose: opts.Verbose); + timer.Stop(); + // Display results Console.WriteLine("\n" + new string('=', 60)); Console.WriteLine("RESULTS:"); Console.WriteLine($"Optimal bins used: {optimalSolution.TotalNumBinsUsed}"); - Console.WriteLine($"FFD bins used: {ffdSolution.TotalNumBinsUsed}"); + Console.WriteLine($"{opts.FFMethod} bins used: {ffdSolution.TotalNumBinsUsed}"); Console.WriteLine($"Gap: {ffdSolution.TotalNumBinsUsed - optimalSolution.TotalNumBinsUsed}"); Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); Console.WriteLine(new string('=', 60)); - if (verbose) + // Verbose output: full solution details as JSON + if (opts.Verbose) { Console.WriteLine("\nOptimal Solution:"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( optimalSolution, Newtonsoft.Json.Formatting.Indented)); - Console.WriteLine("\nFFD Solution:"); + Console.WriteLine("\nHeuristic Solution:"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( ffdSolution, Newtonsoft.Json.Formatting.Indented)); } diff --git a/MetaOptimize.Cli/CliArgs.cs b/MetaOptimize.Cli/CliArgs.cs deleted file mode 100644 index 8b6a8fe4b..000000000 --- a/MetaOptimize.Cli/CliArgs.cs +++ /dev/null @@ -1,259 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// - -namespace MetaOptimize.Cli -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Command-line argument parser and help system for MetaOptimize. - /// Provides a unified interface for all problem types with sensible defaults. - /// - public class CliArgs - { - private readonly Dictionary arguments = new Dictionary(); - - /// - /// Initializes a new instance of the class. - /// - /// Command-line arguments array. - public CliArgs(string[] args) - { - this.ParseArguments(args); - } - - /// - /// Gets the problem type being solved. - /// - public string ProblemType => this.Get("--problemType", "BinPacking"); - - /// - /// Gets a value indicating whether to show help. - /// - public bool ShowHelp => this.Has("--help") || this.Has("-h"); - - /// - /// Gets argument value or returns default if not present. - /// - /// Argument name (e.g., "--timeout"). - /// Default value if not specified. - /// Argument value or default. - public string Get(string key, string defaultValue = "") - { - return this.arguments.ContainsKey(key) ? this.arguments[key] : defaultValue; - } - - /// - /// Checks if argument is present. - /// - /// Argument name. - /// True if argument exists. - public bool Has(string key) - { - return this.arguments.ContainsKey(key); - } - - /// - /// Gets integer argument value. - /// - /// Argument name. - /// Default value. - /// Integer value. - public int GetInt(string key, int defaultValue = 0) - { - return int.TryParse(this.Get(key), out var value) ? value : defaultValue; - } - - /// - /// Gets double argument value. - /// - /// Argument name. - /// Default value. - /// Double value. - public double GetDouble(string key, double defaultValue = 0.0) - { - return double.TryParse(this.Get(key), out var value) ? value : defaultValue; - } - - /// - /// Gets boolean argument value. - /// - /// Argument name. - /// Default value. - /// Boolean value. - public bool GetBool(string key, bool defaultValue = false) - { - if (!this.Has(key)) - { - return defaultValue; - } - - var value = this.Get(key).ToLower(); - return value == "true" || value == "1" || value == "yes" || string.IsNullOrEmpty(value); - } - - /// - /// Displays comprehensive help information for all problem types. - /// - public void ShowHelpMessage() - { - Console.WriteLine("MetaOptimize - Adversarial Input Generation Framework"); - Console.WriteLine("======================================================\n"); - Console.WriteLine("USAGE:"); - Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- [OPTIONS]\n"); - Console.WriteLine("PROBLEM TYPES:"); - Console.WriteLine(" --problemType Problem to solve (required)"); - Console.WriteLine(" - TrafficEngineering: Network flow optimization"); - Console.WriteLine(" - BinPacking: Multi-dimensional bin packing"); - Console.WriteLine(" - PIFO: Packet scheduling optimization"); - Console.WriteLine(" - FailureAnalysis: Network failure scenario analysis\n"); - - Console.WriteLine("COMMON OPTIONS:"); - Console.WriteLine(" --verbose <0|1> Enable detailed output (default: 0)"); - Console.WriteLine(" --timeout Solver timeout in seconds (default: 1000)"); - Console.WriteLine(" --solver Solver choice: Gurobi, Zen (default: Gurobi)"); - Console.WriteLine(" --help, -h Show this help message\n"); - - this.ShowTrafficEngineeringHelp(); - this.ShowBinPackingHelp(); - this.ShowPIFOHelp(); - this.ShowFailureAnalysisHelp(); - - Console.WriteLine("\nEXAMPLES:"); - Console.WriteLine(" # Traffic Engineering with demand pinning"); - Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType TrafficEngineering \\"); - Console.WriteLine(" --topologyFile topology.json --heuristic DemandPinning --paths 2\n"); - Console.WriteLine(" # Bin Packing adversarial generation"); - Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType BinPacking \\"); - Console.WriteLine(" --numBins 6 --numDemands 9 --numDimensions 2 --optimalBins 3\n"); - Console.WriteLine(" # PIFO packet scheduling"); - Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType PIFO \\"); - Console.WriteLine(" --numPackets 18 --maxRank 8 --numQueues 4\n"); - Console.WriteLine(" # Failure Analysis"); - Console.WriteLine(" dotnet run --project MetaOptimize.Cli -- --problemType FailureAnalysis \\"); - Console.WriteLine(" --maxNumFailures 2 --numExtraPaths 1 --failureProbThreshold 0.1\n"); - } - - private void ShowTrafficEngineeringHelp() - { - Console.WriteLine("TRAFFIC ENGINEERING OPTIONS:"); - Console.WriteLine(" --teMode Mode: simple (TEMain), advanced (ssMain) (default: simple)"); - Console.WriteLine(" --topologyFile Network topology JSON file (default: simple.json)"); - Console.WriteLine(" --pathFile Path configuration file"); - Console.WriteLine(" --heuristic Heuristic: DemandPinning, Pop, ExpectedPop, PopDp"); - Console.WriteLine(" (default: DemandPinning)"); - Console.WriteLine(" --paths Number of paths per source-dest pair (default: 2)"); - Console.WriteLine(" --method Optimization method: Direct, Search, FindFeas,"); - Console.WriteLine(" Random, HillClimber, SimulatedAnnealing (default: Direct)"); - Console.WriteLine(" --demandUB Upper bound for demands (default: 100.0)"); - Console.WriteLine(" --demandList Comma-separated demand quantization levels"); - Console.WriteLine(" (default: \"0,5,10,15,20\")"); - Console.WriteLine(" --dpThreshold Demand pinning threshold (default: 0.5)"); - Console.WriteLine(" --innerEncoding Inner encoding: PrimalDual, KKT (default: PrimalDual)"); - Console.WriteLine(" --downscale Topology downscale factor (default: 1.0)"); - Console.WriteLine(" --numThreads Number of Gurobi threads (default: 1)"); - Console.WriteLine(" --enableClustering Enable topology clustering"); - Console.WriteLine(" --numClusters Number of clusters (default: 2)"); - Console.WriteLine(" --clusterDir Directory for cluster files"); - Console.WriteLine(" --logFile Path to log file\n"); - } - - private void ShowBinPackingHelp() - { - Console.WriteLine("BIN PACKING OPTIONS:"); - Console.WriteLine(" --numBins Number of bins (default: 6)"); - Console.WriteLine(" --numDemands Number of items to pack (default: 9)"); - Console.WriteLine(" --numDimensions Number of dimensions (default: 2)"); - Console.WriteLine(" --binCapacity Comma-separated bin capacities per dimension"); - Console.WriteLine(" (default: \"1.00001,1.00001\")"); - Console.WriteLine(" --optimalBins Expected optimal number of bins (default: 3)"); - Console.WriteLine(" --ffdMethod FFD method: FFDSum, FFDDimension (default: FFDSum)"); - Console.WriteLine(" --breakSymmetry Enable symmetry breaking (default: false)\n"); - } - - private void ShowPIFOHelp() - { - Console.WriteLine("PIFO OPTIONS:"); - Console.WriteLine(" --numPackets Number of packets (default: 18)"); - Console.WriteLine(" --maxRank Maximum rank value (default: 8)"); - Console.WriteLine(" --numQueues Number of queues for SP-PIFO (default: 4)"); - Console.WriteLine(" --maxQueueSize Maximum queue size (default: 12)"); - Console.WriteLine(" --windowSize AIFO window size (default: 12)"); - Console.WriteLine(" --burstParam Burst parameter for AIFO (default: 0.1)\n"); - } - - private void ShowFailureAnalysisHelp() - { - Console.WriteLine("FAILURE ANALYSIS OPTIONS:"); - Console.WriteLine(" --topologyFile Network topology JSON file (required for custom topology)"); - Console.WriteLine(" --useDefaultTopology Use built-in test topology (default: true)"); - Console.WriteLine(" --maxNumFailures Maximum number of link failures (default: 1)"); - Console.WriteLine(" --numExtraPaths Number of extra paths (default: 1)"); - Console.WriteLine(" --demandList Comma-separated demand levels (default: \"0,5,10\")"); - Console.WriteLine(" --failureProbThreshold Failure probability threshold (default: 0.25)"); - Console.WriteLine(" --scenarioProbThreshold Scenario probability threshold"); - Console.WriteLine(" --innerEncoding Inner encoding: PrimalDual, KKT (default: PrimalDual)\n"); - } - - private void ParseArguments(string[] args) - { - for (int i = 0; i < args.Length; i++) - { - if (args[i].StartsWith("--") || args[i].StartsWith("-")) - { - string key = args[i]; - string value = string.Empty; - - // Check if next argument is a value (doesn't start with --) - if (i + 1 < args.Length && !args[i + 1].StartsWith("--") && !args[i + 1].StartsWith("-")) - { - value = args[i + 1]; - i++; // Skip next argument since we consumed it - } - - this.arguments[key] = value; - } - } - } - - /// - /// Validates that required arguments are present for the selected problem type. - /// - /// True if valid, false otherwise. - public bool Validate() - { - if (this.ShowHelp) - { - return true; - } - - var problemType = this.ProblemType; - var validProblemTypes = new[] { "TrafficEngineering", "BinPacking", "PIFO", "FailureAnalysis" }; - - if (!validProblemTypes.Contains(problemType)) - { - Console.WriteLine($"ERROR: Invalid problem type '{problemType}'"); - Console.WriteLine($"Valid types: {string.Join(", ", validProblemTypes)}"); - Console.WriteLine("Use --help for more information."); - return false; - } - - // Problem-specific validation - switch (problemType) - { - case "FailureAnalysis": - if (!this.GetBool("--useDefaultTopology", true) && !this.Has("--topologyFile")) - { - Console.WriteLine("ERROR: --topologyFile is required when not using default topology"); - return false; - } - break; - } - - return true; - } - } -} diff --git a/MetaOptimize.Cli/CliOptions.cs b/MetaOptimize.Cli/CliOptions.cs index 326c2f1ff..f68dbfe44 100644 --- a/MetaOptimize.Cli/CliOptions.cs +++ b/MetaOptimize.Cli/CliOptions.cs @@ -7,408 +7,722 @@ namespace MetaOptimize.Cli using CommandLine; /// - /// The CLI command line arguments. + /// Command-line arguments for all MetaOptimize problem types. /// + /// + /// Supports four problem types: + /// - TrafficEngineering: Find worst-case demand patterns for routing heuristics + /// - BinPacking: Find adversarial item sizes that maximize FFD vs optimal gap + /// - PIFO: Find packet sequences that maximize scheduling inversions + /// - FailureAnalysis: Analyze network resilience under link failures + /// + /// Parameters are organized by: + /// - Common: Shared across all problem types + /// - Traffic Engineering: TE-specific options (largest section, most mature) + /// - Bin Packing: VBP-specific options + /// - PIFO: Packet scheduling options + /// - Failure Analysis: Network resilience options. + /// public class CliOptions { /// - /// The instance of the command line arguments. + /// Singleton instance of the parsed command-line arguments. + /// Set by Program.Main() after parsing. /// public static CliOptions Instance { get; set; } + #region Common Options + /// - /// The topology file path. + /// The problem type to solve. + /// Determines which runner is invoked and which parameters are relevant. /// - [Option('f', "file", Required = true, HelpText = "The location of the topology file.")] - public string TopologyFile { get; set; } + [Option("problemType", Default = ProblemType.TrafficEngineering, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] + public ProblemType ProblemType { get; set; } /// - /// The heuristic encoder to use. + /// The solver to use for optimization. + /// Gurobi uses MIP (Mixed Integer Programming), Zen uses SMT (Satisfiability Modulo Theories). /// - /// TODO: Is this specific to only the TE use-case? if so, is there a way to make a separate data structure for each heuristic example to make the code cleaner? - [Option('h', "heuristic", Required = true, HelpText = "The heuristic encoder to use (Pop | DemandPinning | ExpectedPop).")] - public Heuristic Heuristic { get; set; } + [Option('c', "solver", Default = SolverChoice.Gurobi, HelpText = "The solver to use (Gurobi | Zen)")] + public SolverChoice SolverChoice { get; set; } /// - /// The solver we want to use. + /// Timeout for the solver in seconds. + /// Solver terminates and returns best solution found after this time. /// - [Option('c', "solver", Required = true, HelpText = "The solver that we want to use (Gurobi | Zen)")] - public SolverChoice SolverChoice { get; set; } + [Option('o', "timeout", Default = double.PositiveInfinity, HelpText = "Solver timeout in seconds (default: no timeout)")] + public double Timeout { get; set; } /// - /// inner encoding (KKT or PrimalDual). + /// Enable verbose output for debugging. + /// Shows detailed solver progress, intermediate solutions, and timing information. /// - [Option('e', "innerencoding", Default = InnerRewriteMethodChoice.KKT, HelpText = "Method to use for inner encoding.")] - public InnerRewriteMethodChoice InnerEncoding { get; set; } + [Option('v', "verbose", Default = false, HelpText = "Enable verbose output with detailed logs")] + public bool Verbose { get; set; } /// - /// adversarial generator (Encoding or Benders). + /// Enable debug output. + /// Prints additional debugging messages to standard output. /// - /// TODO: the discription of this input is not clear, explain it more. - [Option('e', "adversarialgen", Default = AdversarialGenMethodChoice.Encoding, HelpText = "Method to use for adversarial generator. The benders decomposition approach currently does not work.")] - public AdversarialGenMethodChoice AdversarialGen { get; set; } + [Option('d', "debug", Default = false, HelpText = "Prints debugging messages to standard output")] + public bool Debug { get; set; } + + #endregion + + #region Traffic Engineering Options + + /// + /// The topology file path. + /// JSON file containing network nodes and links with capacities. + /// + /// + /// Expected format: + /// { + /// "nodes": [{"id": "a"}, {"id": "b"}, ...], + /// "links": [{"source": "a", "target": "b", "capacity": 10}, ...] + /// }. + /// + [Option('f', "topologyFile", Default = "..\\Topologies\\simple.json", HelpText = "The location of the topology file (JSON format)")] + public string TopologyFile { get; set; } /// - /// demand list (only applies for PrimalDual). + /// The heuristic encoder to use. /// - /// TODO: this terminology is too TE specific. Can you make it more general to apply to our other heuristics too? Also would be good to expand the comment. - [Option('d', "demandlist", Default = "0", HelpText = "quantized list of demands (only applies to PrimalDual -- should separate value with ',' no space).")] - public String DemandList { get; set; } + /// + /// Available heuristics: + /// - Pop: Partition-based routing with random demand partitioning + /// - DemandPinning: Threshold-based path selection (pin large demands to shortest path) + /// - ExpectedPop: Average performance across multiple partition samples + /// - PopDp: Combined Pop and DemandPinning + /// - ExpectedPopDp: Average gap of running Pop and DemandPinning in parallel + /// - ParallelPop: Multiple Pop instances in parallel + /// - ParallelPopDp: Multiple Pop instances with DemandPinning + /// - ModifiedDp: DemandPinning with upper bound on pinned path lengths. + /// + /// TODO: Is this specific to only the TE use-case? If so, is there a way to make a separate + /// data structure for each heuristic example to make the code cleaner? + [Option('h', "heuristic", Default = Heuristic.Pop, HelpText = "The heuristic encoder to use (Pop | DemandPinning | ExpectedPop | PopDp | ModifiedDp)")] + public Heuristic Heuristic { get; set; } /// - /// whether to simplify the final solution or not. + /// Inner encoding method for bilevel optimization. + /// KKT uses Karush-Kuhn-Tucker conditions, PrimalDual uses primal-dual reformulation. /// - /// TODO: this is too vague. Explain what it means to simplify the solution. - [Option('s', "simplify", Default = false, HelpText = "Whether to simplify the final solution or not")] - public bool Simplify { get; set; } + /// + /// KKT: Converts inner optimization to constraints using optimality conditions. + /// PrimalDual: Uses strong duality to reformulate inner problem. + /// PrimalDual requires demand quantization (see DemandList). + /// + [Option('e', "innerencoding", Default = InnerRewriteMethodChoice.KKT, HelpText = "Method for inner encoding (KKT | PrimalDual)")] + public InnerRewriteMethodChoice InnerEncoding { get; set; } /// - /// Timeout for gurobi solver. + /// Adversarial generator method. /// - [Option('o', "timeout", Default = double.PositiveInfinity, HelpText = "gurobi solver terminates after the specified time")] - public double Timeout { get; set; } + /// TODO: The description of this input is not clear, explain it more. + /// Encoding uses direct bilevel formulation, Benders uses decomposition (currently broken). + [Option("adversarialgen", Default = AdversarialGenMethodChoice.Encoding, HelpText = "Adversarial generator method (Encoding | Benders). Benders decomposition currently does not work.")] + public AdversarialGenMethodChoice AdversarialGen { get; set; } + + /// + /// Quantized list of demand values for PrimalDual encoding. + /// Comma-separated values without spaces. + /// + /// + /// Example: "0,5,10,15,20" allows demands to take only these discrete values. + /// Required when using PrimalDual inner encoding for tractable optimization. + /// More values = finer granularity but slower optimization. + /// + /// TODO: This terminology is too TE specific. Can you make it more general to apply to + /// our other heuristics too? Also would be good to expand the comment. + [Option("demandlist", Default = "0", HelpText = "Quantized demand values (comma-separated, no spaces). Only applies to PrimalDual encoding.")] + public string DemandList { get; set; } /// - /// terminates if no improvement after specified time. + /// Whether to simplify the final solution. /// - [Option('x', "timetoterminate", Default = -1, HelpText = "gurobi solver terminates if no improvement in best objective after the specified time (only applies to MIP)")] + /// TODO: This is too vague. Explain what it means to simplify the solution. + /// Simplification may remove redundant flow allocations or round near-zero values. + [Option('s', "simplify", Default = false, HelpText = "Whether to simplify the final solution")] + public bool Simplify { get; set; } + + /// + /// Terminate if no improvement in best objective after specified time. + /// Only applies to MIP (Mixed Integer Programming) problems. + /// Value of -1 disables this termination condition. + /// + [Option('x', "timetoterminate", Default = -1.0, HelpText = "Terminate if no improvement after specified seconds (MIP only, -1 to disable)")] public double TimeToTerminateIfNoImprovement { get; set; } /// - /// The number of pop slices to use. + /// The number of partitions (slices) for Pop heuristic. + /// Demands are randomly assigned to partitions, each optimized separately. /// - /// TODO: again this is specific to a particular heuristic, is there a way to separate inputs that are heuristic specific from those that are general? - /// One way may be to take a json as input that contains the inputs that are specific to the particular heuristic. - [Option('s', "slices", Default = 2, HelpText = "The number of pop slices to use.")] + /// TODO: Again this is specific to a particular heuristic, is there a way to separate + /// inputs that are heuristic specific from those that are general? + /// One way may be to take a JSON as input that contains the inputs specific to the particular heuristic. + [Option("slices", Default = 2, HelpText = "Number of Pop partitions/slices")] public int PopSlices { get; set; } /// - /// The threshold for demand pinning. + /// The threshold for demand pinning heuristic. + /// Demands above this threshold are pinned to shortest path. /// - /// TODO: same as other comments that are about being heuristic specific. - [Option('t', "pinthreshold", Default = 5, HelpText = "The threshold for the demand pinning heuristic.")] + /// TODO: Same as other comments that are about being heuristic specific. + [Option('t', "pinthreshold", Default = 0.5, HelpText = "Threshold for demand pinning heuristic")] public double DemandPinningThreshold { get; set; } /// - /// The maximum number of paths to use for a demand. + /// The maximum number of paths to consider for each demand pair. + /// Higher values allow more routing flexibility but increase problem size. /// - /// TODO: same as others. - [Option('p', "paths", Default = 2, HelpText = "The maximum number of paths to use for any demand.")] + /// TODO: Same as others. + [Option('p', "paths", Default = 2, HelpText = "Maximum number of paths per demand pair")] public int Paths { get; set; } /// - /// The maximum shortest path length to pin in modified demandpinning. + /// The maximum shortest path length to pin in modified demand pinning. + /// Only applied when using ModifiedDp heuristic. + /// Value of -1 disables this constraint. /// - /// TODO: same as others. - [Option('p', "maxshortestlen", Default = -1, HelpText = "The maximum shortest path length to pin (only applied to ModifiedDp).")] + /// TODO: Same as others. + [Option("maxshortestlen", Default = -1, HelpText = "Maximum shortest path length to pin (ModifiedDp only, -1 to disable)")] public int MaxShortestPathLen { get; set; } /// - /// method for finding gap [search or direct]. + /// Method for finding the optimality gap. /// - /// TODO: expand on the comment to describe what each option does. - [Option('m', "method", Default = MethodChoice.Direct, HelpText = "the method for finding the desirable gap [Direct | Search | FindFeas | Random | HillClimber | SimulatedAnnealing]")] + /// + /// - Direct: Solve bilevel optimization directly for maximum gap + /// - Search: Binary search within interval [0, startinggap] for maximum gap + /// - FindFeas: Find any solution with gap >= startinggap + /// - Random: Random sampling to estimate gap distribution + /// - HillClimber: Local search with neighborhood exploration + /// - SimulatedAnnealing: Probabilistic local search with temperature cooling. + /// + /// TODO: Expand on the comment to describe what each option does. + [Option('m', "method", Default = MethodChoice.Direct, HelpText = "Gap-finding method [Direct | Search | FindFeas | Random | HillClimber | SimulatedAnnealing]")] public MethodChoice Method { get; set; } /// - /// if using search, shows how much close to optimal is ok. + /// Confidence level for Search method. + /// Search terminates when solution is within this fraction of optimal. /// - [Option('d', "confidence", Default = 0.1, HelpText = "if using search, will find a solution as close as this value to optimal.")] + [Option("confidence", Default = 0.1, HelpText = "Search terminates when within this fraction of optimal")] public double Confidencelvl { get; set; } /// - /// if using search, this values is used as the starting gap. + /// Starting gap value for Search and FindFeas methods. + /// Search uses this as upper bound, FindFeas uses as target threshold. /// - [Option('g', "startinggap", Default = 10, HelpText = "if using search, will start the search from this number.")] + [Option('g', "startinggap", Default = 10.0, HelpText = "Starting gap for Search/FindFeas methods")] public double StartingGap { get; set; } /// - /// an upper bound on all the demands to find more useful advers inputs. + /// Upper bound on all demand values. + /// Constrains adversarial inputs to realistic ranges. + /// Value of -1 means no upper bound. /// - /// TODO: is this also heuristic specific? if not, change the terminology to be general if yes, fix as stated above. - [Option('u', "demandub", Default = -1, HelpText = "an upper bound on all the demands.")] + /// TODO: Is this also heuristic specific? If not, change the terminology to be general. + /// If yes, fix as stated above. + [Option('u', "demandub", Default = -1.0, HelpText = "Upper bound on demand values (-1 for no bound)")] public double DemandUB { get; set; } /// - /// an upper bound on all the demands to find more useful advers inputs. + /// Maximum difference of total demands between partitions. + /// Used to balance load across partitions. + /// Value of -1 disables this constraint. /// - /// TODO: same as above. - [Option('x', "partitionSensitivity", Default = -1, HelpText = "the difference of total demands in each partition.")] + /// TODO: Same as above. + [Option("partitionSensitivity", Default = -1.0, HelpText = "Maximum demand difference between partitions (-1 to disable)")] public double PartitionSensitivity { get; set; } /// - /// number of trails for random search. + /// Number of trials for Random search or HillClimber. + /// More trials increase chance of finding better solutions. /// - [Option('n', "num", Default = 1, HelpText = "number of trials for random search or hill climber.")] + [Option('n', "num", Default = 1, HelpText = "Number of trials for Random/HillClimber")] public int NumRandom { get; set; } /// - /// number of neighbors to look. + /// Number of neighbors to evaluate before declaring local optimum. + /// Used by HillClimber and SimulatedAnnealing. /// - [Option('k', "neighbors", Default = 1, HelpText = "number of neighbors to search before marking as local optimum [for hill climber | simulated annealing].")] + [Option('k', "neighbors", Default = 1, HelpText = "Neighbors to check before local optimum [HillClimber | SimulatedAnnealing]")] public int NumNeighbors { get; set; } /// - /// initial temperature for simulated annealing. + /// Initial temperature for simulated annealing. + /// Higher values allow more exploration early in search. /// - [Option('t', "inittmp", Default = 1, HelpText = "initial temperature for simulated annealing.")] + [Option("inittmp", Default = 1.0, HelpText = "Initial temperature for simulated annealing")] public double InitTmp { get; set; } /// - /// initial temperature for simulated annealing. + /// Temperature decrease factor for simulated annealing. + /// Temperature is multiplied by this factor each iteration. /// - [Option('l', "lambda", Default = 1, HelpText = "temperature decrease factor for simulated annealing.")] + [Option('l', "lambda", Default = 1.0, HelpText = "Temperature decrease factor for simulated annealing")] public double TmpDecreaseFactor { get; set; } /// - /// max density of final traffic matrix. + /// Maximum density of the final traffic demand matrix. + /// Density = (non-zero demands) / (total possible demands). /// - /// TODO: same as other comments. - [Option('d', "maxdensity", Default = 1.0, HelpText = "maximum density of the final traffic demand.")] + /// TODO: Same as other comments. + [Option("maxdensity", Default = 1.0, HelpText = "Maximum density of traffic demand matrix (0.0-1.0)")] public double MaxDensity { get; set; } /// - /// max distance for large demands. + /// Maximum path distance for large demands. + /// Restricts large demands to nearby destinations. + /// Value of -1 disables this constraint. /// - /// TODO: same as other comments. - [Option('m', "maxdistancelarge", Default = -1, HelpText = "maximum distance for large demands.")] + /// TODO: Same as other comments. + [Option("maxdistancelarge", Default = -1, HelpText = "Maximum distance for large demands (-1 to disable)")] public int maxLargeDistance { get; set; } /// - /// max distance for small demands. + /// Maximum path distance for small demands. + /// Restricts small demands to nearby destinations. + /// Value of -1 disables this constraint. /// - /// TODO: same as other comments. - [Option('m', "maxdistancesmall", Default = -1, HelpText = "maximum distance for small demands.")] + /// TODO: Same as other comments. + [Option("maxdistancesmall", Default = -1, HelpText = "Maximum distance for small demands (-1 to disable)")] public int maxSmallDistance { get; set; } /// - /// Lower bound for large demands. + /// Lower bound to distinguish large demands from small demands. + /// Demands >= this value are considered "large". + /// Value of -1 disables large/small distinction. /// - /// TODO: same as other comments. - [Option('m', "largedemandlb", Default = -1, HelpText = "to distinguish large demands from small demands.")] + /// TODO: Same as other comments. + [Option("largedemandlb", Default = -1.0, HelpText = "Threshold to distinguish large vs small demands (-1 to disable)")] public double LargeDemandLB { get; set; } /// - /// enable clustering breakdown. + /// Enable hierarchical clustering for scalability. + /// Clusters the topology and optimizes inter/intra-cluster demands separately. + /// More scalable but may not find the globally optimal gap. /// - [Option('c', "enableclustering", Default = false, HelpText = "Use this input to enable clustering. Clustering is more scalable but does not find the best possible gap.")] + [Option("enableclustering", Default = false, HelpText = "Enable clustering for scalability (may not find optimal gap)")] public bool EnableClustering { get; set; } /// - /// cluster directory. + /// Directory containing cluster-level topology files. /// - /// TODO: not clear what this is doing, need a better user-visible and also private commment. - [Option('j', "clusterdir", Default = null, HelpText = "cluster lvl topo directory")] + /// TODO: Not clear what this is doing, need a better user-visible and also private comment. + /// Should contain JSON files defining the cluster structure and inter-cluster links. + [Option("clusterdir", Default = null, HelpText = "Directory containing cluster topology files")] public string ClusterDir { get; set; } /// - /// num clusters. + /// Number of clusters to create. /// - [Option('j', "numclusters", Default = null, HelpText = "number of clusters")] + [Option("numclusters", Default = 2, HelpText = "Number of clusters")] public int NumClusters { get; set; } /// - /// inter-cluster method version. + /// Version of clustering algorithm for inter-cluster demands. /// - /// TODO: what are the options? what is the difference between the different options? - [Option('j', "clusterversion", Default = 1, HelpText = "version of clustering for inter-cluster demands")] + /// TODO: What are the options? What is the difference between the different options? + /// v1, v2, v3 use different strategies for handling demands crossing cluster boundaries. + [Option("clusterversion", Default = 1, HelpText = "Clustering algorithm version for inter-cluster demands")] public int ClusterVersion { get; set; } /// - /// num inter-cluster samples. + /// Number of inter-cluster demand samples. /// - [Option('j', "interclustersamples", Default = 0, HelpText = "number of inter cluster samples")] + [Option("interclustersamples", Default = 0, HelpText = "Number of inter-cluster demand samples")] public int NumInterClusterSamples { get; set; } /// - /// num nodes per cluster for inter-cluster edges. + /// Number of nodes per cluster for inter-cluster edge representation. /// - [Option('j', "nodespercluster", Default = 0, HelpText = "number of nodes per cluster for inter-cluster edges")] + [Option("nodespercluster", Default = 0, HelpText = "Nodes per cluster for inter-cluster edges")] public int NumNodesPerCluster { get; set; } /// - /// inter-cluster quantization lvls. + /// Number of quantization levels for inter-cluster demands. + /// Only applies to clustering version 3. /// - /// TODO: unclear how one should use this parameter. - [Option('j', "numinterclusterquantization", Default = -1, HelpText = "inter-cluster demands number of quantizations [only works for v3].")] + /// TODO: Unclear how one should use this parameter. + [Option("numinterclusterquantization", Default = -1, HelpText = "Inter-cluster demand quantization levels (v3 only)")] public int NumInterClusterQuantizations { get; set; } /// - /// error analysis. + /// Run full optimization after initial clustering solution. + /// Uses clustered solution as starting point for full-scale optimization. + /// Requires clustering to be enabled and PrimalDual inner encoding. /// - /// TODO: not fully clear what this does, needs a better comment both internally and user-visible. - [Option('j', "fullopt", Default = false, HelpText = "after finding the demand, will run the full optimization with demands as init point.")] + /// TODO: Not fully clear what this does, needs a better comment both internally and user-visible. + [Option("fullopt", Default = false, HelpText = "Run full optimization with clustered solution as init point")] public bool FullOpt { get; set; } /// - /// error analysis timer. + /// Timeout for full optimization phase. + /// Value of -1 uses the main timeout. /// - [Option('j', "fullopttimer", Default = -1, HelpText = "the duration to run the full optimization for error analysis.")] + [Option("fullopttimer", Default = -1.0, HelpText = "Timeout for full optimization phase (-1 uses main timeout)")] public double FullOptTimer { get; set; } /// - /// gurobi improve upper bound instead of bestObj. + /// Focus on improving upper bound after initial solution. + /// Runs additional optimization phase targeting the dual bound. /// - [Option('j', "ubfocus", Default = false, HelpText = "if enabled after finding demand, will solve another optimization focusing on improving upper bound.")] + [Option("ubfocus", Default = false, HelpText = "Run additional phase focusing on upper bound improvement")] public bool UBFocus { get; set; } /// - /// gurobi upper bound timer. + /// Timeout for upper bound focus phase. + /// Value of -1 uses the main timeout. /// - [Option('j', "ubfocustimeout", Default = -1, HelpText = "the timer of solver when focusing on ub.")] + [Option("ubfocustimeout", Default = -1.0, HelpText = "Timeout for upper bound focus phase (-1 uses main timeout)")] public double UBFocusTimer { get; set; } /// - /// num processes. + /// Number of parallel processes to use. + /// Value of -1 uses system default. /// - /// TODO: for what? - [Option('s', "numProcesses", Default = -1, HelpText = "num processes to use for.")] + /// TODO: For what? Parallel optimization? Parallel validation? + [Option("numProcesses", Default = -1, HelpText = "Number of parallel processes (-1 for system default)")] public int NumProcesses { get; set; } /// - /// seed. + /// Random seed for reproducibility. + /// Used by Random, HillClimber, SimulatedAnnealing methods. /// - [Option('s', "seed", Default = 1, HelpText = "seed for random generator.")] + [Option("seed", Default = 1, HelpText = "Random seed for reproducibility")] public int Seed { get; set; } /// - /// seed. + /// Standard deviation for neighbor generation in HillClimber. + /// Controls the size of random perturbations when exploring neighbors. /// - [Option('b', "stddev", Default = 100, HelpText = "standard deviation for generating neighbor for hill climber.")] + [Option('b', "stddev", Default = 100, HelpText = "Standard deviation for neighbor generation [HillClimber]")] public int StdDev { get; set; } /// - /// store trajectory. + /// Store optimization progress trajectory. + /// Saves intermediate solutions for analysis. /// - [Option('m', "storeprogress", Default = false, HelpText = "store the progress for the specified approach.")] + [Option("storeprogress", Default = false, HelpText = "Store optimization progress trajectory")] public bool StoreProgress { get; set; } /// - /// file to read paths. + /// File containing pre-computed paths to use. /// - /// TODO: heuristic specific. It would also be good to specify what format the file has to have. - [Option('m', "pathfile", Default = null, HelpText = "file to read the paths from.")] + /// TODO: Heuristic specific. It would also be good to specify what format the file has to have. + /// Expected format: JSON with paths per source-destination pair. + [Option("pathfile", Default = null, HelpText = "File containing pre-computed paths (JSON format)")] public string PathFile { get; set; } /// - /// log file. + /// Path to log file for storing progress. /// - [Option('t', "logfile", Default = null, HelpText = "path to the log file to store the progress.")] + [Option("logfile", Default = null, HelpText = "Path to log file for progress storage")] public string LogFile { get; set; } /// - /// Whether to print debugging information. + /// Factor to downscale the optimization problem. + /// Reduces problem size for faster experimentation. + /// All capacities and demands are multiplied by this factor. /// - [Option('d', "debug", Default = false, HelpText = "Prints debugging messages to standard output.")] - public bool Debug { get; set; } + [Option("downscale", Default = 1.0, HelpText = "Factor to downscale problem size (1.0 = no scaling)")] + public double DownScaleFactor { get; set; } /// - /// To downscale the solver. + /// Number of threads for Gurobi solver. + /// Value of 0 lets Gurobi choose automatically. + /// Value of 1 ensures deterministic results but slower execution. /// - [Option('p', "downscale", Default = 1.0, HelpText = "Factor to downscale MetaOpt.")] - public double DownScaleFactor { get; set; } + [Option("gurobithreads", Default = 1, HelpText = "Gurobi threads (0=auto, 1=deterministic)")] + public int NumGurobiThreads { get; set; } + + #endregion + + #region Bin Packing Options /// - /// number of threads to use in gurobi. + /// Number of bins available for packing. + /// The adversarial generator tries to find items that use many bins with FFD + /// while requiring fewer bins optimally. /// - [Option('t', "gurobithreads", Default = 0, HelpText = "number of threads to use for Gurobi.")] - public int NumGurobiThreads { get; set; } + [Option("numBins", Default = 6, HelpText = "Number of bins available (BinPacking)")] + public int NumBins { get; set; } /// - /// to show more detailed logs. + /// Number of items/demands to pack. + /// More items = larger search space for adversarial inputs. /// - [Option('v', "verbose", Default = false, HelpText = "more detailed logs")] - public bool Verbose { get; set; } + [Option("numDemands", Default = 9, HelpText = "Number of items to pack (BinPacking)")] + public int NumDemands { get; set; } + + /// + /// Number of dimensions for vector bin packing. + /// Each item has size in each dimension, bins have capacity per dimension. + /// + [Option("numDimensions", Default = 2, HelpText = "Number of dimensions (BinPacking)")] + public int NumDimensions { get; set; } + + /// + /// Target number of bins for optimal solution. + /// Adversarial generator finds items that pack optimally in this many bins + /// but require more bins with FFD heuristic. + /// + [Option("optimalBins", Default = 3, HelpText = "Target optimal bin count (BinPacking)")] + public int OptimalBins { get; set; } + + /// + /// First-Fit variant to use as the heuristic. + /// + /// + /// - FF: First Fit (no sorting, place in first bin that fits) + /// - FFDSum: First Fit Decreasing by sum of dimensions + /// - FFDProd: First Fit Decreasing by product of dimensions + /// - FFDDiv: First Fit Decreasing by division of dimensions (2D only). + /// + [Option("ffMethod", Default = FFDMethodChoice.FFDSum, HelpText = "First-Fit variant: FF, FFDSum, FFDProd, FFDDiv")] + public FFDMethodChoice FFMethod { get; set; } + + /// + /// Enable symmetry breaking constraints. + /// Reduces search space by eliminating equivalent solutions. + /// May speed up optimization but could miss some adversarial inputs. + /// + [Option("breakSymmetry", Default = false, HelpText = "Enable symmetry breaking (BinPacking)")] + public bool BreakSymmetry { get; set; } + + /// + /// Bin capacities per dimension (comma-separated). + /// Each value is the capacity for one dimension. + /// Values slightly above 1.0 (e.g., 1.00001) avoid numerical issues. + /// + [Option("binCapacity", Default = "1.00001,1.00001", HelpText = "Comma-separated bin capacities per dimension")] + public string BinCapacity { get; set; } + + #endregion + + #region PIFO Options + + /// + /// Number of packets in the sequence. + /// More packets = larger adversarial search space. + /// + [Option("numPackets", Default = 18, HelpText = "Number of packets (PIFO)")] + public int NumPackets { get; set; } + + /// + /// Maximum rank value for packet priorities. + /// Lower rank = higher priority. + /// + [Option("maxRank", Default = 8, HelpText = "Maximum rank value (PIFO)")] + public int MaxRank { get; set; } + + /// + /// Number of priority queues for SP-PIFO. + /// Packets are mapped to queues based on rank. + /// + [Option("numQueues", Default = 4, HelpText = "Number of queues for SP-PIFO")] + public int NumQueues { get; set; } + + /// + /// Maximum size of each queue. + /// Packets are dropped if queue is full. + /// + [Option("maxQueueSize", Default = 12, HelpText = "Maximum queue size (PIFO)")] + public int MaxQueueSize { get; set; } + + /// + /// Window size for AIFO admission control. + /// AIFO admits packets based on rank relative to recent window. + /// + [Option("windowSize", Default = 12, HelpText = "AIFO window size (PIFO)")] + public int WindowSize { get; set; } + + /// + /// Burst tolerance parameter for AIFO. + /// Controls how much burst traffic is allowed. + /// + [Option("burstParam", Default = 0.1, HelpText = "AIFO burst parameter (PIFO)")] + public double BurstParam { get; set; } + + #endregion + + #region Failure Analysis Options + + /// + /// Use built-in default topology instead of loading from file. + /// Default topology is a simple 4-node diamond for testing. + /// + [Option("useDefaultTopology", Default = true, HelpText = "Use built-in test topology (FailureAnalysis)")] + public bool UseDefaultTopology { get; set; } + + /// + /// Maximum number of simultaneous link failures to consider. + /// Higher values explore more failure scenarios but increase problem size. + /// + [Option("maxNumFailures", Default = 1, HelpText = "Maximum simultaneous link failures")] + public int MaxNumFailures { get; set; } + + /// + /// Number of extra paths for rerouting under failures. + /// More paths = more rerouting options but larger problem. + /// + [Option("numExtraPaths", Default = 1, HelpText = "Extra paths for failure rerouting")] + public int NumExtraPaths { get; set; } + + /// + /// Minimum failure probability for considering a link. + /// Links with probability below this threshold are assumed not to fail. + /// + [Option("failureProbThreshold", Default = 0.25, HelpText = "Minimum failure probability to consider")] + public double FailureProbThreshold { get; set; } + + /// + /// Minimum scenario probability threshold. + /// Failure scenarios with combined probability below this are ignored. + /// + [Option("scenarioProbThreshold", Default = 0.0, HelpText = "Minimum failure scenario probability")] + public double ScenarioProbThreshold { get; set; } + + #endregion } + #region Enums + /// - /// The encoding heuristic. + /// Problem type to solve. + /// + public enum ProblemType + { + /// + /// Traffic engineering: Find worst-case demand patterns for routing heuristics. + /// + TrafficEngineering, + + /// + /// Bin packing: Find item sizes that maximize FFD vs optimal gap. + /// + BinPacking, + + /// + /// PIFO: Find packet sequences that maximize scheduling inversions. + /// + PIFO, + + /// + /// Failure analysis: Find failure scenarios that maximize throughput degradation. + /// + FailureAnalysis, + } + + /// + /// The encoding heuristic for traffic engineering. /// public enum Heuristic { /// - /// The pop heuristic. + /// Partition-based routing with random demand partitioning. /// Pop, /// - /// The average pop heuristic over multiple sample + /// Average Pop performance over multiple partition samples. /// ExpectedPop, /// - /// The threshold heuristic. + /// Threshold-based path selection (pin large demands to shortest path). /// DemandPinning, /// - /// Combine POP and DP. + /// Combined Pop and DemandPinning. /// PopDp, /// - /// The average gap of running POP and DP in parallel. + /// Average gap of running Pop and DemandPinning in parallel. /// ExpectedPopDp, /// - /// Parallel POP. Running multiple instances of POP in parallel. + /// Multiple Pop instances running in parallel. /// ParallelPop, /// - /// Running multiple instances of POP in parallel with DP. + /// Multiple Pop instances in parallel with DemandPinning. /// ParallelPopDp, /// - /// Modified Demand Pinning with upper bound of pinned path lengths. + /// DemandPinning with upper bound on pinned path lengths. /// ModifiedDp, } + /// - /// The solver we want to use. + /// The solver to use for optimization. /// public enum SolverChoice { /// - /// The Gurobi solver. + /// Gurobi solver (MIP - Mixed Integer Programming). + /// Commercial solver, fast, requires license. /// Gurobi, /// - /// The Zen solver. + /// Zen solver (SMT - Satisfiability Modulo Theories). + /// Based on Z3, open source, good for constraint satisfaction. /// Zen, } + /// - /// The method we want to use. + /// Method for finding the optimality gap. /// public enum MethodChoice { /// - /// directly find the max gap + /// Directly solve bilevel optimization for maximum gap. + /// Most accurate but may be slow for large problems. /// Direct, + /// - /// search for the max gap with some interval + /// Binary search within interval for maximum gap. + /// Faster than Direct for some problems. /// Search, + /// - /// find a solution with gap at least equal to startinggap. + /// Find any feasible solution with gap >= startinggap. + /// Fastest when you only need to prove a gap exists. /// FindFeas, + /// - /// find a solution with random search. + /// Random sampling to estimate gap distribution. + /// Good for understanding gap landscape. /// Random, + /// - /// find a solution with hill climber. + /// Local search with neighborhood exploration. + /// May find good solutions faster than Direct. /// HillClimber, + /// - /// find a solution with simulated annealing. + /// Probabilistic local search with temperature cooling. + /// Can escape local optima better than HillClimber. /// SimulatedAnnealing, } -} + + #endregion +} \ No newline at end of file diff --git a/MetaOptimize.Cli/FailureAnalysisRunner.cs b/MetaOptimize.Cli/FailureAnalysisRunner.cs index 2760d0c72..aafbacdad 100644 --- a/MetaOptimize.Cli/FailureAnalysisRunner.cs +++ b/MetaOptimize.Cli/FailureAnalysisRunner.cs @@ -1,53 +1,90 @@ -using System.Diagnostics; -using Gurobi; -using MetaOptimize.FailureAnalysis; -using ZenLib; -using ZenLib.ModelChecking; +// +// Copyright (c) Microsoft. All rights reserved. +// namespace MetaOptimize.Cli { + using System.Diagnostics; + using Gurobi; + using MetaOptimize.FailureAnalysis; + using ZenLib; + using ZenLib.ModelChecking; + /// - /// Non-generic entry point that dispatches to the correct generic implementation. + /// Runner for network failure analysis adversarial optimization. + /// Finds failure scenarios that maximize the gap between normal and degraded network performance. /// + /// + /// Analyzes network resilience by finding worst-case link failure combinations. + /// Compares optimal routing under normal conditions against routing under failure scenarios, + /// identifying vulnerabilities where failures cause significant throughput degradation. + /// + /// Supports configurable failure probability thresholds and multiple simultaneous failures. + /// public static class FailureAnalysisRunner { /// - /// Generic implementation of Failure Analysis optimization. + /// Runs failure analysis adversarial optimization. + /// Dispatches to the appropriate solver-specific implementation. /// - public static void Run(CliArgs args) + /// Command-line options containing failure analysis parameters. + /// Thrown when an unsupported solver is specified. + public static void Run(CliOptions opts) { - var solverChoice = args.Get("--solver", "Gurobi"); - var verbose = args.GetBool("--verbose", false); - var timeout = args.GetDouble("--timeout", 1000); - - switch (solverChoice.ToLower()) + switch (opts.SolverChoice) { - case "gurobi": + case SolverChoice.Gurobi: FailureAnalysisRunnerImpl.CreateSolver = - () => new GurobiSOS(verbose: Convert.ToInt32(verbose), timeout: timeout); - FailureAnalysisRunnerImpl.Run(args); + () => new GurobiSOS(verbose: Convert.ToInt32(opts.Verbose), timeout: opts.Timeout); + FailureAnalysisRunnerImpl.Run(opts); break; - case "zen": + case SolverChoice.Zen: FailureAnalysisRunnerImpl, ZenSolution>.CreateSolver = () => new SolverZen(); - FailureAnalysisRunnerImpl, ZenSolution>.Run(args); + FailureAnalysisRunnerImpl, ZenSolution>.Run(opts); break; default: - throw new Exception($"Unsupported solver: {solverChoice}"); + throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: Gurobi, Zen"); } } } /// - /// Generic implementation of Failure Analysis optimization. + /// Generic implementation of failure analysis adversarial optimization. /// + /// Solver variable type (GRBVar or Zen). + /// Solver solution type (GRBModel or ZenSolution). + /// + /// Uses bilevel optimization to find: + /// 1. Outer level: Failure scenario (which links fail) + /// 2. Inner level: Optimal routing under that failure scenario + /// + /// The gap represents how much throughput is lost due to the failure. + /// internal sealed class FailureAnalysisRunnerImpl { + /// + /// Factory function to create solver instances. + /// Set by the dispatcher before calling Run(). + /// internal static Func> CreateSolver = null; /// - /// Creates default topology for failure analysis testing. + /// Creates a default 4-node diamond topology for testing. /// + /// A simple test topology with nodes a, b, c, d. + /// + /// Topology structure: + /// a + /// /|\ + /// / | \ + /// b--+--c + /// \ | / + /// \|/ + /// d + /// + /// Link capacities vary to create interesting failure scenarios. + /// private static Topology CreateDefaultFailureTopology() { var topology = new Topology(); @@ -59,52 +96,56 @@ private static Topology CreateDefaultFailureTopology() topology.AddEdge("a", "c", capacity: 10); topology.AddEdge("b", "d", capacity: 10); topology.AddEdge("c", "d", capacity: 10); - topology.AddEdge("a", "d", capacity: 5); - topology.AddEdge("b", "c", capacity: 3); + topology.AddEdge("a", "d", capacity: 5); // Direct path with lower capacity + topology.AddEdge("b", "c", capacity: 3); // Cross-link with lowest capacity return topology; } /// - /// Reads topology from JSON file. + /// Reads topology from a JSON file. /// + /// Path to the topology JSON file. + /// Loaded topology (currently returns empty topology - TODO). private static Topology ReadTopologyFromFile(string filePath) { Console.WriteLine($"Loading topology from: {filePath}"); - // TODO: Implement proper JSON topology loading + // TODO: Implement proper JSON topology loading matching TERunner format var topology = new Topology(); return topology; } /// - /// Runs Failure Analysis optimization. + /// Runs failure analysis adversarial optimization. /// - internal static void Run(CliArgs args) + /// Command-line options containing failure analysis parameters. + /// + /// Key parameters from opts: + /// - UseDefaultTopology: Use built-in test topology or load from file + /// - MaxNumFailures: Maximum simultaneous link failures to consider + /// - NumExtraPaths: Additional paths for rerouting under failures + /// - FailureProbThreshold: Minimum probability for considering a failure + /// - InnerEncoding: KKT or PrimalDual for inner optimization + /// - DemandList: Quantized demand levels for optimization. + /// + internal static void Run(CliOptions opts) { - var useDefaultTopology = args.GetBool("--useDefaultTopology", true); - var maxNumFailures = args.GetInt("--maxNumFailures", 1); - var numExtraPaths = args.GetInt("--numExtraPaths", 1); - var demandListStr = args.Get("--demandList", "0,5,10"); - var failureProbThreshold = args.GetDouble("--failureProbThreshold", 0.25); - var scenarioProbThreshold = args.GetDouble("--scenarioProbThreshold", 0.0); - var innerEncoding = args.Get("--innerEncoding", "PrimalDual"); - var verbose = args.GetBool("--verbose", false); - - Console.WriteLine($"Max Failures: {maxNumFailures}, Extra Paths: {numExtraPaths}"); - Console.WriteLine($"Failure Prob Threshold: {failureProbThreshold}"); + Console.WriteLine($"Max Failures: {opts.MaxNumFailures}, Extra Paths: {opts.NumExtraPaths}"); + Console.WriteLine($"Failure Prob Threshold: {opts.FailureProbThreshold}"); + // Load or create topology Topology topology; - if (useDefaultTopology) + if (opts.UseDefaultTopology) { topology = CreateDefaultFailureTopology(); Console.WriteLine("Using default test topology"); } else { - var topologyFile = args.Get("--topologyFile"); - topology = ReadTopologyFromFile(topologyFile); - Console.WriteLine($"Loaded topology from: {topologyFile}"); + topology = ReadTopologyFromFile(opts.TopologyFile); + Console.WriteLine($"Loaded topology from: {opts.TopologyFile}"); } + // Default demand matrix for test topology var demands = new Dictionary<(string, string), double> { { ("a", "d"), 10 }, @@ -115,13 +156,14 @@ internal static void Run(CliArgs args) { ("b", "c"), 0 }, }; - var demandSet = new HashSet( - demandListStr.Split(',').Select(double.Parse)); + // Parse demand quantization levels + var demandSet = new HashSet(opts.DemandList.Split(',').Select(double.Parse)); var demandList = new GenericList(demandSet); + // Link failure probabilities for test topology var probs = new Dictionary<(string, string), double> { - { ("a", "d"), 0.3 }, + { ("a", "d"), 0.3 }, // Direct link has highest failure probability { ("b", "d"), 0.2 }, { ("a", "c"), 0 }, { ("a", "b"), 0 }, @@ -131,39 +173,40 @@ internal static void Run(CliArgs args) var solver = CreateSolver(); - var innerEncodingType = innerEncoding == "KKT" - ? InnerRewriteMethodChoice.KKT - : InnerRewriteMethodChoice.PrimalDual; - var timer = Stopwatch.StartNew(); - var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, 2); - var optimalCutEncoder = new FailureAnalysisEncoder(solver, 2); - var adversarialGenerator = new FailureAnalysisAdversarialGenerator(topology, 2); + // Create encoders for normal and failure scenarios + var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, maxNumPaths: 2); + var failureEncoder = new FailureAnalysisEncoder(solver, maxNumPathTotal: 2); + var adversarialGenerator = new FailureAnalysisAdversarialGenerator( + topology, maxNumPaths: 2); + // Find worst-case failure scenario var (optimalSol, failureSol) = adversarialGenerator.MaximizeOptimalityGap( - optimalEncoder, optimalCutEncoder, - innerEncoding: innerEncodingType, + optimalEncoder, failureEncoder, + innerEncoding: opts.InnerEncoding, constrainedDemands: demands, - maxNumFailures: maxNumFailures, + maxNumFailures: opts.MaxNumFailures, demandList: demandList, - numExtraPaths: numExtraPaths, + numExtraPaths: opts.NumExtraPaths, lagFailureProbabilities: probs, - failureProbThreshold: failureProbThreshold); + failureProbThreshold: opts.FailureProbThreshold); timer.Stop(); + // Display results Console.WriteLine("\n" + new string('=', 60)); Console.WriteLine("RESULTS:"); - Console.WriteLine($"Optimal objective: {optimalSol.MaxObjective}"); + Console.WriteLine($"Optimal objective (no failures): {optimalSol.MaxObjective}"); Console.WriteLine($"Failure scenario objective: {failureSol.MaxObjective}"); - Console.WriteLine($"Gap: {optimalSol.MaxObjective - failureSol.MaxObjective}"); + Console.WriteLine($"Gap (throughput loss): {optimalSol.MaxObjective - failureSol.MaxObjective}"); Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); Console.WriteLine(new string('=', 60)); - if (verbose) + // Verbose output: full solution details + if (opts.Verbose) { - Console.WriteLine("\nOptimal Solution:"); + Console.WriteLine("\nOptimal Solution (no failures):"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( optimalSol, Newtonsoft.Json.Formatting.Indented)); Console.WriteLine("\nFailure Scenario Solution:"); diff --git a/MetaOptimize.Cli/MetaOptimize.Cli.csproj b/MetaOptimize.Cli/MetaOptimize.Cli.csproj index 84841dcf4..97cc9e6d5 100644 --- a/MetaOptimize.Cli/MetaOptimize.Cli.csproj +++ b/MetaOptimize.Cli/MetaOptimize.Cli.csproj @@ -17,7 +17,6 @@ - \ No newline at end of file diff --git a/MetaOptimize.Cli/PIFORunner.cs b/MetaOptimize.Cli/PIFORunner.cs index c6b20c1c5..e3e9451c0 100644 --- a/MetaOptimize.Cli/PIFORunner.cs +++ b/MetaOptimize.Cli/PIFORunner.cs @@ -1,21 +1,42 @@ -using System.Diagnostics; -using Gurobi; +// +// Copyright (c) Microsoft. All rights reserved. +// namespace MetaOptimize.Cli { + using System.Diagnostics; + using Gurobi; + /// - /// Main entry point for traffic engineering experiments. - /// Executes adversarial optimization to find worst-case demand patterns for routing heuristics. + /// Runner for PIFO (Push-In First-Out) packet scheduling adversarial optimization. + /// Finds packet arrival patterns that maximize scheduling inversions between SP-PIFO and AIFO. /// /// - /// Flow: Load topology → Generate demand levels → Run adversarial optimization → Validate results. - /// Finds demand patterns where heuristic routes significantly less than optimal. + /// Compares two packet scheduling algorithms: + /// - SP-PIFO with Drop: Strict Priority PIFO with packet dropping under congestion + /// - AIFO: Approximate Ideal Fair Ordering with window-based admission + /// + /// The adversarial generator finds packet rank sequences where AIFO produces + /// significantly more inversions (out-of-order deliveries) than SP-PIFO. + /// + /// An inversion occurs when a lower-priority packet is scheduled before a + /// higher-priority packet that arrived earlier. /// public sealed class PIFORunner { /// - /// Computes inversion count for a single packet. + /// Computes the number of inversions for a single packet. + /// An inversion occurs when a lower-priority packet precedes this packet in the schedule. /// + /// The scheduling solution to analyze. + /// Mapping from schedule order to packet rank. + /// The packet ID to compute inversions for. + /// Number of inversions for the specified packet. + /// + /// For admitted packets: counts how many packets scheduled earlier have higher rank values + /// (lower priority, since lower rank = higher priority). + /// For dropped packets: counts inversions against all admitted packets. + /// private static int ComputeInversionNum( PIFOOptimizationSolution solution, Dictionary orderToRank, @@ -23,9 +44,11 @@ private static int ComputeInversionNum( { int numInv = 0; + // Check if packet was admitted (0.98 threshold handles floating-point) if (solution.Admit[pid] >= 0.98) { int currOrder = solution.Order[pid]; + // Count packets scheduled earlier with worse priority (higher rank) for (int prev = 0; prev < currOrder; prev++) { if (orderToRank[prev] > solution.Ranks[pid]) @@ -36,6 +59,7 @@ private static int ComputeInversionNum( } else { + // Dropped packet: count against all admitted packets with worse priority foreach (var (order, rank) in orderToRank) { if (rank > solution.Ranks[pid]) @@ -49,13 +73,18 @@ private static int ComputeInversionNum( } /// - /// Computes inversion count for PIFO solutions. + /// Computes total inversion counts for both optimal and heuristic solutions. /// + /// The optimal (SP-PIFO) scheduling solution. + /// The heuristic (AIFO) scheduling solution. + /// Total number of packets in the sequence. + /// Tuple of (optimal inversions, heuristic inversions). private static (int optimal, int heuristic) ComputeInversions( PIFOOptimizationSolution optimalSol, PIFOOptimizationSolution heuristicSol, int numPackets) { + // Build order-to-rank mappings for admitted packets var orderToRankOpt = new Dictionary(); var orderToRankHeu = new Dictionary(); @@ -72,6 +101,7 @@ private static (int optimal, int heuristic) ComputeInversions( } } + // Sum inversions across all packets int numInvOpt = 0; int numInvHeu = 0; @@ -85,58 +115,70 @@ private static (int optimal, int heuristic) ComputeInversions( } /// - /// Runs Bin Packing optimization. - /// Uses MainVBP logic (more complete than vbMain). + /// Runs PIFO packet scheduling adversarial optimization. /// - public static void Run(CliArgs args) + /// Command-line options containing PIFO parameters. + /// + /// Creates encoders for SP-PIFO and AIFO algorithms, then uses adversarial + /// optimization to find packet rank sequences that maximize the cost gap. + /// + /// Key parameters from opts: + /// - NumPackets: Total packets in sequence + /// - MaxRank: Maximum priority rank value + /// - NumQueues: Number of queues for SP-PIFO + /// - MaxQueueSize: Maximum packets per queue + /// - WindowSize: AIFO admission window size + /// - BurstParam: AIFO burst tolerance parameter. + /// + public static void Run(CliOptions opts) { - var maxRank = args.GetInt("--maxRank", 8); - var numPackets = args.GetInt("--numPackets", 18); - var numQueues = args.GetInt("--numQueues", 4); - var maxQueueSize = args.GetInt("--maxQueueSize", 12); - var windowSize = args.GetInt("--windowSize", 12); - var burstParam = args.GetDouble("--burstParam", 0.1); - var timeout = args.GetDouble("--timeout", 1000); - var verbose = args.GetBool("--verbose", false); - - Console.WriteLine($"Packets: {numPackets}, Max Rank: {maxRank}, Queues: {numQueues}"); - Console.WriteLine($"Max Queue Size: {maxQueueSize}, Window Size: {windowSize}"); - - var solver = new GurobiSOS(verbose: Convert.ToInt32(verbose), timeout: timeout); - - // Create encoders - comparing SP-PIFO with drop vs AIFO - var h1 = new SPPIFOWithDropAvgDelayEncoder( - solver, numPackets, numQueues, maxRank, maxQueueSize); - var h2 = new AIFOAvgDelayEncoder( - solver, numPackets, maxRank, maxQueueSize, windowSize, burstParam); + Console.WriteLine($"Packets: {opts.NumPackets}, Max Rank: {opts.MaxRank}, Queues: {opts.NumQueues}"); + Console.WriteLine($"Max Queue Size: {opts.MaxQueueSize}, Window Size: {opts.WindowSize}"); + + var solver = new GurobiSOS(verbose: Convert.ToInt32(opts.Verbose), timeout: opts.Timeout); + + // Create SP-PIFO encoder (optimal baseline) + var spPifoEncoder = new SPPIFOWithDropAvgDelayEncoder( + solver, opts.NumPackets, opts.NumQueues, opts.MaxRank, opts.MaxQueueSize); + // Create AIFO encoder (heuristic to evaluate) + var aifoEncoder = new AIFOAvgDelayEncoder( + solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize, opts.WindowSize, opts.BurstParam); + + // Create adversarial generator var adversarialGenerator = new PIFOAdversarialInputGenerator( - numPackets, maxRank); + opts.NumPackets, opts.MaxRank); var timer = Stopwatch.StartNew(); + + // Find worst-case packet sequence var (optimalSolution, heuristicSolution) = adversarialGenerator.MaximizeOptimalityGap( - h1, h2, verbose: verbose); + spPifoEncoder, aifoEncoder, verbose: opts.Verbose); + timer.Stop(); - // Compute inversions - var (numInvOpt, numInvHeu) = ComputeInversions(optimalSolution, heuristicSolution, numPackets); + // Compute inversion metrics + var (numInvOpt, numInvHeu) = ComputeInversions( + optimalSolution, heuristicSolution, opts.NumPackets); + // Display results Console.WriteLine("\n" + new string('=', 60)); Console.WriteLine("RESULTS:"); - Console.WriteLine($"Optimal cost: {optimalSolution.Cost}"); - Console.WriteLine($"Heuristic cost: {heuristicSolution.Cost}"); + Console.WriteLine($"SP-PIFO cost: {optimalSolution.Cost}"); + Console.WriteLine($"AIFO cost: {heuristicSolution.Cost}"); Console.WriteLine($"Gap: {heuristicSolution.Cost - optimalSolution.Cost}"); - Console.WriteLine($"Inversions (Optimal): {numInvOpt}"); - Console.WriteLine($"Inversions (Heuristic): {numInvHeu}"); + Console.WriteLine($"Inversions (SP-PIFO): {numInvOpt}"); + Console.WriteLine($"Inversions (AIFO): {numInvHeu}"); Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); Console.WriteLine(new string('=', 60)); - if (verbose) + // Verbose output: full solution details + if (opts.Verbose) { - Console.WriteLine("\nOptimal Solution:"); + Console.WriteLine("\nSP-PIFO Solution:"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( optimalSolution, Newtonsoft.Json.Formatting.Indented)); - Console.WriteLine("\nHeuristic Solution:"); + Console.WriteLine("\nAIFO Solution:"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( heuristicSolution, Newtonsoft.Json.Formatting.Indented)); } diff --git a/MetaOptimize.Cli/Program.cs b/MetaOptimize.Cli/Program.cs index 859720e12..2c9ce2a79 100644 --- a/MetaOptimize.Cli/Program.cs +++ b/MetaOptimize.Cli/Program.cs @@ -4,65 +4,76 @@ namespace MetaOptimize.Cli { + using CommandLine; + /// /// Entry point for MetaOptimize CLI. /// Routes to different problem solvers based on command-line arguments. /// + /// + /// Supports four problem types: + /// - TrafficEngineering: Find worst-case demand patterns for routing heuristics + /// - BinPacking: Find adversarial item sizes that maximize FFD vs optimal gap + /// - PIFO: Find packet sequences that maximize scheduling inversions + /// - FailureAnalysis: Analyze network resilience under link failures + /// + /// Uses CommandLineParser for argument parsing with CliOptions. + /// public class Program { /// /// Main entry point for the program. + /// Parses command-line arguments and dispatches to the appropriate runner. /// /// Command-line arguments. public static void Main(string[] args) { - var cliArgs = new CliArgs(args); + var parseResult = CommandLine.Parser.Default.ParseArguments(args); - if (cliArgs.ShowHelp) + parseResult.WithParsed(opts => { - cliArgs.ShowHelpMessage(); - return; - } + CliOptions.Instance = opts; + RunWithOptions(opts); + }); - if (!cliArgs.Validate()) + parseResult.WithNotParsed(errors => { + // CommandLineParser prints help/errors automatically Environment.Exit(1); - } + }); + } + /// + /// Executes the appropriate runner based on the problem type. + /// + /// Parsed command-line options. + private static void RunWithOptions(CliOptions opts) + { try { - var problemType = cliArgs.ProblemType; - Console.WriteLine($"Starting MetaOptimize - Problem Type: {problemType}"); + Console.WriteLine($"Starting MetaOptimize - Problem Type: {opts.ProblemType}"); Console.WriteLine(new string('=', 60)); - switch (problemType) + switch (opts.ProblemType) { - case "TrafficEngineering": - var mode = cliArgs.Get("--teMode", "simple"); - if (mode.ToLower() == "advanced") - { - TERunner.RunAdvanced(args); - } - else - { - TERunner.RunSimple(cliArgs); - } + case ProblemType.TrafficEngineering: + TERunner.Run(opts); break; - case "BinPacking": - BPRunner.Run(cliArgs); + case ProblemType.BinPacking: + BPRunner.Run(opts); break; - case "PIFO": - PIFORunner.Run(cliArgs); + case ProblemType.PIFO: + PIFORunner.Run(opts); break; - case "FailureAnalysis": - FailureAnalysisRunner.Run(cliArgs); + case ProblemType.FailureAnalysis: + FailureAnalysisRunner.Run(opts); break; default: - Console.WriteLine($"ERROR: Unknown problem type '{problemType}'"); + Console.WriteLine($"ERROR: Unknown problem type '{opts.ProblemType}'"); Environment.Exit(1); break; } @@ -78,4 +89,4 @@ public static void Main(string[] args) } } } -} +} \ No newline at end of file diff --git a/MetaOptimize.Cli/TERunner.cs b/MetaOptimize.Cli/TERunner.cs index 316093917..916a8891a 100644 --- a/MetaOptimize.Cli/TERunner.cs +++ b/MetaOptimize.Cli/TERunner.cs @@ -1,374 +1,348 @@ -using System.Diagnostics; -using CommandLine; -using Gurobi; -using ZenLib; -using ZenLib.ModelChecking; +// +// Copyright (c) Microsoft. All rights reserved. +// namespace MetaOptimize.Cli { + using System.Diagnostics; + using CommandLine; + using Gurobi; + using ZenLib; + using ZenLib.ModelChecking; + /// - /// Main entry point for traffic engineering experiments. - /// Executes adversarial optimization to find worst-case demand patterns for routing heuristics. + /// Runner for Traffic Engineering adversarial optimization. + /// Finds demand patterns that maximize the gap between optimal routing and heuristic algorithms. /// + /// + /// Flow: Load topology → Configure heuristic → Run adversarial optimization → Validate results. + /// + /// Supports multiple heuristics: + /// - Pop: Partition-based routing with random demand partitioning + /// - DemandPinning: Threshold-based path selection + /// - ExpectedPop: Average performance across multiple partitions + /// - PopDp: Combined Pop and DemandPinning + /// + /// Supports multiple search methods: + /// - Direct: Find maximum gap directly via bilevel optimization + /// - Search: Binary search for gap within interval + /// - FindFeas: Find any solution with gap >= threshold + /// - Random: Random sampling for gap estimation + /// - HillClimber: Local search with neighborhood exploration + /// - SimulatedAnnealing: Probabilistic local search with temperature cooling + /// + /// Includes clustering support for scalable optimization on large topologies. + /// public static class TERunner { /// - /// Runs Traffic Engineering optimization. - /// Uses CliUtils for topology loading and heuristic setup (same as original ssMain). + /// Runs Traffic Engineering adversarial optimization. + /// Main entry point that loads topology and dispatches to solver. /// - public static void RunAdvanced(string[] args) + /// Command-line options containing TE parameters. + public static void Run(CliOptions opts) { - var opts = CommandLine.Parser.Default.ParseArguments(args).MapResult(o => o, e => null); - CliOptions.Instance = opts; - if (opts == null) { - Environment.Exit(0); + Console.WriteLine("ERROR: Options not parsed correctly."); + Environment.Exit(1); } - // read the topology and clusters. - var (topology, clusters) = CliUtils.getTopology(opts.TopologyFile, opts.PathFile, opts.DownScaleFactor, opts.EnableClustering, - opts.NumClusters, opts.ClusterDir, opts.Verbose); + // Load topology and optional cluster configurations + var (topology, clusters) = CliUtils.getTopology( + opts.TopologyFile, + opts.PathFile, + opts.DownScaleFactor, + opts.EnableClustering, + opts.NumClusters, + opts.ClusterDir, + opts.Verbose); - getSolverAndRunNetwork(topology, clusters); + GetSolverAndRunNetwork(topology, clusters); } - // TODO: this function is missing proper commenting - private static void getSolverAndRunNetwork(Topology topology, List clusters) + /// + /// Creates the appropriate solver and runs network optimization. + /// Dispatches to Zen (SMT) or Gurobi (MIP) based on configuration. + /// + /// The network topology to optimize. + /// Optional cluster topologies for hierarchical optimization. + /// Thrown when an unsupported solver is specified. + private static void GetSolverAndRunNetwork(Topology topology, List clusters) { var opts = CliOptions.Instance; - // use the Z3 solver via the Zen wrapper library. + switch (opts.SolverChoice) { case SolverChoice.Zen: - // run the zen optimizer. RunNetwork(new SolverZen(), topology, clusters); break; + case SolverChoice.Gurobi: - var storeProgress = opts.StoreProgress & (opts.Method == MethodChoice.Direct); - if (opts.Heuristic == Heuristic.DemandPinning) - { - RunNetwork(new GurobiSOS(opts.Timeout, Convert.ToInt32(opts.Verbose), - timeToTerminateNoImprovement: opts.TimeToTerminateIfNoImprovement, - numThreads: opts.NumGurobiThreads, - recordProgress: storeProgress, - logPath: opts.LogFile), - topology, clusters); - } - else - { - RunNetwork(new GurobiSOS(opts.Timeout, Convert.ToInt32(opts.Verbose), - timeToTerminateNoImprovement: opts.TimeToTerminateIfNoImprovement, - numThreads: opts.NumGurobiThreads, - recordProgress: storeProgress, - logPath: opts.LogFile), - topology, clusters); - } + var storeProgress = opts.StoreProgress && (opts.Method == MethodChoice.Direct); + var solver = new GurobiSOS( + opts.Timeout, + Convert.ToInt32(opts.Verbose), + timeToTerminateNoImprovement: opts.TimeToTerminateIfNoImprovement, + numThreads: opts.NumGurobiThreads, + recordProgress: storeProgress, + logPath: opts.LogFile); + RunNetwork(solver, topology, clusters); break; + default: - throw new Exception("Other solvers are currently invalid."); + throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: Gurobi, Zen"); } } - // TODO: this function is missing proper commenting - private static void RunNetwork(ISolver solver, - Topology topology, List clusters) + /// + /// Generic implementation of traffic engineering adversarial optimization. + /// + /// Solver variable type (GRBVar or Zen). + /// Solver solution type (GRBModel or ZenSolution). + /// The solver instance to use. + /// The network topology to optimize. + /// Optional cluster topologies for hierarchical optimization. + /// + /// Execution phases: + /// 1. Setup: Create optimal encoder, heuristic encoder, and adversarial generator + /// 2. Optimization: Run selected method (Direct, Search, Random, etc.) + /// 3. Post-processing: Optional FullOpt and UBFocus refinement + /// 4. Validation: Verify solution with independent solver instances. + /// + private static void RunNetwork( + ISolver solver, + Topology topology, + List clusters) { var opts = CliOptions.Instance; - // setup the optimal encoder and adversarial input generator. + // Setup optimal encoder for maximum flow var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, opts.Paths); - TEAdversarialInputGenerator adversarialInputGenerator; - adversarialInputGenerator = new TEAdversarialInputGenerator(topology, opts.Paths, opts.NumProcesses); - // setup the heuristic encoder and partitions. - var heuristicSolver = solver; - var (heuristicEncoder, partitioning, partitionList) = CliUtils.getHeuristic(heuristicSolver, topology, opts.Heuristic, opts.Paths, opts.PopSlices, - opts.DemandPinningThreshold * opts.DownScaleFactor, numSamples: opts.NumRandom, partitionSensitivity: opts.PartitionSensitivity, - scaleFactor: opts.DownScaleFactor, InnerEncoding: opts.InnerEncoding, maxShortestPathLen: opts.MaxShortestPathLen); + // Setup adversarial input generator + var adversarialInputGenerator = new TEAdversarialInputGenerator( + topology, opts.Paths, opts.NumProcesses); + + // Setup heuristic encoder based on selected algorithm + var (heuristicEncoder, partitioning, partitionList) = CliUtils.getHeuristic( + solver, + topology, + opts.Heuristic, + opts.Paths, + opts.PopSlices, + opts.DemandPinningThreshold * opts.DownScaleFactor, + numSamples: opts.NumRandom, + partitionSensitivity: opts.PartitionSensitivity, + scaleFactor: opts.DownScaleFactor, + InnerEncoding: opts.InnerEncoding, + maxShortestPathLen: opts.MaxShortestPathLen); + + // Parse demand quantization levels + var demandList = new GenericList( + opts.DemandList.Split(",") + .Select(x => double.Parse(x) * opts.DownScaleFactor) + .ToHashSet()); - // find an adversarial example and show the time taken. - var demandList = new GenericList((opts.DemandList.Split(",")).Select(x => double.Parse(x) * opts.DownScaleFactor).ToHashSet()); Utils.logger( - string.Format("Demand List:{0}", Newtonsoft.Json.JsonConvert.SerializeObject(demandList.List, Newtonsoft.Json.Formatting.Indented)), + $"Demand List:{Newtonsoft.Json.JsonConvert.SerializeObject(demandList.List, Newtonsoft.Json.Formatting.Indented)}", opts.Verbose); - var timer = System.Diagnostics.Stopwatch.StartNew(); - Utils.logger("Starting setup", opts.Verbose); + + var timer = Stopwatch.StartNew(); + Utils.logger("Starting optimization", opts.Verbose); + + // Run selected optimization method (TEOptimizationSolution, TEOptimizationSolution) result; switch (opts.Method) { case MethodChoice.Direct: - result = CliUtils.getMetaOptResult(adversarialInputGenerator, optimalEncoder, heuristicEncoder, opts.DemandUB, opts.InnerEncoding, - demandList, opts.EnableClustering, opts.ClusterVersion, clusters, opts.NumInterClusterSamples, opts.NumNodesPerCluster, - opts.NumInterClusterQuantizations, opts.Simplify, opts.Verbose, opts.MaxDensity, opts.LargeDemandLB, opts.maxLargeDistance, - opts.maxSmallDistance, false, null); + result = CliUtils.getMetaOptResult( + adversarialInputGenerator, optimalEncoder, heuristicEncoder, + opts.DemandUB, opts.InnerEncoding, demandList, + opts.EnableClustering, opts.ClusterVersion, clusters, + opts.NumInterClusterSamples, opts.NumNodesPerCluster, + opts.NumInterClusterQuantizations, opts.Simplify, opts.Verbose, + opts.MaxDensity, opts.LargeDemandLB, opts.maxLargeDistance, + opts.maxSmallDistance, false, null); break; + case MethodChoice.Search: - Utils.logger("Going to use search to find a desirable gap", opts.Verbose); - result = adversarialInputGenerator.FindMaximumGapInterval(optimalEncoder, heuristicEncoder, opts.Confidencelvl, opts.StartingGap, opts.DemandUB, - demandList: demandList); + Utils.logger("Using interval search for gap", opts.Verbose); + result = adversarialInputGenerator.FindMaximumGapInterval( + optimalEncoder, heuristicEncoder, + opts.Confidencelvl, opts.StartingGap, opts.DemandUB, + demandList: demandList); break; + case MethodChoice.FindFeas: - Utils.logger("Going to find one feasible solution with the specified gap", opts.Verbose); - result = adversarialInputGenerator.FindOptimalityGapAtLeast(optimalEncoder, heuristicEncoder, opts.StartingGap, opts.DemandUB, - demandList: demandList, simplify: opts.Simplify); + Utils.logger("Finding feasible solution with target gap", opts.Verbose); + result = adversarialInputGenerator.FindOptimalityGapAtLeast( + optimalEncoder, heuristicEncoder, + opts.StartingGap, opts.DemandUB, + demandList: demandList, simplify: opts.Simplify); break; + case MethodChoice.Random: - Utils.logger("Going to do random search to find some advers inputs", opts.Verbose); - result = adversarialInputGenerator.RandomAdversarialGenerator(optimalEncoder, heuristicEncoder, opts.NumRandom, opts.DemandUB, seed: opts.Seed, - verbose: opts.Verbose, storeProgress: opts.StoreProgress, logPath: opts.LogFile, timeout: opts.Timeout); + Utils.logger("Using random search", opts.Verbose); + result = adversarialInputGenerator.RandomAdversarialGenerator( + optimalEncoder, heuristicEncoder, + opts.NumRandom, opts.DemandUB, + seed: opts.Seed, verbose: opts.Verbose, + storeProgress: opts.StoreProgress, logPath: opts.LogFile, + timeout: opts.Timeout); break; + case MethodChoice.HillClimber: - Utils.logger("Going to use HillClimber to find some advers inputs", opts.Verbose); - result = adversarialInputGenerator.HillClimbingAdversarialGenerator(optimalEncoder, heuristicEncoder, opts.NumRandom, - opts.NumNeighbors, opts.DemandUB, opts.StdDev, seed: opts.Seed, verbose: opts.Verbose, storeProgress: opts.StoreProgress, - logPath: opts.LogFile, timeout: opts.Timeout); + Utils.logger("Using hill climbing", opts.Verbose); + result = adversarialInputGenerator.HillClimbingAdversarialGenerator( + optimalEncoder, heuristicEncoder, + opts.NumRandom, opts.NumNeighbors, opts.DemandUB, opts.StdDev, + seed: opts.Seed, verbose: opts.Verbose, + storeProgress: opts.StoreProgress, logPath: opts.LogFile, + timeout: opts.Timeout); break; + case MethodChoice.SimulatedAnnealing: - Utils.logger("Going to use Simulated Annealing to find some advers inputs", opts.Verbose); - Utils.logger(opts.LogFile, opts.Verbose); - result = adversarialInputGenerator.SimulatedAnnealing(optimalEncoder, heuristicEncoder, opts.NumRandom, opts.NumNeighbors, - opts.DemandUB, opts.StdDev, opts.InitTmp, opts.TmpDecreaseFactor, seed: opts.Seed, verbose: opts.Verbose, storeProgress: opts.StoreProgress, - logPath: opts.LogFile, timeout: opts.Timeout); + Utils.logger("Using simulated annealing", opts.Verbose); + result = adversarialInputGenerator.SimulatedAnnealing( + optimalEncoder, heuristicEncoder, + opts.NumRandom, opts.NumNeighbors, opts.DemandUB, opts.StdDev, + opts.InitTmp, opts.TmpDecreaseFactor, + seed: opts.Seed, verbose: opts.Verbose, + storeProgress: opts.StoreProgress, logPath: opts.LogFile, + timeout: opts.Timeout); break; + default: - throw new Exception("Wrong Method, please choose between available methods!!"); + throw new Exception($"Unknown method: {opts.Method}. " + + "Valid options: Direct, Search, FindFeas, Random, HillClimber, SimulatedAnnealing"); } + // Post-processing: Full optimization refinement (clustering only) if (opts.FullOpt) { if (!opts.EnableClustering) { - throw new Exception("does not need to be enable for non-clustering method"); + throw new Exception("FullOpt requires clustering to be enabled"); } if (opts.InnerEncoding != InnerRewriteMethodChoice.PrimalDual) { - throw new Exception("inner encoding should be primal dual"); + throw new Exception("FullOpt requires PrimalDual inner encoding"); } + optimalEncoder.Solver.CleanAll(timeout: opts.FullOptTimer); var currDemands = new Dictionary<(string, string), double>(result.Item1.Demands); Utils.setEmptyPairsToZero(topology, currDemands); - result = adversarialInputGenerator.MaximizeOptimalityGap(optimalEncoder, heuristicEncoder, opts.DemandUB, innerEncoding: opts.InnerEncoding, - demandList: demandList, simplify: opts.Simplify, verbose: opts.Verbose, demandInits: currDemands); + + result = adversarialInputGenerator.MaximizeOptimalityGap( + optimalEncoder, heuristicEncoder, opts.DemandUB, + innerEncoding: opts.InnerEncoding, demandList: demandList, + simplify: opts.Simplify, verbose: opts.Verbose, + demandInits: currDemands); + optimalEncoder.Solver.CleanAll(focusBstBd: false, timeout: opts.Timeout); } + // Post-processing: Upper bound focus refinement if (opts.UBFocus) { var currDemands = new Dictionary<(string, string), double>(result.Item1.Demands); optimalEncoder.Solver.CleanAll(focusBstBd: true, timeout: opts.UBFocusTimer); Utils.setEmptyPairsToZero(topology, currDemands); - result = adversarialInputGenerator.MaximizeOptimalityGap(optimalEncoder, heuristicEncoder, opts.DemandUB, innerEncoding: opts.InnerEncoding, - demandList: demandList, simplify: opts.Simplify, verbose: opts.Verbose, demandInits: currDemands); + + result = adversarialInputGenerator.MaximizeOptimalityGap( + optimalEncoder, heuristicEncoder, opts.DemandUB, + innerEncoding: opts.InnerEncoding, demandList: demandList, + simplify: opts.Simplify, verbose: opts.Verbose, + demandInits: currDemands); + optimalEncoder.Solver.CleanAll(focusBstBd: false, timeout: opts.Timeout); } + + timer.Stop(); + + // Extract results var optimal = result.Item1.MaxObjective; var heuristic = result.Item2.MaxObjective; var demands = new Dictionary<(string, string), double>(result.Item1.Demands); Utils.setEmptyPairsToZero(topology, demands); + + // Display results Console.WriteLine("##############################################"); + Console.WriteLine("RESULTS:"); + Console.WriteLine($"Optimal: {optimal}"); + Console.WriteLine($"Heuristic: {heuristic}"); + Console.WriteLine($"Gap: {optimal - heuristic}"); + Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); Console.WriteLine("##############################################"); - Console.WriteLine("##############################################"); - Console.WriteLine($"optimal={optimal}, heuristic={heuristic}, time={timer.ElapsedMilliseconds}ms"); + + // Special handling for ExpectedPop heuristic if (opts.Heuristic == Heuristic.ExpectedPop) { - CliUtils.findGapExpectedPopAdversarialDemandOnIndependentPartitions(opts, topology, demands, optimal); + CliUtils.findGapExpectedPopAdversarialDemandOnIndependentPartitions( + opts, topology, demands, optimal); } - Console.WriteLine("##############################################"); - Console.WriteLine("##############################################"); - Console.WriteLine("##############################################"); - var optGSolver = new GurobiBinary(); - var optimalEncoderG = new TEMaxFlowOptimalEncoder(optGSolver, maxNumPaths: opts.Paths); - var optZSolver = new SolverZen(); - var optimalEncoderZen = new TEMaxFlowOptimalEncoder, ZenSolution>(optZSolver, maxNumPaths: opts.Paths); - var gSolver = new GurobiBinary(); - var zSolver = new SolverZen(); - IEncoder heuristicEncoderG; - IEncoder, ZenSolution> heuristicEncoderZ; - switch (opts.Heuristic) - { - case Heuristic.Pop: - Console.WriteLine("Starting exploring pop heuristic"); - heuristicEncoderG = new PopEncoder(gSolver, maxNumPaths: opts.Paths, numPartitions: opts.PopSlices, demandPartitions: partitioning); - heuristicEncoderZ = new PopEncoder, ZenSolution>(zSolver, maxNumPaths: opts.Paths, numPartitions: opts.PopSlices, demandPartitions: partitioning); - break; - case Heuristic.DemandPinning: - Console.WriteLine("Starting exploring demand pinning heuristic"); - heuristicEncoderG = new DirectDemandPinningEncoder(gSolver, k: opts.Paths, threshold: opts.DemandPinningThreshold * opts.DownScaleFactor); - heuristicEncoderZ = new DirectDemandPinningEncoder, ZenSolution>(zSolver, k: opts.Paths, threshold: opts.DemandPinningThreshold * opts.DownScaleFactor); - break; - case Heuristic.ExpectedPop: - Console.WriteLine("Starting to explore expected pop heuristic"); - heuristicEncoderG = new ExpectedPopEncoder(gSolver, k: opts.Paths, numSamples: opts.NumRandom, - numPartitionsPerSample: opts.PopSlices, demandPartitionsList: partitionList); - heuristicEncoderZ = new ExpectedPopEncoder, ZenSolution>(zSolver, k: opts.Paths, numSamples: opts.NumRandom, - numPartitionsPerSample: opts.PopSlices, demandPartitionsList: partitionList); - break; - case Heuristic.PopDp: - throw new Exception("Not Implemented Yet."); - default: - throw new Exception("No heuristic selected."); - } - Utils.checkSolution(topology, heuristicEncoderG, optimalEncoderG, heuristic, optimal, demands, "gurobiCheck"); + // Validation: verify solution with independent solvers + ValidateSolution(opts, topology, partitioning, partitionList, optimal, heuristic, demands); } /// - /// Runs Traffic Engineering optimization. - /// Uses CliUtils for topology loading and heuristic setup (same as original ssMain). + /// Validates the solution using independent Gurobi solver instances. /// - public static void RunSimple(CliArgs args) + private static void ValidateSolution( + CliOptions opts, + Topology topology, + IDictionary<(string, string), int> partitioning, + IList> partitionList, + double optimal, + double heuristic, + Dictionary<(string, string), double> demands) { - var topologyFile = args.Get("--topologyFile", "simple.json"); - var heuristic = args.Get("--heuristic", "Pop"); - var paths = args.GetInt("--paths", 1); - var verbose = args.GetBool("--verbose", false); - var timeout = args.GetDouble("--timeout", 1000); - var numThreads = args.GetInt("--numThreads", 1); - var popSlices = args.GetInt("--popSlices", 2); - - Console.WriteLine($"Topology File: {topologyFile}"); - Console.WriteLine($"Heuristic: {heuristic}"); - Console.WriteLine($"Paths per pair: {paths}"); - - // Load topology from JSON (simple loading, not CliUtils) - var topology = ReadTopologyFromFile(topologyFile); - - // Create solver - var solver = new GurobiSOS( - timeout: timeout, - verbose: Convert.ToInt32(verbose), - numThreads: numThreads); + Console.WriteLine("Validating solution..."); - // Create optimal encoder - var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, maxNumPaths: paths); - - // Create heuristic encoder - IEncoder heuristicEncoder; - IDictionary<(string, string), int> partition = null; - - switch (heuristic) - { - case "Pop": - partition = topology.RandomPartition(popSlices); - heuristicEncoder = new PopEncoder( - solver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); - break; - case "DemandPinning": - var dpThreshold = args.GetDouble("--dpThreshold", 0.5); - heuristicEncoder = new DirectDemandPinningEncoder( - solver, k: paths, threshold: dpThreshold); - break; - default: - throw new Exception($"Unsupported heuristic: {heuristic}"); - } - - // Create adversarial generator - var adversarialGenerator = new TEAdversarialInputGenerator(topology, maxNumPaths: paths); - - // Run optimization - SIMPLE call like original TEMain - var timer = Stopwatch.StartNew(); - var (optimalSolution, heuristicSolution) = adversarialGenerator.MaximizeOptimalityGap( - optimalEncoder, heuristicEncoder); - timer.Stop(); - - // Display results - Console.WriteLine("Optimal:"); - Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(optimalSolution, Newtonsoft.Json.Formatting.Indented)); - Console.WriteLine("****"); - Console.WriteLine("Heuristic:"); - Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(heuristicSolution, Newtonsoft.Json.Formatting.Indented)); - Console.WriteLine("****"); - - var optimal = optimalSolution.MaxObjective; - var heuristicObj = heuristicSolution.MaxObjective; - Console.WriteLine($"optimalG={optimal}, heuristicG={heuristicObj}, time={timer.ElapsedMilliseconds}ms"); - - // Validation - like original TEMain - var demands = new Dictionary<(string, string), double>(optimalSolution.Demands); - - var optGSolver = new GurobiSOS(); - var optimalEncoderG = new TEMaxFlowOptimalEncoder(optGSolver, maxNumPaths: paths); + var optGSolver = new GurobiBinary(); + var optimalEncoderG = new TEMaxFlowOptimalEncoder( + optGSolver, maxNumPaths: opts.Paths); - var popGSolver = new GurobiSOS(); + var gSolver = new GurobiBinary(); IEncoder heuristicEncoderG; - switch (heuristic) + switch (opts.Heuristic) { - case "Pop": + case Heuristic.Pop: heuristicEncoderG = new PopEncoder( - popGSolver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); + gSolver, maxNumPaths: opts.Paths, + numPartitions: opts.PopSlices, + demandPartitions: partitioning); break; - case "DemandPinning": - var dpThreshold = args.GetDouble("--dpThreshold", 0.5); + + case Heuristic.DemandPinning: heuristicEncoderG = new DirectDemandPinningEncoder( - popGSolver, k: paths, threshold: dpThreshold); + gSolver, k: opts.Paths, + threshold: opts.DemandPinningThreshold * opts.DownScaleFactor); break; - default: - throw new Exception($"Unsupported heuristic: {heuristic}"); - } - - Utils.checkSolution(topology, heuristicEncoderG, optimalEncoderG, heuristicObj, optimal, demands, "gurobiCheck"); - } - private static Topology ReadTopologyFromFile(string fileName) - { - var baseDir = AppDomain.CurrentDomain.BaseDirectory; - var topologiesDir = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", "Topologies")); - var filePath = Path.Combine(topologiesDir, fileName); - - if (!File.Exists(filePath)) - { - topologiesDir = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "Topologies")); - filePath = Path.Combine(topologiesDir, fileName); - } - - if (!File.Exists(filePath)) - { - throw new FileNotFoundException($"Topology file not found: {fileName}"); - } - - Console.WriteLine($"Loading topology from: {filePath}"); - - var json = File.ReadAllText(filePath); - var data = Newtonsoft.Json.JsonConvert.DeserializeObject(json); - - var topology = new Topology(); + case Heuristic.ExpectedPop: + heuristicEncoderG = new ExpectedPopEncoder( + gSolver, k: opts.Paths, + numSamples: opts.NumRandom, + numPartitionsPerSample: opts.PopSlices, + demandPartitionsList: partitionList); + break; - foreach (var node in data.Nodes) - { - topology.AddNode(node.Id.ToString()); - } + case Heuristic.PopDp: + throw new Exception("PopDp validation not implemented yet."); - foreach (var link in data.Links) - { - topology.AddEdge(link.Source.ToString(), link.Target.ToString(), capacity: link.Capacity); + default: + throw new Exception($"Unknown heuristic for validation: {opts.Heuristic}"); } - Console.WriteLine($"Loaded: {data.Nodes.Count} nodes, {data.Links.Count} edges"); - return topology; - } - - private class TopologyJson - { - public List Nodes { get; set; } = new (); - public List Links { get; set; } = new (); - } - - private class NodeJson - { - [Newtonsoft.Json.JsonProperty("id")] - public object Id { get; set; } - } + Utils.checkSolution( + topology, heuristicEncoderG, optimalEncoderG, + heuristic, optimal, demands, "gurobiCheck"); - private class LinkJson - { - [Newtonsoft.Json.JsonProperty("source")] - public object Source { get; set; } - [Newtonsoft.Json.JsonProperty("target")] - public object Target { get; set; } - [Newtonsoft.Json.JsonProperty("capacity")] - public double Capacity { get; set; } + Console.WriteLine("Validation completed."); } } } \ No newline at end of file diff --git a/MetaOptimize.Cli/TESimpleRunner.cs b/MetaOptimize.Cli/TESimpleRunner.cs deleted file mode 100644 index 5e48a3efb..000000000 --- a/MetaOptimize.Cli/TESimpleRunner.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System.Diagnostics; -using Gurobi; - -namespace MetaOptimize.Cli -{ - /// - /// Traffic Engineering runner - matches original TEMain behavior. - /// - public static class TESimpleRunner - { - /// - /// Runs Traffic Engineering optimization. - /// Uses CliUtils for topology loading and heuristic setup (same as original ssMain). - /// - public static void Run(CliArgs args) - { - var topologyFile = args.Get("--topologyFile", "simple.json"); - var heuristic = args.Get("--heuristic", "Pop"); - var paths = args.GetInt("--paths", 1); - var verbose = args.GetBool("--verbose", false); - var timeout = args.GetDouble("--timeout", 1000); - var numThreads = args.GetInt("--numThreads", 1); - var popSlices = args.GetInt("--popSlices", 2); - - Console.WriteLine($"Topology File: {topologyFile}"); - Console.WriteLine($"Heuristic: {heuristic}"); - Console.WriteLine($"Paths per pair: {paths}"); - - // Load topology from JSON (simple loading, not CliUtils) - var topology = ReadTopologyFromFile(topologyFile); - - // Create solver - var solver = new GurobiSOS( - timeout: timeout, - verbose: Convert.ToInt32(verbose), - numThreads: numThreads); - - // Create optimal encoder - var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, maxNumPaths: paths); - - // Create heuristic encoder - IEncoder heuristicEncoder; - IDictionary<(string, string), int> partition = null; - - switch (heuristic) - { - case "Pop": - partition = topology.RandomPartition(popSlices); - heuristicEncoder = new PopEncoder( - solver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); - break; - case "DemandPinning": - var dpThreshold = args.GetDouble("--dpThreshold", 0.5); - heuristicEncoder = new DirectDemandPinningEncoder( - solver, k: paths, threshold: dpThreshold); - break; - default: - throw new Exception($"Unsupported heuristic: {heuristic}"); - } - - // Create adversarial generator - var adversarialGenerator = new TEAdversarialInputGenerator(topology, maxNumPaths: paths); - - // Run optimization - SIMPLE call like original TEMain - var timer = Stopwatch.StartNew(); - var (optimalSolution, heuristicSolution) = adversarialGenerator.MaximizeOptimalityGap( - optimalEncoder, heuristicEncoder); - timer.Stop(); - - // Display results - Console.WriteLine("Optimal:"); - Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(optimalSolution, Newtonsoft.Json.Formatting.Indented)); - Console.WriteLine("****"); - Console.WriteLine("Heuristic:"); - Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(heuristicSolution, Newtonsoft.Json.Formatting.Indented)); - Console.WriteLine("****"); - - var optimal = optimalSolution.MaxObjective; - var heuristicObj = heuristicSolution.MaxObjective; - Console.WriteLine($"optimalG={optimal}, heuristicG={heuristicObj}, time={timer.ElapsedMilliseconds}ms"); - - // Validation - like original TEMain - var demands = new Dictionary<(string, string), double>(optimalSolution.Demands); - - var optGSolver = new GurobiSOS(); - var optimalEncoderG = new TEMaxFlowOptimalEncoder(optGSolver, maxNumPaths: paths); - - var popGSolver = new GurobiSOS(); - IEncoder heuristicEncoderG; - - switch (heuristic) - { - case "Pop": - heuristicEncoderG = new PopEncoder( - popGSolver, maxNumPaths: paths, numPartitions: popSlices, demandPartitions: partition); - break; - case "DemandPinning": - var dpThreshold = args.GetDouble("--dpThreshold", 0.5); - heuristicEncoderG = new DirectDemandPinningEncoder( - popGSolver, k: paths, threshold: dpThreshold); - break; - default: - throw new Exception($"Unsupported heuristic: {heuristic}"); - } - - Utils.checkSolution(topology, heuristicEncoderG, optimalEncoderG, heuristicObj, optimal, demands, "gurobiCheck"); - } - - private static Topology ReadTopologyFromFile(string fileName) - { - var baseDir = AppDomain.CurrentDomain.BaseDirectory; - var topologiesDir = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", "Topologies")); - var filePath = Path.Combine(topologiesDir, fileName); - - if (!File.Exists(filePath)) - { - topologiesDir = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "Topologies")); - filePath = Path.Combine(topologiesDir, fileName); - } - - if (!File.Exists(filePath)) - { - throw new FileNotFoundException($"Topology file not found: {fileName}"); - } - - Console.WriteLine($"Loading topology from: {filePath}"); - - var json = File.ReadAllText(filePath); - var data = Newtonsoft.Json.JsonConvert.DeserializeObject(json); - - var topology = new Topology(); - - foreach (var node in data.Nodes) - { - topology.AddNode(node.Id.ToString()); - } - - foreach (var link in data.Links) - { - topology.AddEdge(link.Source.ToString(), link.Target.ToString(), capacity: link.Capacity); - } - - Console.WriteLine($"Loaded: {data.Nodes.Count} nodes, {data.Links.Count} edges"); - return topology; - } - - private class TopologyJson - { - public List Nodes { get; set; } = new (); - public List Links { get; set; } = new (); - } - - private class NodeJson - { - [Newtonsoft.Json.JsonProperty("id")] - public object Id { get; set; } - } - - private class LinkJson - { - [Newtonsoft.Json.JsonProperty("source")] - public object Source { get; set; } - [Newtonsoft.Json.JsonProperty("target")] - public object Target { get; set; } - [Newtonsoft.Json.JsonProperty("capacity")] - public double Capacity { get; set; } - } - } -} \ No newline at end of file diff --git a/MetaOptimize.Test/CliOptionsTests.cs b/MetaOptimize.Test/CliOptionsTests.cs new file mode 100644 index 000000000..e52057e71 --- /dev/null +++ b/MetaOptimize.Test/CliOptionsTests.cs @@ -0,0 +1,668 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +namespace MetaOptimize.Test +{ + using System; + using System.Collections.Generic; + using CommandLine; + using MetaOptimize.Cli; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Tests for CLI options parsing and runner integration. + /// Validates the unified CliOptions system works for all problem types. + /// + [TestClass] + public class CliOptionsTests + { + #region CliOptions Parsing Tests + + /// + /// Test that default values are set correctly when no arguments provided. + /// + [TestMethod] + public void TestCliOptionsDefaults() + { + var args = new string[] { }; + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + // Common defaults + Assert.AreEqual(ProblemType.TrafficEngineering, opts.ProblemType); + Assert.AreEqual(SolverChoice.Gurobi, opts.SolverChoice); + Assert.AreEqual(false, opts.Verbose); + Assert.AreEqual(false, opts.Debug); + + // BinPacking defaults + Assert.AreEqual(6, opts.NumBins); + Assert.AreEqual(9, opts.NumDemands); + Assert.AreEqual(2, opts.NumDimensions); + Assert.AreEqual(3, opts.OptimalBins); + Assert.AreEqual(FFDMethodChoice.FFDSum, opts.FFMethod); + Assert.AreEqual(false, opts.BreakSymmetry); + + // PIFO defaults + Assert.AreEqual(18, opts.NumPackets); + Assert.AreEqual(8, opts.MaxRank); + Assert.AreEqual(4, opts.NumQueues); + Assert.AreEqual(12, opts.MaxQueueSize); + Assert.AreEqual(12, opts.WindowSize); + Assert.AreEqual(0.1, opts.BurstParam); + + // FailureAnalysis defaults + Assert.AreEqual(true, opts.UseDefaultTopology); + Assert.AreEqual(1, opts.MaxNumFailures); + Assert.AreEqual(1, opts.NumExtraPaths); + Assert.AreEqual(0.25, opts.FailureProbThreshold); + + // TE defaults + Assert.AreEqual(Heuristic.Pop, opts.Heuristic); + Assert.AreEqual(2, opts.Paths); + Assert.AreEqual(2, opts.PopSlices); + Assert.AreEqual(MethodChoice.Direct, opts.Method); + Assert.AreEqual(InnerRewriteMethodChoice.KKT, opts.InnerEncoding); + }); + } + + /// + /// Test parsing BinPacking problem type with custom parameters. + /// + [TestMethod] + public void TestCliOptionsBinPackingParsing() + { + var args = new string[] + { + "--problemType", "BinPacking", + "--solver", "Gurobi", + "--numBins", "10", + "--numDemands", "15", + "--numDimensions", "3", + "--optimalBins", "5", + "--ffMethod", "FFDProd", + "--breakSymmetry", "true", + "--binCapacity", "1.5,1.5,1.5", + "--verbose", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.BinPacking, opts.ProblemType); + Assert.AreEqual(SolverChoice.Gurobi, opts.SolverChoice); + Assert.AreEqual(10, opts.NumBins); + Assert.AreEqual(15, opts.NumDemands); + Assert.AreEqual(3, opts.NumDimensions); + Assert.AreEqual(5, opts.OptimalBins); + Assert.AreEqual(FFDMethodChoice.FFDProd, opts.FFMethod); + Assert.AreEqual(true, opts.BreakSymmetry); + Assert.AreEqual("1.5,1.5,1.5", opts.BinCapacity); + Assert.AreEqual(true, opts.Verbose); + }); + + result.WithNotParsed(errors => + { + Assert.Fail("Failed to parse BinPacking arguments"); + }); + } + + /// + /// Test parsing PIFO problem type with custom parameters. + /// + [TestMethod] + public void TestCliOptionsPIFOParsing() + { + var args = new string[] + { + "--problemType", "PIFO", + "--solver", "Gurobi", + "--numPackets", "24", + "--maxRank", "10", + "--numQueues", "6", + "--maxQueueSize", "15", + "--windowSize", "15", + "--burstParam", "0.2", + "--timeout", "500", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.PIFO, opts.ProblemType); + Assert.AreEqual(24, opts.NumPackets); + Assert.AreEqual(10, opts.MaxRank); + Assert.AreEqual(6, opts.NumQueues); + Assert.AreEqual(15, opts.MaxQueueSize); + Assert.AreEqual(15, opts.WindowSize); + Assert.AreEqual(0.2, opts.BurstParam); + Assert.AreEqual(500, opts.Timeout); + }); + + result.WithNotParsed(errors => + { + Assert.Fail("Failed to parse PIFO arguments"); + }); + } + + /// + /// Test parsing FailureAnalysis problem type with custom parameters. + /// + [TestMethod] + public void TestCliOptionsFailureAnalysisParsing() + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--solver", "Gurobi", + "--useDefaultTopology", "true", + "--maxNumFailures", "2", + "--numExtraPaths", "2", + "--failureProbThreshold", "0.1", + "--innerencoding", "PrimalDual", + "--demandlist", "0,5,10,15", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.FailureAnalysis, opts.ProblemType); + Assert.AreEqual(true, opts.UseDefaultTopology); + Assert.AreEqual(2, opts.MaxNumFailures); + Assert.AreEqual(2, opts.NumExtraPaths); + Assert.AreEqual(0.1, opts.FailureProbThreshold); + Assert.AreEqual(InnerRewriteMethodChoice.PrimalDual, opts.InnerEncoding); + Assert.AreEqual("0,5,10,15", opts.DemandList); + }); + + result.WithNotParsed(errors => + { + Assert.Fail("Failed to parse FailureAnalysis arguments"); + }); + } + + /// + /// Test parsing TrafficEngineering problem type with custom parameters. + /// + [TestMethod] + public void TestCliOptionsTrafficEngineeringParsing() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--topologyFile", "Swan.json", + "--heuristic", "DemandPinning", + "--solver", "Gurobi", + "--paths", "3", + "--pinthreshold", "0.7", + "--method", "Direct", + "--innerencoding", "KKT", + "--verbose", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.TrafficEngineering, opts.ProblemType); + Assert.AreEqual("Swan.json", opts.TopologyFile); + Assert.AreEqual(Heuristic.DemandPinning, opts.Heuristic); + Assert.AreEqual(SolverChoice.Gurobi, opts.SolverChoice); + Assert.AreEqual(3, opts.Paths); + Assert.AreEqual(0.7, opts.DemandPinningThreshold); + Assert.AreEqual(MethodChoice.Direct, opts.Method); + Assert.AreEqual(InnerRewriteMethodChoice.KKT, opts.InnerEncoding); + Assert.AreEqual(true, opts.Verbose); + }); + + result.WithNotParsed(errors => + { + Assert.Fail("Failed to parse TrafficEngineering arguments"); + }); + } + + /// + /// Test all FFDMethodChoice values parse correctly. + /// + [TestMethod] + public void TestCliOptionsFFMethodChoices() + { + var methods = new[] { "FF", "FFDSum", "FFDProd", "FFDDiv" }; + var expected = new[] { FFDMethodChoice.FF, FFDMethodChoice.FFDSum, FFDMethodChoice.FFDProd, FFDMethodChoice.FFDDiv }; + + for (int i = 0; i < methods.Length; i++) + { + var args = new string[] { "--problemType", "BinPacking", "--ffMethod", methods[i] }; + var result = CommandLine.Parser.Default.ParseArguments(args); + + var expectedMethod = expected[i]; + result.WithParsed(opts => + { + Assert.AreEqual(expectedMethod, opts.FFMethod, $"Failed for {methods[i]}"); + }); + } + } + + /// + /// Test all SolverChoice values parse correctly. + /// + [TestMethod] + public void TestCliOptionsSolverChoices() + { + var solvers = new[] { "Gurobi", "Zen" }; + var expected = new[] { SolverChoice.Gurobi, SolverChoice.Zen }; + + for (int i = 0; i < solvers.Length; i++) + { + var args = new string[] { "--solver", solvers[i] }; + var result = CommandLine.Parser.Default.ParseArguments(args); + + var expectedSolver = expected[i]; + result.WithParsed(opts => + { + Assert.AreEqual(expectedSolver, opts.SolverChoice, $"Failed for {solvers[i]}"); + }); + } + } + + /// + /// Test all MethodChoice values parse correctly. + /// + [TestMethod] + public void TestCliOptionsMethodChoices() + { + var methods = new[] { "Direct", "Search", "FindFeas", "Random", "HillClimber", "SimulatedAnnealing" }; + var expected = new[] + { + MethodChoice.Direct, MethodChoice.Search, MethodChoice.FindFeas, + MethodChoice.Random, MethodChoice.HillClimber, MethodChoice.SimulatedAnnealing, + }; + + for (int i = 0; i < methods.Length; i++) + { + var args = new string[] { "--method", methods[i] }; + var result = CommandLine.Parser.Default.ParseArguments(args); + + var expectedMethod = expected[i]; + result.WithParsed(opts => + { + Assert.AreEqual(expectedMethod, opts.Method, $"Failed for {methods[i]}"); + }); + } + } + + /// + /// Test all Heuristic values parse correctly. + /// + [TestMethod] + public void TestCliOptionsHeuristicChoices() + { + var heuristics = new[] { "Pop", "DemandPinning", "ExpectedPop", "PopDp", "ModifiedDp" }; + var expected = new[] + { + Heuristic.Pop, Heuristic.DemandPinning, Heuristic.ExpectedPop, + Heuristic.PopDp, Heuristic.ModifiedDp, + }; + + for (int i = 0; i < heuristics.Length; i++) + { + var args = new string[] { "--heuristic", heuristics[i] }; + var result = CommandLine.Parser.Default.ParseArguments(args); + + var expectedHeuristic = expected[i]; + result.WithParsed(opts => + { + Assert.AreEqual(expectedHeuristic, opts.Heuristic, $"Failed for {heuristics[i]}"); + }); + } + } + + #endregion + + #region BinPacking Integration Tests + + /// + /// Test BinPacking runner executes with CliOptions. + /// Uses small parameters for fast execution. + /// + [TestMethod] + public void TestBinPackingRunnerSmall() + { + var args = new string[] + { + "--problemType", "BinPacking", + "--solver", "Gurobi", + "--numBins", "4", + "--numDemands", "6", + "--numDimensions", "2", + "--optimalBins", "2", + "--ffMethod", "FFDSum", + "--timeout", "60", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + + // Verify options are set correctly + Assert.AreEqual(ProblemType.BinPacking, opts.ProblemType); + Assert.AreEqual(4, opts.NumBins); + Assert.AreEqual(6, opts.NumDemands); + Assert.AreEqual(2, opts.NumDimensions); + Assert.AreEqual(2, opts.OptimalBins); + + // Parse bin capacities (same logic as BPRunner) + var binCapacities = opts.BinCapacity.Split(',').Select(double.Parse).ToList(); + while (binCapacities.Count < opts.NumDimensions) + { + binCapacities.Add(1.00001); + } + + Assert.AreEqual(2, binCapacities.Count); + + // Create components (don't run full optimization - too slow for unit test) + var solver = new GurobiSOS(timeout: opts.Timeout, verbose: 0); + var bins = new Bins(opts.NumBins, binCapacities); + + Assert.IsNotNull(solver); + Assert.IsNotNull(bins); + }); + } + + /// + /// Test BinPacking bin capacity parsing handles various formats. + /// + [TestMethod] + public void TestBinPackingCapacityParsing() + { + // Single dimension + var capacities1 = "1.5".Split(',').Select(double.Parse).ToList(); + Assert.AreEqual(1, capacities1.Count); + Assert.AreEqual(1.5, capacities1[0]); + + // Two dimensions + var capacities2 = "1.00001,1.00001".Split(',').Select(double.Parse).ToList(); + Assert.AreEqual(2, capacities2.Count); + Assert.AreEqual(1.00001, capacities2[0]); + Assert.AreEqual(1.00001, capacities2[1]); + + // Three dimensions + var capacities3 = "2.0,1.5,1.0".Split(',').Select(double.Parse).ToList(); + Assert.AreEqual(3, capacities3.Count); + Assert.AreEqual(2.0, capacities3[0]); + Assert.AreEqual(1.5, capacities3[1]); + Assert.AreEqual(1.0, capacities3[2]); + } + + #endregion + + #region PIFO Integration Tests + + /// + /// Test PIFO runner options are parsed correctly. + /// + [TestMethod] + public void TestPIFORunnerOptions() + { + var args = new string[] + { + "--problemType", "PIFO", + "--solver", "Gurobi", + "--numPackets", "12", + "--maxRank", "6", + "--numQueues", "3", + "--maxQueueSize", "8", + "--windowSize", "8", + "--burstParam", "0.15", + "--timeout", "30", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + + Assert.AreEqual(ProblemType.PIFO, opts.ProblemType); + Assert.AreEqual(12, opts.NumPackets); + Assert.AreEqual(6, opts.MaxRank); + Assert.AreEqual(3, opts.NumQueues); + Assert.AreEqual(8, opts.MaxQueueSize); + Assert.AreEqual(8, opts.WindowSize); + Assert.AreEqual(0.15, opts.BurstParam); + Assert.AreEqual(30, opts.Timeout); + + // Create solver (don't run full optimization) + var solver = new GurobiSOS(timeout: opts.Timeout, verbose: 0); + Assert.IsNotNull(solver); + }); + } + + #endregion + + #region FailureAnalysis Integration Tests + + /// + /// Test FailureAnalysis runner options are parsed correctly. + /// + [TestMethod] + public void TestFailureAnalysisRunnerOptions() + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--solver", "Gurobi", + "--useDefaultTopology", "true", + "--maxNumFailures", "1", + "--numExtraPaths", "1", + "--failureProbThreshold", "0.2", + "--timeout", "30", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + + Assert.AreEqual(ProblemType.FailureAnalysis, opts.ProblemType); + Assert.AreEqual(true, opts.UseDefaultTopology); + Assert.AreEqual(1, opts.MaxNumFailures); + Assert.AreEqual(1, opts.NumExtraPaths); + Assert.AreEqual(0.2, opts.FailureProbThreshold); + + // Parse demand list (same logic as FailureAnalysisRunner) + var demandSet = new HashSet(opts.DemandList.Split(',').Select(double.Parse)); + Assert.IsTrue(demandSet.Count > 0); + }); + } + + /// + /// Test demand list parsing for FailureAnalysis. + /// + [TestMethod] + public void TestFailureAnalysisDemandListParsing() + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--demandlist", "0,5,10,15,20", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + var demandSet = new HashSet(opts.DemandList.Split(',').Select(double.Parse)); + Assert.AreEqual(5, demandSet.Count); + Assert.IsTrue(demandSet.Contains(0)); + Assert.IsTrue(demandSet.Contains(5)); + Assert.IsTrue(demandSet.Contains(10)); + Assert.IsTrue(demandSet.Contains(15)); + Assert.IsTrue(demandSet.Contains(20)); + }); + } + + #endregion + + #region TrafficEngineering Integration Tests + + /// + /// Test TrafficEngineering runner options are parsed correctly. + /// + [TestMethod] + public void TestTrafficEngineeringRunnerOptions() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--topologyFile", "Topologies/simple.json", + "--heuristic", "Pop", + "--solver", "Gurobi", + "--paths", "2", + "--slices", "2", + "--method", "Direct", + "--timeout", "30", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + + Assert.AreEqual(ProblemType.TrafficEngineering, opts.ProblemType); + Assert.AreEqual("Topologies/simple.json", opts.TopologyFile); + Assert.AreEqual(Heuristic.Pop, opts.Heuristic); + Assert.AreEqual(2, opts.Paths); + Assert.AreEqual(2, opts.PopSlices); + Assert.AreEqual(MethodChoice.Direct, opts.Method); + }); + } + + /// + /// Test TrafficEngineering DemandPinning options. + /// + [TestMethod] + public void TestTrafficEngineeringDemandPinningOptions() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--heuristic", "DemandPinning", + "--pinthreshold", "0.6", + "--paths", "3", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(Heuristic.DemandPinning, opts.Heuristic); + Assert.AreEqual(0.6, opts.DemandPinningThreshold); + Assert.AreEqual(3, opts.Paths); + }); + } + + /// + /// Test TrafficEngineering PrimalDual encoding options. + /// + [TestMethod] + public void TestTrafficEngineeringPrimalDualOptions() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--innerencoding", "PrimalDual", + "--demandlist", "0,10,20,30", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(InnerRewriteMethodChoice.PrimalDual, opts.InnerEncoding); + Assert.AreEqual("0,10,20,30", opts.DemandList); + + // Parse and validate + var demandSet = new HashSet(opts.DemandList.Split(',').Select(double.Parse)); + Assert.AreEqual(4, demandSet.Count); + }); + } + + #endregion + + #region Edge Cases and Error Handling + + /// + /// Test that CliOptions.Instance is set correctly. + /// + [TestMethod] + public void TestCliOptionsInstance() + { + var args = new string[] { "--problemType", "BinPacking" }; + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + Assert.IsNotNull(CliOptions.Instance); + Assert.AreEqual(ProblemType.BinPacking, CliOptions.Instance.ProblemType); + }); + } + + /// + /// Test short option flags work. + /// + [TestMethod] + public void TestShortOptionFlags() + { + var args = new string[] + { + "-f", "test.json", + "-h", "Pop", + "-c", "Gurobi", + "-v", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual("test.json", opts.TopologyFile); + Assert.AreEqual(Heuristic.Pop, opts.Heuristic); + Assert.AreEqual(SolverChoice.Gurobi, opts.SolverChoice); + Assert.AreEqual(true, opts.Verbose); + }); + } + + /// + /// Test timeout parsing with various values. + /// + [TestMethod] + public void TestTimeoutParsing() + { + // Finite timeout + var args1 = new string[] { "--timeout", "100" }; + CommandLine.Parser.Default.ParseArguments(args1).WithParsed(opts => + { + Assert.AreEqual(100, opts.Timeout); + }); + + // Large timeout + var args2 = new string[] { "--timeout", "3600" }; + CommandLine.Parser.Default.ParseArguments(args2).WithParsed(opts => + { + Assert.AreEqual(3600, opts.Timeout); + }); + } + + #endregion + } +} \ No newline at end of file diff --git a/MetaOptimize.Test/MetaOptimize.Test.csproj b/MetaOptimize.Test/MetaOptimize.Test.csproj index 341171474..b88ba2eec 100644 --- a/MetaOptimize.Test/MetaOptimize.Test.csproj +++ b/MetaOptimize.Test/MetaOptimize.Test.csproj @@ -1,4 +1,10 @@  + + net8.0 + false + true + + @@ -12,8 +18,11 @@ + + + \ No newline at end of file From a85c23ef2f1c78bb76a92efa42283f5b03accaa9 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Sat, 29 Nov 2025 10:57:30 -0800 Subject: [PATCH 05/11] Added additional 45 test units in CliParameterCombinationTests.cs and making sure the default valeus are being processed correctly --- MetaOptimize.Cli/CliOptions.cs | 25 +- MetaOptimize.Cli/Program.cs | 11 +- MetaOptimize.Test/CliOptionsTests.cs | 1 - .../CliParameterCombinationTests.cs | 1455 +++++++++++++++++ 4 files changed, 1484 insertions(+), 8 deletions(-) create mode 100644 MetaOptimize.Test/CliParameterCombinationTests.cs diff --git a/MetaOptimize.Cli/CliOptions.cs b/MetaOptimize.Cli/CliOptions.cs index f68dbfe44..9937b0920 100644 --- a/MetaOptimize.Cli/CliOptions.cs +++ b/MetaOptimize.Cli/CliOptions.cs @@ -83,7 +83,7 @@ public class CliOptions /// "links": [{"source": "a", "target": "b", "capacity": 10}, ...] /// }. /// - [Option('f', "topologyFile", Default = "..\\Topologies\\simple.json", HelpText = "The location of the topology file (JSON format)")] + [Option('f', "topologyFile", Default = "..\\MetaOpt\\Topologies\\simple.json", HelpText = "The location of the topology file (JSON format)")] public string TopologyFile { get; set; } /// @@ -491,8 +491,16 @@ public class CliOptions /// Reduces search space by eliminating equivalent solutions. /// May speed up optimization but could miss some adversarial inputs. /// - [Option("breakSymmetry", Default = false, HelpText = "Enable symmetry breaking (BinPacking)")] - public bool BreakSymmetry { get; set; } + [Option("breakSymmetry", Default = "false", HelpText = "Enable symmetry breaking in BinPacking encoder (true/false)")] + public string BreakSymmetryStr { get; set; } + + /// + /// Enable symmetry breaking constraints. + /// Reduces search space by eliminating equivalent solutions. + /// May speed up optimization but could miss some adversarial inputs. + /// + public bool BreakSymmetry => + string.Equals(BreakSymmetryStr, "true", StringComparison.OrdinalIgnoreCase); /// /// Bin capacities per dimension (comma-separated). @@ -556,8 +564,15 @@ public class CliOptions /// Use built-in default topology instead of loading from file. /// Default topology is a simple 4-node diamond for testing. /// - [Option("useDefaultTopology", Default = true, HelpText = "Use built-in test topology (FailureAnalysis)")] - public bool UseDefaultTopology { get; set; } + [Option("useDefaultTopology", Default = "true", HelpText = "Use built-in default topology for Failure Analysis. Requires value: true or false")] + public string UseDefaultTopologyStr { get; set; } + + /// + /// Use built-in default topology instead of loading from file. + /// Default topology is a simple 4-node diamond for testing. + /// + public bool UseDefaultTopology => + string.Equals(UseDefaultTopologyStr, "true", StringComparison.OrdinalIgnoreCase); /// /// Maximum number of simultaneous link failures to consider. diff --git a/MetaOptimize.Cli/Program.cs b/MetaOptimize.Cli/Program.cs index 2c9ce2a79..93daeb325 100644 --- a/MetaOptimize.Cli/Program.cs +++ b/MetaOptimize.Cli/Program.cs @@ -51,8 +51,15 @@ private static void RunWithOptions(CliOptions opts) { try { - Console.WriteLine($"Starting MetaOptimize - Problem Type: {opts.ProblemType}"); - Console.WriteLine(new string('=', 60)); + // Debug output + if (opts.Verbose) + { + Console.WriteLine($"[DEBUG] UseDefaultTopology: {opts.UseDefaultTopology}"); + Console.WriteLine($"[DEBUG] BreakSymmetry: {opts.BreakSymmetry}"); + Console.WriteLine($"[DEBUG] EnableClustering: {opts.EnableClustering}"); + Console.WriteLine($"[DEBUG] Verbose: {opts.Verbose}"); + Console.WriteLine($"[DEBUG] Debug: {opts.Debug}"); + } switch (opts.ProblemType) { diff --git a/MetaOptimize.Test/CliOptionsTests.cs b/MetaOptimize.Test/CliOptionsTests.cs index e52057e71..033c1929d 100644 --- a/MetaOptimize.Test/CliOptionsTests.cs +++ b/MetaOptimize.Test/CliOptionsTests.cs @@ -4,7 +4,6 @@ namespace MetaOptimize.Test { - using System; using System.Collections.Generic; using CommandLine; using MetaOptimize.Cli; diff --git a/MetaOptimize.Test/CliParameterCombinationTests.cs b/MetaOptimize.Test/CliParameterCombinationTests.cs new file mode 100644 index 000000000..929561efb --- /dev/null +++ b/MetaOptimize.Test/CliParameterCombinationTests.cs @@ -0,0 +1,1455 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +namespace MetaOptimize.Test +{ + using System; + using System.Linq; + using CommandLine; + using Gurobi; + using MetaOptimize.Cli; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Comprehensive tests for CLI parameter combinations. + /// Validates that all parameter combinations parse correctly and runners can be initialized. + /// + [TestClass] + public class CliParameterCombinationTests + { + #region Problem Type x Solver Combinations + + /// + /// Test all ProblemType and SolverChoice combinations parse correctly. + /// + [TestMethod] + public void TestAllProblemTypeSolverCombinations() + { + var problemTypes = new[] { "TrafficEngineering", "BinPacking", "PIFO", "FailureAnalysis" }; + var solvers = new[] { "Gurobi", "Zen" }; + + foreach (var problemType in problemTypes) + { + foreach (var solver in solvers) + { + var args = new string[] { "--problemType", problemType, "--solver", solver }; + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.IsNotNull(opts, $"Failed to parse: {problemType} + {solver}"); + }); + + result.WithNotParsed(errors => + { + Assert.Fail($"Parse failed for {problemType} + {solver}"); + }); + } + } + } + + #endregion + + #region Traffic Engineering - Heuristic Combinations + + /// + /// Test all TE heuristic types parse correctly. + /// + [TestMethod] + public void TestTEAllHeuristics() + { + var heuristics = new[] + { + "Pop", "DemandPinning", "ExpectedPop", "PopDp", + "ExpectedPopDp", "ParallelPop", "ParallelPopDp", "ModifiedDp", + }; + + foreach (var heuristic in heuristics) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--heuristic", heuristic, + "--solver", "Gurobi", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.TrafficEngineering, opts.ProblemType); + Assert.AreEqual(heuristic, opts.Heuristic.ToString(), $"Heuristic mismatch for {heuristic}"); + }); + + result.WithNotParsed(errors => + { + Assert.Fail($"Parse failed for heuristic: {heuristic}"); + }); + } + } + + /// + /// Test Pop heuristic with various slice counts. + /// + [TestMethod] + public void TestTEPopWithDifferentSlices() + { + var sliceCounts = new[] { 1, 2, 3, 4, 5, 10 }; + + foreach (var slices in sliceCounts) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--heuristic", "Pop", + "--slices", slices.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(slices, opts.PopSlices, $"Slices mismatch for {slices}"); + }); + } + } + + /// + /// Test DemandPinning with various thresholds. + /// + [TestMethod] + public void TestTEDemandPinningThresholds() + { + var thresholds = new[] { 0.1, 0.25, 0.5, 0.75, 1.0, 5.0, 10.0 }; + + foreach (var threshold in thresholds) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--heuristic", "DemandPinning", + "--pinthreshold", threshold.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(threshold, opts.DemandPinningThreshold, $"Threshold mismatch for {threshold}"); + }); + } + } + + /// + /// Test ModifiedDp with various max shortest path lengths. + /// + [TestMethod] + public void TestTEModifiedDpMaxPathLengths() + { + var lengths = new[] { -1, 1, 2, 3, 5, 10 }; + + foreach (var length in lengths) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--heuristic", "ModifiedDp", + "--maxshortestlen", length.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(length, opts.MaxShortestPathLen, $"MaxShortestPathLen mismatch for {length}"); + }); + } + } + + #endregion + + #region Traffic Engineering - Method Combinations + + /// + /// Test all TE method choices. + /// + [TestMethod] + public void TestTEAllMethods() + { + var methods = new[] + { + "Direct", "Search", "FindFeas", "Random", "HillClimber", "SimulatedAnnealing", + }; + + foreach (var method in methods) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--method", method, + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(method, opts.Method.ToString(), $"Method mismatch for {method}"); + }); + } + } + + /// + /// Test Search method with various confidence levels. + /// + [TestMethod] + public void TestTESearchMethodConfidenceLevels() + { + var confidences = new[] { 0.01, 0.05, 0.1, 0.2, 0.5 }; + + foreach (var confidence in confidences) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--method", "Search", + "--confidence", confidence.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(confidence, opts.Confidencelvl, $"Confidence mismatch for {confidence}"); + }); + } + } + + /// + /// Test Search/FindFeas methods with various starting gaps. + /// + [TestMethod] + public void TestTEStartingGaps() + { + var gaps = new[] { 1.0, 5.0, 10.0, 20.0, 50.0, 100.0 }; + + foreach (var gap in gaps) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--method", "FindFeas", + "--startinggap", gap.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(gap, opts.StartingGap, $"StartingGap mismatch for {gap}"); + }); + } + } + + /// + /// Test Random method with various trial counts. + /// + [TestMethod] + public void TestTERandomMethodTrials() + { + var trials = new[] { 1, 5, 10, 50, 100 }; + + foreach (var trial in trials) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--method", "Random", + "--num", trial.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(trial, opts.NumRandom, $"NumRandom mismatch for {trial}"); + }); + } + } + + /// + /// Test HillClimber with various neighbor counts. + /// + [TestMethod] + public void TestTEHillClimberNeighbors() + { + var neighbors = new[] { 1, 2, 5, 10, 20 }; + + foreach (var neighbor in neighbors) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--method", "HillClimber", + "--neighbors", neighbor.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(neighbor, opts.NumNeighbors, $"NumNeighbors mismatch for {neighbor}"); + }); + } + } + + /// + /// Test SimulatedAnnealing temperature parameters. + /// + [TestMethod] + public void TestTESimulatedAnnealingParams() + { + var initTemps = new[] { 0.5, 1.0, 2.0, 10.0 }; + var lambdas = new[] { 0.5, 0.9, 0.95, 0.99 }; + + foreach (var temp in initTemps) + { + foreach (var lambda in lambdas) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--method", "SimulatedAnnealing", + "--inittmp", temp.ToString(), + "--lambda", lambda.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(temp, opts.InitTmp, $"InitTmp mismatch for {temp}"); + Assert.AreEqual(lambda, opts.TmpDecreaseFactor, $"Lambda mismatch for {lambda}"); + }); + } + } + } + + #endregion + + #region Traffic Engineering - Inner Encoding + + /// + /// Test KKT and PrimalDual inner encodings. + /// + [TestMethod] + public void TestTEInnerEncodings() + { + var encodings = new[] { "KKT", "PrimalDual" }; + + foreach (var encoding in encodings) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--innerencoding", encoding, + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + var expected = encoding == "KKT" + ? InnerRewriteMethodChoice.KKT + : InnerRewriteMethodChoice.PrimalDual; + Assert.AreEqual(expected, opts.InnerEncoding, $"InnerEncoding mismatch for {encoding}"); + }); + } + } + + /// + /// Test PrimalDual with various demand lists. + /// + [TestMethod] + public void TestTEPrimalDualDemandLists() + { + var demandLists = new[] + { + "0", + "0,5", + "0,5,10", + "0,5,10,15,20", + "0,1,2,3,4,5,6,7,8,9,10", + }; + + foreach (var demandList in demandLists) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--innerencoding", "PrimalDual", + "--demandlist", demandList, + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(demandList, opts.DemandList, $"DemandList mismatch for {demandList}"); + + // Verify parsing + var parsed = opts.DemandList.Split(',').Select(double.Parse).ToList(); + var expectedCount = demandList.Split(',').Length; + Assert.AreEqual(expectedCount, parsed.Count); + }); + } + } + + #endregion + + #region Traffic Engineering - Clustering Options + + /// + /// Test clustering enable/disable. + /// + [TestMethod] + public void TestTEClusteringToggle() + { + // Clustering disabled (default) + var args1 = new string[] { "--problemType", "TrafficEngineering" }; + CommandLine.Parser.Default.ParseArguments(args1).WithParsed(opts => + { + Assert.AreEqual(false, opts.EnableClustering); + }); + + // Clustering enabled + var args2 = new string[] + { + "--problemType", "TrafficEngineering", + "--enableclustering", "true", + }; + CommandLine.Parser.Default.ParseArguments(args2).WithParsed(opts => + { + Assert.AreEqual(true, opts.EnableClustering); + }); + } + + /// + /// Test clustering with various cluster counts. + /// + [TestMethod] + public void TestTEClusteringNumClusters() + { + var clusterCounts = new[] { 2, 3, 4, 5, 10 }; + + foreach (var count in clusterCounts) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--enableclustering", "true", + "--numclusters", count.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(count, opts.NumClusters, $"NumClusters mismatch for {count}"); + }); + } + } + + /// + /// Test clustering versions. + /// + [TestMethod] + public void TestTEClusteringVersions() + { + var versions = new[] { 1, 2, 3 }; + + foreach (var version in versions) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--enableclustering", "true", + "--clusterversion", version.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(version, opts.ClusterVersion, $"ClusterVersion mismatch for {version}"); + }); + } + } + + #endregion + + #region Traffic Engineering - Path Options + + /// + /// Test various path counts. + /// + [TestMethod] + public void TestTEPathCounts() + { + var pathCounts = new[] { 1, 2, 3, 4, 5 }; + + foreach (var paths in pathCounts) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--paths", paths.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(paths, opts.Paths, $"Paths mismatch for {paths}"); + }); + } + } + + #endregion + + #region Traffic Engineering - Demand Constraints + + /// + /// Test demand upper bound values. + /// + [TestMethod] + public void TestTEDemandUpperBounds() + { + var bounds = new[] { -1.0, 10.0, 50.0, 100.0, 1000.0 }; + + foreach (var bound in bounds) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--demandub", bound.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(bound, opts.DemandUB, $"DemandUB mismatch for {bound}"); + }); + } + } + + /// + /// Test max density values. + /// + [TestMethod] + public void TestTEMaxDensity() + { + var densities = new[] { 0.1, 0.25, 0.5, 0.75, 1.0 }; + + foreach (var density in densities) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--maxdensity", density.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(density, opts.MaxDensity, $"MaxDensity mismatch for {density}"); + }); + } + } + + #endregion + + #region Bin Packing - FFD Method Combinations + + /// + /// Test all FFD methods with various bin/item counts. + /// + [TestMethod] + public void TestBPAllFFMethodsWithSizes() + { + var ffMethods = new[] { "FF", "FFDSum", "FFDProd", "FFDDiv" }; + var configs = new[] + { + (bins: 4, items: 6, optimal: 2), + (bins: 6, items: 9, optimal: 3), + (bins: 8, items: 12, optimal: 4), + (bins: 10, items: 15, optimal: 5), + }; + + foreach (var method in ffMethods) + { + foreach (var config in configs) + { + var args = new string[] + { + "--problemType", "BinPacking", + "--ffMethod", method, + "--numBins", config.bins.ToString(), + "--numDemands", config.items.ToString(), + "--optimalBins", config.optimal.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(config.bins, opts.NumBins); + Assert.AreEqual(config.items, opts.NumDemands); + Assert.AreEqual(config.optimal, opts.OptimalBins); + }); + + result.WithNotParsed(errors => + { + Assert.Fail($"Parse failed for {method} with config ({config.bins}, {config.items}, {config.optimal})"); + }); + } + } + } + + /// + /// Test BinPacking with various dimension counts. + /// + [TestMethod] + public void TestBPDimensions() + { + var dimensions = new[] { 1, 2, 3, 4, 5 }; + + foreach (var dim in dimensions) + { + // Build capacity string + var capacities = string.Join(",", Enumerable.Repeat("1.00001", dim)); + + var args = new string[] + { + "--problemType", "BinPacking", + "--numDimensions", dim.ToString(), + "--binCapacity", capacities, + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(dim, opts.NumDimensions, $"NumDimensions mismatch for {dim}"); + + var parsedCapacities = opts.BinCapacity.Split(',').Select(double.Parse).ToList(); + Assert.AreEqual(dim, parsedCapacities.Count, $"Capacity count mismatch for {dim}"); + }); + } + } + + /// + /// Test BinPacking with various bin capacities. + /// + [TestMethod] + public void TestBPBinCapacities() + { + var capacityConfigs = new[] + { + "1.0,1.0", + "1.00001,1.00001", + "1.5,1.5", + "2.0,1.0", + "1.0,2.0,1.5", + }; + + foreach (var capacities in capacityConfigs) + { + var args = new string[] + { + "--problemType", "BinPacking", + "--binCapacity", capacities, + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(capacities, opts.BinCapacity, $"BinCapacity mismatch for {capacities}"); + }); + } + } + + /// + /// Test BinPacking symmetry breaking toggle. + /// + [TestMethod] + public void TestBPSymmetryBreaking() + { + // Default (false) + var args1 = new string[] { "--problemType", "BinPacking" }; + CommandLine.Parser.Default.ParseArguments(args1).WithParsed(opts => + { + Assert.AreEqual(false, opts.BreakSymmetry); + }); + + // Enabled + var args2 = new string[] { "--problemType", "BinPacking", "--breakSymmetry", "true" }; + CommandLine.Parser.Default.ParseArguments(args2).WithParsed(opts => + { + Assert.AreEqual(true, opts.BreakSymmetry); + }); + } + + #endregion + + #region PIFO - Parameter Combinations + + /// + /// Test PIFO with various packet counts. + /// + [TestMethod] + public void TestPIFOPacketCounts() + { + var packetCounts = new[] { 10, 12, 15, 18, 20, 24 }; + + foreach (var packets in packetCounts) + { + var args = new string[] + { + "--problemType", "PIFO", + "--numPackets", packets.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(packets, opts.NumPackets, $"NumPackets mismatch for {packets}"); + }); + } + } + + /// + /// Test PIFO with various rank values. + /// + [TestMethod] + public void TestPIFORankValues() + { + var ranks = new[] { 4, 6, 8, 10, 12, 16 }; + + foreach (var rank in ranks) + { + var args = new string[] + { + "--problemType", "PIFO", + "--maxRank", rank.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(rank, opts.MaxRank, $"MaxRank mismatch for {rank}"); + }); + } + } + + /// + /// Test PIFO with various queue configurations. + /// + [TestMethod] + public void TestPIFOQueueConfigs() + { + var configs = new[] + { + (queues: 2, queueSize: 8), + (queues: 4, queueSize: 12), + (queues: 6, queueSize: 15), + (queues: 8, queueSize: 20), + }; + + foreach (var config in configs) + { + var args = new string[] + { + "--problemType", "PIFO", + "--numQueues", config.queues.ToString(), + "--maxQueueSize", config.queueSize.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(config.queues, opts.NumQueues); + Assert.AreEqual(config.queueSize, opts.MaxQueueSize); + }); + } + } + + /// + /// Test PIFO AIFO parameters. + /// + [TestMethod] + public void TestPIFOAIFOParams() + { + var configs = new[] + { + (windowSize: 8, burstParam: 0.05), + (windowSize: 12, burstParam: 0.1), + (windowSize: 15, burstParam: 0.15), + (windowSize: 20, burstParam: 0.2), + }; + + foreach (var config in configs) + { + var args = new string[] + { + "--problemType", "PIFO", + "--windowSize", config.windowSize.ToString(), + "--burstParam", config.burstParam.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(config.windowSize, opts.WindowSize); + Assert.AreEqual(config.burstParam, opts.BurstParam); + }); + } + } + + #endregion + + #region Failure Analysis - Parameter Combinations + + /// + /// Test FailureAnalysis with various failure counts. + /// + [TestMethod] + public void TestFAFailureCounts() + { + var failureCounts = new[] { 1, 2, 3, 4, 5 }; + + foreach (var failures in failureCounts) + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--maxNumFailures", failures.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(failures, opts.MaxNumFailures, $"MaxNumFailures mismatch for {failures}"); + }); + } + } + + /// + /// Test FailureAnalysis with various extra path counts. + /// + [TestMethod] + public void TestFAExtraPaths() + { + var extraPaths = new[] { 1, 2, 3, 4, 5 }; + + foreach (var paths in extraPaths) + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--numExtraPaths", paths.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(paths, opts.NumExtraPaths, $"NumExtraPaths mismatch for {paths}"); + }); + } + } + + /// + /// Test FailureAnalysis failure probability thresholds. + /// + [TestMethod] + public void TestFAFailureProbThresholds() + { + var thresholds = new[] { 0.0, 0.1, 0.25, 0.5, 0.75, 1.0 }; + + foreach (var threshold in thresholds) + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--failureProbThreshold", threshold.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(threshold, opts.FailureProbThreshold, $"FailureProbThreshold mismatch for {threshold}"); + }); + } + } + + /// + /// Test FailureAnalysis scenario probability thresholds. + /// + [TestMethod] + public void TestFAScenarioProbThresholds() + { + var thresholds = new[] { 0.0, 0.01, 0.05, 0.1 }; + + foreach (var threshold in thresholds) + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--scenarioProbThreshold", threshold.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(threshold, opts.ScenarioProbThreshold, $"ScenarioProbThreshold mismatch for {threshold}"); + }); + } + } + + /// + /// Test FailureAnalysis topology toggle. + /// + [TestMethod] + public void TestFATopologyToggle() + { + // With flag = true + var args1 = new string[] + { + "--problemType", "FailureAnalysis", + "--useDefaultTopology", + }; + CommandLine.Parser.Default.ParseArguments(args1).WithParsed(opts => + { + Assert.AreEqual(true, opts.UseDefaultTopology); + }); + + // Without flag = false (uses default) + var args2 = new string[] + { + "--problemType", "FailureAnalysis", + "--topologyFile", "custom.json", "--useDefaultTopology", "false", + }; + CommandLine.Parser.Default.ParseArguments(args2).WithParsed(opts => + { + Assert.AreEqual(false, opts.UseDefaultTopology); + Assert.AreEqual("custom.json", opts.TopologyFile); + }); + } + + #endregion + + #region Common Options - Timeout and Threads + + /// + /// Test various timeout values. + /// + [TestMethod] + public void TestTimeoutValues() + { + var timeouts = new[] { 10.0, 60.0, 300.0, 1000.0, 3600.0 }; + + foreach (var timeout in timeouts) + { + var args = new string[] + { + "--problemType", "BinPacking", + "--timeout", timeout.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(timeout, opts.Timeout, $"Timeout mismatch for {timeout}"); + }); + } + } + + /// + /// Test various Gurobi thread counts. + /// + [TestMethod] + public void TestGurobiThreads() + { + var threadCounts = new[] { 0, 1, 2, 4, 8 }; + + foreach (var threads in threadCounts) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--gurobithreads", threads.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(threads, opts.NumGurobiThreads, $"NumGurobiThreads mismatch for {threads}"); + }); + } + } + + /// + /// Test seed values for reproducibility. + /// + [TestMethod] + public void TestSeedValues() + { + var seeds = new[] { 0, 1, 42, 12345, 999999 }; + + foreach (var seed in seeds) + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--seed", seed.ToString(), + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(seed, opts.Seed, $"Seed mismatch for {seed}"); + }); + } + } + + #endregion + + #region Common Options - Verbose and Debug + + /// + /// Test verbose flag. + /// + [TestMethod] + public void TestVerboseFlag() + { + // Without verbose + var args1 = new string[] { "--problemType", "BinPacking" }; + CommandLine.Parser.Default.ParseArguments(args1).WithParsed(opts => + { + Assert.AreEqual(false, opts.Verbose); + }); + + // With verbose + var args2 = new string[] { "--problemType", "BinPacking", "--verbose" }; + CommandLine.Parser.Default.ParseArguments(args2).WithParsed(opts => + { + Assert.AreEqual(true, opts.Verbose); + }); + + // With -v shorthand + var args3 = new string[] { "--problemType", "BinPacking", "-v" }; + CommandLine.Parser.Default.ParseArguments(args3).WithParsed(opts => + { + Assert.AreEqual(true, opts.Verbose); + }); + } + + /// + /// Test debug flag. + /// + [TestMethod] + public void TestDebugFlag() + { + // Without debug + var args1 = new string[] { "--problemType", "BinPacking" }; + CommandLine.Parser.Default.ParseArguments(args1).WithParsed(opts => + { + Assert.AreEqual(false, opts.Debug); + }); + + // With debug + var args2 = new string[] { "--problemType", "BinPacking", "--debug" }; + CommandLine.Parser.Default.ParseArguments(args2).WithParsed(opts => + { + Assert.AreEqual(true, opts.Debug); + }); + } + + #endregion + + #region Complex Combination Tests + + /// + /// Test TE with Pop heuristic and all common options. + /// + [TestMethod] + public void TestTEPopFullConfiguration() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--topologyFile", "Swan.json", + "--heuristic", "Pop", + "--solver", "Gurobi", + "--paths", "2", + "--slices", "3", + "--method", "Direct", + "--innerencoding", "KKT", + "--timeout", "300", + "--gurobithreads", "1", + "--seed", "42", + "--verbose", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.TrafficEngineering, opts.ProblemType); + Assert.AreEqual("Swan.json", opts.TopologyFile); + Assert.AreEqual(Heuristic.Pop, opts.Heuristic); + Assert.AreEqual(SolverChoice.Gurobi, opts.SolverChoice); + Assert.AreEqual(2, opts.Paths); + Assert.AreEqual(3, opts.PopSlices); + Assert.AreEqual(MethodChoice.Direct, opts.Method); + Assert.AreEqual(InnerRewriteMethodChoice.KKT, opts.InnerEncoding); + Assert.AreEqual(300, opts.Timeout); + Assert.AreEqual(1, opts.NumGurobiThreads); + Assert.AreEqual(42, opts.Seed); + Assert.AreEqual(true, opts.Verbose); + }); + + result.WithNotParsed(errors => + { + Assert.Fail("Failed to parse TE Pop full configuration"); + }); + } + + /// + /// Test TE with DemandPinning and PrimalDual. + /// + [TestMethod] + public void TestTEDemandPinningPrimalDualConfig() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--heuristic", "DemandPinning", + "--pinthreshold", "0.5", + "--innerencoding", "PrimalDual", + "--demandlist", "0,5,10,15,20", + "--paths", "3", + "--method", "Direct", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(Heuristic.DemandPinning, opts.Heuristic); + Assert.AreEqual(0.5, opts.DemandPinningThreshold); + Assert.AreEqual(InnerRewriteMethodChoice.PrimalDual, opts.InnerEncoding); + Assert.AreEqual("0,5,10,15,20", opts.DemandList); + Assert.AreEqual(3, opts.Paths); + }); + } + + /// + /// Test TE with clustering enabled. + /// + [TestMethod] + public void TestTEClusteringFullConfig() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--enableclustering", "true", + "--numclusters", "4", + "--clusterversion", "3", + "--interclustersamples", "10", + "--nodespercluster", "5", + "--numinterclusterquantization", "5", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(true, opts.EnableClustering); + Assert.AreEqual(4, opts.NumClusters); + Assert.AreEqual(3, opts.ClusterVersion); + Assert.AreEqual(10, opts.NumInterClusterSamples); + Assert.AreEqual(5, opts.NumNodesPerCluster); + Assert.AreEqual(5, opts.NumInterClusterQuantizations); + }); + } + + /// + /// Test BinPacking with all options. + /// + [TestMethod] + public void TestBPFullConfiguration() + { + var args = new string[] + { + "--problemType", "BinPacking", + "--solver", "Gurobi", + "--numBins", "8", + "--numDemands", "12", + "--numDimensions", "3", + "--binCapacity", "1.5,1.5,1.5", + "--optimalBins", "4", + "--ffMethod", "FFDProd", + "--breakSymmetry", "true", + "--timeout", "120", + "--verbose", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.BinPacking, opts.ProblemType); + Assert.AreEqual(SolverChoice.Gurobi, opts.SolverChoice); + Assert.AreEqual(8, opts.NumBins); + Assert.AreEqual(12, opts.NumDemands); + Assert.AreEqual(3, opts.NumDimensions); + Assert.AreEqual("1.5,1.5,1.5", opts.BinCapacity); + Assert.AreEqual(4, opts.OptimalBins); + Assert.AreEqual(FFDMethodChoice.FFDProd, opts.FFMethod); + Assert.AreEqual(true, opts.BreakSymmetry); + Assert.AreEqual(120, opts.Timeout); + Assert.AreEqual(true, opts.Verbose); + }); + } + + /// + /// Test PIFO with all options. + /// + [TestMethod] + public void TestPIFOFullConfiguration() + { + var args = new string[] + { + "--problemType", "PIFO", + "--solver", "Gurobi", + "--numPackets", "24", + "--maxRank", "10", + "--numQueues", "6", + "--maxQueueSize", "16", + "--windowSize", "14", + "--burstParam", "0.15", + "--timeout", "180", + "--verbose", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.PIFO, opts.ProblemType); + Assert.AreEqual(24, opts.NumPackets); + Assert.AreEqual(10, opts.MaxRank); + Assert.AreEqual(6, opts.NumQueues); + Assert.AreEqual(16, opts.MaxQueueSize); + Assert.AreEqual(14, opts.WindowSize); + Assert.AreEqual(0.15, opts.BurstParam); + Assert.AreEqual(180, opts.Timeout); + }); + } + + /// + /// Test FailureAnalysis with all options. + /// + [TestMethod] + public void TestFAFullConfiguration() + { + var args = new string[] + { + "--problemType", "FailureAnalysis", + "--solver", "Gurobi", + "--useDefaultTopology", "true", + "--maxNumFailures", "2", + "--numExtraPaths", "2", + "--demandlist", "0,5,10,15", + "--failureProbThreshold", "0.15", + "--scenarioProbThreshold", "0.01", + "--innerencoding", "PrimalDual", + "--timeout", "240", + "--verbose", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + Assert.AreEqual(ProblemType.FailureAnalysis, opts.ProblemType); + Assert.AreEqual(true, opts.UseDefaultTopology); + Assert.AreEqual(2, opts.MaxNumFailures); + Assert.AreEqual(2, opts.NumExtraPaths); + Assert.AreEqual("0,5,10,15", opts.DemandList); + Assert.AreEqual(0.15, opts.FailureProbThreshold); + Assert.AreEqual(0.01, opts.ScenarioProbThreshold); + Assert.AreEqual(InnerRewriteMethodChoice.PrimalDual, opts.InnerEncoding); + Assert.AreEqual(240, opts.Timeout); + }); + } + + #endregion + + #region Smoke Tests - Actual Runner Initialization + + /// + /// Smoke test: BinPacking runner can be initialized. + /// + [TestMethod] + public void SmokeTestBPRunnerInit() + { + var args = new string[] + { + "--problemType", "BinPacking", + "--solver", "Gurobi", + "--numBins", "4", + "--numDemands", "6", + "--numDimensions", "2", + "--optimalBins", "2", + "--timeout", "30", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + + // Parse capacities + var binCapacities = opts.BinCapacity.Split(',').Select(double.Parse).ToList(); + while (binCapacities.Count < opts.NumDimensions) + { + binCapacities.Add(1.00001); + } + + // Create components + var solver = new GurobiSOS(timeout: opts.Timeout, verbose: 0); + var bins = new Bins(opts.NumBins, binCapacities); + var optimalEncoder = new VBPOptimalEncoder( + solver, opts.NumDemands, opts.NumDimensions, BreakSymmetry: opts.BreakSymmetry); + var ffdEncoder = new FFDItemCentricEncoder( + solver, opts.NumDemands, opts.NumDimensions); + var adversarialGenerator = new VBPAdversarialInputGenerator( + bins, opts.NumDemands, opts.NumDimensions); + + Assert.IsNotNull(solver); + Assert.IsNotNull(bins); + Assert.IsNotNull(optimalEncoder); + Assert.IsNotNull(ffdEncoder); + Assert.IsNotNull(adversarialGenerator); + }); + } + + /// + /// Smoke test: PIFO runner can be initialized. + /// + [TestMethod] + public void SmokeTestPIFORunnerInit() + { + var args = new string[] + { + "--problemType", "PIFO", + "--solver", "Gurobi", + "--numPackets", "18", + "--maxRank", "8", + "--numQueues", "4", + "--maxQueueSize", "12", + "--timeout", "1000", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + + var solver = new GurobiSOS(timeout: opts.Timeout, verbose: 0); + + var spPifoEncoder = new SPPIFOWithDropAvgDelayEncoder( + solver, opts.NumPackets, opts.NumQueues, opts.MaxRank, opts.MaxQueueSize); + + var aifoEncoder = new AIFOAvgDelayEncoder( + solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize, opts.WindowSize, opts.BurstParam); + + var adversarialGenerator = new PIFOAdversarialInputGenerator( + opts.NumPackets, opts.MaxRank); + + Assert.IsNotNull(solver); + Assert.IsNotNull(spPifoEncoder); + Assert.IsNotNull(aifoEncoder); + Assert.IsNotNull(adversarialGenerator); + }); + } + + /// + /// Smoke test: TE components can be initialized. + /// + [TestMethod] + public void SmokeTestTERunnerInit() + { + var args = new string[] + { + "--problemType", "TrafficEngineering", + "--heuristic", "Pop", + "--solver", "Gurobi", + "--paths", "2", + "--slices", "2", + "--timeout", "30", + }; + + var result = CommandLine.Parser.Default.ParseArguments(args); + + result.WithParsed(opts => + { + CliOptions.Instance = opts; + + var solver = new GurobiSOS(timeout: opts.Timeout, verbose: 0); + var optimalEncoder = new TEMaxFlowOptimalEncoder(solver, opts.Paths); + + // Create simple test topology + var topology = new Topology(); + topology.AddNode("a"); + topology.AddNode("b"); + topology.AddNode("c"); + topology.AddEdge("a", "b", capacity: 10); + topology.AddEdge("b", "c", capacity: 10); + topology.AddEdge("a", "c", capacity: 5); + + var partition = topology.RandomPartition(opts.PopSlices); + var popEncoder = new PopEncoder( + solver, maxNumPaths: opts.Paths, numPartitions: opts.PopSlices, demandPartitions: partition); + + var adversarialGenerator = new TEAdversarialInputGenerator( + topology, opts.Paths); + + Assert.IsNotNull(solver); + Assert.IsNotNull(optimalEncoder); + Assert.IsNotNull(popEncoder); + Assert.IsNotNull(adversarialGenerator); + }); + } + + #endregion + } +} \ No newline at end of file From 21503135d3cc310ef1f4119e50e75101e0071446 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Mon, 1 Dec 2025 10:06:27 -0800 Subject: [PATCH 06/11] fix for TE topology path to work in both VScode and CLI. --- .vscode/settings.json | 4 +++- MetaOptimize.Cli/CliOptions.cs | 4 ++-- MetaOptimize.Cli/cliUtils.cs | 38 +++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b51f72738..d0c8d23d3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { "dotnet.defaultSolution": "MetaOptimize.sln", - "python.formatting.provider": "none" + "debugpy.debugJustMyCode": false, + "jupyter.debugJustMyCode": false, + "csharp.debug.justMyCode": false } \ No newline at end of file diff --git a/MetaOptimize.Cli/CliOptions.cs b/MetaOptimize.Cli/CliOptions.cs index 9937b0920..dc6df7d87 100644 --- a/MetaOptimize.Cli/CliOptions.cs +++ b/MetaOptimize.Cli/CliOptions.cs @@ -37,7 +37,7 @@ public class CliOptions /// The problem type to solve. /// Determines which runner is invoked and which parameters are relevant. /// - [Option("problemType", Default = ProblemType.TrafficEngineering, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] + [Option("problemType", Default = ProblemType.BinPacking, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] public ProblemType ProblemType { get; set; } /// @@ -83,7 +83,7 @@ public class CliOptions /// "links": [{"source": "a", "target": "b", "capacity": 10}, ...] /// }. /// - [Option('f', "topologyFile", Default = "..\\MetaOpt\\Topologies\\simple.json", HelpText = "The location of the topology file (JSON format)")] + [Option('f', "topologyFile", Default = "..\\Topologies\\simple.json", HelpText = "The location of the topology file (JSON format)")] public string TopologyFile { get; set; } /// diff --git a/MetaOptimize.Cli/cliUtils.cs b/MetaOptimize.Cli/cliUtils.cs index 9d45e254a..79333f527 100644 --- a/MetaOptimize.Cli/cliUtils.cs +++ b/MetaOptimize.Cli/cliUtils.cs @@ -219,7 +219,8 @@ public static (IEncoder, IDictionary<(string, string), int>, IL public static (Topology, List) getTopology(string topologyFile, string pathFile, double downScaleFactor, bool enableClustering, int numClusters, string clusterDir, bool verbose) { - Topology topology = Parser.ReadTopologyJson(topologyFile, pathFile, scaleFactor: downScaleFactor); + string topoPath = ResolveTopologyPath(topologyFile); + Topology topology = Parser.ReadTopologyJson(topoPath, pathFile, scaleFactor: downScaleFactor); List clusters = new List(); if (enableClustering) { @@ -233,6 +234,41 @@ public static (Topology, List) getTopology(string topologyFile, string return (topology, clusters); } + /// + /// It makes sure the topology file path works for both VSCode and CLI run modes. + /// + /// + /// Valid topology path. + private static string ResolveTopologyPath(string topologyFile) + { + // If absolute path or file exists, use as-is + if (Path.IsPathRooted(topologyFile) || File.Exists(topologyFile)) + { + return topologyFile; + } + + // Try common locations + var candidates = new[] + { + topologyFile, + Path.Combine("Topologies", Path.GetFileName(topologyFile)), + Path.Combine("..", "Topologies", Path.GetFileName(topologyFile)), + Path.Combine("..", "MetaOpt", "Topologies", Path.GetFileName(topologyFile)), + }; + + foreach (var candidate in candidates) + { + if (File.Exists(candidate)) + { + Console.WriteLine($"Found topology at: {Path.GetFullPath(candidate)}"); + return candidate; + } + } + + // Fall back to original (will fail with clear error) + return topologyFile; + } + /// /// Get the method from MetaOpt to find adversarial inputs. /// From 3968c3c26d0fbeab8651e415cf463a45473b4329 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Wed, 17 Dec 2025 10:01:49 -0800 Subject: [PATCH 07/11] added support for OrTools in TERunner. Added CLI support for PIFO encoder selection as requested. Users can now specify --pifoMethod1 and --pifoMethod2 (PIFO, SPPIFO, AIFO, ModifiedSPPIFO) along with --considerPktDrop to select the appropriate encoder. Validation enforces that AIFO requires packet drop enabled, and ModifiedSPPIFO requires packet drop disabled. --- MetaOptimize.Cli/CliOptions.cs | 67 +++++++++++++++++++++++++++++++++- MetaOptimize.Cli/PIFORunner.cs | 50 +++++++++++++++++++++---- MetaOptimize.Cli/TERunner.cs | 4 ++ 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/MetaOptimize.Cli/CliOptions.cs b/MetaOptimize.Cli/CliOptions.cs index dc6df7d87..0c77147d8 100644 --- a/MetaOptimize.Cli/CliOptions.cs +++ b/MetaOptimize.Cli/CliOptions.cs @@ -37,7 +37,7 @@ public class CliOptions /// The problem type to solve. /// Determines which runner is invoked and which parameters are relevant. /// - [Option("problemType", Default = ProblemType.BinPacking, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] + [Option("problemType", Default = ProblemType.PIFO, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] public ProblemType ProblemType { get; set; } /// @@ -514,6 +514,36 @@ public class CliOptions #region PIFO Options + /// + /// First PIFO method (h1 encoder). + /// + [Option("pifoMethod1", Default = PIFOMethodChoice.SPPIFO, HelpText = "First PIFO method: PIFO, SPPIFO, AIFO, ModifiedSPPIFO")] + public PIFOMethodChoice PIFOMethod1 { get; set; } + + /// + /// Second PIFO method (h2 encoder). + /// + [Option("pifoMethod2", Default = PIFOMethodChoice.AIFO, HelpText = "Second PIFO method: PIFO, SPPIFO, AIFO, ModifiedSPPIFO")] + public PIFOMethodChoice PIFOMethod2 { get; set; } + + /// + /// Whether to consider packet drop in PIFO scheduling. + /// + [Option("considerPktDrop", Default = true, HelpText = "Consider packet drop (true/false)")] + public bool ConsiderPktDrop { get; set; } + + /// + /// Split queue parameter for ModifiedSPPIFO. + /// + [Option("splitQueue", Default = 4, HelpText = "Split queue count for ModifiedSPPIFO")] + public int SplitQueue { get; set; } + + /// + /// Split rank parameter for ModifiedSPPIFO. + /// + [Option("splitRank", Default = 100, HelpText = "Split rank threshold for ModifiedSPPIFO")] + public int SplitRank { get; set; } + /// /// Number of packets in the sequence. /// More packets = larger adversarial search space. @@ -603,6 +633,7 @@ public class CliOptions public double ScenarioProbThreshold { get; set; } #endregion + } #region Enums @@ -695,6 +726,12 @@ public enum SolverChoice /// Based on Z3, open source, good for constraint satisfaction. /// Zen, + + /// + /// OrTools solver (SMT - Satisfiability Modulo Theories). + /// Based on Z3, open source, good for constraint satisfaction. + /// + OrTools, } /// @@ -739,5 +776,33 @@ public enum MethodChoice SimulatedAnnealing, } + /// + /// PIFO method choices for scheduling algorithms. + /// + public enum PIFOMethodChoice + { + /// + /// Push-In First-Out: Packets are inserted based on rank and dequeued from the head. + /// + PIFO, + + /// + /// Strict Priority PIFO: Uses multiple priority queues to approximate PIFO behavior. + /// + SPPIFO, + + /// + /// Approximate In-Order First-Out: Uses shallow buffers with admission control. + /// Only valid when ConsiderPktDrop is true. + /// + AIFO, + + /// + /// Modified SP-PIFO: Variant with configurable queue splitting via splitQueue and splitRank parameters. + /// Only valid when ConsiderPktDrop is false. + /// + ModifiedSPPIFO, + } + #endregion } \ No newline at end of file diff --git a/MetaOptimize.Cli/PIFORunner.cs b/MetaOptimize.Cli/PIFORunner.cs index e3e9451c0..f6aa1aee4 100644 --- a/MetaOptimize.Cli/PIFORunner.cs +++ b/MetaOptimize.Cli/PIFORunner.cs @@ -114,6 +114,43 @@ private static (int optimal, int heuristic) ComputeInversions( return (numInvOpt, numInvHeu); } + /// + /// Creates the appropriate PIFO encoder based on method and packet drop settings. + /// + /// The PIFO scheduling method to use. + /// The Gurobi solver instance. + /// CLI options containing encoder parameters. + /// Configured encoder implementing IEncoder interface. + /// + /// Thrown when AIFO is used without packet drop, or ModifiedSPPIFO is used with packet drop. + /// + private static IEncoder CreateEncoder(PIFOMethodChoice method, GurobiSOS solver, CliOptions opts) + { + return method switch + { + PIFOMethodChoice.PIFO => opts.ConsiderPktDrop + ? new PIFOWithDropAvgDelayEncoder(solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize) + : new PIFOAvgDelayOptimalEncoder(solver, opts.NumPackets, opts.MaxRank), + + PIFOMethodChoice.SPPIFO => opts.ConsiderPktDrop + ? new SPPIFOWithDropAvgDelayEncoder(solver, opts.NumPackets, opts.NumQueues, opts.MaxRank, opts.MaxQueueSize) + : new SPPIFOAvgDelayEncoder(solver, opts.NumPackets, opts.NumQueues, opts.MaxRank), + + PIFOMethodChoice.AIFO => opts.ConsiderPktDrop + ? new AIFOAvgDelayEncoder(solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize, opts.WindowSize, opts.BurstParam) + : throw new ArgumentException( + "AIFO only works on shallow buffers and decides whether to admit or drop the packet. " + + "As a result, it does not apply to cases where we do not want packet drop."), + + PIFOMethodChoice.ModifiedSPPIFO => !opts.ConsiderPktDrop + ? new ModifiedSPPIFOAvgDelayEncoder(solver, opts.NumPackets, opts.SplitQueue, opts.NumQueues, opts.SplitRank, opts.MaxRank) + : throw new ArgumentException( + "ModifiedSPPIFO does not support packet drop yet."), + + _ => throw new ArgumentException($"Unknown PIFO method: {method}") + }; + } + /// /// Runs PIFO packet scheduling adversarial optimization. /// @@ -137,13 +174,12 @@ public static void Run(CliOptions opts) var solver = new GurobiSOS(verbose: Convert.ToInt32(opts.Verbose), timeout: opts.Timeout); - // Create SP-PIFO encoder (optimal baseline) - var spPifoEncoder = new SPPIFOWithDropAvgDelayEncoder( - solver, opts.NumPackets, opts.NumQueues, opts.MaxRank, opts.MaxQueueSize); + Console.WriteLine($"PIFO Method 1: {opts.PIFOMethod1} (ConsiderPktDrop={opts.ConsiderPktDrop})"); + Console.WriteLine($"PIFO Method 2: {opts.PIFOMethod2} (ConsiderPktDrop={opts.ConsiderPktDrop})"); - // Create AIFO encoder (heuristic to evaluate) - var aifoEncoder = new AIFOAvgDelayEncoder( - solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize, opts.WindowSize, opts.BurstParam); + // Create encoders based on CLI options + var h1 = CreateEncoder(opts.PIFOMethod1, solver, opts); + var h2 = CreateEncoder(opts.PIFOMethod2, solver, opts); // Create adversarial generator var adversarialGenerator = new PIFOAdversarialInputGenerator( @@ -153,7 +189,7 @@ public static void Run(CliOptions opts) // Find worst-case packet sequence var (optimalSolution, heuristicSolution) = adversarialGenerator.MaximizeOptimalityGap( - spPifoEncoder, aifoEncoder, verbose: opts.Verbose); + h1, h2, verbose: opts.Verbose); timer.Stop(); diff --git a/MetaOptimize.Cli/TERunner.cs b/MetaOptimize.Cli/TERunner.cs index 916a8891a..758c70d34 100644 --- a/MetaOptimize.Cli/TERunner.cs +++ b/MetaOptimize.Cli/TERunner.cs @@ -74,6 +74,10 @@ private static void GetSolverAndRunNetwork(Topology topology, List clu switch (opts.SolverChoice) { + case SolverChoice.OrTools: + RunNetwork(new ORToolsSolver(), topology, clusters); + break; + case SolverChoice.Zen: RunNetwork(new SolverZen(), topology, clusters); break; From daf1837171c6b8e8022b061e59bdf6f55914fe3f Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Wed, 17 Dec 2025 10:07:34 -0800 Subject: [PATCH 08/11] updated the OrTools xml doc. --- MetaOptimize.Cli/CliOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MetaOptimize.Cli/CliOptions.cs b/MetaOptimize.Cli/CliOptions.cs index 0c77147d8..7019ecd5d 100644 --- a/MetaOptimize.Cli/CliOptions.cs +++ b/MetaOptimize.Cli/CliOptions.cs @@ -728,8 +728,8 @@ public enum SolverChoice Zen, /// - /// OrTools solver (SMT - Satisfiability Modulo Theories). - /// Based on Z3, open source, good for constraint satisfaction. + /// OR-Tools solver (CP-SAT - Constraint Programming with SAT). + /// Google's open-source optimization suite, good for combinatorial optimization. /// OrTools, } From 0365d5c6db68d61eecf2a524c86ff16b51737758 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Thu, 18 Dec 2025 09:17:58 -0800 Subject: [PATCH 09/11] PR comments answers and solutions. --- Dockerfile | 88 +++++++++++++++++++++++ MetaOptimize.Cli/BPRunner.cs | 3 + MetaOptimize.Cli/CliOptions.cs | 7 +- MetaOptimize.Cli/FailureAnalysisRunner.cs | 28 ++++---- MetaOptimize.Cli/PIFORunner.cs | 65 +++++++++++------ README.md | 70 ++++++++++++++++++ 6 files changed, 223 insertions(+), 38 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..b4a2ca90f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,88 @@ +# ============================================================================= +# MetaOptimize Dockerfile +# ============================================================================= + +# Global ARGs +ARG BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0-azurelinux3.0 +ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/runtime:8.0-azurelinux3.0 + +# Build stage +FROM ${BASE_IMAGE} AS build + +WORKDIR /src + +# Copy build configuration files first (for layer caching) +COPY Directory.Build.props ./ +COPY Directory.Packages.props ./ + +# Copy solution and project files +COPY *.sln ./ +COPY MetaOptimize/*.csproj MetaOptimize/ +COPY MetaOptimize.Cli/*.csproj MetaOptimize.Cli/ +COPY MetaOptimize.Test/*.csproj MetaOptimize.Test/ + +# Copy StyleCop props +COPY .stylecop/ .stylecop/ + +# Restore NuGet packages +RUN dotnet restore -r linux-x64 + +# Copy all source code +COPY . . + +# Build and publish the CLI project in Release mode +RUN dotnet publish MetaOptimize.Cli/MetaOptimize.Cli.csproj \ + -c Release \ + -r linux-x64 \ + --self-contained false \ + -o /app/publish \ + --no-restore + +# ============================================================================= +# Runtime stage +# ============================================================================= +FROM ${RUNTIME_IMAGE} AS runtime + +# Install required packages +RUN tdnf install -y \ + ca-certificates \ + curl \ + tar \ + gzip \ + libstdc++ \ + && tdnf clean all + +# Gurobi version +ARG GUROBI_VERSION=11.0.3 + +# Download and install Gurobi +RUN curl -L "https://packages.gurobi.com/11.0/gurobi${GUROBI_VERSION}_linux64.tar.gz" \ + -o /tmp/gurobi.tar.gz \ + && tar -xzf /tmp/gurobi.tar.gz -C /opt \ + && rm /tmp/gurobi.tar.gz \ + && mv /opt/gurobi1103 /opt/gurobi + +# Set Gurobi environment variables +ENV GUROBI_HOME=/opt/gurobi +ENV PATH="${GUROBI_HOME}/bin:${PATH}" +ENV LD_LIBRARY_PATH="${GUROBI_HOME}/lib" + +# Create application directories +WORKDIR /app +RUN mkdir -p /app/Topologies /app/output /app/licenses + +# Copy published application +COPY --from=build /app/publish . + +# Copy topology files +COPY Topologies/ /app/Topologies/ + +# Environment variables +ENV GUROBI_THREADS=1 + +# Volume mounts +VOLUME ["/app/Topologies", "/app/output", "/app/licenses"] + +# Entry point +ENTRYPOINT ["dotnet", "MetaOptimize.Cli.dll"] +CMD ["--help"] \ No newline at end of file diff --git a/MetaOptimize.Cli/BPRunner.cs b/MetaOptimize.Cli/BPRunner.cs index cad2e011b..7ad01873f 100644 --- a/MetaOptimize.Cli/BPRunner.cs +++ b/MetaOptimize.Cli/BPRunner.cs @@ -34,6 +34,9 @@ public static void Run(CliOptions opts) { switch (opts.SolverChoice) { + case SolverChoice.OrTools: + RunBinPacking(new ORToolsSolver(), opts); + break; case SolverChoice.Zen: RunBinPacking(new SolverZen(), opts); break; diff --git a/MetaOptimize.Cli/CliOptions.cs b/MetaOptimize.Cli/CliOptions.cs index 7019ecd5d..fff5831a4 100644 --- a/MetaOptimize.Cli/CliOptions.cs +++ b/MetaOptimize.Cli/CliOptions.cs @@ -37,14 +37,14 @@ public class CliOptions /// The problem type to solve. /// Determines which runner is invoked and which parameters are relevant. /// - [Option("problemType", Default = ProblemType.PIFO, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] + [Option("problemType", Default = ProblemType.FailureAnalysis, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] public ProblemType ProblemType { get; set; } /// /// The solver to use for optimization. /// Gurobi uses MIP (Mixed Integer Programming), Zen uses SMT (Satisfiability Modulo Theories). /// - [Option('c', "solver", Default = SolverChoice.Gurobi, HelpText = "The solver to use (Gurobi | Zen)")] + [Option('c', "solver", Default = SolverChoice.OrTools, HelpText = "The solver to use (Gurobi | Zen)")] public SolverChoice SolverChoice { get; set; } /// @@ -499,6 +499,9 @@ public class CliOptions /// Reduces search space by eliminating equivalent solutions. /// May speed up optimization but could miss some adversarial inputs. /// + /// + /// If enabled, we leverage symmetry to reduce the optimization size and enhance the scalability of optimal bin packing. + /// public bool BreakSymmetry => string.Equals(BreakSymmetryStr, "true", StringComparison.OrdinalIgnoreCase); diff --git a/MetaOptimize.Cli/FailureAnalysisRunner.cs b/MetaOptimize.Cli/FailureAnalysisRunner.cs index aafbacdad..3af6eae03 100644 --- a/MetaOptimize.Cli/FailureAnalysisRunner.cs +++ b/MetaOptimize.Cli/FailureAnalysisRunner.cs @@ -5,6 +5,7 @@ namespace MetaOptimize.Cli { using System.Diagnostics; + using Google.OrTools.LinearSolver; using Gurobi; using MetaOptimize.FailureAnalysis; using ZenLib; @@ -33,6 +34,10 @@ public static void Run(CliOptions opts) { switch (opts.SolverChoice) { + case SolverChoice.OrTools: + FailureAnalysisRunnerImpl.CreateSolver = () => new ORToolsSolver(); + FailureAnalysisRunnerImpl.Run(opts); + break; case SolverChoice.Gurobi: FailureAnalysisRunnerImpl.CreateSolver = () => new GurobiSOS(verbose: Convert.ToInt32(opts.Verbose), timeout: opts.Timeout); @@ -101,19 +106,6 @@ private static Topology CreateDefaultFailureTopology() return topology; } - /// - /// Reads topology from a JSON file. - /// - /// Path to the topology JSON file. - /// Loaded topology (currently returns empty topology - TODO). - private static Topology ReadTopologyFromFile(string filePath) - { - Console.WriteLine($"Loading topology from: {filePath}"); - // TODO: Implement proper JSON topology loading matching TERunner format - var topology = new Topology(); - return topology; - } - /// /// Runs failure analysis adversarial optimization. /// @@ -134,6 +126,7 @@ internal static void Run(CliOptions opts) // Load or create topology Topology topology; + List clusters = null; if (opts.UseDefaultTopology) { topology = CreateDefaultFailureTopology(); @@ -141,7 +134,14 @@ internal static void Run(CliOptions opts) } else { - topology = ReadTopologyFromFile(opts.TopologyFile); + (topology, clusters) = CliUtils.getTopology( + opts.TopologyFile, + opts.PathFile, + opts.DownScaleFactor, + opts.EnableClustering, + opts.NumClusters, + opts.ClusterDir, + opts.Verbose); Console.WriteLine($"Loaded topology from: {opts.TopologyFile}"); } diff --git a/MetaOptimize.Cli/PIFORunner.cs b/MetaOptimize.Cli/PIFORunner.cs index f6aa1aee4..77b28c5c6 100644 --- a/MetaOptimize.Cli/PIFORunner.cs +++ b/MetaOptimize.Cli/PIFORunner.cs @@ -5,7 +5,6 @@ namespace MetaOptimize.Cli { using System.Diagnostics; - using Gurobi; /// /// Runner for PIFO (Push-In First-Out) packet scheduling adversarial optimization. @@ -118,32 +117,35 @@ private static (int optimal, int heuristic) ComputeInversions( /// Creates the appropriate PIFO encoder based on method and packet drop settings. /// /// The PIFO scheduling method to use. - /// The Gurobi solver instance. + /// The solver instance. /// CLI options containing encoder parameters. /// Configured encoder implementing IEncoder interface. /// /// Thrown when AIFO is used without packet drop, or ModifiedSPPIFO is used with packet drop. /// - private static IEncoder CreateEncoder(PIFOMethodChoice method, GurobiSOS solver, CliOptions opts) + private static IEncoder CreateEncoder( + PIFOMethodChoice method, + ISolver solver, + CliOptions opts) { return method switch { PIFOMethodChoice.PIFO => opts.ConsiderPktDrop - ? new PIFOWithDropAvgDelayEncoder(solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize) - : new PIFOAvgDelayOptimalEncoder(solver, opts.NumPackets, opts.MaxRank), + ? new PIFOWithDropAvgDelayEncoder(solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize) + : new PIFOAvgDelayOptimalEncoder(solver, opts.NumPackets, opts.MaxRank), PIFOMethodChoice.SPPIFO => opts.ConsiderPktDrop - ? new SPPIFOWithDropAvgDelayEncoder(solver, opts.NumPackets, opts.NumQueues, opts.MaxRank, opts.MaxQueueSize) - : new SPPIFOAvgDelayEncoder(solver, opts.NumPackets, opts.NumQueues, opts.MaxRank), + ? new SPPIFOWithDropAvgDelayEncoder(solver, opts.NumPackets, opts.NumQueues, opts.MaxRank, opts.MaxQueueSize) + : new SPPIFOAvgDelayEncoder(solver, opts.NumPackets, opts.NumQueues, opts.MaxRank), PIFOMethodChoice.AIFO => opts.ConsiderPktDrop - ? new AIFOAvgDelayEncoder(solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize, opts.WindowSize, opts.BurstParam) + ? new AIFOAvgDelayEncoder(solver, opts.NumPackets, opts.MaxRank, opts.MaxQueueSize, opts.WindowSize, opts.BurstParam) : throw new ArgumentException( "AIFO only works on shallow buffers and decides whether to admit or drop the packet. " + "As a result, it does not apply to cases where we do not want packet drop."), PIFOMethodChoice.ModifiedSPPIFO => !opts.ConsiderPktDrop - ? new ModifiedSPPIFOAvgDelayEncoder(solver, opts.NumPackets, opts.SplitQueue, opts.NumQueues, opts.SplitRank, opts.MaxRank) + ? new ModifiedSPPIFOAvgDelayEncoder(solver, opts.NumPackets, opts.SplitQueue, opts.NumQueues, opts.SplitRank, opts.MaxRank) : throw new ArgumentException( "ModifiedSPPIFO does not support packet drop yet."), @@ -154,9 +156,31 @@ private static IEncoder CreateEncoder(PIFOMethodChoice method, /// /// Runs PIFO packet scheduling adversarial optimization. /// - /// Command-line options containing PIFO parameters. + /// CLI options. + /// Thrown for unsupported solver. + public static void Run(CliOptions opts) + { + switch (opts.SolverChoice) + { + case SolverChoice.OrTools: + RunWithSolver(new ORToolsSolver(), opts); + break; + case SolverChoice.Zen: + RunWithSolver(new SolverZen(), opts); + break; + case SolverChoice.Gurobi: + RunWithSolver(new GurobiSOS(timeout: opts.Timeout, verbose: Convert.ToInt32(opts.Verbose)), opts); + break; + default: + throw new ArgumentException($"Unsupported solver: {opts.SolverChoice}"); + } + } + + /// + /// Runs PIFO packet scheduling adversarial optimization with the specified solver. + /// /// - /// Creates encoders for SP-PIFO and AIFO algorithms, then uses adversarial + /// Creates encoders for the selected PIFO methods, then uses adversarial /// optimization to find packet rank sequences that maximize the cost gap. /// /// Key parameters from opts: @@ -167,13 +191,10 @@ private static IEncoder CreateEncoder(PIFOMethodChoice method, /// - WindowSize: AIFO admission window size /// - BurstParam: AIFO burst tolerance parameter. /// - public static void Run(CliOptions opts) + private static void RunWithSolver(ISolver solver, CliOptions opts) { Console.WriteLine($"Packets: {opts.NumPackets}, Max Rank: {opts.MaxRank}, Queues: {opts.NumQueues}"); Console.WriteLine($"Max Queue Size: {opts.MaxQueueSize}, Window Size: {opts.WindowSize}"); - - var solver = new GurobiSOS(verbose: Convert.ToInt32(opts.Verbose), timeout: opts.Timeout); - Console.WriteLine($"PIFO Method 1: {opts.PIFOMethod1} (ConsiderPktDrop={opts.ConsiderPktDrop})"); Console.WriteLine($"PIFO Method 2: {opts.PIFOMethod2} (ConsiderPktDrop={opts.ConsiderPktDrop})"); @@ -182,7 +203,7 @@ public static void Run(CliOptions opts) var h2 = CreateEncoder(opts.PIFOMethod2, solver, opts); // Create adversarial generator - var adversarialGenerator = new PIFOAdversarialInputGenerator( + var adversarialGenerator = new PIFOAdversarialInputGenerator( opts.NumPackets, opts.MaxRank); var timer = Stopwatch.StartNew(); @@ -200,21 +221,21 @@ public static void Run(CliOptions opts) // Display results Console.WriteLine("\n" + new string('=', 60)); Console.WriteLine("RESULTS:"); - Console.WriteLine($"SP-PIFO cost: {optimalSolution.Cost}"); - Console.WriteLine($"AIFO cost: {heuristicSolution.Cost}"); + Console.WriteLine($"{opts.PIFOMethod1} cost: {optimalSolution.Cost}"); + Console.WriteLine($"{opts.PIFOMethod2} cost: {heuristicSolution.Cost}"); Console.WriteLine($"Gap: {heuristicSolution.Cost - optimalSolution.Cost}"); - Console.WriteLine($"Inversions (SP-PIFO): {numInvOpt}"); - Console.WriteLine($"Inversions (AIFO): {numInvHeu}"); + Console.WriteLine($"Inversions ({opts.PIFOMethod1}): {numInvOpt}"); + Console.WriteLine($"Inversions ({opts.PIFOMethod2}): {numInvHeu}"); Console.WriteLine($"Time: {timer.ElapsedMilliseconds}ms"); Console.WriteLine(new string('=', 60)); // Verbose output: full solution details if (opts.Verbose) { - Console.WriteLine("\nSP-PIFO Solution:"); + Console.WriteLine($"\n{opts.PIFOMethod1} Solution:"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( optimalSolution, Newtonsoft.Json.Formatting.Indented)); - Console.WriteLine("\nAIFO Solution:"); + Console.WriteLine($"\n{opts.PIFOMethod2} Solution:"); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject( heuristicSolution, Newtonsoft.Json.Formatting.Indented)); } diff --git a/README.md b/README.md index 5a1f408dc..7a3b994c6 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,76 @@ dotnet run We also provide many unittests that can serve as a starting point in `MetaOptimize.Test`. +### Command Line Interface + +MetaOpt provides a unified CLI for all problem types. To see all available options: +```bash +cd MetaOptimize.Cli +dotnet run -- --help +``` + +Or from the parent directory: +```bash +dotnet run --project MetaOptimize.Cli -- --help +``` + +#### Problem Types + +Use `--problemType` to select the optimization problem: + +| Problem Type | Description | +|--------------|-------------| +| `TrafficEngineering` | Network routing optimization | +| `BinPacking` | Vector bin packing | +| `PIFO` | Packet scheduling | +| `FailureAnalysis` | Network failure resilience (Raha) | + +#### Examples + +**Traffic Engineering:** +```bash +dotnet run -- --problemType TrafficEngineering --topologyFile ../Topologies/simple.json --heuristic Pop --innerencoding PrimalDual --demandlist "0,0.25,0.5,0.75,1.0" +``` + +**Bin Packing:** +```bash +dotnet run -- --problemType BinPacking --numBins 6 --numDemands 9 --numDimensions 2 --ffMethod FFDSum --optimalBins 3 +``` + +**PIFO Scheduling:** +```bash +dotnet run -- --problemType PIFO --pifoMethod1 SPPIFO --pifoMethod2 AIFO --considerPktDrop true --numPackets 18 --maxRank 8 +``` + +**Failure Analysis:** +```bash +dotnet run -- --problemType FailureAnalysis --useDefaultTopology true --maxNumFailures 1 --innerencoding KKT +``` + +#### Solver Selection + +Use `--solver` to select the optimization solver: + +| Solver | Description | +|--------|-------------| +| `Gurobi` | Commercial MIP solver (default, requires license) | +| `Zen` | SMT solver based on Z3 (open source) | +| `OrTools` | Google's CP-SAT solver (open source, limited features) | + +Example: +```bash +dotnet run -- --problemType BinPacking --solver OrTools --numBins 6 --numDemands 9 +``` + +#### Common Options + +| Option | Default | Description | +|--------|---------|-------------| +| `--timeout` | ∞ | Solver timeout in seconds | +| `--verbose` | false | Enable detailed output | +| `--innerencoding` | KKT | Bilevel encoding method (KKT or PrimalDual) | +| `--seed` | 1 | Random seed for reproducibility | + ## Analyzing heuristics using MetaOpt

From f22f81d6445e8ffe80da8fe743ebc3282c3e22b9 Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Fri, 19 Dec 2025 09:27:31 -0800 Subject: [PATCH 10/11] PR comments code changes. --- MetaOptimize.Cli/BPRunner.cs | 2 +- MetaOptimize.Cli/CliOptions.cs | 2 +- MetaOptimize.Cli/FailureAnalysisRunner.cs | 2 +- MetaOptimize.Cli/PIFORunner.cs | 2 +- MetaOptimize.Cli/TERunner.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MetaOptimize.Cli/BPRunner.cs b/MetaOptimize.Cli/BPRunner.cs index 7ad01873f..b723626bf 100644 --- a/MetaOptimize.Cli/BPRunner.cs +++ b/MetaOptimize.Cli/BPRunner.cs @@ -46,7 +46,7 @@ public static void Run(CliOptions opts) opts); break; default: - throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: Gurobi, Zen"); + throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: OrTools, Gurobi, Zen"); } } diff --git a/MetaOptimize.Cli/CliOptions.cs b/MetaOptimize.Cli/CliOptions.cs index fff5831a4..ba1417ec8 100644 --- a/MetaOptimize.Cli/CliOptions.cs +++ b/MetaOptimize.Cli/CliOptions.cs @@ -37,7 +37,7 @@ public class CliOptions /// The problem type to solve. /// Determines which runner is invoked and which parameters are relevant. ///

- [Option("problemType", Default = ProblemType.FailureAnalysis, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] + [Option("problemType", Default = ProblemType.BinPacking, HelpText = "Problem type: TrafficEngineering, BinPacking, PIFO, FailureAnalysis")] public ProblemType ProblemType { get; set; } /// diff --git a/MetaOptimize.Cli/FailureAnalysisRunner.cs b/MetaOptimize.Cli/FailureAnalysisRunner.cs index 3af6eae03..68e1538b5 100644 --- a/MetaOptimize.Cli/FailureAnalysisRunner.cs +++ b/MetaOptimize.Cli/FailureAnalysisRunner.cs @@ -49,7 +49,7 @@ public static void Run(CliOptions opts) FailureAnalysisRunnerImpl, ZenSolution>.Run(opts); break; default: - throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: Gurobi, Zen"); + throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: OrTools, Gurobi, Zen"); } } } diff --git a/MetaOptimize.Cli/PIFORunner.cs b/MetaOptimize.Cli/PIFORunner.cs index 77b28c5c6..f3c7fda36 100644 --- a/MetaOptimize.Cli/PIFORunner.cs +++ b/MetaOptimize.Cli/PIFORunner.cs @@ -172,7 +172,7 @@ public static void Run(CliOptions opts) RunWithSolver(new GurobiSOS(timeout: opts.Timeout, verbose: Convert.ToInt32(opts.Verbose)), opts); break; default: - throw new ArgumentException($"Unsupported solver: {opts.SolverChoice}"); + throw new ArgumentException($"Unsupported solver: {opts.SolverChoice}. Valid options: OrTools, Gurobi, Zen"); } } diff --git a/MetaOptimize.Cli/TERunner.cs b/MetaOptimize.Cli/TERunner.cs index 758c70d34..8ba9f87df 100644 --- a/MetaOptimize.Cli/TERunner.cs +++ b/MetaOptimize.Cli/TERunner.cs @@ -95,7 +95,7 @@ private static void GetSolverAndRunNetwork(Topology topology, List clu break; default: - throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: Gurobi, Zen"); + throw new Exception($"Unsupported solver: {opts.SolverChoice}. Valid options: OrTools, Gurobi, Zen"); } } From c01bc13d34db4a7252ac903160efcd232b1434ea Mon Sep 17 00:00:00 2001 From: Dany Rouhana Date: Fri, 19 Dec 2025 09:59:49 -0800 Subject: [PATCH 11/11] removing Dockerfile from this merge. --- Dockerfile | 88 ------------------------------------------------------ 1 file changed, 88 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b4a2ca90f..000000000 --- a/Dockerfile +++ /dev/null @@ -1,88 +0,0 @@ -# ============================================================================= -# MetaOptimize Dockerfile -# ============================================================================= - -# Global ARGs -ARG BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0-azurelinux3.0 -ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/runtime:8.0-azurelinux3.0 - -# Build stage -FROM ${BASE_IMAGE} AS build - -WORKDIR /src - -# Copy build configuration files first (for layer caching) -COPY Directory.Build.props ./ -COPY Directory.Packages.props ./ - -# Copy solution and project files -COPY *.sln ./ -COPY MetaOptimize/*.csproj MetaOptimize/ -COPY MetaOptimize.Cli/*.csproj MetaOptimize.Cli/ -COPY MetaOptimize.Test/*.csproj MetaOptimize.Test/ - -# Copy StyleCop props -COPY .stylecop/ .stylecop/ - -# Restore NuGet packages -RUN dotnet restore -r linux-x64 - -# Copy all source code -COPY . . - -# Build and publish the CLI project in Release mode -RUN dotnet publish MetaOptimize.Cli/MetaOptimize.Cli.csproj \ - -c Release \ - -r linux-x64 \ - --self-contained false \ - -o /app/publish \ - --no-restore - -# ============================================================================= -# Runtime stage -# ============================================================================= -FROM ${RUNTIME_IMAGE} AS runtime - -# Install required packages -RUN tdnf install -y \ - ca-certificates \ - curl \ - tar \ - gzip \ - libstdc++ \ - && tdnf clean all - -# Gurobi version -ARG GUROBI_VERSION=11.0.3 - -# Download and install Gurobi -RUN curl -L "https://packages.gurobi.com/11.0/gurobi${GUROBI_VERSION}_linux64.tar.gz" \ - -o /tmp/gurobi.tar.gz \ - && tar -xzf /tmp/gurobi.tar.gz -C /opt \ - && rm /tmp/gurobi.tar.gz \ - && mv /opt/gurobi1103 /opt/gurobi - -# Set Gurobi environment variables -ENV GUROBI_HOME=/opt/gurobi -ENV PATH="${GUROBI_HOME}/bin:${PATH}" -ENV LD_LIBRARY_PATH="${GUROBI_HOME}/lib" - -# Create application directories -WORKDIR /app -RUN mkdir -p /app/Topologies /app/output /app/licenses - -# Copy published application -COPY --from=build /app/publish . - -# Copy topology files -COPY Topologies/ /app/Topologies/ - -# Environment variables -ENV GUROBI_THREADS=1 - -# Volume mounts -VOLUME ["/app/Topologies", "/app/output", "/app/licenses"] - -# Entry point -ENTRYPOINT ["dotnet", "MetaOptimize.Cli.dll"] -CMD ["--help"] \ No newline at end of file