Current section
Files
Jump to
Current section
Files
lib/ex_stan.ex
defmodule ExStan do
@moduledoc """
**ExStan** is a Elixir interface to Stan, a package for Bayesian inference.
StanĀ® is a state-of-the-art platform for statistical modeling and
high-performance statistical computation. Thousands of users rely on Stan for
statistical modeling, data analysis, and prediction in the social, biological,
and physical sciences, engineering, and business.
This project is primarily based on the [PyStan](https://github.com/stan-dev/pystan) repository.
"""
require Logger
alias ExStan.Model
alias ExStan.Client
@doc """
Builds (compiles) a Stan program.
## Parameters
- `program_code`: Stan program code describing a Stan model.
- `data`: An Elixir map providing the data for the model. Variable names are the keys and the values are their associated values. Default is an empty map, suitable for Stan programs with no `data` block.
- `random_seed`: Random seed, a positive integer for random number generation. Used to ensure that results can be reproduced.
## Returns
- `Model`: an instance of Model
## Notes
C++ reserved words and Stan reserved words may not be used for variable names; see the Stan User's Guide for a complete list.
"""
def build(program_code, data \\ %{}, random_seed \\ nil) do
program_code
|> do_build()
|> handle_build_response()
|> post_model_params(data)
|> validate_model_params()
|> create_model_struct(program_code, data, random_seed)
end
defp do_build(program_code) do
Logger.info("Building model..")
response = Client.post("/models", %{"program_code" => program_code})
case response.status do
201 ->
Logger.info("Model created")
response
_ ->
Logger.error("Model creation unsuccesful")
IO.inspect(response)
end
end
defp handle_build_response(response) do
if response.status != 201 do
raise RuntimeError, message: "Error: #{response.body}"
else
if Map.has_key?(response.body, "stanc_warnings") do
Logger.info("Messages from stanc: #{response.body["stanc_warnings"]}")
end
end
response
end
defp post_model_params(response, data) do
response = Client.post("/#{response.body["name"]}/params", %{"data" => data})
if response.status != 200 do
raise "Error: #{Jason.encode!(response.body)}"
end
response
end
defp validate_model_params(response) do
params_list = response.body["params"]
val =
Enum.count(Enum.map(params_list, fn param -> param["name"] end)) ==
Enum.count(params_list)
if val != true do
raise ArgumentError,
message: "Validation Error: Duplicate parameter names detected in the model."
end
response
end
defp create_model_struct(response, program_code, data, random_seed) do
params_list = response.body["params"]
result =
Enum.map(params_list, fn param ->
{param["constrained_names"], param["name"], param["dims"]}
end)
%Model{
model_name: response.body["name"],
program_code: program_code,
data: data,
param_names: Enum.map(result, fn {_, name, _} -> name end),
constrained_param_names: Enum.map(result, fn {constrained, _, _} -> List.flatten(constrained) end),
dims: Enum.map(result, fn {_, _, dims} -> dims end),
random_seed: random_seed
}
end
end