diff --git a/doc/specs/stdlib_sorting.md b/doc/specs/stdlib_sorting.md index 3a44d84f8..dbf395a10 100644 --- a/doc/specs/stdlib_sorting.md +++ b/doc/specs/stdlib_sorting.md @@ -46,6 +46,9 @@ data: * `ORD_SORT` is intended to sort simple arrays of intrinsic data that have significant sections that were partially ordered before the sort; +* `SORT_ADJOINT` is based on `ORD_SORT`, but in addition to sorting the + input array, it re-orders a second array of the same size + according to the same permutations; * `SORT_INDEX` is based on `ORD_SORT`, but in addition to sorting the input array, it returns indices that map the original array to its sorted version. This enables related arrays to be re-ordered in the @@ -60,10 +63,10 @@ data: The Fortran Standard Library is distributed under the MIT License. However components of the library may be based on code with additional licensing restrictions. In particular `ORD_SORT`, -`SORT_INDEX`, and `SORT` are translations of codes with their +`SORT_ADJOINT`, `SORT_INDEX`, and `SORT` are translations of codes with their own distribution restrictions. -The `ORD_SORT` and `SORT_INDEX` subroutines are essentially +The `ORD_SORT`, `SORT_ADJOINT` and `SORT_INDEX` subroutines are essentially translations to Fortran 2008 of the `"Rust" sort` of the Rust Language distributed as part of [`slice.rs`](https://github.com/rust-lang/rust/blob/90eb44a5897c39e3dff9c7e48e3973671dcd9496/src/liballoc/slice.rs). @@ -140,6 +143,23 @@ argument or allocated internally on the stack. Arrays can be also sorted in a decreasing order by providing the argument `reverse = .true.`. +#### The `SORT_ADJOINT` subroutine + +The `SORT` and `ORD_SORT` subroutines can sort rank 1 isolated +arrays of intrinsic types, but do nothing for the coordinated sorting +of related data, e.g., a related rank 1 array. Therefore the module +provides a subroutine, `SORT_ADJOINT`, that re-order such a rank 1 array +according to the same permutations as for the input array based on the `ORD_SORT` algorithm, +in addition to sorting the input array. + +The logic of `SORT_ADJOINT` parallels that of `ORD_SORT`, with +additional housekeeping to keep the associated array consistent with +the sorted positions of the input array. Because of this additional +housekeeping it has slower runtime performance than `ORD_SORT`. +`SORT_ADJOINT` requires the use of two "scratch" arrays, that may be +provided as optional `work` and `iwork` arguments or allocated +internally on the stack. + #### The `SORT_INDEX` subroutine The `SORT` and `ORD_SORT` subroutines can sort rank 1 isolated @@ -198,7 +218,7 @@ factor of six. Still, even when it shows enhanced performance, its performance on partially sorted data is typically an order of magnitude slower than `ORD_SORT`. Its memory requirements are also low, being of order O(Ln(N)), while the memory requirements of -`ORD_SORT` and `SORT_INDEX` are of order O(N). +`ORD_SORT`, `SORT_ADJOINT` and `SORT_INDEX` are of order O(N). #### The `RADIX_SORT` subroutine @@ -385,6 +405,85 @@ element of `array` is a `NaN`. {!example/sorting/example_radix_sort.f90!} ``` +#### `sort_adjoint` - sorts an associated array +according to the same permutations as for the input array. + +##### Status + +Experimental + +##### Description + +Returns the input `array` sorted in the direction requested while +retaining order stability, and an associated array whose elements are +sorted according to the same permutations as for the input `array`. + +##### Syntax + +`call ` [[stdlib_sorting(module):sort_adjoint(interface)]] `( array, adjoint_array[, work, iwork, reverse ] )` + +##### Class + +Generic subroutine. + +##### Arguments + +`array`: shall be a rank one array of any of the types: +`integer(int8)`, `integer(int16)`, `integer(int32)`, `integer(int64)`, +`real(sp)`, `real(dp)`, `real(qp)`, `character(*)`, `type(string_type)`, +`type(bitset_64)`, or `type(bitset_large)`. +It is an `intent(inout)` argument. On input it +will be an array whose sorting indices are to be determined. On return +it will be the sorted array. + +`adjoint_array`: shall be a rank one `integer` or `real` array of +the size of `array`. It is an `intent(inout)` argument. On return it +shall have values that are the indices needed to sort the original +array in the desired direction. + +`work` (optional): shall be a rank one array of any of the same type as +`array`, and shall have at least `size(array)/2` elements. It is an +`intent(out)` argument. It is intended to be used as "scratch" +memory for internal record keeping. If associated with an array in +static storage, its use can significantly reduce the stack memory +requirements for the code. Its contents on return are undefined. + +`iwork` (optional): shall be a rank one integer array of the same kind +of the array `adjoint_array`, and shall have at least `size(array)/2` elements. It +is an `intent(out)` argument. It is intended to be used as "scratch" +memory for internal record keeping. If associated with an array in +static storage, its use can significantly reduce the stack memory +requirements for the code. Its contents on return are undefined. + +`reverse` (optional): shall be a scalar of type default logical. It +is an `intent(in)` argument. If present with a value of `.true.` then +`array` will be sorted in order of non-increasing values in stable +order. Otherwise `array` will be sorted in order of non-decreasing +values in stable order. + +##### Notes + +`SORT_ADJOINT` implements the hybrid sorting algorithm of `ORD_SORT`, +keeping the values of `adjoint_array` consistent with the elements of `array` +as it is sorted. As a `merge sort` based algorithm, it is a stable +sorting comparison algorithm. The optional `work` and `iwork` arrays +replace "scratch" memory that would otherwise be allocated on the +stack. If `array` is of any kind of `REAL` the order of the elements in +`adjoint_array` and `array` on return are undefined if any element of `array` +is a `NaN`. Sorting of `CHARACTER(*)` and `STRING_TYPE` arrays are +based on the operator `>`, and not on the function `LGT`. + +It should be emphasized that the order of `array` will typically be +different on return + +##### Examples + +Sorting a rank one array with `sort_adjoint`: + +```Fortran +{!example/sorting/example_sort_adjoint.f90!} +``` + #### `sort_index` - creates an array of sorting indices for an input array, while also sorting the array. ##### Status diff --git a/example/sorting/CMakeLists.txt b/example/sorting/CMakeLists.txt index 4628ce20c..6d64ea2f1 100644 --- a/example/sorting/CMakeLists.txt +++ b/example/sorting/CMakeLists.txt @@ -1,5 +1,6 @@ ADD_EXAMPLE(ord_sort) ADD_EXAMPLE(sort) +ADD_EXAMPLE(sort_adjoint) ADD_EXAMPLE(sort_index) ADD_EXAMPLE(radix_sort) ADD_EXAMPLE(sort_bitset) diff --git a/example/sorting/example_sort_adjoint.f90 b/example/sorting/example_sort_adjoint.f90 new file mode 100644 index 000000000..d311fea4e --- /dev/null +++ b/example/sorting/example_sort_adjoint.f90 @@ -0,0 +1,15 @@ +program example_sort_adjoint + use stdlib_sorting, only: sort_adjoint + implicit none + integer, allocatable :: array(:) + real, allocatable :: adjoint(:) + + array = [5, 4, 3, 1, 10, 4, 9] + allocate(adjoint, source=real(array)) + + call sort_adjoint(array, adjoint) + + print *, array !print [1, 3, 4, 4, 5, 9, 10] + print *, adjoint !print [1., 3., 4., 4., 5., 9., 10.] + +end program example_sort_adjoint diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 579b70c72..5b92b7eff 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,7 +38,7 @@ set(fppFiles stdlib_sorting.fypp stdlib_sorting_ord_sort.fypp stdlib_sorting_sort.fypp - stdlib_sorting_sort_index.fypp + stdlib_sorting_sort_adjoint.fypp stdlib_specialfunctions_gamma.fypp stdlib_stats.fypp stdlib_stats_corr.fypp diff --git a/src/stdlib_sorting.fypp b/src/stdlib_sorting.fypp index e0bb93827..c675e5f3f 100644 --- a/src/stdlib_sorting.fypp +++ b/src/stdlib_sorting.fypp @@ -13,6 +13,7 @@ #! This approach allows us to have the same code for all input types. #:set IRSCB_TYPES_ALT_NAME = INT_TYPES_ALT_NAME + REAL_TYPES_ALT_NAME + STRING_TYPES_ALT_NAME + CHAR_TYPES_ALT_NAME & & + BITSET_TYPES_ALT_NAME +#:set IR_INDEX_TYPES_ALT_NAME = INT_TYPES_ALT_NAME + REAL_TYPES_ALT_NAME !! Licensing: !! @@ -292,6 +293,76 @@ module stdlib_sorting !! ! Sort the random data !! call radix_sort( array ) !! ... +!!``` + + public sort_adjoint +!! Version: experimental +!! +!! The generic subroutine implementing the `SORT_ADJ` algorithm to +!! return an adjoint array whose elements are sorted in the same order +!! as the input array in the +!! desired direction. It is primarily intended to be used to sort a +!! rank 1 `integer` or `real` array based on the values of a component of the array. +!! Its use has the syntax: +!! +!! call sort_adjoint( array, adjoint_array[, work, iwork, reverse ] ) +!! +!! with the arguments: +!! +!! * array: the rank 1 array to be sorted. It is an `intent(inout)` +!! argument of any of the types `integer(int8)`, `integer(int16)`, +!! `integer(int32)`, `integer(int64)`, `real(real32)`, `real(real64)`, +!! `real(real128)`, `character(*)`, `type(string_type)`, +!! `type(bitset_64)`, `type(bitset_large)`. If both the +!! type of `array` is real and at least one of the elements is a `NaN`, +!! then the ordering of the `array` and `adjoint_array` results is undefined. +!! Otherwise it is defined to be as specified by reverse. +!! +!! * adjoint_array: a rank 1 `integer` or `real` array. It is an `intent(inout)` +!! argument. Its size shall be the +!! same as `array`. On return, its elements are sorted in the same order +!! as the input `array` in the direction specified by `reverse`. +!! +!! * work (optional): shall be a rank 1 array of the same type as +!! `array`, and shall have at least `size(array)/2` elements. It is an +!! `intent(out)` argument to be used as "scratch" memory +!! for internal record keeping. If associated with an array in static +!! storage, its use can significantly reduce the stack memory requirements +!! for the code. Its value on return is undefined. +!! +!! * iwork (optional): shall be a rank 1 integer array of the same type as `adjoint_array`, +!! and shall have at least `size(array)/2` elements. It is an +!! `intent(out)` argument to be used as "scratch" memory +!! for internal record keeping. If associated with an array in static +!! storage, its use can significantly reduce the stack memory requirements +!! for the code. Its value on return is undefined. +!! +!! * `reverse` (optional): shall be a scalar of type default logical. It +!! is an `intent(in)` argument. If present with a value of `.true.` then +!! `array` will be sorted in order of non-increasing values in stable +!! order. Otherwise `array` will be sorted in order of non-decreasing +!! values in stable order. +!! +!!#### Examples +!! +!! Sorting a related rank one array: +!! +!!```Fortran +!!program example_sort_adjoint +!! use stdlib_sorting, only: sort_adjoint +!! implicit none +!! integer, allocatable :: array(:) +!! real, allocatable :: adj(:) +!! +!! array = [5, 4, 3, 1, 10, 4, 9] +!! allocate(adj, source=real(array)) +!! +!! call sort_adjoint(array, adj) +!! +!! print *, array !print [1, 3, 4, 4, 5, 9, 10] +!! print *, adj !print [1., 3., 4., 4., 5., 9., 10.] +!! +!!end program example_sort_adjoint !!``` public sort_index @@ -505,6 +576,43 @@ module stdlib_sorting end interface sort + interface sort_adjoint +!! Version: experimental +!! +!! The generic subroutine interface implementing the `SORT_ADJ` algorithm, +!! based on the `"Rust" sort` algorithm found in `slice.rs` +!! https://github.com/rust-lang/rust/blob/90eb44a5897c39e3dff9c7e48e3973671dcd9496/src/liballoc/slice.rs#L2159 +!! but modified to return an array of indices that would provide a stable +!! sort of the rank one `ARRAY` input. +!! ([Specification](../page/specs/stdlib_sorting.html#sort_adjoint-creates-an-array-of-sorting-indices-for-an-input-array-while-also-sorting-the-array)) +!! +!! The indices by default correspond to a +!! non-decreasing sort, but if the optional argument `REVERSE` is present +!! with a value of `.TRUE.` the indices correspond to a non-increasing sort. + +#:for ti, tii, namei in IR_INDEX_TYPES_ALT_NAME + #:for t1, t2, name1 in IRSCB_TYPES_ALT_NAME + module subroutine ${name1}$_${namei}$_sort_adjoint( array, adjoint_array, work, iwork, & + reverse ) +!! Version: experimental +!! +!! `${name1}$_${namei}$_sort_adjoint( array, adjoint_array[, work, iwork, reverse] )` sorts +!! an input `ARRAY` of type `${t1}$` +!! using a hybrid sort based on the `"Rust" sort` algorithm found in `slice.rs` +!! and returns the sorted `ARRAY` and an array `INDEX` of indices in the +!! order that would sort the input `ARRAY` in the desired direction. + ${t1}$, intent(inout) :: array(0:) + ${ti}$, intent(inout) :: adjoint_array(0:) + ${t2}$, intent(out), optional :: work(0:) + ${ti}$, intent(out), optional :: iwork(0:) + logical, intent(in), optional :: reverse + end subroutine ${name1}$_${namei}$_sort_adjoint + + #:endfor +#:endfor + + end interface sort_adjoint + interface sort_index !! Version: experimental !! @@ -521,7 +629,24 @@ module stdlib_sorting #:for ki, ti, namei in INT_INDEX_TYPES_ALT_NAME #:for t1, t2, name1 in IRSCB_TYPES_ALT_NAME - module subroutine ${name1}$_sort_index_${namei}$( array, index, work, iwork, & +!> Version: experimental +!> +!> `${name1}$_sort_index_${namei}$( array, index[, work, iwork, reverse] )` sorts +!> an input `ARRAY` of type `${t1}$` +!> using a hybrid sort based on the `"Rust" sort` algorithm found in `slice.rs` +!> and returns the sorted `ARRAY` and an array `INDEX` of indices in the +!> order that would sort the input `ARRAY` in the desired direction. + module procedure ${name1}$_sort_index_${namei}$ + #:endfor +#:endfor + + end interface sort_index + +contains + +#:for ki, ti, namei in INT_INDEX_TYPES_ALT_NAME + #:for t1, t2, name1 in IRSCB_TYPES_ALT_NAME + subroutine ${name1}$_sort_index_${namei}$( array, index, work, iwork, & reverse ) !! Version: experimental !! @@ -535,12 +660,29 @@ module stdlib_sorting ${t2}$, intent(out), optional :: work(0:) ${ti}$, intent(out), optional :: iwork(0:) logical, intent(in), optional :: reverse + + integer(int_index) :: array_size, i + + array_size = size(array, kind=int_index) + + if ( array_size > huge(index)) then + error stop "Too many entries for the kind of index." + end if + + if ( array_size > size(index, kind=int_index) ) then + error stop "Too many entries for the size of index." + end if + + do i = 0, array_size-1 + index(i) = int(i+1, kind=${ki}$) + end do + + call sort_adjoint(array, index, work, iwork, reverse) + end subroutine ${name1}$_sort_index_${namei}$ #:endfor #:endfor - end interface sort_index - end module stdlib_sorting diff --git a/src/stdlib_sorting_ord_sort.fypp b/src/stdlib_sorting_ord_sort.fypp index b96ea295a..c77e1c797 100644 --- a/src/stdlib_sorting_ord_sort.fypp +++ b/src/stdlib_sorting_ord_sort.fypp @@ -117,6 +117,8 @@ contains integer :: stat array_size = size( array, kind=int_index ) + +! If necessary allocate buffers to serve as scratch memory. if ( present(work) ) then if ( size(work, kind=int_index) < array_size/2 ) then error stop "${name1}$_${sname}$_ord_sort: work array is too small." diff --git a/src/stdlib_sorting_sort_index.fypp b/src/stdlib_sorting_sort_adjoint.fypp similarity index 84% rename from src/stdlib_sorting_sort_index.fypp rename to src/stdlib_sorting_sort_adjoint.fypp index cc2afe9cf..a8b99b034 100644 --- a/src/stdlib_sorting_sort_index.fypp +++ b/src/stdlib_sorting_sort_adjoint.fypp @@ -5,13 +5,12 @@ #:set CHAR_TYPES_ALT_NAME = list(zip(["character(len=*)"], ["character(len=:)"], ["character(len=len(array))"], ["char"])) #:set BITSET_TYPES_ALT_NAME = list(zip(BITSET_TYPES, BITSET_TYPES, BITSET_TYPES, BITSET_KINDS)) -#:set INT_INDEX_TYPES_ALT_NAME = list(zip(["int_index", "int_index_low"], ["integer(int_index)", "integer(int_index_low)"], ["default", "low"])) - #! For better code reuse in fypp, make lists that contain the input types, #! with each having output types and a separate name prefix for subroutines #! This approach allows us to have the same code for all input types. #:set IRSCB_TYPES_ALT_NAME = INT_TYPES_ALT_NAME + REAL_TYPES_ALT_NAME + STRING_TYPES_ALT_NAME + CHAR_TYPES_ALT_NAME & & + BITSET_TYPES_ALT_NAME +#:set IR_INDEX_TYPES_ALT_NAME = INT_TYPES_ALT_NAME + REAL_TYPES_ALT_NAME !! Licensing: !! @@ -43,7 +42,7 @@ !! TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE !! SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. !! -!! The generic subroutine, `SORT_INDEX`, is substantially a translation to +!! The generic subroutine, `SORT_ADJ`, is substantially a translation to !! Fortran 2008 of the `"Rust" sort` sorting routines in !! [`slice.rs`](https://github.com/rust-lang/rust/blob/90eb44a5897c39e3dff9c7e48e3973671dcd9496/src/liballoc/slice.rs) !! The `rust sort` implementation is distributed with the header: @@ -62,16 +61,16 @@ !! of modified versions of the code in the Fortran Standard Library under !! the MIT license. -submodule(stdlib_sorting) stdlib_sorting_sort_index +submodule(stdlib_sorting) stdlib_sorting_sort_adjoint implicit none contains -#:for ki, ti, namei in INT_INDEX_TYPES_ALT_NAME +#:for ki, ti, tii, namei in IR_INDEX_TYPES_ALT_NAME #:for t1, t2, t3, name1 in IRSCB_TYPES_ALT_NAME - module subroutine ${name1}$_sort_index_${namei}$( array, index, work, iwork, reverse ) + module subroutine ${name1}$_${namei}$_sort_adjoint( array, adjoint_array, work, iwork, reverse ) ! A modification of `${name1}$_ord_sort` to return an array of indices that ! would perform a stable sort of the `ARRAY` as input, and also sort `ARRAY` ! as desired. The indices by default @@ -96,33 +95,21 @@ contains ! estimation of the optimal `run size` as suggested in Tim Peters' ! original `listsort.txt`, and the optional `work` and `iwork` arrays to be ! used as scratch memory. - ${t1}$, intent(inout) :: array(0:) - ${ti}$, intent(out) :: index(0:) + ${ti}$, intent(inout) :: adjoint_array(0:) ${t3}$, intent(out), optional :: work(0:) ${ti}$, intent(out), optional :: iwork(0:) logical, intent(in), optional :: reverse ${t2}$, allocatable :: buf(:) ${ti}$, allocatable :: ibuf(:) - integer(int_index) :: array_size, i, stat + integer(int_index) :: array_size + integer(int_index) :: stat array_size = size(array, kind=int_index) - if ( array_size > huge(index)) then - error stop "Too many entries for the kind of index." - end if - - if ( array_size > size(index, kind=int_index) ) then - error stop "Too many entries for the size of index." - end if - - do i = 0, array_size-1 - index(i) = int(i+1, kind=${ki}$) - end do - if ( optval(reverse, .false.) ) then - call reverse_segment( array, index ) + call reverse_segment( array, adjoint_array ) end if ! If necessary allocate buffers to serve as scratch memory. @@ -134,13 +121,14 @@ contains if ( size(iwork, kind=int_index) < array_size/2 ) then error stop "iwork array is too small." endif - call merge_sort( array, index, work, iwork ) + call merge_sort( array, adjoint_array, work, iwork ) else allocate( ibuf(0:array_size/2-1), stat=stat ) - if ( stat /= 0 ) error stop "Allocation of index buffer failed." - call merge_sort( array, index, work, ibuf ) + if ( stat /= 0 ) error stop "Allocation of adjoint_array buffer failed." + call merge_sort( array, adjoint_array, work, ibuf ) end if else +! Allocate a buffer to use as scratch memory. #:if t1[0:4] == "char" allocate( ${t3}$ :: buf(0:array_size/2-1), & stat=stat ) @@ -152,16 +140,16 @@ contains if ( size(iwork, kind=int_index) < array_size/2 ) then error stop "iwork array is too small." endif - call merge_sort( array, index, buf, iwork ) + call merge_sort( array, adjoint_array, buf, iwork ) else allocate( ibuf(0:array_size/2-1), stat=stat ) - if ( stat /= 0 ) error stop "Allocation of index buffer failed." - call merge_sort( array, index, buf, ibuf ) + if ( stat /= 0 ) error stop "Allocation of adjoint_array buffer failed." + call merge_sort( array, adjoint_array, buf, ibuf ) end if end if if ( optval(reverse, .false.) ) then - call reverse_segment( array, index ) + call reverse_segment( array, adjoint_array ) end if contains @@ -187,28 +175,28 @@ contains end function calc_min_run - pure subroutine insertion_sort( array, index ) + pure subroutine insertion_sort( array, adjoint_array ) ! Sorts `ARRAY` using an insertion sort, while maintaining consistency in ! location of the indices in `INDEX` to the elements of `ARRAY`. ${t1}$, intent(inout) :: array(0:) - ${ti}$, intent(inout) :: index(0:) + ${ti}$, intent(inout) :: adjoint_array(0:) integer(int_index) :: i, j - ${ti}$ :: key_index + ${ti}$ :: key_adjoint_array ${t3}$ :: key do j=1, size(array, kind=int_index)-1 key = array(j) - key_index = index(j) + key_adjoint_array = adjoint_array(j) i = j - 1 do while( i >= 0 ) if ( array(i) <= key ) exit array(i+1) = array(i) - index(i+1) = index(i) + adjoint_array(i+1) = adjoint_array(i) i = i - 1 end do array(i+1) = key - index(i+1) = key_index + adjoint_array(i+1) = key_adjoint_array end do end subroutine insertion_sort @@ -265,36 +253,36 @@ contains end function collapse - pure subroutine insert_head( array, index ) + pure subroutine insert_head( array, adjoint_array ) ! Inserts `array(0)` into the pre-sorted sequence `array(1:)` so that the ! whole `array(0:)` becomes sorted, copying the first element into ! a temporary variable, iterating until the right place for it is found. ! copying every traversed element into the slot preceding it, and finally, ! copying data from the temporary variable into the resulting hole. -! Consistency of the indices in `index` with the elements of `array` +! Consistency of the indices in `adjoint_array` with the elements of `array` ! are maintained. ${t1}$, intent(inout) :: array(0:) - ${ti}$, intent(inout) :: index(0:) + ${ti}$, intent(inout) :: adjoint_array(0:) ${t3}$ :: tmp integer(int_index) :: i - ${ti}$ :: tmp_index + ${ti}$ :: tmp_adjoint_array tmp = array(0) - tmp_index = index(0) + tmp_adjoint_array = adjoint_array(0) find_hole: do i=1, size(array, kind=int_index)-1 if ( array(i) >= tmp ) exit find_hole array(i-1) = array(i) - index(i-1) = index(i) + adjoint_array(i-1) = adjoint_array(i) end do find_hole array(i-1) = tmp - index(i-1) = tmp_index + adjoint_array(i-1) = tmp_adjoint_array end subroutine insert_head - subroutine merge_sort( array, index, buf, ibuf ) + subroutine merge_sort( array, adjoint_array, buf, ibuf ) ! The Rust merge sort borrows some (but not all) of the ideas from TimSort, ! which is described in detail at ! (http://svn.python.org/projects/python/trunk/Objects/listsort.txt). @@ -312,11 +300,11 @@ contains ! runs(i - 1)%len + runs(i)%len` ! ! The invariants ensure that the total running time is `O(n log n)` -! worst-case. Consistency of the indices in `index` with the elements of +! worst-case. Consistency of the indices in `adjoint_array` with the elements of ! `array` are maintained. ${t1}$, intent(inout) :: array(0:) - ${ti}$, intent(inout) :: index(0:) + ${ti}$, intent(inout) :: adjoint_array(0:) ${t3}$, intent(inout) :: buf(0:) ${ti}$, intent(inout) :: ibuf(0:) @@ -331,7 +319,7 @@ contains min_run = calc_min_run( array_size ) if ( array_size <= min_run ) then - if ( array_size >= 2 ) call insertion_sort( array, index ) + if ( array_size >= 2 ) call insertion_sort( array, adjoint_array ) return end if @@ -354,7 +342,7 @@ contains start = start - 1 end do Descending call reverse_segment( array(start:finish), & - index(start:finish) ) + adjoint_array(start:finish) ) else Ascending: do while( start > 0 ) if ( array(start) < array(start-1) ) exit Ascending @@ -367,7 +355,7 @@ contains Insert: do while ( start > 0 ) if ( finish - start >= min_run - 1 ) exit Insert start = start - 1 - call insert_head( array(start:finish), index(start:finish) ) + call insert_head( array(start:finish), adjoint_array(start:finish) ) end do Insert if ( start == 0 .and. finish == array_size - 1 ) return @@ -386,7 +374,7 @@ contains call merge( array( left % base: & right % base + right % len - 1 ), & left % len, buf, & - index( left % base: & + adjoint_array( left % base: & right % base + right % len - 1 ), ibuf ) runs(r) = run_type( base = left % base, & @@ -402,7 +390,7 @@ contains end subroutine merge_sort - pure subroutine merge( array, mid, buf, index, ibuf ) + pure subroutine merge( array, mid, buf, adjoint_array, ibuf ) ! Merges the two non-decreasing runs `ARRAY(0:MID-1)` and `ARRAY(MID:)` ! using `BUF` as temporary storage, and stores the merged runs into ! `ARRAY(0:)`. `MID` must be > 0, and < `SIZE(ARRAY)-1`. Buffer `BUF` @@ -410,7 +398,7 @@ contains ${t1}$, intent(inout) :: array(0:) integer(int_index), intent(in) :: mid ${t3}$, intent(inout) :: buf(0:) - ${ti}$, intent(inout) :: index(0:) + ${ti}$, intent(inout) :: adjoint_array(0:) ${ti}$, intent(inout) :: ibuf(0:) integer(int_index) :: array_len, i, j, k @@ -424,44 +412,44 @@ contains if ( mid <= array_len - mid ) then ! The left run is shorter. buf(0:mid-1) = array(0:mid-1) - ibuf(0:mid-1) = index(0:mid-1) + ibuf(0:mid-1) = adjoint_array(0:mid-1) i = 0 j = mid merge_lower: do k = 0, array_len-1 if ( buf(i) <= array(j) ) then array(k) = buf(i) - index(k) = ibuf(i) + adjoint_array(k) = ibuf(i) i = i + 1 if ( i >= mid ) exit merge_lower else array(k) = array(j) - index(k) = index(j) + adjoint_array(k) = adjoint_array(j) j = j + 1 if ( j >= array_len ) then array(k+1:) = buf(i:mid-1) - index(k+1:) = ibuf(i:mid-1) + adjoint_array(k+1:) = ibuf(i:mid-1) exit merge_lower end if end if end do merge_lower else ! The right run is shorter buf(0:array_len-mid-1) = array(mid:array_len-1) - ibuf(0:array_len-mid-1) = index(mid:array_len-1) + ibuf(0:array_len-mid-1) = adjoint_array(mid:array_len-1) i = mid - 1 j = array_len - mid -1 merge_upper: do k = array_len-1, 0, -1 if ( buf(j) >= array(i) ) then array(k) = buf(j) - index(k) = ibuf(j) + adjoint_array(k) = ibuf(j) j = j - 1 if ( j < 0 ) exit merge_upper else array(k) = array(i) - index(k) = index(i) + adjoint_array(k) = adjoint_array(i) i = i - 1 if ( i < 0 ) then array(0:k-1) = buf(0:j) - index(0:k-1) = ibuf(0:j) + adjoint_array(0:k-1) = ibuf(0:j) exit merge_upper end if end if @@ -470,10 +458,10 @@ contains end subroutine merge - pure subroutine reverse_segment( array, index ) + pure subroutine reverse_segment( array, adjoint_array ) ! Reverse a segment of an array in place ${t1}$, intent(inout) :: array(0:) - ${ti}$, intent(inout) :: index(0:) + ${ti}$, intent(inout) :: adjoint_array(0:) ${ti}$ :: itemp integer(int_index) :: lo, hi @@ -485,18 +473,19 @@ contains temp = array(lo) array(lo) = array(hi) array(hi) = temp - itemp = index(lo) - index(lo) = index(hi) - index(hi) = itemp + itemp = adjoint_array(lo) + adjoint_array(lo) = adjoint_array(hi) + adjoint_array(hi) = itemp lo = lo + 1 hi = hi - 1 end do end subroutine reverse_segment - end subroutine ${name1}$_sort_index_${namei}$ + end subroutine ${name1}$_${namei}$_sort_adjoint #:endfor #:endfor -end submodule stdlib_sorting_sort_index +end submodule stdlib_sorting_sort_adjoint + diff --git a/test/sorting/test_sorting.fypp b/test/sorting/test_sorting.fypp index 1418a032f..7e74927ea 100644 --- a/test/sorting/test_sorting.fypp +++ b/test/sorting/test_sorting.fypp @@ -1,11 +1,14 @@ #:include "common.fypp" +#:set INT_TYPES_ALT_NAME = list(zip(INT_TYPES, INT_TYPES, INT_KINDS)) +#:set REAL_TYPES_ALT_NAME = list(zip(REAL_TYPES, REAL_TYPES, REAL_KINDS)) + #:set INT_INDEX_TYPES_ALT_NAME = list(zip(["int_index", "int_index_low"], ["integer(int_index)", "integer(int_index_low)"], ["default", "low"])) module test_sorting use, intrinsic :: iso_fortran_env, only: compiler_version, error_unit - use stdlib_kinds, only: int32, int64, dp, sp - use stdlib_sorting + use stdlib_kinds, only: int8, int16, int32, int64, dp, sp + use stdlib_sorting, only: sort, sort_index, sort_adjoint, ord_sort, radix_sort, int_index, int_index_low use stdlib_string_type, only: string_type, assignment(=), operator(>), & operator(<), write(formatted) use stdlib_bitsets, only: bitset_64, bitset_large, & @@ -105,6 +108,13 @@ contains new_unittest('string_sort_indexes_${namei}$', test_string_sort_indexes_${namei}$), & new_unittest('bitset_large_sort_indexes_${namei}$', test_bitsetl_sort_indexes_${namei}$), & new_unittest('bitset_64_sort_indexes_${namei}$', test_bitset64_sort_indexes_${namei}$), & +#:endfor +#:for ki, ti, namei in INT_TYPES_ALT_NAME + new_unittest('int_sort_adjointes_${namei}$', test_int_sort_adjointes_${namei}$), & + new_unittest('char_sort_adjointes_${namei}$', test_char_sort_adjointes_${namei}$), & + new_unittest('string_sort_adjointes_${namei}$', test_string_sort_adjointes_${namei}$), & + new_unittest('bitset_large_sort_adjointes_${namei}$', test_bitsetl_sort_adjointes_${namei}$), & + new_unittest('bitset_64_sort_adjointes_${namei}$', test_bitset64_sort_adjointes_${namei}$), & #:endfor new_unittest('int_ord_sorts', test_int_ord_sorts) & ] @@ -1542,6 +1552,350 @@ contains end subroutine test_bitset64_sort_index_${namei}$ #:endfor +#:for ki, ti, namei in INT_TYPES_ALT_NAME + subroutine test_int_sort_adjointes_${namei}$(error) + !> Error handling + type(error_type), allocatable, intent(out) :: error + integer(int64) :: i + integer(int32), allocatable :: d1(:) + ${ti}$, allocatable :: adjoint(:) + logical :: ltest + + call test_int_sort_adjoint_${namei}$( blocks, "Blocks", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( decrease, "Decreasing", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( identical, "Identical", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( increase, "Increasing", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( rand1, "Random dense", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( rand2, "Random order", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( rand0, "Random sparse", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( rand3, "Random 3", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_int_sort_adjoint_${namei}$( rand10, "Random 10", ltest ) + call check(error, ltest) + if (allocated(error)) return + + d1 = [10, 2, -3, -4, 6, -6, 7, -8, 9, 0, 1, 20] + allocate( adjoint(size(d1))) + adjoint = int([(i, i=1, size(d1))], kind=${namei}$) + call sort_adjoint( d1, adjoint ) + call verify_sort( d1, ltest, i ) + call check(error, ltest) + + end subroutine test_int_sort_adjointes_${namei}$ + + subroutine test_int_sort_adjoint_${namei}$( a, a_name, ltest ) + integer(int32), intent(inout) :: a(:) + character(*), intent(in) :: a_name + logical, intent(out) :: ltest + + integer(int64) :: t0, t1, tdiff + real(dp) :: rate + ${ti}$ :: adjoint(size(a)) + ${ti}$ :: iwork(size(a)) + integer(int64) :: i, j + logical :: valid + + ltest = .true. + + tdiff = 0 + do i = 1, repeat + dummy = a + adjoint = int([(j, j=1_int64, size(a, kind=1_int64))], kind=${namei}$) + call system_clock( t0, rate ) + call sort_adjoint( dummy, adjoint, work, iwork ) + call system_clock( t1, rate ) + tdiff = tdiff + t1 - t0 + end do + tdiff = tdiff/repeat + + call verify_sort( dummy, valid, i ) + ltest = (ltest .and. valid) + if ( .not. valid ) then + write( *, * ) "SORT_ADJOINT did not sort " // a_name // "." + write(*,*) 'i = ', i + write(*,'(a18, 2i7)') 'a(i-1:i) = ', a(i-1:i) + end if + write( lun, '("| Integer |", 1x, i7, 2x, "|", 1x, a15, " |", ' // & + 'a12, " |", F10.6, " |" )' ) & + test_size, a_name, "Sort_adjoint", tdiff/rate + + !reverse + dummy = a + adjoint = int([(j, j=1_int64, size(a, kind=1_int64))], kind=${namei}$) + call sort_adjoint( dummy, adjoint, work, iwork, reverse=.true. ) + + call verify_reverse_sort( dummy, valid, i ) + ltest = (ltest .and. valid) + if ( .not. valid ) then + write( *, * ) "SORT_ADJOINT did not reverse sort " // & + a_name // "." + write(*,*) 'i = ', i + write(*,'(a18, 2i7)') 'a(i-1:i) = ', a(i-1:i) + end if + + end subroutine test_int_sort_adjoint_${namei}$ + + subroutine test_char_sort_adjointes_${namei}$(error) + !> Error handling + type(error_type), allocatable, intent(out) :: error + logical :: ltest + + call test_char_sort_adjoint_${namei}$( char_decrease, "Char. Decrease", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_char_sort_adjoint_${namei}$( char_increase, "Char. Increase", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_char_sort_adjoint_${namei}$( char_rand, "Char. Random", ltest ) + call check(error, ltest) + + end subroutine test_char_sort_adjointes_${namei}$ + + subroutine test_char_sort_adjoint_${namei}$( a, a_name, ltest ) + character(len=4), intent(in) :: a(0:) + character(*), intent(in) :: a_name + logical, intent(out) :: ltest + + ${ti}$ :: adjoint(size(a)) + ${ti}$ :: iwork(size(a)) + integer(int64) :: t0, t1, tdiff + real(dp) :: rate + integer(int64) :: i, j + logical :: valid + + ltest = .true. + + tdiff = 0 + do i = 1, repeat + char_dummy = a + adjoint = int([(j, j=1_int64, size(a, kind=1_int64))], kind=${namei}$) + call system_clock( t0, rate ) + + call sort_adjoint( char_dummy, adjoint, char_work, iwork ) + + call system_clock( t1, rate ) + + tdiff = tdiff + t1 - t0 + end do + tdiff = tdiff/repeat + + call verify_char_sort( char_dummy, valid, i ) + + ltest = (ltest .and. valid) + if ( .not. valid ) then + write( *, * ) "SORT_ADJ did not sort " // a_name // "." + write(*,*) 'i = ', i + write(*,'(a17, 2(1x,a4))') 'char_dummy(i-1:i) = ', char_dummy(i-1:i) + end if + write( lun, '("| Character |", 1x, i7, 2x, "|", 1x, a15, " |", ' // & + 'a12, " |", F10.6, " |" )' ) & + char_size, a_name, "Sort_adjoint", tdiff/rate + + end subroutine test_char_sort_adjoint_${namei}$ + + subroutine test_string_sort_adjointes_${namei}$(error) + !> Error handling + type(error_type), allocatable, intent(out) :: error + logical :: ltest + + call test_string_sort_adjoint_${namei}$( string_decrease, "String Decrease", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_string_sort_adjoint_${namei}$( string_increase, "String Increase", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_string_sort_adjoint_${namei}$( string_rand, "String Random", ltest ) + call check(error, ltest) + + end subroutine test_string_sort_adjointes_${namei}$ + + subroutine test_string_sort_adjoint_${namei}$( a, a_name, ltest ) + type(string_type), intent(in) :: a(0:) + character(*), intent(in) :: a_name + logical, intent(out) :: ltest + + ${ti}$ :: adjoint(size(a)) + ${ti}$ :: iwork(size(a)) + integer(int64) :: t0, t1, tdiff + real(dp) :: rate + integer(int64) :: i, j + logical :: valid + + ltest = .true. + + tdiff = 0 + do i = 1, repeat + string_dummy = a + adjoint = int([(j, j=1_int64, size(a, kind=1_int64))], kind=${namei}$) + call system_clock( t0, rate ) + call sort_adjoint( string_dummy, adjoint, string_work, iwork ) + call system_clock( t1, rate ) + tdiff = tdiff + t1 - t0 + end do + tdiff = tdiff/repeat + + call verify_string_sort( string_dummy, valid, i ) + ltest = (ltest .and. valid) + if ( .not. valid ) then + write( *, * ) "SORT_ADJOINT did not sort " // a_name // "." + write(*,*) 'i = ', i + write(*,'(a17, 2(1x,a4))') 'string_dummy(i-1:i) = ', & + string_dummy(i-1:i) + end if + write( lun, '("| String_type |", 1x, i7, 2x, "|", 1x, a15, " |", ' // & + 'a12, " |", F10.6, " |" )' ) & + string_size, a_name, "Sort_adjoint", tdiff/rate + + end subroutine test_string_sort_adjoint_${namei}$ + + subroutine test_bitsetl_sort_adjointes_${namei}$(error) + !> Error handling + type(error_type), allocatable, intent(out) :: error + logical :: ltest + + call test_bitsetl_sort_adjoint_${namei}$( bitsetl_decrease, "Bitset Decrease", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_bitsetl_sort_adjoint_${namei}$( bitsetl_increase, "Bitset Increase", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_bitsetl_sort_adjoint_${namei}$( bitsetl_rand, "Bitset Random", ltest ) + call check(error, ltest) + + end subroutine test_bitsetl_sort_adjointes_${namei}$ + + subroutine test_bitsetl_sort_adjoint_${namei}$( a, a_name, ltest ) + type(bitset_large), intent(in) :: a(0:) + character(*), intent(in) :: a_name + logical, intent(out) :: ltest + + ${ti}$ :: adjoint(size(a)) + ${ti}$ :: iwork(size(a)) + integer(int64) :: t0, t1, tdiff + real(dp) :: rate + integer(int64) :: i, j + logical :: valid + character(:), allocatable :: bin_im1, bin_i + + ltest = .true. + + tdiff = 0 + do i = 1, repeat + bitsetl_dummy = a + adjoint = int([(j, j=1_int64, size(a, kind=1_int64))], kind=${namei}$) + call system_clock( t0, rate ) + call sort_adjoint( bitsetl_dummy, adjoint, bitsetl_work, iwork ) + call system_clock( t1, rate ) + tdiff = tdiff + t1 - t0 + end do + tdiff = tdiff/repeat + + call verify_bitsetl_sort( bitsetl_dummy, valid, i ) + ltest = (ltest .and. valid) + if ( .not. valid ) then + write( *, * ) "SORT_ADJOINT did not sort " // a_name // "." + write(*,*) 'i = ', i + call bitsetl_dummy(i-1)%to_string(bin_im1) + call bitsetl_dummy(i)%to_string(bin_i) + write(*,'(a, 2(a:,1x))') 'bitsetl_dummy(i-1:i) = ', & + bin_im1, bin_i + end if + write( lun, '("| Bitset_large |", 1x, i7, 2x, "|", 1x, a15, " |", ' // & + 'a12, " |", F10.6, " |" )' ) & + bitset_size, a_name, "Sort_adjoint", tdiff/rate + + end subroutine test_bitsetl_sort_adjoint_${namei}$ + + subroutine test_bitset64_sort_adjointes_${namei}$(error) + !> Error handling + type(error_type), allocatable, intent(out) :: error + logical :: ltest + + call test_bitset64_sort_adjoint_${namei}$( bitset64_decrease, "Bitset Decrease", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_bitset64_sort_adjoint_${namei}$( bitset64_increase, "Bitset Increase", ltest ) + call check(error, ltest) + if (allocated(error)) return + + call test_bitset64_sort_adjoint_${namei}$( bitset64_rand, "Bitset Random", ltest ) + call check(error, ltest) + + end subroutine test_bitset64_sort_adjointes_${namei}$ + + subroutine test_bitset64_sort_adjoint_${namei}$( a, a_name, ltest ) + type(bitset_64), intent(in) :: a(0:) + character(*), intent(in) :: a_name + logical, intent(out) :: ltest + + ${ti}$ :: adjoint(size(a)) + ${ti}$ :: iwork(size(a)) + integer(int64) :: t0, t1, tdiff + real(dp) :: rate + integer(int64) :: i, j + logical :: valid + character(:), allocatable :: bin_im1, bin_i + + ltest = .true. + + tdiff = 0 + do i = 1, repeat + bitset64_dummy = a + adjoint = int([(j, j=1_int64, size(a, kind=1_int64))], kind=${namei}$) + call system_clock( t0, rate ) + call sort_adjoint( bitset64_dummy, adjoint, bitset64_work, iwork ) + call system_clock( t1, rate ) + tdiff = tdiff + t1 - t0 + end do + tdiff = tdiff/repeat + + call verify_bitset64_sort( bitset64_dummy, valid, i ) + ltest = (ltest .and. valid) + if ( .not. valid ) then + write( *, * ) "SORT_ADJOINT did not sort " // a_name // "." + write(*,*) 'i = ', i + call bitset64_dummy(i-1)%to_string(bin_im1) + call bitset64_dummy(i)%to_string(bin_i) + write(*,'(a, 2(a:,1x))') 'bitset64_dummy(i-1:i) = ', & + bin_im1, bin_i + end if + write( lun, '("| Bitset_64 |", 1x, i7, 2x, "|", 1x, a15, " |", ' // & + 'a12, " |", F10.6, " |" )' ) & + bitset_size, a_name, "Sort_adjoint", tdiff/rate + + end subroutine test_bitset64_sort_adjoint_${namei}$ +#:endfor + subroutine verify_sort( a, valid, i ) integer(int32), intent(in) :: a(0:) logical, intent(out) :: valid