npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

odex

v3.0.0-rc.4

Published

Ernst Hairer's ODEX nonstiff ODE solver in JavaScript

Downloads

357

Readme

odex-js : ODEX in JavaScript

Build Status GitHub license npm Coverage Status

Numerically solves of non-stiff systems of ordinary differential equations in JavaScript.

This is a port to JavaScript (actually, TypeScript) of E. Hairer and G. Wanner's implementation of the Gragg-Bulirsch-Stoer method of integrating systems of differential equations. The original code is written in idiomatic Fortran; this code tries to present an idiomatic JavaScript interface while preserving all the of the virtues of the original code, including its speed, configurability, and compact memory footprint.

Examples

(We'll write the usage examples in plain JavaScript. The usage from TypeScript is very similar.)

One first-order equation

The simplest possible example would be y′ = y, with y(0) = 1: we expect the solution y(x) = exp(x). First we create a function to represent this equation:

function yprime(x, y) {
  return y;
}

Since we asked for one independent variable, y is an array of length 1. We return an array of the same size.

Next we create a solver object, telling how many independent variables there are in the system (in this case just one).

var odex = require('odex');
var s = new odex.Solver(f, 1);

Calling integrate on the solver object, supplying the initial conditions x0 and y0 = f(x0) will return a solution function we can use to obtain y values at different points. In this case, we supply the initial data f(0) = 1, and then ask for the solution f(1). We expect to find e

var f = s.integrate(0, [1])
f(1)
// [ 2.7182816547852937 ]

Pretty close! If you want more precision, we can create a Solver object with a higher precision like this:

f = new odex.Solver(yprime, 1, { absoluteTolerance: 1e-10 }).integrate(0, [1])
f(1)
// [ 2.7182817887690183 ]

ODEX produces a ordered stream of integration steps, and the solution function f traverses this stream to apply its argument, so it is a requirement that the arguments to f be increasing.

There's another interface to the solution, which takes a callback function you supply which is invoked with each integration step. The function is called with parameters x0, x1, y1, and f, where y1 = f(x1) and the function f can be used to find solution values in the interval [x0, x1]. The callback intervals will have varying sizes depending on the problem and parameters, but will be contiguous.

new odex.Solver(yprime, 1).solve(0, [1], 2, (x0, x1, y1, f) => console.log(x0,x1,y1))
// 0 0.0001 [ 1.0001000050001667 ]
// 0.0001 0.000877739983046304 [ 1.0008781253095156 ]
// 0.000877739983046304 0.006926534795334954 [ 1.0069505787190927 ]
// 0.006926534795334954 0.053970430542934406 [ 1.055453392180813 ]
// 0.053970430542934406 0.33152917907160984 [ 1.3930967925910038 ]
// 0.33152917907160984 1.3856957995736772 [ 3.997606273502324 ]
// 1.3856957995736772 2 [ 7.389053607394767 ]
// {
//   y: [ 7.389053607394767 ],
//   nStep: 7,
//   xEnd: 2,
//   nAccept: 7,
//   nReject: 0,
//   nEval: 101
// }

If you prefer the callback approach, you can use the convenience function grid to arrange for the output to come at a fixed interval rather than at the varying integration step size:

s = new odex.Solver(yprime, 1)
s.solve(0, [1], 2, s.grid(0.2, (x, y) => console.log(x,y)))
// 0 [ 1 ]
// 0.2 [ 1.2214027470178732 ]
// 0.4 [ 1.4918246605775702 ]
// 0.6000000000000001 [ 1.8221186375109242 ]
// 0.8 [ 2.225540718641694 ]
// 1 [ 2.7182815718767284 ]
// 1.2 [ 3.320116638690292 ]
// 1.4 [ 4.055199677746687 ]
// 1.5999999999999999 [ 4.953027837100804 ]
// 1.7999999999999998 [ 6.04964189926157 ]
// 1.9999999999999998 [ 7.389053607394765 ]
// {
//   y: [ 7.389053607394767 ],
//   nStep: 7,
//   xEnd: 2,
//   nAccept: 7,
//   nReject: 0,
//   nEval: 303
// }

However, please note that you can also do this easily with the integrate interface, with your own for loop:

f = s.integrate(0, [1])
for (let x = 0; x <= 1; x += 0.1) console.log(x, f(x))
// 0 [ 1 ]
// 0.1 [ 1.1051709120288693 ]
// 0.2 [ 1.2214027470178732 ]
// 0.30000000000000004 [ 1.3498588017720885 ]
// 0.4 [ 1.4918246699903248 ]
// 0.5 [ 1.6487211893389364 ]
// 0.6 [ 1.8221186884324072 ]
// 0.7 [ 2.013752579176383 ]
// 0.7999999999999999 [ 2.225540786295286 ]
// 0.8999999999999999 [ 2.4596029539672823 ]
// 0.9999999999999999 [ 2.7182816547852937 ]
A system of two first order equations

Note that in all these examples, y is a vector: this software is designed to solve systems. Let's work with the Lotka-Volterra predator-prey system. The system is:

dx/dt = a x - b x y
dy/dt = c x y - d y

For odex, we rename t to x, and then x and y become y[0] and y[1]. We write a function LV which binds the constants of the population system a, b, c, d and returns a function suitable for the integrator. To represent this system we can write:

function LotkaVolterra(a, b, c, d) {
  return function(x, y) {
    return [
      a * y[0] - b * y[0] * y[1],
      c * y[0] * y[1] - d * y[1]
    ];
  };
};

Then we can solve it. It's the same as the previous examples, but this time we need a solver created to handle two independent variables and must supply initial data for both of them. To find the state of the rabbits and wolves at time 6, if the state at time zero is {y0 = 1, y1 = 1}:

s = new odex.Solver(LotkaVolterra(2/3, 4/3, 1, 1), 2);
f = s.integrate(0, [1, 1])
f(6)
// [ 1.654269726022535, 0.325291085566411 ]

To see more of this system of equations in action, you can visit a demo page which allows you to vary the initial conditions with the mouse.

Bring your own return-value storage

You can call f with two arguments. If you do, the second is taken to be an array to fill with the function values, thus avoiding an allocation:

s = new odex.Solver(LotkaVolterra(2/3, 4/3, 1, 1), 2);
f = s.integrate(0, [1, 1])
v = new Array(2)
f(6, v)
v
// [ 1.654269726022535, 0.325291085566411 ]

##### A second-order equation

You can integrate second order ordinary differential equations by making a
simple transformation to a system of first order equations. Consider
[Airy's equation][airy]: y&Prime;&nbsp;&minus;&nbsp;x&thinsp;y = 0:

In ODEX, we could write y<sub>0</sub> for y and y<sub>1</sub> for y&prime;,
so that y&Prime; = y&prime;<sub>1</sub> and rewrite the system like this:
y&prime;<sub>0</sub>&nbsp;=&nbsp;y<sub>1</sub>;&ensp;
y&prime;<sub>1</sub>&nbsp;&minus;&nbsp;x&nbsp;y<sub>0</sub>&nbsp;=&nbsp;0 to get:

```js
var airy = function(x, y) {
  return [y[1], x * y[0]];
}

There's also a demo page for this equation too.

You might also enjoy a demo of the Lorenz attractor or Van der Pol equation!

Raw Functions

All the above examples have been written with a derivative function of the form y' = f(x, y). There's a more efficient form, called raw, in which instead of returning an array, the derivative function fills in an array supplied in the third argument (and returns nothing). This form is more efficient as it shares memory with the integrator and avoids memory allocations. The Airy function above would be written:

var airy2 = function(x, y, yp) {
  yp[0] = y[1]
  yp[1] = x * y[0]
}

To use a function in this form, supply option rawFunction: true when creating the integrator, e.g.

new Solver(airy2, 2, { rawFunction: true })

It's important to note that the arrays provided belong to the integrator; y must not be modified, and neither array should be read or written outside the scope of the derivative function itself.

Tests

This project comes with a mocha test suite. The suite contains other examples of second-order equations which have been translated to systems of first order equations you may examine.