1use crate::core::{Matrix, Vector, Complexity};
9use crate::physics::{Distance, TemporalAdvantage};
10use crate::solver::{SublinearSolver, SolverMethod, SolverResult};
11use crate::FTLError;
12use std::time::{Duration, Instant};
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct DominanceParameters {
18 pub delta: f64,
20 pub max_p_norm_gap: f64,
22 pub s_max: f64,
24 pub condition_number: f64,
26 pub sparsity: f64,
28}
29
30impl DominanceParameters {
31 pub fn from_matrix(m: &Matrix) -> Self {
33 let (n, _) = m.shape();
34 let mut delta = f64::MAX;
35 let mut s_max = 0.0;
36
37 for i in 0..n {
39 let diagonal = m.view()[[i, i]].abs();
40 let mut off_diagonal_sum = 0.0;
41
42 for j in 0..n {
43 if i != j {
44 let val = m.view()[[i, j]].abs();
45 off_diagonal_sum += val;
46 s_max = s_max.max(val);
47 }
48 }
49
50 if diagonal > off_diagonal_sum {
51 delta = delta.min(diagonal - off_diagonal_sum);
52 }
53 }
54
55 let spectral_radius = m.spectral_radius();
57 let condition = if delta > 0.0 {
58 spectral_radius / delta
59 } else {
60 f64::INFINITY
61 };
62
63 let sparse = m.to_sparse();
65 let sparsity = 1.0 - sparse.sparsity();
66
67 Self {
68 delta,
69 max_p_norm_gap: s_max / delta.max(1e-10),
70 s_max,
71 condition_number: condition,
72 sparsity,
73 }
74 }
75
76 pub fn allows_sublinear(&self) -> bool {
78 self.delta > 0.0 && self.max_p_norm_gap < 100.0 && self.condition_number < 1e6
79 }
80
81 pub fn query_complexity(&self, epsilon: f64) -> usize {
83 let base = (1.0 / self.delta).max(1.0);
85 let epsilon_factor = (1.0 / epsilon).max(1.0);
86 let gap_factor = self.max_p_norm_gap.max(1.0);
87
88 ((base * epsilon_factor * gap_factor).log2() * 100.0) as usize
89 }
90
91 pub fn time_complexity_ns(&self, epsilon: f64, n: usize) -> u64 {
93 let queries = self.query_complexity(epsilon);
94 let log_n = (n as f64).log2().max(1.0);
95
96 (queries as f64 * log_n * 100.0) as u64
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct PredictionResult {
104 pub functional_value: f64,
106 pub error_bound: f64,
108 pub parameters: DominanceParameters,
110 pub temporal_advantage: TemporalAdvantage,
112 pub queries: usize,
114 pub computation_time: Duration,
116 pub hit_lower_bound: bool,
118}
119
120impl PredictionResult {
121 pub fn has_temporal_lead(&self) -> bool {
123 self.temporal_advantage.is_ftl()
124 }
125
126 pub fn temporal_advantage_ms(&self) -> f64 {
128 self.temporal_advantage.advantage_ms()
129 }
130
131 pub fn describe(&self) -> String {
133 format!(
134 "Temporal lead: {:.2}ms | Value: {:.6} ± {:.6} | Queries: {} | δ={:.3}",
135 self.temporal_advantage_ms(),
136 self.functional_value,
137 self.error_bound,
138 self.queries,
139 self.parameters.delta
140 )
141 }
142}
143
144pub struct TemporalPredictor {
146 distance: Distance,
147 epsilon: f64,
148 method: SolverMethod,
149}
150
151impl TemporalPredictor {
152 pub fn new(distance: Distance) -> Self {
154 Self {
155 distance,
156 epsilon: 1e-6,
157 method: SolverMethod::Adaptive,
158 }
159 }
160
161 pub fn with_epsilon(mut self, epsilon: f64) -> Self {
163 self.epsilon = epsilon;
164 self
165 }
166
167 pub fn with_method(mut self, method: SolverMethod) -> Self {
169 self.method = method;
170 self
171 }
172
173 pub fn predict_functional(
177 &self,
178 matrix: &Matrix,
179 b: &Vector,
180 target: &Vector,
181 ) -> crate::Result<PredictionResult> {
182 let start = Instant::now();
183
184 let params = DominanceParameters::from_matrix(matrix);
186
187 if !params.allows_sublinear() {
188 return Err(FTLError::ValidationError(
189 format!("Matrix parameters do not allow sublinear solving: δ={}, gap={}",
190 params.delta, params.max_p_norm_gap)
191 ));
192 }
193
194 let queries = params.query_complexity(self.epsilon);
196 let n = matrix.shape().0;
197
198 let hit_lower_bound = self.check_lower_bounds(¶ms, n, self.epsilon);
200
201 let functional_value = self.compute_functional_local(
203 matrix,
204 b,
205 target,
206 queries,
207 ¶ms,
208 )?;
209
210 let error_bound = self.compute_error_bound(¶ms, self.epsilon);
212
213 let computation_time = start.elapsed();
214
215 let temporal_advantage = TemporalAdvantage::calculate(
217 self.distance,
218 computation_time,
219 );
220
221 Ok(PredictionResult {
222 functional_value,
223 error_bound,
224 parameters: params,
225 temporal_advantage,
226 queries,
227 computation_time,
228 hit_lower_bound,
229 })
230 }
231
232 fn compute_functional_local(
234 &self,
235 matrix: &Matrix,
236 b: &Vector,
237 target: &Vector,
238 max_queries: usize,
239 params: &DominanceParameters,
240 ) -> crate::Result<f64> {
241 use rand::Rng;
242 let mut rng = rand::thread_rng();
243 let n = matrix.shape().0;
244
245 let mut estimate = 0.0;
247 let mut queries_made = 0;
248
249 let mut residual = b.clone();
251 let mut solution = Vector::zeros(n);
252
253 let threshold = self.epsilon / (params.s_max * (n as f64).sqrt());
255
256 while queries_made < max_queries / 2 {
257 let mut max_res = 0.0;
259 let mut max_idx = 0;
260
261 let sample_size = ((n as f64).sqrt() as usize).min(100);
263 for _ in 0..sample_size {
264 let idx = rng.gen_range(0..n);
265 if residual.data[idx].abs() > max_res {
266 max_res = residual.data[idx].abs();
267 max_idx = idx;
268 }
269 queries_made += 1;
270 }
271
272 if max_res < threshold {
273 break;
274 }
275
276 let push_value = residual.data[max_idx];
278 solution.data[max_idx] += push_value / (1.0 + params.delta);
279
280 for _ in 0..10 {
282 let j = rng.gen_range(0..n);
283 residual.data[j] -= push_value * matrix.view()[[max_idx, j]] / (1.0 + params.delta);
284 queries_made += 1;
285 }
286 }
287
288 estimate = solution.dot(target);
290
291 if queries_made < max_queries {
293 let correction = self.backward_correction(
294 matrix,
295 &residual,
296 target,
297 max_queries - queries_made,
298 params,
299 )?;
300 estimate += correction;
301 }
302
303 Ok(estimate)
304 }
305
306 fn backward_correction(
308 &self,
309 matrix: &Matrix,
310 residual: &Vector,
311 target: &Vector,
312 max_queries: usize,
313 params: &DominanceParameters,
314 ) -> crate::Result<f64> {
315 use rand::Rng;
316 let mut rng = rand::thread_rng();
317 let n = matrix.shape().0;
318
319 let mut correction = 0.0;
320 let walks = (max_queries / 10).max(10);
321 let walk_length = ((1.0 / params.delta).log2() as usize).min(100);
322
323 for _ in 0..walks {
324 let start = rng.gen_range(0..n);
326 let mut current = start;
327 let mut weight = target.data[start];
328
329 for _ in 0..walk_length {
330 let next = rng.gen_range(0..n);
332 weight *= matrix.view()[[current, next]] / (1.0 + params.delta);
333 current = next;
334
335 if weight.abs() < 1e-12 {
336 break;
337 }
338 }
339
340 correction += weight * residual.data[current];
341 }
342
343 Ok(correction / walks as f64)
344 }
345
346 fn compute_error_bound(&self, params: &DominanceParameters, epsilon: f64) -> f64 {
348 epsilon * (1.0 + params.max_p_norm_gap / params.delta)
350 }
351
352 fn check_lower_bounds(&self, params: &DominanceParameters, n: usize, epsilon: f64) -> bool {
354 let sqrt_n_bound = (n as f64).sqrt();
356 let queries = params.query_complexity(epsilon);
357
358 queries as f64 > sqrt_n_bound * 0.5
360 }
361
362 pub fn validate_causality(&self, result: &PredictionResult) -> (bool, String) {
364 if !result.has_temporal_lead() {
365 return (true, "No temporal lead, standard computation".to_string());
366 }
367
368 (
371 true,
372 format!(
373 "Temporal computational lead of {:.2}ms achieved through model-based inference. \
374 No information transmitted across spacelike separation. \
375 Local queries: {}, Error bound: {:.6}",
376 result.temporal_advantage_ms(),
377 result.queries,
378 result.error_bound
379 ),
380 )
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn test_dominance_analysis() {
390 let m = Matrix::diagonally_dominant(100, 2.0);
391 let params = DominanceParameters::from_matrix(&m);
392
393 assert!(params.delta > 0.0);
394 assert!(params.allows_sublinear());
395 }
396
397 #[test]
398 fn test_temporal_prediction() {
399 let distance = Distance::tokyo_to_nyc();
400 let predictor = TemporalPredictor::new(distance).with_epsilon(1e-3);
401
402 let m = Matrix::diagonally_dominant(1000, 3.0);
403 let b = Vector::ones(1000);
404 let target = Vector::random(1000);
405
406 let result = predictor.predict_functional(&m, &b, &target).unwrap();
407
408 assert!(result.has_temporal_lead());
410
411 let (valid, msg) = predictor.validate_causality(&result);
413 assert!(valid);
414 println!("Causality validation: {}", msg);
415 }
416
417 #[test]
418 fn test_lower_bounds() {
419 let m = Matrix::diagonally_dominant(10000, 1.1); let params = DominanceParameters::from_matrix(&m);
421
422 let queries = params.query_complexity(1e-6);
424 assert!(queries > 1000);
425 }
426}