diff --git a/ext/crates/sseq/src/bigraded.rs b/ext/crates/sseq/src/bigraded.rs index 96f0196bc..be33608c4 100644 --- a/ext/crates/sseq/src/bigraded.rs +++ b/ext/crates/sseq/src/bigraded.rs @@ -2,79 +2,73 @@ use std::cmp::Ordering::*; use once::OnceBiVec; +use crate::coordinates::Bidegree; + pub struct DenseBigradedModule { dimensions: OnceBiVec>, min_y: i32, } impl DenseBigradedModule { - pub fn new(min_x: i32, min_y: i32) -> Self { - let dimensions = OnceBiVec::new(min_x); - dimensions.push(OnceBiVec::new(min_y)); - Self { dimensions, min_y } - } - - pub const fn min_x(&self) -> i32 { - self.dimensions.min_degree() - } - - pub const fn min_y(&self) -> i32 { - self.min_y + pub fn new(min: Bidegree) -> Self { + let dimensions = OnceBiVec::new(min.x()); + dimensions.push(OnceBiVec::new(min.y())); + Self { + dimensions, + min_y: min.y(), + } } - pub fn max_x(&self) -> i32 { - self.dimensions.max_degree() + pub const fn min(&self) -> Bidegree { + Bidegree::x_y(self.dimensions.min_degree(), self.min_y) } - pub fn max_y(&self) -> i32 { - self.dimensions - .iter() - .map(OnceBiVec::max_degree) - .max() - .unwrap_or_else(|| self.min_y()) + pub fn max(&self) -> Bidegree { + Bidegree::x_y( + self.dimensions.max_degree(), + self.dimensions + .iter() + .map(OnceBiVec::max_degree) + .max() + .unwrap_or(self.min_y), + ) } pub fn range(&self, x: i32) -> std::ops::Range { self.dimensions[x].range() } - pub fn defined(&self, x: i32, y: i32) -> bool { - self.dimensions.get(x).is_some() && self.dimensions[x].get(y).is_some() + pub fn defined(&self, b: Bidegree) -> bool { + self.dimensions.get(b.x()).is_some() && self.dimensions[b.x()].get(b.y()).is_some() } - /// This can only be set when bidegrees to the left and bottom of (x, y) have been set. - pub fn set_dimension(&self, x: i32, y: i32, dim: usize) { + /// This can only be set when bidegrees to the left and bottom of `b` have been set. + pub fn set_dimension(&self, b: Bidegree, dim: usize) { assert!( - x <= self.dimensions.len(), - "Cannot set dimension at ({}, {}) before ({}, {}).", - x, - y, - x - 1, - y + b.x() <= self.dimensions.len(), + "Cannot set dimension at {b} before {b_minus_1x}.", + b_minus_1x = b - Bidegree::x_y(1, 0) ); - if x == self.dimensions.len() { + if b.x() == self.dimensions.len() { self.dimensions - .push_checked(OnceBiVec::new(self.min_y()), x); + .push_checked(OnceBiVec::new(self.min().y()), b.x()); } - match y.cmp(&self.dimensions[x].len()) { - Less => panic!("Already set dimension at ({x}, {y})"), - Equal => self.dimensions[x].push_checked(dim, y), + match b.y().cmp(&self.dimensions[b.x()].len()) { + Less => panic!("Already set dimension at {b}"), + Equal => self.dimensions[b.x()].push_checked(dim, b.y()), Greater => panic!( - "Cannot set dimension at ({}, {}) before ({}, {})", - x, - y, - x, - y - 1 + "Cannot set dimension at {b} before {b_minus_1y}", + b_minus_1y = b - Bidegree::x_y(0, 1) ), } } /// The dimension in a bidegree, None if not yet defined - pub fn get_dimension(&self, x: i32, y: i32) -> Option { - Some(*self.dimensions.get(x)?.get(y)?) + pub fn get_dimension(&self, b: Bidegree) -> Option { + Some(*self.dimensions.get(b.x())?.get(b.y())?) } - pub fn dimension(&self, x: i32, y: i32) -> usize { - self.get_dimension(x, y).unwrap() + pub fn dimension(&self, b: Bidegree) -> usize { + self.get_dimension(b).unwrap() } } diff --git a/ext/crates/sseq/src/coordinates/bidegree.rs b/ext/crates/sseq/src/coordinates/bidegree.rs index 8e8f774f5..ec8263f38 100644 --- a/ext/crates/sseq/src/coordinates/bidegree.rs +++ b/ext/crates/sseq/src/coordinates/bidegree.rs @@ -31,6 +31,10 @@ impl Bidegree { Self { n, s } } + pub const fn x_y(x: i32, y: i32) -> Self { + Self::n_s(x, y) + } + pub const fn zero() -> Self { Self { n: 0, s: 0 } } @@ -47,6 +51,14 @@ impl Bidegree { self.n } + pub fn x(&self) -> i32 { + self.n() + } + + pub fn y(&self) -> i32 { + self.s() + } + /// Returns difference as a bidegree if the difference in homological degrees is nonnegative, /// otherwise returns None. pub fn try_subtract(&self, smaller: Self) -> Option { diff --git a/ext/crates/sseq/src/coordinates/element.rs b/ext/crates/sseq/src/coordinates/element.rs index e103f51a3..b0386217f 100644 --- a/ext/crates/sseq/src/coordinates/element.rs +++ b/ext/crates/sseq/src/coordinates/element.rs @@ -36,6 +36,14 @@ impl BidegreeElement { self.degree.n() } + pub fn x(&self) -> i32 { + self.degree.x() + } + + pub fn y(&self) -> i32 { + self.degree.y() + } + pub fn vec(&self) -> FpSlice<'_> { self.vec.as_slice() } diff --git a/ext/crates/sseq/src/sseq.rs b/ext/crates/sseq/src/sseq.rs index 5c315bd18..f078493c9 100644 --- a/ext/crates/sseq/src/sseq.rs +++ b/ext/crates/sseq/src/sseq.rs @@ -7,14 +7,18 @@ use fp::{ vector::{FpSlice, FpVector}, }; -use crate::{bigraded::DenseBigradedModule, differential::Differential}; +use crate::{ + bigraded::DenseBigradedModule, + coordinates::{Bidegree, BidegreeElement}, + differential::Differential, +}; /// The direction of the differentials pub trait SseqProfile { const MIN_R: i32; - fn profile(r: i32, x: i32, y: i32) -> (i32, i32); - fn profile_inverse(r: i32, x: i32, y: i32) -> (i32, i32); - fn differential_length(diff_x: i32, diff_y: i32) -> i32; + fn profile(r: i32, b: Bidegree) -> Bidegree; + fn profile_inverse(r: i32, b: Bidegree) -> Bidegree; + fn differential_length(offset: Bidegree) -> i32; } pub struct Adams; @@ -22,22 +26,21 @@ pub struct Adams; impl SseqProfile for Adams { const MIN_R: i32 = 2; - fn profile(r: i32, x: i32, y: i32) -> (i32, i32) { - (x - 1, y + r) + fn profile(r: i32, b: Bidegree) -> Bidegree { + b + Bidegree::x_y(-1, r) } - fn profile_inverse(r: i32, x: i32, y: i32) -> (i32, i32) { - (x + 1, y - r) + fn profile_inverse(r: i32, b: Bidegree) -> Bidegree { + b + Bidegree::x_y(1, -r) } - fn differential_length(_diff_x: i32, diff_y: i32) -> i32 { - diff_y + fn differential_length(offset: Bidegree) -> i32 { + offset.y() } } pub struct Product { - pub x: i32, - pub y: i32, + pub b: Bidegree, /// Whether the product acts on the left or not. This affects the sign in the Leibniz rule. pub left: bool, pub matrices: BiVec>>, @@ -66,7 +69,8 @@ pub struct Sseq { /// `P::MIN_R` to make code a bit more streamlined. /// /// # Invariants: - /// - if `differential[x][y][r]` is defined, then `page_data[x][y][r + 1]` and `page_data[tx][ty][r + 1]` are always defined, + /// - if `differential[x][y][r]` is defined, then `page_data[x][y][r + 1]` and + /// `page_data[tx][ty][r + 1]` are always defined, page_data: BiVec>>, /// x -> y -> validity. A bidegree is invalid if the page_data is no longer accurate. @@ -78,24 +82,20 @@ pub struct Sseq { } impl Sseq

{ - pub fn new(p: ValidPrime, min_x: i32, min_y: i32) -> Self { + pub fn new(p: ValidPrime, min: Bidegree) -> Self { Self { p, - classes: Arc::new(DenseBigradedModule::new(min_x, min_y)), - differentials: BiVec::new(min_x), - permanent_classes: BiVec::new(min_x), - page_data: BiVec::new(min_x), - invalid: BiVec::new(min_x), + classes: Arc::new(DenseBigradedModule::new(min)), + differentials: BiVec::new(min.x()), + permanent_classes: BiVec::new(min.x()), + page_data: BiVec::new(min.x()), + invalid: BiVec::new(min.x()), profile: PhantomData, } } - pub fn min_x(&self) -> i32 { - self.classes.min_x() - } - - pub fn min_y(&self) -> i32 { - self.classes.min_y() + pub fn min(&self) -> Bidegree { + self.classes.min() } pub fn classes(&self) -> Arc { @@ -106,34 +106,30 @@ impl Sseq

{ self.classes.range(x) } - pub fn max_x(&self) -> i32 { - self.classes.max_x() + pub fn max(&self) -> Bidegree { + self.classes.max() } - pub fn max_y(&self) -> i32 { - self.classes.max_y() + pub fn defined(&self, b: Bidegree) -> bool { + self.classes.defined(b) } - pub fn defined(&self, x: i32, y: i32) -> bool { - self.classes.defined(x, y) - } - - pub fn set_dimension(&mut self, x: i32, y: i32, dim: usize) { - // This already ensures it is valid to set x, y - self.classes.set_dimension(x, y, dim); - if self.differentials.len() == x { - let min_y = self.classes.min_y(); + pub fn set_dimension(&mut self, b: Bidegree, dim: usize) { + // This already ensures it is valid to set b + self.classes.set_dimension(b, dim); + if self.differentials.len() == b.x() { + let min_y = self.classes.min().y(); self.differentials.push(BiVec::new(min_y)); self.permanent_classes.push(BiVec::new(min_y)); self.page_data.push(BiVec::new(min_y)); self.invalid.push(BiVec::new(min_y)); } - self.differentials[x].push(BiVec::new(P::MIN_R)); - self.page_data[x].push(BiVec::new(P::MIN_R)); - self.page_data[x][y].push(Subquotient::new_full(self.p, dim)); - self.permanent_classes[x].push(Subspace::new(self.p, dim)); - self.invalid[x].push(false); + self.differentials[b.x()].push(BiVec::new(P::MIN_R)); + self.page_data[b.x()].push(BiVec::new(P::MIN_R)); + self.page_data[b.x()][b.y()].push(Subquotient::new_full(self.p, dim)); + self.permanent_classes[b.x()].push(Subspace::new(self.p, dim)); + self.invalid[b.x()].push(false); } pub fn clear(&mut self) { @@ -154,92 +150,80 @@ impl Sseq

{ self.invalid.iter_mut().flatten().for_each(|x| *x = true); } - pub fn dimension(&self, x: i32, y: i32) -> usize { - self.classes.dimension(x, y) + pub fn dimension(&self, b: Bidegree) -> usize { + self.classes.dimension(b) } /// # Returns /// /// Whether a new permanent class was added - pub fn add_permanent_class(&mut self, x: i32, y: i32, class: FpSlice) -> bool { - let old_dim = self.permanent_classes[x][y].dimension(); - let new_dim = self.permanent_classes[x][y].add_vector(class); + pub fn add_permanent_class(&mut self, elem: &BidegreeElement) -> bool { + let old_dim = self.permanent_classes[elem.x()][elem.y()].dimension(); + let new_dim = self.permanent_classes[elem.x()][elem.y()].add_vector(elem.vec()); if old_dim != new_dim { // This was a new permanent class - for d in self.differentials[x][y].iter_mut() { - d.add(class, None); + for d in self.differentials[elem.x()][elem.y()].iter_mut() { + d.add(elem.vec(), None); } - self.invalid[x][y] = true; + self.invalid[elem.x()][elem.y()] = true; } old_dim != new_dim } - /// Ensure `self.differentials[x][y][r]` is defined. Must call `extend_page_data` on the source + /// Ensure `self.differentials[b.x()][b.y()][r]` is defined. Must call `extend_page_data` on the source /// and target after this. - fn extend_differential(&mut self, r: i32, x: i32, y: i32) { - let source_dim = self.classes.dimension(x, y); - while self.differentials[x][y].len() <= r { - let r = self.differentials[x][y].len(); - let (target_x, target_y) = P::profile(r, x, y); - let mut differential = Differential::new( - self.p, - source_dim, - self.classes.dimension(target_x, target_y), - ); - - for class in self.permanent_classes[x][y].basis() { + fn extend_differential(&mut self, r: i32, b: Bidegree) { + let source_dim = self.classes.dimension(b); + while self.differentials[b.x()][b.y()].len() <= r { + let r = self.differentials[b.x()][b.y()].len(); + let target = P::profile(r, b); + let mut differential = + Differential::new(self.p, source_dim, self.classes.dimension(target)); + + for class in self.permanent_classes[b.x()][b.y()].basis() { differential.add(class.as_slice(), None); } - self.differentials[x][y].push(differential); + self.differentials[b.x()][b.y()].push(differential); } } - /// Ensure `self.page_data[x][y][r]` is defined - fn extend_page_data(&mut self, r: i32, x: i32, y: i32) { - let page_data = &mut self.page_data[x][y]; + /// Ensure `self.page_data[b.x()][b.y()][r]` is defined + fn extend_page_data(&mut self, r: i32, b: Bidegree) { + let page_data = &mut self.page_data[b.x()][b.y()]; while page_data.len() <= r { page_data.push(page_data.last().unwrap().clone()) } } - // TODO: Maybe convert to using `BidegreeElement`s instead? /// Add a $d_r$ differential from bidegree $(x, y)$, with the given `source` and `target` /// classes. /// /// # Return /// /// Whether the differential is new - pub fn add_differential( - &mut self, - r: i32, - x: i32, - y: i32, - source: FpSlice, - target: FpSlice, - ) -> bool { - let (tx, ty) = P::profile(r, x, y); + pub fn add_differential(&mut self, r: i32, source: &BidegreeElement, target: FpSlice) -> bool { + let target_b = P::profile(r, source.degree()); - self.extend_differential(r, x, y); - self.extend_page_data(r + 1, x, y); - self.extend_page_data(r + 1, tx, ty); + self.extend_differential(r, source.degree()); + self.extend_page_data(r + 1, source.degree()); + self.extend_page_data(r + 1, target_b); for r in P::MIN_R..r { - self.differentials[x][y][r].add(source, None); - let (tx, ty) = P::profile(r, x, y); - self.extend_page_data(r + 1, tx, ty); + self.differentials[source.x()][source.y()][r].add(source.vec(), None); + self.extend_page_data(r + 1, P::profile(r, source.degree())); } - let is_new = self.differentials[x][y][r].add(source, Some(target)); + let is_new = self.differentials[source.x()][source.y()][r].add(source.vec(), Some(target)); if is_new { - self.invalid[x][y] = true; + self.invalid[source.x()][source.y()] = true; if !target.is_zero() { - self.invalid[tx][ty] = true; - self.add_permanent_class(tx, ty, target); - for r in r + 1..self.page_data[tx][ty].len() { - self.page_data[tx][ty][r].quotient(target); - - let (px, py) = P::profile_inverse(r, tx, ty); - if self.defined(px, py) { - self.invalid[px][py] = true; + self.invalid[target_b.x()][target_b.y()] = true; + self.add_permanent_class(&BidegreeElement::new(target_b, target.to_owned())); + for r in r + 1..self.page_data[target_b.x()][target_b.y()].len() { + self.page_data[target_b.x()][target_b.y()][r].quotient(target); + + let p = P::profile_inverse(r, target_b); + if self.defined(p) { + self.invalid[p.x()][p.y()] = true; } } } @@ -247,71 +231,79 @@ impl Sseq

{ is_new } - pub fn invalid(&self, x: i32, y: i32) -> bool { - self.invalid[x][y] + pub fn invalid(&self, b: Bidegree) -> bool { + self.invalid[b.x()][b.y()] } pub fn update(&mut self) { for x in self.invalid.range() { for y in self.invalid[x].range() { if self.invalid[x][y] { - self.update_bidegree(x, y); + self.update_bidegree(Bidegree::x_y(x, y)); } } } } /// This returns the vec of differentials to draw on each page. - pub fn update_bidegree(&mut self, x: i32, y: i32) -> BiVec>> { - self.invalid[x][y] = false; - for (r, d) in self.differentials[x][y].iter_mut_enum() { - let (tx, ty) = P::profile(r, x, y); - d.reduce_target(self.page_data[tx][ty][r].zeros()); + pub fn update_bidegree(&mut self, b: Bidegree) -> BiVec>> { + self.invalid[b.x()][b.y()] = false; + for (r, d) in self.differentials[b.x()][b.y()].iter_mut_enum() { + let target_b = P::profile(r, b); + d.reduce_target(self.page_data[target_b.x()][target_b.y()][r].zeros()); } // For each page, the array of differentials to draw let mut differentials: BiVec>> = - BiVec::with_capacity(P::MIN_R, self.differentials[x][y].len()); + BiVec::with_capacity(P::MIN_R, self.differentials[b.x()][b.y()].len()); - for r in self.page_data[x][y].range().skip(1) { - let (tx, ty) = P::profile(r - 1, x, y); + for r in self.page_data[b.x()][b.y()].range().skip(1) { + let target_b = P::profile(r - 1, b); - self.page_data[x][y][r].clear_gens(); + self.page_data[b.x()][b.y()][r].clear_gens(); - if r > self.differentials[x][y].len() || self.page_data[tx][ty][r - 1].is_empty() { - let (prev, cur) = self.page_data[x][y].split_borrow_mut(r - 1, r); + if r > self.differentials[b.x()][b.y()].len() + || self.page_data[target_b.x()][target_b.y()][r - 1].is_empty() + { + let (prev, cur) = self.page_data[b.x()][b.y()].split_borrow_mut(r - 1, r); for g in prev.gens() { cur.add_gen(g); } - if r - 1 < self.differentials[x][y].len() { - differentials.push(vec![Vec::new(); self.page_data[x][y][r].dimension()]); + if r - 1 < self.differentials[b.x()][b.y()].len() { + differentials.push(vec![ + Vec::new(); + self.page_data[b.x()][b.y()][r].dimension() + ]); } } else { - let d = &self.differentials[x][y][r - 1]; + let d = &self.differentials[b.x()][b.y()][r - 1]; - let source_dim = self.dimension(x, y); - let target_dim = self.dimension(tx, ty); + let source_dim = self.dimension(b); + let target_dim = self.dimension(target_b); let mut drawn_differentials: Vec> = - Vec::with_capacity(self.page_data[x][y][r - 1].dimension()); + Vec::with_capacity(self.page_data[b.x()][b.y()][r - 1].dimension()); let mut dvec = FpVector::new(self.p, target_dim); let mut matrix = Matrix::new( self.p, - self.page_data[x][y][r - 1].dimension(), + self.page_data[b.x()][b.y()][r - 1].dimension(), source_dim + target_dim, ); - for (row, g) in - std::iter::zip(matrix.iter_mut(), self.page_data[x][y][r - 1].gens()) - { + for (row, g) in std::iter::zip( + matrix.iter_mut(), + self.page_data[b.x()][b.y()][r - 1].gens(), + ) { row.slice_mut(target_dim, target_dim + source_dim).assign(g); d.evaluate(g, dvec.as_slice_mut()); row.slice_mut(0, target_dim).assign(dvec.as_slice()); - drawn_differentials - .push(self.page_data[tx][ty][r - 1].reduce(dvec.as_slice_mut())); + drawn_differentials.push( + self.page_data[target_b.x()][target_b.y()][r - 1] + .reduce(dvec.as_slice_mut()), + ); dvec.set_to_zero(); } differentials.push(drawn_differentials); @@ -324,7 +316,8 @@ impl Sseq

{ if row.is_zero() { break; } - self.page_data[x][y][r].add_gen(row.slice(target_dim, target_dim + source_dim)); + self.page_data[b.x()][b.y()][r] + .add_gen(row.slice(target_dim, target_dim + source_dim)); } } } @@ -333,62 +326,57 @@ impl Sseq

{ /// Whether the calcuations at bidegree (x, y) are complete. This means all classes on the /// final page are known to be permanent. - pub fn complete(&self, x: i32, y: i32) -> bool { - self.page_data[x][y] + pub fn complete(&self, b: Bidegree) -> bool { + self.page_data[b.x()][b.y()] .last() .unwrap() .gens() - .all(|v| self.permanent_classes[x][y].contains(v)) + .all(|v| self.permanent_classes[b.x()][b.y()].contains(v)) } /// Whether there is an inconsistent differential involving bidegree (x, y). - pub fn inconsistent(&self, x: i32, y: i32) -> bool { - self.differentials(x, y) - .iter() - .any(Differential::inconsistent) - || self - .differentials_hitting(x, y) - .any(|(_, d)| d.inconsistent()) + pub fn inconsistent(&self, b: Bidegree) -> bool { + self.differentials(b).iter().any(Differential::inconsistent) + || self.differentials_hitting(b).any(|(_, d)| d.inconsistent()) } - pub fn differentials(&self, x: i32, y: i32) -> &BiVec { - &self.differentials[x][y] + pub fn differentials(&self, b: Bidegree) -> &BiVec { + &self.differentials[b.x()][b.y()] } pub fn differentials_hitting( &self, - x: i32, - y: i32, + b: Bidegree, ) -> impl Iterator + '_ { - let max_r = self.page_data[x][y].len() - 1; + let max_r = self.page_data[b.x()][b.y()].len() - 1; (P::MIN_R..max_r).filter_map(move |r| { - let (sx, sy) = P::profile_inverse(r, x, y); - Some((r, self.differentials.get(sx)?.get(sy)?.get(r)?)) + let source_b = P::profile_inverse(r, b); + Some(( + r, + self.differentials + .get(source_b.x())? + .get(source_b.y())? + .get(r)?, + )) }) } - pub fn permanent_classes(&self, x: i32, y: i32) -> &Subspace { - &self.permanent_classes[x][y] + pub fn permanent_classes(&self, b: Bidegree) -> &Subspace { + &self.permanent_classes[b.x()][b.y()] } - pub fn page_data(&self, x: i32, y: i32) -> &BiVec { - &self.page_data[x][y] + pub fn page_data(&self, b: Bidegree) -> &BiVec { + &self.page_data[b.x()][b.y()] } /// Compute the product between `product` and the class `class` at `(x, y)`. Returns `None` if /// the product is not yet computed. - pub fn multiply( - &self, - x: i32, - y: i32, - class: FpSlice, - prod: &Product, - ) -> Option<(i32, i32, FpVector)> { - let mut result = FpVector::new(self.p, self.classes.get_dimension(x + prod.x, y + prod.y)?); - if let Some(matrix) = &prod.matrices.get(x)?.get(y)? { - matrix.apply(result.as_slice_mut(), 1, class); + pub fn multiply(&self, elem: &BidegreeElement, prod: &Product) -> Option { + let mut result = FpVector::new(self.p, self.classes.get_dimension(elem.degree() + prod.b)?); + if let Some(matrix) = &prod.matrices.get(elem.x())?.get(elem.y())? { + matrix.apply(result.as_slice_mut(), 1, elem.vec()); } - Some((x + prod.x, y + prod.y, result)) + Some(BidegreeElement::new(elem.degree() + prod.b, result)) } /// Apply the Leibniz rule to obtain new differentials. The differential we start with is a d_r @@ -409,18 +397,16 @@ impl Sseq

{ pub fn leibniz( &mut self, r: i32, - x: i32, - y: i32, - class: FpSlice, + elem: &BidegreeElement, source_product: &Product, target_product: Option<&Product>, - ) -> Option<(i32, i32, i32, FpVector)> { - let (source_x, source_y, source_class) = self.multiply(x, y, class, source_product)?; + ) -> Option<(i32, BidegreeElement)> { + let source = self.multiply(elem, source_product)?; // The class and the product are both permanent. if r == i32::MAX && target_product.is_none() { - if self.add_permanent_class(source_x, source_y, source_class.as_slice()) { - return Some((i32::MAX, source_x, source_y, source_class)); + if self.add_permanent_class(&source) { + return Some((i32::MAX, source)); } else { return None; } @@ -429,47 +415,42 @@ impl Sseq

{ let neg_1 = self.p - 1; let target_r = target_product - .map(|prod| P::differential_length(x + prod.x - source_x, y + prod.y - source_y)) + .map(|prod| P::differential_length(elem.degree() + prod.b - source.degree())) .unwrap_or(i32::MAX); let result_r = std::cmp::min(r, target_r); - let (result_x, result_y) = P::profile(result_r, source_x, source_y); - let mut result = FpVector::new(self.p, self.classes.get_dimension(result_x, result_y)?); + let result_b = P::profile(result_r, source.degree()); + let mut result = FpVector::new(self.p, self.classes.get_dimension(result_b)?); if r == result_r { - let diffs = &self.differentials[x][y][r]; - let (d_x, d_y) = P::profile(r, x, y); - let mut dx = FpVector::new(self.p, self.classes.dimension(d_x, d_y)); - diffs.evaluate(class, dx.as_slice_mut()); - let (_, _, target_class) = self.multiply(d_x, d_y, dx.as_slice(), source_product)?; - - if source_product.left && source_product.x % 2 != 0 { - result.add(&target_class, neg_1); + let diffs = &self.differentials[elem.x()][elem.y()][r]; + let d_b = P::profile(r, elem.degree()); + let mut dx = FpVector::new(self.p, self.classes.dimension(d_b)); + diffs.evaluate(elem.vec(), dx.as_slice_mut()); + let d = BidegreeElement::new(d_b, dx); + let target = self.multiply(&d, source_product)?; + + if source_product.left && source_product.b.x() % 2 != 0 { + result.add(&target.into_vec(), neg_1); } else { - result.add(&target_class, 1); + result.add(&target.into_vec(), 1); } } if target_r == result_r { - let (_, _, target) = self.multiply(x, y, class, target_product.unwrap())?; + let target = self.multiply(elem, target_product.unwrap())?; // why is this x - 1 but not x? This is what the original code does and came from trial // and error(?) - if !source_product.left && (x - 1) % 2 != 0 { - result.add(&target, neg_1); + if !source_product.left && (elem.x() - 1) % 2 != 0 { + result.add(&target.into_vec(), neg_1); } else { - result.add(&target, 1); + result.add(&target.into_vec(), 1); } } - if self.add_differential( - result_r, - source_x, - source_y, - source_class.as_slice(), - result.as_slice(), - ) { - Some((result_r, source_x, source_y, source_class)) + if self.add_differential(result_r, &source, result.as_slice()) { + Some((result_r, source)) } else { None } @@ -484,44 +465,46 @@ impl Sseq

{ products: impl Iterator + Clone, header: impl FnOnce(&mut T) -> Result<(), T::Error>, ) -> Result<(), T::Error> { - let min_x = self.min_x(); - assert_eq!(self.min_y(), 0); + let min = self.min(); + assert_eq!(min.y(), 0); - let max_x = self.max_x(); - let max_y = self.max_y(); + let max = self.max(); - g.init(max_x - min_x, max_y)?; + g.init((max - min).x(), max.y())?; header(&mut g)?; - for x in min_x..=max_x { + for x in min.x()..=max.x() { for y in self.range(x) { - let data = self.page_data(x, y).get_max(r); + let b = Bidegree::x_y(x, y); + let shifted_b = b - min; + + let data = self.page_data(b).get_max(r); if data.is_empty() { continue; } - g.node(x - min_x, y, data.dimension())?; + g.node(shifted_b.x(), shifted_b.y(), data.dimension())?; // Now add the products hitting this bidegree for (name, prod) in products.clone() { - let source_x = x - prod.x; - let source_y = y - prod.y; + let source_b = b - prod.b; + let shifted_source = source_b - min; - if !self.defined(source_x, source_y) { + if !self.defined(source_b) { continue; } - let source_data = self.page_data(source_x, source_y).get_max(r); + let source_data = self.page_data(source_b).get_max(r); if source_data.is_empty() { continue; } // For unstable charts this is None in low degrees. - if let Some(matrix) = &prod.matrices[source_x][source_y] { + if let Some(matrix) = &prod.matrices[source_b.x()][source_b.y()] { let matrix = Subquotient::reduce_matrix(matrix, source_data, data); g.structline_matrix( - (source_x - min_x, source_y), - (x - min_x, y), + (shifted_source.x(), shifted_source.y()), + (shifted_b.x(), shifted_b.y()), matrix, Some(name), )?; @@ -530,16 +513,18 @@ impl Sseq

{ // Finally add the differentials if differentials { - let (tx, ty) = P::profile(r, x, y); - if tx < 0 { + let target_b = P::profile(r, b); + let shifted_target = target_b - min; + + if target_b.x() < 0 { continue; } - let d = self.differentials(x, y); + let d = self.differentials(b); if d.len() <= r { continue; } let d = &d[r]; - let target_data = self.page_data(tx, ty).get_max(r); + let target_data = self.page_data(target_b).get_max(r); let pairs = d .get_source_target_pairs() @@ -561,8 +546,8 @@ impl Sseq

{ continue; } g.structline( - (x - min_x, y, i), - (tx - min_x, ty, j), + (shifted_b.x(), shifted_b.y(), i), + (shifted_target.x(), shifted_target.y(), j), Some(&format!("d{r}")), )?; } @@ -585,39 +570,34 @@ mod tests { #[test] fn test_sseq_differential() { let p = ValidPrime::new(3); - let mut sseq = Sseq::::new(p, 0, 0); - sseq.set_dimension(0, 0, 1); - sseq.set_dimension(1, 0, 2); - sseq.set_dimension(1, 1, 2); - sseq.set_dimension(0, 1, 0); - sseq.set_dimension(0, 2, 3); - sseq.set_dimension(0, 3, 1); + let mut sseq = Sseq::::new(p, Bidegree::zero()); + sseq.set_dimension(Bidegree::x_y(0, 0), 1); + sseq.set_dimension(Bidegree::x_y(1, 0), 2); + sseq.set_dimension(Bidegree::x_y(1, 1), 2); + sseq.set_dimension(Bidegree::x_y(0, 1), 0); + sseq.set_dimension(Bidegree::x_y(0, 2), 3); + sseq.set_dimension(Bidegree::x_y(0, 3), 1); sseq.add_differential( 2, - 1, - 0, - FpVector::from_slice(p, &[1, 1]).as_slice(), + &BidegreeElement::new(Bidegree::x_y(1, 0), FpVector::from_slice(p, &[1, 1])), FpVector::from_slice(p, &[0, 1, 2]).as_slice(), ); sseq.add_differential( 3, - 1, - 0, - FpVector::from_slice(p, &[1, 0]).as_slice(), + &BidegreeElement::new(Bidegree::x_y(1, 0), FpVector::from_slice(p, &[1, 0])), FpVector::from_slice(p, &[1]).as_slice(), ); sseq.update(); - let check = |x, y, r, e: Expect| { - e.assert_eq(&sseq.page_data[x][y][r].to_string()); + let check = |b, r, e: Expect| { + e.assert_eq(&sseq.page_data(b)[r].to_string()); }; check( - 1, - 0, + Bidegree::x_y(1, 0), 2, expect![[r#" Generators: @@ -629,8 +609,7 @@ mod tests { "#]], ); check( - 1, - 0, + Bidegree::x_y(1, 0), 3, expect![[r#" Generators: @@ -641,8 +620,7 @@ mod tests { "#]], ); check( - 1, - 0, + Bidegree::x_y(1, 0), 4, expect![[r#" Generators: @@ -653,8 +631,7 @@ mod tests { ); check( - 1, - 1, + Bidegree::x_y(1, 1), 2, expect![[r#" Generators: @@ -667,8 +644,7 @@ mod tests { ); check( - 0, - 2, + Bidegree::x_y(0, 2), 2, expect![[r#" Generators: @@ -681,8 +657,7 @@ mod tests { "#]], ); check( - 0, - 2, + Bidegree::x_y(0, 2), 3, expect![[r#" Generators: @@ -696,8 +671,7 @@ mod tests { ); check( - 0, - 3, + Bidegree::x_y(0, 3), 2, expect![[r#" Generators: @@ -708,8 +682,7 @@ mod tests { "#]], ); check( - 0, - 3, + Bidegree::x_y(0, 3), 3, expect![[r#" Generators: @@ -720,8 +693,7 @@ mod tests { "#]], ); check( - 0, - 3, + Bidegree::x_y(0, 3), 4, expect![[r#" Generators: @@ -734,19 +706,18 @@ mod tests { sseq.add_differential( 2, - 1, - 1, - FpVector::from_slice(p, &[1, 0]).as_slice(), + &BidegreeElement::new(Bidegree::x_y(1, 1), FpVector::from_slice(p, &[1, 0])), FpVector::from_slice(p, &[1]).as_slice(), ); sseq.update(); - let check = |x, y, r, e: Expect| { - e.assert_eq(&sseq.page_data[x][y][r].to_string()); + + // Redefine `check` for borrow-checker reasons + let check = |b, r, e: Expect| { + e.assert_eq(&sseq.page_data(b)[r].to_string()); }; check( - 1, - 0, + Bidegree::x_y(1, 0), 2, expect![[r#" Generators: @@ -758,8 +729,7 @@ mod tests { "#]], ); check( - 1, - 0, + Bidegree::x_y(1, 0), 3, expect![[r#" Generators: @@ -770,8 +740,7 @@ mod tests { "#]], ); check( - 1, - 0, + Bidegree::x_y(1, 0), 4, expect![[r#" Generators: @@ -783,8 +752,7 @@ mod tests { ); check( - 1, - 1, + Bidegree::x_y(1, 1), 2, expect![[r#" Generators: @@ -796,8 +764,7 @@ mod tests { "#]], ); check( - 1, - 1, + Bidegree::x_y(1, 1), 3, expect![[r#" Generators: @@ -809,8 +776,7 @@ mod tests { ); check( - 0, - 2, + Bidegree::x_y(0, 2), 2, expect![[r#" Generators: @@ -823,8 +789,7 @@ mod tests { "#]], ); check( - 0, - 2, + Bidegree::x_y(0, 2), 3, expect![[r#" Generators: @@ -838,8 +803,7 @@ mod tests { ); check( - 0, - 3, + Bidegree::x_y(0, 3), 2, expect![[r#" Generators: @@ -850,8 +814,7 @@ mod tests { "#]], ); check( - 0, - 3, + Bidegree::x_y(0, 3), 3, expect![[r#" Generators: @@ -862,8 +825,7 @@ mod tests { "#]], ); check( - 0, - 3, + Bidegree::x_y(0, 3), 4, expect![[r#" Generators: @@ -878,36 +840,31 @@ mod tests { #[test] fn test_sseq_differential_2() { let p = ValidPrime::new(2); - let mut sseq = Sseq::::new(p, 0, 0); + let mut sseq = Sseq::::new(p, Bidegree::zero()); - sseq.set_dimension(0, 0, 0); - sseq.set_dimension(1, 0, 2); - sseq.set_dimension(0, 1, 0); - sseq.set_dimension(0, 2, 2); + sseq.set_dimension(Bidegree::x_y(0, 0), 0); + sseq.set_dimension(Bidegree::x_y(1, 0), 2); + sseq.set_dimension(Bidegree::x_y(0, 1), 0); + sseq.set_dimension(Bidegree::x_y(0, 2), 2); sseq.add_differential( 2, - 1, - 0, - FpVector::from_slice(p, &[1, 0]).as_slice(), + &BidegreeElement::new(Bidegree::x_y(1, 0), FpVector::from_slice(p, &[1, 0])), FpVector::from_slice(p, &[1, 0]).as_slice(), ); sseq.add_differential( 2, - 1, - 0, - FpVector::from_slice(p, &[0, 1]).as_slice(), + &BidegreeElement::new(Bidegree::x_y(1, 0), FpVector::from_slice(p, &[0, 1])), FpVector::from_slice(p, &[1, 1]).as_slice(), ); sseq.update(); - let check = |x, y, r, e: Expect| { - e.assert_eq(&sseq.page_data[x][y][r].to_string()); + let check = |b: Bidegree, r, e: Expect| { + e.assert_eq(&sseq.page_data[b.x()][b.y()][r].to_string()); }; check( - 1, - 0, + Bidegree::x_y(1, 0), 2, expect![[r#" Generators: @@ -919,8 +876,7 @@ mod tests { "#]], ); check( - 1, - 0, + Bidegree::x_y(1, 0), 3, expect![[r#" Generators: @@ -930,8 +886,7 @@ mod tests { "#]], ); check( - 0, - 2, + Bidegree::x_y(0, 2), 2, expect![[r#" Generators: @@ -943,8 +898,7 @@ mod tests { "#]], ); check( - 0, - 2, + Bidegree::x_y(0, 2), 3, expect![[r#" Generators: diff --git a/ext/examples/secondary_massey.rs b/ext/examples/secondary_massey.rs index 39dfe7a37..c170a404d 100644 --- a/ext/examples/secondary_massey.rs +++ b/ext/examples/secondary_massey.rs @@ -241,7 +241,7 @@ fn main() -> anyhow::Result<()> { ch_lift.extend_all(); fn get_page_data(sseq: &sseq::Sseq, b: Bidegree) -> &fp::matrix::Subquotient { - let d = sseq.page_data(b.n(), b.s()); + let d = sseq.page_data(b); &d[std::cmp::min(3, d.len() - 1)] } diff --git a/ext/examples/secondary_product.rs b/ext/examples/secondary_product.rs index 7eaeb4ce6..d8d44a2cc 100644 --- a/ext/examples/secondary_product.rs +++ b/ext/examples/secondary_product.rs @@ -121,7 +121,7 @@ fn main() -> anyhow::Result<()> { }; fn get_page_data(sseq: &sseq::Sseq, b: Bidegree) -> &fp::matrix::Subquotient { - let d = sseq.page_data(b.n(), b.s()); + let d = sseq.page_data(b); &d[std::cmp::min(3, d.len() - 1)] } diff --git a/ext/src/chain_complex/mod.rs b/ext/src/chain_complex/mod.rs index c357f1c2c..de9d6ba24 100644 --- a/ext/src/chain_complex/mod.rs +++ b/ext/src/chain_complex/mod.rs @@ -61,9 +61,9 @@ where fn to_sseq(&self) -> sseq::Sseq { let p = self.prime(); - let mut sseq = sseq::Sseq::new(p, self.min_degree(), 0); + let mut sseq = sseq::Sseq::new(p, Bidegree::n_s(self.min_degree(), 0)); for b in self.iter_stem() { - sseq.set_dimension(b.n(), b.s(), self.number_of_gens_in_bidegree(b)); + sseq.set_dimension(b, self.number_of_gens_in_bidegree(b)); } sseq } @@ -86,9 +86,8 @@ where }); sseq::Product { + b: Bidegree::x_y(op_deg - 1, 1), left: true, - x: op_deg - 1, - y: 1, matrices, } } diff --git a/ext/src/secondary.rs b/ext/src/secondary.rs index d66151809..ded68f49e 100644 --- a/ext/src/secondary.rs +++ b/ext/src/secondary.rs @@ -19,7 +19,7 @@ use fp::{ use itertools::Itertools; use maybe_rayon::prelude::*; use once::OnceBiVec; -use sseq::coordinates::{Bidegree, BidegreeGenerator, BidegreeRange}; +use sseq::coordinates::{Bidegree, BidegreeElement, BidegreeGenerator, BidegreeRange}; use tracing::Level; use crate::{ @@ -782,20 +782,17 @@ where source_vec.set_entry(i, 1); target_vec.copy_from_slice(&row); - sseq.add_differential( - 2, - b.n(), - b.s(), - source_vec.as_slice(), - target_vec.as_slice(), - ); + let source = BidegreeElement::new(b, source_vec); + sseq.add_differential(2, &source, target_vec.as_slice()); + + source_vec = source.into_vec(); } } } for b in self.underlying.iter_stem() { - if sseq.invalid(b.n(), b.s()) { - sseq.update_bidegree(b.n(), b.s()); + if sseq.invalid(b) { + sseq.update_bidegree(b); } } sseq @@ -1025,7 +1022,7 @@ where let filtration_one_sign = if (b.t() % 2) == 1 { p - 1 } else { 1 }; let page_data = sseq.map(|sseq| { - let d = sseq.page_data(lambda_source.n(), lambda_source.s()); + let d = sseq.page_data(lambda_source); &d[std::cmp::min(3, d.len() - 1)] }); @@ -1136,7 +1133,7 @@ where ); let diff_source = b + shift - Bidegree::n_s(-1, 1); - sseq.differentials(diff_source.n(), diff_source.s())[2].quasi_inverse( + sseq.differentials(diff_source)[2].quasi_inverse( output_class.as_slice_mut(), prod_value.slice(lower_num_gens, lower_num_gens + lambda_num_gens), ); diff --git a/web_ext/sseq_gui/interface/sseq.js b/web_ext/sseq_gui/interface/sseq.js index 445fefe19..a8444cd59 100644 --- a/web_ext/sseq_gui/interface/sseq.js +++ b/web_ext/sseq_gui/interface/sseq.js @@ -46,6 +46,14 @@ const KEEP_LOG = new Set([ 'SetClassName', ]); +function bidegreeToCoordinates({ n, s }) { + return [n, s]; +} + +function coordinatesToBidegree(x, y) { + return { n: x, s: y }; +} + export class BiVec { constructor(minDegree, data) { this.data = data ? data : []; @@ -182,12 +190,12 @@ export class ExtSseq { } addPermanentClass(x, y, target) { + const b = coordinatesToBidegree(x, y); this.send({ recipients: ['Sseq'], action: { AddPermanentClass: { - x: x, - y: y, + b: b, class: target, }, }, @@ -265,6 +273,7 @@ export class ExtSseq { // addProductInteractive takes in the number of classes in bidegree (x, y), because this should be the number of classes in the *unit* spectral sequence, not the main spectral sequence addProductInteractive(x, y, num) { + const b = coordinatesToBidegree(x, y); dialog( `Add product at (${x}, ${y})`, `

@@ -283,8 +292,7 @@ export class ExtSseq { permanent: dialog.querySelector('checkbox-switch') .checked === true, - x: x, - y: y, + b: b, class: eval( dialog.querySelector("input[name='class']") .value, @@ -310,6 +318,8 @@ export class ExtSseq { sourceY + page, MIN_PAGE, ).length; + const sourceB = coordinatesToBidegree(sourceX, sourceY); + const targetB = coordinatesToBidegree(sourceX - 1, sourceY + page); dialog( `Add product differential at (${sourceX}, ${sourceY})`, `
@@ -345,8 +355,7 @@ export class ExtSseq { AddProductDifferential: { source: { permanent: false, - x: sourceX, - y: sourceY, + b: sourceB, class: eval( dialog.querySelector("input[name='source']") .value, @@ -359,8 +368,7 @@ export class ExtSseq { }, target: { permanent: false, - x: sourceX - 1, - y: sourceY + page, + b: targetB, class: eval( dialog.querySelector("input[name='target']") .value, @@ -408,13 +416,13 @@ export class ExtSseq { } addDifferential(r, source_x, source_y, source, target) { + const sourceB = coordinatesToBidegree(source_x, source_y); this.send({ recipients: ['Sseq'], action: { AddDifferential: { r: r, - x: source_x, - y: source_y, + b: sourceB, source: source, target: target, }, @@ -549,8 +557,7 @@ export class ExtSseq { } processSetClass(data) { - const x = data.x; - const y = data.y; + const [x, y] = bidegreeToCoordinates(data.b); const oldClasses = this.classes.get(x, y); // classes is a list, and each member of the list corresponds to a @@ -677,8 +684,7 @@ export class ExtSseq { } processSetDifferential(data) { - const x = data.x; - const y = data.y; + const [x, y] = bidegreeToCoordinates(data.b); while (this.chart.pages.length <= data.differentials.length) { this.newPage(); @@ -709,14 +715,14 @@ export class ExtSseq { } processSetStructline(data) { - const x = data.x; - const y = data.y; + const [x, y] = bidegreeToCoordinates(data.b); for (const mult of data.structlines) { + const [mult_x, mult_y] = bidegreeToCoordinates(mult.mult_b); if (!this.products.has(mult.name)) { this.products.set(mult.name, { - x: mult.mult_x, - y: mult.mult_y, + x: mult_x, + y: mult_y, matrices: new BiVec(this.minDegree), style: { bend: 0, @@ -752,9 +758,9 @@ export class ExtSseq { for (const line of ExtSseq.drawMatrix( matrix, x, - x + mult.mult_x, + x + mult_x, y, - y + mult.mult_y, + y + mult_y, product.style.bend, )) { line.classList.add(`structline`); diff --git a/web_ext/sseq_gui/src/actions.rs b/web_ext/sseq_gui/src/actions.rs index 745b7addb..7ac7173ee 100644 --- a/web_ext/sseq_gui/src/actions.rs +++ b/web_ext/sseq_gui/src/actions.rs @@ -5,7 +5,7 @@ use ext::{CCC, chain_complex::FreeChainComplex}; use fp::vector::FpVector; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use sseq::coordinates::{Bidegree, BidegreeGenerator}; +use sseq::coordinates::{Bidegree, BidegreeElement, BidegreeGenerator}; use crate::{ resolution_wrapper::Resolution, @@ -108,8 +108,7 @@ pub trait ActionT: std::fmt::Debug { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddDifferential { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub r: i32, pub source: Vec, pub target: Vec, @@ -117,20 +116,19 @@ pub struct AddDifferential { impl ActionT for AddDifferential { fn act_sseq(&self, sseq: &mut SseqWrapper) -> Option { - let source = FpVector::from_slice(sseq.p, &self.source); - let target = FpVector::from_slice(sseq.p, &self.target); + let source = BidegreeElement::new(self.b, FpVector::from_slice(sseq.p, &self.source)); + let target_vec = FpVector::from_slice(sseq.p, &self.target); sseq.inner - .add_differential(self.r, self.x, self.y, source.as_slice(), target.as_slice()); - sseq.add_differential_propagate(self.r, self.x, self.y, source.as_slice(), 0); + .add_differential(self.r, &source, target_vec.as_slice()); + sseq.add_differential_propagate(self.r, &source, 0); None } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddProductType { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub class: Vec, pub name: String, pub permanent: bool, @@ -138,32 +136,28 @@ pub struct AddProductType { impl ActionT for AddProductType { fn act_sseq(&self, sseq: &mut SseqWrapper) -> Option { - sseq.add_product_type(&self.name, self.x, self.y, true, self.permanent); + sseq.add_product_type(&self.name, self.b, true, self.permanent); None } fn act_resolution(&self, resolution: &mut Resolution) -> Option { - let b = Bidegree::s_t(self.y, self.x + self.y); - - resolution.add_product(b, self.class.clone(), &self.name); + resolution.add_product(self.b, self.class.clone(), &self.name); None } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddPermanentClass { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub class: Vec, } impl ActionT for AddPermanentClass { fn act_sseq(&self, sseq: &mut SseqWrapper) -> Option { - let class = FpVector::from_slice(sseq.p, &self.class); + let class = BidegreeElement::new(self.b, FpVector::from_slice(sseq.p, &self.class)); - sseq.inner - .add_permanent_class(self.x, self.y, class.as_slice()); - sseq.add_differential_propagate(i32::MAX, self.x, self.y, class.as_slice(), 0); + sseq.inner.add_permanent_class(&class); + sseq.add_differential_propagate(i32::MAX, &class, 0); None } @@ -171,15 +165,14 @@ impl ActionT for AddPermanentClass { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SetClassName { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub idx: usize, pub name: String, } impl ActionT for SetClassName { fn act_sseq(&self, sseq: &mut SseqWrapper) -> Option { - sseq.set_class_name(self.x, self.y, self.idx, self.name.clone()); + sseq.set_class_name(self.b, self.idx, self.name.clone()); None } } @@ -220,24 +213,21 @@ impl ActionT for BlockRefresh { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddClass { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub num: usize, } impl ActionT for AddClass { fn act_sseq(&self, sseq: &mut SseqWrapper) -> Option { - sseq.set_dimension(self.x, self.y, self.num); + sseq.set_dimension(self.b, self.num); None } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddProduct { - pub mult_x: i32, - pub mult_y: i32, - pub source_x: i32, - pub source_y: i32, + pub mult_b: Bidegree, + pub source_b: Bidegree, pub name: String, pub product: Vec>, pub left: bool, @@ -247,10 +237,8 @@ impl ActionT for AddProduct { fn act_sseq(&self, sseq: &mut SseqWrapper) -> Option { sseq.add_product( &self.name, - self.source_x, - self.source_y, - self.mult_x, - self.mult_y, + self.source_b, + self.mult_b, self.left, &self.product, ); @@ -320,16 +308,14 @@ impl ActionT for Resolve {} // Now actions for sseq -> js #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SetStructline { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub structlines: Vec, } impl ActionT for SetStructline {} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SetDifferential { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub true_differentials: Vec, Vec)>>, pub differentials: BiVec>>, } @@ -337,12 +323,11 @@ impl ActionT for SetDifferential {} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SetClass { - pub x: i32, - pub y: i32, + pub b: Bidegree, pub state: ClassState, pub permanents: Vec, pub classes: Vec>, - pub decompositions: Vec<(FpVector, String, i32, i32)>, + pub decompositions: Vec<(FpVector, String, Bidegree)>, pub class_names: Vec, } impl ActionT for SetClass {} diff --git a/web_ext/sseq_gui/src/managers.rs b/web_ext/sseq_gui/src/managers.rs index c06e2a79e..d9742129d 100644 --- a/web_ext/sseq_gui/src/managers.rs +++ b/web_ext/sseq_gui/src/managers.rs @@ -294,8 +294,7 @@ impl SseqManager { *target = Some(SseqWrapper::new( m.p, msg.sseq, - m.min_degree, - 0, + Bidegree::x_y(m.min_degree, 0), Some(self.sender.clone()), )); } diff --git a/web_ext/sseq_gui/src/resolution_wrapper.rs b/web_ext/sseq_gui/src/resolution_wrapper.rs index 1ee0c17ce..924b83024 100644 --- a/web_ext/sseq_gui/src/resolution_wrapper.rs +++ b/web_ext/sseq_gui/src/resolution_wrapper.rs @@ -146,8 +146,7 @@ impl Resolution { recipients: vec![], sseq: self.sseq, action: Action::from(crate::actions::AddClass { - x: b.n(), - y: b.s(), + b, num: self.inner.number_of_gens_in_bidegree(b), }), }) @@ -181,15 +180,17 @@ impl Resolution { pub fn add_structline( &self, name: &str, - source: Bidegree, - mult: Bidegree, + source_b: Bidegree, + mult_b: Bidegree, left: bool, mut product: Vec>, ) { let p = self.prime(); // Product in Ext is not product in E_2 - if (left && mult.s() * source.t() % 2 != 0) || (!left && mult.t() * source.s() % 2 != 0) { + if (left && mult_b.s() * source_b.t() % 2 != 0) + || (!left && mult_b.t() * source_b.s() % 2 != 0) + { for entry in product.iter_mut().flatten() { *entry = ((p - 1) * *entry) % p; } @@ -200,10 +201,8 @@ impl Resolution { recipients: vec![], sseq: self.sseq, action: Action::from(crate::actions::AddProduct { - mult_x: mult.n(), - mult_y: mult.s(), - source_x: source.n(), - source_y: source.s(), + source_b, + mult_b, name: name.to_owned(), product, left, diff --git a/web_ext/sseq_gui/src/sseq.rs b/web_ext/sseq_gui/src/sseq.rs index 20ed0e3f8..a0f99a8e0 100644 --- a/web_ext/sseq_gui/src/sseq.rs +++ b/web_ext/sseq_gui/src/sseq.rs @@ -7,7 +7,10 @@ use fp::{ vector::{FpSlice, FpVector}, }; use serde::{Deserialize, Serialize}; -use sseq::{Adams, Sseq, SseqProfile}; +use sseq::{ + Adams, Sseq, SseqProfile, + coordinates::{Bidegree, BidegreeElement}, +}; use crate::{Sender, actions::*}; @@ -34,8 +37,7 @@ pub struct Product { #[derive(Clone, Serialize, Deserialize, Debug)] pub struct ProductItem { name: String, - mult_x: i32, - mult_y: i32, + mult_b: Bidegree, matrices: BiVec>>, // page -> matrix } @@ -67,23 +69,17 @@ pub struct SseqWrapper { } impl SseqWrapper

{ - pub fn new( - p: ValidPrime, - name: SseqChoice, - min_x: i32, - min_y: i32, - sender: Option, - ) -> Self { + pub fn new(p: ValidPrime, name: SseqChoice, min: Bidegree, sender: Option) -> Self { Self { p, name, sender, block_refresh: 0, - inner: Sseq::new(p, min_x, min_y), + inner: Sseq::new(p, min), products: BTreeMap::default(), - class_names: BiVec::new(min_x), - stale: BiVec::new(min_x), + class_names: BiVec::new(min.x()), + stale: BiVec::new(min.x()), } } @@ -106,35 +102,35 @@ impl SseqWrapper

{ return; } - for x in self.inner.min_x()..=self.inner.max_x() { + for x in self.inner.min().x()..=self.inner.max().x() { for y in self.inner.range(x) { - if !self.inner.invalid(x, y) { + let b = Bidegree::x_y(x, y); + if !self.inner.invalid(b) { continue; } - self.stale[x][y] |= CLASS_FLAG | EDGE_FLAG; + self.stale[b.x()][b.y()] |= CLASS_FLAG | EDGE_FLAG; for product in self.products.values() { - let prod_x = product.inner.x; - let prod_y = product.inner.y; - if self.inner.defined(x - prod_x, y - prod_y) { - self.stale[x - prod_x][y - prod_y] |= EDGE_FLAG; + let prod_origin_b = b - product.inner.b; + if self.inner.defined(prod_origin_b) { + self.stale[prod_origin_b.x()][prod_origin_b.y()] |= EDGE_FLAG; } } - let differentials = self.inner.update_bidegree(x, y); + let differentials = self.inner.update_bidegree(b); if !differentials.is_empty() { // `true_differentials` is a list of differentials of the form d(source) = target we know // to be true. `differentials` is our best guess at what the matrix of differentials is. let true_differentials = self .inner - .differentials(x, y) + .differentials(b) .iter_enum() .map(|(r, d)| { - let (tx, ty) = P::profile(r, x, y); + let target_b = P::profile(r, b); d.get_source_target_pairs() .into_iter() .map(|(mut s, mut t)| { ( - self.inner.page_data(x, y)[r].reduce(s.as_slice_mut()), - self.inner.page_data(tx, ty)[r].reduce(t.as_slice_mut()), + self.inner.page_data(b)[r].reduce(s.as_slice_mut()), + self.inner.page_data(target_b)[r].reduce(t.as_slice_mut()), ) }) .collect::>() @@ -145,8 +141,7 @@ impl SseqWrapper

{ recipients: vec![], sseq: self.name, action: Action::from(SetDifferential { - x, - y, + b, true_differentials, differentials, }), @@ -157,43 +152,45 @@ impl SseqWrapper

{ for x in self.stale.range() { for y in self.stale[x].range() { - if self.stale[x][y] & CLASS_FLAG > 0 { - self.send_class_data(x, y); + let b = Bidegree::x_y(x, y); + if self.stale[b.x()][b.y()] & CLASS_FLAG > 0 { + self.send_class_data(b); } - if self.stale[x][y] & EDGE_FLAG > 0 { - self.send_products(x, y); + if self.stale[b.x()][b.y()] & EDGE_FLAG > 0 { + self.send_products(b); } - self.stale[x][y] = 0; + self.stale[b.x()][b.y()] = 0; } } } - /// Computes products whose source is at (x, y). - fn send_products(&self, x: i32, y: i32) { - if !self.inner.defined(x, y) { + /// Computes products whose source is at `b`. + fn send_products(&self, b: Bidegree) { + if !self.inner.defined(b) { return; } - if self.inner.dimension(x, y) == 0 { + if self.inner.dimension(b) == 0 { return; } let mut structlines: Vec = Vec::with_capacity(self.products.len()); for (name, mult) in &self.products { - if !(mult.inner.matrices.len() > x && mult.inner.matrices[x].len() > y) { + if !(mult.inner.matrices.len() > b.x() && mult.inner.matrices[b.x()].len() > b.y()) { continue; } - let prod_x = mult.inner.x; - let prod_y = mult.inner.y; - let target_dim = self.inner.dimension(x + prod_x, y + prod_y); + let prod_b = mult.inner.b; + let prod_output_b = b + prod_b; + + let target_dim = self.inner.dimension(prod_output_b); if target_dim == 0 { continue; } - if let Some(matrix) = &mult.inner.matrices[x][y] { + if let Some(matrix) = &mult.inner.matrices[b.x()][b.y()] { let max_page = max( - self.inner.page_data(x, y).len(), - self.inner.page_data(x + prod_x, y + prod_y).len(), + self.inner.page_data(b).len(), + self.inner.page_data(prod_output_b).len(), ); let mut matrices: BiVec>> = BiVec::with_capacity(P::MIN_R, max_page); @@ -202,8 +199,8 @@ impl SseqWrapper

{ // Compute the ones where something changes. for r in P::MIN_R + 1..max_page { - let source_data = self.inner.page_data(x, y).get_max(r); - let target_data = self.inner.page_data(x + prod_x, y + prod_y).get_max(r); + let source_data = self.inner.page_data(b).get_max(r); + let target_data = self.inner.page_data(prod_output_b).get_max(r); matrices.push(Subquotient::reduce_matrix(matrix, source_data, target_data)); @@ -216,8 +213,7 @@ impl SseqWrapper

{ structlines.push(ProductItem { name: name.clone(), - mult_x: prod_x, - mult_y: prod_y, + mult_b: prod_b, matrices, }); } @@ -226,33 +222,33 @@ impl SseqWrapper

{ self.send(Message { recipients: vec![], sseq: self.name, - action: Action::from(SetStructline { x, y, structlines }), + action: Action::from(SetStructline { b, structlines }), }); } - fn send_class_data(&self, x: i32, y: i32) { + fn send_class_data(&self, b: Bidegree) { if self.block_refresh > 0 { return; } - let state = if self.inner.inconsistent(x, y) { + let state = if self.inner.inconsistent(b) { ClassState::Error - } else if self.inner.complete(x, y) { + } else if self.inner.complete(b) { ClassState::Done } else { ClassState::InProgress }; - let mut decompositions: Vec<(FpVector, String, i32, i32)> = Vec::new(); + let mut decompositions: Vec<(FpVector, String, Bidegree)> = Vec::new(); for (name, prod) in &self.products { - let prod_x = prod.inner.x; - let prod_y = prod.inner.y; + let prod_b = prod.inner.b; + let prod_origin_b = b - prod_b; if let Some(Some(Some(matrix))) = &prod .inner .matrices - .get(x - prod_x) - .map(|m| m.get(y - prod_y)) + .get(prod_origin_b.x()) + .map(|m| m.get(prod_origin_b.y())) { for i in 0..matrix.rows() { if matrix[i].is_zero() { @@ -260,9 +256,11 @@ impl SseqWrapper

{ } decompositions.push(( matrix[i].clone(), - format!("{name} {}", self.class_names[x - prod_x][y - prod_y][i]), - prod_x, - prod_y, + format!( + "{name} {}", + self.class_names[prod_origin_b.x()][prod_origin_b.y()][i] + ), + prod_b, )); } } @@ -272,15 +270,14 @@ impl SseqWrapper

{ recipients: vec![], sseq: self.name, action: Action::from(SetClass { - x, - y, + b, state, - permanents: self.inner.permanent_classes(x, y).basis().to_vec(), - class_names: self.class_names[x][y].clone(), + permanents: self.inner.permanent_classes(b).basis().to_vec(), + class_names: self.class_names[b.x()][b.y()].clone(), decompositions, classes: self .inner - .page_data(x, y) + .page_data(b) .iter() .map(|x| x.gens().map(FpSlice::to_owned).collect()) .collect::>>(), @@ -299,28 +296,31 @@ impl SseqWrapper

{ impl SseqWrapper

{ /// This function should only be called when everything to the left and bottom of (x, y) /// has been defined. - pub fn set_dimension(&mut self, x: i32, y: i32, dim: usize) { - self.inner.set_dimension(x, y, dim); - if x == self.class_names.len() { - self.class_names.push(BiVec::new(self.inner.min_y())); - self.stale.push(BiVec::new(self.inner.min_y())); + pub fn set_dimension(&mut self, b: Bidegree, dim: usize) { + self.inner.set_dimension(b, dim); + if b.x() == self.class_names.len() { + self.class_names.push(BiVec::new(self.inner.min().y())); + self.stale.push(BiVec::new(self.inner.min().y())); } let mut names = Vec::with_capacity(dim); if dim == 1 { - names.push(format!("x_{{{x},{y}}}")); + names.push(format!("x_{{{x},{y}}}", x = b.x(), y = b.y())); } else { - names.extend((0..dim).map(|i| format!("x_{{{x}, {y}}}^{{({i})}}"))); + names.extend( + (0..dim).map(|i| format!("x_{{{x}, {y}}}^{{({i})}}", x = b.x(), y = b.y())), + ); } - self.class_names[x].push(names); - self.stale[x].push(CLASS_FLAG); + self.class_names[b.x()].push(names); + self.stale[b.x()].push(CLASS_FLAG); } - pub fn set_class_name(&mut self, x: i32, y: i32, idx: usize, name: String) { - self.class_names[x][y][idx] = name; - self.send_class_data(x, y); + pub fn set_class_name(&mut self, b: Bidegree, idx: usize, name: String) { + self.class_names[b.x()][b.y()][idx] = name; + self.send_class_data(b); for prod in self.products.values() { - if self.inner.defined(x + prod.inner.x, y + prod.inner.y) { - self.send_class_data(x + prod.inner.x, y + prod.inner.y); + let prod_output_b = b + prod.inner.b; + if self.inner.defined(prod_output_b) { + self.send_class_data(prod_output_b); } } } @@ -336,9 +336,7 @@ impl SseqWrapper

{ pub fn add_differential_propagate( &mut self, r: i32, - x: i32, - y: i32, - source: FpSlice, + source: &BidegreeElement, product_index: usize, ) { if self.products.is_empty() { @@ -346,18 +344,18 @@ impl SseqWrapper

{ } // This is useful for batch adding differentials from external sources, where not all // classes have been added. - if !self.inner.defined(x, y) { + if !self.inner.defined(source.degree()) { return; } if r != i32::MAX { - let (tx, ty) = P::profile(r, x, y); - if !self.inner.defined(tx, ty) { + let target_b = P::profile(r, source.degree()); + if !self.inner.defined(target_b) { return; } } if product_index + 1 < self.products.len() { - self.add_differential_propagate(r, x, y, source, product_index + 1); + self.add_differential_propagate(r, source, product_index + 1); } let product = self.products.values().nth(product_index).unwrap(); @@ -370,22 +368,15 @@ impl SseqWrapper

{ }; // Separate this to new line to make code easier to read. - let new_d = self.inner.leibniz(r, x, y, source, &product.inner, target); + let new_d = self.inner.leibniz(r, source, &product.inner, target); - if let Some((r, x, y, source)) = new_d { - self.add_differential_propagate(r, x, y, source.as_slice(), product_index); + if let Some((r, source)) = new_d { + self.add_differential_propagate(r, &source, product_index); } } /// Add a product to the list of products, but don't add any computed product - pub fn add_product_type( - &mut self, - name: &str, - mult_x: i32, - mult_y: i32, - left: bool, - permanent: bool, - ) { + pub fn add_product_type(&mut self, name: &str, mult_b: Bidegree, left: bool, permanent: bool) { if let Some(product) = self.products.get_mut(name) { product.user = true; if permanent && !product.permanent { @@ -395,10 +386,9 @@ impl SseqWrapper

{ } else { let product = Product { inner: sseq::Product { - x: mult_x, - y: mult_y, + b: mult_b, left, - matrices: BiVec::new(self.inner.min_x()), + matrices: BiVec::new(self.inner.min().x()), }, user: true, permanent, @@ -409,10 +399,8 @@ impl SseqWrapper

{ } pub fn add_product_differential(&mut self, source: &str, target: &str) { - let r = P::differential_length( - self.products[target].inner.x - self.products[source].inner.x, - self.products[target].inner.y - self.products[source].inner.y, - ); + let offset = self.products[target].inner.b - self.products[source].inner.b; + let r = P::differential_length(offset); self.products.get_mut(source).unwrap().differential = Some((r, true, target.to_owned())); self.products.get_mut(target).unwrap().differential = Some((r, false, source.to_owned())); @@ -425,14 +413,14 @@ impl SseqWrapper

{ // We only use this to figure out the range for x in self.products[name].inner.matrices.range() { for y in self.products[name].inner.matrices[x].range() { - self.propagate_product(x, y, name); + self.propagate_product(Bidegree::x_y(x, y), name); } } } - /// Propagate products by the product indexed by `idx` at (x, y). The product must either be + /// Propagate products by the product indexed by `idx` at `b`. The product must either be /// permanent or the source of a differential. - fn propagate_product(&mut self, x: i32, y: i32, name: &str) { + fn propagate_product(&mut self, b: Bidegree, name: &str) { let product = &self.products[name]; let target = if product.permanent { None @@ -442,40 +430,43 @@ impl SseqWrapper

{ return; }; - for r in self.inner.differentials(x, y).range() { - let pairs = self.inner.differentials(x, y)[r].get_source_target_pairs(); + for r in self.inner.differentials(b).range() { + let pairs = self.inner.differentials(b)[r].get_source_target_pairs(); for (source, _) in pairs { self.inner - .leibniz(r, x, y, source.as_slice(), &product.inner, target); + .leibniz(r, &BidegreeElement::new(b, source), &product.inner, target); } } - let permanent_classes = self.inner.permanent_classes(x, y).basis().to_vec(); + let permanent_classes = self.inner.permanent_classes(b).basis().to_vec(); for class in permanent_classes { - self.inner - .leibniz(i32::MAX, x, y, class.as_slice(), &product.inner, target); + self.inner.leibniz( + i32::MAX, + &BidegreeElement::new(b, class), + &product.inner, + target, + ); } } pub fn add_product( &mut self, name: &str, - x: i32, - y: i32, - mult_x: i32, - mult_y: i32, + b: Bidegree, + mult_b: Bidegree, left: bool, matrix: &[Vec], ) { - assert!(self.inner.defined(x, y)); - assert!(self.inner.defined(x + mult_x, y + mult_y)); + let prod_output_b = b + mult_b; + assert!(self.inner.defined(b)); + assert!(self.inner.defined(prod_output_b)); + if !self.products.contains_key(name) { let product = Product { inner: sseq::Product { - x: mult_x, - y: mult_y, + b: mult_b, left, - matrices: BiVec::new(self.inner.min_x()), + matrices: BiVec::new(self.inner.min().x()), }, user: false, permanent: true, @@ -488,20 +479,20 @@ impl SseqWrapper

{ product .inner .matrices - .extend_with(x, |_| BiVec::new(self.inner.min_y())); - product.inner.matrices[x].extend_with(y - 1, |_| None); + .extend_with(b.x(), |_| BiVec::new(self.inner.min().y())); + product.inner.matrices[b.x()].extend_with(b.y() - 1, |_| None); let matrix = Matrix::from_vec(self.p, matrix); - if self.inner.dimension(x, y) != 0 && self.inner.dimension(x + mult_x, y + mult_y) != 0 { - self.stale[x][y] |= EDGE_FLAG; + if self.inner.dimension(b) != 0 && self.inner.dimension(prod_output_b) != 0 { + self.stale[b.x()][b.y()] |= EDGE_FLAG; if !matrix.is_zero() { - self.stale[x + mult_x][y + mult_y] |= CLASS_FLAG; + self.stale[prod_output_b.x()][prod_output_b.y()] |= CLASS_FLAG; } } - assert_eq!(y, product.inner.matrices[x].len()); - product.inner.matrices[x].push(Some(matrix)); + assert_eq!(b.y(), product.inner.matrices[b.x()].len()); + product.inner.matrices[b.x()].push(Some(matrix)); let product = &*product; @@ -509,18 +500,18 @@ impl SseqWrapper

{ // source and target, and the β product on the source. if let Some((_, false, source_name)) = &product.differential { let source_name = source_name.clone(); - self.propagate_product(x, y, &source_name); + self.propagate_product(b, &source_name); } else if matches!(product.differential, Some((_, true, _))) || product.permanent { - self.propagate_product(x, y, name); + self.propagate_product(b, name); let hitting: Vec = self .inner - .differentials_hitting(x, y) + .differentials_hitting(b) .map(|(r, _)| r) .collect(); for r in hitting { - let (sx, sy) = P::profile_inverse(r, x, y); - if self.inner.defined(sx, sy) { - self.propagate_product(sx, sy, name); + let source_b = P::profile_inverse(r, b); + if self.inner.defined(source_b) { + self.propagate_product(source_b, name); } } } diff --git a/web_ext/sseq_gui/tests/benchmarks/s_2.save b/web_ext/sseq_gui/tests/benchmarks/s_2.save index 5a4fa3d1f..7e70c088c 100644 --- a/web_ext/sseq_gui/tests/benchmarks/s_2.save +++ b/web_ext/sseq_gui/tests/benchmarks/s_2.save @@ -1,14 +1,14 @@ {"recipients":["Resolver"],"sseq":"Main","action":{"Construct":{"algebra_name":"milnor","module_name":"S_2"}}} {"recipients":["Resolver"],"sseq":"Main","action":{"Resolve":{"max_degree":36}}} -{"recipients":["Sseq"],"action":{"AddDifferential":{"r":2,"x":15,"y":1,"source":[1],"target":[1]}},"sseq":"Main"} -{"recipients":["Sseq"],"action":{"AddDifferential":{"r":3,"x":15,"y":2,"source":[1],"target":[1]}},"sseq":"Main"} -{"recipients":["Sseq"],"action":{"AddDifferential":{"r":2,"x":17,"y":4,"source":[1],"target":[1]}},"sseq":"Main"} -{"recipients":["Sseq"],"action":{"AddDifferential":{"r":2,"x":18,"y":4,"source":[0,1],"target":[1]}},"sseq":"Main"} -{"recipients":["Sseq"],"action":{"AddPermanentClass":{"x":0,"y":0,"class":[1]}},"sseq":"Main"} -{"recipients":["Sseq"],"action":{"AddPermanentClass":{"x":8,"y":3,"class":[1]}},"sseq":"Main"} -{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"x":8,"y":3,"class":[1],"name":"c_0"}},"sseq":"Main"} -{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"x":9,"y":5,"class":[1],"name":"Ph_1"}},"sseq":"Main"} -{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"x":14,"y":4,"class":[1],"name":"d_0"}},"sseq":"Main"} -{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"x":20,"y":4,"class":[1],"name":"g"}},"sseq":"Main"} -{"recipients":["Sseq","Resolver"],"action":{"AddProductDifferential":{"source":{"permanent":false,"x":17,"y":4,"class":[1],"name":"e_0"},"target":{"permanent":false,"x":16,"y":6,"class":[1],"name":"h_1^2 d_0"}}},"sseq":"Main"} -{"recipients":["Sseq","Resolver"],"action":{"AddProductDifferential":{"source":{"permanent":false,"x":18,"y":4,"class":[0,1],"name":"f_0"},"target":{"permanent":false,"x":17,"y":6,"class":[1],"name":"h_0^2 e_0"}}},"sseq":"Main"} \ No newline at end of file +{"recipients":["Sseq"],"action":{"AddDifferential":{"r":2,"b":{"n":15,"s":1},"source":[1],"target":[1]}},"sseq":"Main"} +{"recipients":["Sseq"],"action":{"AddDifferential":{"r":3,"b":{"n":15,"s":2},"source":[1],"target":[1]}},"sseq":"Main"} +{"recipients":["Sseq"],"action":{"AddDifferential":{"r":2,"b":{"n":17,"s":4},"source":[1],"target":[1]}},"sseq":"Main"} +{"recipients":["Sseq"],"action":{"AddDifferential":{"r":2,"b":{"n":18,"s":4},"source":[0,1],"target":[1]}},"sseq":"Main"} +{"recipients":["Sseq"],"action":{"AddPermanentClass":{"b":{"n":0,"s":0},"class":[1]}},"sseq":"Main"} +{"recipients":["Sseq"],"action":{"AddPermanentClass":{"b":{"n":8,"s":3},"class":[1]}},"sseq":"Main"} +{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"b":{"n":8,"s":3},"class":[1],"name":"c_0"}},"sseq":"Main"} +{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"b":{"n":9,"s":5},"class":[1],"name":"Ph_1"}},"sseq":"Main"} +{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"b":{"n":14,"s":4},"class":[1],"name":"d_0"}},"sseq":"Main"} +{"recipients":["Sseq","Resolver"],"action":{"AddProductType":{"permanent":true,"b":{"n":20,"s":4},"class":[1],"name":"g"}},"sseq":"Main"} +{"recipients":["Sseq","Resolver"],"action":{"AddProductDifferential":{"source":{"permanent":false,"b":{"n":17,"s":4},"class":[1],"name":"e_0"},"target":{"permanent":false,"b":{"n":16,"s":6},"class":[1],"name":"h_1^2 d_0"}}},"sseq":"Main"} +{"recipients":["Sseq","Resolver"],"action":{"AddProductDifferential":{"source":{"permanent":false,"b":{"n":18,"s":4},"class":[0,1],"name":"f_0"},"target":{"permanent":false,"b":{"n":17,"s":6},"class":[1],"name":"h_0^2 e_0"}}},"sseq":"Main"} \ No newline at end of file