Skip to main content

qualia_core_db/solvers/calculus/
tensor_provenance.rs

1//! Tensor Provenance Tracking
2//!
3//! Implements provenance-linked tensor state representation in the graph.
4//! Tracks the origin, transformations, and data lineage of tensor states
5//! for reproducibility and auditability of numerical computations.
6//!
7//! ## Architecture
8//!
9//! - **Provenance Chain**: Each tensor state maintains a chain of parent states
10//! - **Operation Tracking**: Records the operation that produced each state
11//! - **Metadata Persistence**: Stores provenance metadata in Quin fields
12//! - **Graph Integration**: Links tensor states through subject/object relationships
13//!
14//! ## Usage
15//!
16//! ```no_run
17//! use qualia_core_db::modalities::calculus::tensor_provenance::{TensorState, TensorProvenance};
18//!
19//! let state = TensorState::new([1.0, 2.0, 3.0]);
20//! let transformed = state.apply_operation("rk4_step", &params);
21//! let provenance = transformed.get_provenance();
22//! ```
23
24use crate::NQuin;
25use std::collections::HashMap;
26
27// ─── Tensor State ─────────────────────────────────────────────────────────────
28
29/// Represents a tensor state with provenance tracking
30///
31/// Each tensor state includes the data itself and metadata about
32/// how it was produced, enabling full reproducibility.
33#[derive(Debug, Clone)]
34pub struct TensorState {
35    /// Tensor data (flattened into a vector)
36    pub data: Vec<f64>,
37    /// Tensor shape (dimensions)
38    pub shape: Vec<usize>,
39    /// Provenance information
40    pub provenance: TensorProvenance,
41    /// Unique identifier for this state
42    pub state_id: u64,
43}
44
45impl TensorState {
46    /// Creates a new tensor state with no provenance (root state)
47    pub fn new(data: Vec<f64>, shape: Vec<usize>) -> Self {
48        let state_id = Self::generate_state_id(&data, &shape);
49        Self {
50            data,
51            shape,
52            provenance: TensorProvenance::Root {
53                source: "initial_state".to_string(),
54                timestamp: Self::current_timestamp(),
55            },
56            state_id,
57        }
58    }
59
60    /// Creates a tensor state from a scalar value (1D tensor of size 1)
61    pub fn from_scalar(value: f64) -> Self {
62        Self::new(vec![value], vec![1])
63    }
64
65    /// Applies an operation to create a new tensor state with provenance
66    pub fn apply_operation(&self, operation: &str, params: &HashMap<String, f64>) -> Self {
67        let new_data = self.compute_operation(operation, params);
68        let new_shape = self.infer_shape(operation, &new_data);
69        let state_id = Self::generate_state_id(&new_data, &new_shape);
70
71        Self {
72            data: new_data,
73            shape: new_shape,
74            provenance: TensorProvenance::Derived {
75                parent_id: self.state_id,
76                operation: operation.to_string(),
77                params: params.clone(),
78                timestamp: Self::current_timestamp(),
79            },
80            state_id,
81        }
82    }
83
84    /// Computes the result of an operation on the tensor data
85    fn compute_operation(&self, operation: &str, params: &HashMap<String, f64>) -> Vec<f64> {
86        match operation {
87            "rk4_step" => self.rk4_step(params),
88            "scale" => self.scale(params),
89            "add" => self.add(params),
90            "multiply" => self.multiply(params),
91            "transpose" => self.transpose(),
92            "reduce_sum" => self.reduce_sum(),
93            _ => self.data.clone(), // Identity operation for unknown ops
94        }
95    }
96
97    /// RK4 ODE step operation
98    fn rk4_step(&self, params: &HashMap<String, f64>) -> Vec<f64> {
99        let step_size = params.get("step_size").copied().unwrap_or(0.01);
100        let lambda = params.get("lambda").copied().unwrap_or(0.5);
101
102        // Apply exponential decay: y' = -λy
103        self.data
104            .iter()
105            .map(|&y| y * (-lambda * step_size).exp())
106            .collect()
107    }
108
109    /// Scale operation
110    fn scale(&self, params: &HashMap<String, f64>) -> Vec<f64> {
111        let factor = params.get("factor").copied().unwrap_or(1.0);
112        self.data.iter().map(|&x| x * factor).collect()
113    }
114
115    /// Add operation
116    fn add(&self, params: &HashMap<String, f64>) -> Vec<f64> {
117        let value = params.get("value").copied().unwrap_or(0.0);
118        self.data.iter().map(|&x| x + value).collect()
119    }
120
121    /// Multiply operation
122    fn multiply(&self, params: &HashMap<String, f64>) -> Vec<f64> {
123        let value = params.get("value").copied().unwrap_or(1.0);
124        self.data.iter().map(|&x| x * value).collect()
125    }
126
127    /// Transpose operation (for 2D tensors)
128    fn transpose(&self) -> Vec<f64> {
129        if self.shape.len() == 2 {
130            let rows = self.shape[0];
131            let cols = self.shape[1];
132            let mut transposed = vec![0.0; self.data.len()];
133
134            for i in 0..rows {
135                for j in 0..cols {
136                    transposed[j * rows + i] = self.data[i * cols + j];
137                }
138            }
139            transposed
140        } else {
141            self.data.clone()
142        }
143    }
144
145    /// Reduce sum operation
146    fn reduce_sum(&self) -> Vec<f64> {
147        vec![self.data.iter().sum()]
148    }
149
150    /// Infers the shape of the result tensor
151    fn infer_shape(&self, operation: &str, _data: &Vec<f64>) -> Vec<usize> {
152        match operation {
153            "reduce_sum" => vec![1],
154            "transpose" => {
155                if self.shape.len() == 2 {
156                    vec![self.shape[1], self.shape[0]]
157                } else {
158                    self.shape.clone()
159                }
160            }
161            _ => self.shape.clone(),
162        }
163    }
164
165    /// Gets the provenance chain as a vector
166    pub fn get_provenance_chain(&self) -> Vec<TensorProvenance> {
167        let chain = vec![self.provenance.clone()];
168        // In a full implementation, this would recursively traverse parent states
169        chain
170    }
171
172    /// Converts the tensor state to a Quin for graph storage
173    pub fn to_quin(&self) -> NQuin {
174        let mut quin = NQuin::default();
175        quin.subject = self.state_id;
176
177        // Pack tensor metadata into object field
178        // For simplicity, we store the first element and length
179        if !self.data.is_empty() {
180            quin.object = self.data[0].to_bits() as u64;
181        }
182
183        // Store data length in metadata
184        quin.metadata = self.data.len() as u64;
185
186        // Store provenance hash in context
187        quin.context = self.provenance_hash();
188
189        quin
190    }
191
192    /// Computes a hash of the provenance for graph linking
193    fn provenance_hash(&self) -> u64 {
194        match &self.provenance {
195            TensorProvenance::Root { source, timestamp } => {
196                let combined = format!("{}:{}", source, timestamp);
197                crate::q_hash(&combined)
198            }
199            TensorProvenance::Derived {
200                parent_id,
201                operation,
202                params,
203                timestamp,
204            } => {
205                let param_str: String = params
206                    .iter()
207                    .map(|(k, v)| format!("{}={}", k, v))
208                    .collect::<Vec<_>>()
209                    .join(",");
210                let combined = format!("{}:{}:{}:{}", parent_id, operation, param_str, timestamp);
211                crate::q_hash(&combined)
212            }
213        }
214    }
215
216    /// Generates a unique state ID from data and shape
217    fn generate_state_id(data: &Vec<f64>, shape: &Vec<usize>) -> u64 {
218        let data_hash: u64 = data
219            .iter()
220            .map(|&x| x.to_bits())
221            .fold(0u64, |acc, x| acc.wrapping_add(x));
222
223        let shape_hash: u64 = shape
224            .iter()
225            .fold(0u64, |acc, &x| acc.wrapping_add(x as u64));
226
227        data_hash.wrapping_mul(31).wrapping_add(shape_hash)
228    }
229
230    /// Gets the current timestamp
231    fn current_timestamp() -> u64 {
232        std::time::SystemTime::now()
233            .duration_since(std::time::UNIX_EPOCH)
234            .unwrap()
235            .as_secs()
236    }
237
238    /// Gets the provenance information
239    pub fn get_provenance(&self) -> &TensorProvenance {
240        &self.provenance
241    }
242}
243
244// ─── Tensor Provenance ───────────────────────────────────────────────────────
245
246/// Provenance information for a tensor state
247#[derive(Debug, Clone)]
248pub enum TensorProvenance {
249    /// Root state with no parent
250    Root { source: String, timestamp: u64 },
251    /// Derived state from a parent operation
252    Derived {
253        parent_id: u64,
254        operation: String,
255        params: HashMap<String, f64>,
256        timestamp: u64,
257    },
258}
259
260impl TensorProvenance {
261    /// Checks if this is a root state
262    pub fn is_root(&self) -> bool {
263        matches!(self, TensorProvenance::Root { .. })
264    }
265
266    /// Gets the parent ID if this is a derived state
267    pub fn parent_id(&self) -> Option<u64> {
268        match self {
269            TensorProvenance::Derived { parent_id, .. } => Some(*parent_id),
270            _ => None,
271        }
272    }
273
274    /// Gets the operation name if this is a derived state
275    pub fn operation(&self) -> Option<&str> {
276        match self {
277            TensorProvenance::Derived { operation, .. } => Some(operation),
278            _ => None,
279        }
280    }
281}
282
283// ─── Provenance Graph ───────────────────────────────────────────────────────
284
285/// Graph structure for tracking tensor state relationships
286pub struct ProvenanceGraph {
287    /// Map of state IDs to tensor states
288    states: HashMap<u64, TensorState>,
289    /// Edges representing parent-child relationships
290    edges: Vec<(u64, u64)>, // (parent_id, child_id)
291}
292
293impl ProvenanceGraph {
294    /// Creates a new empty provenance graph
295    pub fn new() -> Self {
296        Self {
297            states: HashMap::new(),
298            edges: Vec::new(),
299        }
300    }
301
302    /// Adds a tensor state to the graph
303    pub fn add_state(&mut self, state: TensorState) {
304        let state_id = state.state_id;
305
306        // Add edge if this is a derived state
307        if let Some(parent_id) = state.provenance.parent_id() {
308            self.edges.push((parent_id, state_id));
309        }
310
311        self.states.insert(state_id, state);
312    }
313
314    /// Gets a tensor state by ID
315    pub fn get_state(&self, state_id: u64) -> Option<&TensorState> {
316        self.states.get(&state_id)
317    }
318
319    /// Gets the lineage (chain of parent states) for a given state
320    pub fn get_lineage(&self, state_id: u64) -> Vec<u64> {
321        let mut lineage = Vec::new();
322        let mut current_id = state_id;
323
324        while let Some(state) = self.states.get(&current_id) {
325            lineage.push(current_id);
326
327            if let Some(parent_id) = state.provenance.parent_id() {
328                current_id = parent_id;
329            } else {
330                break;
331            }
332        }
333
334        lineage
335    }
336
337    /// Gets all children of a given state
338    pub fn get_children(&self, state_id: u64) -> Vec<u64> {
339        self.edges
340            .iter()
341            .filter(|(parent, _)| *parent == state_id)
342            .map(|(_, child)| *child)
343            .collect()
344    }
345
346    /// Validates the provenance graph for consistency
347    pub fn validate(&self) -> Result<(), String> {
348        // Check that all edges reference valid states
349        for (parent_id, child_id) in &self.edges {
350            if !self.states.contains_key(parent_id) {
351                return Err(format!("Parent state {} not found in graph", parent_id));
352            }
353            if !self.states.contains_key(child_id) {
354                return Err(format!("Child state {} not found in graph", child_id));
355            }
356        }
357
358        // Check for cycles (simple check: no state should be its own ancestor)
359        for state_id in self.states.keys() {
360            let lineage = self.get_lineage(*state_id);
361            if lineage.len()
362                != lineage
363                    .iter()
364                    .collect::<std::collections::HashSet<_>>()
365                    .len()
366            {
367                return Err(format!("Cycle detected in lineage of state {}", state_id));
368            }
369        }
370
371        Ok(())
372    }
373}
374
375impl Default for ProvenanceGraph {
376    fn default() -> Self {
377        Self::new()
378    }
379}
380
381// ─── Tests ─────────────────────────────────────────────────────────────────────
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386
387    #[test]
388    fn test_tensor_state_creation() {
389        let state = TensorState::new(vec![1.0, 2.0, 3.0], vec![3]);
390        assert_eq!(state.data.len(), 3);
391        assert!(state.provenance.is_root());
392    }
393
394    #[test]
395    fn test_tensor_state_from_scalar() {
396        let state = TensorState::from_scalar(5.0);
397        assert_eq!(state.data, vec![5.0]);
398        assert_eq!(state.shape, vec![1]);
399    }
400
401    #[test]
402    fn test_apply_operation_rk4_step() {
403        let state = TensorState::new(vec![1.0, 2.0, 3.0], vec![3]);
404        let mut params = HashMap::new();
405        params.insert("step_size".to_string(), 0.01);
406        params.insert("lambda".to_string(), 0.5);
407
408        let transformed = state.apply_operation("rk4_step", &params);
409        assert!(!transformed.provenance.is_root());
410        assert_eq!(transformed.provenance.operation(), Some("rk4_step"));
411    }
412
413    #[test]
414    fn test_apply_operation_scale() {
415        let state = TensorState::new(vec![1.0, 2.0, 3.0], vec![3]);
416        let mut params = HashMap::new();
417        params.insert("factor".to_string(), 2.0);
418
419        let transformed = state.apply_operation("scale", &params);
420        assert_eq!(transformed.data, vec![2.0, 4.0, 6.0]);
421    }
422
423    #[test]
424    fn test_apply_operation_add() {
425        let state = TensorState::new(vec![1.0, 2.0, 3.0], vec![3]);
426        let mut params = HashMap::new();
427        params.insert("value".to_string(), 10.0);
428
429        let transformed = state.apply_operation("add", &params);
430        assert_eq!(transformed.data, vec![11.0, 12.0, 13.0]);
431    }
432
433    #[test]
434    fn test_provenance_chain() {
435        let state1 = TensorState::new(vec![1.0], vec![1]);
436        let mut params = HashMap::new();
437        params.insert("factor".to_string(), 2.0);
438        let state2 = state1.apply_operation("scale", &params);
439
440        let chain = state2.get_provenance_chain();
441        // Current implementation only returns the current state's provenance
442        // Full chain traversal would require graph access
443        assert_eq!(chain.len(), 1);
444        assert!(!chain[0].is_root());
445        assert_eq!(chain[0].operation(), Some("scale"));
446    }
447
448    #[test]
449    fn test_provenance_graph() {
450        let mut graph = ProvenanceGraph::new();
451
452        let state1 = TensorState::new(vec![1.0], vec![1]);
453        let state1_id = state1.state_id;
454        graph.add_state(state1.clone());
455
456        let mut params = HashMap::new();
457        params.insert("factor".to_string(), 2.0);
458        let state2 = TensorState::from_scalar(1.0).apply_operation("scale", &params);
459        let state2_id = state2.state_id;
460        graph.add_state(state2);
461
462        assert!(graph.get_state(state1_id).is_some());
463        assert!(graph.get_state(state2_id).is_some());
464    }
465
466    #[test]
467    fn test_provenance_graph_lineage() {
468        let mut graph = ProvenanceGraph::new();
469
470        let state1 = TensorState::new(vec![1.0], vec![1]);
471        let state1_id = state1.state_id;
472        graph.add_state(state1.clone());
473
474        let mut params = HashMap::new();
475        params.insert("factor".to_string(), 2.0);
476        let state2 = state1.apply_operation("scale", &params);
477        let state2_id = state2.state_id;
478        graph.add_state(state2);
479
480        let lineage = graph.get_lineage(state2_id);
481        assert_eq!(lineage.len(), 2);
482        assert!(lineage.contains(&state1_id));
483        assert!(lineage.contains(&state2_id));
484    }
485
486    #[test]
487    fn test_provenance_graph_validate() {
488        let mut graph = ProvenanceGraph::new();
489
490        let state1 = TensorState::new(vec![1.0], vec![1]);
491        graph.add_state(state1.clone());
492
493        let mut params = HashMap::new();
494        params.insert("factor".to_string(), 2.0);
495        let state2 = state1.apply_operation("scale", &params);
496        graph.add_state(state2);
497
498        assert!(graph.validate().is_ok());
499    }
500
501    #[test]
502    fn test_tensor_to_quin() {
503        let state = TensorState::new(vec![1.0, 2.0, 3.0], vec![3]);
504        let quin = state.to_quin();
505
506        assert_eq!(quin.subject, state.state_id);
507        assert_eq!(quin.metadata, 3);
508    }
509
510    #[test]
511    fn test_reduce_sum() {
512        let state = TensorState::new(vec![1.0, 2.0, 3.0], vec![3]);
513        let reduced = state.apply_operation("reduce_sum", &HashMap::new());
514
515        assert_eq!(reduced.data, vec![6.0]);
516        assert_eq!(reduced.shape, vec![1]);
517    }
518
519    #[test]
520    fn test_transpose_2d() {
521        let state = TensorState::new(vec![1.0, 2.0, 3.0, 4.0], vec![2, 2]);
522        let transposed = state.apply_operation("transpose", &HashMap::new());
523
524        assert_eq!(transposed.data, vec![1.0, 3.0, 2.0, 4.0]);
525        assert_eq!(transposed.shape, vec![2, 2]);
526    }
527}