This is an alternative site for discovering Elm packages.
You may be looking for the
official Elm package site
instead.

type State
= Data
{ time : Float
, coordinates : List Float
, velocities : List Float
}

A state describes a physical system at a moment in time. It contains three types of numbers:

- Time.
- Coordinates describing the position of the system. If you took a snapshot of the system, what would it look like?
- Velocities describing the rates of change for each coordinate. If you took another snapshot a split-second later, how much would it have changed?

Coordinates and velocities do not need to be rectangular (in X-Y-Z space). For example, a satellite orbiting the Earth could be described by spherical coordinates (altitude, latitude, and longitude).

state : Float -> List ( Float, Float ) -> State

Create a state with the given time, coordinates, and velocities. Because coordinates and velocities correspond, they are given as a list of pairs.

```
t = 10.0 -- seconds
x = 0.0 -- meters
y = 10.0 -- meters
xSpeed = 0.0 -- meters per second
ySpeed = -1.0 -- meters per second
state = t [ (x, xSpeed), (y, ySpeed) ]
```

state1 : ( Float, Float ) -> State

Create a 1-dimensional state. The time is set to zero.

```
state1 (x, v) -- equals state 0 [ (x, v) ]
```

state2 : ( Float, Float ) -> ( Float, Float ) -> State

Create a 2-dimensional state. The time is set to zero.

```
state2 (x, vx) (y, vy)
-- equals state 0 [ (x, vx), (y, vy) ]
```

state3 : ( Float, Float ) -> ( Float, Float ) -> ( Float, Float ) -> State

Create a 3-dimensional state. The time is set to zero.

```
state2 (x, vx) (y, vy) (z, vz)
-- equals state 0 [ (x, vx), (y, vy), (z, vz) ]
```

aboutEqual : Float -> State -> State -> Bool

```
aboutEqual tolerance a b
```

Compares all numbers in state A and state B. Returns `True`

if they differ by
less than the given tolerance. States with different dimensions are never equal.

```
aboutEqual 1e-3 (state1 (0, 0)) (state1 (0, 1e-4)) -- returns True
aboutEqual 1e-6 (state1 (0, 0)) (state1 (0, 1e-4)) -- returns False
aboutEqual 1e-6 (state1 (0, 0)) (state2 (0, 0) (0, 0)) -- returns False
```

dimension : State -> Int

Returns the number of coordinates in a state.

```
dimension (state1 (0, 0)) -- returns 1
dimension (state3 (0, 0) (0, 0) (0, 0)) -- returns 3
```

time : State -> Float

Returns the time of a state.

```
time (state1 (1, 5)) -- returns 0
time (state 3.5 [ (0, 0) ]) -- returns 3.5
```

coordinate : Int -> State -> Float

For a given index `n`

, returns the `n`

th coordinate of a state. This works
similarly to `Array.get`

. The index is zero-based. An out-of-bounds index returns
zero.

```
theState = state3 (1, 2) (3, 4) (5, 6)
coordinate 0 theState -- returns 1
coordinate 2 theState -- returns 5
coordinate 3 theState -- returns 0
coordinate -1 theState -- returns 0
```

velocity : Int -> State -> Float

Returns the `n`

th velocity of a state.

```
theState = state3 (1, 2) (3, 4) (5, 6)
velocity 0 theState -- returns 2
velocity 2 theState -- returns 6
velocity 3 theState -- returns 0
```

type Acceleration
= Force (State -> List Float)

An acceleration describes how the velocities of a state change with time.

acceleration : (State -> List Float) -> Acceleration

Create an acceleration from a function. The function takes a state and returns a list of changes to the velocities.

Example 1: An object in freefall. The state has 2 dimensions, X and Y.

```
gravity = -10
fallingAccel = acceleration (always [0, gravity])
```

Example 2: A weight attached to a spring. The weight is pushed/pulled towards the resting position of the spring. The state has 1 dimension, X.

