Skip to content

Commit 2eb22a6

Browse files
committed
add is_close/all_close .
1 parent a6527d2 commit 2eb22a6

15 files changed

+624
-158
lines changed

doc/specs/forlab_io.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ Prints a line of progress percentage on the screen.
529529
```fortran
530530
program demo_io_progress_perc
531531
532-
use forlab_strings, only: progress_perc
532+
use forlab_io, only: progress_perc
533533
use forlab_stats, only: randu
534534
535535
do i = 0, 100, 10

doc/specs/forlab_math.md

+122-64
Original file line numberDiff line numberDiff line change
@@ -6,70 +6,6 @@ title: MATH
66

77
[TOC]
88

9-
### `is_close`
10-
11-
#### Description
12-
13-
Returns a boolean scalar/array where two scalars/arrays are element-wise equal within a tolerance.
14-
15-
The tolerance values are positive, typically very small numbers. The relative difference `(rtol*abs(b))` and the absolute difference `atol` are added together to compare against the absolute difference between `a` and `b`.
16-
17-
```fortran
18-
!> For `real` type
19-
abs(a - b) <= rtol*abs(b) + atol
20-
!> For `complex` type
21-
abs(a%re - b%re) <= rtol*abs(b%re) + atol
22-
abs(a%im - b%im) <= rtol*abs(b%im) + atol
23-
```
24-
25-
#### Syntax
26-
27-
`bool = [[forlab_math(module):is_close(interface)]] (a, b [, rtol, atol])`
28-
29-
#### Status
30-
31-
Experimental.
32-
33-
#### Class
34-
35-
Elemental function.
36-
37-
#### Arguments
38-
39-
`a`: Shall be a `real/complex` scalar/array.
40-
This argument is `intent(in)`.
41-
42-
`b`: Shall be a `real/complex` scalar/array.
43-
This argument is `intent(in)`.
44-
45-
`rtol`: Shall be a `real` scalar.
46-
This argument is `intent(in)` and `optional`, which is `1.0e-5` by default.
47-
48-
`atol`: Shall be a `real` scalar.
49-
This argument is `intent(in)` and `optional`, which is `1.0e-8` by default.
50-
51-
Note: All `real/complex` arguments must have same `kind`.
52-
If the value of `rtol/atol` is negative (not recommended), it will be corrected to `abs(rtol/atol)` by the internal process of `is_close`.
53-
54-
#### Result value
55-
56-
Returns a `logical` scalar/array.
57-
58-
#### Example
59-
60-
```fortran
61-
program demo_math_is_close
62-
use forlab_math, only: is_close
63-
use stdlib_error, only: check
64-
real :: x(2) = [1, 2]
65-
print *, is_close(x,[real :: 1, 2.1]) !! [T, F]
66-
print *, all(is_close(x,[real :: 1, 2.1]))!! F
67-
print *, is_close(2.0, 2.1, atol=0.1) !! T
68-
call check(all(is_close(x, [2.0, 2.0])), msg="all(is_close(x, [2.0, 2.0])) failed.", warn=.true.)
69-
!! all(is_close(x, [2.0, 2.0])) failed.
70-
end program demo_math_is_close
71-
```
72-
739
### `arange`
7410

7511
#### Status
@@ -184,4 +120,126 @@ program demo_math_signum
184120
!> (0.447213590,-0.894427180)
185121
186122
end program demo_math_signum
123+
```
124+
125+
126+
### `is_close`
127+
128+
#### Description
129+
130+
Returns a boolean scalar/array where two scalars/arrays are element-wise equal within a tolerance, behaves like `isclose` in Python stdlib.
131+
132+
```fortran
133+
!> For `real` type
134+
is_close(a, b, rel_tol, abs_tol) = abs(a - b) <= max(rel_tol*(abs(a), abs(b)), abs_tol)
135+
!> For `complex` type
136+
is_close(a, b, rel_tol, abs_tol) = is_close(a%re, b%re, rel_tol, abs_tol) .and. &
137+
is_close(a%im, b%im, rel_tol, abs_tol)
138+
```
139+
140+
#### Syntax
141+
142+
`bool = [[stdlib_math(module):is_close(interface)]] (a, b [, rel_tol, abs_tol])`
143+
144+
#### Status
145+
146+
Experimental.
147+
148+
#### Class
149+
150+
Elemental function.
151+
152+
#### Arguments
153+
154+
`a`: Shall be a `real/complex` scalar/array.
155+
This argument is `intent(in)`.
156+
157+
`b`: Shall be a `real/complex` scalar/array.
158+
This argument is `intent(in)`.
159+
160+
`rel_tol`: Shall be a `real` scalar.
161+
This argument is `intent(in)` and `optional`, which is `1.0e-9` by default.
162+
163+
`abs_tol`: Shall be a `real` scalar.
164+
This argument is `intent(in)` and `optional`, which is `0.0` by default.
165+
166+
Note: All `real/complex` arguments must have same `kind`.
167+
If the value of `rel_tol/abs_tol` is negative (not recommended),
168+
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `is_close`.
169+
170+
#### Result value
171+
172+
Returns a `logical` scalar/array.
173+
174+
#### Example
175+
176+
```fortran
177+
program demo_math_is_close
178+
use forlab_math, only: is_close
179+
use stdlib_error, only: check
180+
real :: x(2) = [1, 2]
181+
print *, is_close(x,[real :: 1, 2.1]) !! [T, F]
182+
print *, is_close(2.0, 2.1, abs_tol=0.1) !! T
183+
call check(all(is_close(x, [2.0, 2.0])), msg="all(is_close(x, [2.0, 2.0])) failed.", warn=.true.)
184+
!! all(is_close(x, [2.0, 2.0])) failed.
185+
end program demo_math_is_close
186+
```
187+
188+
### `all_close`
189+
190+
#### Description
191+
192+
Returns a boolean scalar where two arrays are element-wise equal within a tolerance, behaves like `all(is_close(a, b [, rel_tol, abs_tol]))`.
193+
194+
#### Syntax
195+
196+
`bool = [[stdlib_math(module):all_close(interface)]] (a, b [, rel_tol, abs_tol])`
197+
198+
#### Status
199+
200+
Experimental.
201+
202+
#### Class
203+
204+
Impure function.
205+
206+
#### Arguments
207+
208+
`a`: Shall be a `real/complex` array.
209+
This argument is `intent(in)`.
210+
211+
`b`: Shall be a `real/complex` array.
212+
This argument is `intent(in)`.
213+
214+
`rel_tol`: Shall be a `real` scalar.
215+
This argument is `intent(in)` and `optional`, which is `1.0e-9` by default.
216+
217+
`abs_tol`: Shall be a `real` scalar.
218+
This argument is `intent(in)` and `optional`, which is `0.0` by default.
219+
220+
Note: All `real/complex` arguments must have same `kind`.
221+
If the value of `rel_tol/abs_tol` is negative (not recommended),
222+
it will be corrected to `abs(rel_tol/abs_tol)` by the internal process of `all_close`.
223+
224+
#### Result value
225+
226+
Returns a `logical` scalar.
227+
228+
#### Example
229+
230+
```fortran
231+
program demo_math_all_close
232+
use forlab_math, only: all_close
233+
use stdlib_error, only: check
234+
real :: x(2) = [1, 2], random(4, 4)
235+
complex :: z(4, 4)
236+
237+
call check(all_close(x, [2.0, 2.0], rel_tol=1.0e-6, abs_tol=1.0e-3), &
238+
msg="all_close(x, [2.0, 2.0]) failed.", warn=.true.)
239+
!! all_close(x, [2.0, 2.0]) failed.
240+
call random_number(random(4, 4))
241+
z = 1.0
242+
print *, all_close(z+1.0e-11*random, z) !! T
243+
244+
end program demo_math_all_close
187245
```

