MindOpt V1.1 新增C#接口
C#的开发者好消息:阿里达摩院MindOpt求解器最新版本V1.1,增加了C#相关API和文档。
什么是优化求解器
优化求解器产品是求解优化问题的专业计算软件。可广泛应用于云计算、电力能源、工业制造、交通物流、零售、金融等领域,能帮助做方案设计、生产方案优化、资源合理分配、辅助决策等,是深埋于智能决策场景底层的“降本增效”的好工具,工业设计软件之芯。
优化求解器作为底层计算软件,因其技术壁垒高、研发难度大,几十年来,商用求解器一直被少数国外厂商垄断。阿里达摩院从2019年投入自研MindOpt优化求解器,截止目前经历27个版本的迭代,取得了多项国内和国际第一的成绩。就在上个月,2023年12月,在工信部产业发展促进中心等单位主办的首届能源电子产业创新大赛上,MindOpt获得电力用国产求解器第一名。
对优化求解器有更多好奇心的初学者,可查阅小编之前的文章《什么是优化技术?给算法小白同学的快速讲解和上手文》(或公众号精排版)
MindOpt C#接口-使用说明
本次MindOpt更新的V1.1版本,除了在性能上进一步提升,还增加了C#的接口。
加上之前已经支持的,当前MindOpt支持
- 主流操作系统:Windows、MacOS、Linux的X86和ARM芯片版本,
- 调用方式支持:
- 命令行运行;
- 通用编程语言 C、C++、C#、Java、Python,部分Julia;
- 优化领域的建模语言 AMPL、Pyomo、PuLP、GAMS以及达摩院自研的MAPL(MindOpt APL)。
步骤1:下载MindOpt和配置License
自2021年起,MindOpt坚持做软件包开放下载,通过阿里云平台,方便用户自助获取商用级别的优化求解器软件,长期有免费额度,企业用户和教育用户均可自助0元购买后使用。
下载安装包:https://opt.aliyun.com/portal/docs/htmldoc/solverDownLoad。
此地址索引了阿里云的文档窗口,方便用户记住地址 opt.aliyun.com 和快速找到MindOpt的其他文档和资源。也可以点击右边的“打开原始文档”,在原阿里云网站中打开。
安装软件:
- Windows双击安装包
- Linux和macOS命令行运行
bash mindopt-install-xxx-1.1.0.sh
指令安装
安装后即可在命令行中运行mindopt
来检查是否安装成功。
如果有在云计算环境运行的,如docker、maxCompute、Flink等环境,也可把本软件当作一个lib库来用,从安装后目录里面拿对应的文件。
配置License:
当前MindOpt供用户自助获取的License是联网云鉴权License,只传鉴权信息(阿里云账号和LIcenseKey码等),不传递用户的建模等数据,可放心使用。
导航栏上点击 产品-优化求解器,将进入阿里云的商品购买介绍页,根据指引点购买进入控制台来购买。可参考文档:https://help.aliyun.com/zh/optimization-solver/getting-started/activate-and-use-the-service
购买后,在已购服务列表中会看到LicenseKey,然后配置fl_client.ini
文件,放置在mindopt文件夹,如下:
配置了后,可命令行执行mindopt -c,会遍历所有的License方式,最后看求解任务成功执行,命令行中如果有如下信息,即可认为此fl_client.ini
文件配置成功。
[INFO ] Using floating license configuration in '/Users/xxxx/mindopt'
[INFO ] Completed validation with floating license configuration.
步骤2:试运行MindOpt安装目录的C# examples
进入到安装目录examples文件夹,有提供各种编程语言、建模语言和数据的实例,可参考运行。
Example.csproj是一个示例项目,配置中如下方式引用了MindOpt库(不同操作系统地址不同):
<ItemGroup><ReferenceInclude="mindoptcs.dll"><HintPath>$(MINDOPT_HOME)/osx64-aarch/lib/mindoptcs.dll</HintPath></Reference></ItemGroup>
.NET 编译和运行:
这里以命令行运行为例讲解如何运行,如在Linux或mac机器上,安装了.NET SDK 8.x ,将.cs
代码文件拷贝入csproject文件夹中,运行如下指令编译和运行:
- 示例1:拷贝不需要依赖外部数据的ExampleDiet.cs进入csproject文件夹
cd csproject cp ../ExampleDiet.cs . dotnet build dotnet run --framework netcoreapp8.0
如下图示意,会执行求解和输出
- 示例2:拷贝需要依赖外部数据的ReadMps.cs进入csproject文件夹。其中第四行引用的数据地址也是MindOpt的安装目录examples文件夹中提供的示例数据。
cd csproject cp ../ReadMps.cs . dotnet build dotnet run ../../data/afiro.mps --framework netcoreapp8.0
C#源代码:如何使用MindOpt解决建模和求解优化问题
建模数学原理的讲解,可参考MindOpt平台上的资源-案例广场:https://opt.aliyun.com/platform/case。以下仅给出代码供参考如何调用。
1. 定制食谱
usingMindopt; namespaceExample{ publicclassExampleDiet { publicstaticvoidMain(string[] args) { MDOEnvenv=newMDOEnv(); MDOModelmodel=newMDOModel(env); model.Set(MDO.StringAttr.ModelName, "diet"); Dictionary<string, double[]>requirements=new() { {"Calories", newdouble[]{ 2000, MDO.INFINITY }}, {"Carbohydrates", newdouble[]{ 350, 375 }}, {"Protein", newdouble[]{ 55, MDO.INFINITY }}, {"VitA", newdouble[]{ 100, MDO.INFINITY }}, {"VitC", newdouble[]{ 100, MDO.INFINITY }}, {"Calcium", newdouble[]{ 100, MDO.INFINITY }}, {"Iron", newdouble[]{ 100, MDO.INFINITY }}, {"Volume", newdouble[]{ -MDO.INFINITY,75 }} }; Dictionary<string, double[]>foods=new() { {"Cheeseburger", newdouble[]{ 0, MDO.INFINITY, 1.84 }}, {"HamSandwich", newdouble[]{ 0, MDO.INFINITY, 2.19 }}, {"Hamburger", newdouble[]{ 0, MDO.INFINITY, 1.84 }}, {"FishSandwich", newdouble[]{ 0, MDO.INFINITY, 1.44 }}, {"ChickenSandwich", newdouble[]{ 0, MDO.INFINITY, 2.29 }}, {"Fries", newdouble[]{ 0, MDO.INFINITY, 0.77 }}, {"SausageBiscuit", newdouble[]{ 0, MDO.INFINITY, 1.29 }}, {"LowfatMilk", newdouble[]{ 0, MDO.INFINITY, 0.60 }}, {"OrangeJuice", newdouble[]{ 0, MDO.INFINITY, 0.72 }} }; double[,] values=newdouble[,] { { 510.0, 34.0, 28.0, 15.0, 6.0, 30.0, 20.0, 4.0 }, { 370.0, 35.0, 24.0, 15.0, 10.0, 20.0, 20.0, 7.5 }, { 500.0, 42.0, 25.0, 6.0, 2.0, 25.0, 20.0, 3.5 }, { 370.0, 38.0, 14.0, 2.0, 0.0, 15.0, 10.0, 5.0 }, { 400.0, 42.0, 31.0, 8.0, 15.0, 15.0, 8.0, 7.3 }, { 220.0, 26.0, 3.0, 0.0, 15.0, 0.0, 2.0, 2.6 }, { 345.0, 27.0, 15.0, 4.0, 0.0, 20.0, 15.0, 4.1 }, { 110.0, 12.0, 9.0, 10.0, 120.0, 30.0, 0.0, 8.0 }, { 80.0, 20.0, 1.0, 2.0, 4.0, 2.0, 2.0, 12.0 } }; string[] nutritionNames=requirements.Keys.Cast<string>().ToArray(); intnumNutrition=nutritionNames.Length; string[] foodNames=foods.Keys.Cast<string>().ToArray(); intnumFood=foodNames.Length; Dictionary<string, Dictionary<string, double>>reqValues=new(); for (inti=0; i<foodNames.Length; i++) { reqValues[foodNames[i]] =newDictionary<string, double>(); for (intj=0; j<nutritionNames.Length; j++) reqValues[foodNames[i]][nutritionNames[j]] =values[i, j]; } try { MDOVar[] foodVars=newMDOVar[numFood]; for (inti=0; i<numFood; ++i) { double[] foodData=foods[foodNames[i]]; foodVars[i] =model.AddVar(foodData[0], foodData[1], 0, MDO.CONTINUOUS, foodNames[i]); } // 添加约束for (inti=0; i<numNutrition; i++) { MDOLinExprlin=newMDOLinExpr(); stringnutri=nutritionNames[i]; for (intj=0; j<numFood; j++) { stringfood=foodNames[j]; lin.AddTerm(reqValues[food][nutri], foodVars[j]); } model.AddRange(lin, requirements[nutritionNames[i]][0], requirements[nutritionNames[i]][1], nutritionNames[i]); } // 添加目标函数MDOLinExprlinExpr=newMDOLinExpr(); for (inti=0; i<numFood; i++) linExpr.AddTerm(foods[foodNames[i]][2], foodVars[i]); model.SetObjective(linExpr, MDO.MINIMIZE); model.Optimize(); model.Write("ExmapleDiet.mps"); // 打印结果foreach (MDOVarfoodVarinfoodVars) Console.WriteLine($"You should buy {foodVar.Get(MDO.DoubleAttr.X)} unit of {foodVar.Get(MDO.StringAttr.VarName)}"); } catch (MDOExceptione) { Console.WriteLine(e.Message); } finally { model.Dispose(); env.Dispose(); } } } }
2. 设施选址
usingMindopt; namespaceExample{ publicclassExampleFacility { publicstaticvoidMain(string[] args) { MDOEnvenv=newMDOEnv(); MDOModelmodel=newMDOModel(env); model.Set(MDO.StringAttr.ModelName, "Facility"); Dictionary<Tuple<double, double>, int>marketInfo=new() { {Tuple.Create(0.0, 1.7), 100}, {Tuple.Create(1.4, 2.9), 200}, }; Dictionary<Tuple<int, int>, double>facilitiesInfo=new() { {Tuple.Create(0, 1), 3.0}, {Tuple.Create(0, 2), 1.0}, {Tuple.Create(1, 0), 1.5}, {Tuple.Create(1, 1), 1.3}, {Tuple.Create(1, 2), 1.8}, {Tuple.Create(2, 0), 1.6}, {Tuple.Create(2, 1), 1.1}, {Tuple.Create(2, 2), 1.9} }; doubletransportFeePerM=1.23; try { inti=0; intj=0; MDOVar[] xVars=model.AddVars(facilitiesInfo.Count, MDO.BINARY); MDOVar[,] yVars=newMDOVar[marketInfo.Count, facilitiesInfo.Count]; for (i=0; i<marketInfo.Count; i++) { for (j=0; j<facilitiesInfo.Count; j++) yVars[i, j] =model.AddVar(0, MDO.INFINITY, 0, MDO.CONTINUOUS, $"{i}{j}"); } i=0; foreach (KeyValuePair<Tuple<double, double>, int>marketPairinmarketInfo) { MDOLinExprlinExpr=newMDOLinExpr(); for (j=0; j<facilitiesInfo.Count; j++) { linExpr.AddTerm(1, yVars[i, j]); MDOLinExprlhe=newMDOLinExpr(); lhe.AddTerm(1.0/marketPair.Value, yVars[i, j]); model.AddConstr(lhe, MDO.LESS_EQUAL, xVars[j], $"is_built[{i}, {j}]"); } model.AddConstr(linExpr, MDO.EQUAL, marketPair.Value, $"is_satisify[{i}, {j}]"); i++; } MDOLinExprobjective=newMDOLinExpr(); j=0; foreach (KeyValuePair<Tuple<int, int>, double>facPairinfacilitiesInfo) { objective.AddTerm(facPair.Value, xVars[j]); i=0; foreach (Tuple<double, double>marketPosinmarketInfo.Keys) { objective.AddTerm(calcTransFee(marketPos, facPair.Key, transportFeePerM), xVars[j]); i++; } j++; } model.SetObjective(objective, MDO.MINIMIZE); model.Optimize(); model.Write("ExampleFacility.mps"); i=0; foreach (Tuple<int, int>posinfacilitiesInfo.Keys) { MDOVarx=xVars[i]; if (x.Get(MDO.DoubleAttr.X) ==1) { Console.Write($"The No.{i} warehouse should be built at "); Console.WriteLine($"({pos.Item1}, {pos.Item2})"); } i++; } Console.WriteLine($"Trnasport fee is: {objective.Value} "); } catch (MDOExceptione) { Console.WriteLine(e.Message); } finally { model.Dispose(); env.Dispose(); } } privatestaticdoublecalcTransFee(Tuple<double, double>pos1, Tuple<int, int>pos2, doubleunitPrice) { doublex1=pos1.Item1-pos2.Item1; doublex2=pos1.Item2-pos2.Item2; return (x1*x1+x2*x2) *unitPrice; } } }
3. 人力分配
usingMindopt; namespaceExample{ publicclassExampleWorkforce { publicstaticvoidMain(string[] args) { // 创建MindOpt模型MDOEnvenv=newMDOEnv(); MDOModelmodel=newMDOModel(env); // 每天需要的工日数目string[] dayName= { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; int[] workersPerDay= { 3, 1, 4, 2, 1, 3, 3 }; // 对应工人的工资Dictionary<string, int>workers=new() { {"Xiaoming", 13}, {"Huahua", 10}, {"HongHong", 11}, {"Dahua", 8}, {"Lihua", 9}, {"Niuniu", 14}, {"Gouzi", 14} }; List<string>workers_name=newList<string>(workers.Keys); // 定义可用集合List<Tuple<string, string>>availability=new() { Tuple.Create( "Xiaoming", "Tuesday" ), Tuple.Create( "Xiaoming", "Wednesday" ), Tuple.Create( "Xiaoming", "Friday" ), Tuple.Create( "Xiaoming", "Sunday" ), Tuple.Create( "Huahua", "Monday" ), Tuple.Create( "Huahua", "Tuesday" ), Tuple.Create( "Huahua", "Friday" ), Tuple.Create( "Huahua", "Saturday" ), Tuple.Create( "HongHong", "Wednesday" ), Tuple.Create( "HongHong", "Thursday" ), Tuple.Create( "HongHong", "Friday" ), Tuple.Create( "HongHong", "Sunday" ), Tuple.Create( "Dahua", "Tuesday" ), Tuple.Create( "Dahua", "Wednesday" ), Tuple.Create( "Dahua", "Friday" ), Tuple.Create( "Dahua", "Saturday" ), Tuple.Create( "Lihua", "Monday" ), Tuple.Create( "Lihua", "Tuesday" ), Tuple.Create( "Lihua", "Wednesday" ), Tuple.Create( "Lihua", "Thursday" ), Tuple.Create( "Lihua", "Friday" ), Tuple.Create( "Lihua", "Sunday" ), Tuple.Create( "Niuniu", "Monday" ), Tuple.Create( "Niuniu", "Tuesday" ), Tuple.Create( "Niuniu", "Wednesday" ), Tuple.Create( "Niuniu", "Saturday" ), Tuple.Create( "Gouzi", "Monday" ), Tuple.Create( "Gouzi", "Tuesday" ), Tuple.Create( "Gouzi", "Wednesday" ), Tuple.Create( "Gouzi", "Friday" ), Tuple.Create( "Gouzi", "Saturday" ), Tuple.Create( "Gouzi", "Sunday" ) }; try { MDOVar[] x=newMDOVar[availability.Count]; for (inti=0; i<x.Length; i++) { Tuple<string, string>workerDay=availability[i]; x[i] =model.AddVar(0, 1, 0, MDO.BINARY, $"sched({workerDay.Item1}, {workerDay.Item2})"); } Dictionary<string, MDOLinExpr>dayCount=new(); foreach (stringdayindayName) dayCount[day] =newMDOLinExpr(); for (inti=0; i<availability.Count; i++) { MDOLinExprexpr=newMDOLinExpr(); Tuple<string, string>workerDay=availability[i]; dayCount[workerDay.Item2].AddTerm(1, x[i]); } for (inti=0; i<dayName.Length; i++) { MDOLinExprexpr=dayCount[dayName[i]]; model.AddConstr(expr, MDO.EQUAL, workersPerDay[i], "c1_"+availability[i].Item2); } MDOLinExprobj=newMDOLinExpr(); for (inti=0; i<availability.Count; i++) obj.AddTerm(workers[availability[i].Item1], x[i]); model.SetObjective(obj, MDO.MINIMIZE); model.Optimize(); for (inti=0; i<availability.Count; i++) { if (x[i].Get(MDO.DoubleAttr.X) >0) Console.WriteLine($"{availability[i].Item1} should work at {availability[i].Item2}"); } Console.WriteLine($"The total cost is { + model.Get(MDO.DoubleAttr.ObjVal)}"); } catch (MDOExceptione) { Console.WriteLine(e.Message); } finally { model.Dispose(); env.Dispose(); } } } }