use panproto_inst::WInstance;
use crate::Lens;
use crate::asymmetric::{Complement, get, put};
use crate::error::LensError;
pub trait Fibration {
type Fiber;
type BaseMorphism;
type Error;
fn cartesian_lift(
&self,
morphism: &Self::BaseMorphism,
view: &Self::Fiber,
complement: &Complement,
) -> Result<Self::Fiber, Self::Error>;
fn opcartesian_lift(
&self,
morphism: &Self::BaseMorphism,
source: &Self::Fiber,
) -> Result<(Self::Fiber, Complement), Self::Error>;
}
pub struct WTypeFibration;
impl Fibration for WTypeFibration {
type Fiber = WInstance;
type BaseMorphism = Lens;
type Error = LensError;
fn cartesian_lift(
&self,
morphism: &Self::BaseMorphism,
view: &Self::Fiber,
complement: &Complement,
) -> Result<Self::Fiber, Self::Error> {
put(morphism, view, complement)
}
fn opcartesian_lift(
&self,
morphism: &Self::BaseMorphism,
source: &Self::Fiber,
) -> Result<(Self::Fiber, Complement), Self::Error> {
get(morphism, source)
}
}
pub fn verify_cartesian_universal(
lens: &Lens,
instance: &WInstance,
) -> Result<(), CartesianViolation> {
let fib = WTypeFibration;
let (view, complement) =
fib.opcartesian_lift(lens, instance)
.map_err(|e| CartesianViolation {
law: "opcartesian_lift (get)",
detail: format!("{e}"),
})?;
let restored =
fib.cartesian_lift(lens, &view, &complement)
.map_err(|e| CartesianViolation {
law: "cartesian_lift (put)",
detail: format!("{e}"),
})?;
if !crate::laws::instances_equivalent(instance, &restored) {
return Err(CartesianViolation {
law: "PutGet (structural equivalence)",
detail: format!(
"original {} nodes/{} arcs, restored {} nodes/{} arcs",
instance.node_count(),
instance.arc_count(),
restored.node_count(),
restored.arc_count()
),
});
}
let (view2, _) = fib
.opcartesian_lift(lens, &restored)
.map_err(|e| CartesianViolation {
law: "GetPut (get after put)",
detail: format!("{e}"),
})?;
if !crate::laws::instances_equivalent(&view, &view2) {
return Err(CartesianViolation {
law: "GetPut (view structural equivalence)",
detail: format!(
"original view {} nodes/{} arcs, round-tripped view {} nodes/{} arcs",
view.node_count(),
view.arc_count(),
view2.node_count(),
view2.arc_count()
),
});
}
Ok(())
}
pub fn verify_cartesian_factorization(
f: &Lens,
h: &Lens,
source_instance: &WInstance,
) -> Result<(), CartesianViolation> {
use crate::compose::compose;
let composite = compose(h, f).map_err(|e| CartesianViolation {
law: "compose(h, f)",
detail: format!("{e}"),
})?;
let (view_via_composite, c_composite) =
get(&composite, source_instance).map_err(|e| CartesianViolation {
law: "get(f∘h, source)",
detail: format!("{e}"),
})?;
let (intermediate, c_h) = get(h, source_instance).map_err(|e| CartesianViolation {
law: "get(h, source)",
detail: format!("{e}"),
})?;
let (view_via_chain, c_f) = get(f, &intermediate).map_err(|e| CartesianViolation {
law: "get(f, get(h, source))",
detail: format!("{e}"),
})?;
if !crate::laws::instances_equivalent(&view_via_composite, &view_via_chain) {
return Err(CartesianViolation {
law: "Cartesian universal factorization: get(f∘h) ≠ get(f) ∘ get(h)",
detail: format!(
"composite get yielded {} nodes / {} arcs, chained get yielded {} / {}",
view_via_composite.node_count(),
view_via_composite.arc_count(),
view_via_chain.node_count(),
view_via_chain.arc_count(),
),
});
}
let restored_via_composite =
put(&composite, &view_via_composite, &c_composite).map_err(|e| CartesianViolation {
law: "put(f∘h, view, c_composite)",
detail: format!("{e}"),
})?;
let restored_intermediate = put(f, &view_via_chain, &c_f).map_err(|e| CartesianViolation {
law: "put(f, view, c_f)",
detail: format!("{e}"),
})?;
let restored_via_chain =
put(h, &restored_intermediate, &c_h).map_err(|e| CartesianViolation {
law: "put(h, put(f, view, c_f), c_h)",
detail: format!("{e}"),
})?;
if !crate::laws::instances_equivalent(&restored_via_composite, &restored_via_chain) {
return Err(CartesianViolation {
law: "Cartesian universal factorization: put(f∘h) ≠ put(h) ∘ put(f)",
detail: format!(
"composite put yielded {} nodes / {} arcs, chained put yielded {} / {}",
restored_via_composite.node_count(),
restored_via_composite.arc_count(),
restored_via_chain.node_count(),
restored_via_chain.arc_count(),
),
});
}
Ok(())
}
#[derive(Debug)]
pub struct CartesianViolation {
pub law: &'static str,
pub detail: String,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::tests::{identity_lens, projection_lens, three_node_instance, three_node_schema};
#[test]
fn identity_lens_cartesian_universal() {
let schema = three_node_schema();
let lens = identity_lens(&schema);
let instance = three_node_instance();
let result = verify_cartesian_universal(&lens, &instance);
assert!(
result.is_ok(),
"identity lens should satisfy cartesian universal property: {result:?}"
);
}
#[test]
fn projection_lens_cartesian_universal() {
let schema = three_node_schema();
let lens = projection_lens(&schema, "text");
let instance = three_node_instance();
let result = verify_cartesian_universal(&lens, &instance);
assert!(
result.is_ok(),
"projection lens should satisfy cartesian universal property: {result:?}"
);
}
#[test]
fn wtype_fibration_opcartesian_then_cartesian() {
let schema = three_node_schema();
let lens = identity_lens(&schema);
let instance = three_node_instance();
let fib = WTypeFibration;
let (view, complement) = fib.opcartesian_lift(&lens, &instance).unwrap();
let restored = fib.cartesian_lift(&lens, &view, &complement).unwrap();
assert_eq!(restored.node_count(), instance.node_count());
}
#[test]
fn cartesian_factorization_holds_for_identity_chain() {
let schema = three_node_schema();
let h = identity_lens(&schema);
let f = identity_lens(&schema);
let instance = three_node_instance();
verify_cartesian_factorization(&f, &h, &instance).expect("identity chain must factor");
}
#[test]
fn cartesian_factorization_holds_for_projection_chain() {
let schema = three_node_schema();
let h = identity_lens(&schema);
let f = projection_lens(&schema, "text");
let instance = three_node_instance();
verify_cartesian_factorization(&f, &h, &instance)
.expect("identity-then-projection must factor");
}
}