Skip to main content

holos_tda/persistent_class_artifact/
wire.rs

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
12/// Encode the artifact payload followed by its SHA-256 digest.
13impl PersistentClassArtifact {
14    /// Encode canonical `HOLOSPC` version 1 bytes.
15    ///
16    /// Resource limits are checked for every call. Semantic fields were
17    /// validated when the private artifact was built.
18    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}