eryon_actors/engine/
wolfram.rs

1/*
2    Appellation: wolfram <module>
3    Contrib: @FL03
4*/
5use super::{ComputationalEngine, Engine, RawEngine};
6use crate::types::{Rulespace, Snapshot};
7
8use eryon::traits::Symbolic;
9use eryon::{Direction, Head, RawState, State, Tail};
10
11/// An implementation of the Wolfram [2, 3] UTM that consideres two states and three symbols;
12/// the machine has proven to be capable of universal computation
13#[derive(Clone, Debug, Default, PartialEq)]
14pub struct WolframUTM<Q = usize, A = usize>
15where
16    Q: RawState + Eq + core::hash::Hash,
17    A: Symbolic,
18{
19    pub(crate) alphabet: [A; 3],
20    pub(crate) execution_history: Vec<(Head<Q, A>, Tail<Q, A>)>,
21    pub(crate) position: usize,
22    pub(crate) ruleset: Rulespace<Q, A>,
23    pub(crate) state: State<Q>,
24    pub(crate) tape: Vec<A>,
25}
26
27impl<Q, A> WolframUTM<Q, A>
28where
29    Q: RawState + Eq + core::hash::Hash,
30    A: Symbolic,
31{
32    pub const STATES: usize = 2;
33    pub const SYMBOLS: usize = 3;
34    // Create a new UTM with a specific program
35    pub fn new(alphabet: [A; 3], State(state): State<Q>) -> Self {
36        WolframUTM {
37            alphabet,
38            execution_history: Vec::new(),
39            position: 0,
40            state: State(state),
41            tape: Vec::new(),
42            ruleset: Rulespace::new(),
43        }
44    }
45    /// create a new instance with the given alphabet and default state
46    pub fn from_alphabet(alphabet: [A; 3]) -> Self
47    where
48        Q: Default,
49    {
50        Self::new(alphabet, State::default())
51    }
52    /// returns an immutable reference to the machine's alphabet
53    pub const fn alphabet(&self) -> &[A; 3] {
54        &self.alphabet
55    }
56    /// returns a copy of the machines current position
57    #[inline]
58    pub fn position(&self) -> usize {
59        self.position
60    }
61    /// returns a reference to the machine's ruleset
62    pub const fn ruleset(&self) -> &Rulespace<Q, A> {
63        &self.ruleset
64    }
65    /// returns a mutable reference to the machine's ruleset
66    pub fn ruleset_mut(&mut self) -> &mut Rulespace<Q, A> {
67        &mut self.ruleset
68    }
69    /// returns a copy of the machine's current state
70    #[inline]
71    pub fn state(&self) -> State<&Q> {
72        self.state.view()
73    }
74    /// returns a mutable reference to the machine's state
75    pub fn state_mut(&mut self) -> State<&mut Q> {
76        self.state.view_mut()
77    }
78    /// returns an immutable reference to the tape
79    pub const fn tape(&self) -> &Vec<A> {
80        &self.tape
81    }
82    /// returns a mutable reference to the tape
83    pub fn tape_mut(&mut self) -> &mut Vec<A> {
84        &mut self.tape
85    }
86    /// returns the head of the machine
87    pub fn head(&self) -> Head<&Q, &A> {
88        // self.get_current_symbol().cloned().unwrap_or_default()
89        Head::new(self.state(), &self.tape[self.position])
90    }
91    /// check if a symbol is in the alphabet
92    pub fn contains(&self, symbol: &A) -> bool {
93        self.alphabet.contains(symbol)
94    }
95    /// get the current symbol on the tape
96    pub fn get_current_symbol(&self) -> Option<&A> {
97        self.tape.get(self.position)
98    }
99    /// get the tail for the current head
100    pub fn get_tail(&self) -> Option<&Tail<Q, A>>
101    where
102        Q: Clone,
103    {
104        self.ruleset.get(&self.head().cloned())
105    }
106    /// get the tail for a given head
107    pub fn get_tail_for(&self, head: Head<Q, A>) -> &Tail<Q, A> {
108        &self.ruleset[&head]
109    }
110    /// set the alphabet of the machine
111    pub fn set_alphabet(&mut self, alphabet: [A; 3]) {
112        self.alphabet = alphabet;
113    }
114    /// set the position of the head of the machine
115    pub fn set_position(&mut self, position: usize) -> &mut Self {
116        self.position = position;
117        self
118    }
119    /// set the ruleset of the machine
120    pub fn set_ruleset(&mut self, ruleset: Rulespace<Q, A>) -> &mut Self {
121        self.ruleset = ruleset;
122        self
123    }
124    /// set the state of the machine
125    pub fn set_state(&mut self, State(state): State<Q>) -> &mut Self {
126        self.state = State(state);
127        self
128    }
129    /// set the tape of the machine
130    pub fn set_tape(&mut self, tape: Vec<A>) -> &mut Self {
131        self.tape = tape;
132        self
133    }
134    /// consumes this instance to create another with the given alphabet
135    pub fn with_alphabet(self, alphabet: [A; 3]) -> Self {
136        Self { alphabet, ..self }
137    }
138    /// consumes this instance to create another with the given ruleset
139    pub fn with_ruleset(self, ruleset: Rulespace<Q, A>) -> Self {
140        Self { ruleset, ..self }
141    }
142    /// consumes this instance to create another with the given state
143    pub fn with_state(self, State(state): State<Q>) -> Self {
144        Self {
145            state: State(state),
146            ..self
147        }
148    }
149    /// consumes this instance to create another with the given tape
150    pub fn with_tape(self, tape: Vec<A>) -> Self {
151        Self {
152            position: 0,
153            tape,
154            ..self
155        }
156    }
157    /// reset the position of the machine's head to `0`
158    pub fn reset_position(&mut self) -> &mut Self {
159        self.set_position(0);
160        self
161    }
162    /// use the given input as the tape and reset the position of the machine's head
163    pub fn reset_with_tape(&mut self, tape: Vec<A>) -> &mut Self {
164        self.set_tape(tape).reset_position()
165    }
166    /// process some input
167    pub fn run(&mut self, input: Vec<A>) -> crate::Result<Vec<Snapshot<Q, [A; 3], Vec<A>>>>
168    where
169        Q: Clone,
170    {
171        self.run_for(input, usize::MAX)
172    }
173    /// process some input
174    #[cfg_attr(
175        feature = "tracing",
176        tracing::instrument(
177            fields(max),
178            skip(self, input),
179            level = "trace",
180            name = "run_for",
181            target = "utm"
182        )
183    )]
184    pub fn run_for(
185        &mut self,
186        input: Vec<A>,
187        max: usize,
188    ) -> crate::Result<Vec<Snapshot<Q, [A; 3], Vec<A>>>>
189    where
190        Q: Clone,
191    {
192        // Initialize the tape with the input
193        self.reset_with_tape(input);
194
195        let mut history = vec![self.snapshot().cloned()];
196
197        // Run until the machine halts (i.e., step() returns false)
198        let mut halt = false;
199        while self.step().is_ok() && !halt {
200            // Record each state after a step
201            history.push(self.snapshot().cloned());
202            // prevent infinite loops; halt if the history exceeds the maximum
203            if history.len() > max {
204                halt = true;
205                #[cfg(feature = "tracing")]
206                tracing::warn!(
207                    "Infinite loop detected; check the system and it's ruleset to ensure convergence within {} steps",
208                    max
209                )
210            }
211        }
212
213        Ok(history)
214    }
215
216    /// execute one step of the machine
217    pub fn step(&mut self) -> crate::Result<()>
218    where
219        Q: Clone,
220    {
221        let symbol = self.get_current_symbol().cloned().unwrap_or_default();
222
223        // Check if the current symbol is in our current triad context
224        if !self.contains(&symbol) {
225            panic!("symbol {} not in any accessible triad context", symbol);
226        }
227        let head = Head::new(self.state.clone(), symbol);
228        // Find the transition rule for the current state and symbol
229        self.handle(&head)
230    }
231
232    pub fn write(&mut self, symbol: A) {
233        self.tape[self.position] = symbol;
234    }
235    /// given the head of the machine, find the corresponding tail and update the machine
236    #[cfg_attr(
237        feature = "tracing",
238        tracing::instrument(
239            skip(self, head),
240            level = "trace",
241            name = "handle",
242            target = "wolfram"
243        )
244    )]
245    fn handle(&mut self, head: &Head<Q, A>) -> crate::Result<()>
246    where
247        Q: Clone,
248    {
249        if let Some(tail) = self.ruleset.get(head) {
250            let Tail {
251                state: new_state,
252                symbol: new_symbol,
253                direction,
254            } = tail.clone();
255            // Update the tape
256            if self.position >= self.tape.len() {
257                self.tape.resize(self.position + 1, A::default());
258            }
259            self.tape[self.position] = new_symbol;
260
261            // Update state
262            self.state = new_state;
263
264            // Move the head
265            match direction {
266                Direction::Left => {
267                    if self.position > 0 {
268                        self.position -= 1;
269                    } else {
270                        self.tape.insert(0, A::default());
271                    }
272                }
273                Direction::Right => {
274                    self.position += 1;
275                    if self.position >= self.tape.len() {
276                        self.tape.push(A::default());
277                    }
278                }
279                Direction::Stay => {}
280            }
281            return Ok(());
282        }
283        Err(crate::ActorError::DriverError(
284            "No rule found for the current head...".to_string(),
285        )) // No transition rule found
286    }
287}
288
289use crate::mem::TopoLedger;
290
291impl WolframUTM {
292    /// returns a string representation of the UTM
293    pub fn pretty_print(&self) -> String {
294        let mut result = String::new();
295        // insert the state and alphabet
296        result.push_str(&format!(
297            "(State: {:?}, Alphabet: {:?})",
298            self.state, self.alphabet
299        ));
300        // Print the tape with the current position marked
301        for (i, &symbol) in self.tape.iter().enumerate() {
302            if i == self.position {
303                result.push_str(&format!("[{}]", symbol));
304            } else {
305                result.push_str(&format!(" {} ", symbol));
306            }
307        }
308        // return the result
309        result
310    }
311    /// Update with a learned rule
312    pub fn update_rule(&mut self, head: Head<usize, usize>, tail: Tail<usize, usize>) {
313        // Store the rule in the ruleset
314        let entry = self.ruleset.entry(head).or_default();
315        *entry = tail;
316    }
317
318    /// Learn from a successful execution sequence
319    pub fn learn_from_execution(&mut self, memory: &mut TopoLedger, steps: usize, reward: f32) {
320        // Record the last 'steps' state transitions
321        if self.execution_history.len() < steps {
322            return;
323        }
324
325        // Calculate reinforcement amount based on reward
326        let reinforcement = reward / steps as f32;
327
328        // Get the most recent steps
329        let history = self.execution_history.clone();
330        let relevant_history = history
331            .iter()
332            .rev()
333            .take(steps)
334            .collect::<Vec<_>>()
335            .into_iter()
336            .rev();
337
338        // Reinforce each rule in the sequence
339        for &(head, tail) in relevant_history {
340            let Head { state, symbol } = head;
341            let Tail {
342                state: next_state,
343                symbol: next_symbol,
344                direction,
345            } = tail;
346            // Store or update the rule in memory
347            memory.store_learned_rule(
348                state,
349                symbol,
350                next_state,
351                next_symbol,
352                direction,
353                reinforcement,
354            );
355
356            // Also update the UTM's rules directly
357            self.update_rule(head, tail);
358        }
359    }
360}
361
362impl<Q, S> RawEngine for WolframUTM<Q, S>
363where
364    Q: RawState + Clone + Eq + core::hash::Hash,
365    S: Symbolic,
366{
367    type Store = Vec<S>;
368
369    seal!();
370}
371
372impl<Q, S> Engine for WolframUTM<Q, S>
373where
374    Q: RawState + Clone + Default + Eq + core::hash::Hash + Send + Sync,
375    S: Symbolic,
376{
377    seal!();
378}
379
380impl<Q, S> ComputationalEngine<Q, [S; 3]> for WolframUTM<Q, S>
381where
382    Q: RawState + Clone + Eq + core::hash::Hash,
383    S: Symbolic,
384{
385    fn new(alphabet: [S; 3], initial_state: State<Q>) -> Self {
386        WolframUTM::new(alphabet, initial_state)
387    }
388
389    fn alphabet(&self) -> &[S; 3] {
390        &self.alphabet
391    }
392
393    fn alphabet_mut(&mut self) -> &mut [S; 3] {
394        &mut self.alphabet
395    }
396
397    fn head(&self) -> Head<&Q, &S> {
398        self.head()
399    }
400
401    fn head_mut(&mut self) -> Head<&mut Q, &mut S> {
402        Head::new(self.state.view_mut(), &mut self.tape[self.position])
403    }
404
405    fn position(&self) -> usize {
406        self.position
407    }
408
409    fn position_mut(&mut self) -> &mut usize {
410        &mut self.position
411    }
412
413    fn state(&self) -> State<&Q> {
414        self.state.view()
415    }
416
417    fn state_mut(&mut self) -> State<&mut Q> {
418        self.state.view_mut()
419    }
420
421    fn tape(&self) -> &Vec<S> {
422        &self.tape
423    }
424
425    fn tape_mut(&mut self) -> &mut Vec<S> {
426        &mut self.tape
427    }
428
429    fn step(&mut self) -> Result<(), crate::ActorError> {
430        self.step()
431    }
432
433    fn set_alphabet(&mut self, alphabet: [S; 3]) {
434        self.alphabet = alphabet;
435    }
436
437    fn set_position(&mut self, position: usize) {
438        self.position = position;
439    }
440
441    fn set_state(&mut self, state: State<Q>) {
442        self.state = state;
443    }
444
445    fn set_tape<I>(&mut self, tape: I)
446    where
447        I: IntoIterator<Item = S>,
448    {
449        self.tape = Vec::from_iter(tape);
450    }
451}
452
453impl<Q, S> core::fmt::Display for WolframUTM<Q, S>
454where
455    Q: RawState + Eq + core::hash::Hash,
456    S: Symbolic,
457{
458    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
459        write!(
460            f,
461            "{head:?} @ {pos}: {tape:?}",
462            head = self.head(),
463            pos = self.position(),
464            tape = self.tape()
465        )
466    }
467}
468
469impl<Q, S> core::ops::Index<usize> for WolframUTM<Q, S>
470where
471    Q: RawState + Eq + core::hash::Hash,
472    S: Symbolic,
473{
474    type Output = S;
475
476    fn index(&self, index: usize) -> &Self::Output {
477        &self.tape[index]
478    }
479}
480
481impl<Q, S> core::ops::Index<Head<Q, S>> for WolframUTM<Q, S>
482where
483    Q: RawState + Eq + core::hash::Hash,
484    S: Symbolic,
485{
486    type Output = Tail<Q, S>;
487
488    fn index(&self, index: Head<Q, S>) -> &Self::Output {
489        &self.ruleset[&index]
490    }
491}