Skip to content

Commit 2601bf1

Browse files
authored
Merge pull request #488 from fortran-fans/add_close
[stdlib_math] add `is_close` routines.
2 parents 10f9438 + 549ee96 commit 2601bf1

8 files changed

+364
-17
lines changed

doc/specs/stdlib_math.md

+149-9
Original file line numberDiff line numberDiff line change
@@ -320,25 +320,25 @@ program demo_logspace_rstart_cbase
320320
321321
end program demo_logspace_rstart_cbase
322322
```
323-
## `arange`
323+
### `arange`
324324

325-
### Status
325+
#### Status
326326

327327
Experimental
328328

329-
### Class
329+
#### Class
330330

331331
Pure function.
332332

333-
### Description
333+
#### Description
334334

335335
Creates a one-dimensional `array` of the `integer/real` type with fixed-spaced values of given spacing, within a given interval.
336336

337-
### Syntax
337+
#### Syntax
338338

339339
`result = [[stdlib_math(module):arange(interface)]](start [, end, step])`
340340

341-
### Arguments
341+
#### Arguments
342342

343343
All arguments should be the same type and kind.
344344

@@ -354,18 +354,18 @@ The default `end` value is the inputted `start` value.
354354
This is an `intent(in)` and `optional` argument.
355355
The default `step` value is `1`.
356356

357-
#### Warning
357+
##### Warning
358358
If `step = 0`, the `step` argument will be corrected to `1/1.0` by the internal process of the `arange` function.
359359
If `step < 0`, the `step` argument will be corrected to `abs(step)` by the internal process of the `arange` function.
360360

361-
### Return value
361+
#### Return value
362362

363363
Returns a one-dimensional `array` of fixed-spaced values.
364364

365365
For `integer` type arguments, the length of the result vector is `(end - start)/step + 1`.
366366
For `real` type arguments, the length of the result vector is `floor((end - start)/step) + 1`.
367367

368-
### Example
368+
#### Example
369369

370370
```fortran
371371
program demo_math_arange
@@ -388,3 +388,143 @@ program demo_math_arange
388388
389389
end program demo_math_arange
390390
```
391+
392+
### `is_close`
393+
394+
#### Description
395+
396+
Returns a boolean scalar/array where two scalars/arrays are element-wise equal within a tolerance.
397+
398+
```fortran
399+
!> For `real` type
400+
is_close(a, b, rel_tol, abs_tol) = abs(a - b) <= max(rel_tol*(abs(a), abs(b)), abs_tol)
401+
402+
!> and for `complex` type
403+
is_close(a, b, rel_tol, abs_tol) = is_close(a%re, b%re, rel_tol, abs_tol) .and. &
404+
is_close(a%im, b%im, rel_tol, abs_tol)
405+
```
406+
407+
#### Syntax
408+
409+
`bool = [[stdlib_math(module):is_close(interface)]] (a, b [, rel_tol, abs_tol, equal_nan])`
410+
411+
#### Status
412+
413+
Experimental.
414+
415+
#### Class
416+
417+
Elemental function.
418+
419+
#### Arguments
420+
421+
Note: All `real/complex` arguments must have same `kind`.
422+
If the value of `rel_tol/abs_tol` is negative (not recommended),
423+
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `is_close`.
424+
425+
`a`: Shall be a `real/complex` scalar/array.
426+
This argument is `intent(in)`.
427+
428+
`b`: Shall be a `real/complex` scalar/array.
429+
This argument is `intent(in)`.
430+
431+
`rel_tol`: Shall be a `real` scalar/array.
432+
This argument is `intent(in)` and `optional`, which is `sqrt(epsilon(..))` by default.
433+
434+
`abs_tol`: Shall be a `real` scalar/array.
435+
This argument is `intent(in)` and `optional`, which is `0.0` by default.
436+
437+
`equal_nan`: Shall be a `logical` scalar/array.
438+
This argument is `intent(in)` and `optional`, which is `.false.` by default.
439+
Whether to compare `NaN` values as equal. If `.true.`,
440+
`NaN` values in `a` will be considered equal to `NaN` values in `b`.
441+
442+
#### Result value
443+
444+
Returns a `logical` scalar/array.
445+
446+
#### Example
447+
448+
```fortran
449+
program demo_math_is_close
450+
451+
use stdlib_math, only: is_close
452+
use stdlib_error, only: check
453+
real :: x(2) = [1, 2], y, NAN
454+
455+
y = -3
456+
NAN = sqrt(y)
457+
458+
print *, is_close(x,[real :: 1, 2.1]) !! [T, F]
459+
print *, is_close(2.0, 2.1, abs_tol=0.1) !! T
460+
print *, NAN, is_close(2.0, NAN), is_close(2.0, NAN, equal_nan=.true.) !! NAN, F, F
461+
print *, is_close(NAN, NAN), is_close(NAN, NAN, equal_nan=.true.) !! F, T
462+
463+
end program demo_math_is_close
464+
```
465+
466+
### `all_close`
467+
468+
#### Description
469+
470+
Returns a boolean scalar where two arrays are element-wise equal within a tolerance.
471+
472+
#### Syntax
473+
474+
`bool = [[stdlib_math(module):all_close(interface)]] (a, b [, rel_tol, abs_tol, equal_nan])`
475+
476+
#### Status
477+
478+
Experimental.
479+
480+
#### Class
481+
482+
Pure function.
483+
484+
#### Arguments
485+
486+
Note: All `real/complex` arguments must have same `kind`.
487+
If the value of `rel_tol/abs_tol` is negative (not recommended),
488+
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `all_close`.
489+
490+
`a`: Shall be a `real/complex` array.
491+
This argument is `intent(in)`.
492+
493+
`b`: Shall be a `real/complex` array.
494+
This argument is `intent(in)`.
495+
496+
`rel_tol`: Shall be a `real` scalar.
497+
This argument is `intent(in)` and `optional`, which is `sqrt(epsilon(..))` by default.
498+
499+
`abs_tol`: Shall be a `real` scalar.
500+
This argument is `intent(in)` and `optional`, which is `0.0` by default.
501+
502+
`equal_nan`: Shall be a `logical` scalar.
503+
This argument is `intent(in)` and `optional`, which is `.false.` by default.
504+
Whether to compare `NaN` values as equal. If `.true.`,
505+
`NaN` values in `a` will be considered equal to `NaN` values in `b`.
506+
507+
#### Result value
508+
509+
Returns a `logical` scalar.
510+
511+
#### Example
512+
513+
```fortran
514+
program demo_math_all_close
515+
516+
use stdlib_math, only: all_close
517+
use stdlib_error, only: check
518+
real :: x(2) = [1, 2], y, NAN
519+
complex :: z(4, 4)
520+
521+
y = -3
522+
NAN = sqrt(y)
523+
z = (1.0, 1.0)
524+
525+
print *, all_close(z+cmplx(1.0e-11, 1.0e-11), z) !! T
526+
print *, NAN, all_close([NAN], [NAN]), all_close([NAN], [NAN], equal_nan=.true.)
527+
!! NAN, F, T
528+
529+
end program demo_math_all_close
530+
```

src/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ set(fppFiles
4040
stdlib_math_linspace.fypp
4141
stdlib_math_logspace.fypp
4242
stdlib_math_arange.fypp
43+
stdlib_math_is_close.fypp
44+
stdlib_math_all_close.fypp
4345
stdlib_string_type.fypp
4446
stdlib_string_type_constructor.fypp
4547
stdlib_strings_to_string.fypp

src/Makefile.manual

+14-5
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ SRCFYPP = \
3535
stdlib_stats_distribution_normal.fypp \
3636
stdlib_stats_var.fypp \
3737
stdlib_math.fypp \
38-
stdlib_math_linspace.fypp \
39-
stdlib_math_logspace.fypp \
38+
stdlib_math_linspace.fypp \
39+
stdlib_math_logspace.fypp \
40+
stdlib_math_is_close.fypp \
41+
stdlib_math_all_close.fypp \
4042
stdlib_string_type.fypp \
4143
stdlib_string_type_constructor.fypp \
4244
stdlib_strings.fypp \
@@ -81,7 +83,8 @@ $(SRCGEN): %.f90: %.fypp common.fypp
8183
# Fortran module dependencies
8284
f18estop.o: stdlib_error.o
8385
stdlib_ascii.o: stdlib_kinds.o
84-
stdlib_bitsets.o: stdlib_kinds.o
86+
stdlib_bitsets.o: stdlib_kinds.o \
87+
stdlib_optval.o
8588
stdlib_bitsets_64.o: stdlib_bitsets.o
8689
stdlib_bitsets_large.o: stdlib_bitsets.o
8790
stdlib_error.o: stdlib_optval.o
@@ -104,7 +107,8 @@ stdlib_io_npy_save.o: \
104107
stdlib_error.o \
105108
stdlib_strings.o
106109
stdlib_linalg.o: \
107-
stdlib_kinds.o
110+
stdlib_kinds.o \
111+
stdlib_optval.o
108112
stdlib_linalg_diag.o: \
109113
stdlib_linalg.o \
110114
stdlib_kinds.o
@@ -195,6 +199,11 @@ stdlib_math_logspace.o: \
195199
stdlib_math_arange.o: \
196200
stdlib_math.o
197201
stdlib_linalg_outer_product.o: stdlib_linalg.o
202+
stdlib_math_is_close.o: \
203+
stdlib_math.o
204+
stdlib_math_all_close.o: \
205+
stdlib_math.o \
206+
stdlib_math_is_close.o
198207
stdlib_stringlist_type.o: stdlib_string_type.o \
199208
stdlib_math.o \
200-
stdlib_optval.o
209+
stdlib_optval.o

src/stdlib_math.fypp

+34-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module stdlib_math
1414
public :: EULERS_NUMBER_QP
1515
#:endif
1616
public :: DEFAULT_LINSPACE_LENGTH, DEFAULT_LOGSPACE_BASE, DEFAULT_LOGSPACE_LENGTH
17-
public :: arange
17+
public :: arange, is_close, all_close
1818

1919
integer, parameter :: DEFAULT_LINSPACE_LENGTH = 100
2020
integer, parameter :: DEFAULT_LOGSPACE_LENGTH = 50
@@ -294,6 +294,39 @@ module stdlib_math
294294
#:endfor
295295
end interface arange
296296

297+
!> Version: experimental
298+
!>
299+
!> Returns a boolean scalar/array where two scalar/arrays are element-wise equal within a tolerance.
300+
!> ([Specification](../page/specs/stdlib_math.html#is_close))
301+
interface is_close
302+
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
303+
#:for k1, t1 in RC_KINDS_TYPES
304+
elemental module logical function is_close_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol, equal_nan) result(close)
305+
${t1}$, intent(in) :: a, b
306+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
307+
logical, intent(in), optional :: equal_nan
308+
end function is_close_${t1[0]}$${k1}$
309+
#:endfor
310+
end interface is_close
311+
312+
!> Version: experimental
313+
!>
314+
!> Returns a boolean scalar where two arrays are element-wise equal within a tolerance.
315+
!> ([Specification](../page/specs/stdlib_math.html#all_close))
316+
interface all_close
317+
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
318+
#:set RANKS = range(1, MAXRANK + 1)
319+
#:for k1, t1 in RC_KINDS_TYPES
320+
#:for r1 in RANKS
321+
logical pure module function all_close_${r1}$_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol, equal_nan) result(close)
322+
${t1}$, intent(in) :: a${ranksuffix(r1)}$, b${ranksuffix(r1)}$
323+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
324+
logical, intent(in), optional :: equal_nan
325+
end function all_close_${r1}$_${t1[0]}$${k1}$
326+
#:endfor
327+
#:endfor
328+
end interface all_close
329+
297330
contains
298331

299332
#:for k1, t1 in IR_KINDS_TYPES

src/stdlib_math_all_close.fypp

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#:include "common.fypp"
2+
#:set RANKS = range(1, MAXRANK + 1)
3+
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
4+
5+
submodule (stdlib_math) stdlib_math_all_close
6+
7+
implicit none
8+
9+
contains
10+
11+
#:for k1, t1 in RC_KINDS_TYPES
12+
#:for r1 in RANKS
13+
logical pure module function all_close_${r1}$_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol, equal_nan) result(close)
14+
15+
${t1}$, intent(in) :: a${ranksuffix(r1)}$, b${ranksuffix(r1)}$
16+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
17+
logical, intent(in), optional :: equal_nan
18+
19+
close = all(is_close(a, b, rel_tol, abs_tol, equal_nan))
20+
21+
end function all_close_${r1}$_${t1[0]}$${k1}$
22+
#:endfor
23+
#:endfor
24+
25+
end submodule stdlib_math_all_close

src/stdlib_math_arange.fypp

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ contains
4444
step_ = optval(step, 1_${k1}$)
4545
step_ = sign(merge(step_, 1_${k1}$, step_ /= 0_${k1}$), end_ - start_)
4646

47-
allocate(result((end_ - start_)/step_ + 1))
47+
allocate(result((end_ - start_)/step_ + 1_${k1}$))
4848

4949
result = [(i, i=start_, end_, step_)]
5050

src/stdlib_math_is_close.fypp

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#:include "common.fypp"
2+
3+
submodule(stdlib_math) stdlib_math_is_close
4+
5+
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
6+
implicit none
7+
8+
#:for k1 in REAL_KINDS
9+
real(${k1}$), parameter :: sqrt_eps_${k1}$ = sqrt(epsilon(1.0_${k1}$))
10+
#:endfor
11+
12+
contains
13+
14+
#! Determines whether the values of `a` and `b` are close.
15+
16+
#:for k1, t1 in REAL_KINDS_TYPES
17+
elemental module logical function is_close_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol, equal_nan) result(close)
18+
${t1}$, intent(in) :: a, b
19+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
20+
logical, intent(in), optional :: equal_nan
21+
logical :: equal_nan_
22+
23+
equal_nan_ = optval(equal_nan, .false.)
24+
25+
if (ieee_is_nan(a) .or. ieee_is_nan(b)) then
26+
close = merge(.true., .false., equal_nan_ .and. ieee_is_nan(a) .and. ieee_is_nan(b))
27+
else
28+
close = abs(a - b) <= max( abs(optval(rel_tol, sqrt_eps_${k1}$)*max(abs(a), abs(b))), &
29+
abs(optval(abs_tol, 0.0_${k1}$)) )
30+
end if
31+
32+
end function is_close_${t1[0]}$${k1}$
33+
#:endfor
34+
35+
#:for k1, t1 in CMPLX_KINDS_TYPES
36+
elemental module logical function is_close_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol, equal_nan) result(close)
37+
${t1}$, intent(in) :: a, b
38+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
39+
logical, intent(in), optional :: equal_nan
40+
41+
close = is_close_r${k1}$(a%re, b%re, rel_tol, abs_tol, equal_nan) .and. &
42+
is_close_r${k1}$(a%im, b%im, rel_tol, abs_tol, equal_nan)
43+
44+
end function is_close_${t1[0]}$${k1}$
45+
#:endfor
46+
47+
end submodule stdlib_math_is_close

0 commit comments

Comments
 (0)