use super::Bag;
use crate::{
control::prepare_term_link_templates,
entity::{BudgetValue, Concept, Item, RCTask},
inference::{Budget, BudgetFunctions},
language::Term,
parameters::{Parameters, DEFAULT_PARAMETERS},
};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Memory {
#[serde(deserialize_with = "Memory::deserialize_concepts")]
concepts: Bag<Concept>,
pub(crate) parameters: Parameters,
}
impl Memory {
pub fn concept_forgetting_rate(&self) -> usize {
self.parameters.concept_forgetting_cycle
}
#[doc(alias = "belief_forgetting_rate")]
pub fn term_link_forgetting_rate(&self) -> usize {
self.parameters.term_link_forgetting_cycle
}
#[doc(alias = "task_forgetting_rate")]
pub fn task_link_forgetting_rate(&self) -> usize {
self.parameters.task_link_forgetting_cycle
}
pub fn new(parameters: Parameters) -> Self {
Self {
concepts: Bag::new(
parameters.concept_bag_size,
parameters.concept_forgetting_cycle,
),
parameters,
}
}
pub fn init(&mut self) {
self.concepts.init();
}
#[doc(alias = "name_to_concept")]
pub fn key_to_concept(&self, key: &str) -> Option<&Concept> {
self.concepts.get(key)
}
#[doc(alias = "name_to_concept_mut")]
pub fn key_to_concept_mut(&mut self, key: &str) -> Option<&mut Concept> {
self.concepts.get_mut(key)
}
pub fn term_to_key(term: &Term) -> String {
term.name()
}
pub fn term_to_concept(&self, term: &Term) -> Option<&Concept> {
self.key_to_concept(&Self::term_to_key(term))
}
pub fn term_to_concept_mut(&mut self, term: &Term) -> Option<&mut Concept> {
self.key_to_concept_mut(&Self::term_to_key(term))
}
pub fn has_concept(&self, term: &Term) -> bool {
self.concepts.has(&Self::term_to_key(term))
}
pub fn get_concept_or_create(&mut self, term: &Term) -> Option<&mut Concept> {
if !term.is_constant() {
return None;
}
let has_concept = self.has_concept(term);
match has_concept {
true => self.term_to_concept_mut(term),
false => self.make_new_concept(term),
}
}
fn make_new_concept(&mut self, term: &Term) -> Option<&mut Concept> {
let concept = Concept::new(
term.clone(),
(&self.parameters).into(),
self.concept_initial_budget(),
prepare_term_link_templates(term),
);
let new_key = concept.key().clone();
let old_concept = self.concepts.put_in(concept);
let make_success = match old_concept {
None => true,
Some(old) => old.key() != &new_key,
};
match make_success {
true => self.key_to_concept_mut(&new_key),
false => None,
}
}
fn concept_initial_budget(&self) -> BudgetValue {
BudgetValue::from_floats(
self.parameters.concept_initial_priority,
self.parameters.concept_initial_durability,
self.parameters.concept_initial_quality,
)
}
#[must_use]
pub fn activate_concept_calculate(
&self,
concept: &Concept,
incoming_budget: &impl Budget,
) -> BudgetValue {
let mut activated = incoming_budget.activate_to_concept(concept);
match self.has_concept(concept.term()) {
true => activated,
false => {
self.concepts.forget(&mut activated);
activated
}
}
}
pub fn activate_concept_apply(concept: &mut impl Budget, new_budget: BudgetValue) {
concept.copy_budget_from(&new_budget);
}
pub fn take_out_concept(&mut self) -> Option<Concept> {
self.concepts.take_out()
}
pub fn pick_out_concept(&mut self, key: &str) -> Option<Concept> {
self.concepts.pick_out(key)
}
pub fn put_back_concept(&mut self, concept: Concept) -> Option<Concept> {
self.concepts.put_back(concept)
}
pub fn iter_concepts(&self) -> impl Iterator<Item = &Concept> {
self.concepts.iter()
}
}
impl Default for Memory {
fn default() -> Self {
Self::new(DEFAULT_PARAMETERS)
}
}
impl Memory {
fn deserialize_concepts<'de, D>(deserializer: D) -> Result<Bag<Concept>, D::Error>
where
D: Deserializer<'de>,
{
let mut bag = Bag::<Concept>::deserialize(deserializer)?;
let all_task_rcs = Self::concept_bag_all_task_rcs(&mut bag);
RCTask::unify_rcs(all_task_rcs);
Ok(bag)
}
fn concept_bag_all_task_rcs(bag: &mut Bag<Concept>) -> impl Iterator<Item = &mut RCTask> {
bag.iter_mut().flat_map(Concept::iter_tasks_mut)
}
pub(crate) fn all_task_rcs(&mut self) -> impl Iterator<Item = &mut RCTask> {
Self::concept_bag_all_task_rcs(&mut self.concepts)
}
}
#[cfg(test)]
pub mod tests_memory {
use super::*;
use crate::{
assert_eq_try,
entity::*,
ok,
parameters::DEFAULT_PARAMETERS,
test_term as term,
util::{AResult, ToDisplayAndBrief},
};
use nar_dev_utils::*;
pub fn memory_synced(memory: &impl GetMemory) {
memory
.get_memory()
.iter_concepts()
.flat_map(Concept::iter_tasks)
.for_each(rc_synced)
}
pub fn rc_synced(rc: &RCTask) {
assert!(
rc.is_synced_serial(),
"共享指针不同步:{} != {}",
rc.serial_(),
rc.inner_serial_()
)
}
pub fn zip<'t, T: 't, I1, I2>(a: I1, b: I2) -> impl Iterator<Item = (T, T)>
where
I1: IntoIterator<Item = T> + 't,
I2: IntoIterator<Item = T> + 't,
{
a.into_iter().zip(b)
}
pub trait GetMemory {
fn get_memory(&self) -> &Memory;
}
impl GetMemory for Memory {
fn get_memory(&self) -> &Memory {
self
}
}
pub fn memory_consistent<M1: GetMemory, M2: GetMemory>(old: &M1, new: &M2) -> AResult {
let [old, new] = [old.get_memory(), new.get_memory()];
assert_eq_try!(
&old.parameters,
&new.parameters,
"记忆区不一致——超参数不一致"
);
bag_consistent(&old.concepts, &new.concepts, concept_consistent)?;
ok!()
}
pub fn bag_consistent<T: Item>(
old: &Bag<T>,
new: &Bag<T>,
consistent_t: impl Fn(&T, &T) -> AResult,
) -> AResult {
fn sorted_items<T: Item>(m: &Bag<T>) -> Vec<&T> {
manipulate! {
m.iter().collect::<Vec<_>>()
=> .sort_by_key(|&t| t.key())
}
}
let [items_old, items_new] = f_parallel![sorted_items; old; new];
assert_eq_try!(items_old.len(), items_new.len(), "袋不一致——内容数量不相等");
for (item_old, item_new) in zip(items_old, items_new) {
consistent_t(item_old, item_new)?;
}
ok!()
}
pub fn concept_consistent(concept_old: &Concept, concept_new: &Concept) -> AResult {
let term = Concept::term;
let [term_old, term_new] = f_parallel![term; concept_old; concept_new];
assert_eq_try!(term_old, term_new);
let term = term_old;
assert_eq_try!(
BudgetValue::from(concept_old),
BudgetValue::from(concept_new),
"概念'{term}'的预算值不一致"
);
fn sorted_task_links(c: &Concept) -> Vec<&TaskLink> {
manipulate! {
c.iter_task_links().collect::<Vec<_>>()
=> .sort_by_key(|link| link.key())
}
}
let [task_links_old, task_links_new] =
f_parallel![sorted_task_links; concept_old; concept_new];
assert_eq_try!(
task_links_old.len(),
task_links_new.len(),
"概念'{term}'的任务链数量不一致"
);
for (old, new) in zip(task_links_old, task_links_new) {
task_consistent(&old.target(), &new.target())?;
}
fn sorted_term_links(c: &Concept) -> Vec<&TermLink> {
manipulate! {
c.iter_term_links().collect::<Vec<_>>()
=> .sort_by_key(|link| link.key())
}
}
let [links_old, links_new] = f_parallel![sorted_term_links; concept_old; concept_new];
assert_eq_try!(
links_old,
links_new,
"概念'{term}'的词项链不一致\nold = {links_old:?}\nnew = {links_new:?}",
);
for (old, new) in zip(concept_old.iter_beliefs(), concept_new.iter_beliefs()) {
assert_eq_try!(
old,
new,
"概念'{term}'的信念列表不一致\nold = {}\nnew = {}",
old.to_display_long(),
new.to_display_long(),
);
}
ok!()
}
pub fn task_consistent(a: &Task, b: &Task) -> AResult {
let [ka, kb] = [a.key(), b.key()];
assert_eq_try!(ka, kb, "任务不一致——key不一致:{ka} != {kb}",);
let [ca, cb] = [a.content(), b.content()];
assert_eq_try!(ca, cb, "任务不一致——content不一致:{ca} != {cb}");
assert_eq_try!(
a.as_judgement().map(TruthValue::from),
b.as_judgement().map(TruthValue::from),
"任务不一致——真值不一致"
);
assert_eq_try!(
BudgetValue::from(a),
BudgetValue::from(b),
"任务不一致——预算不一致"
);
assert_eq_try!(
a.punctuation(),
b.punctuation(),
"任务不一致——punctuation不一致"
);
assert_eq_try!(
a.parent_belief(),
b.parent_belief(),
"任务不一致——parent_belief不一致"
);
match (a.parent_task(), b.parent_task()) {
(Some(a), Some(b)) => {
task_consistent(&a.get_(), &b.get_())?;
}
(None, None) => {}
_ => panic!("任务不一致——父任务不一致"),
};
ok!()
}
#[test]
fn test_soundness() -> AResult {
fn test(memory: &Memory) -> AResult {
let ser = serde_json::to_string(memory)?;
let de = serde_json::from_str::<Memory>(&ser)?;
memory_consistent(memory, &de)?;
ok!()
}
const R_TERM: std::ops::RangeInclusive<char> = 'A'..='Z';
for t_end in R_TERM {
let mut memory = Memory::new(DEFAULT_PARAMETERS);
for t in 'A'..=t_end {
memory.make_new_concept(&term!(str t.to_string()));
}
test(&memory)?;
}
ok!()
}
}