Skip to main content

lib/micrograd_ex.ex

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