1use crate::NQuin;
5
6pub enum TemporalOp {
8 Before,
9 Meets,
10 Overlaps,
11 Starts,
12 During,
13 Finishes,
14 Equals,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Rcc8Relation {
20 Disconnected,
22 ExternallyConnected,
24 PartiallyOverlapping,
26 TangentiallyProperPart,
28 TangentiallyProperPartInverse,
30 NonTangentialProperPart,
32 NonTangentialProperPartInverse,
34 Equal,
36}
37
38#[derive(Debug, Clone)]
40pub struct SpatialRegion {
41 pub region_id: u64,
42 pub boundary_points: Vec<(f64, f64)>, pub centroid: (f64, f64),
44 pub area: f64,
45}
46
47impl SpatialRegion {
48 pub fn new(region_id: u64, boundary_points: Vec<(f64, f64)>) -> Self {
50 let centroid = Self::compute_centroid(&boundary_points);
51 let area = Self::compute_area(&boundary_points);
52
53 Self {
54 region_id,
55 boundary_points,
56 centroid,
57 area,
58 }
59 }
60
61 fn compute_centroid(points: &[(f64, f64)]) -> (f64, f64) {
63 if points.is_empty() {
64 return (0.0, 0.0);
65 }
66
67 let (sum_x, sum_y) = points
68 .iter()
69 .fold((0.0, 0.0), |(sx, sy), (x, y)| (sx + x, sy + y));
70
71 (sum_x / points.len() as f64, sum_y / points.len() as f64)
72 }
73
74 fn compute_area(points: &[(f64, f64)]) -> f64 {
76 if points.len() < 3 {
77 return 0.0;
78 }
79
80 let mut area = 0.0;
81 for i in 0..points.len() {
82 let j = (i + 1) % points.len();
83 area += points[i].0 * points[j].1;
84 area -= points[j].0 * points[i].1;
85 }
86
87 area.abs() / 2.0
88 }
89
90 pub fn contains_point(&self, point: (f64, f64)) -> bool {
92 if self.boundary_points.len() < 3 {
93 return false;
94 }
95
96 let mut inside = false;
97 let (x, y) = point;
98 let n = self.boundary_points.len();
99
100 for i in 0..n {
101 let j = (i + 1) % n;
102 let (xi, yi) = self.boundary_points[i];
103 let (xj, yj) = self.boundary_points[j];
104
105 if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
106 inside = !inside;
107 }
108 }
109
110 inside
111 }
112
113 pub fn intersects(&self, other: &SpatialRegion) -> bool {
115 let self_bounds = self.get_bounding_box();
117 let other_bounds = other.get_bounding_box();
118
119 !(self_bounds.0 > other_bounds.1
120 || self_bounds.1 < other_bounds.0
121 || self_bounds.2 > other_bounds.3
122 || self_bounds.3 < other_bounds.2)
123 }
124
125 fn get_bounding_box(&self) -> (f64, f64, f64, f64) {
127 if self.boundary_points.is_empty() {
128 return (0.0, 0.0, 0.0, 0.0);
129 }
130
131 let (min_x, max_x) = self
132 .boundary_points
133 .iter()
134 .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), (x, _)| {
135 (min.min(*x), max.max(*x))
136 });
137
138 let (min_y, max_y) = self
139 .boundary_points
140 .iter()
141 .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), (_, y)| {
142 (min.min(*y), max.max(*y))
143 });
144
145 (min_x, max_x, min_y, max_y)
146 }
147}
148
149pub fn evaluate_rcc8(region_a: &SpatialRegion, region_b: &SpatialRegion) -> Rcc8Relation {
151 if region_a.region_id == region_b.region_id {
153 return Rcc8Relation::Equal;
154 }
155
156 let intersects = region_a.intersects(region_b);
158
159 if !intersects {
160 return Rcc8Relation::Disconnected;
161 }
162
163 let a_inside_b = region_a
165 .boundary_points
166 .iter()
167 .all(|&point| region_b.contains_point(point));
168 let b_inside_a = region_b
169 .boundary_points
170 .iter()
171 .all(|&point| region_a.contains_point(point));
172
173 if a_inside_b && b_inside_a {
174 Rcc8Relation::Equal
175 } else if a_inside_b {
176 let boundaries_touch = check_boundary_touch(region_a, region_b);
178 if boundaries_touch {
179 Rcc8Relation::TangentiallyProperPart
180 } else {
181 Rcc8Relation::NonTangentialProperPart
182 }
183 } else if b_inside_a {
184 let boundaries_touch = check_boundary_touch(region_a, region_b);
185 if boundaries_touch {
186 Rcc8Relation::TangentiallyProperPartInverse
187 } else {
188 Rcc8Relation::NonTangentialProperPartInverse
189 }
190 } else {
191 let interior_overlap = check_boundary_touch(region_a, region_b);
198 if interior_overlap {
199 Rcc8Relation::PartiallyOverlapping
200 } else {
201 Rcc8Relation::ExternallyConnected
202 }
203 }
204}
205
206fn check_boundary_touch(region_a: &SpatialRegion, region_b: &SpatialRegion) -> bool {
208 for &point_a in ®ion_a.boundary_points {
211 if region_b.contains_point(point_a) {
212 return true;
213 }
214 }
215
216 for &point_b in ®ion_b.boundary_points {
217 if region_a.contains_point(point_b) {
218 return true;
219 }
220 }
221
222 false
223}
224
225pub const MAX_BOUNDARY_POINTS: usize = 64;
234
235const POINT_SCALE: f64 = 1_000_000.0;
237const GEO_EPS: f64 = 1e-9;
238
239pub fn pack_point(x: f64, y: f64) -> u64 {
242 let xi = (x * POINT_SCALE).round() as i32 as u32 as u64;
243 let yi = (y * POINT_SCALE).round() as i32 as u32 as u64;
244 (xi << 32) | yi
245}
246
247pub fn unpack_point(packed: u64) -> (f64, f64) {
249 let xi = (packed >> 32) as u32 as i32;
250 let yi = (packed & 0xFFFF_FFFF) as u32 as i32;
251 (xi as f64 / POINT_SCALE, yi as f64 / POINT_SCALE)
252}
253
254fn bbox(poly: &[(f64, f64)]) -> (f64, f64, f64, f64) {
255 let mut mnx = f64::INFINITY;
256 let mut mxx = f64::NEG_INFINITY;
257 let mut mny = f64::INFINITY;
258 let mut mxy = f64::NEG_INFINITY;
259 for &(x, y) in poly {
260 mnx = mnx.min(x);
261 mxx = mxx.max(x);
262 mny = mny.min(y);
263 mxy = mxy.max(y);
264 }
265 (mnx, mxx, mny, mxy)
266}
267
268fn bbox_overlap(a: &[(f64, f64)], b: &[(f64, f64)]) -> bool {
269 let (amnx, amxx, amny, amxy) = bbox(a);
270 let (bmnx, bmxx, bmny, bmxy) = bbox(b);
271 !(amnx > bmxx || amxx < bmnx || amny > bmxy || amxy < bmny)
272}
273
274fn point_in_interior(p: (f64, f64), poly: &[(f64, f64)]) -> bool {
276 if poly.len() < 3 {
277 return false;
278 }
279 let (x, y) = p;
280 let mut inside = false;
281 let n = poly.len();
282 for i in 0..n {
283 let j = (i + 1) % n;
284 let (xi, yi) = poly[i];
285 let (xj, yj) = poly[j];
286 if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
287 inside = !inside;
288 }
289 }
290 inside
291}
292
293fn point_on_boundary(p: (f64, f64), poly: &[(f64, f64)]) -> bool {
295 let n = poly.len();
296 if n < 2 {
297 return false;
298 }
299 for i in 0..n {
300 let a = poly[i];
301 let b = poly[(i + 1) % n];
302 let cross = (b.0 - a.0) * (p.1 - a.1) - (b.1 - a.1) * (p.0 - a.0);
304 if cross.abs() <= GEO_EPS
305 && p.0 >= a.0.min(b.0) - GEO_EPS
306 && p.0 <= a.0.max(b.0) + GEO_EPS
307 && p.1 >= a.1.min(b.1) - GEO_EPS
308 && p.1 <= a.1.max(b.1) + GEO_EPS
309 {
310 return true;
311 }
312 }
313 false
314}
315
316#[inline]
317fn inside_or_on(p: (f64, f64), poly: &[(f64, f64)]) -> bool {
318 point_in_interior(p, poly) || point_on_boundary(p, poly)
319}
320
321fn all_inside_or_on(pts: &[(f64, f64)], poly: &[(f64, f64)]) -> bool {
322 !pts.is_empty() && pts.iter().all(|&p| inside_or_on(p, poly))
323}
324
325fn any_on_boundary(pts: &[(f64, f64)], poly: &[(f64, f64)]) -> bool {
326 pts.iter().any(|&p| point_on_boundary(p, poly))
327}
328
329fn interiors_overlap(a: &[(f64, f64)], b: &[(f64, f64)]) -> bool {
330 a.iter().any(|&p| point_in_interior(p, b)) || b.iter().any(|&p| point_in_interior(p, a))
331}
332
333pub fn evaluate_rcc8_points(
338 id_a: u64,
339 a: &[(f64, f64)],
340 id_b: u64,
341 b: &[(f64, f64)],
342) -> Rcc8Relation {
343 if id_a == id_b {
344 return Rcc8Relation::Equal;
345 }
346 if !bbox_overlap(a, b) {
347 return Rcc8Relation::Disconnected;
348 }
349 let a_in_b = all_inside_or_on(a, b);
350 let b_in_a = all_inside_or_on(b, a);
351 if a_in_b && b_in_a {
352 return Rcc8Relation::Equal;
353 }
354 if a_in_b {
355 return if any_on_boundary(a, b) {
356 Rcc8Relation::TangentiallyProperPart
357 } else {
358 Rcc8Relation::NonTangentialProperPart
359 };
360 }
361 if b_in_a {
362 return if any_on_boundary(b, a) {
363 Rcc8Relation::TangentiallyProperPartInverse
364 } else {
365 Rcc8Relation::NonTangentialProperPartInverse
366 };
367 }
368 if interiors_overlap(a, b) {
369 Rcc8Relation::PartiallyOverlapping
370 } else {
371 Rcc8Relation::ExternallyConnected
373 }
374}
375
376pub fn evaluate_temporal(
378 op: TemporalOp,
379 t1_start: i64,
380 t1_end: i64,
381 t2_start: i64,
382 t2_end: i64,
383) -> bool {
384 match op {
385 TemporalOp::Before => t1_end < t2_start,
386 TemporalOp::Meets => t1_end == t2_start,
387 TemporalOp::Overlaps => t1_start < t2_start && t1_end > t2_start && t1_end < t2_end,
388 TemporalOp::Starts => t1_start == t2_start && t1_end < t2_end,
389 TemporalOp::During => t1_start > t2_start && t1_end < t2_end,
390 TemporalOp::Finishes => t1_end == t2_end && t1_start > t2_start,
391 TemporalOp::Equals => t1_start == t2_start && t1_end == t2_end,
392 }
393}
394
395const SPATIAL_SCALE: f64 = 1_000.0;
400
401pub fn region_to_quin(region: &SpatialRegion, context: u64) -> NQuin {
406 let subject = region.region_id;
407 let predicate = crate::q_hash("has_spatial_region");
408
409 let cx = (region.centroid.0 * SPATIAL_SCALE).round() as u64;
412 let cy = (region.centroid.1 * SPATIAL_SCALE).round() as u64;
413 let ar = (region.area * SPATIAL_SCALE).round() as u64;
414
415 let object = ((cx & 0xFFFF) << 48) | ((cy & 0xFFFF) << 32) | (ar & 0xFFFF_FFFF);
416
417 let mut quin = NQuin {
418 subject,
419 predicate,
420 object,
421 context,
422 metadata: 0,
423 parity: 0,
424 };
425
426 quin.parity = quin.subject ^ quin.predicate ^ quin.object ^ quin.context ^ quin.metadata;
428
429 quin
430}
431
432pub fn quin_to_region(quin: &NQuin) -> Option<SpatialRegion> {
434 let centroid_x = ((quin.object >> 48) & 0xFFFF) as f64 / SPATIAL_SCALE;
436 let centroid_y = ((quin.object >> 32) & 0xFFFF) as f64 / SPATIAL_SCALE;
437 let area = (quin.object & 0xFFFF_FFFF) as f64 / SPATIAL_SCALE;
438
439 let region_id = quin.subject;
441
442 Some(SpatialRegion {
443 region_id,
444 boundary_points: vec![], centroid: (centroid_x, centroid_y),
446 area,
447 })
448}
449
450pub type Aabb = (f64, f64, f64, f64);
457
458pub fn polygon_aabb(poly: &[(f64, f64)]) -> Aabb {
460 let (mnx, mxx, mny, mxy) = bbox(poly);
461 (mnx, mny, mxx, mxy)
462}
463
464#[inline]
466pub fn aabb_overlap(a: Aabb, b: Aabb) -> bool {
467 !(a.0 > b.2 || a.2 < b.0 || a.1 > b.3 || a.3 < b.1)
468}
469
470pub fn spatial_index_query(query: Aabb, region_boxes: &[Aabb], out: &mut [usize]) -> usize {
474 let mut n = 0usize;
475 for (i, &b) in region_boxes.iter().enumerate() {
476 if aabb_overlap(query, b) {
477 if n >= out.len() {
478 break;
479 }
480 out[n] = i;
481 n += 1;
482 }
483 }
484 n
485}
486
487pub fn minkowski_interval(dt: f64, dx: f64, dy: f64, dz: f64, c: f64) -> f64 {
492 -(c * c) * dt * dt + dx * dx + dy * dy + dz * dz
493}
494
495pub fn causally_connectable(dt: f64, dx: f64, dy: f64, dz: f64, c: f64) -> bool {
499 minkowski_interval(dt, dx, dy, dz, c) <= GEO_EPS
500}
501
502pub fn heat_equation_step(u: &[f64], alpha: f64, dt: f64, dx: f64, out: &mut [f64]) -> bool {
508 let n = u.len();
509 if n < 2 || out.len() < n || dx == 0.0 {
510 return false;
511 }
512 let r = alpha * dt / (dx * dx);
513 out[0] = u[0];
514 out[n - 1] = u[n - 1];
515 for i in 1..n - 1 {
516 out[i] = u[i] + r * (u[i - 1] - 2.0 * u[i] + u[i + 1]);
517 }
518 true
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_rcc8_basic_relations() {
527 let region_a = SpatialRegion::new(1, vec![(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)]);
528 let region_b = SpatialRegion::new(2, vec![(1.0, 1.0), (3.0, 1.0), (3.0, 3.0), (1.0, 3.0)]);
529
530 let relation = evaluate_rcc8(®ion_a, ®ion_b);
531 assert_eq!(relation, Rcc8Relation::PartiallyOverlapping);
532 }
533
534 #[test]
535 fn test_rcc8_points_zero_heap() {
536 let big = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
538 let small = [(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0)];
539 assert_eq!(
541 evaluate_rcc8_points(1, &small, 2, &big),
542 Rcc8Relation::NonTangentialProperPart
543 );
544 assert_eq!(
545 evaluate_rcc8_points(2, &big, 1, &small),
546 Rcc8Relation::NonTangentialProperPartInverse
547 );
548
549 let far = [(20.0, 20.0), (22.0, 20.0), (22.0, 22.0), (20.0, 22.0)];
551 assert_eq!(
552 evaluate_rcc8_points(1, &big, 3, &far),
553 Rcc8Relation::Disconnected
554 );
555
556 let a = [(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)];
558 let b = [(2.0, 2.0), (6.0, 2.0), (6.0, 6.0), (2.0, 6.0)];
559 assert_eq!(
560 evaluate_rcc8_points(1, &a, 2, &b),
561 Rcc8Relation::PartiallyOverlapping
562 );
563
564 assert_eq!(evaluate_rcc8_points(5, &a, 5, &b), Rcc8Relation::Equal);
566
567 let (x, y) = unpack_point(pack_point(-45.123456, 170.654321));
569 assert!((x + 45.123456).abs() < 1e-5 && (y - 170.654321).abs() < 1e-5);
570 }
571
572 #[test]
573 fn test_temporal_relations() {
574 assert!(evaluate_temporal(TemporalOp::Before, 0, 10, 15, 25));
575 assert!(evaluate_temporal(TemporalOp::Meets, 0, 10, 10, 20));
576 assert!(evaluate_temporal(TemporalOp::Overlaps, 0, 15, 10, 25));
577 assert!(evaluate_temporal(TemporalOp::During, 5, 15, 0, 25));
578 }
579
580 #[test]
581 fn test_region_quin_conversion() {
582 let region = SpatialRegion::new(42, vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]);
583 let quin = region_to_quin(®ion, 123);
584 let extracted = quin_to_region(&quin).unwrap();
585
586 assert_eq!(extracted.region_id, region.region_id);
587 assert_eq!(extracted.centroid, region.centroid);
588 assert_eq!(extracted.area, region.area);
589 }
590
591 #[test]
592 fn spatial_index_broad_phase() {
593 let boxes = [
595 (0.0, 0.0, 1.0, 1.0),
596 (2.0, 2.0, 3.0, 3.0),
597 (0.5, 0.5, 2.5, 2.5),
598 ];
599 let query = (0.8, 0.8, 1.2, 1.2);
600 let mut out = [0usize; 8];
601 let n = spatial_index_query(query, &boxes, &mut out);
602 assert_eq!(n, 2, "query overlaps box 0 and box 2, not box 1");
603 assert!(out[..n].contains(&0) && out[..n].contains(&2) && !out[..n].contains(&1));
604 assert_eq!(
606 polygon_aabb(&[(0.0, 0.0), (2.0, 0.0), (2.0, 1.0)]),
607 (0.0, 0.0, 2.0, 1.0)
608 );
609 }
610
611 #[test]
612 fn minkowski_light_cone_classification() {
613 let c = 1.0;
614 assert!(minkowski_interval(2.0, 1.0, 0.0, 0.0, c) < 0.0);
616 assert!(causally_connectable(2.0, 1.0, 0.0, 0.0, c));
617 assert!(minkowski_interval(1.0, 1.0, 0.0, 0.0, c).abs() < 1e-9);
619 assert!(causally_connectable(1.0, 1.0, 0.0, 0.0, c));
620 assert!(minkowski_interval(1.0, 3.0, 0.0, 0.0, c) > 0.0);
622 assert!(!causally_connectable(1.0, 3.0, 0.0, 0.0, c));
623 }
624
625 #[test]
626 fn heat_pde_step_diffuses_and_conserves() {
627 let u = [0.0, 0.0, 1.0, 0.0, 0.0];
629 let mut out = [0.0f64; 5];
630 assert!(heat_equation_step(&u, 0.25, 1.0, 1.0, &mut out));
631 assert!((out[2] - 0.5).abs() < 1e-9);
633 assert!((out[1] - 0.25).abs() < 1e-9 && (out[3] - 0.25).abs() < 1e-9);
634 assert_eq!(out[0], 0.0);
635 let sum: f64 = out.iter().sum();
637 assert!((sum - 1.0).abs() < 1e-9);
638 assert!(!heat_equation_step(&u, 0.25, 1.0, 0.0, &mut out));
640 }
641}