formulac 0.8.0

A complex-number and extensible function supported math expression parser for Rust
Documentation
# formulac

`formulac` is a Rust library for parsing, evaluating, and differentiating mathematical expressions.
It supports complex numbers, user-defined functions, higher-order derivatives, and generic numeric types.
Ideal for symbolic computation, mathematical simulations, and evaluating formulas in Rust applications.

## Features

- **Generic numeric backend (T: Real)**
  - Arbitrary-precision support via `Real` trait
  - Supports `f64` as well as custom numeric types (e.g., arbitrary precision floats)
  - Designed without requiring `Copy`, enabling efficient use of non-trivial numeric types
- **Complex number support**
  - Built on `num_complex::Complex<T>`.
- **Const-generic API**
  - Function argument arity is encoded in the type system (`Builder<T, N>`)
  - Eliminates runtime argument length checks and enables compile-time optimization
- **Reverse Polish Notation (RPN)**
  - Converts infix expressions to RPN using the Shunting-Yard algorithm
- **Built-in mathematical operators & functions**
  - Supports `+`, `-`, `*`, `/`, `^`, and standard functions like `sin`, `cos`, `exp`, `log`, and more
  - See `src/astnode.rs` or [API Overview]#core-types--api-overview for the list of available functions, constants, and operator symbols
- **Abstract Syntax Tree (AST)**
  - Expressions are parsed into `AstNode` structures, enabling inspection, simplification, and compilation into executable closures
- **User-defined functions**
  - Easily register custom functions via `Builder::with_user_functions`
- **Symbolic differentiation**
  - Supports `diff(...)` operator
  - Higher-order derivatives supported
- **Safe and dependency-light**
  - No `unsafe`
  - Minimal dependencies

---

## Usage

Add to your `Cargo.toml`:

```bash
cargo add formulac
```

### Basic Example

```rust
use num_complex::Complex;
use formulac::Builder;

fn main() {
    // 1 argument: z for T = f64
    let expr = Builder::<f64, 1>::new("sin(z) + a * cos(z)", ["z"])
        .with_constants([("a", Complex::new(3.0, 2.0))])
        .compile()
        .unwrap();

    let result = expr([Complex::new(1.0, 2.0)]);
    println!("Result = {}", result);
}
```

### Using Custom Numeric Types

`formulac` is generic over numeric type `T`.

```rust
Builder::<f64, 1>::new(...)
```

can be replaced with other types implementing `Real`.

#### Requirements for `T`

`T` must implement the crate-defined `Real` trait, which is used throughout the library
as the underlying numeric type for `Complex<T>`.

It provides:

- Basic arithmetic operations
- Conversion from primitive numeric types
- Mathematical constants and functions

This design allows integration with:

- arbitrary precision floats
- domain-specific numeric types

Note:
- `T` is not required to implement `Copy`
- Values are handled via `Clone` internally

For full details, see `crate::core::Real`.

### Registering a Custom Function

You can register custom functions using `Builder::with_user_functions`.

```rust
use num_complex::Complex;
use formulac::{Builder, UserFn};

fn main() {
    // Define a function f(x) = x^2 + 1
    let func = UserFn::<f64>::new("f", |[x]| x * x + Complex::new(1.0, 0.0));

    let builder = Builder::<f64, 1>::new("f(3)", [])
        .with_user_functions([func]);

    let expr = builder.compile()
        .expect("Failed to compile formula with UserFn");

    assert_eq!(expr([]), Complex::new(10.0, 0.0));

    let func2 = UserFn::<f64>::new(
        "f", // it conflicts the above function.
        |[x]| x + Complex::new(2.0, 1.0),
    );

    // If multiple functions with the same name are provided, the later one overrides the former.
    let expr = builder.with_user_functions([func2])
        .compile().unwrap();

    assert_eq!(expr([]), Complex::new(5.0, 1.0));
}
```

---

## Differentiation Support

`formulac` can represent **derivative expressions** in the AST.
Built-in functions (e.g. `sin`, `cos`, `exp`, `log`, …) already have derivative rules,
but **user-defined functions require the user to explicitly register their derivative form**.
If no derivative is provided, `diff(...)` will result in an error at compile time (during `compile()`).

**Note on Differential Order:**

- Only integer-order derivatives are supported; fractional derivatives are not allowed.
- The maximum allowed order is `i8::MAX` (127). Attempting to compute a derivative higher than this will result in a runtime error.

### Differentiation Example

You can directly write differentiation expressions using the `diff` operator:

```rust
use num_complex::Complex;
use formulac::{Builder, UserFn};

fn main() {
    // Differentiate sin(x) with respect to x
    let formula = "diff(sin(x), x)";
    let expr = Builder::<f64, 1>::new(formula, ["x"])
        .compile()
        .expect("Failed to compile formula");

    let result = expr([Complex::new(1.0, 0.0)]); // evaluates cos(1)
    println!("Result = {}", result);
}
```

When computing derivatives of order 2 or higher, specify the order:

```rust
use num_complex::Complex;
use formulac::{Builder, UserFn};

fn main() {
    // Differentiate sin(x) with respect to x
    let formula = "diff(sin(x), x, 2)";
    let expr = Builder::<f64, 1>::new(formula, ["x"])
        .compile()
        .expect("Failed to compile formula");

    let result = expr([Complex::new(1.0, 0.0)]); // evaluates to -sin(1)
    println!("Result = {}", result);
}
```

### Example: User-defined function with derivative

You can define your own functions and provide derivatives for them. The derivative must be registered in the same order as the function arguments.

```rust
use num_complex::Complex;
use formulac::{Builder, UserFn};

fn main() {
    // Define f(x) = x^2, derivative f'(x) = 2x
    let deriv = UserFn::<f64>::new("df", |[x]| Complex::new(2.0, 0.0) * x);
    let func = UserFn::<f64>::new("f", |[x]| x * x).with_derivative([deriv]).unwrap();

    let expr = Builder::<f64, 1>::new("diff(f(x), x)", ["x"])
        .with_user_functions([func])
        .compile()
        .expect("Failed to compile formula with UserFn");

    let result = expr([Complex::new(3.0, 0.0)]); // evaluates f'(3) = 6
    println!("Result: {}", result);
}
```

### Example: Multi-variable functions (Partial derivatives)

For functions with multiple variables, you can register partial derivatives with respect to each argument. Use the same order as the function arguments.

```rust
use num_complex::Complex;
use formulac::{Builder, UserFn};

fn main() {
    // Define a partial derivative w.r.t x: ∂g/∂x = 2*x*y
    let deriv_x = UserFn::<f64>::new("dg_dx", |[x, y]| Complex::new(2.0, 0.0) * x * y);
    // Define a partial derivative w.r.t y: ∂g/∂y = x^2 + 3*y^2
    let deriv_y = UserFn::<f64>::new("dg_dy", |[x, y]| x * x + Complex::new(3.0, 0.0) * y * y);
    // Define g(x, y) = x^2 * y + y^3
    let func = UserFn::<f64>::new("g", |[x, y]| x * x * y  + y * y * y)
        .with_derivative([deriv_x, deriv_y]);

    // 2 arguments: x and y
    let expr_dx = Builder::<f64, 2>::new("diff(g(x, y), x)", ["x", "y"])
        .with_user_functions([func.clone()]) // use it again later
        .compile()
        .unwrap();
    let result_dx = expr_dx([Complex::new(2.0, 0.0), Complex::new(3.0, 0.0)]);
    println!("∂g/∂x at (2, 3) = {}", result_dx); // 12

    let expr_dy = Builder::<f64, 2>::new("diff(g(x, y), y)", ["x", "y"])
        .with_user_functions([func])
        .compile()
        .unwrap();
    let result_dy = expr_dy([Complex::new(2.0, 0.0), Complex::new(3.0, 0.0)]);
    println!("∂g/∂y at (2, 3) = {}", result_dy); // 31
}
```

## Core Types & API Overview

- **`Builder<T, const N: usize>`**
  Compiles a formula string into a Rust closure `Fn([Complex<T>; N]) -> Complex<T>` that evaluates the expression for given variable values.

- **`UserFn<T>`**
  Represents a user-defined function.
  Accepts a fixed-size array `[Complex<T>; N]` as arguments.

### Notes on Design

- `T` is **not required to implement `Copy`**
  - Enables support for heavy numeric types (e.g., arbitrary precision floats)
- Values are handled via `Clone`
  - Cost depends on the underlying type
  - For small types (e.g., `f64`), this is negligible
  - For large types, this trades performance for flexibility
- Closure require:
  ```rust
  T: Send + Sync + 'static
  ```
  - This is needed because the compiled function captures owned data
  - Enables safe reuse across threads

### Available mathematical constants

| String           | Value      | Description                               |
| ---------------- | ---------- | ----------------------------------------- |
| `E`              | `e`        | The base of natural logarithm (≈ 2.71828) |
| `FRAC_1_PI`      | `1 / π`    | Reciprocal of π                           |
| `FRAC_1_SQRT_2`  | `1 / √2`   | Reciprocal of square root of 2            |
| `FRAC_2_PI`      | `2 / π`    | 2 divided by π                            |
| `FRAC_2_SQRT_PI` | `2 / √π`   | 2 divided by square root of π             |
| `FRAC_PI_2`      | `π / 2`    | Half of π                                 |
| `FRAC_PI_3`      | `π / 3`    | One-third of π                            |
| `FRAC_PI_4`      | `π / 4`    | One-fourth of π                           |
| `FRAC_PI_6`      | `π / 6`    | One-sixth of π                            |
| `FRAC_PI_8`      | `π / 8`    | One-eighth of π                           |
| `LN_2`           | `ln(2)`    | Natural logarithm of 2                    |
| `LN_10`          | `ln(10)`   | Natural logarithm of 10                   |
| `LOG2_10`        | `log2(10)` | Base-2 logarithm of 10                    |
| `LOG2_E`         | `log2(e)`  | Base-2 logarithm of e                     |
| `LOG10_2`        | `log10(2)` | Base-10 logarithm of 2                    |
| `LOG10_E`        | `log10(e)` | Base-10 logarithm of e                    |
| `PI`             | `π`        | Ratio of circle circumference to diameter |
| `SQRT_2`         | `√2`       | Square root of 2                          |
| `TAU`            | `2 * π`    | Full circle in radians                    |

### Available unary operators

| String | Function   | Description       |
| ------ | ---------- | ----------------- |
| `+`    | `Positive` | Identity operator |
| `-`    | `Negative` | Negation operator |

### Available binary operators

| String | Function | Description    |
| ------ | -------- | -------------- |
| `+`    | `Add`    | Addition       |
| `-`    | `Sub`    | Subtraction    |
| `*`    | `Mul`    | Multiplication |
| `/`    | `Div`    | Division       |
| `^`    | `Pow`    | Power (x^y)    |

### Available Functions

| String  | Function     | Description                      |
| ------- | ------------ | -------------------------------- |
| `sin`   | `Sin(x)`     | Sine function                    |
| `cos`   | `Cos(x)`     | Cosine function                  |
| `tan`   | `Tan(x)`     | Tangent function                 |
| `asin`  | `Asin(x)`    | Arc sine                         |
| `acos`  | `Acos(x)`    | Arc cosine                       |
| `atan`  | `Atan(x)`    | Arc tangent                      |
| `sinh`  | `Sinh(x)`    | Hyperbolic sine                  |
| `cosh`  | `Cosh(x)`    | Hyperbolic cosine                |
| `tanh`  | `Tanh(x)`    | Hyperbolic tangent               |
| `asinh` | `Asinh(x)`   | Hyperbolic arcsine               |
| `acosh` | `Acosh(x)`   | Hyperbolic arccosine             |
| `atanh` | `Atanh(x)`   | Hyperbolic arctangent            |
| `exp`   | `Exp(x)`     | Exponential function e^x         |
| `ln`    | `Ln(x)`      | Natural logarithm                |
| `log10` | `Log10(x)`   | Base-10 logarithm                |
| `sqrt`  | `Sqrt(x)`    | Square root                      |
| `abs`   | `Abs(x)`     | Absolute value                   |
| `conj`  | `Conj(x)`    | Complex conjugate                |
| `pow`   | `Pow(x, y)`  | x raised to y (complex exponent) |
| `powi`  | `Powi(x, n)` | x raised to integer n            |

### Available differential operator

| String          | Function        | Description                                                          |
| --------------- | --------------- | -------------------------------------------------------------------- |
| `diff(f, x)`    | `diff(f, x)`    | First-order derivative of `f` with respect to `x`                    |
| `diff(f, x, n)` | `diff(f, x, n)` | n-th order derivative of `f` with respect to `x` (**max `i8::MAX`**) |

---

## Benchmarking

The `formulac` crate provides benchmarks using the Criterion crate to measure both compilation and execution performance.

**Note**:

- Criterion is a dev-dependency, so benchmarks are only available in development builds.
- :warning: Some benchmarks (e.g., 1000 nested operations) may take longer to run.
- Benchmarks require the `criterion` crate as a dev-dependency and are intended for development/testing purposes only.

### Benchmark Source

The benchmarks are located in `benches/benches.rs` and cover:

- Linear expressions (many operands like polynomials)
- Nested expressions (e.g., sin(sin(...)))
- Numeric literals
- Expressions with and without parentheses
- Many constants references (up to 100 constants)
- Differentiate expressions
- Invalid expressions (error cases)
- Practical expressions (polynomials, wave functions, exponential decay)
- Comparison of direct calls vs. parsed calls for standard functions (sin, cos, pow, etc.)

### Run Benchmarks

Run the benchmarks with:

``` bash
cargo bench
```

Both `compile` and `exec` times are measured. Criterion generates detailed statistics and plots in `target/criterion`.

### Viewing Results

Open the generated HTML report in a browser to view benchmark results and comparisons:

``` bash
xdg-open target/criterion/report/index.html
```

---

## License

Licensed under **MIT OR Apache-2.0** — choose the license that best suits your project.

---

## Contribution & Contact

Contributions, feature requests, and bug reports are welcome!
Please feel free to open issues or submit pull requests via the GitHub repository.