Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions benches/scalar_micro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,54 @@ const SCALAR_MICRO_GROUPS: &[BenchGroupDoc] = &[
name: "atanh_sqrt_two_error",
description: "Rejects atanh(sqrt(2)) through exact structural domain checks.",
},
BenchDoc {
name: "sinh_ln_two",
description: "Folds sinh(ln(2)) to the exact rational 3/4 via the integer-log-collapse shortcut.",
},
BenchDoc {
name: "cosh_ln_two",
description: "Folds cosh(ln(2)) to the exact rational 5/4 via the integer-log-collapse shortcut.",
},
BenchDoc {
name: "tanh_ln_two",
description: "Folds tanh(ln(2)) to the exact rational 3/5 via the integer-log-collapse shortcut.",
},
BenchDoc {
name: "sinh_rational_one",
description: "Builds sinh(1) through the generic (exp(x) - exp(-x))/2 identity path.",
},
BenchDoc {
name: "cosh_rational_one",
description: "Builds cosh(1) through the generic (exp(x) + exp(-x))/2 identity path.",
},
BenchDoc {
name: "tanh_rational_one",
description: "Builds tanh(1) through the generic (exp(x) - exp(-x))/(exp(x) + exp(-x)) identity path.",
},
BenchDoc {
name: "atan2_origin",
description: "Hits the origin (0, 0) short-circuit returning exact zero.",
},
BenchDoc {
name: "atan2_axis_positive_y",
description: "Hits the positive-y axis short-circuit returning exact pi/2.",
},
BenchDoc {
name: "atan2_axis_negative_x",
description: "Hits the negative-x axis short-circuit returning exact pi.",
},
BenchDoc {
name: "atan2_quadrant_one_unit_diagonal",
description: "Quadrant I unit diagonal reduces to atan(1) = pi/4 exact special form.",
},
BenchDoc {
name: "atan2_quadrant_two_pi_correction",
description: "Quadrant II (1, -2) exercises atan(small ratio) + pi correction.",
},
BenchDoc {
name: "atan2_quadrant_three_negative_pi",
description: "Quadrant III (-1, -2) exercises atan(small ratio) - pi correction.",
},
BenchDoc {
name: "log2_power_of_two",
description: "Folds log2(1024) to the exact rational 10 via the integer-log-detection shortcut.",
Expand Down Expand Up @@ -1075,6 +1123,100 @@ fn bench_exact_transcendental_special_forms(c: &mut Criterion) {
)
});

let ln_two = Real::new(Rational::new(2)).ln().unwrap();
let rational_one = Real::one();

group.bench_function("sinh_ln_two", |b| {
b.iter_batched(
|| ln_two.clone(),
|value| black_box(value.sinh().unwrap()),
BatchSize::SmallInput,
)
});
group.bench_function("cosh_ln_two", |b| {
b.iter_batched(
|| ln_two.clone(),
|value| black_box(value.cosh().unwrap()),
BatchSize::SmallInput,
)
});
group.bench_function("tanh_ln_two", |b| {
b.iter_batched(
|| ln_two.clone(),
|value| black_box(value.tanh().unwrap()),
BatchSize::SmallInput,
)
});
group.bench_function("sinh_rational_one", |b| {
b.iter_batched(
|| rational_one.clone(),
|value| black_box(value.sinh().unwrap()),
BatchSize::SmallInput,
)
});
group.bench_function("cosh_rational_one", |b| {
b.iter_batched(
|| rational_one.clone(),
|value| black_box(value.cosh().unwrap()),
BatchSize::SmallInput,
)
});
group.bench_function("tanh_rational_one", |b| {
b.iter_batched(
|| rational_one.clone(),
|value| black_box(value.tanh().unwrap()),
BatchSize::SmallInput,
)
});

let zero = Real::zero();
let positive_one = Real::one();
let negative_one = -Real::one();
let negative_two = Real::from(-2_i32);

