Skip to content

Commit 1d72f8b

Browse files
committed
Add erfc implementation extracted from boost.
1 parent 83a2c0f commit 1d72f8b

File tree

5 files changed

+146
-13
lines changed

5 files changed

+146
-13
lines changed

InterSpec/PeakDists.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,19 @@ namespace PeakDists
4343
20191230: wcjohns extracted the boost::math::erf() function implementation
4444
from boost 1.65.1 for double precision (53 bit mantissa) into this function,
4545
boost_erf_imp(). Removing some of the supporting code structure, and
46-
explicitly writing out the polynomial equation evaluation seems to speed
47-
things up by about a factor of ~3 over calling boost::math::erf().
46+
explicitly writing out the polynomial equation evaluation, and removing some
47+
very minor corrections, seems to speed things up by about a factor of ~3 over
48+
calling boost::math::erf() (and agrees with boosts implementation to within
49+
1E-10%, across the entire range).
4850
4951
In the implementation file, there is a commented out erf_approx() function looks to be about 25% faster than
5052
this boost version, but I havent carefully checked out the precision implications
5153
so not switching to it yet.
5254
*/
5355
double boost_erf_imp( double z );
5456

55-
/** Returns `1-boost_erf_imp(z)` */
57+
/** Returns `1-boost_erf_imp(z)`, however, due to rounding, is a separate implementation than erf.
58+
*/
5659
double boost_erfc_imp( double z );
5760

5861

src/D3SpectrumDisplayDiv.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1850,7 +1850,7 @@ void D3SpectrumDisplayDiv::performExistingRoiEdgeDragWork(
18501850
&& !m_last_being_drug_peaks.empty()
18511851
&& (dt < std::chrono::seconds(15)) )
18521852
{
1853-
cout << "Using previously drug-n-fit peaks" << endl;
1853+
//cout << "Using previously drug-n-fit peaks" << endl;
18541854

18551855
const auto old_continuum = m_last_being_drug_peaks.front()->continuum();
18561856
assert( old_continuum );

src/PeakDists.cpp

Lines changed: 117 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,121 @@ namespace PeakDists
172172
}//double boost_erf_imp( double z )
173173

174174

