Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quadrature: spec tweaks & Simpson's rule #209

Merged
merged 20 commits into from
Jun 15, 2020

Conversation

nshaffer
Copy link
Contributor

@nshaffer nshaffer commented Jun 3, 2020

Implementation of simps and simps_weights

Interfaces and implementations are for 1d real arrays. The included tests exercise the routines for integrands which are (analytically) exactly integrated by Simpson's rule. Known edge cases (length 0, 1, or 6) arrays are explicitly tested as well. There is a lot of new code, and it is not always simple, but hopefully the tests stand as an empirical demonstration of correctness.

Q: To simplify the implementation, simps and simps_weights have been made recursive. This is only an implementation detail. Am I right to think pure recursive does not limit the routines' usability any more than just pure, e.g., in do concurrent or where constructs?

Changes to simps and simps_weights optional argument even

In the current spec, the optional argument even is an integer that is supposed to let the user specify the index at which to insert a Simpson's 3/8 rule so that even-length arrays can be treated. In implementation, this introduced too many corner cases to be worthwhile. In this implementation, even is still an integer, but its meaning is different. If it is negative, the 3/8 rule is used for the leftmost abscissas. If it is positive, the 3/8 rule is used for the rightmost abscissas. If it is zero or absent, the result is as if the even<0 and even>0 cases were averaged. There are still corner cases and complicated control flow, but it's much simpler than the alternative. The spec has been revised to reflect this new behavior.

Improved fypp style

Looking toward future extensions to complex types and multiple ranks, the fypp style has been changed to more closely match that of other heavily preprocessed modules.

trapz_weights example "bugfix"

In the spec for trapz_weights, the example had a typo and also used dot_product to evaluate the integral. Looking toward implementation for complex integrands, this seems wrong (introduces a complex conjugate), so the example is changed to use sum instead.

@nshaffer
Copy link
Contributor Author

nshaffer commented Jun 3, 2020

It looks like the error tolerance is too strict in some of the tests in the CI. Of course they all pass on my machine ;) (resolved)

Copy link
Member

@jvdp1 jvdp1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this implementation. I am not the target user for such functions. However, I had some minor comments. If they are inappropriate, please ignore them.

Note: For an easier review, you could maybe submit the changes of trapz and trapz_weights in another PR (not needed for me though).

