eryon_actors/
vnode.rs

1/*
2    Appellation: vnode <module>
3    Contrib: @FL03
4*/
5//! # Virtual Node ([`Vnode`])
6//!
7//! A virtual speaks to a virtualized workspace automatically allocated by the runtime during
8//! the initialization process. These objects are responsible for maintaining their internal
9//! _plants_, or engines, equipping them with everything needed to successfully execute a given
10//! workload.
11//!
12
13mod impl_neural;
14mod impl_vnode;
15
16use crate::ctx::ActorContext;
17use crate::drivers::{Driver, NeuralPlant, RawDriver, WolframPlant};
18use crate::mem::TopoLedger;
19use crate::operators::{Agent, Observer, Operator, OperatorKind};
20use crate::surface::{PointKind, SurfaceNetwork};
21use crate::traits::Actor;
22use crate::types::VirtualMemoryAnalysis;
23use rstmt::nrt::{LPR, Triad, Triads};
24use rstmt::{Aspn as Note, PitchMod};
25
26use ndarray::Array2;
27use num_traits::{Float, FromPrimitive, NumAssign, ToPrimitive};
28use scsys::Id;
29use std::collections::HashMap;
30use std::time::Instant;
31
32/// a type alias denoting a [VNode] equipped with a [NeuralPlant]
33pub type NeuralNode<T = f32> = VNode<NeuralPlant<T>, T>;
34/// a type alias denoting a [VNode] equipped with a [WolframPlant]
35pub type WolframNode<T = f32> = VNode<WolframPlant, T>;
36
37/// This struct manifests the partitions automatically allocated by the runtime as virtualized
38/// workspaces responsible for maintaining their plant's. Their capabilities are defined by the
39/// current operator, allowing a node to become an observer, agent, etc
40///
41/// Node's equip their plants (or drivers) with everything needed to successfully execute a
42/// given transaction, providing storage and networking capabilities while abstracting away
43/// platform-specific logic. This enables plant's to retain their topological and mathematical
44/// properties and reduces overhead in the process.
45///
46/// VNode's are rely on a topological memory system to facilitate various operations. This
47/// memory system is responsible for storing and managing the topological features of the
48/// plant, such as the current state, critical points, and navigational history.
49///
50/// Additionally, each instance is equipped with its own neural network designed to
51/// _materialize_ the surface of the node's headspace. This process specifically speaks to the
52/// dynamic configuration of a neural network with respect to a topological entity. This
53/// provides the system with an additional degree of freedom when it comes to learning as
54/// certain engines are capable of learning the _rules_ of headspace leaving the surface to
55/// predict and learn more traditional patterns.
56#[derive(Clone, Debug)]
57pub struct VNode<D, T = f32>
58where
59    D: RawDriver<Triad>,
60{
61    pub(crate) id: Id,
62    /// a fully-managed topological unit of compute
63    pub(crate) driver: D,
64    /// the node operator
65    pub(crate) operator: Operator<T>,
66    /// Topological memory system
67    pub(crate) store: TopoLedger<T>,
68    /// Neural surface network for behavior learning within the current headspace
69    pub(crate) surface: Option<SurfaceNetwork<T>>,
70    /// Training momentum for surface network
71    pub(crate) prev_weight_changes: Option<(Array2<T>, Array2<T>)>,
72    /// Critical points in tonal space (parameter name -> pitch class)
73    pub(crate) critical_points: HashMap<String, usize>,
74    /// Reference to previously visited tonics
75    pub(crate) tonic_history: Vec<usize>,
76    /// Last context calculation
77    pub(crate) last_context: Option<(ActorContext<T>, Instant)>,
78}
79
80impl<D, T> VNode<D, T>
81where
82    D: RawDriver<Triad>,
83{
84    /// initialize a new virual node with a default driver
85    pub fn new() -> Self
86    where
87        D: Default,
88        T: Float + FromPrimitive + ToPrimitive,
89    {
90        let plant = D::default();
91        Self::from_driver(plant)
92    }
93    /// Create a new virtual node with topological memory
94    pub fn from_driver(plant: D) -> Self
95    where
96        T: Float + FromPrimitive + ToPrimitive,
97    {
98        let memory = TopoLedger::new();
99        let id: Id;
100        #[cfg(feature = "rand")]
101        {
102            id = Id::<u128>::random().map(|id| id as usize);
103        }
104        #[cfg(not(feature = "rand"))]
105        {
106            id = Id::atomic();
107        }
108        let mut vnode = VNode {
109            id,
110            critical_points: HashMap::new(),
111            driver: plant,
112            last_context: None,
113            operator: Operator::Observer(Observer::new()),
114            prev_weight_changes: None,
115            store: memory,
116            surface: None,
117            tonic_history: Vec::new(),
118        };
119
120        // Initialize by recording the initial state
121        vnode.record_state();
122
123        vnode
124    }
125    /// initialize a new virtual node from the given headspace
126    pub fn from_headspace(headspace: Triad) -> Self
127    where
128        D: Driver<Triad>,
129        T: Float + FromPrimitive + ToPrimitive,
130    {
131        let plant = D::from_headspace(headspace);
132        Self::from_driver(plant)
133    }
134    /// returns a copy of the node id
135    pub const fn id(&self) -> Id {
136        self.id
137    }
138    /// Return the triad class of the underlying plant
139    pub fn class(&self) -> Triads {
140        self.driver().headspace().class()
141    }
142    /// return an immutable reference to the critical points
143    pub const fn critical_points(&self) -> &HashMap<String, usize> {
144        &self.critical_points
145    }
146    /// return a mutable reference to the critical points
147    pub const fn critical_points_mut(&mut self) -> &mut HashMap<String, usize> {
148        &mut self.critical_points
149    }
150    /// return an immutable reference to the plant
151    pub const fn driver(&self) -> &D {
152        &self.driver
153    }
154    /// return a mutable reference to the plant
155    pub const fn driver_mut(&mut self) -> &mut D {
156        &mut self.driver
157    }
158
159    /// returns a mutable reference to the node's headspace
160    #[inline]
161    pub fn headspace_mut(&mut self) -> &mut Triad {
162        self.driver.headspace_mut()
163    }
164    /// returns an immutable reference to the last context
165    pub fn last_context(&self) -> Option<&(ActorContext<T>, Instant)> {
166        self.last_context.as_ref()
167    }
168    /// returns a mutable reference to the last context
169    pub fn last_context_mut(&mut self) -> Option<&mut (ActorContext<T>, Instant)> {
170        self.last_context.as_mut()
171    }
172    /// return an immutable reference to the memory system
173    pub const fn store(&self) -> &TopoLedger<T> {
174        &self.store
175    }
176    /// return a mutable reference to the memory system
177    pub const fn store_mut(&mut self) -> &mut TopoLedger<T> {
178        &mut self.store
179    }
180    #[deprecated(since = "0.3.0", note = "use `store` instead")]
181    pub const fn memory(&self) -> &TopoLedger<T> {
182        &self.store
183    }
184    #[deprecated(since = "0.3.0", note = "use `store_mut` instead")]
185    pub const fn memory_mut(&mut self) -> &mut TopoLedger<T> {
186        &mut self.store
187    }
188    /// returns an immutable reference to the actor of the node
189    pub const fn operator(&self) -> &Operator<T> {
190        &self.operator
191    }
192    /// returns a mutable reference to the actor of the node
193    pub const fn operator_mut(&mut self) -> &mut Operator<T> {
194        &mut self.operator
195    }
196    /// returns an immutable reference to the surface network
197    pub fn surface(&self) -> Option<&SurfaceNetwork<T>> {
198        self.surface.as_ref()
199    }
200    /// returns a mutable reference to the surface network
201    pub fn surface_mut(&mut self) -> Option<&mut SurfaceNetwork<T>> {
202        self.surface.as_mut()
203    }
204    /// returns an immutable reference the tonic history
205    pub const fn tonic_history(&self) -> &Vec<usize> {
206        &self.tonic_history
207    }
208    /// returns a mutable reference to the history of the node
209    pub const fn tonic_history_mut(&mut self) -> &mut Vec<usize> {
210        &mut self.tonic_history
211    }
212    /// update the id and  return a mutable reference to the node
213    pub fn set_id(&mut self, id: Id) -> &mut Self {
214        self.id = id;
215        self
216    }
217    /// update the current critical points and return a mutable reference to the node
218    pub fn set_critical_points(&mut self, critical_points: HashMap<String, usize>) -> &mut Self {
219        self.critical_points = critical_points;
220        self
221    }
222    /// update the current driver and return a mutable reference to the node
223    pub fn set_driver(&mut self, driver: D) -> &mut Self
224    where
225        T: Float + FromPrimitive + ToPrimitive,
226    {
227        self.driver = driver;
228        self
229    }
230    /// update the current operator and return a mutable reference to the node
231    pub fn set_operator(&mut self, operator: Operator<T>) -> &mut Self {
232        self.operator = operator;
233        self
234    }
235    /// update the current memory system and return a mutable reference to the node
236    pub fn set_store(&mut self, store: TopoLedger<T>) -> &mut Self {
237        self.store = store;
238        self
239    }
240    /// update the current surface network and return a mutable reference to the node
241    pub fn set_surface(&mut self, surface: Option<SurfaceNetwork<T>>) -> &mut Self {
242        self.surface = surface;
243        self
244    }
245    /// update the current tonic history and return a mutable reference to the node
246    pub fn set_tonic_history(&mut self, tonic_history: Vec<usize>) -> &mut Self {
247        self.tonic_history = tonic_history;
248        self
249    }
250    /// consumes the node to create another with the given critical points
251    pub fn with_critical_points(self, critical_points: HashMap<String, usize>) -> Self {
252        Self {
253            critical_points,
254            ..self
255        }
256    }
257    /// consumes the node to create another with the given driver
258    pub fn with_driver(self, driver: D) -> Self {
259        Self { driver, ..self }
260    }
261    /// consumes the node to create another with the given id
262    pub fn with_id(self, id: Id) -> Self {
263        Self { id, ..self }
264    }
265    /// consumes the node to create another with the given operator
266    pub fn with_operator(self, operator: Operator<T>) -> Self {
267        Self { operator, ..self }
268    }
269    /// consumes the node to create another with the given memory system
270    pub fn with_store(self, store: TopoLedger<T>) -> Self {
271        Self { store, ..self }
272    }
273    /// consumes the node to create another with the given surface network
274    pub fn with_surface(self, surface: Option<SurfaceNetwork<T>>) -> Self {
275        Self { surface, ..self }
276    }
277    /// consumes the node to create another with the given tonic history
278    pub fn with_tonic_history(self, tonic_history: Vec<usize>) -> Self {
279        Self {
280            tonic_history,
281            ..self
282        }
283    }
284    /// consumes the node to create another with the given surface network
285    /// add a critical point to the plant and return the previous value, if it exists.
286    pub fn add_critical_point(&mut self, name: PointKind, pitch_class: usize) -> Option<usize> {
287        self.critical_points_mut()
288            .insert(name.to_string(), pitch_class.pmod())
289    }
290    /// compute the centroid of the headspace
291    pub fn calculate_centroid(&self) -> Option<[T; 2]>
292    where
293        T: num_traits::Float + num_traits::FromPrimitive,
294    {
295        self.driver().headspace().centroid()
296    }
297    /// returns an immutable reference to the node's headspace
298    pub fn headspace(&self) -> &Triad {
299        self.driver().headspace()
300    }
301    /// Get the current operator mode
302    pub const fn kind(&self) -> OperatorKind {
303        match self.operator {
304            Operator::Agent(_) => OperatorKind::Agent,
305            Operator::Observer(_) => OperatorKind::Observer,
306        }
307    }
308    /// Record a successful navigation from one point to another
309    pub fn record_navigation(&mut self, origin: &Triad, transforms_used: &[LPR])
310    where
311        T: Float + FromPrimitive + ToPrimitive,
312    {
313        let to_triad = *self.driver().headspace();
314
315        self.store_mut()
316            .record_navigation(&origin.notes(), &to_triad.notes(), transforms_used);
317    }
318    /// Remove a critical point from the plant
319    pub fn remove_critical_point(&mut self, name: PointKind) -> Option<usize> {
320        self.critical_points_mut().remove(&name.to_string())
321    }
322    #[deprecated(since = "0.3.0", note = "use `total_critical_points` instead")]
323    pub fn critical_point_count(&self) -> usize {
324        self.critical_points().len()
325    }
326    #[deprecated(since = "0.3.0", note = "use `total_features` instead")]
327    pub fn feature_count(&self) -> usize {
328        self.store().count_features()
329    }
330    /// returns the number of critical points
331    pub fn total_critical_points(&self) -> usize {
332        self.critical_points().len()
333    }
334    /// Get the number of features in memory
335    pub fn total_features(&self) -> usize {
336        self.store().count_features()
337    }
338    /// Return the notes (alphabet) of the underlying plant
339    pub fn get_notes(&self) -> [usize; 3] {
340        self.driver().headspace().notes()
341    }
342    /// get the tonic (root) of the headspace
343    pub fn get_tonic(&self) -> Note {
344        let class = self.driver().headspace().root();
345        let octave = self.driver().headspace().octave();
346        Note::new(class, octave)
347    }
348    /// returns true if the node has initialized a surface network
349    pub fn has_surface_network(&self) -> bool {
350        self.surface().is_some()
351    }
352    /// Record the current state in memory
353    pub fn record_state(&mut self) -> usize
354    where
355        T: Float + FromPrimitive + ToPrimitive,
356    {
357        let notes = self.get_notes();
358
359        // Create a feature for the triad (dimension 2)
360        let feature_id = self.store_mut().create_feature(2, notes.to_vec());
361
362        // Calculate and record the tonic
363        let tonic = self.driver().headspace().root();
364        self.tonic_history.push(tonic);
365
366        feature_id
367    }
368    /// set the last context of the node
369    pub fn set_last_context(&mut self, context: ActorContext<T>) {
370        self.last_context = Some((context, Instant::now()));
371    }
372    /// set the operator by kind
373    pub fn set_operator_by_kind(&mut self, mode: OperatorKind)
374    where
375        T: core::iter::Sum + Float + FromPrimitive + NumAssign + ToPrimitive,
376    {
377        match mode {
378            OperatorKind::Agent => {
379                if !matches!(self.operator, Operator::Agent(_)) {
380                    // Deactivate current operator if needed
381                    if let Operator::Observer(observer) = &mut self.operator {
382                        let _ = observer.on_deactivate(&self.driver, &mut self.store);
383                    }
384
385                    // Create new agent and activate it
386                    let mut agent = Agent::new();
387                    let _ = <Agent<T> as Actor<D, T>>::initialize(&mut agent);
388                    let _ = agent.on_activate(&self.driver, &mut self.store);
389
390                    self.operator = Operator::Agent(agent);
391                    self.last_context = None; // Invalidate context cache
392                }
393            }
394            OperatorKind::Observer => {
395                if !matches!(self.operator, Operator::Observer(_)) {
396                    // Deactivate current operator if needed
397                    if let Operator::Agent(agent) = &mut self.operator {
398                        let _ = agent.on_deactivate(&self.driver, &mut self.store);
399                    }
400
401                    // Create new observer and activate it
402                    let mut observer = Observer::new();
403                    let _ = <Observer<T> as Actor<D, T>>::initialize(&mut observer);
404                    let _ = observer.on_activate(&self.driver, &mut self.store);
405
406                    self.operator = Operator::Observer(observer);
407                    self.last_context = None; // Invalidate context cache
408                }
409            }
410        }
411    }
412}
413
414impl<D, T> Default for VNode<D, T>
415where
416    D: Default + RawDriver<Triad>,
417    T: Default + Float + FromPrimitive,
418{
419    fn default() -> Self {
420        Self::new()
421    }
422}
423
424impl<D, T> Eq for VNode<D, T>
425where
426    D: RawDriver<Triad>,
427    T: PartialEq,
428{
429}
430
431impl<D, T> PartialEq<VNode<D, T>> for VNode<D, T>
432where
433    D: RawDriver<Triad>,
434    Operator<T>: PartialEq,
435{
436    fn eq(&self, other: &VNode<D, T>) -> bool {
437        self.driver().headspace() == other.driver().headspace()
438            && self.operator() == other.operator()
439            && self.critical_points() == other.critical_points()
440            && self.tonic_history() == other.tonic_history()
441    }
442}
443
444impl<D, T> PartialEq<VNode<D, T>> for &VNode<D, T>
445where
446    D: RawDriver<Triad>,
447    Operator<T>: PartialEq,
448{
449    fn eq(&self, other: &VNode<D, T>) -> bool {
450        self.driver().headspace() == other.driver().headspace()
451            && self.operator() == other.operator()
452            && self.critical_points() == other.critical_points()
453            && self.tonic_history() == other.tonic_history()
454    }
455}
456
457impl<D, T> PartialEq<VNode<D, T>> for &mut VNode<D, T>
458where
459    D: RawDriver<Triad>,
460    Operator<T>: PartialEq,
461{
462    fn eq(&self, other: &VNode<D, T>) -> bool {
463        self.driver().headspace() == other.driver().headspace()
464            && self.operator() == other.operator()
465            && self.critical_points() == other.critical_points()
466            && self.tonic_history() == other.tonic_history()
467    }
468}
469
470impl<'a, D, T> PartialEq<&'a VNode<D, T>> for VNode<D, T>
471where
472    D: RawDriver<Triad>,
473    Operator<T>: PartialEq,
474{
475    fn eq(&self, other: &&'a VNode<D, T>) -> bool {
476        self.driver().headspace() == other.driver().headspace()
477            && self.operator() == other.operator()
478            && self.critical_points() == other.critical_points()
479            && self.tonic_history() == other.tonic_history()
480    }
481}
482
483impl<'a, D, T> PartialEq<&'a mut VNode<D, T>> for VNode<D, T>
484where
485    D: RawDriver<Triad>,
486    Operator<T>: PartialEq,
487{
488    fn eq(&self, other: &&'a mut VNode<D, T>) -> bool {
489        self.driver().headspace() == other.driver().headspace()
490            && self.operator() == other.operator()
491            && self.critical_points() == other.critical_points()
492            && self.tonic_history() == other.tonic_history()
493    }
494}
495
496impl<D, T> core::hash::Hash for VNode<D, T>
497where
498    D: RawDriver<Triad>,
499    Operator<T>: core::hash::Hash,
500{
501    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
502        self.driver().headspace().hash(state);
503        self.operator().hash(state);
504    }
505}