| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| use crate::model::Pt; |
| use crate::priors::{edge_prior, Cubic, Lambdas}; |
| use crate::window::{windowed_data, Face, GradCubic, RegionColor}; |
|
|
| |
| |
| |
| |
| |
| |
| pub struct EdgeGeom { |
| pub cubics: Vec<Cubic>, |
| |
| pub active: bool, |
| |
| |
| polyline: Vec<Pt>, |
| } |
|
|
| |
| |
| |
| #[derive(Clone, Copy, Debug)] |
| pub struct SlotLoc { |
| pub edge: usize, |
| pub cubic: usize, |
| pub k: usize, |
| } |
|
|
| |
| pub struct LoopSpec { |
| |
| pub face: usize, |
| |
| pub darts: Vec<(usize, i32)>, |
| } |
|
|
| |
| #[derive(Clone, Copy, Debug)] |
| pub struct GradSpec { |
| pub edge: usize, |
| pub cubic: usize, |
| pub left: usize, |
| pub right: usize, |
| } |
|
|
| |
| pub struct PriorSpec { |
| pub edge: usize, |
| pub closed: bool, |
| |
| pub apt_weight: Option<Vec<f64>>, |
| } |
|
|
| |
| pub struct WindowHandle { |
| pub width: usize, |
| pub height: usize, |
| pub x0: f64, |
| pub y0: f64, |
| pub target: Vec<f64>, |
| pub weights: Option<Vec<f64>>, |
| pub l0: f64, |
| pub background: f64, |
| pub edges: Vec<EdgeGeom>, |
| pub face_labels: Vec<usize>, |
| pub loops: Vec<LoopSpec>, |
| pub colors: Vec<RegionColor>, |
| pub slots: Vec<Vec<SlotLoc>>, |
| pub grads: Vec<GradSpec>, |
| pub priors: Vec<PriorSpec>, |
| pub bern: Vec<[f64; 4]>, |
| pub lam: Lambdas, |
| pub include_spt: bool, |
| |
| scratch: Scratch, |
| } |
|
|
| #[derive(Default)] |
| struct Scratch { |
| |
| |
| edge_polylines: Vec<Vec<Pt>>, |
| } |
|
|
| |
| |
| |
| |
| #[inline] |
| fn allclose(a: Pt, b: Pt) -> bool { |
| const RTOL: f64 = 1e-5; |
| const ATOL: f64 = 1e-8; |
| (a[0] - b[0]).abs() <= ATOL + RTOL * b[0].abs() |
| && (a[1] - b[1]).abs() <= ATOL + RTOL * b[1].abs() |
| } |
|
|
| |
| |
| |
| |
| fn flatten_loop(edge_polylines: &[Vec<Pt>], darts: &[(usize, i32)]) -> Vec<Pt> { |
| let mut out: Vec<Pt> = Vec::new(); |
| for &(ei, dir) in darts { |
| let p = &edge_polylines[ei]; |
| |
| |
| if out.is_empty() { |
| if dir < 0 { |
| out.extend(p.iter().rev().copied()); |
| } else { |
| out.extend_from_slice(p); |
| } |
| } else if dir < 0 { |
| out.extend(p.iter().rev().skip(1).copied()); |
| } else if !p.is_empty() { |
| |
| |
| |
| |
| |
| out.extend_from_slice(&p[1..]); |
| } |
| } |
| if out.len() > 1 && allclose(out[0], out[out.len() - 1]) { |
| out.pop(); |
| } |
| out |
| } |
|
|
| impl WindowHandle { |
| |
| |
| pub fn prime(&mut self) { |
| self.scratch.edge_polylines = vec![Vec::new(); self.edges.len()]; |
| } |
|
|
| |
| pub fn active_order(&self) -> Vec<usize> { |
| (0..self.edges.len()).filter(|&i| self.edges[i].active).collect() |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub fn eval( |
| &mut self, |
| params: &[f64], |
| active_polys: &[Vec<Pt>], |
| grad_pts: &[Vec<Pt>], |
| ) -> (f64, Vec<f64>) { |
| |
| |
| |
| for (si, locs) in self.slots.iter().enumerate() { |
| let p = [params[2 * si], params[2 * si + 1]]; |
| for l in locs { |
| self.edges[l.edge].cubics[l.cubic][l.k] = p; |
| } |
| } |
|
|
| |
| let mut polys = std::mem::take(&mut self.scratch.edge_polylines); |
| let mut next_active = 0; |
| for (i, e) in self.edges.iter().enumerate() { |
| polys[i].clear(); |
| if e.active { |
| polys[i].extend_from_slice(&active_polys[next_active]); |
| next_active += 1; |
| } else { |
| polys[i].extend_from_slice(&e.polyline); |
| } |
| } |
|
|
| |
| |
| |
| let mut faces: Vec<Face> = self |
| .face_labels |
| .iter() |
| .map(|&label| Face { |
| label, |
| loops: Vec::new(), |
| }) |
| .collect(); |
| for lp in &self.loops { |
| let poly = flatten_loop(&polys, &lp.darts); |
| if poly.len() >= 3 { |
| faces[lp.face].loops.push(poly); |
| } |
| } |
|
|
| |
| let grad_cubics: Vec<GradCubic> = self |
| .grads |
| .iter() |
| .zip(grad_pts) |
| .map(|(g, pts)| GradCubic { |
| left: g.left, |
| right: g.right, |
| pts: pts.clone(), |
| }) |
| .collect(); |
|
|
| |
| let (e_data, vgrads) = windowed_data( |
| self.width, |
| self.height, |
| self.x0, |
| self.y0, |
| &self.target, |
| &faces, |
| &self.colors, |
| self.background, |
| self.l0, |
| &grad_cubics, |
| self.weights.as_deref(), |
| ); |
|
|
| |
| |
| |
| let mut acc: Vec<Vec<[Pt; 4]>> = self |
| .edges |
| .iter() |
| .map(|e| vec![[[0.0_f64; 2]; 4]; e.cubics.len()]) |
| .collect(); |
|
|
| |
| |
| |
| for (gi, g) in self.grads.iter().enumerate() { |
| let vg = &vgrads[gi]; |
| let slot = &mut acc[g.edge][g.cubic]; |
| for (k, sk) in slot.iter_mut().enumerate() { |
| let mut gx = 0.0; |
| let mut gy = 0.0; |
| for (i, row) in self.bern.iter().enumerate() { |
| gx += row[k] * vg[i][0]; |
| gy += row[k] * vg[i][1]; |
| } |
| sk[0] += gx; |
| sk[1] += gy; |
| } |
| } |
|
|
| |
| |
| |
| |
| let mut e_prior = 0.0; |
| for ps in &self.priors { |
| let (pe, pg) = edge_prior( |
| &self.edges[ps.edge].cubics, |
| ps.closed, |
| &self.lam, |
| &self.bern, |
| self.include_spt, |
| ps.apt_weight.as_deref(), |
| ); |
| e_prior += pe; |
| for (ci, gc) in pg.iter().enumerate() { |
| for k in 0..4 { |
| acc[ps.edge][ci][k][0] += gc[k][0]; |
| acc[ps.edge][ci][k][1] += gc[k][1]; |
| } |
| } |
| } |
|
|
| |
| let mut grad = vec![0.0_f64; self.slots.len() * 2]; |
| for (si, locs) in self.slots.iter().enumerate() { |
| let mut gx = 0.0; |
| let mut gy = 0.0; |
| for l in locs { |
| gx += acc[l.edge][l.cubic][l.k][0]; |
| gy += acc[l.edge][l.cubic][l.k][1]; |
| } |
| grad[2 * si] = gx; |
| grad[2 * si + 1] = gy; |
| } |
|
|
| self.scratch.edge_polylines = polys; |
| (e_data + e_prior, grad) |
| } |
|
|
| |
| |
| |
| |
| pub fn read_slots(&self) -> Vec<f64> { |
| let mut out = vec![0.0; self.slots.len() * 2]; |
| for (si, locs) in self.slots.iter().enumerate() { |
| let l = locs[0]; |
| let p = self.edges[l.edge].cubics[l.cubic][l.k]; |
| out[2 * si] = p[0]; |
| out[2 * si + 1] = p[1]; |
| } |
| out |
| } |
| } |
|
|
| #[cfg(feature = "python")] |
| pub use bindings::register; |
|
|
| #[cfg(feature = "python")] |
| mod bindings { |
| use super::*; |
| use numpy::{PyReadonlyArray1, PyReadonlyArray2, PyReadonlyArray3, ToPyArray}; |
| use pyo3::prelude::*; |
| use pyo3::types::PyList; |
|
|
| |
| |
| |
| |
| |
| #[pyclass(name = "WindowHandle", unsendable)] |
| pub struct PyWindowHandle { |
| inner: WindowHandle, |
| } |
|
|
| #[pymethods] |
| impl PyWindowHandle { |
| #[new] |
| #[pyo3(signature = (width, height, x0, y0, target, weights, l0, background, |
| edge_cubics, edge_active, frozen_polys, face_labels, loop_face, loop_darts, |
| colors01, color_kind, quad_coeffs, quad_transform, |
| slots, grad_spec, prior_spec, prior_apt_weights, |
| bern, lam_spt, lam_apt, lam_hpt, lam_lpt, include_spt))] |
| #[allow(clippy::too_many_arguments)] |
| fn new<'py>( |
| width: usize, |
| height: usize, |
| x0: f64, |
| y0: f64, |
| target: PyReadonlyArray3<'py, f64>, |
| weights: Option<PyReadonlyArray2<'py, f64>>, |
| l0: f64, |
| background: f64, |
| edge_cubics: &Bound<'py, PyList>, |
| edge_active: Vec<bool>, |
| |
| |
| frozen_polys: &Bound<'py, PyList>, |
| face_labels: Vec<usize>, |
| loop_face: Vec<usize>, |
| loop_darts: &Bound<'py, PyList>, |
| colors01: PyReadonlyArray2<'py, f64>, |
| color_kind: Vec<u8>, |
| quad_coeffs: PyReadonlyArray3<'py, f64>, |
| quad_transform: PyReadonlyArray2<'py, f64>, |
| slots: &Bound<'py, PyList>, |
| grad_spec: PyReadonlyArray2<'py, i64>, |
| prior_spec: PyReadonlyArray2<'py, i64>, |
| prior_apt_weights: &Bound<'py, PyList>, |
| bern: PyReadonlyArray2<'py, f64>, |
| lam_spt: f64, |
| lam_apt: f64, |
| lam_hpt: f64, |
| lam_lpt: f64, |
| include_spt: bool, |
| ) -> PyResult<Self> { |
| |
| let mut edges: Vec<EdgeGeom> = Vec::with_capacity(edge_cubics.len()); |
| for (i, item) in edge_cubics.iter().enumerate() { |
| let arr: PyReadonlyArray3<f64> = item.extract()?; |
| let v = arr.as_array(); |
| let s = v.shape()[0]; |
| if v.shape()[1] != 4 || v.shape()[2] != 2 { |
| return Err(pyo3::exceptions::PyValueError::new_err( |
| "each edge's cubics must be (s, 4, 2)", |
| )); |
| } |
| let cubics: Vec<Cubic> = (0..s) |
| .map(|ci| { |
| let mut c = [[0.0_f64; 2]; 4]; |
| for (k, p) in c.iter_mut().enumerate() { |
| *p = [v[[ci, k, 0]], v[[ci, k, 1]]]; |
| } |
| c |
| }) |
| .collect(); |
| let poly_arr: PyReadonlyArray2<f64> = frozen_polys.get_item(i)?.extract()?; |
| let pv = poly_arr.as_array(); |
| edges.push(EdgeGeom { |
| cubics, |
| active: *edge_active.get(i).unwrap_or(&true), |
| polyline: (0..pv.shape()[0]).map(|r| [pv[[r, 0]], pv[[r, 1]]]).collect(), |
| }); |
| } |
|
|
| |
| let mut loops: Vec<LoopSpec> = Vec::with_capacity(loop_darts.len()); |
| for (i, item) in loop_darts.iter().enumerate() { |
| let arr: PyReadonlyArray2<i64> = item.extract()?; |
| let v = arr.as_array(); |
| loops.push(LoopSpec { |
| face: loop_face[i], |
| darts: (0..v.shape()[0]) |
| .map(|r| (v[[r, 0]] as usize, v[[r, 1]] as i32)) |
| .collect(), |
| }); |
| } |
|
|
| |
| let mut slot_vec: Vec<Vec<SlotLoc>> = Vec::with_capacity(slots.len()); |
| for item in slots.iter() { |
| let arr: PyReadonlyArray2<i64> = item.extract()?; |
| let v = arr.as_array(); |
| slot_vec.push( |
| (0..v.shape()[0]) |
| .map(|r| SlotLoc { |
| edge: v[[r, 0]] as usize, |
| cubic: v[[r, 1]] as usize, |
| k: v[[r, 2]] as usize, |
| }) |
| .collect(), |
| ); |
| } |
|
|
| |
| let gs = grad_spec.as_array(); |
| let grads: Vec<GradSpec> = (0..gs.shape()[0]) |
| .map(|r| GradSpec { |
| edge: gs[[r, 0]] as usize, |
| cubic: gs[[r, 1]] as usize, |
| left: gs[[r, 2]] as usize, |
| right: gs[[r, 3]] as usize, |
| }) |
| .collect(); |
|
|
| let ps = prior_spec.as_array(); |
| let mut priors: Vec<PriorSpec> = Vec::with_capacity(ps.shape()[0]); |
| for r in 0..ps.shape()[0] { |
| let w: Option<Vec<f64>> = { |
| let item = prior_apt_weights.get_item(r)?; |
| if item.is_none() { |
| None |
| } else { |
| let a: PyReadonlyArray1<f64> = item.extract()?; |
| Some(a.as_array().iter().copied().collect()) |
| } |
| }; |
| priors.push(PriorSpec { |
| edge: ps[[r, 0]] as usize, |
| closed: ps[[r, 1]] != 0, |
| apt_weight: w, |
| }); |
| } |
|
|
| |
| let cv = colors01.as_array(); |
| let qc = quad_coeffs.as_array(); |
| let qt = quad_transform.as_array(); |
| let colors: Vec<RegionColor> = cv |
| .rows() |
| .into_iter() |
| .enumerate() |
| .map(|(i, r)| { |
| if color_kind.get(i).copied().unwrap_or(0) == 1 { |
| let mut coeffs = [[0.0_f64; 3]; 6]; |
| for (k, row) in coeffs.iter_mut().enumerate() { |
| for (ch, c) in row.iter_mut().enumerate() { |
| *c = qc[[i, k, ch]]; |
| } |
| } |
| RegionColor::Quad { |
| coeffs, |
| cx: qt[[i, 0]], |
| cy: qt[[i, 1]], |
| s: qt[[i, 2]], |
| } |
| } else { |
| RegionColor::Flat([r[0], r[1], r[2]]) |
| } |
| }) |
| .collect(); |
|
|
| let bv = bern.as_array(); |
| let basis: Vec<[f64; 4]> = (0..bv.shape()[0]) |
| .map(|i| [bv[[i, 0]], bv[[i, 1]], bv[[i, 2]], bv[[i, 3]]]) |
| .collect(); |
|
|
| |
| |
| |
| |
| let n_edges = edges.len(); |
| let n_labels = colors.len(); |
| for (si, locs) in slot_vec.iter().enumerate() { |
| for l in locs { |
| if l.edge >= n_edges || l.cubic >= edges[l.edge].cubics.len() || l.k >= 4 { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "slot {si} references (edge {}, cubic {}, k {}) which does not exist", |
| l.edge, l.cubic, l.k |
| ))); |
| } |
| } |
| } |
| for g in &grads { |
| if g.edge >= n_edges |
| || g.cubic >= edges[g.edge].cubics.len() |
| || g.left >= n_labels |
| || g.right >= n_labels |
| { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "grad_spec row (edge {}, cubic {}, left {}, right {}) is out of range", |
| g.edge, g.cubic, g.left, g.right |
| ))); |
| } |
| } |
| for ps in &priors { |
| if ps.edge >= n_edges { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "prior_spec references edge {} which does not exist", |
| ps.edge |
| ))); |
| } |
| } |
| for lp in &loops { |
| if lp.face >= face_labels.len() { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "loop references face {} which does not exist", |
| lp.face |
| ))); |
| } |
| for &(ei, _) in &lp.darts { |
| if ei >= n_edges { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "loop dart references edge {ei} which does not exist" |
| ))); |
| } |
| } |
| } |
| for &label in &face_labels { |
| if label >= n_labels { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "face label {label} has no colour" |
| ))); |
| } |
| } |
|
|
| let mut inner = WindowHandle { |
| width, |
| height, |
| x0, |
| y0, |
| target: target.as_array().iter().copied().collect(), |
| weights: weights.map(|w| w.as_array().iter().copied().collect()), |
| l0, |
| background, |
| edges, |
| face_labels, |
| loops, |
| colors, |
| slots: slot_vec, |
| grads, |
| priors, |
| bern: basis, |
| lam: Lambdas { |
| spt: lam_spt, |
| apt: lam_apt, |
| hpt: lam_hpt, |
| lpt: lam_lpt, |
| }, |
| include_spt, |
| scratch: Scratch::default(), |
| }; |
| inner.prime(); |
| Ok(PyWindowHandle { inner }) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| fn eval<'py>( |
| &mut self, |
| py: Python<'py>, |
| params: PyReadonlyArray1<'py, f64>, |
| active_polys: &Bound<'py, PyList>, |
| grad_pts: &Bound<'py, PyList>, |
| ) -> PyResult<(f64, Py<numpy::PyArray1<f64>>)> { |
| let x: Vec<f64> = params.as_array().iter().copied().collect(); |
| if x.len() != self.inner.slots.len() * 2 { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "expected {} params for {} slots, got {}", |
| self.inner.slots.len() * 2, |
| self.inner.slots.len(), |
| x.len() |
| ))); |
| } |
| let n_active = self.inner.edges.iter().filter(|e| e.active).count(); |
| if active_polys.len() != n_active || grad_pts.len() != self.inner.grads.len() { |
| return Err(pyo3::exceptions::PyValueError::new_err(format!( |
| "expected {} active polylines and {} gradient polylines, got {} and {}", |
| n_active, |
| self.inner.grads.len(), |
| active_polys.len(), |
| grad_pts.len() |
| ))); |
| } |
| let mut ap: Vec<Vec<Pt>> = Vec::with_capacity(active_polys.len()); |
| for item in active_polys.iter() { |
| let a: PyReadonlyArray2<f64> = item.extract()?; |
| let v = a.as_array(); |
| ap.push((0..v.shape()[0]).map(|r| [v[[r, 0]], v[[r, 1]]]).collect()); |
| } |
| let mut gp: Vec<Vec<Pt>> = Vec::with_capacity(grad_pts.len()); |
| for item in grad_pts.iter() { |
| let a: PyReadonlyArray2<f64> = item.extract()?; |
| let v = a.as_array(); |
| gp.push((0..v.shape()[0]).map(|r| [v[[r, 0]], v[[r, 1]]]).collect()); |
| } |
| let (e, g) = self.inner.eval(&x, &ap, &gp); |
| Ok(( |
| e, |
| numpy::ndarray::Array1::from_vec(g).to_pyarray(py).unbind(), |
| )) |
| } |
|
|
| |
| fn read_slots<'py>(&self, py: Python<'py>) -> Py<numpy::PyArray1<f64>> { |
| numpy::ndarray::Array1::from_vec(self.inner.read_slots()) |
| .to_pyarray(py) |
| .unbind() |
| } |
|
|
| |
| #[getter] |
| fn n_params(&self) -> usize { |
| self.inner.slots.len() * 2 |
| } |
|
|
| |
| #[getter] |
| fn n_grad(&self) -> usize { |
| self.inner.grads.len() |
| } |
|
|
| |
| #[getter] |
| fn active_order(&self) -> Vec<usize> { |
| self.inner.active_order() |
| } |
|
|
| |
| |
| |
| #[getter] |
| fn edge_counts(&self) -> (usize, usize) { |
| let active = self.inner.edges.iter().filter(|e| e.active).count(); |
| (self.inner.edges.len(), active) |
| } |
| } |
|
|
| pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { |
| m.add_class::<PyWindowHandle>()?; |
| Ok(()) |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| fn test_bern(m: usize) -> Vec<[f64; 4]> { |
| (0..m) |
| .map(|i| { |
| let t = i as f64 / (m - 1) as f64; |
| let mt = 1.0 - t; |
| [mt * mt * mt, 3.0 * mt * mt * t, 3.0 * mt * t * t, t * t * t] |
| }) |
| .collect() |
| } |
|
|
| |
| |
| |
| #[test] |
| fn loop_assembly_reverses_before_dropping_the_shared_joint() { |
| let a: Vec<Pt> = vec![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]]; |
| let b: Vec<Pt> = vec![[4.0, 0.0], [3.0, 0.0], [2.0, 0.0]]; |
| let poly = flatten_loop(&[a, b], &[(0, 1), (1, -1)]); |
| assert_eq!( |
| poly, |
| vec![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [3.0, 0.0], [4.0, 0.0]], |
| "the shared endpoint must appear once, and the reversed edge must run 2 -> 4" |
| ); |
| } |
|
|
| |
| |
| #[test] |
| fn closing_duplicate_is_dropped_only_when_the_ends_actually_meet() { |
| let closed: Vec<Pt> = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]; |
| assert_eq!(flatten_loop(&[closed], &[(0, 1)]).len(), 3); |
| let open: Vec<Pt> = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.5, 9.0]]; |
| assert_eq!(flatten_loop(&[open], &[(0, 1)]).len(), 4); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #[test] |
| fn eval_uses_the_supplied_vertices_and_never_recomputes_them() { |
| let bern = test_bern(4); |
| let c: Cubic = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [3.0, 0.0]]; |
| let square = |x: f64| -> Vec<Pt> { vec![[0.0, 0.0], [x, 0.0], [x, 4.0], [0.0, 4.0]] }; |
| let mut h = WindowHandle { |
| width: 4, |
| height: 4, |
| x0: 0.0, |
| y0: 0.0, |
| target: vec![1.0; 4 * 4 * 3], |
| weights: None, |
| l0: 1.0, |
| background: 1.0, |
| edges: vec![EdgeGeom { cubics: vec![c], active: true, polyline: Vec::new() }], |
| face_labels: vec![0], |
| loops: vec![LoopSpec { face: 0, darts: vec![(0, 1)] }], |
| colors: vec![RegionColor::Flat([0.0, 0.0, 0.0])], |
| slots: vec![vec![SlotLoc { edge: 0, cubic: 0, k: 1 }]], |
| grads: vec![], |
| priors: vec![], |
| bern, |
| lam: Lambdas { spt: 0.0, apt: 0.0, hpt: 0.0, lpt: 0.0 }, |
| include_spt: false, |
| scratch: Scratch::default(), |
| }; |
| h.prime(); |
| |
| |
| let (narrow, _) = h.eval(&[1.0, 0.0], &[square(1.0)], &[]); |
| let (wide, _) = h.eval(&[1.0, 0.0], &[square(4.0)], &[]); |
| assert!( |
| (narrow - wide).abs() > 1e-9, |
| "energy ignored the supplied vertices ({narrow} vs {wide}) — the crate is flattening \ |
| its own geometry again, which is exactly the defect this guards" |
| ); |
| } |
|
|
| |
| |
| #[test] |
| fn frozen_edges_keep_their_supplied_polyline_and_are_not_in_the_active_order() { |
| let bern = test_bern(4); |
| let c: Cubic = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [3.0, 0.0]]; |
| let frozen: Vec<Pt> = vec![[9.0, 9.0], [8.0, 8.0]]; |
| let h = WindowHandle { |
| width: 2, |
| height: 2, |
| x0: 0.0, |
| y0: 0.0, |
| target: vec![0.0; 2 * 2 * 3], |
| weights: None, |
| l0: 1.0, |
| background: 1.0, |
| edges: vec![ |
| EdgeGeom { cubics: vec![c], active: false, polyline: frozen.clone() }, |
| EdgeGeom { cubics: vec![c], active: true, polyline: Vec::new() }, |
| ], |
| face_labels: vec![], |
| loops: vec![], |
| colors: vec![RegionColor::Flat([0.0, 0.0, 0.0])], |
| slots: vec![], |
| grads: vec![], |
| priors: vec![], |
| bern, |
| lam: Lambdas::default(), |
| include_spt: false, |
| scratch: Scratch::default(), |
| }; |
| assert_eq!(h.active_order(), vec![1], "only the active edge needs a fresh polyline"); |
| assert_eq!(h.edges[0].polyline, frozen); |
| } |
|
|
| |
| |
| #[test] |
| fn one_slot_writes_every_location_it_names() { |
| let bern = test_bern(4); |
| let c: Cubic = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [3.0, 0.0]]; |
| let mut h = WindowHandle { |
| width: 2, |
| height: 2, |
| x0: 0.0, |
| y0: 0.0, |
| target: vec![0.0; 2 * 2 * 3], |
| weights: None, |
| l0: 1.0, |
| background: 1.0, |
| edges: vec![ |
| EdgeGeom { cubics: vec![c], active: true, polyline: Vec::new() }, |
| EdgeGeom { cubics: vec![c], active: true, polyline: Vec::new() }, |
| ], |
| face_labels: vec![], |
| loops: vec![], |
| colors: vec![RegionColor::Flat([0.0, 0.0, 0.0])], |
| slots: vec![vec![ |
| SlotLoc { edge: 0, cubic: 0, k: 0 }, |
| SlotLoc { edge: 1, cubic: 0, k: 3 }, |
| ]], |
| grads: vec![], |
| priors: vec![], |
| bern, |
| lam: Lambdas::default(), |
| include_spt: false, |
| scratch: Scratch::default(), |
| }; |
| h.prime(); |
| h.eval(&[9.0, -4.0], &[Vec::new(), Vec::new()], &[]); |
| assert_eq!(h.edges[0].cubics[0][0], [9.0, -4.0]); |
| assert_eq!(h.edges[1].cubics[0][3], [9.0, -4.0]); |
| assert_eq!(h.read_slots(), vec![9.0, -4.0]); |
| } |
| } |
|
|