nenya 0.0.2

A Rust crate for adaptive rate limiting and PID control.
Documentation
# Nenya

**Nenya** is an adaptive rate limiter using a Proportional-Integral-Derivative (PID) controller. This
project contains two major components:

- **Nenya**: A Rust crate for adaptive rate limiting.
- **Nenya-Sentinel**: A standalone rate limiter gRPC service that is intended to run as a sidecar
  for existing services.

## Overview

### Nenya

Nenya is a Rust crate that offers adaptive rate limiting functionality using a PID
controller. The crate aims to provide a dynamic and efficient way to manage
request rates, making it suitable for high-throughput services.

#### Features

- **PID Controller**: Utilizes a highly configurable Proportional-Integral-Derivative
  (PID) controller to dynamically adjust the rate limits based on current traffic
  patterns
- **Configurable Sliding Window**: Uses a configurable sliding window to
  determine Transactions Per Second (TPS), ensuring accurate rate limiting decisions
- **Configuration**: Allows fine-tuning of PID parameters (`kp`, `ki`, `kd`),
  error limits, output limits, and update intervals

### Nenya-Sentinel (Work In Progress)

Nenya-Sentinel is a standalone rate limiting service that will support gRPC for
easy integration as a sidecar in microservice architectures.

## Getting Started

To get started with Nenya, add it to your Cargo.toml:

```toml
[dependencies]
nenya = "0.0.2"
```

### Examples

A basic rate limiter with a static set point:

```rust
use nenya::RateLimiterBuilder;
use nenya::pid_controller::PIDControllerBuilder;
use std::time::Duration;

fn main() {
    // Create a rate limiter
    let mut rate_limiter = RateLimiterBuilder::new(10.0)
        .update_interval(Duration::from_secs(1))
        .build();

    // Simulate request processing and check if throttling is necessary
    for _ in 0..20 {
        if rate_limiter.should_throttle() {
            println!("Request throttled");
        } else {
            println!("Request accepted");
        }
    }
}
```

A dynamic rate limiter using a PID Controller:

```rust
use nenya::RateLimiterBuilder;
use nenya::pid_controller::PIDControllerBuilder;
use std::time::Duration;

fn main() {
    // Create a PID controller with specific parameters
    let pid_controller = PIDControllerBuilder::new(10.0)
        .kp(1.0)
        .ki(0.1)
        .kd(0.01)
        .build();

    // Create a rate limiter using the PID Controller
    let mut rate_limiter = RateLimiterBuilder::new(10.0)
        .min_rate(5.0)
        .max_rate(15.0)
        .pid_controller(pid_controller)
        .update_interval(Duration::from_secs(1))
        .build();

    // Simulate request processing and check if throttling is necessary
    for _ in 0..20 {
        if rate_limiter.should_throttle() {
            println!("Request throttled");
        } else {
            println!("Request accepted");
        }
    }
}
```

### Request Simulator

Nenya includes a request simulation example for testing and tuning. You can
run the simulation with:

```sh
cargo run --example request_simulator_plot -- \
    --target_tps 80.0 \
    --min_tps 75.0 \
    --max_tps 100.0 \
    --trailing_window 1 \
    --duration 120 \
    --base_tps 80.0 \
    --amplitudes 20.0,7.0,10.0 \
    --frequencies 0.05,2.8,4.0 \
    --kp 0.8 \
    --ki 0.05 \
    --kd 0.04 \
    --error_limit 10.0 \
    --output_limit 3.0 \
    --update_interval 500 \
    --error_bias 0.0

```

Most of these arguments have sane defaults and can be omitted. For more details
see:

```sh
cargo run --example request_simulator_plot -- --help
```

## Adaptive Rate Limiting

The rate limiter achieves an adaptive rate limit using a
Proportional–Integral–Derivative (PID) controller which determines the target
rate limit based on the request rate. This implementation includes error
bias, accumulated error clamping, anti-windup feedback, and output clamping.

### Overview

1. **Error Calculation**: The error is calculated by subtracting the request
   rate from the setpoint.
2. **Proportional Term**: The proportional term is the product of the
   proportional gain and the error.
3. **Error Bias**:  The error is adjusted by a bias factor, reacting more to
   positive errors if $B > 0$ and more to negative errors if $B < 0$.
4. **Integral Term**: The integral term is the accumulated error over time,
   clamped to prevent windup.
5. **Derivative Term**: The derivative term is the rate of change of the error.
6. **Raw Correction**: The raw correction is the sum of the P, I, and D terms.
7. **Output Clamping**: The output is clamped to a specified limit to prevent
   excessive corrections.
8. **Anti-Windup Feedback**: If clamping occurs, the accumulated error is
   adjusted to prevent windup.
9. **Final Output**: The clamped correction is the final output of the PID
   controller.
10. **Request Limit Adjustment**: The clamped correction is added to the
    current request limit to derive the new request limit.

### 1. Error Calculation

The error $e(t)$ is calculated as the difference between the setpoint $S$ and
the request rate $r(t)$:

```math
e(t) = S - r(t)
```

### 2. Proportional Term (P)

The proportional term $P(t)$ is computed using the proportional gain $K_p$:

```math
P(t) = K_p \cdot e(t)
```

### 3. Error Bias

The error is adjusted by a bias $B$ to react more to positive or negative
errors:

```math
\text{biased\_error}(t) =
\begin{cases}
e(t) \cdot (1 + B) & \text{if } e(t) > 0 \\
e(t) \cdot (1 - B) & \text{if } e(t) \leq 0
\end{cases}
```

### 4. Integral Term (I)

The accumulated error $E(t)$ is clamped to prevent integral windup:

```math
E(t) = \text{clamp}\left( E(t-1) + \text{biased\_error}(t), -L, L \right)
```

where $L$ is the error limit.

The integral term $I(t)$ is then:

```math
I(t) = K_i \cdot E(t)
```

### 5. Derivative Term (D)

The derivative term $D(t)$ is computed using the derivative gain $K_d$
and the rate of change of the error:

```math
D(t) = K_d \cdot \frac{d e(t)}{dt}
```

For discrete time steps, this can be approximated as:

```math
D(t) = K_d \cdot \left( e(t) - e(t-1) \right)
```

### 6. Raw Correction

The raw correction $u(t)$ is the sum of the proportional, integral, and
derivative terms:

```math
u(t) = P(t) + I(t) + D(t)
```

### 7. Output Clamping

The output correction is clamped to prevent excessive output:

```math
u_{\text{clamped}}(t) = \text{clamp}(u(t), -M, M)
```

where $M$ is the output limit.

### 8. Anti-Windup Feedback

If the correction is clamped, the accumulated error $E(t)$ is adjusted to
prevent windup:

```math
\text{if } u(t) \neq u_{\text{clamped}}(t) \text{ then } E(t) = E(t) - \frac{u(t) - u_{\text{clamped}}(t)}{K_i}
```

### 9. Final Output

The final output of the PID controller is:

```math
u_{\text{clamped}}(t)
```

### 10. Request Limit Adjustment

The output is added to the current request limit $R(t-1)$ to derive the new
request limit $R(t)$:

```math
R(t) = R(t-1) + u_{\text{clamped}}(t)
```

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.