[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master 028b454 23/23: Find any root with only 64 fun
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master 028b454 23/23: Find any root with only 64 function evaluations |
Date: |
Tue, 27 Jul 2021 21:59:55 -0400 (EDT) |
branch: master
commit 028b4541c5fe0ac6d6bdc0657f5b00f6f4b81f93
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Find any root with only 64 function evaluations
Statistics:
evaluations max mean sample-std-dev commit date
7212 63 18.6 6.94 d6bd8029e 20210718T1630Z
7501 75 19.3 7.36 d2dcbf860 20210723T2039Z
7331 64 18.9 7.12 dc63e62cd 20210724T2119Z
7332 65 18.9 7.13 this today
Picking 64 as the sprauchling limit has the charming consequence that
no solve can take more than 128 evaluations, though it cost one
evaluation.
'Mang a' the ups an' doons o' life,
There's lots o' things gae wrang,
To disappoint us in our aims,
And put us aff our gang;
But could we see the goal we seek,
That's yont the hill we speel,
We'd think our failure to get there
Was maybe just as weel.
We've routh o' disappointments as
We sprauchle up the brae,
We dinna get the things we want,
We lose the things we hae;
An' yet 'mid a' that comes an' gangs,
As time rows aff the reel,
We're forced to this conclusion, that
It's maybe just as weel.
We deem events that loom afar
Will prove to be a curse,
Yet when they happen--o'd, hoo sune
We think them the reverse;
In fact, we never can foresee
What may prove wae or weel,
An' tho' we mourn owre blasted hopes,
It's maybe just as weel.
--Robert McLean Calder
---
calendar_date.cpp | 1 +
financial.hpp | 1 +
gpt_specamt.cpp | 1 +
ihs_avsolve.cpp | 1 +
solve.cpp | 1 +
zero.hpp | 26 ++++++++++++++++++++++----
zero_test.cpp | 19 +++++++++++++------
7 files changed, 40 insertions(+), 10 deletions(-)
diff --git a/calendar_date.cpp b/calendar_date.cpp
index 957880b..19a0e13 100644
--- a/calendar_date.cpp
+++ b/calendar_date.cpp
@@ -750,6 +750,7 @@ class birthdate_limit
, 366 + a_priori_maximum_
,bias_
,0
+ ,64
);
LMI_ASSERT(root_is_valid == z.validity);
int j = bourn_cast<int>(z.root);
diff --git a/financial.hpp b/financial.hpp
index 2788c75..bea9b39 100644
--- a/financial.hpp
+++ b/financial.hpp
@@ -107,6 +107,7 @@ class irr_helper
,1000.0 // Assumed upper bound.
,bias_lower // Return the final bound with the lower FV.
,decimals_
+ ,64
// ,os_trace
);
switch(z.validity)
diff --git a/gpt_specamt.cpp b/gpt_specamt.cpp
index 4760d64..d3727d8 100644
--- a/gpt_specamt.cpp
+++ b/gpt_specamt.cpp
@@ -165,6 +165,7 @@ currency gpt_specamt::CalculateSpecAmt
,999999999.99
,bias_higher
,z.round_min_specamt.decimals()
+ ,64
);
// Because it is implausible that the upper bound is too low,
diff --git a/ihs_avsolve.cpp b/ihs_avsolve.cpp
index 0ab7c85..6ec6b7d 100644
--- a/ihs_avsolve.cpp
+++ b/ihs_avsolve.cpp
@@ -486,6 +486,7 @@ currency AccountValue::Solve
,upper_bound
,bias
,decimals
+ ,64
,os_trace
);
currency const solution_cents = round_minutiae().c(solution.root);
diff --git a/solve.cpp b/solve.cpp
index 34e55ab..741e6dc 100644
--- a/solve.cpp
+++ b/solve.cpp
@@ -328,6 +328,7 @@ currency AccountValue::Solve()
,UpperBound
,Bias
,Decimals
+ ,64
,status()
);
currency const solution_cents = round_to_cents.c(solution.root);
diff --git a/zero.hpp b/zero.hpp
index d42690f..3d33e45 100644
--- a/zero.hpp
+++ b/zero.hpp
@@ -30,6 +30,7 @@
#include "ssize_lmi.hpp"
#include <cfloat> // DBL_EPSILON, DECIMAL_DIG
+#include <climits> // INT_MAX
#include <cmath> // fabs(), isfinite(), isnan(), pow()
#include <cstdint> // uint64_t
#include <cstring> // memcpy()
@@ -64,6 +65,7 @@ enum root_impetus : char
,force_b_to_be_best_approximation = 'k'
,interpolate_linear = 'L'
,interpolate_inverse_quadratic = 'Q'
+ ,interpolate_guaranteed_64_evals = 'G'
,dithering_near_root = '0'
,secant_out_of_bounds = '1'
,parabola_not_single_valued = '2'
@@ -276,6 +278,13 @@ inline double binary64_midpoint(double d0, double d1)
/// enforce it, and also handles the special case where both ordinates
/// are zero.
///
+/// For Brent's method, the worst-case number of iterations is the
+/// square of the number required by naive bisection, so it may take
+/// an unreasonable amount of time for ill-conditioned problems. The
+/// optional 'sprauchling_limit' argument specifies the maximum number
+/// of evaluations to allow before switching to binary64 bisection,
+/// which is guaranteed to converge in 64 further evaluations.
+///
/// Notes referred to in the source code
///
/// Note 0. If one of the bounds is a zero, it is returned as soon as
@@ -351,8 +360,9 @@ root_type lmi_root
,double bound0
,double bound1
,double tolerance
- ,std::ostream& os_trace = null_stream()
- ,root_bias bias = bias_none
+ ,int sprauchling_limit = INT_MAX
+ ,std::ostream& os_trace = null_stream()
+ ,root_bias bias = bias_none
)
{
int n_iter {0};
@@ -490,7 +500,13 @@ root_type lmi_root
; // Do nothing.
}
}
- if(std::fabs(e) < tol)
+ if(sprauchling_limit < n_eval)
+ {
+ impetus = interpolate_guaranteed_64_evals;
+ n = binary64_midpoint(b, c); // "next" iterate
+ d = e = n - b;
+ }
+ else if(std::fabs(e) < tol)
{
impetus = dithering_near_root;
d = e = n - b;
@@ -604,7 +620,8 @@ root_type decimal_root
,double bound1
,root_bias bias
,int decimals
- ,std::ostream& os_trace = null_stream()
+ ,int sprauchling_limit = INT_MAX
+ ,std::ostream& os_trace = null_stream()
)
{
round_to<double> const round_dec {decimals, r_to_nearest};
@@ -631,6 +648,7 @@ root_type decimal_root
,round_dec(bound0)
,round_dec(bound1)
,0.5 * std::pow(10.0, -decimals)
+ ,sprauchling_limit
,os_trace
,bias
);
diff --git a/zero_test.cpp b/zero_test.cpp
index d1fa18f..415095a 100644
--- a/zero_test.cpp
+++ b/zero_test.cpp
@@ -147,7 +147,7 @@ void test_a_function
std::ostringstream os1;
os1.precision(DECIMAL_DIG);
- root_type r = lmi_root(f, bound0, bound1, tol, os1);
+ root_type r = lmi_root(f, bound0, bound1, tol, INT_MAX, os1);
INVOKE_LMI_TEST(root_is_valid == r.validity, file, line);
error = r.root - exact_root;
INVOKE_LMI_TEST_RELATION(std::fabs(error),<=,maximum_error,file,line);
@@ -346,7 +346,7 @@ void test_fundamentals()
// Same, with expatiation.
std::ostringstream oss;
- r = decimal_root(e_function, 0.5, 5.0, bias_none, 9, oss);
+ r = decimal_root(e_function, 0.5, 5.0, bias_none, 9, INT_MAX, oss);
std::cout << oss.str() << std::endl;
// Test use with function object.
@@ -753,7 +753,7 @@ void test_celebrated_equation()
auto f = [](double x) {return x * x * x - 2.0 * x - 5.0;};
std::ostringstream oss;
oss.precision(17);
- root_type r = decimal_root(f, -2.56, 2.56, bias_none, 21, oss);
+ root_type r = decimal_root(f, -2.56, 2.56, bias_none, 21, INT_MAX, oss);
LMI_TEST(root_is_valid == r.validity);
// This constant is from the cited blog; lmi yields this,
// which agrees to sixteen significant digits:
@@ -827,7 +827,7 @@ void test_wikipedia_example()
{
auto f = [](double x) {return (x + 3.0) * (x - 1.0) * (x - 1.0);};
std::ostringstream oss;
- root_type r = decimal_root(f, -4.0, 4.0 / 3.0, bias_none, 15, oss);
+ root_type r = decimal_root(f, -4.0, 4.0 / 3.0, bias_none, 15, INT_MAX,
oss);
LMI_TEST(root_is_valid == r.validity);
LMI_TEST(std::fabs(-3.0 - r.root) <= 1.0e-15);
// Display this to investigate further:
@@ -965,7 +965,7 @@ void test_hodgepodge()
// rather than a theoretical maximum. Perhaps they'll always
// succeed, because floating-point behavior is determinate;
// but small variations betoken no catastrophe.
- LMI_TEST_RELATION(159,<=,r.n_eval); // weak
+ LMI_TEST_RELATION(156,<=,r.n_eval); // weak
LMI_TEST_RELATION(r.n_eval,<=,166); // weak
d = brent_zero(eq_2_1, -100.0, 100.0, 0.5);
@@ -1029,13 +1029,20 @@ void test_hodgepodge()
LMI_TEST_EQUAL(55, r.n_eval); // weak
std::ostringstream oss;
- r = lmi_root(signum_offset, -1.0e300, 1.0e300, 5.0e-19, oss);
+ r = lmi_root(signum_offset, -1.0e300, 1.0e300, 5.0e-19, INT_MAX, oss);
LMI_TEST(root_is_valid == r.validity);
LMI_TEST(materially_equal(-1.0 / 3.0, r.root));
LMI_TEST(r.n_eval <= 3023);
LMI_TEST_EQUAL(1052, r.n_eval); // weak
// Display this to investigate further:
// std::cout << oss.str() << std::endl;
+
+ // Find a root of this irksome function in 64 evaluations,
+ // to maximal precision, in an enormous interval.
+ r = lmi_root(signum_offset, -1.0e300, 1.0e300, 5.0e-19, 0);
+ LMI_TEST(root_is_valid == r.validity);
+ LMI_TEST(materially_equal(-1.0 / 3.0, r.root));
+ LMI_TEST_RELATION(64,<=,r.n_eval);
}
void test_former_rounding_problem()
- [lmi-commits] [lmi] master 548f9ab 06/23: Document known shortcomings of a unit-testing helper, (continued)
- [lmi-commits] [lmi] master 548f9ab 06/23: Document known shortcomings of a unit-testing helper, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master 19a4a3a 03/23: For now at least, trace solves in regression test, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master 04c58eb 09/23: Count iterations and evaluations separately, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master bbf2517 10/23: Use signum() where a signum function is wanted, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master e08be34 12/23: Improve a unit test, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master 02dde17 13/23: Avoid catastrophic cancellation, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master d2dcbf8 15/23: Reduce complexity, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master e93ca8f 17/23: Demonstration, for immediate reversion, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master 4500338 22/23: Simplify, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master 2ad4944 20/23: Record some more observations--no files changed, Greg Chicares, 2021/07/27
- [lmi-commits] [lmi] master 028b454 23/23: Find any root with only 64 function evaluations,
Greg Chicares <=