src/tests/quadrature/test_simps.f90 Outdated Show resolved Hide resolved
src/stdlib_experimental_quadrature.fypp Outdated Show resolved Hide resolved
src/stdlib_experimental_quadrature.fypp Outdated Show resolved Hide resolved
src/stdlib_experimental_quadrature_simps.fypp Show resolved Hide resolved
src/stdlib_experimental_quadrature_simps.fypp Show resolved Hide resolved
src/stdlib_experimental_quadrature_simps.fypp Show resolved Hide resolved
#:for k1, t1 in REAL_KINDS_TYPES
pure module function trapz_x_${k1}$(y, x) result(integral)
${t1}$, dimension(:), intent(in) :: y
${t1}$, dimension(size(y)), intent(in) :: x
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a private auxiliary function where you know that the actual argument for x will be contiguous in practice, or is this a public user-callable function? (I haven't looked carefully at the whole component to know.) If it is the latter, I don't think you want to be declaring x as an explicit-shape array, dimension(size(y)). This implies F77-style argument passing, and if the actual argument isn't contiguous it will do an implicit copy-in of that argument to a contiguous temporary and pass it instead. Clearly x and y need to have the same size, but that style of declaration does nothing whatsoever to ensure that that is the case (in fact it hides the true length of the actual argument). This really requires a run-time check or assertion, with x declared as an assumed-shape array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This routine is public by way of the generic trapz interface, so your comment is germane. I have no complaints about using assumed-shape declaration here, but I need to educate myself more on array argument passing to understand why it matters. It is a little surprising to learn that the explicit-shape declaration introduces a temporary for non-contiguous actual arguments, whereas the assumed-shape declaration does not.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is described is just the implementation detail of (a) specific compiler(s). Although I didn't have the time to check, I strongly doubt that any of such considerations are present in the standard.

Copy link
Member

@nncarlson nncarlson Jun 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite true that this isn't explicitly stated in the standard, however it is implied. The array ${t1}$, dimension(size(y)), intent(in) :: x is simply contiguous (6.5.4) whereas the corresponding actual argument need not be. The only way for that to work is for the array data to be reorganized into contiguous storage.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that the only possible way to match a non-contiguous array with a contiguous one is by implicit copy-in? I don't know the answer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the recommendation! I like the approach of making the in-argument assumed shape, and then later benchmarking against making it contiguous as well.

A related questions is if function results should also be assumed-shape. E.g.,

function trapz_weights_dp(x) result(w)
real(dp), dimension(:), intent(in) :: x
real(dp), dimension(size(x)) :: w ! explicit-shape result
! real(dp), dimension(:), allocatable :: w ! assumed-shape result
...
end function trapz_weights_dp

The discussion in #114 suggests that allocatable function results are undesirable. Do I understand right that an explicit-shape result is still expected to be the better option?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure there was any conclusion in #114 as to which performed better. The issue probably being whether the array was allocated on the stack or the heap. I haven't looked to see how you're using this particular function, but personally I've switched to generally avoiding array-valued functions altogether, as I feel this results in a lot of unnecessary allocations/deallocations of memory that passing the result as an argument avoids. I'm not really confident that compiler optimizers are able to eliminate these.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, there is no such thing as an "assumed-shape" function result. It's just an allocatable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@klausler, thanks for that excellent advice and explanation. @milancurcic we should put this recommendation somewhere in our tutorial section or FAQ, as this question will keep coming up often.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just pushed a commit that makes the x argument of trapz_x_* and simps_x_* assumed-shape with a runtime check on its size. Thanks, @nncarlson, for raising the point, and @epagone and @klausler for the discussion.

@jvdp1
Copy link
Member

jvdp1 commented Jun 3, 2020

Thanks for the answers. I have additional minor comments. In the specs, could you:

  • remove "to be implemented" in the titles?
  • replace simps in result = simps... by something like result = [[stdlib_experimental_quadrature(module):simps(interface)]](....) for the docs?

@nshaffer
Copy link
Contributor Author

nshaffer commented Jun 4, 2020

Thanks for the answers. I have additional minor comments. In the specs, could you:

* remove "to be implemented" in the titles?

* replace `simps` in `result = simps...` by something like `result = [[stdlib_experimental_quadrature(module):simps(interface)]](....)` for the docs?

Sure, no problemo. I see that there are now also links for the docs in stdlib_experimental_quadrature.fypp for the trapz and trapz_weights interfaces. I suppose we want the same for simps and simps_weights, but I don't know how to get the URL right. Do you know what to do?

@jvdp1
Copy link
Member

jvdp1 commented Jun 4, 2020

I suppose we want the same for simps and simps_weights

It would be good, indeed. You can use a similar link as for trapz:

        !! Integrates sampled values using trapezoidal rule
        !! ([Specification](../page/specs/stdlib_experimental_quadrature.html#description))	

The word description must be adapted (e.g., description_2)

@nshaffer
Copy link
Contributor Author

nshaffer commented Jun 4, 2020

I suppose we want the same for simps and simps_weights

It would be good, indeed. You can use a similar link as for trapz:

        !! Integrates sampled values using trapezoidal rule
        !! ([Specification](../page/specs/stdlib_experimental_quadrature.html#description))	

The word description must be adapted (e.g., description_2)

Cool beans, made sense after I installed FORD and built the docs. Should be taken care of now.

@jvdp1
Copy link
Member

jvdp1 commented Jun 4, 2020

Cool beans, made sense after I installed FORD and built the docs. Should be taken care of now.

Note that you can load it from here.

Copy link
Member

@certik certik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks good to me, I think the API works.

After all feedback and comments are addressed, I am fine with merging.

${t1}$ :: a, b, c

n = size(y)
if (size(x) /= n) error stop "simps: Arguments `x` and `y` must be the same size."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you use the stdlib subroutine error_stop to avoid issues with older Fortran standards

Suggested change
if (size(x) /= n) error stop "simps: Arguments `x` and `y` must be the same size."
if (size(x) /= n) call error_stop("simps: Arguments `x` and `y` must be the same size.")

The functions cannot be pure when calling error_stop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a defect of our error_stop. The only functionality it adds over a bare error stop statement is to optionally put both a message and an error code. Because that message is written to stderr, purity is lost. At least with a bare error stop, we can keep purity with sufficiently recent compilers (e.g., GCC version 8 or newer). I am perfectly happy to pretend pre-f2018 standards do not exist, but I think that does not align with stdlib's current goals.

The alternative is to revert to declaring x to be dimension(size(y)). This has the potential performance penalties described in other comments, but at least the compiler/runtime will do the error checking and reporting for us without running afoul of purity rules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think reverting to declaring x to be dimension(size(y)) is a good idea, as mentioned in earlier comments.

It has been agreed that Gfortran <=7 were not supported for stdlib. However, recent versions of other compilers may not support the 2018 error stop yet. I am perfectly ok to use the 2018 error stop, but this will restrict stdlib to very recent compilers.

@certik
Copy link
Member

certik commented Jun 7, 2020 via email

@nshaffer
Copy link
Contributor Author

nshaffer commented Jun 7, 2020

I would recommend to recommend to use error_stop and open another issue to get rid of it all over stdlib, if desired.

On Sun, Jun 7, 2020, at 10:04 AM, Jeremie Vandenplas wrote: @.**** commented on this pull request. In src/stdlib_experimental_quadrature_simps.fypp <#209 (comment)>: > +#:for k1, t1 in REAL_KINDS_TYPES + + pure recursive module function simps_x_${k1}$(y, x, even) result(integral) + ${t1}$, dimension(:), intent(in) :: y + ${t1}$, dimension(:), intent(in) :: x + integer, intent(in), optional :: even + ${t1}$ :: integral + + integer :: i + integer :: n + + ${t1}$ :: h1, h2 + ${t1}$ :: a, b, c + + n = size(y) + if (size(x) /= n) error stop "simps: Arguments x and y must be the same size." I don't think reverting to declaring x to be dimension(size(y)) is a good idea, as mentioned in earlier comments. It has been agreed that Gfortran <=7 were not supported for stdlib. However, recent versions of other compilers may not support the 2018 error stop yet. I am perfectly ok to use the 2018 error stop, but this will restrict stdlib to very recent compilers. — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#209 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAFAWHJR7OUKKKYDXBNKUDRVO3B3ANCNFSM4NRLGEBA.

Ok, done. I've replaced the size check using error_stop with a call to check and dropped the pure attributed from simps_x_* and trapz_x_*.

Copy link
Member

@jvdp1 jvdp1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. +1 to merge

@jvdp1
Copy link
Member

jvdp1 commented Jun 15, 2020

All comments were answered. I'll merge it.
Thank you @nshaffer

@jvdp1 jvdp1 merged commit 0cd354a into fortran-lang:master Jun 15, 2020
@certik
Copy link
Member

certik commented Jun 15, 2020

@nshaffer do you want to open a new issue for the "error_stop" thing we discussed above?

@jvdp1
Copy link
Member

jvdp1 commented Jun 15, 2020

@nshaffer do you want to open a new issue for the "error_stop" thing we discussed above?

Related to #212

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants