|
| 1 | +# pymath Agent Guidelines |
| 2 | + |
| 3 | +This project is a strict port of CPython's math and cmath modules to Rust. |
| 4 | + |
| 5 | +## Core Principle |
| 6 | + |
| 7 | +**Every function must match CPython exactly** - same logic, same special case handling, same error conditions. |
| 8 | + |
| 9 | +- `math` module → `Modules/mathmodule.c` |
| 10 | +- `cmath` module → `Modules/cmathmodule.c` |
| 11 | + |
| 12 | +## Porting Rules |
| 13 | + |
| 14 | +### 1. Use Existing Helpers |
| 15 | + |
| 16 | +CPython uses helpers like `math_1`, `math_2`, `FUNC1`, `FUNC2`, etc. We have Rust equivalents: |
| 17 | + |
| 18 | +| CPython | Rust | |
| 19 | +|---------|------| |
| 20 | +| `FUNC1(name, func, can_overflow, ...)` | `math_1(x, func, can_overflow)` | |
| 21 | +| `FUNC2(name, func, ...)` | `math_2(x, y, func)` | |
| 22 | +| `m_log`, `m_log10`, `m_log2` | Same names in `exponential.rs` | |
| 23 | + |
| 24 | +If a function uses a helper in CPython, use the corresponding helper here. |
| 25 | + |
| 26 | +### 2. Create Missing Helpers |
| 27 | + |
| 28 | +If CPython has a helper we don't have yet, implement it. Examples: |
| 29 | +- `math_1_fn` for Rust function pointers (vs C function pointers) |
| 30 | +- Special case handlers for specific functions |
| 31 | + |
| 32 | +### 3. Error Handling |
| 33 | + |
| 34 | +CPython sets `errno` and calls `is_error()`. We return `Result` directly: |
| 35 | + |
| 36 | +```rust |
| 37 | +// CPython: |
| 38 | +errno = EDOM; |
| 39 | +if (errno && is_error(r, 1)) return NULL; |
| 40 | + |
| 41 | +// Rust: |
| 42 | +return Err(crate::Error::EDOM); |
| 43 | +``` |
| 44 | + |
| 45 | +**Never use `set_errno(libc::EDOM)` or similar** - just return `Err()` directly. |
| 46 | +The only valid `set_errno` call is `set_errno(0)` to clear errno before libm calls. |
| 47 | + |
| 48 | +### 4. Special Cases |
| 49 | + |
| 50 | +CPython has explicit special case handling for IEEE specials (NaN, Inf, etc.). Copy this logic exactly: |
| 51 | + |
| 52 | +```rust |
| 53 | +// Example from pow(): |
| 54 | +if !x.is_finite() || !y.is_finite() { |
| 55 | + if x.is_nan() { |
| 56 | + return Ok(if y == 0.0 { 1.0 } else { x }); // NaN**0 = 1 |
| 57 | + } |
| 58 | + // ... more special cases |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +### 5. Reference CPython Source |
| 63 | + |
| 64 | +Always check CPython source in the `cpython/` directory: |
| 65 | + |
| 66 | +**For math module** (`Modules/mathmodule.c`): |
| 67 | +- Function implementations |
| 68 | +- Helper macros (`FUNC1`, `FUNC1D`, `FUNC2`) |
| 69 | +- Special case comments |
| 70 | +- Error conditions |
| 71 | + |
| 72 | +**For cmath module** (`Modules/cmathmodule.c`): |
| 73 | +- `special_type()` enum and function |
| 74 | +- 7x7 special value tables (e.g., `tanh_special_values`) |
| 75 | +- `SPECIAL_VALUE` macro usage |
| 76 | +- Complex-specific error handling |
| 77 | + |
| 78 | +### 6. Fused Multiply-Add (mul_add) |
| 79 | + |
| 80 | +For bit-exact matching with CPython, use `crate::mul_add(a, b, c)` instead of `a * b + c` in specific cases. |
| 81 | + |
| 82 | +**Why this matters**: CPython compiled with clang on macOS may use FMA (fused multiply-add) instructions for expressions like `1.0 + x * x`. FMA computes `a * b + c` in a single operation without intermediate rounding, which can produce results that differ by 1-2 ULP from separate multiply and add operations. |
| 83 | + |
| 84 | +**When to use `mul_add`**: |
| 85 | +- Expressions of the form `a * b + c` or `c + a * b` in complex math functions |
| 86 | +- Especially in formulas like `1.0 + x * x` → `mul_add(x, x, 1.0)` |
| 87 | + |
| 88 | +**Example** (from `c_tanh`): |
| 89 | +```rust |
| 90 | +// Wrong - may differ from CPython by 1-2 ULP |
| 91 | +let denom = 1.0 + txty * txty; |
| 92 | +let r_re = tx * (1.0 + ty * ty) / denom; |
| 93 | + |
| 94 | +// Correct - matches CPython exactly |
| 95 | +let denom = mul_add(txty, txty, 1.0); |
| 96 | +let r_re = tx * mul_add(ty, ty, 1.0) / denom; |
| 97 | +``` |
| 98 | + |
| 99 | +**Example** (from `c_asinh`): |
| 100 | +```rust |
| 101 | +// mul_add for cross-product calculations |
| 102 | +let r_re = m::asinh(mul_add(s1.re, s2.im, -(s2.re * s1.im))); |
| 103 | +let r_im = m::atan2(z.im, mul_add(s1.re, s2.re, -(s1.im * s2.im))); |
| 104 | +``` |
| 105 | + |
| 106 | +**Example** (from `c_atanh`): |
| 107 | +```rust |
| 108 | +// mul_add for squared terms |
| 109 | +let one_minus_re = 1.0 - z.re; |
| 110 | +let r_re = m::log1p(4.0 * z.re / mul_add(one_minus_re, one_minus_re, ay * ay)) / 4.0; |
| 111 | +let r_im = -m::atan2(-2.0 * z.im, mul_add(one_minus_re, 1.0 + z.re, -(ay * ay))) / 2.0; |
| 112 | +``` |
| 113 | + |
| 114 | +**Feature flag**: The `mul_add` feature controls whether hardware FMA is used: |
| 115 | + |
| 116 | +- `mul_add` enabled: Uses `f64::mul_add()` (hardware FMA instruction) |
| 117 | +- `mul_add` disabled (default): Falls back to `a * b + c` (separate operations) |
| 118 | + |
| 119 | +Note: macOS CI always enables `mul_add` because CPython on macOS uses FMA. |
| 120 | + |
| 121 | +**How to identify missing mul_add usage**: |
| 122 | +1. If a test fails with 1-2 ULP difference |
| 123 | +2. Look for `a * b + c` or `c + a * b` patterns in the failing function |
| 124 | +3. Replace with `mul_add(a, b, c)` and re-test |
| 125 | + |
| 126 | +### 7. Platform-specific sincos (macOS, cmath only) |
| 127 | + |
| 128 | +On macOS, Python's cmath module uses Apple's `__sincos_stret` function, which computes sin and cos together with slightly different results than calling them separately (up to 1 ULP difference). |
| 129 | + |
| 130 | +For bit-exact matching on macOS, use `m::sincos(x)` which returns `(sin, cos)` tuple: |
| 131 | + |
| 132 | +```rust |
| 133 | +// Instead of: |
| 134 | +let sin_x = m::sin(x); |
| 135 | +let cos_x = m::cos(x); |
| 136 | + |
| 137 | +// Use: |
| 138 | +let (sin_x, cos_x) = m::sincos(x); |
| 139 | +``` |
| 140 | + |
| 141 | +Required in cmath functions that use both sin and cos of the same angle: |
| 142 | + |
| 143 | +- `cosh`, `sinh` - for the imaginary argument |
| 144 | +- `exp` - for the imaginary argument |
| 145 | +- `rect` - for the phi angle |
| 146 | + |
| 147 | +On non-macOS platforms, `m::sincos(x)` falls back to calling sin and cos separately. |
| 148 | + |
| 149 | +## Testing |
| 150 | + |
| 151 | +### EDGE_VALUES |
| 152 | + |
| 153 | +All float functions must be tested with `crate::test::EDGE_VALUES` which includes: |
| 154 | +- Zeros: `0.0`, `-0.0` |
| 155 | +- Infinities: `INFINITY`, `NEG_INFINITY` |
| 156 | +- NaNs: `NAN`, `-NAN`, and NaN with different payload |
| 157 | +- Subnormals |
| 158 | +- Boundary values: `MIN_POSITIVE`, `MAX`, `MIN` |
| 159 | +- Large values near infinity |
| 160 | +- Trigonometric special values: `PI`, `PI/2`, `PI/4`, `TAU` |
| 161 | + |
| 162 | +### Error Type Verification |
| 163 | + |
| 164 | +Tests must verify both: |
| 165 | +1. Correct values for Ok results |
| 166 | +2. Correct error types (EDOM vs ERANGE) for Err results |
| 167 | + |
| 168 | +Python `ValueError` → `Error::EDOM` |
| 169 | +Python `OverflowError` → `Error::ERANGE` |
| 170 | + |
| 171 | +## File Structure |
| 172 | + |
| 173 | +### Core |
| 174 | +- `src/lib.rs` - Root module, `mul_add` function |
| 175 | +- `src/err.rs` - Error types (EDOM, ERANGE) |
| 176 | +- `src/test.rs` - Test helpers, `EDGE_VALUES`, `EDGE_INTS` |
| 177 | + |
| 178 | +### System libm bindings |
| 179 | +- `src/m_sys.rs` - Raw FFI declarations (`extern "C"`) |
| 180 | +- `src/m.rs` - Safe wrappers, platform-specific `sincos` |
| 181 | + |
| 182 | +### math module |
| 183 | +- `src/math.rs` - Main module, `math_1`, `math_2` helpers, `hypot`, constants |
| 184 | +- `src/math/exponential.rs` - exp, log, pow, sqrt, cbrt, etc. |
| 185 | +- `src/math/trigonometric.rs` - sin, cos, tan, asin, acos, atan, etc. |
| 186 | +- `src/math/misc.rs` - frexp, ldexp, modf, fmod, copysign, isclose, ulp, etc. |
| 187 | +- `src/math/gamma.rs` - gamma, lgamma, erf, erfc |
| 188 | +- `src/math/aggregate.rs` - fsum, prod, sumprod, dist (vector operations) |
| 189 | +- `src/math/integer.rs` - gcd, lcm, isqrt, comb, perm, factorial (requires `_bigint` feature) |
| 190 | + |
| 191 | +### cmath module (requires `complex` feature) |
| 192 | +- `src/cmath.rs` - Main module, `special_type`, `special_value!` macro, shared constants |
| 193 | +- `src/cmath/exponential.rs` - sqrt, exp, log, log10 |
| 194 | +- `src/cmath/trigonometric.rs` - sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, asinh, acosh, atanh |
| 195 | +- `src/cmath/misc.rs` - phase, polar, rect, abs, isfinite, isnan, isinf, isclose |
0 commit comments