1use sha2::{Digest, Sha256};
2
3use crate::certificate::{CertificateError, CertificateLimits};
4use crate::field::is_prime;
5use crate::{CriticalPair, PersistentClass, SparseDistanceMatrix};
6
7use super::{
8 F64_BITS_CODEC, MAGIC, PersistenceCycleTerm, PersistenceTriangleTerm, PersistentClassArtifact,
9 VERSION,
10};
11
12impl PersistentClassArtifact {
14 pub fn encode(&self, limits: CertificateLimits) -> Result<Vec<u8>, CertificateError> {
19 validate_encode_limits(self, limits)?;
20 let payload_len = encoded_payload_len(self)?;
21 validate_encoded_length(payload_len, limits)?;
22 let payload = encode_payload(self, payload_len)?;
23 debug_assert_eq!(payload.len(), payload_len);
24 let digest: [u8; 32] = Sha256::digest(&payload).into();
25 let mut output = payload;
26 output.extend_from_slice(&digest);
27 Ok(output)
28 }
29}
30
31fn validate_encode_limits(
32 artifact: &PersistentClassArtifact,
33 limits: CertificateLimits,
34) -> Result<(), CertificateError> {
35 validate_artifact_limits(&artifact.source, limits)?;
36 validate_total_terms(artifact, limits)?;
37 validate_chain_limit(artifact, limits)?;
38 super::build::count_triangles(
39 &artifact.source,
40 artifact.threshold.unwrap_or(f64::INFINITY),
41 limits.max_triangles,
42 )?;
43 Ok(())
44}
45
46fn validate_encoded_length(
47 payload_len: usize,
48 limits: CertificateLimits,
49) -> Result<(), CertificateError> {
50 let total = payload_len
51 .checked_add(32)
52 .ok_or_else(|| certificate_error("persistent class artifact byte count overflows"))?;
53 if total > limits.max_bytes {
54 return Err(certificate_error(
55 "persistent class artifact exceeds its byte limit",
56 ));
57 }
58 Ok(())
59}
60
61pub(super) fn encode_payload(
62 artifact: &PersistentClassArtifact,
63 capacity: usize,
64) -> Result<Vec<u8>, CertificateError> {
65 let mut output = Vec::with_capacity(capacity);
66 encode_header(&mut output, artifact)?;
67 encode_source(&mut output, artifact)?;
68 encode_threshold(&mut output, artifact.threshold);
69 encode_class_header(&mut output, artifact)?;
70 encode_cocycle(&mut output, &artifact.class)?;
71 encode_pair(&mut output, &artifact.critical_pair)?;
72 encode_cycle(&mut output, &artifact.cycle)?;
73 encode_chain(&mut output, &artifact.bounding_chain)?;
74 Ok(output)
75}
76
77fn encode_header(
78 output: &mut Vec<u8>,
79 artifact: &PersistentClassArtifact,
80) -> Result<(), CertificateError> {
81 output.extend_from_slice(MAGIC);
82 put_u16(output, VERSION);
83 output.push(F64_BITS_CODEC);
84 put_u32(output, artifact.class.cocycle.modulus);
85 put_u64(output, to_u64(artifact.source.len(), "vertex count")?);
86 put_u64(
87 output,
88 to_u64(artifact.source.num_edges(), "source edge count")?,
89 );
90 Ok(())
91}
92
93fn encode_source(
94 output: &mut Vec<u8>,
95 artifact: &PersistentClassArtifact,
96) -> Result<(), CertificateError> {
97 for (u, v, value) in artifact.source.edges() {
98 put_u64(output, to_u64(u, "source edge endpoint")?);
99 put_u64(output, to_u64(v, "source edge endpoint")?);
100 put_u64(output, value.to_bits());
101 }
102 Ok(())
103}
104
105fn encode_class_header(
106 output: &mut Vec<u8>,
107 artifact: &PersistentClassArtifact,
108) -> Result<(), CertificateError> {
109 output.extend_from_slice(artifact.class.group_id.as_bytes());
110 output.extend_from_slice(artifact.class.id.as_bytes());
111 put_u64(output, to_u64(artifact.class.basis_index, "basis index")?);
112 put_u64(output, artifact.class.interval.birth.to_bits());
113 put_u64(output, artifact.class.interval.death.to_bits());
114 put_u64(output, artifact.class.cocycle.scale.to_bits());
115 Ok(())
116}
117
118fn encoded_payload_len(artifact: &PersistentClassArtifact) -> Result<usize, CertificateError> {
119 let mut length = 8 + 2 + 1 + 4 + 8 + 8;
120 add_payload_component(&mut length, source_bytes(artifact))?;
121 length = checked_add(length, threshold_bytes(artifact.threshold))?;
122 add_payload_component(&mut length, class_bytes(artifact))?;
123 length = checked_add(length, pair_bytes(artifact))?;
124 add_payload_component(&mut length, cycle_bytes(artifact))?;
125 add_payload_component(&mut length, chain_bytes(artifact))?;
126 Ok(length)
127}
128
129fn add_payload_component(
130 length: &mut usize,
131 component: Result<usize, CertificateError>,
132) -> Result<(), CertificateError> {
133 let component = component?;
134 *length = checked_add(*length, component)?;
135 Ok(())
136}
137
138fn source_bytes(artifact: &PersistentClassArtifact) -> Result<usize, CertificateError> {
139 checked_mul(artifact.source.num_edges(), 24)
140}
141
142fn threshold_bytes(threshold: Option<f64>) -> usize {
143 threshold.map_or(1, |_| 9)
144}
145
146fn class_bytes(artifact: &PersistentClassArtifact) -> Result<usize, CertificateError> {
147 let mut length = 32 + 32 + 8 + 8 + 8 + 8;
148 length = checked_add(
149 length,
150 collection_with_count_bytes(artifact.class.cocycle.terms.len(), 20)?,
151 )?;
152 Ok(length)
153}
154
155fn pair_bytes(artifact: &PersistentClassArtifact) -> usize {
156 16 + 1 + artifact.critical_pair.death.as_ref().map_or(0, |_| 24)
157}
158
159fn cycle_bytes(artifact: &PersistentClassArtifact) -> Result<usize, CertificateError> {
160 collection_with_count_bytes(artifact.cycle.len(), 20)
161}
162
163fn chain_bytes(artifact: &PersistentClassArtifact) -> Result<usize, CertificateError> {
164 collection_with_count_bytes(artifact.bounding_chain.len(), 28)
165}
166
167fn collection_with_count_bytes(count: usize, width: usize) -> Result<usize, CertificateError> {
168 checked_add(8, checked_mul(count, width)?)
169}
170
171fn checked_mul(left: usize, right: usize) -> Result<usize, CertificateError> {
172 left.checked_mul(right)
173 .ok_or_else(|| certificate_error("persistent class artifact byte count overflows"))
174}
175
176fn checked_add(left: usize, right: usize) -> Result<usize, CertificateError> {
177 left.checked_add(right)
178 .ok_or_else(|| certificate_error("persistent class artifact byte count overflows"))
179}
180
181fn encode_threshold(output: &mut Vec<u8>, threshold: Option<f64>) {
182 match threshold {
183 None => output.push(0),
184 Some(value) => {
185 output.push(1);
186 put_u64(output, value.to_bits());
187 }
188 }
189}
190
191fn encode_cocycle(output: &mut Vec<u8>, class: &PersistentClass) -> Result<(), CertificateError> {
192 put_u64(
193 output,
194 to_u64(class.cocycle.terms.len(), "cocycle term count")?,
195 );
196 for term in &class.cocycle.terms {
197 put_u64(output, to_u64(term.u, "cocycle endpoint")?);
198 put_u64(output, to_u64(term.v, "cocycle endpoint")?);
199 put_u32(output, term.coefficient);
200 }
201 Ok(())
202}
203
204fn encode_pair(output: &mut Vec<u8>, pair: &CriticalPair) -> Result<(), CertificateError> {
205 let [u, v] = [pair.birth.vertices[0], pair.birth.vertices[1]];
206 put_u64(output, to_u64(u, "birth endpoint")?);
207 put_u64(output, to_u64(v, "birth endpoint")?);
208 match &pair.death {
209 None => output.push(0),
210 Some(death) => {
211 let [u, v, w] = [death.vertices[0], death.vertices[1], death.vertices[2]];
212 output.push(1);
213 put_u64(output, to_u64(u, "death vertex")?);
214 put_u64(output, to_u64(v, "death vertex")?);
215 put_u64(output, to_u64(w, "death vertex")?);
216 }
217 }
218 Ok(())
219}
220
221fn encode_cycle(
222 output: &mut Vec<u8>,
223 cycle: &[PersistenceCycleTerm],
224) -> Result<(), CertificateError> {
225 put_u64(output, to_u64(cycle.len(), "cycle term count")?);
226 for term in cycle {
227 put_u64(output, to_u64(term.u, "cycle endpoint")?);
228 put_u64(output, to_u64(term.v, "cycle endpoint")?);
229 put_u32(output, term.coefficient);
230 }
231 Ok(())
232}
233
234fn encode_chain(
235 output: &mut Vec<u8>,
236 chain: &[PersistenceTriangleTerm],
237) -> Result<(), CertificateError> {
238 put_u64(output, to_u64(chain.len(), "bounding-chain term count")?);
239 for term in chain {
240 for &vertex in &term.vertices {
241 put_u64(output, to_u64(vertex, "bounding-chain vertex")?);
242 }
243 put_u32(output, term.coefficient);
244 }
245 Ok(())
246}
247
248pub(super) fn validate_for_build(
249 artifact: &PersistentClassArtifact,
250 limits: CertificateLimits,
251) -> Result<(), CertificateError> {
252 validate_artifact_limits(&artifact.source, limits)?;
253 validate_total_terms(artifact, limits)?;
254 validate_chain_limit(artifact, limits)?;
255 validate_semantics(artifact)
256}
257
258fn validate_semantics(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
259 validate_threshold_option(artifact.threshold)?;
260 validate_source(&artifact.source)?;
261 validate_class(artifact)?;
262 artifact
263 .class
264 .validate_provenance(&artifact.source)
265 .map_err(|error| certificate_error(error.to_string()))?;
266 validate_pair(artifact)?;
267 validate_cycle(artifact)?;
268 validate_chain(artifact)
269}
270
271fn validate_artifact_limits(
272 source: &SparseDistanceMatrix,
273 limits: CertificateLimits,
274) -> Result<(), CertificateError> {
275 if source.len() > limits.max_vertices {
276 return Err(certificate_error(format!(
277 "{} source vertices exceed the limit {}",
278 source.len(),
279 limits.max_vertices
280 )));
281 }
282 if source.num_edges() > limits.max_edges {
283 return Err(certificate_error(format!(
284 "{} source edges exceed the limit {}",
285 source.num_edges(),
286 limits.max_edges
287 )));
288 }
289 if limits.max_dimension < 1 {
290 return Err(certificate_error(
291 "persistent H1 artifacts require a dimension limit of at least 1",
292 ));
293 }
294 if limits.max_bars == 0 {
295 return Err(certificate_error("persistent class bar exceeds its limit"));
296 }
297 Ok(())
298}
299
300fn validate_total_terms(
301 artifact: &PersistentClassArtifact,
302 limits: CertificateLimits,
303) -> Result<(), CertificateError> {
304 let cocycle = artifact.class.cocycle.terms.len();
305 let cycle = artifact.cycle.len();
306 let chain = artifact.bounding_chain.len();
307 let total_terms = cocycle
308 .checked_add(cycle)
309 .and_then(|count| count.checked_add(chain))
310 .ok_or_else(|| certificate_error("persistent class term count overflows"))?;
311 check_total_terms(total_terms, limits.max_terms, "persistent class")
312}
313
314fn validate_chain_limit(
315 artifact: &PersistentClassArtifact,
316 limits: CertificateLimits,
317) -> Result<(), CertificateError> {
318 if artifact.bounding_chain.len() > limits.max_triangles {
319 return Err(certificate_error(format!(
320 "{} bounding-chain triangles exceed the limit {}",
321 artifact.bounding_chain.len(),
322 limits.max_triangles
323 )));
324 }
325 Ok(())
326}
327
328fn validate_threshold_option(threshold: Option<f64>) -> Result<(), CertificateError> {
329 if let Some(value) = threshold {
330 validate_threshold(value)?;
331 }
332 Ok(())
333}
334
335fn validate_threshold(value: f64) -> Result<(), CertificateError> {
336 if value.is_nan() || value < 0.0 || is_negative_zero(value) {
337 return Err(certificate_error(
338 "persistent class threshold must be non-negative and not negative zero",
339 ));
340 }
341 Ok(())
342}
343
344fn validate_source(source: &SparseDistanceMatrix) -> Result<(), CertificateError> {
345 let mut previous = None;
346 for (u, v, value) in source.edges() {
347 if previous.is_some_and(|edge| edge >= (u, v)) {
348 return Err(certificate_error(
349 "source edges are not in strict endpoint order",
350 ));
351 }
352 if u >= v || v >= source.len() || !valid_weight(value) {
353 return Err(certificate_error("source edge is invalid"));
354 }
355 previous = Some((u, v));
356 }
357 Ok(())
358}
359
360fn validate_class(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
361 let class = &artifact.class;
362 validate_class_modulus(class.cocycle.modulus)?;
363 validate_class_interval(class)?;
364 validate_class_threshold(artifact)?;
365 validate_cocycle(artifact)
366}
367
368fn validate_class_modulus(modulus: u32) -> Result<(), CertificateError> {
369 if modulus == 0 {
370 return Err(certificate_error(
371 "persistent class modulus is not supported",
372 ));
373 }
374 if modulus >= 32_768 {
375 return Err(certificate_error(
376 "persistent class modulus is not supported",
377 ));
378 }
379 if !is_prime(u64::from(modulus)) {
380 return Err(certificate_error(
381 "persistent class modulus is not supported",
382 ));
383 }
384 Ok(())
385}
386
387fn validate_class_interval(class: &PersistentClass) -> Result<(), CertificateError> {
388 validate_interval_values(class.interval)?;
389 validate_class_scale(class)
390}
391
392fn validate_interval_values(interval: crate::Bar) -> Result<(), CertificateError> {
393 if interval.dim != 1 {
394 return Err(certificate_error(
395 "persistent class interval or scale is invalid",
396 ));
397 }
398 if !valid_finite_nonnegative(interval.birth) {
399 return Err(certificate_error(
400 "persistent class interval or scale is invalid",
401 ));
402 }
403 if !valid_death(interval.death) {
404 return Err(certificate_error(
405 "persistent class interval or scale is invalid",
406 ));
407 }
408 if interval.death.is_finite() && interval.death <= interval.birth {
409 return Err(certificate_error(
410 "persistent class interval or scale is invalid",
411 ));
412 }
413 Ok(())
414}
415
416fn validate_class_scale(class: &PersistentClass) -> Result<(), CertificateError> {
417 let scale = class.cocycle.scale;
418 if !valid_finite_nonnegative(scale) {
419 return Err(certificate_error(
420 "persistent class interval or scale is invalid",
421 ));
422 }
423 if scale < class.interval.birth {
424 return Err(certificate_error(
425 "persistent class interval or scale is invalid",
426 ));
427 }
428 if class.interval.death.is_finite() && scale >= class.interval.death {
429 return Err(certificate_error(
430 "persistent class interval or scale is invalid",
431 ));
432 }
433 Ok(())
434}
435
436fn validate_class_threshold(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
437 let Some(threshold) = artifact.threshold else {
438 return Ok(());
439 };
440 if artifact.class.interval.birth > threshold {
441 return Err(certificate_error(
442 "persistent class lies beyond its declared threshold",
443 ));
444 }
445 if artifact.class.cocycle.scale > threshold {
446 return Err(certificate_error(
447 "persistent class lies beyond its declared threshold",
448 ));
449 }
450 if artifact.class.interval.death.is_finite() && artifact.class.interval.death > threshold {
451 return Err(certificate_error(
452 "persistent class lies beyond its declared threshold",
453 ));
454 }
455 Ok(())
456}
457
458fn validate_cocycle(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
459 let cocycle = &artifact.class.cocycle;
460 validate_cocycle_header(cocycle)?;
461 let mut previous = None;
462 for term in &cocycle.terms {
463 validate_cocycle_term(artifact, term, previous)?;
464 previous = Some((term.u, term.v));
465 }
466 Ok(())
467}
468
469fn validate_cocycle_header(cocycle: &crate::Cocycle) -> Result<(), CertificateError> {
470 if cocycle.terms.is_empty() {
471 return Err(certificate_error(
472 "persistent class cocycle must start with coefficient one",
473 ));
474 }
475 if cocycle.terms[0].coefficient != 1 {
476 return Err(certificate_error(
477 "persistent class cocycle must start with coefficient one",
478 ));
479 }
480 Ok(())
481}
482
483fn validate_cocycle_term(
484 artifact: &PersistentClassArtifact,
485 term: &crate::CocycleTerm,
486 previous: Option<(usize, usize)>,
487) -> Result<(), CertificateError> {
488 if term.u >= term.v {
489 return Err(certificate_error(
490 "persistent class cocycle term is invalid",
491 ));
492 }
493 if term.v >= artifact.source.len() {
494 return Err(certificate_error(
495 "persistent class cocycle term is invalid",
496 ));
497 }
498 if term.coefficient == 0 {
499 return Err(certificate_error(
500 "persistent class cocycle term is invalid",
501 ));
502 }
503 if term.coefficient >= artifact.class.cocycle.modulus {
504 return Err(certificate_error(
505 "persistent class cocycle term is invalid",
506 ));
507 }
508 if previous.is_some_and(|edge| edge >= (term.u, term.v)) {
509 return Err(certificate_error(
510 "persistent class cocycle term is invalid",
511 ));
512 }
513 if artifact.source.get(term.u, term.v) > artifact.class.cocycle.scale {
514 return Err(certificate_error(
515 "persistent class cocycle term is invalid",
516 ));
517 }
518 Ok(())
519}
520
521fn validate_pair(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
522 validate_pair_birth(artifact)?;
523 validate_pair_death(artifact)
524}
525
526fn validate_pair_birth(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
527 let pair = &artifact.critical_pair;
528 let [birth_u, birth_v] = edge_vertices(&pair.birth, "birth edge")?;
529 if birth_v >= artifact.source.len() {
530 return Err(certificate_error(
531 "critical birth edge is outside the source",
532 ));
533 }
534 if artifact.source.get(birth_u, birth_v).to_bits() != pair.birth.value.to_bits() {
535 return Err(certificate_error(
536 "critical birth edge does not match the class",
537 ));
538 }
539 if pair.birth.value.to_bits() != artifact.class.interval.birth.to_bits() {
540 return Err(certificate_error(
541 "critical birth edge does not match the class",
542 ));
543 }
544 Ok(())
545}
546
547fn validate_pair_death(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
548 match (&artifact.critical_pair.death, artifact.class.interval.death) {
549 (None, death) if death.is_infinite() => Ok(()),
550 (Some(simplex), death) if death.is_finite() => {
551 validate_finite_pair_death(artifact, simplex, death)
552 }
553 _ => Err(certificate_error(
554 "critical pair essential status differs from the class",
555 )),
556 }
557}
558
559fn validate_finite_pair_death(
560 artifact: &PersistentClassArtifact,
561 simplex: &crate::CriticalSimplex,
562 death: f64,
563) -> Result<(), CertificateError> {
564 let [u, v, w] = triangle_vertices(simplex, "death triangle")?;
565 if w >= artifact.source.len() {
566 return Err(certificate_error(
567 "critical death triangle is outside the source",
568 ));
569 }
570 let value = artifact
571 .source
572 .get(u, v)
573 .max(artifact.source.get(u, w))
574 .max(artifact.source.get(v, w));
575 if value.to_bits() != simplex.value.to_bits() {
576 return Err(certificate_error(
577 "critical death triangle does not match the class",
578 ));
579 }
580 if value.to_bits() != death.to_bits() {
581 return Err(certificate_error(
582 "critical death triangle does not match the class",
583 ));
584 }
585 Ok(())
586}
587
588fn validate_cycle(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
589 let cycle = &artifact.cycle;
590 if cycle.is_empty() {
591 return Err(certificate_error("persistent class cycle has no terms"));
592 }
593 let mut previous = None;
594 let scale = artifact.critical_pair.birth.value;
595 for term in cycle {
596 validate_cycle_term(artifact, term, previous, scale)?;
597 previous = Some((term.u, term.v));
598 }
599 Ok(())
600}
601
602fn validate_cycle_term(
603 artifact: &PersistentClassArtifact,
604 term: &PersistenceCycleTerm,
605 previous: Option<(usize, usize)>,
606 scale: f64,
607) -> Result<(), CertificateError> {
608 if term.u >= term.v {
609 return Err(certificate_error("persistent class cycle term is invalid"));
610 }
611 if term.v >= artifact.source.len() {
612 return Err(certificate_error("persistent class cycle term is invalid"));
613 }
614 if term.coefficient == 0 {
615 return Err(certificate_error("persistent class cycle term is invalid"));
616 }
617 if term.coefficient >= artifact.class.cocycle.modulus {
618 return Err(certificate_error("persistent class cycle term is invalid"));
619 }
620 if previous.is_some_and(|edge| edge >= (term.u, term.v)) {
621 return Err(certificate_error("persistent class cycle term is invalid"));
622 }
623 if artifact.source.get(term.u, term.v) > scale {
624 return Err(certificate_error("persistent class cycle term is invalid"));
625 }
626 Ok(())
627}
628
629fn validate_chain(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
630 if artifact.critical_pair.death.is_none() {
631 return validate_essential_chain(artifact);
632 }
633 validate_finite_chain(artifact)
634}
635
636fn validate_essential_chain(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
637 if !artifact.bounding_chain.is_empty() {
638 return Err(certificate_error(
639 "essential persistent class cannot carry a bounding chain",
640 ));
641 }
642 Ok(())
643}
644
645fn validate_finite_chain(artifact: &PersistentClassArtifact) -> Result<(), CertificateError> {
646 if artifact.bounding_chain.is_empty() {
647 return Err(certificate_error(
648 "finite persistent class needs a bounding chain",
649 ));
650 }
651 let death = artifact
652 .critical_pair
653 .death
654 .as_ref()
655 .expect("finite critical pair has a death");
656 let death_scale = death.value;
657 let mut previous = None;
658 for term in &artifact.bounding_chain {
659 validate_chain_term(artifact, term, previous, death_scale)?;
660 previous = Some(term.vertices);
661 }
662 Ok(())
663}
664
665fn validate_chain_term(
666 artifact: &PersistentClassArtifact,
667 term: &PersistenceTriangleTerm,
668 previous: Option<[usize; 3]>,
669 death_scale: f64,
670) -> Result<(), CertificateError> {
671 validate_chain_term_shape(artifact, term, previous)?;
672 validate_chain_term_support(artifact, term, death_scale)
673}
674
675fn validate_chain_term_shape(
676 artifact: &PersistentClassArtifact,
677 term: &PersistenceTriangleTerm,
678 previous: Option<[usize; 3]>,
679) -> Result<(), CertificateError> {
680 let [u, v, w] = term.vertices;
681 if u >= v {
682 return Err(certificate_error(
683 "persistent class bounding-chain term is invalid",
684 ));
685 }
686 if v >= w {
687 return Err(certificate_error(
688 "persistent class bounding-chain term is invalid",
689 ));
690 }
691 if w >= artifact.source.len() {
692 return Err(certificate_error(
693 "persistent class bounding-chain term is invalid",
694 ));
695 }
696 if term.coefficient == 0 {
697 return Err(certificate_error(
698 "persistent class bounding-chain term is invalid",
699 ));
700 }
701 if term.coefficient >= artifact.class.cocycle.modulus {
702 return Err(certificate_error(
703 "persistent class bounding-chain term is invalid",
704 ));
705 }
706 if previous.is_some_and(|triangle| triangle >= term.vertices) {
707 return Err(certificate_error(
708 "persistent class bounding-chain term is invalid",
709 ));
710 }
711 Ok(())
712}
713
714fn validate_chain_term_support(
715 artifact: &PersistentClassArtifact,
716 term: &PersistenceTriangleTerm,
717 death_scale: f64,
718) -> Result<(), CertificateError> {
719 let [u, v, w] = term.vertices;
720 if artifact.source.get(u, v) > death_scale {
721 return Err(certificate_error(
722 "persistent class bounding-chain term is invalid",
723 ));
724 }
725 if artifact.source.get(u, w) > death_scale {
726 return Err(certificate_error(
727 "persistent class bounding-chain term is invalid",
728 ));
729 }
730 if artifact.source.get(v, w) > death_scale {
731 return Err(certificate_error(
732 "persistent class bounding-chain term is invalid",
733 ));
734 }
735 Ok(())
736}
737
738fn edge_vertices(
739 simplex: &crate::CriticalSimplex,
740 name: &str,
741) -> Result<[usize; 2], CertificateError> {
742 if simplex.vertices.len() != 2 || !simplex.vertices.windows(2).all(|pair| pair[0] < pair[1]) {
743 return Err(certificate_error(format!(
744 "{name} vertices are not canonical"
745 )));
746 }
747 Ok([simplex.vertices[0], simplex.vertices[1]])
748}
749
750fn triangle_vertices(
751 simplex: &crate::CriticalSimplex,
752 name: &str,
753) -> Result<[usize; 3], CertificateError> {
754 if simplex.vertices.len() != 3 || !simplex.vertices.windows(2).all(|pair| pair[0] < pair[1]) {
755 return Err(certificate_error(format!(
756 "{name} vertices are not canonical"
757 )));
758 }
759 Ok([
760 simplex.vertices[0],
761 simplex.vertices[1],
762 simplex.vertices[2],
763 ])
764}
765
766fn valid_weight(value: f64) -> bool {
767 value.is_finite() && value >= 0.0 && !is_negative_zero(value)
768}
769
770fn valid_finite_nonnegative(value: f64) -> bool {
771 valid_weight(value)
772}
773
774fn valid_death(value: f64) -> bool {
775 valid_weight(value) || (value.is_infinite() && value.is_sign_positive())
776}
777
778fn is_negative_zero(value: f64) -> bool {
779 value == 0.0 && value.to_bits() != 0
780}
781
782fn check_total_terms(count: usize, maximum: usize, kind: &str) -> Result<(), CertificateError> {
783 if count > maximum {
784 Err(certificate_error(format!(
785 "{count} {kind} terms exceed the limit {maximum}"
786 )))
787 } else {
788 Ok(())
789 }
790}
791
792fn to_u64(value: usize, name: &str) -> Result<u64, CertificateError> {
793 u64::try_from(value).map_err(|_| certificate_error(format!("{name} does not fit in u64")))
794}
795
796fn put_u16(output: &mut Vec<u8>, value: u16) {
797 output.extend_from_slice(&value.to_be_bytes());
798}
799
800fn put_u32(output: &mut Vec<u8>, value: u32) {
801 output.extend_from_slice(&value.to_be_bytes());
802}
803
804fn put_u64(output: &mut Vec<u8>, value: u64) {
805 output.extend_from_slice(&value.to_be_bytes());
806}
807
808fn certificate_error(message: impl Into<String>) -> CertificateError {
809 CertificateError::new(message)
810}