```
springStrength = 2.0
restPosition = 5.0
mass = 1.0
hookesLaw position =
mass * springStrength * (restPosition - position)
springAccel = acceleration (\s -> [ hookesLaw (coordinate 0 s) ])
```

evolve : Acceleration -> Float -> State -> State

Given an acceleration, a change in time, and a state, evolve the state
forward in time. (Under the hood, `evolve`

uses the
Runge-Kutta method.)

```
start = state2 (0, 1) (10, 0)
oneSecondLater = evolve fallingAccel 1.0 start
-- returns state 0.5 [ (1, 1) (5, -10) ]
```

Toss this sucker into a `foldp`

, and watch the Universe come to life before
your eyes!

```
model = Signal.foldp (evolve fallingAccel) start (Time.fps 30)
```

```
module Mechanics exposing (State, Acceleration, state1, state2, state3, state, aboutEqual, dimension, time, coordinate, velocity, evolve, acceleration)
{-|
# Building states
@docs State, state, state1, state2, state3
# Inspecting states
@docs aboutEqual, dimension, time, coordinate, velocity
# Changing states
@docs Acceleration, acceleration, evolve
-}
import Array exposing (Array)
-- Building states
{-| A state describes a physical system at a moment in time. It contains three types of numbers:
* Time.
* Coordinates describing the position of the system. If you took a snapshot of the system, what would it look like?
* Velocities describing the rates of change for each coordinate. If you took another snapshot a split-second later, how much would it have changed?
Coordinates and velocities do not need to be rectangular (in X-Y-Z space). For example, a satellite orbiting the Earth could be described by spherical coordinates (altitude, latitude, and longitude).
-}
type State
= Data
{ time : Float
, coordinates : List Float
, velocities : List Float
}
{-| Create a state with the given time, coordinates, and velocities. Because coordinates and velocities correspond, they are given as a list of pairs.
t = 10.0 -- seconds
x = 0.0 -- meters
y = 10.0 -- meters
xSpeed = 0.0 -- meters per second
ySpeed = -1.0 -- meters per second
state = t [ (x, xSpeed), (y, ySpeed) ]
-}
state : Float -> List ( Float, Float ) -> State
state time coords =
{ time = time
, coordinates = List.map Tuple.first coords
, velocities = List.map Tuple.second coords
}
|> Data
{-| Create a 1-dimensional state. The time is set to zero.
state1 (x, v) -- equals state 0 [ (x, v) ]
-}
state1 : ( Float, Float ) -> State
state1 x =
state 0 [ x ]
{-| Create a 2-dimensional state. The time is set to zero.
state2 (x, vx) (y, vy)
-- equals state 0 [ (x, vx), (y, vy) ]
-}
state2 : ( Float, Float ) -> ( Float, Float ) -> State
state2 x y =
state 0 [ x, y ]
{-| Create a 3-dimensional state. The time is set to zero.
state2 (x, vx) (y, vy) (z, vz)
-- equals state 0 [ (x, vx), (y, vy), (z, vz) ]
-}
state3 : ( Float, Float ) -> ( Float, Float ) -> ( Float, Float ) -> State
state3 x y z =
state 0 [ x, y, z ]
-- Inspecting states
{-|
aboutEqual tolerance a b
Compares all numbers in state A and state B. Returns `True` if they differ by
less than the given tolerance. States with different dimensions are never equal.
aboutEqual 1e-3 (state1 (0, 0)) (state1 (0, 1e-4)) -- returns True
aboutEqual 1e-6 (state1 (0, 0)) (state1 (0, 1e-4)) -- returns False
aboutEqual 1e-6 (state1 (0, 0)) (state2 (0, 0) (0, 0)) -- returns False
-}
aboutEqual : Float -> State -> State -> Bool
aboutEqual tolerance (Data a) (Data b) =
let
eq x y =
(x - y) ^ 2 < tolerance ^ 2
eqAll xs ys =
(List.length xs == List.length ys)
&& (List.map2 eq xs ys |> List.all identity)
in
(eq a.time b.time)
&& (eqAll a.coordinates b.coordinates)
&& (eqAll a.velocities b.velocities)
{-| Returns the number of coordinates in a state.
dimension (state1 (0, 0)) -- returns 1
dimension (state3 (0, 0) (0, 0) (0, 0)) -- returns 3
-}
dimension : State -> Int
dimension (Data state) =
List.length state.coordinates
{-| Returns the time of a state.
time (state1 (1, 5)) -- returns 0
time (state 3.5 [ (0, 0) ]) -- returns 3.5
-}
time : State -> Float
time (Data state) =
state.time
{-| For a given index `n`, returns the `n`th coordinate of a state. This works
similarly to `Array.get`. The index is zero-based. An out-of-bounds index returns
zero.
theState = state3 (1, 2) (3, 4) (5, 6)
coordinate 0 theState -- returns 1
coordinate 2 theState -- returns 5
coordinate 3 theState -- returns 0
coordinate -1 theState -- returns 0
-}
coordinate : Int -> State -> Float
coordinate i (Data state) =
Array.fromList state.coordinates
|> Array.get i
|> Maybe.withDefault 0
{-| Returns the `n`th velocity of a state.
theState = state3 (1, 2) (3, 4) (5, 6)
velocity 0 theState -- returns 2
velocity 2 theState -- returns 6
velocity 3 theState -- returns 0
-}
velocity : Int -> State -> Float
velocity i (Data state) =
Array.fromList state.velocities
|> Array.get i
|> Maybe.withDefault 0
-- Evolving states
{-| An acceleration describes how the velocities of a state change with time.
-}
type Acceleration
= Force (State -> List Float)
{-| Create an acceleration from a function. The function takes a state and
returns a list of changes to the velocities.
Example 1: An object in freefall. The state has 2 dimensions, X and Y.
gravity = -10
fallingAccel = acceleration (always [0, gravity])
Example 2: A weight attached to a spring. The weight is pushed/pulled towards
the resting position of the spring. The state has 1 dimension, X.
springStrength = 2.0
restPosition = 5.0
mass = 1.0
hookesLaw position =
mass * springStrength * (restPosition - position)
springAccel = acceleration (\s -> [ hookesLaw (coordinate 0 s) ])
-}
acceleration : (State -> List Float) -> Acceleration
acceleration a =
Force a
{-| Given an acceleration, a change in time, and a state, evolve the state
forward in time. (Under the hood, `evolve` uses the
[Runge-Kutta method](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods).)
start = state2 (0, 1) (10, 0)
oneSecondLater = evolve fallingAccel 1.0 start
-- returns state 0.5 [ (1, 1) (5, -10) ]
Toss this sucker into a `foldp`, and watch the Universe come to life before
your eyes!
model = Signal.foldp (evolve fallingAccel) start (Time.fps 30)
-}
evolve : Acceleration -> Float -> State -> State
evolve accel dt state =
let
a =
stateDerivative accel state
b =
nudge (0.5 * dt) a state |> stateDerivative accel
c =
nudge (0.5 * dt) b state |> stateDerivative accel
d =
nudge dt c state |> stateDerivative accel
in
state
|> nudge (dt / 6) a
|> nudge (dt / 3) b
|> nudge (dt / 3) c
|> nudge (dt / 6) d
nudge : Float -> State -> State -> State
nudge dt (Data derivative) (Data state) =
let
add dxdt x =
x + dt * dxdt
combine getter =
List.map2 add (getter derivative) (getter state)
in
{ time = state.time + dt
, coordinates = combine .coordinates
, velocities = combine .velocities
}
|> Data
stateDerivative : Acceleration -> State -> State
stateDerivative (Force accel) (Data state) =
{ state
| coordinates = state.velocities
, velocities = accel (Data state)
}
|> Data
```