example/math/demo_math_all_close.f90

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
program demo_math_all_close
2+
use forlab_math, only: all_close
3+
use stdlib_error, only: check
4+
real :: x(2) = [1, 2], random(4, 4)
5+
complex :: z(4, 4)
6+
7+
call check(all_close(x, [2.0, 2.0], rel_tol=1.0e-6, abs_tol=1.0e-3), &
8+
msg="all_close(x, [2.0, 2.0]) failed.", warn=.true.)
9+
!! all_close(x, [2.0, 2.0]) failed.
10+
call random_number(random(4, 4))
11+
z = 1.0
12+
print *, all_close(z+1.0e-11*random, z) !! T
13+
14+
end program demo_math_all_close

example/math/demo_math_is_close.f90

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
program demo_math_is_close
2-
use forlab_math, only: is_close
2+
use forlab_math, only: is_close
33
use stdlib_error, only: check
44
real :: x(2) = [1, 2]
5-
print *, is_close(x,[real :: 1, 2.1]) !! [T, F]
6-
print *, all(is_close(x,[real :: 1, 2.1]))!! F
7-
print *, is_close(2.0, 2.1, atol=0.1) !! T
5+
print *, is_close(x,[real :: 1, 2.1]) !! [T, F]
6+
print *, is_close(2.0, 2.1, abs_tol=0.1) !! T
87
call check(all(is_close(x, [2.0, 2.0])), msg="all(is_close(x, [2.0, 2.0])) failed.", warn=.true.)
98
!! all(is_close(x, [2.0, 2.0])) failed.
109
end program demo_math_is_close