175+
/** 20231123: The JavaScript needed a better `erfc` implementation than just `1-erf`, or otherwise there were huge
176+
artifacts, and regions that just wouldnt draw, so wcjohns extracted this `erfc` function from boost; boost implements `erf`
177+
and `erfc` in the same function, but since the JS just needs `erfc`, wcjohns separated the erf and erf implementation, even
178+
though they are largely duplicate - saves some small amount of code/overhead.
179+
*/
180+
double boost_erfc_imp( double z )
181+
{
182+
/* Since this function is a (slight) modification of boost source code, it
183+
is subject to the Boost Version 1 Software License, whose text is:
184+
185+
Boost Software License - Version 1.0 - August 17th, 2003
186+
187+
Permission is hereby granted, free of charge, to any person or organization
188+
obtaining a copy of the software and accompanying documentation covered by
189+
this license (the "Software") to use, reproduce, display, distribute,
190+
execute, and transmit the Software, and to prepare derivative works of the
191+
Software, and to permit third-parties to whom the Software is furnished to
192+
do so, all subject to the following:
193+
194+
The copyright notices in the Software and this entire statement, including
195+
the above license grant, this restriction and the following disclaimer,
196+
must be included in all copies of the Software, in whole or in part, and
197+
all derivative works of the Software, unless such copies or derivative
198+
works are solely in the form of machine-executable object code generated by
199+
a source language processor.
200+
201+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
202+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
203+
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
204+
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
205+
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
206+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
207+
DEALINGS IN THE SOFTWARE.
208+
*/
209+
210+
if( z < 0 )
211+
return (z < -0.5) ? (2 - boost_erfc_imp( -z )) : (1 + boost_erf_imp( -z ));
212+
213+
if( z < 0.5 )
214+
return 1 - boost_erf_imp( z );
215+
216+
if( z >= 28 )
217+
return 0;
218+
219+
if(z < 1.5f)
220+
{
221+
static const double P[] = { -0.098090592216281240205, 0.178114665841120341155,
222+
0.191003695796775433986, 0.0888900368967884466578, 0.0195049001251218801359,
223+
0.00180424538297014223957
224+
};
225+
static const double Q[] = { 1.0, 1.84759070983002217845,
226+
1.42628004845511324508, 0.578052804889902404909, 0.12385097467900864233,
227+
0.0113385233577001411017, 0.337511472483094676155e-5
228+
};
229+
230+
const double zarg = z - 0.5;
231+
const double P_eval = ((((zarg*P[5] + P[4])*zarg + P[3])*zarg + P[2])*zarg + P[1])*zarg + P[0];
232+
const double Q_eval = (((((Q[6]*zarg + Q[5])*zarg + Q[4])*zarg + Q[3])*zarg + Q[2])*zarg + Q[1])*zarg + Q[0];
233+
234+
return (0.405935764312744140625f + P_eval / Q_eval) * (exp(-z * z) / z);
235+
}//if(z < 1.5f)
236+
237+
if( z < 2.5f )
238+
{
239+
static const double P[] = { -0.0243500476207698441272, 0.0386540375035707201728,
240+
0.04394818964209516296, 0.0175679436311802092299, 0.00323962406290842133584,
241+
0.000235839115596880717416
242+
};
243+
static const double Q[] = { 1.0, 1.53991494948552447182, 0.982403709157920235114,
244+
0.325732924782444448493, 0.0563921837420478160373, 0.00410369723978904575884
245+
};
246+
247+
const double zarg = z - 1.5;
248+
const double P_eval = ((((zarg*P[5] + P[4])*zarg + P[3])*zarg + P[2])*zarg + P[1])*zarg + P[0];
249+
const double Q_eval = ((((zarg*Q[5] + Q[4])*zarg + Q[3])*zarg + Q[2])*zarg + Q[1])*zarg + Q[0];
250+
251+
return (0.50672817230224609375f + P_eval / Q_eval) * (exp(-z * z) / z);
252+
// Boost implementation has an additional minor error correction here
253+
}//if( z < 2.5f
254+
255+
if( z < 4.5f )
256+
{
257+
static const double P[] = { 0.00295276716530971662634, 0.0137384425896355332126,
258+
0.00840807615555585383007, 0.00212825620914618649141, 0.000250269961544794627958,
259+
0.113212406648847561139e-4
260+
};
261+
static const double Q[] = { 1.0, 1.04217814166938418171,
262+
0.442597659481563127003, 0.0958492726301061423444, 0.0105982906484876531489,
263+
0.000479411269521714493907
264+
};
265+
266+
const double zarg = z - 3.5;
267+
const double P_eval = ((((zarg*P[5] + P[4])*zarg + P[3])*zarg + P[2])*zarg + P[1])*zarg + P[0];
268+
const double Q_eval = ((((zarg*Q[5] + Q[4])*zarg + Q[3])*zarg + Q[2])*zarg + Q[1])*zarg + Q[0];
269+
return (0.5405750274658203125f + P_eval / Q_eval) * (exp(-z * z) / z);
270+
// Boost implementation has an additional minor error correction here
271+
}//if( z < 4.5f )
272+
273+
static const double P[] = { 0.00628057170626964891937, 0.0175389834052493308818,
274+
-0.212652252872804219852, -0.687717681153649930619, -2.5518551727311523996,
275+
-3.22729451764143718517, -2.8175401114513378771
276+
};
277+
static const double Q[] = { 1.0, 2.79257750980575282228, 11.0567237927800161565,
278+
15.930646027911794143, 22.9367376522880577224, 13.5064170191802889145,
279+
5.48409182238641741584
280+
};
281+
282+
const double zarg = 1.0 / z;
283+
const double P_eval = (((((P[6]*zarg + P[5])*zarg + P[4])*zarg + P[3])*zarg + P[2])*zarg + P[1])*zarg + P[0];
284+
const double Q_eval = (((((Q[6]*zarg + Q[5])*zarg + Q[4])*zarg + Q[3])*zarg + Q[2])*zarg + Q[1])*zarg + Q[0];
285+
286+
return (0.5579090118408203125f + P_eval / Q_eval) * (exp(-z * z) / z);
287+
// Boost implementation has an additional minor error correction here
288+
}//double boost_erfc_imp( double z )
289+
175290
/*
176291
double erf_approx( double x )
177292
{
@@ -186,11 +301,7 @@ namespace PeakDists
186301
}
187302
*/
188303

