pcalc.presence_invariant
1# -*- coding: utf-8 -*- 2# Copyright (c) 2025 Krishna Kumar 3# SPDX-License-Identifier: MIT 4 5from typing import Tuple 6from .basis_topology import BasisTopology 7from .presence import PresenceAssertion 8 9 10class PresenceInvariant: 11 """ 12 Computes the components of the Presence Invariant directly from a BasisTopology. 13 14 This invariant expresses a local conservation law of presence mass over any finite 15 time interval [t0, t1), using only the closure of the topology. 16 """ 17 18 def __init__(self, topology: BasisTopology): 19 self.topology = topology 20 self.closed_presences = topology.closure() 21 22 def _filter_window(self, t0: float, t1: float) -> list[PresenceAssertion]: 23 return [ 24 p for p in self.closed_presences 25 if p.overlaps(t0,t1) 26 ] 27 28 def get_presence_summary(self, t0: float, t1: float) -> Tuple[float, int, float]: 29 """ 30 Computes: 31 - Total presence mass A 32 - Number of overlapping presences N 33 - Interval length T 34 """ 35 if t0 >= t1: 36 return 0.0, 0, 0.0 37 38 A = 0.0 39 N = 0 40 T = t1 - t0 41 42 for p in self._filter_window(t0, t1): 43 r = p.mass_contribution(t0, t1) 44 if r > 0.0: 45 A += r 46 N += 1 47 48 return A, N, T 49 50 def avg_presence_density(self, t0: float, t1: float) -> float: 51 A, _, T = self.get_presence_summary(t0, t1) 52 return A / T if T > 0 else 0.0 53 54 def incidence_rate(self, t0: float, t1: float) -> float: 55 _, N, T = self.get_presence_summary(t0, t1) 56 return N / T if T > 0 else 0.0 57 58 59 def avg_presence_mass(self, t0: float, t1: float) -> float: 60 A, N, _ = self.get_presence_summary(t0, t1) 61 return A / N if N > 0 else 0.0 62 63 def invariant(self, t0: float, t1: float) -> Tuple[float, float, float]: 64 """ 65 Returns: 66 (avg_presence_density, incidence_rate, avg_presence_mass) 67 68 These satisfy the Presence Invariant: 69 avg_presence_density = incidence_rate × avg_presence_mass 70 """ 71 A, N, T = self.get_presence_summary(t0, t1) 72 L = A / T if T > 0 else 0.0 73 Λ = N / T if T > 0 else 0.0 74 w = A / N if N > 0 else 0.0 75 return L, Λ, w
class
PresenceInvariant:
11class PresenceInvariant: 12 """ 13 Computes the components of the Presence Invariant directly from a BasisTopology. 14 15 This invariant expresses a local conservation law of presence mass over any finite 16 time interval [t0, t1), using only the closure of the topology. 17 """ 18 19 def __init__(self, topology: BasisTopology): 20 self.topology = topology 21 self.closed_presences = topology.closure() 22 23 def _filter_window(self, t0: float, t1: float) -> list[PresenceAssertion]: 24 return [ 25 p for p in self.closed_presences 26 if p.overlaps(t0,t1) 27 ] 28 29 def get_presence_summary(self, t0: float, t1: float) -> Tuple[float, int, float]: 30 """ 31 Computes: 32 - Total presence mass A 33 - Number of overlapping presences N 34 - Interval length T 35 """ 36 if t0 >= t1: 37 return 0.0, 0, 0.0 38 39 A = 0.0 40 N = 0 41 T = t1 - t0 42 43 for p in self._filter_window(t0, t1): 44 r = p.mass_contribution(t0, t1) 45 if r > 0.0: 46 A += r 47 N += 1 48 49 return A, N, T 50 51 def avg_presence_density(self, t0: float, t1: float) -> float: 52 A, _, T = self.get_presence_summary(t0, t1) 53 return A / T if T > 0 else 0.0 54 55 def incidence_rate(self, t0: float, t1: float) -> float: 56 _, N, T = self.get_presence_summary(t0, t1) 57 return N / T if T > 0 else 0.0 58 59 60 def avg_presence_mass(self, t0: float, t1: float) -> float: 61 A, N, _ = self.get_presence_summary(t0, t1) 62 return A / N if N > 0 else 0.0 63 64 def invariant(self, t0: float, t1: float) -> Tuple[float, float, float]: 65 """ 66 Returns: 67 (avg_presence_density, incidence_rate, avg_presence_mass) 68 69 These satisfy the Presence Invariant: 70 avg_presence_density = incidence_rate × avg_presence_mass 71 """ 72 A, N, T = self.get_presence_summary(t0, t1) 73 L = A / T if T > 0 else 0.0 74 Λ = N / T if T > 0 else 0.0 75 w = A / N if N > 0 else 0.0 76 return L, Λ, w
Computes the components of the Presence Invariant directly from a BasisTopology.
This invariant expresses a local conservation law of presence mass over any finite time interval [t0, t1), using only the closure of the topology.
PresenceInvariant(topology: pcalc.BasisTopology)
def
get_presence_summary(self, t0: float, t1: float) -> Tuple[float, int, float]:
29 def get_presence_summary(self, t0: float, t1: float) -> Tuple[float, int, float]: 30 """ 31 Computes: 32 - Total presence mass A 33 - Number of overlapping presences N 34 - Interval length T 35 """ 36 if t0 >= t1: 37 return 0.0, 0, 0.0 38 39 A = 0.0 40 N = 0 41 T = t1 - t0 42 43 for p in self._filter_window(t0, t1): 44 r = p.mass_contribution(t0, t1) 45 if r > 0.0: 46 A += r 47 N += 1 48 49 return A, N, T
Computes:
- Total presence mass A
- Number of overlapping presences N
- Interval length T
def
invariant(self, t0: float, t1: float) -> Tuple[float, float, float]:
64 def invariant(self, t0: float, t1: float) -> Tuple[float, float, float]: 65 """ 66 Returns: 67 (avg_presence_density, incidence_rate, avg_presence_mass) 68 69 These satisfy the Presence Invariant: 70 avg_presence_density = incidence_rate × avg_presence_mass 71 """ 72 A, N, T = self.get_presence_summary(t0, t1) 73 L = A / T if T > 0 else 0.0 74 Λ = N / T if T > 0 else 0.0 75 w = A / N if N > 0 else 0.0 76 return L, Λ, w
Returns:
(avg_presence_density, incidence_rate, avg_presence_mass)
These satisfy the Presence Invariant:
avg_presence_density = incidence_rate × avg_presence_mass