Skip to content

Commit fb265f9

Browse files
committed
Merge branch 'master' into multitrait
2 parents 756be7b + 88a19fb commit fb265f9

7 files changed

Lines changed: 440 additions & 0 deletions

File tree

EidosScribe/EidosHelpFunctions.rtf

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2747,6 +2747,34 @@ If a value is supplied for
27472747
\f2 \
27482748
\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0
27492749

2750+
\f1\fs18 \cf2 (logical$)allClose(float\'a0x, float\'a0y, [float$\'a0rtol\'a0=\'a01e-5], [float$\'a0atol\'a0=\'a01e-8], [logical$\'a0equalNAN\'a0=\'a0F])\
2751+
\pard\pardeftab720\li547\ri720\sb60\sa60\partightenfactor0
2752+
2753+
\f3\fs20 \cf2 Returns
2754+
\f1\fs18 T
2755+
\f3\fs20 if
2756+
\f0\b all pairs of values between
2757+
\f11\fs18 x
2758+
\f0\fs20 and y are \'93close\'94
2759+
\f3\b0 ; if any pair of values is not close, returns
2760+
\f1\fs18 F
2761+
\f3\fs20 . The definition of \'93close\'94 matches that used in the
2762+
\f1\fs18 isClose()
2763+
\f3\fs20 function; see that documentation for all further details, including the way that values in
2764+
\f1\fs18 x
2765+
\f3\fs20 and
2766+
\f1\fs18 y
2767+
\f3\fs20 are paired, as well as the meaning of the
2768+
\f1\fs18 rtol
2769+
\f3\fs20 ,
2770+
\f1\fs18 atol
2771+
\f3\fs20 , and
2772+
\f1\fs18 equalNAN
2773+
\f3\fs20 parameters. This function is essentially equivalent to
2774+
\f1\fs18 all(isClose(x, y, rtol, atol, equalNAN))
2775+
\f3\fs20 , but is more efficient.\
2776+
\pard\pardeftab720\li720\fi-446\ri720\sb180\sa60\partightenfactor0
2777+
27502778
\f1\fs18 \cf0 (logical$)any(logical
27512779
\f2 \'a0
27522780
\f1 x, ...)\
@@ -3251,6 +3279,109 @@ This is quite similar to a function in R of the same name; note, however, that E
32513279
\f2 \
32523280
\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0
32533281

3282+
\f1\fs18 \cf2 (logical)isClose(float\'a0x, float\'a0y, [float$\'a0rtol\'a0=\'a01e-5], [float$\'a0atol\'a0=\'a01e-8], [logical$\'a0equalNAN\'a0=\'a0F])\
3283+
\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0
3284+
3285+
\f3\fs20 \cf2 Returns a logical vector indicating
3286+
\f0\b whether each pair of values in
3287+
\f11\fs18 x
3288+
\f0\fs20 and
3289+
\f11\fs18 y
3290+
\f0\fs20 are \'93close\'94
3291+
\f3\b0 ; if any pair of values is not close, returns
3292+
\f1\fs18 F
3293+
\f3\fs20 . See the
3294+
\f1\fs18 allClose()
3295+
\f3\fs20 function for an efficient way to test whether
3296+
\f7\i all
3297+
\f3\i0 pairs of values in
3298+
\f1\fs18 x
3299+
\f3\fs20 and
3300+
\f1\fs18 y
3301+
\f3\fs20 are close.\
3302+
A pair of values
3303+
\f1\fs18 a
3304+
\f3\fs20 and
3305+
\f1\fs18 b
3306+
\f3\fs20 is considered \'93close\'94 according to the following criteria. If both
3307+
\f1\fs18 a
3308+
\f3\fs20 and
3309+
\f1\fs18 b
3310+
\f3\fs20 are finite, they are close if
3311+
\f1\fs18 abs(a \uc0\u8722 b) <= (atol + rtol * abs(b))
3312+
\f3\fs20 . If both
3313+
\f1\fs18 a
3314+
\f3\fs20 and
3315+
\f1\fs18 b
3316+
\f3\fs20 are infinite, they are close if they have the same sign. If both
3317+
\f1\fs18 a
3318+
\f3\fs20 and
3319+
\f1\fs18 b
3320+
\f3\fs20 are
3321+
\f1\fs18 NAN
3322+
\f3\fs20 , they are close if
3323+
\f1\fs18 equalNAN
3324+
\f3\fs20 is
3325+
\f1\fs18 T
3326+
\f3\fs20 . In all other cases,
3327+
\f1\fs18 a
3328+
\f3\fs20 and
3329+
\f1\fs18 b
3330+
\f3\fs20 are not close. For finite values,
3331+
\f1\fs18 rtol
3332+
\f3\fs20 thus defines a relative tolerance, and
3333+
\f1\fs18 atol
3334+
\f3\fs20 an absolute tolerance; the relative difference
3335+
\f1\fs18 rtol * abs(b)
3336+
\f3\fs20 and the absolute difference
3337+
\f1\fs18 atol
3338+
\f3\fs20 are added together and compared against the absolute difference between
3339+
\f1\fs18 a
3340+
\f3\fs20 and
3341+
\f1\fs18 b
3342+
\f3\fs20 to determine closeness.\
3343+
Note that the default value for
3344+
\f1\fs18 atol
3345+
\f3\fs20 is not appropriate when comparing numbers with magnitudes much smaller than one; be sure to select
3346+
\f1\fs18 atol
3347+
\f3\fs20 for the use case at hand, especially for defining the threshold below which a non-zero value
3348+
\f1\fs18 a
3349+
\f3\fs20 will be considered \'93close\'94 to a very small or zero value
3350+
\f1\fs18 b
3351+
\f3\fs20 . Note also that
3352+
\f1\fs18 isClose()
3353+
\f3\fs20 is not symmetric in
3354+
\f1\fs18 a
3355+
\f3\fs20 and
3356+
\f1\fs18 b
3357+
\f3\fs20 ; it assumes that b is the reference value for calculating the relative difference.\
3358+
Regarding how values in
3359+
\f1\fs18 x
3360+
\f3\fs20 and
3361+
\f1\fs18 y
3362+
\f3\fs20 are paired, three cases are supported. If
3363+
\f1\fs18 x
3364+
\f3\fs20 and
3365+
\f1\fs18 y
3366+
\f3\fs20 are the same length, then
3367+
\f1\fs18 x
3368+
\f3\fs20 and
3369+
\f1\fs18 y
3370+
\f3\fs20 are paired element-wise; if
3371+
\f1\fs18 x
3372+
\f3\fs20 is singleton, the single
3373+
\f1\fs18 x
3374+
\f3\fs20 value is paired with each value in
3375+
\f1\fs18 y
3376+
\f3\fs20 ; or if
3377+
\f1\fs18 y
3378+
\f3\fs20 is singleton, each value in
3379+
\f1\fs18 x
3380+
\f3\fs20 is paired with the single
3381+
\f1\fs18 y
3382+
\f3\fs20 value.\
3383+
\pard\pardeftab397\li720\fi-446\ri720\sb180\sa60\partightenfactor0
3384+
32543385
\f1\fs18 \cf2 \expnd0\expndtw0\kerning0
32553386
(integer$)length(*\'a0x)\
32563387
\pard\pardeftab397\li547\ri720\sb60\sa60\partightenfactor0

QtSLiM/help/EidosHelpFunctions.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@
233233
<p class="p1"><b>3.5.<span class="Apple-converted-space">  </span>Value inspection &amp; manipulation functions</b></p>
234234
<p class="p2">(logical$)all(logical<span class="s1"> </span>x, ...)</p>
235235
<p class="p3">Returns <span class="s2">T</span> if <b>all values are </b><span class="s2"><b>T</b></span> in <span class="s2">x</span> and in any other arguments supplied; if any value is <span class="s2">F</span>, returns <span class="s2">F</span><span class="s3">.</span><span class="Apple-converted-space">  </span>All arguments must be of <span class="s2">logical</span> type.<span class="Apple-converted-space">  </span>If all arguments are zero-length, <span class="s2">T</span> is returned.</p>
236+
<p class="p4">(logical$)allClose(float x, float y, [float$ rtol = 1e-5], [float$ atol = 1e-8], [logical$ equalNAN = F])</p>
237+
<p class="p5">Returns <span class="s2">T</span> if <b>all pairs of values between </b><span class="s2"><b>x</b></span><b> and y are “close”</b>; if any pair of values is not close, returns <span class="s2">F</span>.<span class="Apple-converted-space">  </span>The definition of “close” matches that used in the <span class="s2">isClose()</span> function; see that documentation for all further details, including the way that values in <span class="s2">x</span> and <span class="s2">y</span> are paired, as well as the meaning of the <span class="s2">rtol</span>, <span class="s2">atol</span>, and <span class="s2">equalNAN</span> parameters.<span class="Apple-converted-space">  </span>This function is essentially equivalent to <span class="s2">all(isClose(x, y, rtol, atol, equalNAN))</span>, but is more efficient.</p>
236238
<p class="p2">(logical$)any(logical<span class="s1"> </span>x, ...)</p>
237239
<p class="p3">Returns <span class="s2">T</span> if <b>any value is </b><span class="s2"><b>T</b></span> in <span class="s2">x</span> or in any other arguments supplied; if all values are <span class="s2">F</span>, returns <span class="s2">F</span><span class="s3">.</span><span class="Apple-converted-space">  </span>All arguments must be of <span class="s2">logical</span> type.<span class="Apple-converted-space">  </span>If all arguments are zero-length, <span class="s2">F</span> is returned.</p>
238240
<p class="p2">(void)cat(*<span class="s1"> </span>x, [string$<span class="s1"> </span>sep<span class="s1"> </span>= " "]<span class="s6">, [logical$ error = F]</span>)</p>
@@ -267,6 +269,11 @@
267269
<p class="p2">(*)ifelse(logical test, * trueValues, * falseValues)</p>
268270
<p class="p3">Returns the result of a <b>vector conditional</b> operation: a vector composed of values from <span class="s2">trueValues</span>, for indices where <span class="s2">test</span> is <span class="s2">T</span>, and values from <span class="s2">falseValues</span>, for indices where <span class="s2">test</span> is <span class="s2">F</span>.<span class="Apple-converted-space">  </span>The lengths of <span class="s2">trueValues</span> and <span class="s2">falseValues</span> must either be equal to <span class="s2">1</span> or to the length of <span class="s2">test</span>; however, <span class="s2">trueValues</span> and <span class="s2">falseValues</span> don’t need to be the same length as each other.<span class="Apple-converted-space">  </span>Furthermore, the type of <span class="s2">trueValues</span> and <span class="s2">falseValues</span> must be the same (including, if they are <span class="s2">object</span> type, their element type).<span class="Apple-converted-space">  </span>The return will be of the same length as <span class="s2">test</span>, and of the same type as <span class="s2">trueValues</span> and <span class="s2">falseValues</span>.<span class="Apple-converted-space">  </span>Each element of the return vector will be taken from the corresponding element of <span class="s2">trueValues</span> if the corresponding element of <span class="s2">test</span> is <span class="s2">T</span>, or from the corresponding element of <span class="s2">falseValues</span> if the corresponding element of <span class="s2">test</span> is <span class="s2">F</span>; if the vector from which the value is to be taken (i.e., <span class="s2">trueValues</span> or <span class="s2">falseValues</span>) has a length of <span class="s2">1</span>, that single value is used repeatedly, recycling the vector.<span class="s14"><span class="Apple-converted-space">  </span>If </span><span class="s15">test</span><span class="s14">, </span><span class="s15">trueValues</span><span class="s14">, and/or </span><span class="s15">falseValues</span><span class="s14"> are matrices or arrays, that will be ignored by </span><span class="s15">ifelse()</span><span class="s14"> <i>except</i> that the result will be of the same dimensionality as </span><span class="s15">test</span><span class="s14">.</span></p>
269271
<p class="p3">This is quite similar to a function in R of the same name; note, however, that Eidos evaluates all arguments to functions calls immediately, so <span class="s2">trueValues</span> and <span class="s2">falseValues</span> will be evaluated fully regardless of the values in <span class="s2">test</span>, unlike in R.<span class="Apple-converted-space">  </span>Value expressions without side effects are therefore recommended.</p>
272+
<p class="p4">(logical)isClose(float x, float y, [float$ rtol = 1e-5], [float$ atol = 1e-8], [logical$ equalNAN = F])</p>
273+
<p class="p5">Returns a logical vector indicating <b>whether each pair of values in </b><span class="s2"><b>x</b></span><b> and </b><span class="s2"><b>y</b></span><b> are “close”</b>; if any pair of values is not close, returns <span class="s2">F</span>.<span class="Apple-converted-space">  </span>See the <span class="s2">allClose()</span> function for an efficient way to test whether <i>all</i> pairs of values in <span class="s2">x</span> and <span class="s2">y</span> are close.</p>
274+
<p class="p5">A pair of values <span class="s2">a</span> and <span class="s2">b</span> is considered “close” according to the following criteria.<span class="Apple-converted-space">  </span>If both <span class="s2">a</span> and <span class="s2">b</span> are finite, they are close if <span class="s2">abs(a − b) &lt;= (atol + rtol * abs(b))</span>.<span class="Apple-converted-space">  </span>If both <span class="s2">a</span> and <span class="s2">b</span> are infinite, they are close if they have the same sign.<span class="Apple-converted-space">  </span>If both <span class="s2">a</span> and <span class="s2">b</span> are <span class="s2">NAN</span>, they are close if <span class="s2">equalNAN</span> is <span class="s2">T</span>.<span class="Apple-converted-space">  </span>In all other cases, <span class="s2">a</span> and <span class="s2">b</span> are not close.<span class="Apple-converted-space">  </span>For finite values, <span class="s2">rtol</span> thus defines a relative tolerance, and <span class="s2">atol</span> an absolute tolerance; the relative difference <span class="s2">rtol * abs(b)</span> and the absolute difference <span class="s2">atol</span> are added together and compared against the absolute difference between <span class="s2">a</span> and <span class="s2">b</span> to determine closeness.</p>
275+
<p class="p5">Note that the default value for <span class="s2">atol</span> is not appropriate when comparing numbers with magnitudes much smaller than one; be sure to select <span class="s2">atol</span> for the use case at hand, especially for defining the threshold below which a non-zero value <span class="s2">a</span> will be considered “close” to a very small or zero value <span class="s2">b</span>.<span class="Apple-converted-space">  </span>Note also that <span class="s2">isClose()</span> is not symmetric in <span class="s2">a</span> and <span class="s2">b</span>; it assumes that b is the reference value for calculating the relative difference.</p>
276+
<p class="p5">Regarding how values in <span class="s2">x</span> and <span class="s2">y</span> are paired, three cases are supported.<span class="Apple-converted-space">  </span>If <span class="s2">x</span> and <span class="s2">y</span> are the same length, then <span class="s2">x</span> and <span class="s2">y</span> are paired element-wise; if <span class="s2">x</span> is singleton, the single <span class="s2">x</span> value is paired with each value in <span class="s2">y</span>; or if <span class="s2">y</span> is singleton, each value in <span class="s2">x</span> is paired with the single <span class="s2">y</span> value.</p>
270277
<p class="p4"><span class="s5">(integer$)length(* x)</span></p>
271278
<p class="p5"><span class="s5">Returns the <b>size</b> (e.g., length) of </span><span class="s9">x</span><span class="s5">: the number of elements contained in </span><span class="s9">x</span><span class="s5">.<span class="Apple-converted-space">  </span>Note that </span><span class="s9">length()</span><span class="s5"> is a synonym for </span><span class="s9">size()</span><span class="s5">.</span></p>
272279
<p class="p2">(integer)match(* x, * table)</p>

VERSIONS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ development head (in the master branch):
2020
fix #575, QtSLiM terminates early when single-stepping with a rescheduled script block
2121
fix #579, crash in models with (a) tree-seq recording, (b) multiple chromosomes, AND (c) rejection of proposed offspring in modifyChild()
2222
add readLine() function for obtaining stdin input to running models, thanks to Chris Talbot
23+
fix #580, add isClose() and allClose() Eidos functions for comparison within a given tolerance
2324

2425

2526
multitrait branch:

eidos/eidos_functions.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,20 @@ const std::vector<EidosFunctionSignature_CSP> &EidosInterpreter::BuiltInFunction
216216
//
217217

218218
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("all", Eidos_ExecuteFunction_all, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddLogical("x")->AddEllipsis());
219+
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("allClose", Eidos_ExecuteFunction_allClose, kEidosValueMaskLogical))->AddFloat("x")->AddFloat("y")->
220+
AddFloat_OS("rtol", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-5)))->
221+
AddFloat_OS("atol", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-8)))->
222+
AddLogical_OS("equalNAN", gStaticEidosValue_LogicalF));
219223
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("any", Eidos_ExecuteFunction_any, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddLogical("x")->AddEllipsis());
220224
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("cat", Eidos_ExecuteFunction_cat, kEidosValueMaskVOID))->AddAny("x")->AddString_OS("sep", gStaticEidosValue_StringSpace)->AddLogical_OS("error", gStaticEidosValue_LogicalF));
221225
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("catn", Eidos_ExecuteFunction_catn, kEidosValueMaskVOID))->AddAny_O("x", gStaticEidosValue_StringEmpty)->AddString_OS("sep", gStaticEidosValue_StringSpace)->AddLogical_OS("error", gStaticEidosValue_LogicalF));
222226
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("format", Eidos_ExecuteFunction_format, kEidosValueMaskString))->AddString_S("format")->AddNumeric("x"));
223227
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("identical", Eidos_ExecuteFunction_identical, kEidosValueMaskLogical | kEidosValueMaskSingleton))->AddAny("x")->AddAny("y"));
224228
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("ifelse", Eidos_ExecuteFunction_ifelse, kEidosValueMaskAny))->AddLogical("test")->AddAny("trueValues")->AddAny("falseValues"));
229+
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("isClose", Eidos_ExecuteFunction_isClose, kEidosValueMaskLogical))->AddFloat("x")->AddFloat("y")->
230+
AddFloat_OS("rtol", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-5)))->
231+
AddFloat_OS("atol", EidosValue_Float_SP(new (gEidosValuePool->AllocateChunk()) EidosValue_Float(1e-8)))->
232+
AddLogical_OS("equalNAN", gStaticEidosValue_LogicalF));
225233
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("match", Eidos_ExecuteFunction_match, kEidosValueMaskInt))->AddAny("x")->AddAny("table"));
226234
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("order", Eidos_ExecuteFunction_order, kEidosValueMaskInt))->AddAnyBase("x")->AddLogical_OS("ascending", gStaticEidosValue_LogicalT));
227235
signatures->emplace_back((EidosFunctionSignature *)(new EidosFunctionSignature("paste", Eidos_ExecuteFunction_paste, kEidosValueMaskString | kEidosValueMaskSingleton))->AddEllipsis()->AddString_OS("sep", gStaticEidosValue_StringSpace));

eidos/eidos_functions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,14 @@ EidosValue_SP Eidos_ExecuteFunction_string(const std::vector<EidosValue_SP> &p_a
153153

154154
// value inspection/manipulation functions
155155
EidosValue_SP Eidos_ExecuteFunction_all(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
156+
EidosValue_SP Eidos_ExecuteFunction_allClose(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
156157
EidosValue_SP Eidos_ExecuteFunction_any(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
157158
EidosValue_SP Eidos_ExecuteFunction_cat(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
158159
EidosValue_SP Eidos_ExecuteFunction_catn(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
159160
EidosValue_SP Eidos_ExecuteFunction_format(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
160161
EidosValue_SP Eidos_ExecuteFunction_identical(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
161162
EidosValue_SP Eidos_ExecuteFunction_ifelse(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
163+
EidosValue_SP Eidos_ExecuteFunction_isClose(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
162164
EidosValue_SP Eidos_ExecuteFunction_match(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
163165
EidosValue_SP Eidos_ExecuteFunction_order(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);
164166
EidosValue_SP Eidos_ExecuteFunction_paste(const std::vector<EidosValue_SP> &p_arguments, EidosInterpreter &p_interpreter);

0 commit comments

Comments
 (0)