use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::arena::LineageId;
use crate::setun::Trit;
fn default_polarity() -> Trit {
Trit::True
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(transparent)]
pub struct BondId(pub u32);
impl BondId {
pub const NULL: Self = Self(u32::MAX);
#[inline]
pub fn is_valid(self) -> bool {
self != Self::NULL
}
#[inline]
pub fn index(self) -> usize {
self.0 as usize
}
}
impl From<u32> for BondId {
fn from(v: u32) -> Self {
Self(v)
}
}
impl From<usize> for BondId {
fn from(v: usize) -> Self {
Self(v as u32)
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct BondFlags: u32 {
const ACTIVE = 1 << 0;
const LEARNED = 1 << 1;
const BIDIRECTIONAL = 1 << 2;
const PROTECTED = 1 << 3;
}
}
impl Default for BondFlags {
fn default() -> Self {
Self::ACTIVE
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[repr(C)]
pub struct Bond {
pub source: LineageId,
pub target: LineageId,
pub strength: f32,
pub cost: f32,
pub decay_rate: f32,
pub last_access: u64,
pub flags: BondFlags,
#[serde(default = "default_polarity")]
pub polarity: Trit,
}
impl Default for Bond {
fn default() -> Self {
Self {
source: LineageId::NULL,
target: LineageId::NULL,
strength: 1.0,
cost: 0.1,
decay_rate: 0.0005,
last_access: now_nanos(),
flags: BondFlags::ACTIVE,
polarity: Trit::True,
}
}
}
impl Bond {
pub fn new(source: LineageId, target: LineageId, strength: f32) -> Self {
Self {
source,
target,
strength,
last_access: now_nanos(),
..Default::default()
}
}
pub fn learned(source: LineageId, target: LineageId, strength: f32) -> Self {
let mut bond = Self::new(source, target, strength);
bond.flags.insert(BondFlags::LEARNED);
bond
}
#[inline]
pub fn is_active(&self) -> bool {
self.flags.contains(BondFlags::ACTIVE)
}
#[inline]
pub fn is_learned(&self) -> bool {
self.flags.contains(BondFlags::LEARNED)
}
pub fn current_strength(&self) -> f32 {
if self.flags.contains(BondFlags::PROTECTED) {
return self.strength;
}
let elapsed_secs = elapsed_seconds(self.last_access);
self.strength * (-self.decay_rate * elapsed_secs).exp()
}
pub fn reinforce(&mut self, delta: f32) {
self.strength = (self.current_strength() + delta).clamp(0.0, 1.0);
self.last_access = now_nanos();
}
#[inline]
pub fn other(&self, from: LineageId) -> LineageId {
if self.source == from {
self.target
} else {
self.source
}
}
}
pub const BOND_PRUNE_THRESHOLD: f32 = 0.05;
pub struct BondGraph {
bonds: Vec<Bond>,
count: usize,
free_list: Vec<BondId>,
adjacency: Vec<Vec<BondId>>,
max_lineages: usize,
}
impl BondGraph {
pub fn with_capacity(max_lineages: usize, max_bonds: usize) -> Self {
Self {
bonds: Vec::with_capacity(max_bonds),
count: 0,
free_list: Vec::new(),
adjacency: vec![Vec::new(); max_lineages],
max_lineages,
}
}
#[inline]
pub fn len(&self) -> usize {
self.count
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
pub fn capacity(&self) -> usize {
self.bonds.capacity()
}
pub fn connect(&mut self, bond: Bond) -> Option<BondId> {
if !bond.source.is_valid() || !bond.target.is_valid() {
return None;
}
if bond.source.index() >= self.max_lineages || bond.target.index() >= self.max_lineages {
return None;
}
let id = if let Some(recycled) = self.free_list.pop() {
self.bonds[recycled.index()] = bond;
recycled
} else {
let id = BondId(self.bonds.len() as u32);
self.bonds.push(bond);
id
};
self.adjacency[bond.source.index()].push(id);
self.adjacency[bond.target.index()].push(id);
self.count += 1;
Some(id)
}
#[inline]
pub fn get(&self, id: BondId) -> Option<&Bond> {
self.bonds.get(id.index()).filter(|b| b.is_active())
}
#[inline]
pub fn get_mut(&mut self, id: BondId) -> Option<&mut Bond> {
self.bonds.get_mut(id.index()).filter(|b| b.is_active())
}
pub fn neighbors(&self, lineage: LineageId) -> &[BondId] {
if lineage.index() < self.adjacency.len() {
&self.adjacency[lineage.index()]
} else {
&[]
}
}
pub fn neighbors_with_strength(
&self,
lineage: LineageId,
) -> impl Iterator<Item = (LineageId, f32)> + '_ {
self.neighbors(lineage).iter().filter_map(move |&bond_id| {
let bond = self.get(bond_id)?;
let neighbor = bond.other(lineage);
Some((neighbor, bond.current_strength()))
})
}
pub fn disconnect(&mut self, id: BondId) -> bool {
if let Some(bond) = self.bonds.get_mut(id.index()) {
if bond.is_active() {
if let Some(adj) = self.adjacency.get_mut(bond.source.index()) {
adj.retain(|&bid| bid != id);
}
if let Some(adj) = self.adjacency.get_mut(bond.target.index()) {
adj.retain(|&bid| bid != id);
}
bond.flags.remove(BondFlags::ACTIVE);
self.free_list.push(id);
self.count -= 1;
return true;
}
}
false
}
pub fn find_bond(&self, a: LineageId, b: LineageId) -> Option<BondId> {
let a_neighbors = self.neighbors(a);
let b_neighbors = self.neighbors(b);
let (search_in, find_target) = if a_neighbors.len() <= b_neighbors.len() {
(a_neighbors, b)
} else {
(b_neighbors, a)
};
for &bond_id in search_in {
if let Some(bond) = self.get(bond_id) {
if bond.other(if find_target == b { a } else { b }) == find_target {
return Some(bond_id);
}
}
}
None
}
pub fn prune(&mut self, threshold: f32) -> usize {
let mut pruned = 0;
let to_prune: Vec<_> = self
.bonds
.iter()
.enumerate()
.filter_map(|(i, bond)| {
if bond.is_active() && bond.current_strength() < threshold {
Some(BondId(i as u32))
} else {
None
}
})
.collect();
for id in to_prune {
if self.disconnect(id) {
pruned += 1;
}
}
pruned
}
pub fn iter(&self) -> impl Iterator<Item = (BondId, &Bond)> {
self.bonds
.iter()
.enumerate()
.filter(|(_, b)| b.is_active())
.map(|(i, b)| (BondId(i as u32), b))
}
}
#[inline]
fn now_nanos() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0)
}
#[inline]
fn elapsed_seconds(timestamp: u64) -> f32 {
let now = now_nanos();
if now > timestamp {
((now - timestamp) as f64 / 1_000_000_000.0) as f32
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bond_default() {
let b = Bond::default();
assert_eq!(b.strength, 1.0);
assert!(b.is_active());
assert!(!b.is_learned());
}
#[test]
fn test_bond_learned() {
let b = Bond::learned(LineageId(0), LineageId(1), 0.5);
assert!(b.is_learned());
}
#[test]
fn test_bond_reinforce() {
let mut b = Bond::new(LineageId(0), LineageId(1), 0.5);
b.reinforce(0.2);
assert!(b.strength > 0.5);
}
#[test]
fn test_bond_graph_connect() {
let mut graph = BondGraph::with_capacity(100, 1000);
let bond = Bond::new(LineageId(0), LineageId(1), 0.8);
let id = graph.connect(bond).unwrap();
assert_eq!(graph.len(), 1);
assert!(graph.get(id).is_some());
}
#[test]
fn test_bond_graph_neighbors() {
let mut graph = BondGraph::with_capacity(100, 1000);
graph.connect(Bond::new(LineageId(0), LineageId(1), 0.8));
graph.connect(Bond::new(LineageId(0), LineageId(2), 0.6));
graph.connect(Bond::new(LineageId(1), LineageId(2), 0.4));
let n0: Vec<_> = graph.neighbors_with_strength(LineageId(0)).collect();
assert_eq!(n0.len(), 2);
let n2: Vec<_> = graph.neighbors_with_strength(LineageId(2)).collect();
assert_eq!(n2.len(), 2);
}
#[test]
fn test_bond_graph_find() {
let mut graph = BondGraph::with_capacity(100, 1000);
let id = graph
.connect(Bond::new(LineageId(5), LineageId(10), 0.7))
.unwrap();
assert_eq!(graph.find_bond(LineageId(5), LineageId(10)), Some(id));
assert_eq!(graph.find_bond(LineageId(10), LineageId(5)), Some(id)); assert_eq!(graph.find_bond(LineageId(0), LineageId(1)), None);
}
#[test]
fn test_bond_graph_disconnect() {
let mut graph = BondGraph::with_capacity(100, 1000);
let id = graph
.connect(Bond::new(LineageId(0), LineageId(1), 0.8))
.unwrap();
assert_eq!(graph.len(), 1);
assert!(graph.disconnect(id));
assert_eq!(graph.len(), 0);
assert!(graph.get(id).is_none());
}
}