2012-07-03 85 views
9

Ho un problema di ottimizzazione non lineare con vincoli. Può essere risolto in Microsoft Excel con il componente aggiuntivo Risolutore, ma ho problemi a replicarlo in C#.Come è possibile emulare la funzionalità Risolutore di Microsoft Excel (GRG non lineare) in C#?

Il mio problema è mostrato nel following spreadsheet. Sto risolvendo il classico problema A x = b ma con l'avvertenza che tutti i componenti di x devono essere non negativi. Quindi, invece di utilizzare l'algebra lineare standard, utilizzo il Risolutore con il vincolo non negativo, riducendo al minimo la somma delle differenze quadratiche e ottenendo una soluzione ragionevole. Ho provato a replicare questo in C# usando Microsoft Solver Foundation o Solver SDK. Tuttavia non riesco ad arrivare da nessuna parte con loro perché con MSF non riesco a capire come definire l'obiettivo e con il Risolutore SDK ottengo sempre lo stato "ottimale" e una soluzione di tutti gli 0 che non è sicuramente nemmeno un locale minimo.

Ecco il mio codice per il Risolutore SDK:

static double[][] A = new double[][] { new double[] { 1, 0, 0, 0, 0 }, new double[] { 0.760652602, 1, 0, 0, 0 }, new double[] { 0.373419404, 0.760537565, 1, 0, 0 }, new double[] { 0.136996731, 0.373331934, 0.760422587, 1, 0 }, new double[] { 0.040625222, 0.136953801, 0.373244464, 0.76030755, 1 } }; 
static double[][] b = new double[][] { new double[] { 2017159 }, new double[] { 1609660 }, new double[] { 837732.8125 }, new double[] { 330977.3125 }, new double[] { 87528.38281 } }; 

static void Main(string[] args) 
{ 
    using(Problem problem = new Problem(Solver_Type.Minimize, 5, 0)) 
    { 
     problem.VarDecision.LowerBound.Array = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0 }; 
     problem.VarDecision.UpperBound.Array = new double[] { Constants.PINF, Constants.PINF, Constants.PINF, Constants.PINF, Constants.PINF }; 

     problem.Evaluators[Eval_Type.Function].OnEvaluate += new EvaluateEventHandler(SumOfSquaredErrors); 

     problem.ProblemType = Problem_Type.OptNLP; 

     problem.Solver.Optimize(); 

     Optimize_Status status = problem.Solver.OptimizeStatus; 

     Console.WriteLine(status.ToString()); 
     foreach(double x in problem.VarDecision.FinalValue.Array) 
     { 
      Console.WriteLine(x); 
     } 
    } 
} 

static Engine_Action SumOfSquaredErrors(Evaluator evaluator) 
{ 
    double[][] x = new double[evaluator.Problem.Variables[0].Value.Array.Length][]; 
    for(int i = 0; i < x.Length; i++) 
    { 
     x[i] = new double[1] { evaluator.Problem.Variables[0].Value.Array[i] }; 
    } 

    double[][] b_calculated = MatrixMultiply(A, x); 

    double sum_sq = 0.0; 
    for(int i = 0; i < b_calculated.Length; i++) 
    { 
     sum_sq += Math.Pow(b_calculated[i][0] - b[i][0], 2); 
    } 
    evaluator.Problem.FcnObjective.Value[0] = sum_sq; 

    return Engine_Action.Continue; 
} 

static double[][] MatrixMultiply(double[][] left, double[][] right) 
{ 
    if(left[0].Length != right.Length) 
    { 
     throw new ArgumentException(); 
    } 

    double[][] sum = new double[left.Length][]; 
    for(int i = sum.GetLowerBound(0); i <= sum.GetUpperBound(0); i++) 
    { 
     sum[i] = new double[right[i].Length]; 
    } 

    for(int i = 0; i < sum.Length; i++) 
    { 
     for(int j = 0; j < sum[0].Length; j++) 
     { 
      for(int k = 0; k < right.Length; k++) 
      { 
       sum[i][j] += left[i][k] * right[k][j]; 
      } 
     } 
    } 

    return sum; 
} 

Non ho alcun codice per Microsoft Risolutore Fondazione perché non credo che la funzione obiettivo può essere scritto in una sola riga ed è doesn' t consentire a delegati come Solver SDK.

+0

Quindi, che ne dici di mostrarci il tuo codice?Se torni indietro di tutti gli 0, probabilmente stai facendo qualcosa di sbagliato. –

+0

Ecco qua. Lo avrei fatto prima, ma ho dovuto scrivere una funzione di moltiplicazione della matrice rapida e sporca dato che sto usando una classe proprietaria 'Matrix'. –

+0

vorrebbe vedere il codice Microsoft solver foundation – FistOfFury

risposta

