use std::cmp::Ordering;
use hyperreal::Real;
use hypersolve::{
AlgebraicRootArithmeticOp, AlgebraicRootArithmeticReport, AlgebraicRootArithmeticStatus,
AlgebraicRootRepresentation, arithmetic_algebraic_root_representations,
};
use crate::classify::compare_reals;
use crate::{BezierAlgebraicImageStatus, BezierEndpointTangentImage2, Classification, CurvePolicy};
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAlgebraicTangentVector2 {
dx: AlgebraicRootRepresentation,
dy: AlgebraicRootRepresentation,
}
impl BezierAlgebraicTangentVector2 {
pub const fn new(
dx: AlgebraicRootRepresentation,
dy: AlgebraicRootRepresentation,
) -> BezierAlgebraicTangentVector2 {
Self { dx, dy }
}
pub fn from_endpoint_image(
image: &BezierEndpointTangentImage2,
) -> BezierAlgebraicTangentVectorReport {
if image.status() != BezierAlgebraicImageStatus::Transformed {
return BezierAlgebraicTangentVectorReport {
status: BezierAlgebraicTangentVectorStatus::ImageNotTransformed,
vector: None,
message: Some("endpoint tangent image was not transformed".to_owned()),
};
}
let (dx, dy) = match image {
BezierEndpointTangentImage2::Polynomial(image) => {
let dx = image
.dx()
.and_then(|coordinate| coordinate.representation());
let dy = image
.dy()
.and_then(|coordinate| coordinate.representation());
(dx, dy)
}
BezierEndpointTangentImage2::RationalQuadratic(image) => {
let dx = image
.dx()
.and_then(|coordinate| coordinate.representation());
let dy = image
.dy()
.and_then(|coordinate| coordinate.representation());
(dx, dy)
}
};
let (Some(dx), Some(dy)) = (dx, dy) else {
return BezierAlgebraicTangentVectorReport {
status: BezierAlgebraicTangentVectorStatus::MissingCoordinateImage,
vector: None,
message: Some("endpoint tangent image omitted a represented coordinate".to_owned()),
};
};
BezierAlgebraicTangentVectorReport {
status: BezierAlgebraicTangentVectorStatus::Extracted,
vector: Some(Self::new(dx.clone(), dy.clone())),
message: None,
}
}
pub const fn dx(&self) -> &AlgebraicRootRepresentation {
&self.dx
}
pub const fn dy(&self) -> &AlgebraicRootRepresentation {
&self.dy
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BezierAlgebraicTangentVectorStatus {
Extracted,
ImageNotTransformed,
MissingCoordinateImage,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAlgebraicTangentVectorReport {
pub status: BezierAlgebraicTangentVectorStatus,
pub vector: Option<BezierAlgebraicTangentVector2>,
pub message: Option<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BezierTangentTurnOrdering2 {
FirstBeforeSecond,
SecondBeforeFirst,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BezierAlgebraicTangentOrderStatus {
Ordered,
SameDirection,
ZeroTangent,
ArithmeticFailed,
SignUndecided,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BezierAlgebraicSameTangentOrderStatus {
Ordered,
SameDirection,
ZeroTangent,
ArithmeticFailed,
SignUndecided,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAlgebraicScalarSignReport {
pub arithmetic: Vec<AlgebraicRootArithmeticReport>,
pub scalar: Option<AlgebraicRootRepresentation>,
pub sign: Option<Ordering>,
pub message: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAlgebraicTangentOrderReport {
pub status: BezierAlgebraicTangentOrderStatus,
pub ordering: Option<BezierTangentTurnOrdering2>,
pub base_first_cross: Option<BezierAlgebraicScalarSignReport>,
pub base_second_cross: Option<BezierAlgebraicScalarSignReport>,
pub first_second_cross: Option<BezierAlgebraicScalarSignReport>,
pub message: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BezierAlgebraicSameTangentOrderReport {
pub status: BezierAlgebraicSameTangentOrderStatus,
pub ordering: Option<BezierTangentTurnOrdering2>,
pub first_curvature_cross: Option<BezierAlgebraicScalarSignReport>,
pub second_curvature_cross: Option<BezierAlgebraicScalarSignReport>,
pub magnitude_difference: Option<BezierAlgebraicScalarSignReport>,
pub message: Option<String>,
}
pub fn compare_algebraic_tangent_turn_from_base(
base: &BezierAlgebraicTangentVector2,
first: &BezierAlgebraicTangentVector2,
second: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> Classification<BezierAlgebraicTangentOrderReport> {
for tangent in [base, first, second] {
match tangent_nonzero(tangent, policy) {
AlgebraicTangentNonzero::Nonzero => {}
AlgebraicTangentNonzero::Zero(report) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::ZeroTangent,
None,
None,
None,
None,
Some(format!("zero tangent certified by {:?}", report.sign)),
));
}
AlgebraicTangentNonzero::Undecided(report) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::SignUndecided,
None,
None,
None,
None,
report.message,
));
}
AlgebraicTangentNonzero::ArithmeticFailed(report) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::ArithmeticFailed,
None,
None,
None,
None,
report.message,
));
}
}
}
let (first_half, base_first_cross) = match turn_half(base, first, policy) {
AlgebraicHalfTurn::Half(half, cross) => (half, cross),
AlgebraicHalfTurn::ZeroTangent(cross, dot) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::ZeroTangent,
None,
Some(cross),
None,
Some(dot),
Some("first tangent has zero direction relative to base".to_owned()),
));
}
AlgebraicHalfTurn::Undecided(cross, dot) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::SignUndecided,
None,
Some(cross),
None,
dot,
Some("could not certify first candidate half-turn".to_owned()),
));
}
AlgebraicHalfTurn::ArithmeticFailed(cross, dot) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::ArithmeticFailed,
None,
Some(cross),
None,
dot,
Some("could not construct first candidate half-turn scalar".to_owned()),
));
}
};
let (second_half, base_second_cross) = match turn_half(base, second, policy) {
AlgebraicHalfTurn::Half(half, cross) => (half, cross),
AlgebraicHalfTurn::ZeroTangent(cross, dot) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::ZeroTangent,
None,
Some(base_first_cross),
Some(cross),
Some(dot),
Some("second tangent has zero direction relative to base".to_owned()),
));
}
AlgebraicHalfTurn::Undecided(cross, dot) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::SignUndecided,
None,
Some(base_first_cross),
Some(cross),
dot,
Some("could not certify second candidate half-turn".to_owned()),
));
}
AlgebraicHalfTurn::ArithmeticFailed(cross, dot) => {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::ArithmeticFailed,
None,
Some(base_first_cross),
Some(cross),
dot,
Some("could not construct second candidate half-turn scalar".to_owned()),
));
}
};
if first_half != second_half {
return Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::Ordered,
Some(if first_half < second_half {
BezierTangentTurnOrdering2::FirstBeforeSecond
} else {
BezierTangentTurnOrdering2::SecondBeforeFirst
}),
Some(base_first_cross),
Some(base_second_cross),
None,
None,
));
}
let first_second_cross = cross_sign(first, second, policy);
match sign_status(&first_second_cross) {
ScalarSignStatus::Positive => Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::FirstBeforeSecond),
Some(base_first_cross),
Some(base_second_cross),
Some(first_second_cross),
None,
)),
ScalarSignStatus::Negative => Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::SecondBeforeFirst),
Some(base_first_cross),
Some(base_second_cross),
Some(first_second_cross),
None,
)),
ScalarSignStatus::Zero => Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::SameDirection,
None,
Some(base_first_cross),
Some(base_second_cross),
Some(first_second_cross),
Some("candidate tangent directions are collinear with the same half-turn".to_owned()),
)),
ScalarSignStatus::Undecided => Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::SignUndecided,
None,
Some(base_first_cross),
Some(base_second_cross),
Some(first_second_cross),
Some("could not certify candidate tangent order sign".to_owned()),
)),
ScalarSignStatus::ArithmeticFailed => Classification::Decided(order_report(
BezierAlgebraicTangentOrderStatus::ArithmeticFailed,
None,
Some(base_first_cross),
Some(base_second_cross),
Some(first_second_cross),
Some("could not construct candidate tangent order scalar".to_owned()),
)),
}
}
pub fn compare_algebraic_same_tangent_second_order(
first_tangent: &BezierAlgebraicTangentVector2,
first_second_derivative: &BezierAlgebraicTangentVector2,
second_tangent: &BezierAlgebraicTangentVector2,
second_second_derivative: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> Classification<BezierAlgebraicSameTangentOrderReport> {
for tangent in [first_tangent, second_tangent] {
match tangent_nonzero(tangent, policy) {
AlgebraicTangentNonzero::Nonzero => {}
AlgebraicTangentNonzero::Zero(report) => {
return Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::ZeroTangent,
None,
None,
None,
None,
Some(format!("zero tangent certified by {:?}", report.sign)),
));
}
AlgebraicTangentNonzero::Undecided(report) => {
return Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SignUndecided,
None,
None,
None,
None,
report.message,
));
}
AlgebraicTangentNonzero::ArithmeticFailed(report) => {
return Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::ArithmeticFailed,
None,
None,
None,
None,
report.message,
));
}
}
}
let first_cross = cross_sign(first_tangent, first_second_derivative, policy);
let second_cross = cross_sign(second_tangent, second_second_derivative, policy);
match (sign_status(&first_cross), sign_status(&second_cross)) {
(ScalarSignStatus::ArithmeticFailed, _) | (_, ScalarSignStatus::ArithmeticFailed) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::ArithmeticFailed,
None,
Some(first_cross),
Some(second_cross),
None,
Some("could not construct algebraic curvature cross scalar".to_owned()),
))
}
(ScalarSignStatus::Undecided, _) | (_, ScalarSignStatus::Undecided) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SignUndecided,
None,
Some(first_cross),
Some(second_cross),
None,
Some("could not certify algebraic curvature cross sign".to_owned()),
))
}
(ScalarSignStatus::Zero, ScalarSignStatus::Zero) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SameDirection,
None,
Some(first_cross),
Some(second_cross),
None,
Some("both algebraic second-order side witnesses vanished".to_owned()),
))
}
(ScalarSignStatus::Zero, _) | (_, ScalarSignStatus::Zero) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SameDirection,
None,
Some(first_cross),
Some(second_cross),
None,
Some("one algebraic second-order side witness vanished".to_owned()),
))
}
(ScalarSignStatus::Positive, ScalarSignStatus::Negative) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::FirstBeforeSecond),
Some(first_cross),
Some(second_cross),
None,
None,
))
}
(ScalarSignStatus::Negative, ScalarSignStatus::Positive) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::SecondBeforeFirst),
Some(first_cross),
Some(second_cross),
None,
None,
))
}
(ScalarSignStatus::Positive, ScalarSignStatus::Positive)
| (ScalarSignStatus::Negative, ScalarSignStatus::Negative) => {
compare_algebraic_same_side_curvature_magnitude(
first_tangent,
first_cross,
second_tangent,
second_cross,
policy,
)
}
}
}
pub fn compare_algebraic_same_tangent_third_order(
first_tangent: &BezierAlgebraicTangentVector2,
first_third_derivative: &BezierAlgebraicTangentVector2,
second_tangent: &BezierAlgebraicTangentVector2,
second_third_derivative: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> Classification<BezierAlgebraicSameTangentOrderReport> {
for tangent in [first_tangent, second_tangent] {
match tangent_nonzero(tangent, policy) {
AlgebraicTangentNonzero::Nonzero => {}
AlgebraicTangentNonzero::Zero(report) => {
return Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::ZeroTangent,
None,
None,
None,
None,
Some(format!("zero tangent certified by {:?}", report.sign)),
));
}
AlgebraicTangentNonzero::Undecided(report) => {
return Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SignUndecided,
None,
None,
None,
None,
report.message,
));
}
AlgebraicTangentNonzero::ArithmeticFailed(report) => {
return Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::ArithmeticFailed,
None,
None,
None,
None,
report.message,
));
}
}
}
let first_cross = cross_sign(first_tangent, first_third_derivative, policy);
let second_cross = cross_sign(second_tangent, second_third_derivative, policy);
match (sign_status(&first_cross), sign_status(&second_cross)) {
(ScalarSignStatus::ArithmeticFailed, _) | (_, ScalarSignStatus::ArithmeticFailed) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::ArithmeticFailed,
None,
Some(first_cross),
Some(second_cross),
None,
Some("could not construct algebraic third-order cross scalar".to_owned()),
))
}
(ScalarSignStatus::Undecided, _) | (_, ScalarSignStatus::Undecided) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SignUndecided,
None,
Some(first_cross),
Some(second_cross),
None,
Some("could not certify algebraic third-order cross sign".to_owned()),
))
}
(ScalarSignStatus::Zero, _) | (_, ScalarSignStatus::Zero) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SameDirection,
None,
Some(first_cross),
Some(second_cross),
None,
Some("an algebraic third-order side witness vanished".to_owned()),
))
}
(ScalarSignStatus::Positive, ScalarSignStatus::Negative) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::FirstBeforeSecond),
Some(first_cross),
Some(second_cross),
None,
None,
))
}
(ScalarSignStatus::Negative, ScalarSignStatus::Positive) => {
Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::SecondBeforeFirst),
Some(first_cross),
Some(second_cross),
None,
None,
))
}
(ScalarSignStatus::Positive, ScalarSignStatus::Positive)
| (ScalarSignStatus::Negative, ScalarSignStatus::Negative) => {
compare_algebraic_same_side_magnitude(
first_tangent,
first_cross,
second_tangent,
second_cross,
2,
"third-order",
policy,
)
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ScalarSignStatus {
Positive,
Negative,
Zero,
Undecided,
ArithmeticFailed,
}
enum AlgebraicTangentNonzero {
Nonzero,
Zero(BezierAlgebraicScalarSignReport),
Undecided(BezierAlgebraicScalarSignReport),
ArithmeticFailed(BezierAlgebraicScalarSignReport),
}
enum AlgebraicHalfTurn {
Half(u8, BezierAlgebraicScalarSignReport),
ZeroTangent(
BezierAlgebraicScalarSignReport,
BezierAlgebraicScalarSignReport,
),
Undecided(
BezierAlgebraicScalarSignReport,
Option<BezierAlgebraicScalarSignReport>,
),
ArithmeticFailed(
BezierAlgebraicScalarSignReport,
Option<BezierAlgebraicScalarSignReport>,
),
}
fn tangent_nonzero(
tangent: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> AlgebraicTangentNonzero {
let norm = norm_squared_sign(tangent, policy);
match sign_status(&norm) {
ScalarSignStatus::Positive => AlgebraicTangentNonzero::Nonzero,
ScalarSignStatus::Zero => AlgebraicTangentNonzero::Zero(norm),
ScalarSignStatus::Negative | ScalarSignStatus::Undecided => {
AlgebraicTangentNonzero::Undecided(norm)
}
ScalarSignStatus::ArithmeticFailed => AlgebraicTangentNonzero::ArithmeticFailed(norm),
}
}
fn turn_half(
base: &BezierAlgebraicTangentVector2,
candidate: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> AlgebraicHalfTurn {
let cross = cross_sign(base, candidate, policy);
match sign_status(&cross) {
ScalarSignStatus::Positive => AlgebraicHalfTurn::Half(0, cross),
ScalarSignStatus::Negative => AlgebraicHalfTurn::Half(1, cross),
ScalarSignStatus::Zero => {
let dot = dot_sign(base, candidate, policy);
match sign_status(&dot) {
ScalarSignStatus::Positive => AlgebraicHalfTurn::Half(0, cross),
ScalarSignStatus::Negative => AlgebraicHalfTurn::Half(1, cross),
ScalarSignStatus::Zero => AlgebraicHalfTurn::ZeroTangent(cross, dot),
ScalarSignStatus::Undecided => AlgebraicHalfTurn::Undecided(cross, Some(dot)),
ScalarSignStatus::ArithmeticFailed => {
AlgebraicHalfTurn::ArithmeticFailed(cross, Some(dot))
}
}
}
ScalarSignStatus::Undecided => AlgebraicHalfTurn::Undecided(cross, None),
ScalarSignStatus::ArithmeticFailed => AlgebraicHalfTurn::ArithmeticFailed(cross, None),
}
}
fn cross_sign(
left: &BezierAlgebraicTangentVector2,
right: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> BezierAlgebraicScalarSignReport {
let left_x_right_y = multiply(left.dx(), right.dy());
let left_y_right_x = multiply(left.dy(), right.dx());
let scalar = subtract(
left_x_right_y.result_representation.as_ref(),
left_x_right_y.exact_result.as_ref(),
left_y_right_x.result_representation.as_ref(),
left_y_right_x.exact_result.as_ref(),
);
scalar_sign_report(vec![left_x_right_y, left_y_right_x, scalar], policy)
}
fn dot_sign(
left: &BezierAlgebraicTangentVector2,
right: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> BezierAlgebraicScalarSignReport {
let left_x_right_x = multiply(left.dx(), right.dx());
let left_y_right_y = multiply(left.dy(), right.dy());
let scalar = add(
left_x_right_x.result_representation.as_ref(),
left_x_right_x.exact_result.as_ref(),
left_y_right_y.result_representation.as_ref(),
left_y_right_y.exact_result.as_ref(),
);
scalar_sign_report(vec![left_x_right_x, left_y_right_y, scalar], policy)
}
fn norm_squared_sign(
vector: &BezierAlgebraicTangentVector2,
policy: &CurvePolicy,
) -> BezierAlgebraicScalarSignReport {
let dx_squared = multiply(vector.dx(), vector.dx());
let dy_squared = multiply(vector.dy(), vector.dy());
let scalar = add(
dx_squared.result_representation.as_ref(),
dx_squared.exact_result.as_ref(),
dy_squared.result_representation.as_ref(),
dy_squared.exact_result.as_ref(),
);
scalar_sign_report(vec![dx_squared, dy_squared, scalar], policy)
}
fn compare_algebraic_same_side_curvature_magnitude(
first_tangent: &BezierAlgebraicTangentVector2,
first_cross: BezierAlgebraicScalarSignReport,
second_tangent: &BezierAlgebraicTangentVector2,
second_cross: BezierAlgebraicScalarSignReport,
policy: &CurvePolicy,
) -> Classification<BezierAlgebraicSameTangentOrderReport> {
compare_algebraic_same_side_magnitude(
first_tangent,
first_cross,
second_tangent,
second_cross,
3,
"curvature",
policy,
)
}
fn compare_algebraic_same_side_magnitude(
first_tangent: &BezierAlgebraicTangentVector2,
first_cross: BezierAlgebraicScalarSignReport,
second_tangent: &BezierAlgebraicTangentVector2,
second_cross: BezierAlgebraicScalarSignReport,
speed_power: usize,
witness_name: &str,
policy: &CurvePolicy,
) -> Classification<BezierAlgebraicSameTangentOrderReport> {
let first_speed = norm_squared_sign(first_tangent, policy);
let second_speed = norm_squared_sign(second_tangent, policy);
if !matches!(sign_status(&first_speed), ScalarSignStatus::Positive)
|| !matches!(sign_status(&second_speed), ScalarSignStatus::Positive)
{
return Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SignUndecided,
None,
Some(first_cross),
Some(second_cross),
None,
Some("could not certify positive tangent speeds".to_owned()),
));
}
let magnitude = same_side_magnitude_difference(
&first_cross,
&second_cross,
&first_speed,
&second_speed,
speed_power,
policy,
);
match sign_status(&magnitude) {
ScalarSignStatus::Negative => Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::FirstBeforeSecond),
Some(first_cross),
Some(second_cross),
Some(magnitude),
None,
)),
ScalarSignStatus::Positive => Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::Ordered,
Some(BezierTangentTurnOrdering2::SecondBeforeFirst),
Some(first_cross),
Some(second_cross),
Some(magnitude),
None,
)),
ScalarSignStatus::Zero => Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SameDirection,
None,
Some(first_cross),
Some(second_cross),
Some(magnitude),
Some(format!(
"same-side algebraic {witness_name} magnitudes are equal"
)),
)),
ScalarSignStatus::Undecided => Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::SignUndecided,
None,
Some(first_cross),
Some(second_cross),
Some(magnitude),
Some(format!(
"could not certify same-side algebraic {witness_name} magnitude"
)),
)),
ScalarSignStatus::ArithmeticFailed => Classification::Decided(same_tangent_report(
BezierAlgebraicSameTangentOrderStatus::ArithmeticFailed,
None,
Some(first_cross),
Some(second_cross),
Some(magnitude),
Some(format!(
"could not construct same-side algebraic {witness_name} magnitude"
)),
)),
}
}
fn same_side_magnitude_difference(
first_cross: &BezierAlgebraicScalarSignReport,
second_cross: &BezierAlgebraicScalarSignReport,
first_speed: &BezierAlgebraicScalarSignReport,
second_speed: &BezierAlgebraicScalarSignReport,
speed_power: usize,
policy: &CurvePolicy,
) -> BezierAlgebraicScalarSignReport {
let Some(first_cross_scalar) = first_cross.scalar.as_ref() else {
return scalar_sign_report(
vec![missing_operand_report(
AlgebraicRootArithmeticOp::Multiply,
"first curvature cross scalar was absent",
)],
policy,
);
};
let Some(second_cross_scalar) = second_cross.scalar.as_ref() else {
return scalar_sign_report(
vec![missing_operand_report(
AlgebraicRootArithmeticOp::Multiply,
"second curvature cross scalar was absent",
)],
policy,
);
};
let Some(first_speed_scalar) = first_speed.scalar.as_ref() else {
return scalar_sign_report(
vec![missing_operand_report(
AlgebraicRootArithmeticOp::Multiply,
"first speed scalar was absent",
)],
policy,
);
};
let Some(second_speed_scalar) = second_speed.scalar.as_ref() else {
return scalar_sign_report(
vec![missing_operand_report(
AlgebraicRootArithmeticOp::Multiply,
"second speed scalar was absent",
)],
policy,
);
};
let first_cross_squared = multiply(first_cross_scalar, first_cross_scalar);
let second_cross_squared = multiply(second_cross_scalar, second_cross_scalar);
let first_speed_power = power_representation(first_speed_scalar, speed_power);
let second_speed_power = power_representation(second_speed_scalar, speed_power);
let first_scaled = multiply_report_results(&first_cross_squared, &second_speed_power);
let second_scaled = multiply_report_results(&second_cross_squared, &first_speed_power);
let difference = subtract(
first_scaled.result_representation.as_ref(),
first_scaled.exact_result.as_ref(),
second_scaled.result_representation.as_ref(),
second_scaled.exact_result.as_ref(),
);
let mut arithmetic = Vec::new();
arithmetic.push(first_cross_squared);
arithmetic.push(second_cross_squared);
arithmetic.extend(first_speed_power.arithmetic);
arithmetic.extend(second_speed_power.arithmetic);
arithmetic.push(first_scaled);
arithmetic.push(second_scaled);
arithmetic.push(difference);
scalar_sign_report(arithmetic, policy)
}
struct AlgebraicPowerReport {
arithmetic: Vec<AlgebraicRootArithmeticReport>,
representation: Option<AlgebraicRootRepresentation>,
exact: Option<Real>,
}
fn power_representation(value: &AlgebraicRootRepresentation, power: usize) -> AlgebraicPowerReport {
assert!(power >= 1, "algebraic power must be positive");
let mut arithmetic = Vec::new();
let mut representation = Some(value.clone());
let mut exact = None;
for _ in 1..power {
let product = binary_from_report_values(
representation.as_ref(),
exact.as_ref(),
Some(value),
None,
AlgebraicRootArithmeticOp::Multiply,
);
representation = product.result_representation.clone();
exact = product.exact_result.clone();
arithmetic.push(product);
}
AlgebraicPowerReport {
arithmetic,
representation,
exact,
}
}
fn multiply_report_results(
left: &AlgebraicRootArithmeticReport,
right: &AlgebraicPowerReport,
) -> AlgebraicRootArithmeticReport {
binary_from_report_values(
left.result_representation.as_ref(),
left.exact_result.as_ref(),
right.representation.as_ref(),
right.exact.as_ref(),
AlgebraicRootArithmeticOp::Multiply,
)
}
fn multiply(
left: &AlgebraicRootRepresentation,
right: &AlgebraicRootRepresentation,
) -> AlgebraicRootArithmeticReport {
arithmetic_algebraic_root_representations(
left,
Some(right),
AlgebraicRootArithmeticOp::Multiply,
)
}
fn add(
left_representation: Option<&AlgebraicRootRepresentation>,
left_exact: Option<&Real>,
right_representation: Option<&AlgebraicRootRepresentation>,
right_exact: Option<&Real>,
) -> AlgebraicRootArithmeticReport {
binary_from_report_values(
left_representation,
left_exact,
right_representation,
right_exact,
AlgebraicRootArithmeticOp::Add,
)
}
fn subtract(
left_representation: Option<&AlgebraicRootRepresentation>,
left_exact: Option<&Real>,
right_representation: Option<&AlgebraicRootRepresentation>,
right_exact: Option<&Real>,
) -> AlgebraicRootArithmeticReport {
binary_from_report_values(
left_representation,
left_exact,
right_representation,
right_exact,
AlgebraicRootArithmeticOp::Subtract,
)
}
fn binary_from_report_values(
left_representation: Option<&AlgebraicRootRepresentation>,
left_exact: Option<&Real>,
right_representation: Option<&AlgebraicRootRepresentation>,
right_exact: Option<&Real>,
op: AlgebraicRootArithmeticOp,
) -> AlgebraicRootArithmeticReport {
let left = match representation_or_exact(left_representation, left_exact) {
Some(value) => value,
None => return missing_operand_report(op, "left arithmetic operand was absent"),
};
let right = match representation_or_exact(right_representation, right_exact) {
Some(value) => value,
None => return missing_operand_report(op, "right arithmetic operand was absent"),
};
arithmetic_algebraic_root_representations(&left, Some(&right), op)
}
fn representation_or_exact(
representation: Option<&AlgebraicRootRepresentation>,
exact: Option<&Real>,
) -> Option<AlgebraicRootRepresentation> {
representation
.cloned()
.or_else(|| exact.map(exact_value_representation))
}
fn exact_value_representation(value: &Real) -> AlgebraicRootRepresentation {
AlgebraicRootRepresentation {
constraint_index: 0,
symbol: hypersolve::SymbolId(0),
interval_index: 0,
polynomial_coefficients: vec![-value.clone(), Real::one()],
interval: hypersolve::IsolatedRootInterval {
lower: value.clone(),
upper: value.clone(),
exact_root: Some(value.clone()),
distinct_root_count: 1,
},
kind: hypersolve::AlgebraicRootKind::ExactRationalWitness,
validation: hypersolve::AlgebraicRootValidationReport {
status: hypersolve::AlgebraicRootValidationStatus::Valid,
message: None,
},
}
}
fn missing_operand_report(
operation: AlgebraicRootArithmeticOp,
message: impl Into<String>,
) -> AlgebraicRootArithmeticReport {
AlgebraicRootArithmeticReport {
operation,
status: AlgebraicRootArithmeticStatus::InvalidEvidence,
exact_result: None,
result_representation: None,
message: Some(message.into()),
}
}
fn scalar_sign_report(
arithmetic: Vec<AlgebraicRootArithmeticReport>,
policy: &CurvePolicy,
) -> BezierAlgebraicScalarSignReport {
let Some(last) = arithmetic.last() else {
return BezierAlgebraicScalarSignReport {
arithmetic,
scalar: None,
sign: None,
message: Some("scalar construction produced no arithmetic reports".to_owned()),
};
};
if !matches!(
last.status,
AlgebraicRootArithmeticStatus::ComputedExactRationalWitness
| AlgebraicRootArithmeticStatus::ComputedRepresentation
) {
return BezierAlgebraicScalarSignReport {
message: last.message.clone(),
arithmetic,
scalar: None,
sign: None,
};
}
let scalar = match representation_or_exact(
last.result_representation.as_ref(),
last.exact_result.as_ref(),
) {
Some(scalar) => scalar,
None => {
return BezierAlgebraicScalarSignReport {
arithmetic,
scalar: None,
sign: None,
message: Some("scalar arithmetic omitted represented result".to_owned()),
};
}
};
let sign = represented_sign(&scalar, policy);
let message = sign.is_none().then(|| {
"represented scalar isolating interval did not certify sign relative to zero".to_owned()
});
BezierAlgebraicScalarSignReport {
arithmetic,
scalar: Some(scalar),
sign,
message,
}
}
fn represented_sign(value: &AlgebraicRootRepresentation, policy: &CurvePolicy) -> Option<Ordering> {
if let Some(witness) = value.exact_rational_witness() {
return compare_reals(witness, &Real::zero(), policy);
}
let lower = compare_reals(&value.interval.lower, &Real::zero(), policy)?;
let upper = compare_reals(&value.interval.upper, &Real::zero(), policy)?;
if matches!(lower, Ordering::Greater) {
Some(Ordering::Greater)
} else if matches!(upper, Ordering::Less) {
Some(Ordering::Less)
} else {
None
}
}
fn sign_status(report: &BezierAlgebraicScalarSignReport) -> ScalarSignStatus {
if report.scalar.is_none() {
return ScalarSignStatus::ArithmeticFailed;
}
match report.sign {
Some(Ordering::Greater) => ScalarSignStatus::Positive,
Some(Ordering::Less) => ScalarSignStatus::Negative,
Some(Ordering::Equal) => ScalarSignStatus::Zero,
None => ScalarSignStatus::Undecided,
}
}
fn order_report(
status: BezierAlgebraicTangentOrderStatus,
ordering: Option<BezierTangentTurnOrdering2>,
base_first_cross: Option<BezierAlgebraicScalarSignReport>,
base_second_cross: Option<BezierAlgebraicScalarSignReport>,
first_second_cross: Option<BezierAlgebraicScalarSignReport>,
message: Option<String>,
) -> BezierAlgebraicTangentOrderReport {
BezierAlgebraicTangentOrderReport {
status,
ordering,
base_first_cross,
base_second_cross,
first_second_cross,
message,
}
}
fn same_tangent_report(
status: BezierAlgebraicSameTangentOrderStatus,
ordering: Option<BezierTangentTurnOrdering2>,
first_curvature_cross: Option<BezierAlgebraicScalarSignReport>,
second_curvature_cross: Option<BezierAlgebraicScalarSignReport>,
magnitude_difference: Option<BezierAlgebraicScalarSignReport>,
message: Option<String>,
) -> BezierAlgebraicSameTangentOrderReport {
BezierAlgebraicSameTangentOrderReport {
status,
ordering,
first_curvature_cross,
second_curvature_cross,
magnitude_difference,
message,
}
}