# Deriving Gradient Endpoints from Each Other

This guide shows how to derive missing gradient endpoints from ones you have already
implemented, using the experimental fallback helpers.

```{warning}
This feature is **experimental** and available in `tesseract_core.runtime.experimental`.
The API may change in future releases.
```

```{warning}
All four helpers materialise the **full Jacobian matrix**, which can be expensive for high-dimensional inputs or outputs and often defeats the purpose of using JVPs/VJPs. Use with caution.
```

## Overview

Tesseracts expose up to three gradient endpoints — `jacobian`, `jacobian_vector_product`
(JVP), and `vector_jacobian_product` (VJP). The helpers `jvp_from_jacobian`,
`vjp_from_jacobian`, `jacobian_from_jvp`, and `jacobian_from_vjp` let you derive any
one of these endpoints from another you have already implemented, without writing
additional gradient code.

## Deriving JVP and VJP from the Jacobian

If you have a `jacobian` implementation (e.g. through an analytical derivation or a
simulation adjoint), you can derive JVP and VJP from it:

```python
from tesseract_core.runtime.experimental import jvp_from_jacobian, vjp_from_jacobian

def jacobian(inputs, jac_inputs, jac_outputs):
    # Your existing Jacobian implementation
    ...

def jacobian_vector_product(inputs, jvp_inputs, jvp_outputs, tangent_vector):
    return jvp_from_jacobian(
        jacobian, inputs, jvp_inputs, jvp_outputs, tangent_vector
    )

def vector_jacobian_product(inputs, vjp_inputs, vjp_outputs, cotangent_vector):
    return vjp_from_jacobian(
        jacobian, inputs, vjp_inputs, vjp_outputs, cotangent_vector
    )
```

## Deriving the Jacobian from JVP or VJP

If you have a JVP or VJP but no explicit Jacobian, you can materialise the full
Jacobian matrix from either:

- **`jacobian_from_jvp`** — sweeps one-hot tangent vectors over each _input_ element.
  Costs **N** JVP calls (N = total input elements). Prefer this when outputs are
  high-dimensional.
- **`jacobian_from_vjp`** — sweeps one-hot cotangent vectors over each _output_ element.
  Costs **M** VJP calls (M = total output elements). Prefer this when inputs are
  high-dimensional.

### From JVP

```python
from tesseract_core.runtime.experimental import jacobian_from_jvp

def jacobian_vector_product(inputs, jvp_inputs, jvp_outputs, tangent_vector):
    # Your existing JVP implementation
    ...

def jacobian(inputs, jac_inputs, jac_outputs):
    return jacobian_from_jvp(
        jacobian_vector_product, inputs, jac_inputs, jac_outputs
    )
```

### From VJP

`jacobian_from_vjp` needs to know the output shapes before probing, so it takes an
`eval_fn` argument — either `apply` or `abstract_eval`. `abstract_eval` is preferred
because it determines shapes without running the full forward computation.

```python
from tesseract_core.runtime.experimental import jacobian_from_vjp

def vector_jacobian_product(inputs, vjp_inputs, vjp_outputs, cotangent_vector):
    # Your existing VJP implementation
    ...

def abstract_eval(inputs):
    # Your existing abstract_eval implementation (preferred)
    ...

def jacobian(inputs, jac_inputs, jac_outputs):
    return jacobian_from_vjp(
        vector_jacobian_product, abstract_eval, inputs, jac_inputs, jac_outputs
    )
```

## Example code

A concrete example is the `univariate_gradient_fallbacks` Tesseract — a variant of
[univariate](https://github.com/pasteurlabs/tesseract-core/tree/main/examples/univariate)
(Rosenbrock function) where the Jacobian is computed via JAX and JVP/VJP are derived
automatically using `jvp_from_jacobian` and `vjp_from_jacobian`.

```{literalinclude} ../../../../examples/univariate_gradient_fallbacks/tesseract_api.py
:language: python
:caption: tesseract_api.py
```

```{literalinclude} ../../../../examples/univariate_gradient_fallbacks/tesseract_config.yaml
:language: yaml
:caption: tesseract_config.yaml
```

## See also

- {ref}`tr-autodiff` for background on differentiable programming in Tesseracts
- {doc}`/content/examples/building-blocks/finitediff` for a gradient-free alternative
  when you have no gradient endpoint at all
- The [univariate example](https://github.com/pasteurlabs/tesseract-core/tree/main/examples/univariate)
  for the version with manually implemented JVP and VJP
