I decided that I’d learn OCaml by doing something basic I’m familiar with, so I wrote this file for solving a mass on a spring, and I wanted to ask if people could provide any critique/suggestions for improvement/style changes as this is literally the first program I’ve written.
I’m using a module because I want to get familiar with them, so everything related to a simulation is in this module. I also defined a signature for a simulation type but I’m not sure how to use it because I’d need like 5 M:S with type kind of statements.
module Simulation = struct
(* Simulation interface. idk how to use it tho. ideas?*)
module type S = sig
type err
type state
type params
type history
type algorithms
val step : state -> params -> algorithms -> ( state, err ) result
val step_append : state -> params -> algorithms -> history -> ( state, err ) result
val init_history : int -> history
end
This submodule implements Simulation.S (though if I do that the associated types become, idk is opaque the right word? so I can’t use it from outside the module). The state of the simulation at any point is a position and velocity, I have some parameters for the simulation, a array storing the past states, and a list of algorithms that can be used to step.
(* SHO simulation impl *)
module SHO = struct
type err = string
type state = {
x : float;
v : float;
}
type params = {
k: float;
dt: float;
}
type history = {
ptr: int ref;
xs: float array;
vs: float array;
ts: float array;
}
type algorithms =
| Euler
| Leapfrog
| RK2
| RK4
I’m not sure if these functions are idiomatic ocaml. So I’d appreciate any feedback.
let step ( s:state ) ( p:params ) ( m:algorithms ) =
match m with
| Euler ->
Ok {
x = s.x +. s.v *. p.dt;
v = s.v +. (-. p.k *. s.x) *. p.dt;
}
| _ -> Error "unimplemented!"
let init_history n = {
ptr = ref 0;
xs = Array.create_float n;
vs = Array.create_float n;
ts = Array.create_float n;
}
let push_state (p:params) (h:history) (s:state) =
let n = h.ptr.contents + 1 in
if n >= Array.length h.ts then Error "no space left"
else Ok (
h.ts.(n) <- (h.ts.(n-1) +. p.dt);
h.xs.(n) <- s.x;
h.vs.(n) <- s.v;
)
let step_append (s:state) (p:params) (m:algorithms) (h:history) =
let s' = step s p m in
if Result.is_error s' then s'
else let s'' = Result.get_ok s' in
match push_state p h s'' with
| Ok () -> Ok s''
| Error msg -> Error msg
end
end
I suppose this block just depends on how the above code was written, but once again, any suggestions are welcome. Maybe better ways to do string formatting or printing of records?
let () =
Simulation.SHO.(
let h = init_history 10 in
let rec loop s p m n =
if n <= 0 then () else
match ( step_append s p m h ) with
| Ok s' -> (
print_string "v = "; print_float s'.v; print_endline "";
print_string "x = "; print_float s'.x; print_endline "";
loop s' p m (n-1)
)
| Error msg -> print_string "error: "; print_endline msg
in
loop {x = 1.0; v = 0.0} {k = 1.0; dt = 0.001} Euler 15
)
tysm for taking the time to read/comment <3
Copy-pastable code in one chunk
module Simulation = struct
(* Simulation interface. idk how to use it tho. ideas?*)
module type S = sig
type err
type state
type params
type history
type algorithms
val step : state -> params -> algorithms -> ( state, err ) result
val step_append : state -> params -> algorithms -> history -> ( state, err ) result
val init_history : int -> history
end
(* SHO simulation impl *)
module SHO = struct
type err = string
type state = {
x : float;
v : float;
}
type params = {
k: float;
dt: float;
}
type history = {
ptr: int ref;
xs: float array;
vs: float array;
ts: float array;
}
type algorithms =
| Euler
| Leapfrog
| RK2
| RK4
let step ( s:state ) ( p:params ) ( m:algorithms ) =
match m with
| Euler ->
Ok {
x = s.x +. s.v *. p.dt;
v = s.v +. (-. p.k *. s.x) *. p.dt;
}
| _ -> Error "unimplemented!"
let init_history n = {
ptr = ref 0;
xs = Array.create_float n;
vs = Array.create_float n;
ts = Array.create_float n;
}
let push_state (p:params) (h:history) (s:state) =
let n = h.ptr.contents + 1 in
if n >= Array.length h.ts then Error "no space left"
else Ok (
h.ts.(n) <- (h.ts.(n-1) +. p.dt);
h.xs.(n) <- s.x;
h.vs.(n) <- s.v;
)
let step_append (s:state) (p:params) (m:algorithms) (h:history) =
let s' = step s p m in
if Result.is_error s' then s'
else let s'' = Result.get_ok s' in
match push_state p h s'' with
| Ok () -> Ok s''
| Error msg -> Error msg
end
end
let () =
Simulation.SHO.(
let h = init_history 10 in
let rec loop s p m n =
if n <= 0 then () else
match ( step_append s p m h ) with
| Ok s' -> (
print_string "v = "; print_float s'.v; print_endline "";
print_string "x = "; print_float s'.x; print_endline "";
loop s' p m (n-1)
)
| Error msg -> print_string "error: "; print_endline msg
in
loop {x = 1.0; v = 0.0} {k = 1.0; dt = 0.001} Euler 15
)