From 5dcbbdbb9888de7f5d699a3ea58d168fc1a7f832 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:14:51 +0000 Subject: [PATCH 1/2] fix: remove CheckFunctionEpsilon from IsCriteria in NelderMead stop criteria MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IsCriteria function in OptimizationStop.fs included two calls to CheckFunctionEpsilon which stops optimization when the function value drops below MinFunctionEpsilon (default 1e-8). This caused premature termination for any function with a minimum near or below zero — including all functions with negative optima. Remove the two CheckFunctionEpsilon calls from IsCriteria. The check is appropriate for constrained positive-only optimisation methods but is incorrect for Nelder-Mead, which handles arbitrary real-valued functions. The stationary-point and iteration-limit checks correctly handle convergence. Update PSF test to compare against the true minimum (0,0,0,0) since the optimizer now converges correctly. Add a new test for the quadratic function from issue #260 that has a negative minimum at x=0.16, f(x)≈-0.1556. Closes #260 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Optimization/OptimizationStop.fs | 4 +- tests/FSharp.Stats.Tests/Optimization.fs | 44 ++++++++++++++----- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/FSharp.Stats/Optimization/OptimizationStop.fs b/src/FSharp.Stats/Optimization/OptimizationStop.fs index e02420d7..ab642865 100644 --- a/src/FSharp.Stats/Optimization/OptimizationStop.fs +++ b/src/FSharp.Stats/Optimization/OptimizationStop.fs @@ -281,9 +281,7 @@ module OptimizationStop = let testFlags = __.CheckIteration stopCounter ||| __.CheckFunctionEvaluations stopCounter ||| - __.CheckStationaryPoint stopCounter fold fnew ||| - __.CheckFunctionEpsilon fnew ||| - __.CheckFunctionEpsilon fold + __.CheckStationaryPoint stopCounter fold fnew stopCounter.EndCriteria <- testFlags testFlags = StopCriteriaType.None diff --git a/tests/FSharp.Stats.Tests/Optimization.fs b/tests/FSharp.Stats.Tests/Optimization.fs index a4205bcc..e1c5a979 100644 --- a/tests/FSharp.Stats.Tests/Optimization.fs +++ b/tests/FSharp.Stats.Tests/Optimization.fs @@ -86,19 +86,39 @@ let NelderMeadTests = let optim = NelderMead.minimize nmc x0 psf - test "Psf: solution value" { - let expected = 5.675294665e-09 - let actual = optim.Solution - Expect.floatClose Accuracy.low actual expected "psf: solution did not match" + test "Psf: solution value near zero" { + // PSF minimum is at (0,0,0,0) with value 0; verify convergence + Expect.floatClose { absolute = 1e-12; relative = 1e-6 } optim.Solution 0.0 "psf: solution should converge near 0" + } + + testCase "v: solution vector near zero" <| fun () -> + // PSF minimum is at (0,0,0,0); verify all components converge close to 0 + let acc = { absolute = 1e-4; relative = 1e-3 } + Expect.floatClose acc optim.SolutionVector[0] 0. "psf: x1 should be near 0" + Expect.floatClose acc optim.SolutionVector[1] 0. "psf: x2 should be near 0" + Expect.floatClose acc optim.SolutionVector[2] 0. "psf: x3 should be near 0" + Expect.floatClose acc optim.SolutionVector[3] 0. "psf: x4 should be near 0" + ] + + testList "Test negative-minimum quadratic (issue #260)" [ + // f(x) = x^2 - 0.32x - 0.13 has minimum at x=0.16, f(0.16) ≈ -0.1556 + // Before the fix, CheckFunctionEpsilon caused early termination for negative f values + let myFunction (xs: Vector) = + let x = xs.[0] + x**2. - 0.32*x - 0.13 + + let x0 = vector [| -0.3 |] + let nmc = NelderMead.NmConfig.defaultInit() + let optim = NelderMead.minimize nmc x0 myFunction + + test "negative-minimum: solution x value" { + Expect.floatClose Accuracy.medium optim.SolutionVector[0] 0.16 "quadratic: x* should be near 0.16" + } + + test "negative-minimum: solution function value" { + let expected = -0.1556 // 0.16^2 - 0.32*0.16 - 0.13 + Expect.floatClose Accuracy.medium optim.Solution expected "quadratic: f(x*) should be near -0.1556" } - // - testCase "v: solution vector" <| fun () -> - let expected = [|-0.0005532762725; 5.500401575e-05; -0.002250883404;-0.002282958824|] - - Expect.floatClose Accuracy.low optim.SolutionVector[0] expected[0] "psf: x1 did not match" - Expect.floatClose Accuracy.low optim.SolutionVector[1] expected[1] "psf: x2 did not match" - Expect.floatClose Accuracy.low optim.SolutionVector[2] expected[2] "psf: x3 did not match" - Expect.floatClose Accuracy.low optim.SolutionVector[3] expected[3] "psf: x4 did not match" ] ] \ No newline at end of file From aca47a6162adae952be8faf1eca8704001820b56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Apr 2026 12:14:54 +0000 Subject: [PATCH 2/2] ci: trigger checks