1use std::sync::Arc;
2
3use algebra::{
4 AdemAlgebra, Algebra, GeneratedAlgebra, MilnorAlgebra, SteenrodAlgebra,
5 module::{
6 FDModule, FreeModule, Module, QuotientModule as QM,
7 homomorphism::{
8 FreeModuleHomomorphism, FullModuleHomomorphism, IdentityHomomorphism,
9 ModuleHomomorphism, QuotientHomomorphism, QuotientHomomorphismSource,
10 },
11 },
12};
13use bivec::BiVec;
14use fp::{
15 matrix::{AugmentedMatrix, Matrix, Subspace},
16 vector::FpVector,
17};
18use rustc_hash::FxHashSet as HashSet;
19use sseq::coordinates::Bidegree;
20
21use crate::{
22 chain_complex::{
23 AugmentedChainComplex, BoundedChainComplex, ChainComplex, ChainMap,
24 FiniteAugmentedChainComplex, FiniteChainComplex, FreeChainComplex,
25 },
26 resolution_homomorphism::ResolutionHomomorphism,
27};
28
29const PENALTY_UNIT: i32 = 10000;
30
31pub type Yoneda<CC> = FiniteAugmentedChainComplex<
32 FDModule<<CC as ChainComplex>::Algebra>,
33 FullModuleHomomorphism<FDModule<<CC as ChainComplex>::Algebra>>,
34 FullModuleHomomorphism<
35 FDModule<<CC as ChainComplex>::Algebra>,
36 <<CC as AugmentedChainComplex>::TargetComplex as ChainComplex>::Module,
37 >,
38 <CC as AugmentedChainComplex>::TargetComplex,
39>;
40
41fn rate_operation<A: Algebra>(algebra: &Arc<A>, op_deg: i32, op_idx: usize) -> i32 {
42 let algebra = &**algebra as &dyn std::any::Any;
43
44 if let Some(algebra) = algebra.downcast_ref::<SteenrodAlgebra>() {
45 match algebra {
46 SteenrodAlgebra::AdemAlgebra(a) => rate_adem_operation(a, op_deg, op_idx),
47 SteenrodAlgebra::MilnorAlgebra(a) => rate_milnor_operation(a, op_deg, op_idx),
48 }
49 } else if let Some(algebra) = algebra.downcast_ref::<MilnorAlgebra>() {
50 rate_milnor_operation(algebra, op_deg, op_idx)
51 } else if let Some(algebra) = algebra.downcast_ref::<AdemAlgebra>() {
52 rate_adem_operation(algebra, op_deg, op_idx)
53 } else {
54 0
55 }
56}
57
58fn rate_milnor_operation(algebra: &MilnorAlgebra, deg: i32, idx: usize) -> i32 {
59 let elt = algebra.basis_element_from_index(deg, idx);
60 elt.p_part
61 .iter()
62 .enumerate()
63 .map(|(i, &r)| r.count_ones() << i)
64 .sum::<u32>() as i32
65}
66
67fn rate_adem_operation(algebra: &AdemAlgebra, deg: i32, idx: usize) -> i32 {
68 let elt = algebra.basis_element_from_index(deg, idx);
69 elt.ps.iter().map(|&r| r.count_ones()).sum::<u32>() as i32
70}
71
72fn split_mut_borrow<T>(v: &mut [T], i: usize, j: usize) -> (&mut T, &mut T) {
73 assert!(i < j);
74 let (first, second) = v.split_at_mut(j);
75 (&mut first[i], &mut second[0])
76}
77
78pub fn yoneda_representative_element<CC>(cc: Arc<CC>, b: Bidegree, class: &[u32]) -> Yoneda<CC>
79where
80 CC: FreeChainComplex
81 + AugmentedChainComplex<
82 ChainMap = FreeModuleHomomorphism<
83 <<CC as AugmentedChainComplex>::TargetComplex as ChainComplex>::Module,
84 >,
85 >,
86 CC::TargetComplex: BoundedChainComplex,
87 CC::Algebra: GeneratedAlgebra,
88{
89 let p = cc.prime();
90
91 let target = FDModule::new(cc.algebra(), "".to_string(), BiVec::from_vec(0, vec![1]));
92 let map = FreeModuleHomomorphism::new(cc.module(b.s()), Arc::new(target), b.t());
93 let mut rows = vec![FpVector::new(p, 1); cc.number_of_gens_in_bidegree(b)];
94 for (&i, row) in std::iter::zip(class, &mut rows) {
95 row.set_entry(0, i);
96 }
97
98 map.add_generators_from_rows(b.t(), rows);
99
100 let cm = ChainMap {
101 s_shift: b.s(),
102 chain_maps: vec![map],
103 };
104 let yoneda = Arc::new(yoneda_representative(Arc::clone(&cc), cm));
105
106 let module = cc.target().module(0);
108
109 for t in cc.min_degree()..=b.t() {
110 assert_eq!(
111 yoneda.euler_characteristic(t),
112 module.dimension(t) as isize,
113 "Incorrect Euler characteristic at t = {t}",
114 );
115 }
116
117 let f = ResolutionHomomorphism::from_module_homomorphism(
118 "".to_string(),
119 Arc::clone(&cc),
120 Arc::clone(&yoneda),
121 &FullModuleHomomorphism::identity_homomorphism(module),
122 );
123
124 f.extend_through_stem(b);
125 let final_map = f.get_map(b.s());
126 for (i, &v) in class.iter().enumerate() {
127 assert_eq!(final_map.output(b.t(), i).len(), 1);
128 assert_eq!(final_map.output(b.t(), i).entry(0), v);
129 }
130
131 drop(f);
132 Arc::try_unwrap(yoneda).unwrap_or_else(|_| unreachable!())
133}
134
135pub fn yoneda_representative<CC>(
137 cc: Arc<CC>,
138 map: ChainMap<FreeModuleHomomorphism<impl Module<Algebra = CC::Algebra>>>,
139) -> Yoneda<CC>
140where
141 CC: FreeChainComplex
142 + AugmentedChainComplex<
143 ChainMap = FreeModuleHomomorphism<
144 <<CC as AugmentedChainComplex>::TargetComplex as ChainComplex>::Module,
145 >,
146 >,
147 CC::TargetComplex: BoundedChainComplex,
148 CC::Algebra: GeneratedAlgebra,
149{
150 yoneda_representative_with_strategy(
151 cc,
152 map,
153 |module: &FreeModule<CC::Algebra>, subspace: &Subspace, t: i32, i: usize| {
154 let opgen = module.index_to_op_gen(t, i);
155
156 let mut pref = rate_operation(
157 &module.algebra(),
158 opgen.operation_degree,
159 opgen.operation_index,
160 );
161
162 for row in subspace.iter() {
163 if row.entry(i) != 0 {
164 pref += PENALTY_UNIT;
165 }
166 }
167 pref
168 },
169 )
170}
171
172#[tracing::instrument(skip_all)]
173pub fn yoneda_representative_with_strategy<CC>(
174 cc: Arc<CC>,
175 map: ChainMap<FreeModuleHomomorphism<impl Module<Algebra = CC::Algebra>>>,
176 strategy: impl Fn(&CC::Module, &Subspace, i32, usize) -> i32,
177) -> Yoneda<CC>
178where
179 CC: FreeChainComplex
180 + AugmentedChainComplex<
181 ChainMap = FreeModuleHomomorphism<
182 <<CC as AugmentedChainComplex>::TargetComplex as ChainComplex>::Module,
183 >,
184 >,
185 CC::TargetComplex: BoundedChainComplex,
186 CC::Algebra: GeneratedAlgebra,
187{
188 let p = cc.prime();
189 let target_cc = cc.target();
190 let algebra = cc.algebra();
191
192 let t_shift: i32 = map.chain_maps[0].degree_shift();
193 let s_shift: i32 = map.s_shift;
194
195 let s_max = std::cmp::max(target_cc.max_s(), map.s_shift + map.chain_maps.len() as i32) - 1;
196
197 let t_max = {
198 let t_max_aug: Vec<i32> = (0..=s_max)
200 .map(|s| {
201 let mut t_max = cc.min_degree();
202 if s < target_cc.max_s() {
203 t_max = std::cmp::max(t_max, target_cc.module(s).max_degree().unwrap())
204 }
205 if s >= map.s_shift
206 && let Some(f) = map.chain_maps.get((s - map.s_shift) as usize)
207 {
208 t_max =
209 std::cmp::max(t_max, f.degree_shift() + f.target().max_degree().unwrap());
210 }
211 t_max
212 })
213 .collect();
214
215 let mut t_max = vec![cc.min_degree(); s_max as usize + 1];
216 for s in (0..=s_max as usize).rev() {
217 t_max[s] = t_max_aug[s];
218 if s < s_max as usize {
219 t_max[s] = std::cmp::max(t_max[s], t_max_aug[s + 1]);
221
222 t_max[s] = std::cmp::max(t_max[s], t_max[s + 1] - 1);
225 }
226 }
227 t_max
228 };
229
230 let t_min = cc.min_degree();
231
232 let mut modules = (0..=s_max)
233 .map(|s| QM::new(cc.module(s), t_max[s as usize]))
234 .collect::<Vec<_>>();
235
236 for s in (1..=s_max).rev() {
237 let span = tracing::info_span!("Cleaning yoneda representative", s);
238 let _tracing_guard = span.enter();
239 let t_max = t_max[s as usize];
240 let mut differential_images: BiVec<Subspace> = {
241 let mut result = BiVec::new(t_min);
242
243 if s < s_max {
244 let prev = &modules[s as usize + 1];
245 let curr = &modules[s as usize];
246 let d = cc.differential(s + 1);
247
248 result.extend_with(t_max, |t| {
249 let mut differentials =
250 Matrix::new(p, prev.dimension(t), curr.module.dimension(t));
251
252 for (i, mut row) in differentials.iter_mut().enumerate() {
253 let j = prev.basis_list[t][i];
254 d.apply_to_basis_element(row.copy(), 1, t, j);
255 curr.reduce(t, row);
256 }
257
258 let mut result = Subspace::from_matrix(differentials);
259
260 let dim = result.dimension();
261 result.update_then_row_reduce(|result_matrix| result_matrix.trim(0, dim, 0));
262 result
263 });
264 }
265 result
266 };
267
268 let (target, source) = split_mut_borrow(&mut modules, s as usize - 1, s as usize);
269 let d = cc.differential(s);
270
271 let dim_diff: BiVec<isize> = {
273 let mut dim_diff = BiVec::new(t_min);
274 dim_diff.extend_with(t_max, |t| {
275 source.dimension(t) as isize - target.dimension(t) as isize
276 });
277 dim_diff
278 };
279
280 macro_rules! check {
281 ($t:ident) => {
282 if $t <= target.truncation {
283 assert_eq!(
284 source.dimension($t) as isize - target.dimension($t) as isize,
285 dim_diff[$t],
286 "Failed dimension check at (s, t) = ({s}, {t})",
287 t = $t
288 );
289 }
290 };
291 }
292
293 if s < s_max {
295 let mut prev_differentials: BiVec<Option<Subspace>> =
296 BiVec::with_capacity(t_min, t_max + 1);
297 let mut prev_subspaces: BiVec<Option<Subspace>> =
298 BiVec::with_capacity(t_min, t_max + 1);
299 let mut prev_basis_list: BiVec<Option<Vec<usize>>> =
300 BiVec::with_capacity(t_min, t_max + 1);
301
302 for _ in t_min..=t_max {
303 prev_differentials.push(None);
304 prev_subspaces.push(None);
305 prev_basis_list.push(None);
306 }
307
308 let source_gens: Vec<_> = source.module.iter_gens(t_max).collect();
310 'gen_loop: for (gen_deg, gen_idx) in source_gens {
311 if s < target_cc.max_s() && target_cc.module(s).dimension(gen_deg) > 0 {
313 let m = cc.chain_map(s);
314 if !m.output(gen_deg, gen_idx).is_zero() {
315 continue 'gen_loop;
316 }
317 }
318
319 if s >= s_shift
321 && gen_deg >= t_shift
322 && let Some(m) = map.chain_maps.get((s - s_shift) as usize)
323 && m.target().dimension(gen_deg - t_shift) > 0
324 && !m.output(gen_deg, gen_idx).is_zero()
325 {
326 continue 'gen_loop;
327 }
328
329 for t in gen_deg..=t_max {
330 let diff_im = &mut differential_images[t];
331 if diff_im.is_empty() {
332 continue;
333 }
334
335 prev_differentials[t] = Some(diff_im.clone());
336 prev_subspaces[t] = Some(source.subspaces[t].clone());
337 prev_basis_list[t] = Some(source.basis_list[t].clone());
338
339 let start = source.module.generator_offset(t, gen_deg, gen_idx);
340 let end = start + algebra.dimension(t - gen_deg);
341
342 source.quotient_basis_elements(t, start..end);
343
344 let needs_to_revert = diff_im.update_then_row_reduce(|diff_im_matrix| {
345 for mut row in diff_im_matrix.iter_mut() {
346 source.reduce(t, row.copy());
347 if row.as_slice().is_zero() {
348 return true;
349 }
350 }
351
352 diff_im_matrix.row_reduce();
353 if diff_im_matrix.row(diff_im_matrix.rows() - 1).is_zero() {
354 return true;
355 }
356 false
357 });
358
359 if needs_to_revert {
360 for t in gen_deg..=t {
361 if prev_differentials[t].is_none() {
362 continue;
363 }
364 differential_images[t] = prev_differentials[t].take().unwrap();
365 source.subspaces[t] = prev_subspaces[t].take().unwrap();
366 source.basis_list[t] = prev_basis_list[t].take().unwrap();
367 }
368 continue 'gen_loop;
369 }
370 }
371
372 for t in gen_deg..=t_max {
374 let start = source.module.generator_offset(t, gen_deg, gen_idx);
375 let end = start + algebra.dimension(t - gen_deg);
376
377 if prev_differentials[t].is_none() {
378 source.quotient_basis_elements(t, start..end);
380 } else {
381 prev_differentials[t] = None;
382 prev_subspaces[t] = None;
383 prev_basis_list[t] = None;
384 }
385
386 let mut indices = start..end;
387 target.quotient_vectors(t, |row| {
388 d.apply_to_basis_element(row, 1, t, indices.next()?);
389 Some(())
390 });
391 check!(t);
392 }
393 }
394 }
395
396 for t in (t_min..=t_max).rev() {
398 if t - s < cc.min_degree() {
399 continue;
400 }
401 if source.dimension(t) == 0 {
402 continue;
403 }
404
405 let keep: Option<&Subspace> = if s < s_max {
406 Some(&differential_images[t])
407 } else {
408 None
409 };
410
411 let augmentation_map = if s < target_cc.max_s() && target_cc.module(s).dimension(t) > 0
412 {
413 Some(cc.chain_map(s))
414 } else {
415 None
416 };
417 let preserve_map = if s >= s_shift && t >= t_shift {
418 map.chain_maps.get((s - s_shift) as usize).and_then(|m| {
419 if m.target().dimension(t - t_shift) > 0 {
420 Some(m)
421 } else {
422 None
423 }
424 })
425 } else {
426 None
427 };
428
429 let (mut matrix, mut images) =
434 compute_kernel_image(source, augmentation_map.as_deref(), preserve_map, keep, t);
435
436 matrix.row_reduce();
437
438 let subspace = &source.subspaces[t];
439 let mut pivot_columns: Vec<(i32, usize)> = matrix
440 .pivots()
441 .iter()
442 .enumerate()
443 .filter_map(|(i, &v)| {
444 if v >= 0 {
445 Some((strategy(&source.module, subspace, t, i), i))
446 } else {
447 None
448 }
449 })
450 .collect::<Vec<_>>();
451 let orig_pivot_columns: Vec<usize> = pivot_columns.iter().map(|&(_, i)| i).collect();
452
453 pivot_columns.sort_unstable();
454
455 let chosen_cols: HashSet<usize> = HashSet::from_iter(
456 images.find_pivots_permutation(pivot_columns.iter().map(|(_, i)| *i)),
457 );
458
459 let get_source_iter = || {
460 std::iter::zip(matrix.iter(), orig_pivot_columns.iter()).filter_map(|(row, col)| {
461 if chosen_cols.contains(col) {
462 None
463 } else {
464 Some(row)
465 }
466 })
467 };
468 let mut source_iter = get_source_iter();
469 let mut source_iter2 = get_source_iter();
470
471 source.quotient_vectors(t, |mut row| {
472 row.add(source_iter.next()?, 1);
473 Some(())
474 });
475
476 target.quotient_vectors(t, |row| {
477 d.apply(row, 1, t, source_iter2.next()?);
478 Some(())
479 });
480
481 if s != s_max {
482 check!(t);
483 }
484 }
485 }
486
487 let modules = modules.into_iter().map(Arc::new).collect::<Vec<_>>();
488
489 let differentials: Vec<_> = (0..s_max)
490 .map(|s| {
491 Arc::new(FullModuleHomomorphism::from(&QuotientHomomorphism::new(
492 cc.differential(s + 1),
493 Arc::clone(&modules[s as usize + 1]),
494 Arc::clone(&modules[s as usize]),
495 )))
496 })
497 .collect();
498
499 let chain_maps = (0..=s_max)
500 .map(|s| {
501 Arc::new(FullModuleHomomorphism::from(
502 &QuotientHomomorphismSource::new(cc.chain_map(s), Arc::clone(&modules[s as usize])),
503 ))
504 })
505 .collect::<Vec<_>>();
506
507 let yoneda_rep =
508 FiniteChainComplex::new(modules, differentials).augment(cc.target(), chain_maps);
509
510 yoneda_rep.map(|m| FDModule::from(m))
511}
512
513fn compute_kernel_image<M: Module, F: ModuleHomomorphism, G: ModuleHomomorphism>(
521 source: &QM<M>,
522 augmentation_map: Option<&F>,
523 preserve_map: Option<&G>,
524 keep: Option<&Subspace>,
525 t: i32,
526) -> (Matrix, Matrix)
527where
528 M::Algebra: GeneratedAlgebra,
529{
530 let algebra = source.algebra();
531 let p = algebra.prime();
532
533 let mut generators: Vec<(i32, usize)> = Vec::new();
534 let mut target_dims = Vec::new();
535
536 let source_orig_dimension = source.module.dimension(t);
537 let source_dimension = source.dimension(t);
538
539 for op_deg in 1..=source.max_degree().unwrap() - t {
540 for op_idx in algebra.generators(op_deg) {
541 generators.push((op_deg, op_idx));
542 target_dims.push(source.module.dimension(t + op_deg));
543 }
544 }
545
546 if let Some(m) = &augmentation_map {
547 target_dims.push(m.target().dimension(t));
548 }
549
550 if let Some(m) = &preserve_map {
551 let dim = m.target().dimension(t - m.degree_shift());
552 target_dims.push(dim);
553 }
554
555 let total_dimension: usize = target_dims.iter().sum();
556 let mut matrix = AugmentedMatrix::new(
557 p,
558 source_dimension,
559 [
560 total_dimension + source_orig_dimension,
561 source_orig_dimension,
562 ],
563 );
564
565 for (row_idx, &i) in source.basis_list[t].iter().enumerate() {
566 let mut offset = 0;
567 let mut row = matrix.row_segment_mut(row_idx, 0, 0);
568
569 let mut cols = target_dims.iter().copied();
570 for (op_deg, op_idx) in &generators {
571 let len = cols.next().unwrap();
572 source.act_on_original_basis(
573 row.slice_mut(offset, offset + len),
574 1,
575 *op_deg,
576 *op_idx,
577 t,
578 i,
579 );
580 offset += len;
581 }
582
583 if let Some(m) = &augmentation_map {
584 let len = cols.next().unwrap();
585 m.apply_to_basis_element(row.slice_mut(offset, offset + len), 1, t, i);
586 offset += len;
587 }
588
589 if let Some(m) = &preserve_map {
590 let len = cols.next().unwrap();
591 m.apply_to_basis_element(row.slice_mut(offset, offset + len), 1, t, i);
592 offset += len;
593 }
594
595 let mut slice = row.slice_mut(offset, offset + source_orig_dimension);
596 slice.set_entry(i, 1);
597 if let Some(keep) = &keep {
598 keep.reduce(slice);
599 }
600
601 let mut row = matrix.row_segment_mut(row_idx, 1, 1);
602 row.set_entry(i, 1);
603 }
604 matrix.row_reduce();
605
606 let first_kernel_row = matrix.find_first_row_in_block(total_dimension);
607 let first_image_row = matrix.find_first_row_in_block(matrix.start[1]);
608 let image_rows = matrix.rows() - first_image_row;
609
610 let matrix = matrix.into_tail_segment(first_kernel_row, source_dimension, 1);
611
612 let mut image_matrix = Matrix::new(p, image_rows, source_orig_dimension);
613 for (mut target, source) in std::iter::zip(
614 image_matrix.iter_mut(),
615 matrix.iter().skip(first_image_row - first_kernel_row),
616 ) {
617 target.assign(source);
618 }
619 (matrix, image_matrix)
620}