Found while looking at Term as a Coefficient for #219. All four reproduce on main (da073eb); tests below fail today.
1. Term::half() ignores self
coeff.rs#L9 returns Term::from_f64(0.5) instead of self / 2. PauliSum projections call v.half() (proj.rs#L16, #L35), so symbolic measurement projections replace the coefficient with the constant 0.5.
Fix: self.clone() * 0.5.
2. AddAssign<f64>: One + const is a no-op
add.rs#L24: the Inner::One arm builds a Sum but never assigns it to self.inner, so sin(x) += 1.0 leaves sin(x) unchanged.
3. MulAssign<Term>: One * Sum is a no-op
mul.rs#L172: new_sum.mul_term(...) result is dropped, self keeps its One value.
4. MulAssign<Term>: Sum * Sum drops negative constants
mul.rs#L114 and #L121 guard with c0 > min_eps instead of c0.abs() > min_eps, so cross terms with a negative constant vanish, e.g. (sin x − 1)(cos x − 1). The guard can simply be removed: Sum::add_term already drops |coeff| < min_eps.
5. (not verified by a test) MulAssign<Prod> drops phase
mul.rs#L64 merges sin/cos powers but never combines phase, which would affect the ComplexCoefficient path.
Reproducing tests
use ppvm_sym::Term;
const X: [f64; 1] = [0.3];
fn v(t: &Term) -> f64 { t.eval(&X).unwrap() }
#[test]
fn half_ignores_self() {
use ppvm_traits::traits::Coefficient;
assert!((v(&Term::from_f64(4.0).half()) - 2.0).abs() < 1e-12);
}
#[test]
fn one_plus_const() {
let mut t = Term::var(0).sin();
t += 1.0;
assert!((v(&t) - (X[0].sin() + 1.0)).abs() < 1e-12);
}
#[test]
fn one_times_sum() {
let mut s = Term::var(0).sin();
s += Term::var(0).cos();
let mut t = Term::var(0).sin();
t *= s;
assert!((v(&t) - X[0].sin() * (X[0].sin() + X[0].cos())).abs() < 1e-12);
}
#[test]
fn sum_times_sum_negative_const() {
let mut a = Term::var(0).sin();
a += Term::from_f64(-1.0);
let mut b = Term::var(0).cos();
b += Term::from_f64(-1.0);
assert!((v(&(a * b)) - (X[0].sin() - 1.0) * (X[0].cos() - 1.0)).abs() < 1e-12);
}
Found while looking at
Termas aCoefficientfor #219. All four reproduce onmain(da073eb); tests below fail today.1.
Term::half()ignoresselfcoeff.rs#L9returnsTerm::from_f64(0.5)instead ofself / 2.PauliSumprojections callv.half()(proj.rs#L16,#L35), so symbolic measurement projections replace the coefficient with the constant0.5.Fix:
self.clone() * 0.5.2.
AddAssign<f64>:One + constis a no-opadd.rs#L24: theInner::Onearm builds aSumbut never assigns it toself.inner, sosin(x) += 1.0leavessin(x)unchanged.3.
MulAssign<Term>:One * Sumis a no-opmul.rs#L172:new_sum.mul_term(...)result is dropped,selfkeeps itsOnevalue.4.
MulAssign<Term>:Sum * Sumdrops negative constantsmul.rs#L114and#L121guard withc0 > min_epsinstead ofc0.abs() > min_eps, so cross terms with a negative constant vanish, e.g.(sin x − 1)(cos x − 1). The guard can simply be removed:Sum::add_termalready drops|coeff| < min_eps.5. (not verified by a test)
MulAssign<Prod>drops phasemul.rs#L64mergessin/cospowers but never combinesphase, which would affect theComplexCoefficientpath.Reproducing tests