Как я могу использовать R (Cran ROI) для замены Google ORTools Linear Solver?
Я понимаю, что R способен выполнять оптимизацию и линейное решение задач с помощью пакета Cran ROI. У меня есть продукт производственного уровня, который в настоящее время использует Google ORTools LinearSolver для выполнения этой функции, но я хотел бы перейти на R для единообразия и гибкости. Пожалуйста, ознакомьтесь с текущей реализацией ниже и предложите любые возможные рекомендации. Спасибо!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Comp.LS.Model;
using Google.OrTools.LinearSolver;
using Comp.ECM.Utility;
using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Comp.LS.Optimization {
public enum EGoogSolverType {
[StringValue("GLPK_LINEAR_PROGRAMMING")]
GLPK,
[StringValue("CLP_LINEAR_PROGRAMMING")]
CLP
}
/// <summary>
/// Linear programming solver using Goog's operations research toolkit
/// </summary>
public class GoogLinearSolver : ISolver {
private Solver m_objSolver;
private Variable[] m_objSolverVars;
private Constraint[] m_objSolverConstraints;
public int VarCount { get; set; }
public double[,] CoefficientsMatrix { get; set; }
public double[] ConstantsVector { get; set; }
public EConstraintOp[] ConstraintOperationsVector { get; set; }
public EConstraintOp TargetApproach { get; set; }
public double Target { get; set; }
public double[] TargetCoefficients { get; set; }
public Solution Solve() {
_GenerateVariables();
_GenerateObjective();
_GenerateConstraints();
return _InnerSolve();
}
private Solution _InnerSolve() {
m_objSolver.SetTimeLimit(120);
int iSolveStatus = m_objSolver.Solve();
if (iSolveStatus != Solver.OPTIMAL) {
return new Solution {
OutputCoefficients = m_objSolverVars.Select(v => v.SolutionValue()).ToArray(),
VariableReducedCosts = m_objSolverVars.Select(v => v.ReducedCost()).ToArray(),
ConstraintActivities = m_objSolverConstraints.Select(c => c.Activity()).ToArray(),
ConstraintDualValues = m_objSolverConstraints.Select(c => c.DualValue()).ToArray(),
Iterations = (int)m_objSolver.Iterations(),
SolverWallTime = new TimeSpan(0, 0, 0, 0, (int)m_objSolver.WallTime()),
SourceSolver = this,
Success = false,
SolverStatus = iSolveStatus.ToString()
};
}
return new Solution {
OutputCoefficients = m_objSolverVars.Select(v => v.SolutionValue()).ToArray(),
VariableReducedCosts = m_objSolverVars.Select(v => v.ReducedCost()).ToArray(),
ConstraintActivities = m_objSolverConstraints.Select(c => c.Activity()).ToArray(),
ConstraintDualValues = m_objSolverConstraints.Select(c => c.DualValue()).ToArray(),
Iterations = (int)m_objSolver.Iterations(),
SolverWallTime = new TimeSpan(0, 0, 0, 0, (int)m_objSolver.WallTime()),
SourceSolver = this,
Success = true
};
}
private void _GenerateVariables() {
m_objSolverVars = new Variable[VarCount];
for (int iCurVar = 0; iCurVar < VarCount; iCurVar++) {
m_objSolverVars[iCurVar] = m_objSolver.MakeNumVar(0.0d, double.PositiveInfinity, "var" + iCurVar.ToString());
}
}
private void _GenerateObjective() {
for (int iCurVar = 0; iCurVar < VarCount; iCurVar++) {
m_objSolver.SetObjectiveCoefficient(m_objSolverVars[iCurVar], (TargetCoefficients != null) ? TargetCoefficients[iCurVar] : 1.0d);
}
if (TargetApproach.FlagSet(EConstraintOp.GreaterThan)) {
m_objSolver.SetMaximization();
} else {
m_objSolver.SetMinimization();
}
}
public double Tolerance { get; set; }
private void _GenerateConstraints() {
m_objSolverConstraints = new Constraint[ConstantsVector.Length];
for (int iCurCon = 0; iCurCon < ConstraintOperationsVector.Length; iCurCon++) {
double dMinVal = double.NegativeInfinity, dMaxVal = double.PositiveInfinity;
switch(ConstraintOperationsVector[iCurCon]) {
case EConstraintOp.Equals:
//dMinVal = dMaxVal = ConstantsVector[iCurCon];
dMinVal = ConstantsVector[iCurCon] - ConstantsVector[iCurCon] * Tolerance;
dMaxVal = ConstantsVector[iCurCon] + ConstantsVector[iCurCon] * Tolerance;
break;
case EConstraintOp.GreaterThan:
case EConstraintOp.GreaterThan | EConstraintOp.Equals:
dMinVal = ConstantsVector[iCurCon] - ConstantsVector[iCurCon] * Tolerance;
break;
case EConstraintOp.LessThan:
case EConstraintOp.LessThan | EConstraintOp.Equals:
dMaxVal = ConstantsVector[iCurCon] + ConstantsVector[iCurCon] * Tolerance;
break;
}
m_objSolverConstraints[iCurCon] = m_objSolver.MakeConstraint(dMinVal, dMaxVal, "c" + iCurCon.ToString());
for (int iCurVar = 0; iCurVar < VarCount; iCurVar++) {
m_objSolverConstraints[iCurCon].SetCoefficient(m_objSolverVars[iCurVar], CoefficientsMatrix[iCurCon, iCurVar]);
}
}
}
/// <summary>
/// Gets or sets the solver library to use. Defaults to GLPK.
/// </summary>
public static ERSolverType DefaultSolverType { get; set; }
static GoogLinearSolver() {
DefaultSolverType = ERSolverType.GLPK;
}
/// <summary>
/// Creates a new instance of the solver
/// </summary>
public RLinearSolver() {
m_objSolver = Solver.CreateSolver("IntegerProgramming", DefaultSolverType.GetStringValue());
Tolerance = 0.0d;
}
public override string ToString() {
if (CoefficientsMatrix == null)
return "Uninitialized RLinearSolver";
var iRowBound = CoefficientsMatrix.GetUpperBound(0);
var iColBound = CoefficientsMatrix.GetUpperBound(1);
StringBuilder objCoeffString = new StringBuilder();
List<string> objCurRow = new List<string>();
for (int iRow = 0; iRow <= iRowBound; iRow++) {
for (int iCol = 0; iCol <= iColBound; iCol++) {
objCurRow.Add(CoefficientsMatrix[iRow, iCol].ToString("0.00"));
}
objCoeffString.AppendLine(String.Format("[ {0} ]", String.Join(", ", objCurRow.ToArray())));
objCurRow.Clear();
}
objCoeffString.AppendLine(String.Format("RHS: [ {0} ]", String.Join(", ", ConstantsVector.Select(c => c.ToString("0.00")).ToArray())));
objCoeffString.AppendFormat("Target Coefficients: [ {0} ]" + Environment.NewLine, String.Join(", ", TargetCoefficients.Select(c => c.ToString("0.00")).ToArray()));
objCoeffString.AppendLine("Target Value: " + Target.ToString("0.00"));
return objCoeffString.ToString();
}
}
}
Кроме того, я использую разъем R из.NET следующим образом:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RDotNet;
using RDotNet.Devices;
using System.IO;
using Comp.ECM.Utility;
using System.Threading;
using System.Diagnostics;
using System.Data;
using Microsoft.Win32;
using System.Text.RegularExpressions;
using System.Collections;
namespace Comp.LS.RLanguage {
public class RConnector : IDisposable {
private static Lazy<REngine> m_objEngine;
private static string s_strExecPath = @"C:\Program Files\R\R-3.3.2\bin\i386\R.exe";
private static MutexQueue s_objEngineLock = new MutexQueue(true);
static RConnector() {
var strRBasePath = _GetRInstallPath();
if (strRBasePath != null)
s_strExecPath = Path.Combine(strRBasePath, "bin", "i386");
else
s_strExecPath = SettingsManager.GetAppSetting("RFullPath", @"C:\Program Files\R\R-3.3.2\bin\i386\R.exe");
m_objEngine = new Lazy<REngine>(() => {
EnsureRInPath();
RDotNet.StartupParameter sp = new StartupParameter();
sp.Interactive = false;
sp.Quiet = true;
var objEngineInstance = REngine.GetInstance(parameter: sp);
try {
objEngineInstance.Initialize(parameter: sp);
} catch (Exception ex) {
throw ex;
}
objEngineInstance.AutoPrint = false;
return objEngineInstance;
}, true);
}
public static void EnsureRInPath() {
try {
REngine.SetEnvironmentVariables();
var strCurPath = Environment.GetEnvironmentVariable("PATH") ?? "";
var strRPath = Path.GetDirectoryName(s_strExecPath ?? "");
if (!(strCurPath.ToUpper().Contains(strRPath.ToUpper() + Path.PathSeparator)
|| strCurPath.ToUpper().EndsWith(strRPath.ToUpper()))) {
Environment.SetEnvironmentVariable("PATH", strRPath + Path.PathSeparator +
Environment.GetEnvironmentVariable("PATH"), EnvironmentVariableTarget.Process);
}
}
catch { }
}
private static string _GetRInstallPath() {
try {
var objKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\R-core\R", false);
if (objKey != null) {
var objValue = objKey.GetValue("InstallPath");
if (objValue != null)
return objValue.ToString();
}
} catch (Exception ex) {
ex.LogException("Unable to determine path to R x86 executable from registry.");
}
return null;
}
public static void ClearREnvironment() {
m_objEngine.Value.ClearGlobalEnvironment();
}
public DataFrame LoadDataFrame(string VarName, DataTable FrameData, bool StringsAsFactors = true) {
var objColNameMap = FrameData.Columns.Cast<DataColumn>().ToDictionary(c => c.ColumnName,
c => Regex.Replace(c.ColumnName, "[^A-Za-z0-9]", "_"));
var objColValues = objColNameMap.ToDictionary(c => c.Key, c => new List<object>());
var objCastTypes = objColNameMap.ToDictionary(c => c.Key, c => {
var dt = FrameData.Columns[c.Key].DataType;
if (dt == typeof(short) || dt == typeof(ushort) || dt == typeof(int) || dt == typeof(uint)
|| dt == typeof(long) || dt == typeof(ulong)) {
return typeof(int);
} else if (dt == typeof(string) || dt.IsEnum || dt == typeof(DateTime)) {
return typeof(string);
} else if (dt == typeof(double) || dt == typeof(float) || dt == typeof(decimal)) {
return typeof(double);
} else if (dt == typeof(byte) || dt == typeof(bool)) {
return dt;
} else {
return null;
}
});
foreach (var objCol in objCastTypes.Where(t => t.Value == null)) {
objColNameMap.Remove(objCol.Key);
objColValues.Remove(objCol.Key);
}
foreach (var objRow in FrameData.Rows.Cast<DataRow>()) {
foreach (var objCol in FrameData.Columns.Cast<DataColumn>()) {
if (objColNameMap.ContainsKey(objCol.ColumnName)) {
var objNewType = objCastTypes[objCol.ColumnName];
var objVal = (objRow[objCol] == DBNull.Value) ? null : objRow[objCol];
if (objNewType.IsValueType && objVal == null) {
objVal = Activator.CreateInstance(objNewType);
} else if (objNewType == typeof(string)) {
if (objCol.DataType.IsEnum && objVal != null) {
objVal = Enum.Parse(objCol.DataType, objVal.ToString()).ToString();
} else if (objCol.DataType == typeof(DateTime) && objVal != null) {
objVal = objVal.ToString();
}
if (objVal == null) {
objVal = String.Empty;
}
}
if (!(objVal is string)) {
objVal = Convert.ChangeType(objVal, objNewType);
}
objColValues[objCol.ColumnName].Add(objVal);
}
}
}
var objColValsTyped = objColNameMap.Select(v => {
var objListType = typeof(List<>);
var objItemType = objCastTypes[v.Value];
var objListConsType = objListType.MakeGenericType(objItemType);
var objTypedList = Activator.CreateInstance(objListConsType);
foreach (var objItem in objColValues[v.Value]) {
objListConsType.InvokeMember("Add",
System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, null,
objTypedList, new[] { objItem });
}
var objRetArrayType = objListConsType.InvokeMember("ToArray", System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, objTypedList, null);
return objRetArrayType as IEnumerable;
}).ToArray();
var objNewFrame = m_objEngine.Value.CreateDataFrame(objColValsTyped, objColNameMap.Values.Cast<string>().ToArray(),
stringsAsFactors: StringsAsFactors);
m_objEngine.Value.SetSymbol(VarName, objNewFrame);
return objNewFrame;
}
public string Evaluate(string expression) {
var objResult = Eval(expression);
try {
return objResult.AsCharacter().FirstOrDefault();
} catch {
return null;
}
}
public SymbolicExpression Eval(string Expression) {
return m_objEngine.Value.Evaluate(Expression);
}
public SymbolicExpression Eval(string ExpressionFormat, params object[] Values) {
var strEvalString = String.Format(ExpressionFormat, Values);
return Eval(strEvalString);
}
public class DataFrameParseError {
public int RowNumber { get; set; }
public string ColumnName { get; set; }
public string RawValue { get; set; }
public Exception ErrorInfo { get; set; }
}
public DataTable GetDataFrame(string VarName) {
var objRetTable = new DataTable();
// Stores any parsing errors in a tuple with values:
// (Row number, column name, raw column value, exception info)
var objConvErrors = new List<DataFrameParseError>();
List<string> objColNames, objColTypes;
var strTempVar = "";
if(VarName.Contains("(")) {
strTempVar = string.Format("dfTemp{0}", Guid.NewGuid().ToString("N"));
Eval("{0} <- {1};", strTempVar, VarName);
VarName = strTempVar;
}
var objColTypesRaw = Eval("sapply({0}, typeof)", VarName).AsCharacter();
objColNames = objColTypesRaw.Names.ToList();
objColTypes = objColTypesRaw.ToList();
var objFactorInfo = Eval("sapply({0}, is.factor)", VarName).AsLogical().ToArray();
var objCastFuncs = new Dictionary<string, Func<string, object>>();
objRetTable.Columns.AddRange(
objColNames.Zip(objColTypes, (n, t) => {
Type objColType = null;
Func<string, object> objCastFn = s => s;
switch (t) {
case "double":
objColType = typeof(double);
objCastFn = s => double.Parse(s);
break;
case "integer":
if (objFactorInfo[objColNames.IndexOf(n)]) {
// Although coded as an integer, this is a factor column, so the factor
// string value is what's returned by write.table
objColType = typeof(string);
} else {
// Actually an integer
objColType = typeof(long);
objCastFn = s => long.Parse(s);
}
break;
case "logical":
objColType = typeof(bool);
objCastFn = s => bool.Parse(s.ToLower());
break;
case "character":
case "char":
objColType = typeof(string);
break;
default:
objColType = typeof(object);
break;
}
objCastFuncs.Add(n, objCastFn);
return new DataColumn(n, objColType);
}).ToArray());
var objColIndexMap = objColNames.Select((n, i) => Tuple.Create(n, i))
.ToDictionary(v => v.Item2, v => v.Item1);
var iRowNum = 1;
foreach(var objRow in GetDataFrameRaw(VarName, 2000)) {
var objDataRow = objRetTable.NewRow();
var objConvertedVals = objRow.Select((s, i) => {
if (String.IsNullOrWhiteSpace(s) || s == "NA") {
return Tuple.Create(objColIndexMap[i], (object)DBNull.Value);
} else {
try {
return Tuple.Create(objColIndexMap[i], objCastFuncs[objColIndexMap[i]](s));
} catch (Exception ex) {
objConvErrors.Add(new DataFrameParseError {
RowNumber = iRowNum,
ColumnName = objColIndexMap[i],
RawValue = s,
ErrorInfo = ex
});
return Tuple.Create(objColIndexMap[i], (object)DBNull.Value);
}
}
}).ToList();
foreach(var objVal in objConvertedVals) {
objDataRow[objVal.Item1] = objVal.Item2;
}
objRetTable.Rows.Add(objDataRow);
iRowNum++;
}
if(!String.IsNullOrWhiteSpace(strTempVar)) {
try {
Eval(String.Format("rm({0})"));
} catch { }
}
return objRetTable;
}
public IEnumerable<string[]> GetDataFrameRaw(string VarName, int RowBatchSize = 500) {
var iRowCount = m_objEngine.Value.Evaluate("nrow(" + VarName + ")").AsInteger().FirstOrDefault();
var iColCount = m_objEngine.Value.Evaluate("ncol(" + VarName + ")").AsInteger().FirstOrDefault();
if (iRowCount == 0 || iColCount == 0)
yield break;
if (RowBatchSize <= 0)
RowBatchSize = iRowCount;
var iCurStart = 0;
var iCurEnd = 0;
do {
iCurStart = iCurEnd + 1;
iCurEnd = Math.Min(iCurStart + RowBatchSize - 1, iRowCount);
var strGetFrameCmd = String.Format(@"capture.output(write.table({0}[{1}:{2},], sep = ""\t"", col.names = FALSE, row.names = FALSE, quote = FALSE))",
VarName, iCurStart, iCurEnd);
var strFrameRows = m_objEngine.Value.Evaluate(strGetFrameCmd).AsCharacter().ToArray();
foreach (var strRow in strFrameRows) {
var objSplitRow = strRow.Split(new string[] { "\t" }, StringSplitOptions.None).ToArray();
if (objSplitRow.Length == iColCount) {
yield return objSplitRow;
}
}
} while (iCurEnd < iRowCount);
}
public static string EscapeString(string Input) {
return Input?.Replace("\"", "\\\"")?.Replace(@"\",@"\\");
}
public void WriteCommand(string Command) {
Eval(Command);
}
public RConnector(bool UseProcess = false) {
s_objEngineLock.WaitOne();
}
public REngine GetEngine() {
return m_objEngine.Value;
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing) {
if (!disposedValue) {
if (disposing) {
s_objEngineLock.Release();
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~RConnector() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose() {
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
public static class RConnectorHelper {
public static string EscapeForR(this string Input) {
return RConnector.EscapeString(Input);
}
public static string QuoteForR(this string Input) {
return String.Format(@"""{0}""", Input.EscapeForR());
}
}
}