defmodule MicrogradEx do
@moduledoc """
Elixir-native micrograd: a tiny scalar reverse-mode automatic differentiation
engine plus the small neural-network library from the original project.
The original Python `micrograd` stores `data` and `grad` on mutable objects.
Elixir data is immutable, so this port keeps the forward graph in each
`MicrogradEx.Value` and returns a separate `MicrogradEx.Gradients` table from
`backward/1`. That one design difference is the main FP adaptation:
* forward expressions create new values;
* `backward/1` creates a new gradient table;
* neural-network training creates a new updated model.
## Example
iex> x = MicrogradEx.value(3.0)
iex> y = MicrogradEx.pow(x, 2)
iex> gradients = MicrogradEx.backward(y)
iex> MicrogradEx.grad(x, gradients)
6.0
"""
alias MicrogradEx.Gradients
alias MicrogradEx.Value
@doc """
Creates a scalar differentiable value.
This is a convenience wrapper around `MicrogradEx.Value.new/2`; use the
`Value` module directly when writing longer expressions.
"""
def value(data, opts \\ []), do: Value.new(data, opts)
@doc """
Adds two values or numbers.
Numbers are automatically promoted to leaf `Value` structs. This mirrors the
Python implementation's `other = Value(other)` coercion, but it stays explicit
and regular because Elixir has no operator overloading.
"""
def add(left, right), do: Value.add(left, right)
@doc """
Subtracts the second value or number from the first.
"""
def sub(left, right), do: Value.sub(left, right)
@doc """
Multiplies two values or numbers.
"""
def mul(left, right), do: Value.mul(left, right)
@doc """
Divides the first value or number by the second.
"""
def divide(left, right), do: Value.divide(left, right)
@doc """
Raises a value or number to a scalar power.
"""
def pow(value, exponent), do: Value.pow(value, exponent)
@doc """
Applies the rectified linear unit activation.
"""
def relu(value), do: Value.relu(value)
@doc """
Runs reverse-mode automatic differentiation from an output value.
"""
def backward(%Value{} = output), do: Value.backward(output)
@doc """
Looks up the gradient for a value in a gradient table.
"""
def grad(%Value{} = value, %Gradients{} = gradients), do: Value.grad(value, gradients)
end