fpm.toml

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name = "forlab"
22
version = "1.0.1"
33
license = "MIT"
4-
author = "forlab contributors"
5-
maintainer = "https://github.com/fortran-fans/forlab"
6-
copyright = "2016-2021 forlab contributors"
4+
author = "FORLAB contributors"
5+
maintainer = "@Fortran-Fans/FORALB"
6+
copyright = "2016-2021 FORLAB contributors"
77
description = "A Fortran module that provides a lot of functions for scientific computing"
88
categories = ["numerical"]
99
keywords = ["numerical", "easy-to-use"]
@@ -79,6 +79,10 @@ name = "math_is_close"
7979
source-dir = "test/math"
8080
main = "test_math_is_close.f90"
8181
[[test]]
82+
name = "math_all_close"
83+
source-dir = "test/math"
84+
main = "test_math_all_close.f90"
85+
[[test]]
8286
name = "math_arange"
8387
source-dir = "test/math"
8488
main = "test_math_arange.f90"

meta-src/common.fypp

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#! Default Kinds
4040
#:set DK = "sp"
4141

42+
#:set MAXRANK = 4
43+
4244
#:def ranksuffix(RANK)
4345
$:'' if RANK == 0 else '(' + ':' + ',:' * (RANK - 1) + ')'
4446
#:enddef ranksuffix

meta-src/forlab_math.fypp

+23-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#:include 'common.fypp'
22
module forlab_math
3+
34
use stdlib_kinds, only: sp, dp, qp, int8, int16, int32, int64
45
use stdlib_optval, only: optval
56
implicit none
@@ -8,7 +9,9 @@ module forlab_math
89
public :: angle
910
public :: cosd, sind,tand
1011
public :: acosd, asind, atand
11-
public :: is_close, arange, signum
12+
public :: arange, signum
13+
14+
public :: is_close, all_close
1215

1316
#:set CIR_NAME=["acos","asin","atan"]
1417
#:for l1 in CIR_NAME
@@ -47,19 +50,33 @@ module forlab_math
4750

4851
!> Version: experimental
4952
!>
50-
!> Determines whether the values of `a` and `b` are close.
51-
!> ([Specification](../page/specs/forlab_logic.html#is_close))
53+
!> Returns a boolean scalar/array where two scalar/arrays are element-wise equal within a tolerance.
54+
!> ([Specification](../page/specs/forlab_math.html#is_close))
5255
interface is_close
5356
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
5457
#:for k1, t1 in RC_KINDS_TYPES
55-
elemental module function is_close_${t1[0]}$${k1}$(a, b, rtol, atol) result(result)
58+
elemental module function is_close_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol) result(close)
5659
${t1}$, intent(in) :: a, b
57-
real(${k1}$), intent(in), optional :: rtol, atol
58-
logical :: result
60+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
61+
logical :: close
5962
end function is_close_${t1[0]}$${k1}$
6063
#:endfor
6164
end interface is_close
6265

66+
!> Version: experimental
67+
!>
68+
!> Returns a boolean scalar where two arrays are element-wise equal within a tolerance.
69+
!> ([Specification](../page/specs/forlab_math.html#all_close))
70+
interface all_close
71+
#:set RC_KINDS_TYPES = REAL_KINDS_TYPES + CMPLX_KINDS_TYPES
72+
#:for k1, t1 in RC_KINDS_TYPES
73+
logical module function all_close_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol) result(close)
74+
${t1}$, intent(in) :: a(..), b(..)
75+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
76+
end function all_close_${t1[0]}$${k1}$
77+
#:endfor
78+
end interface all_close
79+
6380
!> Version: experimental
6481
!>
6582
!> `arange` creates a rank-1 `array` of the `integer/real` type

meta-src/forlab_math_all_close.fypp

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 (forlab_math) forlab_math_all_close
6+
7+
implicit none
8+
character(*), parameter :: error_1 = "*<ERROR>* The ranks of `a` and `b` in `all_close` are not equal."
9+
character(*), parameter :: error_2 = "*<ERROR>* The rank of `a` in `all_close` is too large to be supported."
10+
11+
contains
12+
13+
#:def inrank(r1)
14+
rank(${r1}$)
15+
select rank(b)
16+
rank(${r1}$)
17+
close = all(is_close(a, b, rel_tol, abs_tol))
18+
rank default
19+
error stop error_1
20+
end select
21+
#:enddef
22+
23+
#:for k1, t1 in RC_KINDS_TYPES
24+
logical module function all_close_${t1[0]}$${k1}$(a, b, rel_tol, abs_tol) result(close)
25+
26+
${t1}$, intent(in) :: a(..), b(..)
27+
real(${k1}$), intent(in), optional :: rel_tol, abs_tol
28+
29+
select rank(a)
30+
31+
#:for r1 in RANKS
32+
$:inrank(r1)
33+
#:endfor
34+
35+
rank default
36+
error stop error_2
37+
end select
38+
39+
end function all_close_${t1[0]}$${k1}$
40+
#:endfor
41+
42+
end submodule forlab_math_all_close

0 commit comments

Comments
 (0)