Как я могу использовать 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());
        }
    }
}

0 ответов

Другие вопросы по тегам