group.bench_function("atan2_origin", |b| {
b.iter_batched(
|| (zero.clone(), zero.clone()),
|(y, x)| black_box(y.atan2(x)),
BatchSize::SmallInput,
)
});
group.bench_function("atan2_axis_positive_y", |b| {
b.iter_batched(
|| (positive_one.clone(), zero.clone()),
|(y, x)| black_box(y.atan2(x)),
BatchSize::SmallInput,
)
});
group.bench_function("atan2_axis_negative_x", |b| {
b.iter_batched(
|| (zero.clone(), negative_one.clone()),
|(y, x)| black_box(y.atan2(x)),
BatchSize::SmallInput,
)
});
group.bench_function("atan2_quadrant_one_unit_diagonal", |b| {
b.iter_batched(
|| (positive_one.clone(), positive_one.clone()),
|(y, x)| black_box(y.atan2(x)),
BatchSize::SmallInput,
)
});
group.bench_function("atan2_quadrant_two_pi_correction", |b| {
b.iter_batched(
|| (positive_one.clone(), negative_two.clone()),
|(y, x)| black_box(y.atan2(x)),
BatchSize::SmallInput,
)
});
group.bench_function("atan2_quadrant_three_negative_pi", |b| {
b.iter_batched(
|| (negative_one.clone(), negative_two.clone()),
|(y, x)| black_box(y.atan2(x)),
BatchSize::SmallInput,
)
});

let log2_power = Real::new(Rational::new(1024));
let log2_three = Real::new(Rational::new(3));
let ln_five = Real::new(Rational::new(5)).ln().unwrap();
Expand Down
74 changes: 49 additions & 25 deletions benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,38 +346,62 @@ Construction-time shortcuts for exact rational multiples of pi and inverse compo
| `exact_transcendental_special_forms/asin_sin_6pi_7` | not run | not run | Recognizes the principal branch of asin(sin(6pi/7)). |
| `exact_transcendental_special_forms/acos_cos_9pi_7` | not run | not run | Recognizes the principal branch of acos(cos(9pi/7)). |
| `exact_transcendental_special_forms/atan_tan_6pi_7` | not run | not run | Recognizes the principal branch of atan(tan(6pi/7)). |
| `exact_transcendental_special_forms/asinh_large` | not run | not run | Builds a large inverse hyperbolic sine without exact intermediate Reals. |
| `exact_transcendental_special_forms/atanh_sqrt_half` | 192.18 ns | 189.98 ns - 194.71 ns | Builds atanh(sqrt(2)/2) after exact structural domain checks. |
| `exact_transcendental_special_forms/atanh_sqrt_two_error` | 199.45 ns | 121.09 ns - 355.20 ns | Rejects atanh(sqrt(2)) through exact structural domain checks. |
| `exact_transcendental_special_forms/log2_power_of_two` | 173.46 ns | 171.28 ns - 175.58 ns | Folds log2(1024) to the exact rational 10 via the integer-log-detection shortcut. |
| `exact_transcendental_special_forms/log2_rational_three` | 289.47 ns | 284.87 ns - 294.52 ns | Builds log2(3) as a lightweight Log2 symbolic certificate. |
| `exact_transcendental_special_forms/log2_ln_quotient_fold` | 1.283 us | 1.228 us - 1.371 us | Folds ln(5) / ln(2) into a Log2 certificate via the divide-recognize shortcut. |
| `exact_transcendental_special_forms/asinh_large` | 361.76 ns | 360.65 ns - 362.87 ns | Builds a large inverse hyperbolic sine without exact intermediate Reals. |
| `exact_transcendental_special_forms/atanh_sqrt_half` | 636.03 ns | 633.97 ns - 638.10 ns | Builds atanh(sqrt(2)/2) after exact structural domain checks. |
| `exact_transcendental_special_forms/atanh_sqrt_two_error` | 453.00 ns | 445.31 ns - 460.68 ns | Rejects atanh(sqrt(2)) through exact structural domain checks. |
| `exact_transcendental_special_forms/sinh_ln_two` | 1.562 us | 1.519 us - 1.604 us | Folds sinh(ln(2)) to the exact rational 3/4 via the integer-log-collapse shortcut. |
| `exact_transcendental_special_forms/cosh_ln_two` | 1.329 us | 1.312 us - 1.346 us | Folds cosh(ln(2)) to the exact rational 5/4 via the integer-log-collapse shortcut. |
| `exact_transcendental_special_forms/tanh_ln_two` | 1.470 us | 1.436 us - 1.505 us | Folds tanh(ln(2)) to the exact rational 3/5 via the integer-log-collapse shortcut. |
| `exact_transcendental_special_forms/sinh_rational_one` | 2.103 us | 2.091 us - 2.115 us | Builds sinh(1) through the generic (exp(x) - exp(-x))/2 identity path. |
| `exact_transcendental_special_forms/cosh_rational_one` | 1.761 us | 1.745 us - 1.777 us | Builds cosh(1) through the generic (exp(x) + exp(-x))/2 identity path. |
| `exact_transcendental_special_forms/tanh_rational_one` | 5.608 us | 5.595 us - 5.621 us | Builds tanh(1) through the generic (exp(x) - exp(-x))/(exp(x) + exp(-x)) identity path. |
| `exact_transcendental_special_forms/atan2_origin` | 528.39 ns | 219.56 ns - 837.23 ns | Hits the origin (0, 0) short-circuit returning exact zero. |
| `exact_transcendental_special_forms/atan2_axis_positive_y` | 289.65 ns | 285.15 ns - 294.15 ns | Hits the positive-y axis short-circuit returning exact pi/2. |
| `exact_transcendental_special_forms/atan2_axis_negative_x` | 261.49 ns | 260.46 ns - 262.51 ns | Hits the negative-x axis short-circuit returning exact pi. |
| `exact_transcendental_special_forms/atan2_quadrant_one_unit_diagonal` | 756.33 ns | 746.31 ns - 766.34 ns | Quadrant I unit diagonal reduces to atan(1) = pi/4 exact special form. |
| `exact_transcendental_special_forms/atan2_quadrant_two_pi_correction` | 1.890 us | 1.872 us - 1.908 us | Quadrant II (1, -2) exercises atan(small ratio) + pi correction. |
| `exact_transcendental_special_forms/atan2_quadrant_three_negative_pi` | 1.156 us | 1.140 us - 1.172 us | Quadrant III (-1, -2) exercises atan(small ratio) - pi correction. |
| `exact_transcendental_special_forms/log2_power_of_two` | not run | not run | Folds log2(1024) to the exact rational 10 via the integer-log-detection shortcut. |
| `exact_transcendental_special_forms/log2_rational_three` | not run | not run | Builds log2(3) as a lightweight Log2 symbolic certificate. |
| `exact_transcendental_special_forms/log2_ln_quotient_fold` | not run | not run | Folds ln(5) / ln(2) into a Log2 certificate via the divide-recognize shortcut. |

