agent-tk 0.4.0

`agent-tk` (`agent toolkit/tasks-knowledge`) is a crate for the development of autonomous agents using Rust, with an emphasis on tasks and knowledge based agents. This project is part of the [auKsys](http://auksys.org) project.
Documentation
#![allow(dead_code)]

use std::collections::HashMap;

#[derive(Clone, Copy, PartialEq)]
pub(crate) enum Value
{
  Null,
  Integer(usize),
  Float(f64),
  Average
  {
    current: f64,
    count: f64,
  },
}

impl From<f64> for Value
{
  fn from(value: f64) -> Self
  {
    Value::Float(value)
  }
}

impl From<usize> for Value
{
  fn from(value: usize) -> Self
  {
    Value::Integer(value)
  }
}

impl std::ops::Add for Value
{
  type Output = Value;
  fn add(self, rhs: Self) -> Self::Output
  {
    match self
    {
      Value::Null => rhs,
      Value::Integer(i) => match rhs
      {
        Value::Null => self,
        Value::Integer(ir) => (i + ir).into(),
        Value::Float(fr) => (i as f64 + fr).into(),
        Value::Average { .. } => Value::Null,
      },
      Value::Float(f) => match rhs
      {
        Value::Null => self,
        Value::Integer(ir) => (f + ir as f64).into(),
        Value::Float(fr) => (f + fr).into(),
        Value::Average { .. } => Value::Null,
      },
      Value::Average { .. } => Value::Null,
    }
  }
}

impl std::fmt::Display for Value
{
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
  {
    match self
    {
      Value::Null => 0.fmt(f),
      Value::Integer(i) => i.fmt(f),
      Value::Float(fl) => fl.fmt(f),
      Value::Average { current, .. } => current.fmt(f),
    }
  }
}

impl PartialOrd for Value
{
  fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>
  {
    match self
    {
      Value::Null => match other
      {
        Value::Null => Some(std::cmp::Ordering::Equal),
        Value::Integer(ir) => 0_usize.partial_cmp(ir),
        Value::Float(fr) => 0.0_f64.partial_cmp(fr),
        Value::Average { current, .. } => 0.0_f64.partial_cmp(current),
      },
      Value::Integer(i) => match other
      {
        Value::Null => i.partial_cmp(&0),
        Value::Integer(ir) => i.partial_cmp(ir),
        Value::Float(fr) => (*i as f64).partial_cmp(fr),
        Value::Average { current, .. } => (*i as f64).partial_cmp(current),
      },
      Value::Float(f) => match other
      {
        Value::Null => f.partial_cmp(&0.0),
        Value::Integer(ir) => f.partial_cmp(&(*ir as f64)),
        Value::Float(fr) => f.partial_cmp(fr),
        Value::Average { current, .. } => f.partial_cmp(current),
      },
      Value::Average { current, .. } => match other
      {
        Value::Null => current.partial_cmp(&0.0),
        Value::Integer(ir) => current.partial_cmp(&(*ir as f64)),
        Value::Float(fr) => current.partial_cmp(fr),
        Value::Average {
          current: rcurrent, ..
        } => current.partial_cmp(rcurrent),
      },
    }
  }
}

impl Value
{
  fn incremented(&self) -> Value
  {
    match self
    {
      Value::Null => 1.into(),
      Value::Integer(i) => (i + 1).into(),
      Value::Float(f) => (f + 1.0).into(),
      Value::Average { current, .. } => (current + 1.0).into(),
    }
  }
}

#[derive(Clone, Default)]
pub(crate) struct Statistics
{
  name: String,
  statistics: ccutils::futures::ArcMutex<HashMap<String, Value>>,
}

trait HashMapExtension
{
  fn get_value(&self, key: impl Into<String>) -> Value;
}

impl HashMapExtension for HashMap<String, Value>
{
  fn get_value(&self, key: impl Into<String>) -> Value
  {
    *self.get(&key.into()).unwrap_or(&Value::Null)
  }
}

static STATISTICS: std::sync::LazyLock<std::sync::Mutex<HashMap<String, Statistics>>> =
  std::sync::LazyLock::new(|| std::sync::Mutex::new(HashMap::<String, Statistics>::new()));

impl Statistics
{
  pub(crate) fn get(name: impl Into<String>) -> Statistics
  {
    let mut stats = STATISTICS.lock().unwrap();
    let name = name.into();
    match stats.get(&name)
    {
      Some(st) => st.clone(),
      None =>
      {
        let st = Statistics {
          name: name.clone(),
          ..Default::default()
        };
        stats.insert(name, st.clone());
        st
      }
    }
  }
  pub(crate) fn increment(&self, key: impl Into<String>)
  {
    futures::executor::block_on(self.increment_async(key));
  }
  pub(crate) async fn increment_async(&self, key: impl Into<String>)
  {
    let mut statistics = self.statistics.lock().await;
    let key = key.into();
    let count = statistics.get_value(&key);
    statistics.insert(key, count.incremented());
  }
  pub(crate) async fn set_async(&self, key: impl Into<String>, value: Value)
  {
    let mut statistics = self.statistics.lock().await;
    statistics.insert(key.into(), value);
  }
  pub(crate) async fn update_average_async(&self, key: impl Into<String>, value: f64)
  {
    let mut statistics = self.statistics.lock().await;
    let key = key.into();
    let avg = match statistics.get_value(&key)
    {
      Value::Average { current, count } => Value::Average {
        current: (current * count + value) / (count + 1.0),
        count: count + 1.0,
      },
      _ => Value::Average {
        current: value,
        count: 1.0,
      },
    };
    statistics.insert(key, avg);
  }
  pub(crate) async fn display_async(
    &self,
    key: impl Into<String>,
    key_ref: impl Into<String>,
    delta: Value,
    keys: impl IntoIterator<Item = &str>,
  )
  {
    let mut statistics = self.statistics.lock().await;
    let key_ref = key_ref.into();
    let value = statistics.get_value(key.into());
    let value_ref = statistics.get_value(&key_ref);
    if value > value_ref + delta
    {
      statistics.insert(key_ref, value);
      let mut r = String::new();
      for it in keys.into_iter()
      {
        r += format!("{} {} ", it, statistics.get_value(it)).as_str();
      }
      log::info!("{} {}", self.name, r);
    }
  }
}