# `ALLM.Embedding`
[🔗](https://github.com/cykod/ALLM/blob/v0.5.0/lib/allm/embedding.ex#L1)

A single embedding vector — Layer A serializable data.

One `%Embedding{}` corresponds to one input string in an
`t:ALLM.EmbeddingRequest.t/0`. `:index` is the position of that input in
the original request's `:input` list and is **always** a
`t:non_neg_integer/0` — never `nil` — so that a response merged across
several provider calls still guarantees
`Enum.at(ALLM.EmbeddingResponse.vectors(resp), i)` is the embedding of
`Enum.at(request.input, i)`.

    iex> e = ALLM.Embedding.new(vector: [3.0, 4.0], index: 0)
    iex> ALLM.Embedding.magnitude(e)
    5.0

## Construction

`:vector` is enforced by `@enforce_keys`, so `new/1` without it raises
`ArgumentError`. An *unknown* key raises `KeyError` via `struct!/2`.
Enforcement is on key **absence**, not value — `new(vector: nil)`
succeeds. There is no `is_list/1` guard: constructing an embedding is the
job of adapter decoders, which always supply a list, and an adapter that
would build a `vector: []` returns
`%ALLM.Error.EmbeddingAdapterError{reason: :malformed_response}` instead.
`magnitude/1` and `normalize/1` are undefined on a `nil` vector — they
raise `Protocol.UndefinedError`, not the `ArithmeticError` documented
below.

## Numeric contract

* `normalize/1` is idempotent to within `1.0e-9`.
* `normalize/1` returns the embedding unchanged whenever `magnitude/1` is
  `0.0` — which covers `vector: []`, an all-zero vector, and a vector
  whose components underflow when squared (`[1.0e-200, 1.0e-200]`). No
  `ArithmeticError`, no `NaN`.
* `magnitude/1` **raises `ArithmeticError` on float overflow**: Erlang's
  `*` raises `badarith` rather than producing infinity. No embedding
  provider returns components anywhere near `1.0e150`, so the naive sum of
  squares is correct for every real input; the bound is documented rather
  than paid for with max-scaling.

# `t`

```elixir
@type t() :: %ALLM.Embedding{
  index: non_neg_integer(),
  metadata: map(),
  vector: [float()]
}
```

# `magnitude`

```elixir
@spec magnitude(t()) :: float()
```

Euclidean norm of the vector.

Raises `ArithmeticError` when a component squares to a float overflow —
see the module docs.

## Examples

    iex> ALLM.Embedding.magnitude(ALLM.Embedding.new(vector: [3.0, 4.0]))
    5.0

    iex> ALLM.Embedding.magnitude(ALLM.Embedding.new(vector: []))
    0.0

# `new`

```elixir
@spec new(keyword()) :: t()
```

Build an `%Embedding{}` from keyword opts.

A missing `:vector` raises `ArgumentError` (`@enforce_keys`); an unknown
key raises `KeyError` (`struct!/2`).

## Examples

    iex> e = ALLM.Embedding.new(vector: [0.1, 0.2])
    iex> e.index
    0
    iex> e.metadata
    %{}

# `normalize`

```elixir
@spec normalize(t()) :: t()
```

L2-normalize the vector. Returns the embedding unchanged when the
magnitude is `0.0`.

`:index` and `:metadata` are preserved.

## Examples

    iex> ALLM.Embedding.normalize(ALLM.Embedding.new(vector: [0.0, 2.0])).vector
    [0.0, 1.0]

    iex> zero = ALLM.Embedding.new(vector: [0.0, 0.0])
    iex> ALLM.Embedding.normalize(zero) == zero
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