2

Un'alternativa sarebbe quella di formulare questo come un problema di PL:

minimizzare somma degli elementi in x

soggette a Ax> = b

Questo dovrebbe essere abbastanza semplice formulare usando Solver Foundation, basato su uno dei campioni LP.

UPDATE 5 luglio

L'approccio sopra appare anche eccessivamente complesso, ma forse questo è dovuto alle API Frontline Risolutore. Utilizzando Microsoft Foundation Risolutore, e minimizzando la somma delle differenze al quadrato, il seguente programma:

private static void Main(string[] args) 
{ 
    var solver = SolverContext.GetContext(); 
    var model = solver.CreateModel(); 

    var A = new[,] 
     { 
      { 1, 0, 0, 0, 0 }, 
      { 0.760652602, 1, 0, 0, 0 }, 
      { 0.373419404, 0.760537565, 1, 0, 0 }, 
      { 0.136996731, 0.373331934, 0.760422587, 1, 0 }, 
      { 0.040625222, 0.136953801, 0.373244464, 0.76030755, 1 } 
     }; 
    var b = new[] { 2017159, 1609660, 837732.8125, 330977.3125, 87528.38281 }; 

    var n = A.GetLength(1); 
    var x = new Decision[n]; 
    for (var i = 0; i < n; ++i) 
     model.AddDecision(x[i] = new Decision(Domain.RealNonnegative, null)); 

    // START NLP SECTION 
    var m = A.GetLength(0); 
    Term goal = 0.0; 
    for (var j = 0; j < m; ++j) 
    { 
     Term Ax = 0.0; 
     for (var i = 0; i < n; ++i) Ax += A[j, i] * x[i]; 
     goal += Model.Power(Ax - b[j], 2.0); 
    } 
    model.AddGoal(null, GoalKind.Minimize, goal); 
    // END NLP SECTION 

    var solution = solver.Solve(); 
    Console.WriteLine("f = {0}", solution.Goals.First().ToDouble()); 
    for (var i = 0; i < n; ++i) Console.WriteLine("x[{0}] = {1}", i, x[i].GetDouble()); 
} 

genera la soluzione seguente, che dovrebbe essere in linea con la soluzione dal foglio Excel collegato:

f = 254184688.179922 
x[0] = 2017027.31820845 
x[1] = 76226.6063397686 
x[2] = 26007.3375581303 
x[3] = 1.00650383558278E-07 
x[4] = 4.18546775823669E-09 

Se non mi sbaglio, a differenza di GRG, Solver Foundation non supporta i vincoli generali non lineari pronti all'uso, credo che avrete bisogno di plug-in aggiuntivi per gestirli. Per il tuo problema, questo ovviamente non è un problema.

Per completezza, per formulare il problema LP invece, scambiare il codice tra INIZIO NLP SEZIONE e END NLP SEZIONE con il seguente codice:

var m = A.GetLength(0); 
    var constraints = new Term[m]; 
    for (var j = 0; j < m; ++j) 
    { 
     Term Ax = 0.0; 
     for (var i = 0; i < n; ++i) Ax += A[j, i] * x[i]; 
     model.AddConstraint(null, constraints[j] = Model.GreaterEqual(Ax, b[j])); 
    } 
    model.AddGoal(null, GoalKind.Minimize, Model.Sum(x)); 

che produrrà il seguente output (nota che le funzioni obiettive sono diverse nei due casi, quindi le grandi differenze in f):

f = 2125502.27815564 
x[0] = 2017159 
x[1] = 75302.7580022821 
x[2] = 27215.9247379241 
x[3] = 5824.5954154355 
x[4] = 0 
+0

Test preliminari con Risolutore in Excel suggeriscono che questa formulazione potrebbe funzionare, sebbene fornisca una soluzione leggermente meno ottimale. Tuttavia, non sono sicuro che sia più adatto a Microsoft Solver Foundation. Sposta semplicemente il problema di definire l'obiettivo (che è difficile a causa della moltiplicazione della matrice) per definire il vincolo. –

+0

(Scusate per la risposta tardiva, sono stato in viaggio.) Quando affermate che la soluzione LP è meno ottimale, presumo che stiate osservando la differenza 2-norma (somma delle differenze al quadrato). Se si considera invece la norma 1 (somma delle differenze assolute), sono abbastanza sicuro che la soluzione LP sia la migliore. Non ho accesso al Frontline Solver, quindi cercherò di formulare il problema usando Solver Foundation. Proverò a tornare con una risposta aggiornata il prima possibile. –

+0

Sei corretto, 1-norma è migliore con la tua formulazione mentre 2-norma è migliore con la formulazione originale. Se la tua formulazione può essere fatta con Solver Foundation, penso che sarebbe una buona soluzione. –

Problemi correlati