# `Soothsayer.Trend`
[🔗](https://github.com/georgeguimaraes/soothsayer/blob/v0.6.3/lib/soothsayer/trend.ex#L1)

Trend component with optional piecewise linear changepoints.

Handles network building, feature engineering, and weight extraction for the trend.

The trend function with changepoints is:
```
trend(t) = k * t + m + sum(delta_j * max(0, t - s_j))
```

Where:
- `k` = base growth rate (learned)
- `m` = offset (learned)
- `s_j` = changepoint positions (computed from data, fixed)
- `delta_j` = rate adjustments at each changepoint (learned)

# `build_changepoint_features`

```elixir
@spec build_changepoint_features(Nx.Tensor.t(), [number()]) :: Nx.Tensor.t() | nil
```

Builds changepoint feature tensor computing max(0, t - s_j) for each changepoint.

## Parameters

  * `t` - Tensor of time values with shape `{n_samples, 1}`.
  * `changepoint_positions` - List of numeric changepoint positions.

## Returns

  A tensor of shape `{n_samples, changepoints}` with changepoint features.

## Examples

    iex> t = Nx.tensor([[1.0], [2.0], [3.0]])
    iex> Soothsayer.Trend.build_changepoint_features(t, [1.5])
    #Nx.Tensor<f32[3][1] [[0.0], [0.5], [1.5]]>

# `build_component`

```elixir
@spec build_component(Axon.t(), map()) :: Axon.t()
```

Builds the trend component layer.

## Parameters

  * `input` - Axon input node from `build_input/1`.
  * `config` - Model configuration map.

## Returns

  An Axon dense layer when enabled, or `Axon.constant(0)` when disabled.

# `build_features`

```elixir
@spec build_features([Date.t()], map()) :: {Nx.Tensor.t(), map()}
```

Builds trend features tensor and metadata from dates.

## Parameters

  * `dates` - List of dates.
  * `config` - Model configuration map with `:trend` key.

## Returns

  A tuple `{tensor, metadata}` where:
  - `tensor` has shape `{n_dates, 1 + changepoints}`
  - `metadata` contains `:first_date` and `:changepoint_positions`

## Examples

    iex> dates = [~D[2023-01-01], ~D[2023-01-02], ~D[2023-01-03]]
    iex> config = %{trend: %{changepoints: 0, changepoints_range: 0.8}}
    iex> {tensor, metadata} = Soothsayer.Trend.build_features(dates, config)
    iex> Nx.shape(tensor)
    {3, 1}
    iex> metadata.first_date
    ~D[2023-01-01]

# `build_input`

```elixir
@spec build_input(map()) :: Axon.t()
```

Creates the Axon input node for the trend component.

## Parameters

  * `config` - Model configuration map with `:trend` key.

## Returns

  An Axon input node with shape `{nil, 1 + changepoints}`.

# `build_trend_input`

```elixir
@spec build_trend_input(Nx.Tensor.t(), Nx.Tensor.t() | nil) :: Nx.Tensor.t()
```

Builds the complete trend input by concatenating t with changepoint features.

## Parameters

  * `t` - Tensor of time values with shape `{n_samples, 1}`.
  * `changepoint_features` - Tensor of changepoint features with shape `{n_samples, changepoints}`.

## Returns

  A tensor of shape `{n_samples, 1 + changepoints}`.

## Examples

    iex> t = Nx.tensor([[1.0], [2.0]])
    iex> cp_features = Nx.tensor([[0.0, 0.0], [0.5, 0.0]])
    iex> Soothsayer.Trend.build_trend_input(t, cp_features)
    #Nx.Tensor<f32[2][3] [[1.0, 0.0, 0.0], [2.0, 0.5, 0.0]]>

# `compute_changepoint_indices`

```elixir
@spec compute_changepoint_indices(non_neg_integer(), non_neg_integer(), float()) :: [
  non_neg_integer()
]
```

Computes evenly spaced changepoint indices within the first portion of data.

## Parameters

  * `n_samples` - Total number of samples in the dataset.
  * `changepoints` - Number of changepoints to create.
  * `changepoints_range` - Fraction of data to place changepoints in (0-1).

## Returns

  A list of indices where changepoints will be placed.

## Examples

    iex> Soothsayer.Trend.compute_changepoint_indices(100, 5, 0.8)
    [16, 32, 48, 64, 80]

# `compute_changepoint_positions`

```elixir
@spec compute_changepoint_positions([Date.t()], non_neg_integer(), float()) :: [
  Date.t()
]
```

Computes changepoint positions as dates from the data.

## Parameters

  * `dates` - List of dates in the dataset.
  * `changepoints` - Number of changepoints to create.
  * `changepoints_range` - Fraction of data to place changepoints in (0-1).

## Returns

  A list of dates where changepoints are positioned.

## Examples

    iex> dates = Enum.map(0..99, fn i -> Date.add(~D[2023-01-01], i) end)
    iex> Soothsayer.Trend.compute_changepoint_positions(dates, 5, 0.8)
    [~D[2023-01-17], ~D[2023-02-02], ~D[2023-02-18], ~D[2023-03-06], ~D[2023-03-22]]

# `date_to_numeric`

```elixir
@spec date_to_numeric([Date.t()], Date.t()) :: Nx.Tensor.t()
```

Converts dates to numeric values (days since first date).

## Parameters

  * `dates` - List of dates.
  * `first_date` - Reference date (typically first date in dataset).

## Returns

  A tensor of numeric values representing days since first_date.

## Examples

    iex> Soothsayer.Trend.date_to_numeric([~D[2023-01-01], ~D[2023-01-02]], ~D[2023-01-01])
    #Nx.Tensor<f32[2] [0.0, 1.0]>

# `get_weights`

```elixir
@spec get_weights(Soothsayer.Model.t()) :: %{
  kernel: Nx.Tensor.t(),
  bias: Nx.Tensor.t()
}
```

Extracts learned trend weights from a fitted model.

## Parameters

  * `model` - A fitted `Soothsayer.Model` struct.

## Returns

  A map with `:kernel` and `:bias` tensors.

# `numeric_to_date`

```elixir
@spec numeric_to_date([number()], Date.t()) :: [Date.t()]
```

Converts numeric values back to dates.

## Parameters

  * `numeric` - List of numeric values (days since first_date).
  * `first_date` - Reference date.

## Returns

  A list of dates.

## Examples

    iex> Soothsayer.Trend.numeric_to_date([0.0, 1.0], ~D[2023-01-01])
    [~D[2023-01-01], ~D[2023-01-02]]

---

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