2012-07-03 45 views
9

我有一個約束的非線性優化問題。它可以通過Solver加載項在Microsoft Excel中解決,但是我在C#中複製它時遇到了問題。如何在C#中模擬Microsoft Excel的求解器功能(GRG非線性)?

我的問題顯示在following spreadsheet。我正在解決經典問題A x = b問題,但需要注意的是,x的所有組件必須是非負的。因此,我不使用標準線性代數,而是使用Solver和非負約束,最小化平方差的總和,並得到一個合理的解決方案。我試圖在C#中使用Microsoft Solver FoundationSolver SDK進行復制。然而,我似乎無法與他們取得聯繫,因爲在MSF中,我無法弄清楚如何定義目標,並且通過Solver SDK,我總是可以獲得狀態「最佳」,並且所有0的解決方案絕對不是本地的最小。

這裏是我的求解器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; 
} 

我沒有對微軟求解基金會的任何代碼,因爲我不認爲目標函數可以在一行內寫完就和它doesn」允許像Solver SDK那樣的代表。

+0

那麼給我們展示你的代碼怎麼樣?如果你收回所有0,那麼你可能做錯了什麼。 –

+0

你走了。以前會這樣做,但是由於我使用了專有的Matrix類,所以我不得不寫一個快速而且髒的矩陣乘法函數。 –

+0

想要查看微軟求助代碼的基礎代碼 – FistOfFury

回答

2

一種替代辦法是配製本作爲LP問題:

X

斧最小化的元素的總和> = B

這應該是相當簡單的根據其中一個LP樣本制定使用Solver Foundation。

UPDATE 7月5日

上述方法看起來也過於複雜,但也許這是由於前線解算器API。使用Microsoft求解基金會,最小化的平方差的和,下面的程序:

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()); 
} 

生成以下解決方案,這應該是符合從鏈接的Excel片的溶液:

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 

如果我沒有錯,與GRG不同,Solver Foundation不能支持一般的非線性約束,我相信您需要額外的插件來處理這些約束。對於你的問題,這當然不是問題。

爲了完整起見,配製LP問題相反,用下面的代碼交換START NLP SECTIONEND NLP SECTION之間的代碼:

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)); 

這將產生下面的輸出(注意目標函數在兩種情況下是不同的,因此f)的差異很大:

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

使用Excel中的求解器進行初步測試表明這種方法可能有效,儘管它提供了一個稍微不太理想的解決方案。但是,我不確定這是否更適合Microsoft Solver Foundation。它只是將定義目標(由於矩陣乘法而很難)的問題轉移到定義約束。 –

+0

(對不起,遲到的迴應,我一直在旅行。)當你聲稱LP解決方案不太理想時,我假設你正在尋找2-範數(平方差之和)。如果你考慮1-範數(絕對差值的總和),我相當肯定LP解決方案是更好的。我無法訪問Frontline Solver,因此我會嘗試使用Solver Foundation來制定您的問題。我會盡快回復最新的答案。 –

+0

你是對的,1-norm更適合你的配方,而2-norm更適合原配方。如果您的公式可以與Solver基金會合作,我認爲這將是一個很好的解決方案。 –