### `symbolic_reductions`

Existing symbolic constant algebra cases considered for additional reductions.

| Benchmark output | Mean | 95% CI | What it measures |
| --- | ---: | ---: | --- |
| `symbolic_reductions/sqrt_pi_square` | 137.90 ns | 134.60 ns - 141.59 ns | Reduces sqrt(pi^2). |
| `symbolic_reductions/sqrt_pi_e_square` | 174.66 ns | 173.75 ns - 175.54 ns | Reduces sqrt((pi * e)^2). |
| `symbolic_reductions/ln_scaled_e` | 1.394 us | 1.382 us - 1.408 us | Reduces ln(2 * e). |
| `symbolic_reductions/sub_pi_three` | 248.16 ns | 244.62 ns - 252.32 ns | Builds the certified pi - 3 constant-offset form. |
| `symbolic_reductions/pi_minus_three_facts` | 36.67 ns | 36.34 ns - 37.03 ns | Reads structural facts for the cached pi - 3 offset form. |
| `symbolic_reductions/div_exp_exp` | 566.66 ns | 562.95 ns - 570.95 ns | Reduces e^3 / e. |
| `symbolic_reductions/div_pi_square_e` | 464.50 ns | 463.12 ns - 465.90 ns | Reduces pi^2 / e. |
| `symbolic_reductions/div_const_products` | 855.78 ns | 852.01 ns - 860.32 ns | Reduces (pi^3 * e^5) / (pi * e^2). |
| `symbolic_reductions/inverse_pi` | 89.50 ns | 89.20 ns - 89.85 ns | Builds the reciprocal of pi. |
| `symbolic_reductions/div_one_pi` | 141.50 ns | 140.79 ns - 142.37 ns | Reduces 1 / pi. |
| `symbolic_reductions/div_rational_exp` | 292.12 ns | 289.84 ns - 294.69 ns | Reduces 2 / e. |
| `symbolic_reductions/div_e_pi` | 269.92 ns | 261.15 ns - 279.59 ns | Reduces e / pi. |
| `symbolic_reductions/mul_pi_inverse_pi` | 247.62 ns | 246.97 ns - 248.30 ns | Multiplies pi by its reciprocal. |
| `symbolic_reductions/mul_pi_e_sqrt_two` | 438.83 ns | 437.88 ns - 439.69 ns | Builds the factored pi * e * sqrt(2) form. |
| `symbolic_reductions/mul_const_product_sqrt_sqrt` | 685.04 ns | 676.75 ns - 693.88 ns | Cancels sqrt(2) from (pi * e * sqrt(2)) * sqrt(2). |
| `symbolic_reductions/div_const_product_sqrt_e` | 728.58 ns | 725.63 ns - 731.29 ns | Reduces (pi * e * sqrt(2)) / e. |
| `symbolic_reductions/inverse_const_product_sqrt` | 478.56 ns | 475.73 ns - 482.08 ns | Builds a rationalized reciprocal of pi * e * sqrt(2). |
| `symbolic_reductions/inverse_sqrt_two` | 101.33 ns | 100.92 ns - 101.81 ns | Builds the rationalized reciprocal of unit-scaled sqrt(2). |
| `symbolic_reductions/div_sqrt_two_sqrt_three` | 842.52 ns | 838.35 ns - 847.63 ns | Rationalizes a quotient of two unit-scaled square roots. |
| `symbolic_reductions/sqrt_pi_square` | not run | not run | Reduces sqrt(pi^2). |
| `symbolic_reductions/sqrt_pi_e_square` | not run | not run | Reduces sqrt((pi * e)^2). |
| `symbolic_reductions/ln_scaled_e` | not run | not run | Reduces ln(2 * e). |
| `symbolic_reductions/sub_pi_three` | not run | not run | Builds the certified pi - 3 constant-offset form. |
| `symbolic_reductions/pi_minus_three_facts` | not run | not run | Reads structural facts for the cached pi - 3 offset form. |
| `symbolic_reductions/div_exp_exp` | not run | not run | Reduces e^3 / e. |
| `symbolic_reductions/div_pi_square_e` | not run | not run | Reduces pi^2 / e. |
| `symbolic_reductions/div_const_products` | not run | not run | Reduces (pi^3 * e^5) / (pi * e^2). |
| `symbolic_reductions/inverse_pi` | not run | not run | Builds the reciprocal of pi. |
| `symbolic_reductions/div_one_pi` | not run | not run | Reduces 1 / pi. |
| `symbolic_reductions/div_rational_exp` | not run | not run | Reduces 2 / e. |
| `symbolic_reductions/div_e_pi` | not run | not run | Reduces e / pi. |
| `symbolic_reductions/mul_pi_inverse_pi` | not run | not run | Multiplies pi by its reciprocal. |
| `symbolic_reductions/mul_pi_e_sqrt_two` | not run | not run | Builds the factored pi * e * sqrt(2) form. |
| `symbolic_reductions/mul_const_product_sqrt_sqrt` | not run | not run | Cancels sqrt(2) from (pi * e * sqrt(2)) * sqrt(2). |
| `symbolic_reductions/div_const_product_sqrt_e` | not run | not run | Reduces (pi * e * sqrt(2)) / e. |
| `symbolic_reductions/inverse_const_product_sqrt` | not run | not run | Builds a rationalized reciprocal of pi * e * sqrt(2). |
| `symbolic_reductions/inverse_sqrt_two` | not run | not run | Builds the rationalized reciprocal of unit-scaled sqrt(2). |
| `symbolic_reductions/div_sqrt_two_sqrt_three` | not run | not run | Rationalizes a quotient of two unit-scaled square roots. |

