use kcode_k1_transaction_id::TxId;
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, error::Error as StdError, fmt};
pub const FORMAT_VERSION: u8 = 1;
pub const EWA_HALF_LIFE_MASS: f64 = 30.0;
pub const PRUNE_EPSILON: f64 = 0.01;
pub const MAX_NAVIGATION_CONNECTIONS: usize = 12;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KmapError(pub String);
impl fmt::Display for KmapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl StdError for KmapError {}
pub type Result<T> = std::result::Result<T, KmapError>;
fn error(message: &str) -> KmapError {
KmapError(message.to_owned())
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct NodeId(pub [u8; 12]);
impl From<TxId> for NodeId {
fn from(value: TxId) -> Self {
Self(*value.as_bytes())
}
}
impl From<NodeId> for TxId {
fn from(value: NodeId) -> Self {
Self::from_bytes(value.0)
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ConnectionTier {
Navigation,
Automated,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct Weight {
pub value: f64,
pub mass: f64,
}
impl Weight {
pub fn new(value: f64, mass: f64) -> Result<Self> {
validate_value_mass(value, mass)?;
Ok(Self { value, mass })
}
pub const fn initial() -> Self {
Self {
value: 1.0,
mass: 3.0,
}
}
pub fn update(self, value: f64, mass: f64) -> Result<Option<Self>> {
validate_value_mass(self.value, self.mass)?;
validate_value_mass(value, mass)?;
let aged_mass = self.mass * (-mass / EWA_HALF_LIFE_MASS).exp2();
let new_mass = aged_mass + mass;
if !new_mass.is_finite() || new_mass <= 0.0 {
return Err(error("updated mass is not representable"));
}
let new_value = (value - self.value)
.mul_add(mass / new_mass, self.value)
.clamp(0.0, 1.0);
let updated = Self::new(new_value, new_mass)?;
Ok((updated.value >= PRUNE_EPSILON).then_some(updated))
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Connection {
pub target: NodeId,
pub tier: ConnectionTier,
pub weight: Weight,
}
impl Connection {
pub fn new(target: NodeId, tier: ConnectionTier) -> Self {
Self {
target,
tier,
weight: Weight::initial(),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConnectionSpec {
pub target: NodeId,
pub tier: ConnectionTier,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ConnectionChange {
Set(ConnectionSpec),
Remove(NodeId),
}
impl ConnectionChange {
fn target(&self) -> NodeId {
match self {
Self::Set(spec) => spec.target,
Self::Remove(target) => *target,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ConnectionMeasurement {
pub source: NodeId,
pub target: NodeId,
pub value: f64,
pub mass: f64,
}
impl ConnectionMeasurement {
pub fn new(source: NodeId, target: NodeId, value: f64, mass: f64) -> Result<Self> {
validate_value_mass(value, mass)?;
Ok(Self {
source,
target,
value,
mass,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MeasurementOutcome {
Updated,
Pruned,
Absent,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct Node {
pub title: String,
pub navigation_hint: String,
pub narrative: String,
pub connections: Vec<Connection>,
}
impl Node {
pub fn new(
title: impl Into<String>,
navigation_hint: impl Into<String>,
narrative: impl Into<String>,
connections: Vec<Connection>,
) -> Result<Self> {
let node = Self {
title: title.into(),
navigation_hint: navigation_hint.into(),
narrative: narrative.into(),
connections,
};
node.validate()?;
Ok(node)
}
pub fn from_specs(
title: impl Into<String>,
navigation_hint: impl Into<String>,
narrative: impl Into<String>,
specs: Vec<ConnectionSpec>,
) -> Result<Self> {
validate_specs(&specs)?;
Self::new(
title,
navigation_hint,
narrative,
specs
.into_iter()
.map(|spec| Connection::new(spec.target, spec.tier))
.collect(),
)
}
pub fn validate(&self) -> Result<()> {
ensure_unique(self.connections.iter().map(|connection| connection.target))?;
for connection in &self.connections {
validate_value_mass(connection.weight.value, connection.weight.mass)?;
}
validate_navigation(self.connections.iter().map(|connection| connection.tier))
}
pub fn apply_connection_changes(&mut self, changes: &[ConnectionChange]) -> Result<()> {
ensure_unique(changes.iter().map(ConnectionChange::target))?;
let mut connections = self.connections.clone();
for change in changes {
match change {
ConnectionChange::Set(spec) => {
if let Some(connection) = connections
.iter_mut()
.find(|connection| connection.target == spec.target)
{
connection.tier = spec.tier;
} else {
connections.push(Connection::new(spec.target, spec.tier));
}
}
ConnectionChange::Remove(target) => {
connections.retain(|connection| connection.target != *target);
}
}
}
let replacement = Self::new(
self.title.clone(),
self.navigation_hint.clone(),
self.narrative.clone(),
connections,
)?;
self.connections = replacement.connections;
Ok(())
}
pub fn apply_measurement(
&mut self,
target: NodeId,
value: f64,
mass: f64,
) -> Result<MeasurementOutcome> {
validate_value_mass(value, mass)?;
let Some(index) = self
.connections
.iter()
.position(|connection| connection.target == target)
else {
return Ok(MeasurementOutcome::Absent);
};
match self.connections[index].weight.update(value, mass)? {
Some(weight) => {
self.connections[index].weight = weight;
Ok(MeasurementOutcome::Updated)
}
None => {
self.connections.remove(index);
Ok(MeasurementOutcome::Pruned)
}
}
}
pub fn encode(&self) -> Result<Vec<u8>> {
self.validate()?;
encode(self)
}
pub fn decode(bytes: &[u8]) -> Result<Self> {
let value: Self = decode(bytes)?;
value.validate()?;
Ok(value)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum KmapAction {
CreateNode {
title: String,
navigation_hint: String,
narrative: String,
connections: Vec<ConnectionSpec>,
},
UpdateNode {
node_id: NodeId,
title: Option<String>,
navigation_hint: Option<String>,
narrative: Option<String>,
connection_changes: Vec<ConnectionChange>,
},
ApplyMeasurements {
measurements: Vec<ConnectionMeasurement>,
},
}
impl KmapAction {
pub fn create_node(
title: impl Into<String>,
navigation_hint: impl Into<String>,
narrative: impl Into<String>,
connections: Vec<ConnectionSpec>,
) -> Result<Self> {
let value = Self::CreateNode {
title: title.into(),
navigation_hint: navigation_hint.into(),
narrative: narrative.into(),
connections,
};
value.validate()?;
Ok(value)
}
pub fn update_node(
node_id: NodeId,
title: Option<String>,
navigation_hint: Option<String>,
narrative: Option<String>,
connection_changes: Vec<ConnectionChange>,
) -> Result<Self> {
let value = Self::UpdateNode {
node_id,
title,
navigation_hint,
narrative,
connection_changes,
};
value.validate()?;
Ok(value)
}
pub fn apply_measurements(measurements: Vec<ConnectionMeasurement>) -> Result<Self> {
let value = Self::ApplyMeasurements { measurements };
value.validate()?;
Ok(value)
}
pub fn validate(&self) -> Result<()> {
match self {
Self::CreateNode { connections, .. } => validate_specs(connections),
Self::UpdateNode {
connection_changes, ..
} => ensure_unique(connection_changes.iter().map(ConnectionChange::target)),
Self::ApplyMeasurements { measurements } => {
measurements.iter().try_for_each(|measurement| {
validate_value_mass(measurement.value, measurement.mass)
})
}
}
}
pub fn encode(&self) -> Result<Vec<u8>> {
self.validate()?;
encode(self)
}
pub fn decode(bytes: &[u8]) -> Result<Self> {
let value: Self = decode(bytes)?;
value.validate()?;
Ok(value)
}
}
fn validate_value_mass(value: f64, mass: f64) -> Result<()> {
if !value.is_finite() || !(0.0..=1.0).contains(&value) {
return Err(error("value must be finite and in [0, 1]"));
}
if !mass.is_finite() || mass <= 0.0 {
return Err(error("mass must be finite and positive"));
}
Ok(())
}
fn validate_specs(specs: &[ConnectionSpec]) -> Result<()> {
ensure_unique(specs.iter().map(|spec| spec.target))?;
validate_navigation(specs.iter().map(|spec| spec.tier))
}
fn ensure_unique(targets: impl IntoIterator<Item = NodeId>) -> Result<()> {
let mut seen = HashSet::new();
if targets.into_iter().all(|target| seen.insert(target)) {
Ok(())
} else {
Err(error("duplicate connection target"))
}
}
fn validate_navigation(tiers: impl IntoIterator<Item = ConnectionTier>) -> Result<()> {
if tiers
.into_iter()
.filter(|tier| *tier == ConnectionTier::Navigation)
.count()
> MAX_NAVIGATION_CONNECTIONS
{
Err(error("too many Navigation connections"))
} else {
Ok(())
}
}
fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
let mut bytes = vec![FORMAT_VERSION];
bytes.extend(postcard::to_stdvec(value).map_err(|_| error("wire encoding failed"))?);
Ok(bytes)
}
fn decode<T: for<'a> Deserialize<'a>>(bytes: &[u8]) -> Result<T> {
let Some((&version, payload)) = bytes.split_first() else {
return Err(error("empty wire value"));
};
if version != FORMAT_VERSION {
return Err(error("unsupported wire version"));
}
let (value, trailing) =
postcard::take_from_bytes(payload).map_err(|_| error("malformed wire payload"))?;
if trailing.is_empty() {
Ok(value)
} else {
Err(error("trailing wire bytes"))
}
}