Skip to content

Commit 47d3a60

Browse files
committed
CI and maintanence files
1 parent 50291e7 commit 47d3a60

File tree

3 files changed

+210
-16
lines changed

3 files changed

+210
-16
lines changed

.github/workflows/rust.yml

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,26 @@ env:
1111

1212
jobs:
1313
build:
14-
name: Build and test
14+
name: Build and test (${{ matrix.os }})
1515
runs-on: ${{ matrix.os }}
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
os: [
20-
ubuntu-latest,
21-
windows-latest,
22-
macos-latest,
23-
]
19+
os: [ubuntu-latest, windows-latest, macos-latest]
2420
rust: [stable]
21+
include:
22+
- os: ubuntu-latest
23+
features: complex
24+
- os: windows-latest
25+
features: complex
26+
- os: macos-latest
27+
features: complex,mul_add
2528
steps:
2629
- uses: actions/checkout@v4
2730
- name: Set up Python
2831
uses: actions/setup-python@v5
2932
with:
30-
python-version: "3.13"
31-
check-latest: true
33+
python-version: "3.14"
3234
- name: Setup Rust
3335
uses: actions-rs/toolchain@v1
3436
with:
@@ -37,13 +39,9 @@ jobs:
3739
override: true
3840
- name: Build
3941
run: cargo build --verbose
40-
- name: Run tests
41-
if: matrix.os != 'macos-latest'
42-
run: cargo test --verbose && cargo test --release --verbose
43-
- name: Run tests with FMA (macOS)
44-
if: matrix.os == 'macos-latest'
45-
run: cargo test --verbose --features mul_add && cargo test --release --verbose --features mul_add
4642
- name: Run tests with num-bigint
47-
run: cargo test --verbose --features num-bigint
43+
run: cargo test --verbose --features ${{ matrix.features }},num-bigint
44+
- name: Run tests with num-bigint (Release)
45+
run: cargo test --verbose --features ${{ matrix.features }},num-bigint --release
4846
- name: Run tests with malachite-bigint
49-
run: cargo test --verbose --features malachite-bigint
47+
run: cargo test --verbose --features ${{ matrix.features }},malachite-bigint

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.14

AGENTS.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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

Comments
 (0)