basic transpile

main
henine 2 years ago
parent c54b7e122f
commit 2c985945de

@ -1,18 +1,91 @@
require Logger
defmodule ElixirStan do defmodule ElixirStan do
@moduledoc """ @moduledoc """
Documentation for `ElixirStan`. Documentation for `ElixirStan`.
""" """
@doc """ @doc """
Hello world. Get the version of the stanc3 executable bundled with the library.
"""
@spec stanc_version :: String.t() | {:error, String.t()}
def stanc_version() do
case System.cmd(Application.app_dir(:elixir_stan, "/priv/bin/stanc"), ["--version"]) do
{version_string, 0} -> String.trim(version_string)
{error, _} -> {:error, error}
end
end
@doc """
Computes the hash of a file used for caching.
Computes SHA256 of the file and truncates it to 8 bytes, then encodes it in a hex string.
"""
@spec hash_file(binary()) :: String.t()
def hash_file(file) when is_binary(file) do
<<hash::bitstring-size(64), _::binary>> = :crypto.hash(:sha256, file)
Base.encode16 hash
end
## Examples @spec model_tmp_dir(Path.t()) :: binary | {:error, atom}
def model_tmp_dir(path) do
with {:ok, model_code} <- File.read(path) do
model_name = Path.basename(path, ".stan")
model_hash = hash_file(model_code)
iex> ElixirStan.hello() Path.join([
:world System.tmp_dir(),
"elixir_stan",
model_name <> model_hash <> String.replace(stanc_version(), " ", "_")
])
end
end
@doc """
Transpiles the model into C++ and stores it in a temporary directory.
Temporary directory is based on `System.tmp_dir()`. An `elixir_stan` directory is created in the system temporary
directory and the model is stored in a subdirectory of that. The model directory is based on the model name, the hash
of the model code and the version of stanc used to compile it.
""" """
def hello do @spec transpile_model(Path.t()) :: :ok | {:error, any()}
:world def transpile_model(path) do
with {:ok, model_code} <- File.read(path) do
model_tmp_path = model_tmp_dir(path)
# Create model dir
:ok = File.mkdir_p(model_tmp_path)
model_tmp_file = Path.join(model_tmp_path, Path.basename(path))
# Write model code
:ok = File.write(model_tmp_file, model_code)
case System.cmd(Application.app_dir(:elixir_stan, "/priv/bin/stanc"), [model_tmp_file]) do
{_, 0} -> :ok
end
end
end
@doc """
Cleans the model tmp directory.
"""
@spec clean_model(Path.t()) :: :ok | {:error, String.t()}
def clean_model(path) do
model_tmp_path = model_tmp_dir(path)
# Remove model dir
case File.rm_rf(model_tmp_path) do
{:ok, _} ->
:ok
{:error, error, file} ->
Logger.error(
"Could not clean model: #{path}\n\t Error: #{:file.format_error(error)}\n\t File: #{file}"
)
{:error, :file.format_error(error)}
end
end end
end end

@ -14,7 +14,7 @@ defmodule ElixirStan.MixProject do
# Run "mix help compile.app" to learn about applications. # Run "mix help compile.app" to learn about applications.
def application do def application do
[ [
extra_applications: [:logger] extra_applications: [:logger, :crypto]
] ]
end end

Binary file not shown.

@ -0,0 +1,13 @@
data {
int<lower=0> N;
vector[N] y;
}
parameters {
real mu;
real<lower=0> sigma;
}
model {
y ~ normal(mu, sigma);
}

@ -2,7 +2,33 @@ defmodule ElixirStanTest do
use ExUnit.Case use ExUnit.Case
doctest ElixirStan doctest ElixirStan
test "greets the world" do test "check stanc version" do
assert ElixirStan.hello() == :world assert ElixirStan.stanc_version() == "stanc3 v2.31.0 (Unix)"
end
test "hash test" do
assert ElixirStan.hash_file("a") == "CA978112CA1BBDCA"
end
test "check basic temp path" do
# assert Application.app_dir(:elixir_stan, "test/data/normal.stan") == "a"
assert ElixirStan.model_tmp_dir("test/data/normal.stan") ==
"/tmp/elixir_stan/normalA32F3F2E4143ABA4stanc3_v2.31.0_(Unix)"
end
test "check basic transpile and clean" do
assert ElixirStan.transpile_model("test/data/normal.stan") == :ok
assert File.exists?(
Path.join(ElixirStan.model_tmp_dir("test/data/normal.stan"), "normal.stan")
)
assert File.exists?(
Path.join(ElixirStan.model_tmp_dir("test/data/normal.stan"), "normal.hpp")
)
assert ElixirStan.clean_model("test/data/normal.stan") == :ok
assert !File.exists?(ElixirStan.model_tmp_dir("test/data/normal.stan"))
end end
end end

Loading…
Cancel
Save