├── .github └── workflows │ └── go.yml ├── LICENSE ├── README.md ├── go.mod ├── ode.go └── ode_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | paths: 11 | - "**.go" 12 | - ".github/workflows/go.yml" 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.18 23 | - name: Build 24 | run: go build -v ./... 25 | - name: Test 26 | run: go test -v -race -cover ./... 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015, 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of ode nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ode [![GoDoc](https://godoc.org/github.com/sj14/ode?status.png)](https://godoc.org/github.com/sj14/ode) 2 | 3 | A package for the go programming language to solve ordinary differential equations. 4 | 5 | ## Requirements 6 | 7 | - Go Version >= 1.0 8 | 9 | ## Example 10 | ```go 11 | // Example to show how the ode package works 12 | package main 13 | 14 | import "fmt" 15 | import "github.com/sj14/ode" 16 | 17 | func main() { 18 | // SIR Start Values 19 | yStartSIR := []float64{700, 400, 100} 20 | 21 | // Do the calculation (start, step size, end, start values, function) 22 | y := ode.RungeKutta4(0, 10, 100, yStartSIR, sir) 23 | 24 | // Output the results to the console 25 | for _, val := range y { 26 | fmt.Println(val) 27 | } 28 | } 29 | 30 | // The function to calculate 31 | func sir(t float64, y []float64) []float64 { 32 | result := make([]float64, 3) 33 | result[0] = -0.0001 * y[1] * y[0] 34 | result[1] = 0.0001*y[1]*y[0] - 0.005*y[1] 35 | result[2] = 0.005 * y[1] 36 | return result 37 | } 38 | ``` 39 | ## Output 40 | ```go 41 | [0 700 400 100] 42 | [10 410.90567125203955 662.4382350812937 126.65609366666666] 43 | [20 191.97505869594923 843.2048493089754 164.82009199507524] 44 | [30 79.86753851144415 911.0287568497188 209.10370463883703] 45 | [40 32.32376388920436 912.7706547315336 254.90558137926206] 46 | [50 13.263171694988241 886.7634313299168 299.973396975095] 47 | [60 5.607931908716353 850.9501619076912 343.4419061835925] 48 | [70 2.4571403113927808 812.5094770189563 385.03338266965085] 49 | [80 1.1171479813071248 774.1848647682339 424.697987250459] 50 | [90 0.5268422655752388 737.0010917960352 462.4720659383895] 51 | [100 0.2574396186972187 701.3189883896745 498.4235719916282] 52 | ``` 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sj14/ode 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /ode.go: -------------------------------------------------------------------------------- 1 | // Package ode provides the Euler-Forward and Runge-Kutta 2 | // Method for solving ordinary differential equations. 3 | package ode 4 | 5 | // EulerForward is an implementation of the explicit Euler method. 6 | func EulerForward(from, h, to float64, y []float64, fn func(float64, []float64) []float64) [][]float64 { 7 | var steps = int((to-from)/h) + 1 8 | var parameters = len(y) 9 | var t = from 10 | yn := make([]float64, parameters) 11 | 12 | // initialize 'outer slice' 13 | ySlice := make([][]float64, steps) 14 | // initialize first 'inner slice' 15 | ySlice[0] = make([]float64, parameters+1) 16 | 17 | // fill with start values 18 | ySlice[0][0] = t 19 | for i := 0; i < parameters; i++ { 20 | ySlice[0][i+1] = y[i] 21 | } 22 | 23 | for step := 1; step < steps; step++ { 24 | t = h*float64(step) + from 25 | // initialize 'inner slice' 26 | ySlice[step] = make([]float64, parameters+1) 27 | ySlice[step][0] = t 28 | 29 | for value := 0; value < parameters; value++ { 30 | yn[value] = ySlice[step-1][value+1] 31 | } 32 | 33 | for value := 0; value < parameters; value++ { 34 | ySlice[step][value+1] = ySlice[step-1][value+1] + h*fn(t, yn)[value] 35 | } 36 | } 37 | return ySlice 38 | } 39 | 40 | // RungeKutta4 is an implementation of the 4th order Runge-Kutta method. 41 | func RungeKutta4(from, h, to float64, y []float64, fn func(float64, []float64) []float64) [][]float64 { 42 | var steps = int((to-from)/h) + 1 43 | var parameters = len(y) 44 | var t = from 45 | 46 | // initialize 'outer slice' 47 | ySlice := make([][]float64, steps) 48 | // initialize first 'inner slice' 49 | ySlice[0] = make([]float64, parameters+1) 50 | 51 | // fill with start values 52 | ySlice[0][0] = t 53 | for i := 0; i < parameters; i++ { 54 | ySlice[0][i+1] = y[i] 55 | } 56 | 57 | var k1 = make([]float64, parameters) 58 | var k2 = make([]float64, parameters) 59 | var k3 = make([]float64, parameters) 60 | var k4 = make([]float64, parameters) 61 | 62 | var yn = make([]float64, parameters) 63 | 64 | // the parameters to pass into the corresponding function calls 65 | var k2p = make([]float64, parameters) 66 | var k3p = make([]float64, parameters) 67 | var k4p = make([]float64, parameters) 68 | 69 | for step := 1; step < steps; step++ { 70 | // initialize 'inner slice' 71 | ySlice[step] = make([]float64, parameters+1) 72 | 73 | // get last yn from the slice to make the rest of the function shorter 74 | for value := 0; value < parameters; value++ { 75 | yn[value] = ySlice[step-1][value+1] 76 | } 77 | 78 | // generate k1 79 | for value := 0; value < parameters; value++ { 80 | k1[value] = fn(t, yn)[value] 81 | } 82 | 83 | t = float64(step-1)*h + h/2 + from 84 | 85 | // generate the parameter for k2 86 | for value := 0; value < parameters; value++ { 87 | k2p[value] = yn[value] + 0.5*h*k1[value] 88 | } 89 | 90 | // generate k2 91 | for value := 0; value < parameters; value++ { 92 | k2[value] = fn(t, k2p)[value] 93 | } 94 | 95 | // generate the parameter for k3 96 | for value := 0; value < parameters; value++ { 97 | k3p[value] = yn[value] + 0.5*h*k2[value] 98 | } 99 | 100 | // generate k3 101 | for value := 0; value < parameters; value++ { 102 | k3[value] = fn(t, k3p)[value] 103 | } 104 | 105 | // generate the parameter for k4 106 | for value := 0; value < parameters; value++ { 107 | k4p[value] = yn[value] + h*fn(t, k3p)[value] 108 | } 109 | 110 | t = float64(step)*h + from 111 | 112 | // generate k4 113 | for value := 0; value < parameters; value++ { 114 | k4[value] = fn(t, k4p)[value] 115 | } 116 | 117 | ySlice[step][0] = h*float64(step) + from 118 | 119 | // generate yn (saved in the slice) 120 | for value := 0; value < parameters; value++ { 121 | ySlice[step][value+1] = ySlice[step-1][value+1] + h*(k1[value]+2*k2[value]+2*k3[value]+k4[value])/6.0 122 | } 123 | } 124 | return ySlice 125 | } 126 | -------------------------------------------------------------------------------- /ode_test.go: -------------------------------------------------------------------------------- 1 | package ode_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/sj14/ode" 8 | ) 9 | 10 | // PopulationGrowthSimple Start Values 11 | var yStartPop = []float64{10} 12 | 13 | func populationGrowthSimple(t float64, y []float64) []float64 { 14 | result := make([]float64, 1) 15 | result[0] = 0.008 * y[0] 16 | return result 17 | } 18 | 19 | // SIR Start Values 20 | var yStartSIR = []float64{700, 400, 100} 21 | 22 | func sir(t float64, y []float64) []float64 { 23 | result := make([]float64, 3) 24 | result[0] = -0.0001 * y[1] * y[0] 25 | result[1] = 0.0001*y[1]*y[0] - 0.005*y[1] 26 | result[2] = 0.005 * y[1] 27 | return result 28 | } 29 | 30 | func BenchmarkEulerForwardPopulation(b *testing.B) { 31 | ode.EulerForward(0, 0.1, 10000, yStartPop, populationGrowthSimple) 32 | } 33 | 34 | func BenchmarkRungeKuttaPopulation(b *testing.B) { 35 | ode.RungeKutta4(0, 0.1, 10000, yStartPop, populationGrowthSimple) 36 | } 37 | 38 | func BenchmarkEulerForwardSIR(b *testing.B) { 39 | ode.EulerForward(0, 0.1, 10000, yStartSIR, sir) 40 | } 41 | 42 | func BenchmarkRungeKuttaSIR(b *testing.B) { 43 | ode.RungeKutta4(0, 0.1, 10000, yStartSIR, sir) 44 | } 45 | 46 | func ExampleEulerForward_population() { 47 | y := ode.EulerForward(0, 10, 100, yStartPop, populationGrowthSimple) 48 | 49 | for _, val := range y { 50 | fmt.Println(val) 51 | } 52 | // Output: 53 | // [0 10] 54 | // [10 10.8] 55 | // [20 11.664000000000001] 56 | // [30 12.597120000000002] 57 | // [40 13.604889600000002] 58 | // [50 14.693280768000001] 59 | // [60 15.868743229440001] 60 | // [70 17.138242687795202] 61 | // [80 18.50930210281882] 62 | // [90 19.990046271044324] 63 | // [100 21.58924997272787] 64 | } 65 | 66 | func ExampleEulerForward_sir() { 67 | y := ode.EulerForward(0, 10, 100, yStartSIR, sir) 68 | 69 | for _, val := range y { 70 | fmt.Println(val) 71 | } 72 | // Output: 73 | // [0 700 400 100] 74 | // [10 420 660 120] 75 | // [20 142.79999999999995 904.2 153] 76 | // [30 13.68023999999997 988.10976 198.21] 77 | // [40 0.16266133685759776 952.2218506631424 247.61548800000003] 78 | // [50 0.00777165764371518 904.7656478091992 295.2265805331572] 79 | // [60 0.0007401287811479003 859.5343969476019 340.46486292361715] 80 | // [70 0.00010396263558037628 816.5583132663673 383.44158277099723] 81 | // [80 1.9071081228138196e-05 775.7304824946034 424.2694984343156] 82 | // [90 4.277062185340778e-06 736.9439731638922 463.0560225590458] 83 | // [100 1.1251069850067054e-06 700.0967776576529 499.9032212172404] 84 | } 85 | 86 | func ExampleRungeKutta4_population() { 87 | yz := ode.RungeKutta4(0, 10, 100, yStartPop, populationGrowthSimple) 88 | 89 | for _, value := range yz { 90 | fmt.Println(value) 91 | } 92 | // Output: 93 | // [0 10] 94 | // [10 10.832870400000001] 95 | // [20 11.735108110319617] 96 | // [30 12.71249052890813] 97 | // [40 13.771276236088923] 98 | // [50 14.91824507081511] 99 | // [60 16.16074154475789] 100 | // [70 17.506721872225803] 101 | // [80 18.96480491706675] 102 | // [90 20.544327382786687] 103 | // [100 22.255403599289938] 104 | } 105 | 106 | func ExampleRungeKutta4_sir() { 107 | y := ode.RungeKutta4(0, 10, 100, yStartSIR, sir) 108 | 109 | for _, val := range y { 110 | fmt.Println(val) 111 | } 112 | // Output: 113 | // [0 700 400 100] 114 | // [10 410.90567125203955 662.4382350812937 126.65609366666666] 115 | // [20 191.97505869594923 843.2048493089754 164.82009199507524] 116 | // [30 79.86753851144415 911.0287568497188 209.10370463883703] 117 | // [40 32.32376388920436 912.7706547315336 254.90558137926206] 118 | // [50 13.263171694988241 886.7634313299168 299.973396975095] 119 | // [60 5.607931908716353 850.9501619076912 343.4419061835925] 120 | // [70 2.4571403113927808 812.5094770189563 385.03338266965085] 121 | // [80 1.1171479813071248 774.1848647682339 424.697987250459] 122 | // [90 0.5268422655752388 737.0010917960352 462.4720659383895] 123 | // [100 0.2574396186972187 701.3189883896745 498.4235719916282] 124 | } 125 | --------------------------------------------------------------------------------