189-
double boost_erfc_imp( double z )
190-
{
191-
return 1.0 - boost_erf_imp( z );
192-
}
193-
304+
194305
void photopeak_function_integral( const double mean,
195306
const double sigma,
196307
const double amp,
@@ -263,7 +374,6 @@ namespace PeakDists
263374
double bortel_indefinite_integral( const double x, const double mean,
264375
const double sigma, const double skew )
265376
{
266-
const double root_two = boost::math::constants::root_two<double>();
267377
const double one_div_root_two = boost::math::constants::one_div_root_two<double>();
268378

269379
const double t = (x - mean) / sigma;
@@ -493,7 +603,7 @@ namespace PeakDists
493603
return t_eqn*sigma + mean;
494604
}
495605

496-
const double root_two = boost::math::constants::root_two<double>();
606+
const double root_two = boost::math::constants::root_two<double>(); //1.414213562373095048801688724209698078
497607
const double t_eqn = root_two* boost::math::erf_inv( (1.0/gauss_indef_amp) * (prob - indefinite_of_tail + gaus_indef_at_skew) );
498608
return t_eqn*sigma + mean;
499609
};//auto x_from_eqn

src/PeakFit.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,10 +1979,10 @@ PeakShrdVec refitPeaksThatShareROI( const std::shared_ptr<const Measurement> &da
19791979
refit_peaks.push_back( make_shared<PeakDef>(p) );
19801980

19811981
const double refit_chi2Dof = chi2_for_region( refit_peaks, dataH, lower_channel, upper_channel ) / nbin;
1982-
cout << "refit_chi2Dof=" << refit_chi2Dof << ", prechi2Dof=" << prechi2Dof << ", postchi2Dof=" << postchi2Dof << endl;
1982+
//cout << "refit_chi2Dof=" << refit_chi2Dof << ", prechi2Dof=" << prechi2Dof << ", postchi2Dof=" << postchi2Dof << endl;
19831983
if( refit_chi2Dof <= prechi2Dof )
19841984
{
1985-
cout << "Using re-fit peaks!" << endl;
1985+
//cout << "Using re-fit peaks!" << endl;
19861986
answer = refit_peaks;
19871987
}
19881988
}//if( output_peak.size() == inpeaks.size() )

target/testing/test_PeakDists.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
//#include <boost/test/unit_test.hpp>
3232
#include <boost/test/included/unit_test.hpp>
3333

34+
#include <boost/math/special_functions/erf.hpp>
35+
3436
#include "SpecUtils/DateTime.h"
3537

3638
#include "InterSpec/PeakDists.h"
@@ -42,6 +44,24 @@ using namespace PeakDists;
4244
using namespace boost::unit_test;
4345

4446

47+
BOOST_AUTO_TEST_CASE( Erfc )
48+
{
49+
for( double x = -10; x <= 10; x += 0.01 )
50+
{
51+
const double our_val = PeakDists::boost_erf_imp(x);
52+
const double boost_val = boost::math::erf(x);
53+
BOOST_CHECK_CLOSE( our_val, boost_val, 1.0E-12 );
54+
}
55+
56+
for( double z = -30; z <= 30; z += 0.01 )
57+
{
58+
const double our_val = PeakDists::boost_erfc_imp(z);
59+
const double boost_val = boost::math::erfc(z);
60+
BOOST_CHECK_CLOSE( our_val, boost_val, 1.0E-10 );
61+
}
62+
}//BOOST_AUTO_TEST_CASE( Erfc )
63+
64+
4565
BOOST_AUTO_TEST_CASE( GaussianDist )
4666
{
4767
// Check Gaussian has unit area

0 commit comments

Comments
 (0)