From 147f30be1d82dea7a31344f3a277d7af81e9a745 Mon Sep 17 00:00:00 2001 From: crertel Date: Sun, 7 Dec 2025 22:54:01 -0600 Subject: [PATCH] Add benchmarks for modules. --- bench/mat33_bench.exs | 79 +++++++++++++++++++++++++++++++ bench/mat44_bench.exs | 74 +++++++++++++++++++++++++++++ bench/quatern_bench.exs | 100 ++++++++++++++++++++++++++++++++++++++++ bench/vec2_bench.exs | 74 +++++++++++++++++++++++++++++ bench/vec3_bench.exs | 70 ++++++++++++++++++++++++++++ mix.exs | 3 ++ mix.lock | 6 +++ 7 files changed, 406 insertions(+) create mode 100644 bench/mat33_bench.exs create mode 100644 bench/mat44_bench.exs create mode 100644 bench/quatern_bench.exs create mode 100644 bench/vec2_bench.exs create mode 100644 bench/vec3_bench.exs diff --git a/bench/mat33_bench.exs b/bench/mat33_bench.exs new file mode 100644 index 0000000..a6b93ff --- /dev/null +++ b/bench/mat33_bench.exs @@ -0,0 +1,79 @@ +alias Graphmath.Mat33 + +# Test data setup - 3x3 matrices stored as 9-element tuples (row-major) +mat33_a = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} +mat33_b = {9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0} +mat33_invertible = {1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0} +vec2 = {3.0, 4.0} +vec3 = {3.0, 4.0, 1.0} +scalar = 2.5 +angle = :math.pi() / 4 +mat33_list = for _ <- 1..10, do: Mat33.identity() + +Benchee.run( + %{ + "Baseline" => fn -> {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} end, + + # Identity and creation + "Mat33.identity/0" => fn -> Mat33.identity() end, + "Mat33.zero/0" => fn -> Mat33.zero() end, + + # Basic arithmetic + "Mat33.add/2" => fn -> Mat33.add(mat33_a, mat33_b) end, + "Mat33.subtract/2" => fn -> Mat33.subtract(mat33_a, mat33_b) end, + "Mat33.scale/2" => fn -> Mat33.scale(mat33_a, scalar) end, + + # Matrix multiplication + "Mat33.multiply/2" => fn -> Mat33.multiply(mat33_a, mat33_b) end, + "Mat33.multiply_transpose/2" => fn -> Mat33.multiply_transpose(mat33_a, mat33_b) end, + + # Inverse + "Mat33.inverse/1" => fn -> Mat33.inverse(mat33_invertible) end, + + # Vector transformation + "Mat33.apply/2 (vec2)" => fn -> Mat33.apply(mat33_a, vec3) end, + "Mat33.apply_transpose/2 (vec2)" => fn -> Mat33.apply_transpose(mat33_a, vec3) end, + "Mat33.apply_left/2 (vec2)" => fn -> Mat33.apply_left(vec3,mat33_a) end, + "Mat33.apply_left_transpose/2 (vec2)" => fn -> Mat33.apply_left_transpose(vec3,mat33_a) end, + "Mat33.transform_point/2" => fn -> Mat33.transform_point(mat33_a, vec2) end, + "Mat33.transform_vector/2" => fn -> Mat33.transform_vector(mat33_a, vec2) end, + + # Rotation matrices + "Mat33.make_rotate/1" => fn -> Mat33.make_rotate(angle) end, + + # Scale matrices + "Mat33.make_scale/1 (uniform)" => fn -> Mat33.make_scale(2.0) end, + "Mat33.make_scale/2" => fn -> Mat33.make_scale(1.0, 2.0, 3.0) end, + + # Translation matrices + "Mat33.make_translate/2" => fn -> Mat33.make_translate(5.0, 10.0) end, + + # Component access + "Mat33.at/3" => fn -> Mat33.at(mat33_a, 1, 1) end, + "Mat33.row0/1" => fn -> Mat33.row0(mat33_a) end, + "Mat33.row1/1" => fn -> Mat33.row1(mat33_a) end, + "Mat33.row2/1" => fn -> Mat33.row2(mat33_a) end, + "Mat33.column0/1" => fn -> Mat33.column0(mat33_a) end, + "Mat33.column1/1" => fn -> Mat33.column1(mat33_a) end, + "Mat33.column2/1" => fn -> Mat33.column2(mat33_a) end, + "Mat33.diag/1" => fn -> Mat33.diag(mat33_a) end, + + # Round-trip operations + "Mat33 rotate->inverse" => fn -> + m = Mat33.make_rotate(angle) + Mat33.inverse(m) + end, + + # Batch operation (chain multiply 10 matrices) + "Mat33 batch multiply (10 matrices)" => fn -> + Enum.reduce(mat33_list, Mat33.identity(), &Mat33.multiply/2) + end + }, + warmup: 2, + time: 5, + memory_time: 2, + formatters: [ + Benchee.Formatters.Console, + {Benchee.Formatters.Markdown, file: "bench/results/mat33_results.md"} + ] +) diff --git a/bench/mat44_bench.exs b/bench/mat44_bench.exs new file mode 100644 index 0000000..94578d8 --- /dev/null +++ b/bench/mat44_bench.exs @@ -0,0 +1,74 @@ +alias Graphmath.Mat44 + +# Test data setup - 4x4 matrices stored as 16-element tuples (row-major) +mat44_a = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0} +mat44_b = {16.0, 15.0, 14.0, 13.0, 12.0, 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0} +vec3 = {3.0, 4.0, 5.0} +vec4 = {3.0, 4.0, 5.0, 1.0} +scalar = 2.5 +angle = :math.pi() / 4 +mat44_list = for _ <- 1..10, do: Mat44.identity() + +Benchee.run( + %{ + # Identity and creation + "Mat44.identity/0" => fn -> Mat44.identity() end, + "Mat44.zero/0" => fn -> Mat44.zero() end, + + # Basic arithmetic + "Mat44.add/2" => fn -> Mat44.add(mat44_a, mat44_b) end, + "Mat44.subtract/2" => fn -> Mat44.subtract(mat44_a, mat44_b) end, + "Mat44.scale/2" => fn -> Mat44.scale(mat44_a, scalar) end, + + # Matrix multiplication + "Mat44.multiply/2" => fn -> Mat44.multiply(mat44_a, mat44_b) end, + "Mat44.multiply_transpose/2" => fn -> Mat44.multiply_transpose(mat44_a, mat44_b) end, + + # Vector transformation + "Mat44.apply/2 (vec3)" => fn -> Mat44.apply(mat44_a, vec4) end, + "Mat44.apply_transpose/2 (vec3)" => fn -> Mat44.apply_transpose(mat44_a, vec4) end, + "Mat44.apply_left/2 (vec3)" => fn -> Mat44.apply_left(vec4, mat44_a) end, + "Mat44.apply_left_transpose/2 (vec3)" => fn -> Mat44.apply_left_transpose(vec4, mat44_a) end, + "Mat44.transform_point/2" => fn -> Mat44.transform_point(mat44_a, vec3) end, + "Mat44.transform_vector/2" => fn -> Mat44.transform_vector(mat44_a, vec3) end, + + # Rotation matrices + "Mat44.make_rotate_x/1" => fn -> Mat44.make_rotate_x(angle) end, + "Mat44.make_rotate_y/1" => fn -> Mat44.make_rotate_y(angle) end, + "Mat44.make_rotate_z/1" => fn -> Mat44.make_rotate_z(angle) end, + + # Scale matrices + "Mat44.make_scale/1 (uniform)" => fn -> Mat44.make_scale(2.0) end, + "Mat44.make_scale/4" => fn -> Mat44.make_scale(2.0, 3.0, 4.0, 5.0) end, + + # Translation matrices + "Mat44.make_translate/3" => fn -> Mat44.make_translate(5.0, 10.0, 15.0) end, + + # Component access + "Mat44.row0/1" => fn -> Mat44.row0(mat44_a) end, + "Mat44.row1/1" => fn -> Mat44.row1(mat44_a) end, + "Mat44.row2/1" => fn -> Mat44.row2(mat44_a) end, + "Mat44.row3/1" => fn -> Mat44.row3(mat44_a) end, + "Mat44.column0/1" => fn -> Mat44.column0(mat44_a) end, + "Mat44.column1/1" => fn -> Mat44.column1(mat44_a) end, + "Mat44.column2/1" => fn -> Mat44.column2(mat44_a) end, + "Mat44.column3/1" => fn -> Mat44.column3(mat44_a) end, + "Mat44.diag/1" => fn -> Mat44.diag(mat44_a) end, + "Mat44.at/3" => fn -> Mat44.at(mat44_a, 2, 2) end, + + # Extract submatrices + "Mat44.round/2" => fn -> Mat44.round(mat44_a, 3) end, + + # Batch operation (chain multiply 10 matrices) + "Mat44 batch multiply (10 matrices)" => fn -> + Enum.reduce(mat44_list, Mat44.identity(), &Mat44.multiply/2) + end + }, + warmup: 2, + time: 5, + memory_time: 2, + formatters: [ + Benchee.Formatters.Console, + {Benchee.Formatters.Markdown, file: "bench/results/mat44_results.md"} + ] +) diff --git a/bench/quatern_bench.exs b/bench/quatern_bench.exs new file mode 100644 index 0000000..a7b83b8 --- /dev/null +++ b/bench/quatern_bench.exs @@ -0,0 +1,100 @@ +alias Graphmath.Quatern +alias Graphmath.Mat33 + +# Test data setup - quaternions stored as {w, x, y, z} +quat_a = Quatern.from_axis_angle(:math.pi() / 4, {0.0, 1.0, 0.0}) +quat_b = Quatern.from_axis_angle(:math.pi() / 3, {1.0, 0.0, 0.0}) +quat_identity = Quatern.identity() +vec3 = {3.0, 4.0, 5.0} +scalar = 2.5 +angle = :math.pi() / 4 +axis = {0.0, 1.0, 0.0} +quat_list = for _ <- 1..10, do: Quatern.identity() + +Benchee.run( + %{ + # Baseline + "Baseline" => fn -> {1.0, 0.0, 0.0, 0.0} end, + + # Identity and creation + "Quatern.zero/0" => fn -> Quatern.zero() end, + "Quatern.identity/0" => fn -> Quatern.identity() end, + "Quatern.from_axis_angle/2" => fn -> Quatern.from_axis_angle(angle, axis) end, + "Quatern.random/0" => fn -> Quatern.random() end, + + # Basic arithmetic + "Quatern.add/2" => fn -> Quatern.add(quat_a, quat_b) end, + "Quatern.subtract/2" => fn -> Quatern.subtract(quat_a, quat_b) end, + "Quatern.scale/2" => fn -> Quatern.scale(quat_a, scalar) end, + + # Quaternion multiplication (Hamilton product) + "Quatern.multiply/2" => fn -> Quatern.multiply(quat_a, quat_b) end, + + # Conjugate and inverse + "Quatern.conjugate/1" => fn -> Quatern.conjugate(quat_a) end, + "Quatern.inverse/1" => fn -> Quatern.inverse(quat_a) end, + + # Length operations + "Quatern.norm/1" => fn -> Quatern.norm(quat_a) end, + "Quatern.normalize/1" => fn -> Quatern.normalize(quat_a) end, + "Quatern.normalize_strict/1" => fn -> Quatern.normalize_strict(quat_a) end, + + # Vector rotation + "Quatern.transform_vector/2" => fn -> Quatern.transform_vector(quat_a, vec3) end, + + # Interpolation + "Quatern.slerp/3" => fn -> Quatern.slerp(quat_a, quat_b, 0.5) end, + + # Dot product + "Quatern.dot/2" => fn -> Quatern.dot(quat_a, quat_b) end, + + # Comparison + "Quatern.equal/2" => fn -> Quatern.equal(quat_a, quat_b) end, + "Quatern.equal/3 (epsilon)" => fn -> Quatern.equal(quat_a, quat_b, 0.001) end, + "Quatern.equal_elements/2" => fn -> Quatern.equal_elements(quat_a, quat_b) end, + "Quatern.equal_elements/3 (epsilon)" => fn -> Quatern.equal_elements(quat_a, quat_b, 0.001) end, + + # Conversion + "Quatern.to_rotation_matrix_33/1" => fn -> Quatern.to_rotation_matrix_33(quat_a) end, + "Quatern.to_rotation_matrix_44/1" => fn -> Quatern.to_rotation_matrix_44(quat_a) end, + "Quatern.from_rotation_matrix/1" => fn -> + mat = Mat33.make_rotate(angle) + Quatern.from_rotation_matrix(mat) + end, + "Quatern.from_axis_angle/1" => fn -> Quatern.from_axis_angle( 0.0, {1,0,0}) end, + "Quatern.from_list/1" => fn -> Quatern.from_list([1,2,3,0]) end, + + # Pitch/yaw/roll extraction + "Quatern.get_pitch/1" => fn -> Quatern.get_pitch(quat_a) end, + "Quatern.get_yaw/1" => fn -> Quatern.get_yaw(quat_a) end, + "Quatern.get_roll/1" => fn -> Quatern.get_roll(quat_a) end, + + # Complex operations + "Quatern.integrate" => fn -> Quatern.integrate({1.0, 0.0, 0.0, 0.0}, {0.0, :math.pi(), 0.0}, 1.0) end, + "Quatern rotate->inverse->transform" => fn -> + q = Quatern.from_axis_angle(angle, {0.0, 1.0, 0.0}) + q_inv = Quatern.inverse(q) + rotated = Quatern.transform_vector(q, vec3) + Quatern.transform_vector(q_inv, rotated) + end, + + # Batch operation (chain multiply 10 quaternions) + "Quatern batch multiply (10 quaternions)" => fn -> + Enum.reduce(quat_list, Quatern.identity(), &Quatern.multiply/2) + end, + + # Slerp chain (useful for animation) + "Quatern slerp chain (10 steps)" => fn -> + for t <- 0..9 do + Quatern.slerp(quat_a, quat_b, t / 9.0) + end + end + }, + warmup: 2, + time: 5, + memory_time: 2, + formatters: [ + Benchee.Formatters.Console, + {Benchee.Formatters.Markdown, file: "bench/results/quatern_results.md"} + ] +) diff --git a/bench/vec2_bench.exs b/bench/vec2_bench.exs new file mode 100644 index 0000000..5791ed3 --- /dev/null +++ b/bench/vec2_bench.exs @@ -0,0 +1,74 @@ +alias Graphmath.Vec2 + +# Test data setup +vec2_a = {1.5, 2.5} +vec2_b = {3.0, 4.0} +vec2_list = for _ <- 1..100, do: {Enum.random(1..1000) / 1.0, Enum.random(1..1000) / 1.0} +scalar = 2.5 +angle = :math.pi() / 4 + +Benchee.run( + %{ + # Invocation reference + "Baseline/0" => fn -> {1,2} end, + + # Creation + "Vec2.create/0" => fn -> Vec2.create() end, + "Vec2.create/2" => fn -> Vec2.create(1.5, 2.5) end, + + # Basic arithmetic + "Vec2.add/2" => fn -> Vec2.add(vec2_a, vec2_b) end, + "Vec2.subtract/2" => fn -> Vec2.subtract(vec2_a, vec2_b) end, + "Vec2.multiply/2" => fn -> Vec2.multiply(vec2_a, vec2_b) end, + "Vec2.scale/2" => fn -> Vec2.scale(vec2_a, scalar) end, + + # Products + "Vec2.dot/2" => fn -> Vec2.dot(vec2_a, vec2_b) end, + "Vec2.perp_prod/2" => fn -> Vec2.perp_prod(vec2_a, vec2_b) end, + + # Length operations + "Vec2.length/1" => fn -> Vec2.length(vec2_a) end, + "Vec2.length_squared/1" => fn -> Vec2.length_squared(vec2_a) end, + "Vec2.length_manhattan/1" => fn -> Vec2.length_manhattan(vec2_a) end, + "Vec2.normalize/1" => fn -> Vec2.normalize(vec2_a) end, + "Vec2.p_norm/2" => fn -> Vec2.p_norm(vec2_a, 1) end, + + # Distance operations + "Vec2.chebyshev_distance/2" => fn -> Vec2.chebyshev_distance(vec2_a, vec2_b) end, + "Vec2.minkowski_distance/2" => fn -> Vec2.minkowski_distance(vec2_a, vec2_b, 2) end, + + # Interpolation + "Vec2.lerp/3" => fn -> Vec2.lerp(vec2_a, vec2_b, 0.5) end, + + # Rotation + "Vec2.rotate/2" => fn -> Vec2.rotate(vec2_a, angle) end, + + # Comparison + "Vec2.equal/2" => fn -> Vec2.equal(vec2_a, vec2_b) end, + "Vec2.equal/3 (epsilon)" => fn -> Vec2.equal(vec2_a, vec2_b, 0.001) end, + "Vec2.near/3" => fn -> Vec2.near(vec2_a, vec2_b, 0.001) end, + + # Projection/Reflection + "Vec2.project/2" => fn -> Vec2.project(vec2_a, vec2_b) end, + + # Perpendicular + "Vec2.perp/1" => fn -> Vec2.perp(vec2_a) end, + + # Random generation + "Vec2.random_circle/0" => fn -> Vec2.random_circle() end, + "Vec2.random_disc/0" => fn -> Vec2.random_disc() end, + "Vec2.random_box/0" => fn -> Vec2.random_box() end, + + # Batch operation (sum of list) + "Vec2 batch add (100 vectors)" => fn -> + Enum.reduce(vec2_list, {0.0, 0.0}, &Vec2.add/2) + end + }, + warmup: 2, + time: 5, + memory_time: 2, + formatters: [ + Benchee.Formatters.Console, + {Benchee.Formatters.Markdown, file: "bench/results/vec2_results.md"} + ] +) diff --git a/bench/vec3_bench.exs b/bench/vec3_bench.exs new file mode 100644 index 0000000..6f015f0 --- /dev/null +++ b/bench/vec3_bench.exs @@ -0,0 +1,70 @@ +alias Graphmath.Vec3 + +# Test data setup +vec3_a = {1.5, 2.5, 3.5} +vec3_b = {3.0, 4.0, 5.0} +vec3_c = {0.5, 1.0, 1.5} +vec3_list = for _ <- 1..100, do: {Enum.random(1..1000) / 1.0, Enum.random(1..1000) / 1.0, Enum.random(1..1000) / 1.0} +scalar = 2.5 +axis = Vec3.normalize({0.0, 1.0, 0.0}) +angle = :math.pi() / 4 + +Benchee.run( + %{ + # Baseline + "Baseline" => fn -> {1,2,3} end, + + # Creation + "Vec3.create/0" => fn -> Vec3.create() end, + "Vec3.create/3" => fn -> Vec3.create(1.5, 2.5, 3.5) end, + + # Basic arithmetic + "Vec3.add/2" => fn -> Vec3.add(vec3_a, vec3_b) end, + "Vec3.subtract/2" => fn -> Vec3.subtract(vec3_a, vec3_b) end, + "Vec3.multiply/2" => fn -> Vec3.multiply(vec3_a, vec3_b) end, + "Vec3.scale/2" => fn -> Vec3.scale(vec3_a, scalar) end, + + # Products + "Vec3.dot/2" => fn -> Vec3.dot(vec3_a, vec3_b) end, + "Vec3.cross/2" => fn -> Vec3.cross(vec3_a, vec3_b) end, + "Vec3.scalar_triple/3" => fn -> Vec3.scalar_triple(vec3_a, vec3_b, vec3_c) end, + + # Length operations + "Vec3.length/1" => fn -> Vec3.length(vec3_a) end, + "Vec3.length_squared/1" => fn -> Vec3.length_squared(vec3_a) end, + "Vec3.length_manhattan/1" => fn -> Vec3.length_manhattan(vec3_a) end, + "Vec3.normalize/1" => fn -> Vec3.normalize(vec3_a) end, + + # Distance operations + "Vec3.minkowski_distance/3" => fn -> Vec3.minkowski_distance(vec3_a, vec3_b, 2) end, + "Vec3.chebyshev_distance/2" => fn -> Vec3.chebyshev_distance(vec3_a, vec3_b) end, + + # Interpolation + "Vec3.lerp/3" => fn -> Vec3.lerp(vec3_a, vec3_b, 0.5) end, + + # Rotation (Rodrigues' formula) + "Vec3.rotate/3" => fn -> Vec3.rotate(vec3_a, axis, angle) end, + + # Comparison + "Vec3.equal/2" => fn -> Vec3.equal(vec3_a, vec3_b) end, + "Vec3.equal/3 (epsilon)" => fn -> Vec3.equal(vec3_a, vec3_b, 0.001) end, + "Vec3.near/3" => fn -> Vec3.near(vec3_a, vec3_b, 0.001) end, + + # Random generation + "Vec3.random_sphere/0" => fn -> Vec3.random_sphere() end, + "Vec3.random_ball/0" => fn -> Vec3.random_ball() end, + "Vec3.random_box/0" => fn -> Vec3.random_box() end, + + # Batch operation (sum of list) + "Vec3 batch add (100 vectors)" => fn -> + Enum.reduce(vec3_list, {0.0, 0.0, 0.0}, &Vec3.add/2) + end + }, + warmup: 2, + time: 5, + memory_time: 2, + formatters: [ + Benchee.Formatters.Console, + {Benchee.Formatters.Markdown, file: "bench/results/vec3_results.md"} + ] +) diff --git a/mix.exs b/mix.exs index ca66e56..84d36d5 100644 --- a/mix.exs +++ b/mix.exs @@ -37,6 +37,9 @@ defmodule Graphmath.Mixfile do defp deps do [ + {:benchee, "~> 1.3", only: :dev}, + {:benchee_html, "~> 1.0", only: :dev}, + {:benchee_markdown, "~> 0.3", only: :dev}, {:credo, "~> 1.7.7", only: :dev}, {:dialyxir, "~> 1.4.3", only: [:dev], runtime: false}, {:ex_doc, "~> 0.34.2", only: [:dev, :docs]}, diff --git a/mix.lock b/mix.lock index d94a6d7..ceb48dc 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,12 @@ %{ + "benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"}, + "benchee_html": {:hex, :benchee_html, "1.0.1", "1e247c0886c3fdb0d3f4b184b653a8d6fb96e4ad0d0389267fe4f36968772e24", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:benchee_json, "~> 1.0", [hex: :benchee_json, repo: "hexpm", optional: false]}], "hexpm", "b00a181af7152431901e08f3fc9f7197ed43ff50421a8347b0c80bf45d5b3fef"}, + "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, + "benchee_markdown": {:hex, :benchee_markdown, "0.3.3", "d48a1d9782693fae6c294fdb12f653bb90088172d467996bedb9887ff41cf4ef", [:mix], [{:benchee, ">= 1.1.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}], "hexpm", "106dab9ae0b448747da89b9af7285b71841f5d8131f37c6612b7370a157860a4"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [: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", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, @@ -22,5 +27,6 @@ "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, }