From fe773689c503f46d5737c6c22086ad738a1b76a4 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:29:08 +0000 Subject: [PATCH 01/20] linting deps --- lib/exstatic/native.ex | 1 - mix.exs | 18 ++++++++++++++++-- mix.lock | 5 +++++ test/exstatic/distribution/t_test.exs | 1 - 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/exstatic/native.ex b/lib/exstatic/native.ex index 691f3b5..9bcd5a3 100644 --- a/lib/exstatic/native.ex +++ b/lib/exstatic/native.ex @@ -35,4 +35,3 @@ defmodule Exstatic.Native do @spec standardized_t_variance(float()) :: {:ok, float()} | {:error, String.t()} def standardized_t_variance(_df), do: :erlang.nif_error(:nif_not_loaded) end - diff --git a/mix.exs b/mix.exs index 54d02b5..c97eb03 100644 --- a/mix.exs +++ b/mix.exs @@ -5,8 +5,9 @@ defmodule Exstatic.MixProject do [ app: :exstatic, version: "0.1.0", - elixir: "~> 1.17", + elixir: "~> 1.18", start_permanent: Mix.env() == :prod, + aliases: aliases(), deps: deps() ] end @@ -18,10 +19,23 @@ defmodule Exstatic.MixProject do ] end + defp aliases do + [ + lint: [ + "compile --warnings-as-errors, dialyzer --plt", + "format", + "credo --all --mute-exit-status", + "dialyzer" + ] + ] + end + # Run "mix help deps" to learn about dependencies. defp deps do [ - {:rustler, "~> 0.35.1", runtime: false} + {:rustler, "~> 0.35.1", runtime: false}, + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} ] end end diff --git a/mix.lock b/mix.lock index f83fd89..032c7ef 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,9 @@ %{ + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, + "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, diff --git a/test/exstatic/distribution/t_test.exs b/test/exstatic/distribution/t_test.exs index f6d0ade..c3d24cf 100644 --- a/test/exstatic/distribution/t_test.exs +++ b/test/exstatic/distribution/t_test.exs @@ -68,4 +68,3 @@ defmodule Exstatic.Distribution.StandardizedTTest do end end end - From a8dbea8fc9d5c83e642a04e781d8550ca2d1dd60 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:35:01 +0000 Subject: [PATCH 02/20] test workflow --- .github/workflows/test.yml | 99 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6e9685c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,99 @@ +name: Test +on: + push: + branches: [main] + pull_request: + branches: [main] +jobs: + test: + name: Test (Elixir ${{ matrix.elixir }} | OTP ${{ matrix.otp }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - elixir: 1.16.x + otp: 26 + os: ubuntu-22.04 + - elixir: 1.17.x + otp: 27 + os: ubuntu-22.04 + - elixir: 1.18.x + otp: 27 + os: ubuntu-22.04 + env: + MIX_ENV: test + steps: + - name: Setup Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache dependencies + uses: actions/cache@v4 + id: cache-deps + with: + path: | + deps + _build + key: | + mix-${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }} + restore-keys: | + mix-${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}- + - name: Install dependencies + env: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} + run: mix deps.get + + - name: Compile + run: mix compile --warnings-as-errors + + - name: Check for unused packages + run: mix deps.unlock --check-unused + + - run: mix format --check-formatted + + - run: mix credo --strict + + - run: mix dialyzer + + - name: Check for abandonded packages + run: mix hex.audit + + - name: Check outdated dependencies + run: mix hex.outdated --within-requirements || true + + - name: Check for vulnerable packages + run: mix hex.audit + + - name: Run tests + run: mix test + + - name: Run tests (with coverage) + run: mix test --cover --export-coverage default + + - name: Check coverage + run: mix test.coverage + + - name: Scan for security vulnerabilities + run: mix sobelow --exit --threshold medium + + publish: + name: Publish + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + elixir-version: 1.18 + otp-version: 27.2 + - name: Fetch dependencies + run: mix deps.get + - name: Publish package + env: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} + run: mix hex.publish --organization ${{ vars.HEX_ORG }} --dry-run --replace --yes From f595664052b3e01382a4b52f18e14e8879cb2a02 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:35:48 +0000 Subject: [PATCH 03/20] release workflow --- .github/workflows/release.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..69e0a1e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: Release +on: + release: + types: [published] +jobs: + publish: + name: Publish + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + elixir-version: 1.18 + otp-version: 27.2 + - name: Fetch dependencies + run: mix deps.get + - name: Publish package + env: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} + run: mix hex.publish --organization ${{ vars.HEX_ORG }} --replace --yes From 6ed0be1f7cbb20f3287f4d836547576a353651d1 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:37:02 +0000 Subject: [PATCH 04/20] only test on 1.18 --- .github/workflows/test.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e9685c..cbe4714 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,12 +12,6 @@ jobs: fail-fast: false matrix: include: - - elixir: 1.16.x - otp: 26 - os: ubuntu-22.04 - - elixir: 1.17.x - otp: 27 - os: ubuntu-22.04 - elixir: 1.18.x otp: 27 os: ubuntu-22.04 From d8175ccab8cc39a04e0fe801c99e77acc054581e Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:56:53 +0000 Subject: [PATCH 05/20] package info --- README.md | 2 +- mix.exs | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dad766d..2ac021c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Normal.variance(dist) # => 1.0 ### Requirements -- Elixir ~> 1.17 +- Elixir ~> 1.18 Precompiled NIF binaries are included for common platforms. No additional system dependencies are required for normal usage. diff --git a/mix.exs b/mix.exs index c97eb03..43ab0e7 100644 --- a/mix.exs +++ b/mix.exs @@ -1,14 +1,22 @@ defmodule Exstatic.MixProject do use Mix.Project + @version "0.1.0" + @source_url "https://github.com/intellection/exstatic" + def project do [ app: :exstatic, - version: "0.1.0", + version: @version, elixir: "~> 1.18", start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + dialyzer: [plt_add_apps: [:mix]], + description: description(), + package: package(), + name: "Exstatic", + source_url: @source_url ] end @@ -30,6 +38,20 @@ defmodule Exstatic.MixProject do ] end + defp description do + "A statistical distribution library for Elixir with native Rust implementations." + end + + defp package do + [ + name: "exstatic", + organization: "zappi", + files: ~w(lib .formatter.exs mix.exs README*), + licenses: ["MIT"], + links: %{"GitHub" => @source_url} + ] + end + # Run "mix help deps" to learn about dependencies. defp deps do [ From a77e4d4c661b729176c4f15a751675f58873225c Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:04:43 +0000 Subject: [PATCH 06/20] docs --- mix.exs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 43ab0e7..fc778f1 100644 --- a/mix.exs +++ b/mix.exs @@ -14,6 +14,7 @@ defmodule Exstatic.MixProject do deps: deps(), dialyzer: [plt_add_apps: [:mix]], description: description(), + docs: docs(), package: package(), name: "Exstatic", source_url: @source_url @@ -42,6 +43,14 @@ defmodule Exstatic.MixProject do "A statistical distribution library for Elixir with native Rust implementations." end + defp docs do + [ + main: "readme", + source_ref: "v#{@version}", + extras: ["README.md", "CHANGELOG.md"] + ] + end + defp package do [ name: "exstatic", @@ -57,7 +66,8 @@ defmodule Exstatic.MixProject do [ {:rustler, "~> 0.35.1", runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, - {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false} + {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.34", only: :dev, runtime: false} ] end end From f02db108cb06b29f4f0d5318dca3c5771f95c26c Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:05:49 +0000 Subject: [PATCH 07/20] ex doc --- mix.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mix.lock b/mix.lock index 032c7ef..74689b0 100644 --- a/mix.lock +++ b/mix.lock @@ -2,14 +2,20 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.37.0", "970f92b39e62c460aa8a367508e938f5e4da6e2ff3eaed3f8530b25870f45471", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "b0ee7f17373948e0cf471e59c3a0ee42f3bd1171c67d91eb3626456ef9c6202c"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rustler": {:hex, :rustler, "0.35.1", "ec81961ef9ee833d721dafb4449cab29b16b969a3063a842bb9e3ea912f6b938", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "3713b2e70e68ec2bfa8291dfd9cb811fe64a770f254cd9c331f8b34fa7989115"}, From 5fb0cb0b59f43e94ee5909695574a8347944bf8c Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:09:02 +0000 Subject: [PATCH 08/20] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..af61942 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## v0.1.0 + +- Initial release. From 788a5ca0e723fed31a59b73e45133cee3db0d1b8 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:20:11 +0000 Subject: [PATCH 09/20] moduledocs --- README.md | 2 +- lib/exstatic/continuous.ex | 14 ++++++++++++++ lib/exstatic/continuous_cdf.ex | 13 +++++++++++++ lib/exstatic/distribution.ex | 3 +++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ac021c..66ef0d5 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Full documentation can be found at [https://hexdocs.pm/exstatic](https://hexdocs ### Requirements -- Elixir ~> 1.17 +- Elixir ~> 1.18 - Rust (for compiling NIFs) ### Setup diff --git a/lib/exstatic/continuous.ex b/lib/exstatic/continuous.ex index 8506519..4e8f2aa 100644 --- a/lib/exstatic/continuous.ex +++ b/lib/exstatic/continuous.ex @@ -1,4 +1,18 @@ defmodule Exstatic.Continuous do + @moduledoc """ + Defines the behaviour for continuous probability distributions that support + probability density function (PDF) calculations. + + A continuous probability distribution is characterised by its probability + density function (PDF), which describes the likelihood of different values + occurring. + + This behaviour is implemented by distributions that can compute: + + - `pdf/2` – the probability density at a given `x` + - `ln_pdf/2` – the natural logarithm of the probability density, often used + for numerical stability. + """ @type t() :: struct() @callback pdf(distribution :: t(), x :: float()) :: float() diff --git a/lib/exstatic/continuous_cdf.ex b/lib/exstatic/continuous_cdf.ex index 8b67750..b0148af 100644 --- a/lib/exstatic/continuous_cdf.ex +++ b/lib/exstatic/continuous_cdf.ex @@ -1,4 +1,17 @@ defmodule Exstatic.ContinuousCDF do + @moduledoc """ + Defines the behaviour for continuous probability distributions that support + cumulative distribution function (CDF) and related calculations. + + A cumulative distribution function (CDF) describes the probability that + a random variable is less than or equal to a given value. This behaviour also + supports related functions: + + - `cdf/2` – computes P(X ≤ x), the cumulative probability. + - `sf/2` – computes P(X > x), also known as the survival function (SF). + - `inverse_cdf/2` – computes the quantile function, which finds `x` such + that P(X ≤ x) = p. + """ @type t() :: struct() @callback cdf(distribution :: t(), x :: float()) :: float() diff --git a/lib/exstatic/distribution.ex b/lib/exstatic/distribution.ex index 462f0da..6c768a4 100644 --- a/lib/exstatic/distribution.ex +++ b/lib/exstatic/distribution.ex @@ -1,4 +1,7 @@ defmodule Exstatic.Distribution do + @moduledoc """ + A behaviour for probability distributions. + """ @type t() :: struct() @type error() :: {:error, atom()} From 4215b00168a4eec64be616edf0f7d8d8dfa79939 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:22:56 +0000 Subject: [PATCH 10/20] rename behaviour Continuous -> ContinuousPDF --- lib/exstatic/{continuous.ex => continuous_pdf.ex} | 2 +- lib/exstatic/distribution/normal.ex | 6 +++--- lib/exstatic/distribution/t.ex | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename lib/exstatic/{continuous.ex => continuous_pdf.ex} (95%) diff --git a/lib/exstatic/continuous.ex b/lib/exstatic/continuous_pdf.ex similarity index 95% rename from lib/exstatic/continuous.ex rename to lib/exstatic/continuous_pdf.ex index 4e8f2aa..c699f67 100644 --- a/lib/exstatic/continuous.ex +++ b/lib/exstatic/continuous_pdf.ex @@ -1,4 +1,4 @@ -defmodule Exstatic.Continuous do +defmodule Exstatic.ContinuousPDF do @moduledoc """ Defines the behaviour for continuous probability distributions that support probability density function (PDF) calculations. diff --git a/lib/exstatic/distribution/normal.ex b/lib/exstatic/distribution/normal.ex index 5e06037..432817b 100644 --- a/lib/exstatic/distribution/normal.ex +++ b/lib/exstatic/distribution/normal.ex @@ -20,7 +20,7 @@ defmodule Exstatic.Distribution.Normal do """ @behaviour Exstatic.Distribution - @behaviour Exstatic.Continuous + @behaviour Exstatic.ContinuousPDF @behaviour Exstatic.ContinuousCDF defstruct [:mean, :std_dev] @@ -92,7 +92,7 @@ defmodule Exstatic.Distribution.Normal do iex> TestHelper.assert_in_delta(Normal.pdf(n, 0.0), 0.03989422804014326779399, 1.0e-17) true """ - @impl Exstatic.Continuous + @impl Exstatic.ContinuousPDF def pdf(%__MODULE__{} = dist, x) when is_number(x) do Exstatic.Native.normal_pdf(dist.mean, dist.std_dev, x) end @@ -274,7 +274,7 @@ defmodule Exstatic.Distribution.Normal do iex> TestHelper.assert_in_delta(result, -0.9189385332046727) # approximately -ln(√(2π)) true """ - @impl Exstatic.Continuous + @impl Exstatic.ContinuousPDF def ln_pdf(%__MODULE__{} = dist, x) when is_number(x) do Exstatic.Native.normal_ln_pdf(dist.mean, dist.std_dev, x) end diff --git a/lib/exstatic/distribution/t.ex b/lib/exstatic/distribution/t.ex index 7e17a9d..81bc1c8 100644 --- a/lib/exstatic/distribution/t.ex +++ b/lib/exstatic/distribution/t.ex @@ -22,7 +22,7 @@ defmodule Exstatic.Distribution.StandardizedT do """ @behaviour Exstatic.Distribution - @behaviour Exstatic.Continuous + @behaviour Exstatic.ContinuousPDF @behaviour Exstatic.ContinuousCDF defstruct [:df] @@ -98,7 +98,7 @@ defmodule Exstatic.Distribution.StandardizedT do iex> TestHelper.assert_in_delta(StandardizedT.pdf(t, 0.0), 0.37960669, 1.0e-6) true """ - @impl Exstatic.Continuous + @impl Exstatic.ContinuousPDF def pdf(%__MODULE__{} = dist, x) when is_number(x) do Exstatic.Native.standardized_t_pdf(dist.df, x) end From 1c434c9f6a85b443ebd90a2ac88a0aa6b6eb031b Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:29:01 +0000 Subject: [PATCH 11/20] rename file --- lib/exstatic/distribution/{t.ex => standardized_t.ex} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/exstatic/distribution/{t.ex => standardized_t.ex} (100%) diff --git a/lib/exstatic/distribution/t.ex b/lib/exstatic/distribution/standardized_t.ex similarity index 100% rename from lib/exstatic/distribution/t.ex rename to lib/exstatic/distribution/standardized_t.ex From 5a31ef691e4929616b13f929e8f78a1e49a7ada3 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:29:07 +0000 Subject: [PATCH 12/20] optional callbacks --- lib/exstatic/continuous_cdf.ex | 2 ++ lib/exstatic/continuous_pdf.ex | 2 ++ lib/exstatic/distribution.ex | 2 ++ 3 files changed, 6 insertions(+) diff --git a/lib/exstatic/continuous_cdf.ex b/lib/exstatic/continuous_cdf.ex index b0148af..e109014 100644 --- a/lib/exstatic/continuous_cdf.ex +++ b/lib/exstatic/continuous_cdf.ex @@ -18,4 +18,6 @@ defmodule Exstatic.ContinuousCDF do @callback sf(distribution :: t(), x :: float()) :: float() @callback inverse_cdf(distribution :: t(), p :: float()) :: {:ok, float()} | {:error, :invalid_probability} + + @optional_callbacks inverse_cdf: 2 end diff --git a/lib/exstatic/continuous_pdf.ex b/lib/exstatic/continuous_pdf.ex index c699f67..daeaab7 100644 --- a/lib/exstatic/continuous_pdf.ex +++ b/lib/exstatic/continuous_pdf.ex @@ -17,4 +17,6 @@ defmodule Exstatic.ContinuousPDF do @callback pdf(distribution :: t(), x :: float()) :: float() @callback ln_pdf(distribution :: t(), x :: float()) :: float() + + @optional_callbacks ln_pdf: 2 end diff --git a/lib/exstatic/distribution.ex b/lib/exstatic/distribution.ex index 6c768a4..e471537 100644 --- a/lib/exstatic/distribution.ex +++ b/lib/exstatic/distribution.ex @@ -14,4 +14,6 @@ defmodule Exstatic.Distribution do @callback mode(distribution :: t()) :: float() @callback min(distribution :: t()) :: :neg_infinity | float() @callback max(distribution :: t()) :: :infinity | float() + + @optional_callbacks std_dev: 1, entropy: 1, skewness: 1, median: 1, mode: 1, min: 1, max: 1 end From 3420af1c92c07bfa89bb3232d5dfdbd732bc8d2e Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:33:49 +0000 Subject: [PATCH 13/20] credo --- .credo.exs | 210 +++++++++++++++++++++++++++++++++++++++++++++ .zed/settings.json | 15 ---- 2 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 .credo.exs delete mode 100644 .zed/settings.json diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..6e587fd --- /dev/null +++ b/.credo.exs @@ -0,0 +1,210 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: ["lib/", "test/"], + excluded: [ + ~r"/_build/", + ~r"/deps/", + "test/support/" + ] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: true, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, [force: :spaces]}, + {Credo.Check.Consistency.UnusedVariableNames, [force: :meaningful]}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, + [ + ignore_names: + ~r/(\.\w+Controller|\.Endpoint|\.\w+Live(\.\w+)?|\.Repo|\.Router|\.\w+Socket|\.\w+View|\.\w+Test|\.\w+Telemetry|\.\w+RecipeLive)$/ + ]}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StrictModuleLayout, + [ignore_module_attributes: ~w/transform_description/a]}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} + diff --git a/.zed/settings.json b/.zed/settings.json deleted file mode 100644 index 7a5f440..0000000 --- a/.zed/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "lsp": { - "rust-analyzer": { - "binary": { - "ignore_system_version": true - }, - "initialization_options": { - "linkedProjects": ["./native/exstatic/Cargo.toml"], - "rust": { - "analyzerTargetDir": true - } - } - } - } -} From e49c9f1beb9eb5e45e4c73699e785b7cc3610c83 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:37:47 +0000 Subject: [PATCH 14/20] dialyzer --- lib/exstatic/distribution/normal.ex | 4 +++- lib/exstatic/distribution/standardized_t.ex | 2 ++ test/test_helper.exs | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/exstatic/distribution/normal.ex b/lib/exstatic/distribution/normal.ex index 432817b..1d3cccc 100644 --- a/lib/exstatic/distribution/normal.ex +++ b/lib/exstatic/distribution/normal.ex @@ -52,6 +52,7 @@ defmodule Exstatic.Distribution.Normal do iex> Normal.new(0.0, -1.0) {:error, :invalid_std_dev} """ + @spec new(float(), float()) :: {:ok, t()} | {:error, :invalid_std_dev} def new(mean, std_dev) when is_number(mean) and is_number(std_dev) do if std_dev <= 0 do {:error, :invalid_std_dev} @@ -63,7 +64,8 @@ defmodule Exstatic.Distribution.Normal do @doc """ Creates a standard normal distribution with mean 0 and standard deviation 1. """ - def standard(), do: %__MODULE__{mean: 0.0, std_dev: 1.0} + @spec standard() :: t() + def standard, do: %__MODULE__{mean: 0.0, std_dev: 1.0} @impl Exstatic.Distribution def mean(%__MODULE__{mean: mean}), do: mean diff --git a/lib/exstatic/distribution/standardized_t.ex b/lib/exstatic/distribution/standardized_t.ex index 81bc1c8..87023dc 100644 --- a/lib/exstatic/distribution/standardized_t.ex +++ b/lib/exstatic/distribution/standardized_t.ex @@ -47,6 +47,7 @@ defmodule Exstatic.Distribution.StandardizedT do iex> StandardizedT.new(-5.0) {:error, :invalid_df} """ + @spec new(float()) :: {:ok, t()} | {:error, :invalid_df} def new(df) when is_number(df) and df > 1 do {:ok, %__MODULE__{df: df}} end @@ -65,6 +66,7 @@ defmodule Exstatic.Distribution.StandardizedT do 0.0 """ @impl Exstatic.Distribution + @spec mean(t) :: float() def mean(_t), do: 0.0 @doc """ diff --git a/test/test_helper.exs b/test/test_helper.exs index 2a7deed..048b64a 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,5 @@ defmodule TestHelper do + @spec assert_in_delta(number, number, number) :: boolean def assert_in_delta(actual, expected, delta \\ 1.0e-10) do abs(actual - expected) < delta end From eb4120959cdaf7b7d9282d3a02b00bb8bbfcd37b Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:51:29 +0000 Subject: [PATCH 15/20] specs for normal dist --- test/exstatic/distribution/normal_test.exs | 139 ++++++++++++++++++ .../{t_test.exs => standardized_t_test.exs} | 0 2 files changed, 139 insertions(+) rename test/exstatic/distribution/{t_test.exs => standardized_t_test.exs} (100%) diff --git a/test/exstatic/distribution/normal_test.exs b/test/exstatic/distribution/normal_test.exs index 6c014ab..ed94eb6 100644 --- a/test/exstatic/distribution/normal_test.exs +++ b/test/exstatic/distribution/normal_test.exs @@ -4,4 +4,143 @@ defmodule Exstatic.Distribution.NormalTest do alias Exstatic.Distribution.Normal doctest Exstatic.Distribution.Normal + + describe "new/2" do + test "creates a valid normal distribution" do + assert {:ok, %Normal{mean: +0.0, std_dev: 1.0}} = Normal.new(0.0, 1.0) + assert {:ok, %Normal{mean: 10.0, std_dev: 2.5}} = Normal.new(10.0, 2.5) + end + + test "returns an error for invalid standard deviation" do + assert {:error, :invalid_std_dev} = Normal.new(0.0, 0.0) + assert {:error, :invalid_std_dev} = Normal.new(0.0, -1.0) + end + end + + describe "standard/0" do + test "returns a standard normal distribution (mean=0, std_dev=1)" do + dist = Normal.standard() + assert dist.mean == 0.0 + assert dist.std_dev == 1.0 + end + end + + describe "mean/1" do + test "returns the mean of the distribution" do + {:ok, dist} = Normal.new(5.0, 1.0) + assert Normal.mean(dist) == 5.0 + end + end + + describe "std_dev/1" do + test "returns the standard deviation of the distribution" do + {:ok, dist} = Normal.new(5.0, 2.0) + assert Normal.std_dev(dist) == 2.0 + end + end + + describe "variance/1" do + test "computes the variance correctly" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert Normal.variance(dist) == 1.0 + + {:ok, dist} = Normal.new(0.0, 2.0) + assert Normal.variance(dist) == 4.0 + + {:ok, dist} = Normal.new(0.0, 0.1) + assert TestHelper.assert_in_delta(Normal.variance(dist), 0.01) + end + end + + describe "entropy/1" do + test "computes the entropy correctly" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert TestHelper.assert_in_delta(Normal.entropy(dist), 1.41893853320467274178, 1.0e-15) + + {:ok, dist} = Normal.new(0.0, 10.0) + assert TestHelper.assert_in_delta(Normal.entropy(dist), 3.721523626198718425798, 1.0e-15) + end + end + + describe "skewness/1" do + test "returns 0 for normal distributions" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert Normal.skewness(dist) == 0.0 + end + end + + describe "median/1" do + test "returns the mean as the median" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert Normal.median(dist) == 0.0 + + {:ok, dist} = Normal.new(5.0, 1.0) + assert Normal.median(dist) == 5.0 + end + end + + describe "mode/1" do + test "returns the mean as the mode" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert Normal.mode(dist) == 0.0 + + {:ok, dist} = Normal.new(5.0, 2.0) + assert Normal.mode(dist) == 5.0 + end + end + + describe "min/1 and max/1" do + test "returns negative and positive infinity respectively" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert Normal.min(dist) == :neg_infinity + assert Normal.max(dist) == :infinity + end + end + + describe "pdf/2" do + test "computes valid probability density function (PDF) values" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert TestHelper.assert_in_delta(Normal.pdf(dist, 0.0), 0.3989422804014327, 1.0e-15) + assert TestHelper.assert_in_delta(Normal.pdf(dist, 1.0), 0.24197072451914337, 1.0e-15) + assert TestHelper.assert_in_delta(Normal.pdf(dist, -1.0), 0.24197072451914337, 1.0e-15) + end + end + + describe "cdf/2" do + test "computes valid cumulative distribution function (CDF) values" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert TestHelper.assert_in_delta(Normal.cdf(dist, 0.0), 0.5, 1.0e-9) + assert TestHelper.assert_in_delta(Normal.cdf(dist, -5.0), 0.0000002866515718, 1.0e-16) + assert TestHelper.assert_in_delta(Normal.cdf(dist, 5.0), 0.9999997133484281, 1.0e-16) + end + end + + describe "sf/2" do + test "computes valid survival function (SF) values" do + {:ok, dist} = Normal.new(0.0, 1.0) + assert TestHelper.assert_in_delta(Normal.sf(dist, 0.0), 0.5, 1.0e-9) + assert TestHelper.assert_in_delta(Normal.sf(dist, -5.0), 0.9999997133484281, 1.0e-16) + assert TestHelper.assert_in_delta(Normal.sf(dist, 5.0), 0.0000002866515718, 1.0e-16) + end + end + + describe "ln_pdf/2" do + test "computes the log probability density function (ln(PDF))" do + {:ok, dist} = Normal.new(0.0, 1.0) + expected_ln_pdf = -0.9189385332046727 + assert TestHelper.assert_in_delta(Normal.ln_pdf(dist, 0.0), expected_ln_pdf, 1.0e-9) + end + end + + describe "inverse_cdf/2" do + test "computes the inverse CDF (quantile function) correctly" do + {:ok, dist} = Normal.new(0.0, 1.0) + + assert {:ok, result} = Normal.inverse_cdf(dist, 0.975) + assert TestHelper.assert_in_delta(result, 1.959963984540054, 1.0e-9) + + assert {:error, :invalid_probability} = Normal.inverse_cdf(dist, -0.1) + assert {:error, :invalid_probability} = Normal.inverse_cdf(dist, 1.5) + end + end end diff --git a/test/exstatic/distribution/t_test.exs b/test/exstatic/distribution/standardized_t_test.exs similarity index 100% rename from test/exstatic/distribution/t_test.exs rename to test/exstatic/distribution/standardized_t_test.exs From 19e2b3fea8bc39b3fdf19d647b183eba68425d77 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:00:20 +0000 Subject: [PATCH 16/20] ignore Rust NIF calls in test coverage --- mix.exs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index fc778f1..b54c898 100644 --- a/mix.exs +++ b/mix.exs @@ -17,7 +17,13 @@ defmodule Exstatic.MixProject do docs: docs(), package: package(), name: "Exstatic", - source_url: @source_url + source_url: @source_url, + test_coverage: [ + summary: [threshold: 90], + ignore_modules: [ + Exstatic.Native + ] + ] ] end From b2152834d497e44fd6a605f295f9a492cc365d1e Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:02:00 +0000 Subject: [PATCH 17/20] delete vscode config --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b44cc34..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.linkedProjects": ["native/exstatic/Cargo.toml"] -} From fe4204790721267b6f15ed1989322e5915ae92b2 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:09:13 +0000 Subject: [PATCH 18/20] sobelow --- mix.exs | 3 ++- mix.lock | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index b54c898..5ee458f 100644 --- a/mix.exs +++ b/mix.exs @@ -73,7 +73,8 @@ defmodule Exstatic.MixProject do {:rustler, "~> 0.35.1", runtime: false}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.34", only: :dev, runtime: false} + {:ex_doc, "~> 0.34", only: :dev, runtime: false}, + {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false} ] end end diff --git a/mix.lock b/mix.lock index 74689b0..ff0a6bb 100644 --- a/mix.lock +++ b/mix.lock @@ -19,6 +19,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "rustler": {:hex, :rustler, "0.35.1", "ec81961ef9ee833d721dafb4449cab29b16b969a3063a842bb9e3ea912f6b938", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "3713b2e70e68ec2bfa8291dfd9cb811fe64a770f254cd9c331f8b34fa7989115"}, + "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, } From 489260fc13da262369a4394c858ea5f34186436b Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:24:47 +0000 Subject: [PATCH 19/20] elixir wrapper module should not be referenced directly --- lib/exstatic/distribution/standardized_t.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/exstatic/distribution/standardized_t.ex b/lib/exstatic/distribution/standardized_t.ex index 87023dc..802a86c 100644 --- a/lib/exstatic/distribution/standardized_t.ex +++ b/lib/exstatic/distribution/standardized_t.ex @@ -73,7 +73,7 @@ defmodule Exstatic.Distribution.StandardizedT do Returns the variance of the t-distribution. - If `1 < df ≤ 2`, the variance is `:infinity`. - - Otherwise, the variance is computed using `Exstatic.Native.standardized_t_variance/1`. + - Otherwise, the variance is computed using the native Rust implementation. ## Examples From c034edb1fe165a9a2784c4941d36c198a1091c41 Mon Sep 17 00:00:00 2001 From: Brook <71849503+Primebrook@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:42:11 +0000 Subject: [PATCH 20/20] update package and docs links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66ef0d5..40c308f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Exstatic -[![Hex.pm](https://img.shields.io/hexpm/v/exstatic.svg)](https://hex.pm/packages/exstatic) -[![Docs](https://img.shields.io/badge/hex-docs-blue.svg)](https://hexdocs.pm/exstatic) +[![Hex.pm](https://img.shields.io/hexpm/v/exstatic.svg)](https://hex.pm/packages/zappi/exstatic) +[![Docs](https://img.shields.io/badge/hex-docs-blue.svg)](https://zappi.hexdocs.pm/exstatic) Exstatic provides idiomatic Elixir interfaces for working with statistical distributions. Built on top of the battle-tested [statrs](https://docs.rs/statrs) Rust library, it combines Elixir's elegant syntax with Rust's numerical computing capabilities.