### `exact_product_sums`

Fixed product-sum reducers used by determinant and cofactor kernels.

| Benchmark output | Mean | 95% CI | What it measures |
| --- | ---: | ---: | --- |
| `exact_product_sums/signed_product_sum_lcm_6x2` | not run | not run | Computes an exact rational six-term signed product sum with mixed denominators. |
| `exact_product_sums/signed_product_sum_common_scale_6x2` | not run | not run | Computes an exact rational six-term signed product sum through the carried common-scale reducer. |
| `exact_product_sums/signed_product_sum_sparse_single_6x2` | not run | not run | Computes a sparse exact rational six-term signed product sum with one active product. |
| `exact_product_sums/real_signed_product_sum_rational_det3` | not run | not run | Computes a 3x3 determinant-shaped signed product sum through the public `Real` builder. |
| `exact_product_sums/real_signed_product_sum_mixed_symbolic_det3` | not run | not run | Computes the same determinant-shaped builder with symbolic factors and rational scales. |

<!-- END scalar_micro -->

Expand Down
48 changes: 48 additions & 0 deletions src/computable/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3203,6 +3203,54 @@ impl Computable {
.add(self.inverse().atan().negate())
}

/// Two-argument arctangent of `(self, x)`, returning the angle of the
/// point `(x, self)` measured counterclockwise from the positive `x`
/// axis in the principal range `(-pi, pi]`.
///
/// `self` is the `y` coordinate and `x` is the `x` coordinate, matching
/// the IEEE 754 `atan2(y, x)` convention. The implementation reduces to
/// the single-argument [`Computable::atan`] kernel after a quadrant
/// correction:
/// - `x > 0`: returns `atan(self / x)`.
/// - `x < 0` and `self >= 0`: returns `atan(self / x) + pi`.
/// - `x < 0` and `self < 0`: returns `atan(self / x) - pi`.
/// - axes return exact constants: `pi/2`, `-pi/2`, `pi`, or zero.
/// - the origin `(0, 0)` returns zero, matching `f64::atan2`.
pub fn atan2(self, x: Computable) -> Computable {
let y_sign = self.sign();
let x_sign = x.sign();
match (y_sign, x_sign) {
(Sign::NoSign, Sign::NoSign) | (Sign::NoSign, Sign::Plus) => {
crate::trace_dispatch!("computable", "atan2", "axis-zero-y");
return Self::zero();
}
(Sign::NoSign, Sign::Minus) => {
crate::trace_dispatch!("computable", "atan2", "axis-negative-x");
return Self::pi();
}
(Sign::Plus, Sign::NoSign) => {
crate::trace_dispatch!("computable", "atan2", "axis-positive-y");
return Self::pi().shift_right(1);
}
(Sign::Minus, Sign::NoSign) => {
crate::trace_dispatch!("computable", "atan2", "axis-negative-y");
return Self::pi().shift_right(1).negate();
}
_ => {}
}
let base = self.multiply(x.clone().inverse()).atan();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parameter x is passed by value to atan2 and is not used after this line. Therefore, calling x.clone() before inverse() is redundant and incurs an unnecessary heap allocation of the Approximation enum. You can directly consume x by calling x.inverse().

        let base = self.multiply(x.inverse()).atan();

if x_sign == Sign::Plus {
crate::trace_dispatch!("computable", "atan2", "quadrant-right");
base
} else if y_sign == Sign::Plus {
crate::trace_dispatch!("computable", "atan2", "quadrant-upper-left");
base.add(Self::pi())
} else {
crate::trace_dispatch!("computable", "atan2", "quadrant-lower-left");
base.add(Self::pi().negate())
}
}

/// Inverse sine of this number.
pub fn asin(self) -> Computable {
if let Some(rational) = self.exact_rational() {
Expand Down
Loading
Loading