Show HN: Rust library for numerical integration of real-valued functions

125 points - last Tuesday at 12:41 PM


Integrate is a fast, small, lightweight Rust library for performing numerical integration of real-valued functions. It is designed to integrate functions, providing a simple and efficient way to approximate definite integrals using various numerical methods.

Integrate supports a variety of numerical integration techniques: - Newton-Cotes methods:

  - Rectangle Rule.
  - Trapezoidal Rule.
  - Simpson's Rule.
  - Newton's 3/8 Rule.
- Gauss quadrature methods:

  - Gauss-Legendre.
  - Gauss-Laguerre.
  - Gauss-Hermite.
  - Gauss-Chebyshev First Kind.
  - Gauss-Chebyshev Second Kind.
- Adaptive Methods:

  - Adaptive Simpson's method
- Romberg’s method.

Source
  • antononcube

    last Tuesday at 4:46 PM

    I think the project should be called "NIntegrate".

    BTW, that is not a serious suggestion; it is just that Wolfram Language (aka Mathematica) has both `Integrate` and `NIntegrate` for symbolic and numeric integration, respectively.

      • last Tuesday at 6:26 PM

    • zackangelo

      last Tuesday at 8:05 PM

      > Does the function oscillate over the region of integration? If it does, then make sure that the step size is chosen to be smaller than the wave length of the function.

      Nyquist limit, but for numerical integration?

        • alleycat5000

          last Tuesday at 11:58 PM

          One way to think about is that these techniques work by integrating exactly the polynomial that interpolates the function where you're sampling it, so you need to resolve the features of the function to get good accuracy.

          • dawnofdusk

            last Tuesday at 11:09 PM

            The Nyquist sampling theorem is of course proved by considering a Fourier transform, which is given by an integral, so the relation to integration in general should not be surprising.

            • im3w1l

              last Tuesday at 10:49 PM

              Well in both cases it comes down to aliasing I think (high frequency wave presents as low frequency wave with too big step size)

          • JanisErdmanis

            last Tuesday at 2:42 PM

            It looks a bit sloppy to hardcode so many constants in a single file: `src/gauss_quadrature/legendre.rs`. Isn't it possible to generate them with the help of rust macros in the same way Julia uses metaprogramming?

              • ok123456

                last Tuesday at 3:18 PM

                Gaussian quadrature points are typically solved numerically. There's a good chance these ultimately came from a table.

                Additionally, compile time floating-point evaluation is limited. When I looked around recently, I didn't see a rust equivalent of gcem; any kind of transcendental function evaluation (which finding Gaussian quadrature points absolutely would require) would not allow compile-time evaluation.

                  • AlotOfReading

                    last Tuesday at 4:29 PM

                    Support for float const fns was merged just a couple months ago and hasn't been officially announced yet.

                      • BD103

                        last Tuesday at 4:54 PM

                        Support for constant float operations was released in Rust 1.82! https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html

                        • ok123456

                          last Tuesday at 4:37 PM

                          IIRC, that only supports elementary arithmetic operations. Useful but not general.

                            • AlotOfReading

                              last Tuesday at 4:54 PM

                              It's relatively straightforward to build transcendental functions out of the basic operations and the stdlib support will eventually get there, but rust's float story is still a work in progress. They're trying to do things more properly and document semantics better than C and C++ have.

                      • zokier

                        last Tuesday at 6:44 PM

                        I was under the impression that macros can execute arbitrary code, surely some FP would not be big problem. And if not macros then build.rs script certainly could do the trick.

                          • dhosek

                            last Tuesday at 7:39 PM

                            build.rs can definitely execute arbitrary code, which means that a lot of places (including, IIRC crates.io) will forbid crates that rely on build.rs. I ended up refactoring my build.rs into a separate sub-application in finl_unicode that built data tables which are then checked into git and used pre-built. I include the sub-app in the source code so that anyone with access to the repo could continue development if I were to die tomorrow.

                              • n_plus_1_acc

                                last Tuesday at 9:09 PM

                                There are many crates with build.rs scripts on crates.io, because they host just the source code with the Cargo.{toml,lock}.

                                  • dhosek

                                    last Tuesday at 10:42 PM

                                    I ran into some issues with crates.io and my build.rs when I first released the crate, although it’s long enough ago, that I don’t remember exactly what the issue was. It might also have been that the build.rs script downloaded raw data files from unicode.org

                                      • Arnavion

                                        last Wednesday at 2:20 AM

                                        crates.io doesn't care what your build.rs does because it doesn't try to compile your code, neither now or ever in the past. There would be no point in it trying to compile your code; there are lots of crates that are bindings to C libraries that crates.io's builders can't be expected to have, crates that target architectures which crates.io can't be expected to have builders for, etc.

                    • mtantaoui

                      last Tuesday at 4:55 PM

                      this was mainly to use an Iteration free method in this paper: https://www.cfm.brown.edu/faculty/gk/APMA2560/Handouts/GL_qu...

                      this method is much faster and simpler.

                      • two_handfuls

                        last Tuesday at 4:19 PM

                        Probably but that would slow down compilation a lot.

                          • simlevesque

                            last Tuesday at 6:06 PM

                            Exactly, it's not like the constants are gonna change.

                            • blharr

                              last Tuesday at 6:32 PM

                              You wouldn't have to recompile them every time. What if you didn't necessarily use macros but auto-generated it in a file that you keep separate from the other code at the bottom?

                              • dataflow

                                last Wednesday at 1:52 AM

                                What I would do in these cases is to define the general computation function, but special-case it to return the hard-coded value for specific common inputs if it's being evaluated at compile time. Then add a test to verify both behaviors.

                        • wjholden

                          last Tuesday at 2:49 PM

                          I was always amazed that R can do:

                            > integrate(dnorm, -Inf, +Inf)
                            1 with absolute error < 9.4e-05
                          
                          Can we do the same in this library?

                            • legobmw99

                              last Tuesday at 3:57 PM

                              It seems like it is lacking the functionality R's integrate has for handling infinite boundaries, but I suppose you could implement that yourself on the outside.

                              For what it's worth,

                                  use integrate::adaptive_quadrature::simpson::adaptive_simpson_method;
                                  use statrs::distribution::{Continuous, Normal};
                              
                                  fn dnorm(x: f64) -> f64 {
                                      Normal::new(0.0, 1.0).unwrap().pdf(x)
                                  }
                                  
                                  fn main() {
                                      let result = adaptive_simpson_method(dnorm, -100.0, 100.0, 1e-2, 1e-8);
                                      println!("Result: {:?}", result);
                                  }
                              
                              prints Result: Ok(1.000000000053865)

                              It does seem to be a usability hazard that the function being integrated is defined as a fn, rather than a Fn, as you can't pass closures that capture variables, requiring the weird dnorm definition

                              • antononcube

                                last Tuesday at 4:48 PM

                                You will be completely blown away, then, from what Wolfram Language (aka Mathematica) can do. (When it comes to numerical integration.)

                                https://reference.wolfram.com/language/tutorial/NIntegrateOv...

                                • mtantaoui

                                  last Tuesday at 5:01 PM

                                  for ]-inf, inf[ integrals, you can use Gauss Hermite method, just keep in mind to multiply your function with exp(x^2).

                                      use integrate::{
                                          gauss_quadrature::hermite::gauss_hermite_rule,
                                      };
                                      use statrs::distribution::{Continuous, Normal};
                                  
                                      fn dnorm(x: f64) -> f64 {
                                          Normal::new(0.0, 1.0).unwrap().pdf(x)* x.powi(2).exp()
                                      }
                                  
                                      fn main() {
                                          let n: usize = 170;
                                          let result = gauss_hermite_rule(dnorm, n);
                                          println!("Result: {:?}", result);
                                      }
                                  
                                  
                                  I got Result: 1.0000000183827922.

                                  • Buttons840

                                    last Tuesday at 3:48 PM

                                    How many evaluations of the underlying function does it make? (Hoping someone will fire up their R interpreter and find out.)

                                    Or, probably, dnorm is a probability distribution which includes a likeliness function, and a cumulative likeliness function, etc. I bet it doesn't work on arbitrary functions.

                                      • thrasibule

                                        last Tuesday at 3:58 PM

                                        R integrate is just a wrapper around quadpack. It works with arbitrary functions, but arguably dnorm is pretty well behaved.

                                • last Tuesday at 12:41 PM

                                  • wiz21c

                                    last Tuesday at 7:50 PM

                                    In the rectangle method, there is "let x = a + i * h + (h / F1::from(2)...)"

                                    I didn't check, but I wonder if it is not better to do x = a + (i+0.5)*h... My reasoning is that if "i" is very big, then i * h can be much bigger than h/2 and thus h/2 may be eaten by numerical precision when added to i*h... And then you have to convince the optimizer to do it the way you want. But well, it's late...

                                      • zokier

                                        last Tuesday at 10:37 PM

                                        herbie recommends `fma(h, (i + 0.5), a)`, but also doesn't report any accuracy problems with the original either

                                          • wiz21c

                                            last Wednesday at 8:40 AM

                                            yeah, fused mul-add is certainly better. Dunno how one epxresses that in rust though :-) Ahhh seems like there at least f64::mul_add() in stdlib :-)

                                    • last Tuesday at 2:51 PM

                                      • cozzyd

                                        last Tuesday at 5:53 PM

                                        I don't see any explicit SIMD in here. Is the rust compiler able to emit SIMD instructions automatically in cases like this? (I guess I could compile and disassemble to check... )

                                          • jvanderbot

                                            last Tuesday at 5:59 PM

                                            In my experience Rust is very good about using simd for loading and not great at using it automatically for math. This is from some experimentation at work and checking disassembly so ymmv

                                            There are common library extensions for that.

                                              • cozzyd

                                                last Wednesday at 2:08 AM

                                                    ymmv
                                                
                                                Or zmmv if avx-512 is supported?

                                                  • jvanderbot

                                                    last Wednesday at 4:51 PM

                                                    This is a good joke, if anyone is wondering.

                                        • zppln

                                          last Tuesday at 7:12 PM

                                          Fast, small, lightweight compared to what?

                                            • aDyslecticCrow

                                              last Wednesday at 2:57 PM

                                              It's a minimal implementation (small, lightweight) of well-known and established algorithms (fast).

                                              • akkad33

                                                last Tuesday at 8:36 PM

                                                Those are adjectives not comparatives.

                                            • antononcube

                                              last Tuesday at 4:49 PM

                                              Thanks for showing this! It is very motivating to develop (and finish) my Raku numerical integration project.

                                                • mtantaoui

                                                  last Tuesday at 5:21 PM

                                                  Thanks! That’s awesome to hear—I’d love to see how your Raku numerical integration project turns out!

                                                  You can email me if you want to, I'll be happy to help.

                                              • legobmw99

                                                last Tuesday at 8:57 PM

                                                Is there a technical reason to now allow closures as the integrand?

                                                  • n_plus_1_acc

                                                    last Tuesday at 9:10 PM

                                                    Mayve because they aren't guaranteed to be actual functions (in the mathematical sense) and could return random values

                                                      • legobmw99

                                                        last Tuesday at 9:17 PM

                                                        The Fn trait could be used, which prevents mutation, but allows a lot of useful closures. I should note, a motivated user could provide a junk function no matter what the type accepted is

                                                • last Tuesday at 1:20 PM

                                                  • Dowwie

                                                    last Tuesday at 3:57 PM

                                                    [flagged]