Skip to main content

antecedent_core/
ids.rs

1//! Compact, copyable identifiers used throughout causal-library.
2//!
3//! User-facing names live in schemas and dictionaries, not in hot graph or
4//! numerical structures.
5//!
6//! SPDX-License-Identifier: MIT OR Apache-2.0
7
8use core::fmt;
9use core::num::NonZeroU32;
10
11/// Dense variable index assigned by [`crate::schema::CausalSchema`] construction.
12#[repr(transparent)]
13#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
14pub struct VariableId(u32);
15
16impl VariableId {
17    /// Create an identifier from a raw dense index.
18    ///
19    /// Prefer obtaining IDs from schema construction; this constructor exists
20    /// for deserialization and tests.
21    #[must_use]
22    pub const fn from_raw(raw: u32) -> Self {
23        Self(raw)
24    }
25
26    /// Underlying dense index.
27    #[must_use]
28    pub const fn raw(self) -> u32 {
29        self.0
30    }
31
32    /// Convert to `usize` for indexing.
33    #[must_use]
34    pub const fn as_usize(self) -> usize {
35        self.0 as usize
36    }
37}
38
39impl fmt::Display for VariableId {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "V{}", self.0)
42    }
43}
44
45/// Multi-environment partition identifier.
46#[repr(transparent)]
47#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
48pub struct EnvironmentId(u32);
49
50impl EnvironmentId {
51    /// Create from a raw dense index.
52    #[must_use]
53    pub const fn from_raw(raw: u32) -> Self {
54        Self(raw)
55    }
56
57    /// Underlying dense index.
58    #[must_use]
59    pub const fn raw(self) -> u32 {
60        self.0
61    }
62}
63
64impl fmt::Display for EnvironmentId {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(f, "E{}", self.0)
67    }
68}
69
70/// Opaque handle for a rule-driven temporal intervention schedule.
71///
72/// Paired with explicit `active_at` offsets on [`crate::intervention::TemporalPolicy::Dynamic`].
73#[repr(transparent)]
74#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
75pub struct DynamicRuleId(u32);
76
77impl DynamicRuleId {
78    /// Create from a raw dense index.
79    #[must_use]
80    pub const fn from_raw(raw: u32) -> Self {
81        Self(raw)
82    }
83
84    /// Underlying dense index.
85    #[must_use]
86    pub const fn raw(self) -> u32 {
87        self.0
88    }
89}
90
91impl fmt::Display for DynamicRuleId {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "DR{}", self.0)
94    }
95}
96
97/// Handle to a custom target distribution.
98///
99/// Resolved against a caller-supplied registry; sampling is deferred.
100#[repr(transparent)]
101#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
102pub struct DistributionRef(u32);
103
104impl DistributionRef {
105    /// Create from a raw dense index.
106    #[must_use]
107    pub const fn from_raw(raw: u32) -> Self {
108        Self(raw)
109    }
110
111    /// Underlying dense index.
112    #[must_use]
113    pub const fn raw(self) -> u32 {
114        self.0
115    }
116}
117
118impl fmt::Display for DistributionRef {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(f, "DRef{}", self.0)
121    }
122}
123
124/// Regime identifier for regime-aware analysis.
125#[repr(transparent)]
126#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
127pub struct RegimeId(u32);
128
129impl RegimeId {
130    /// Create from a raw dense index.
131    #[must_use]
132    pub const fn from_raw(raw: u32) -> Self {
133        Self(raw)
134    }
135
136    /// Underlying dense index.
137    #[must_use]
138    pub const fn raw(self) -> u32 {
139        self.0
140    }
141}
142
143impl fmt::Display for RegimeId {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        write!(f, "R{}", self.0)
146    }
147}
148
149/// Temporal lag magnitude.
150///
151/// [`Lag::CONTEMPORANEOUS`] (`Lag(0)`) is contemporaneous. Historical nodes use
152/// positive lag values internally. Negative-lag conventions are confined to
153/// import/export adapters.
154#[repr(transparent)]
155#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
156pub struct Lag(u32);
157
158impl Lag {
159    /// Contemporaneous lag (`0`).
160    pub const CONTEMPORANEOUS: Self = Self(0);
161
162    /// Create a lag from a non-negative magnitude.
163    #[must_use]
164    pub const fn from_raw(raw: u32) -> Self {
165        Self(raw)
166    }
167
168    /// Create a positive historical lag.
169    #[must_use]
170    pub const fn historical(steps: NonZeroU32) -> Self {
171        Self(steps.get())
172    }
173
174    /// Underlying magnitude.
175    #[must_use]
176    pub const fn raw(self) -> u32 {
177        self.0
178    }
179
180    /// Whether this lag is contemporaneous.
181    #[must_use]
182    pub const fn is_contemporaneous(self) -> bool {
183        self.0 == 0
184    }
185}
186
187impl fmt::Display for Lag {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        write!(f, "L{}", self.0)
190    }
191}
192
193/// Attribution component (typically a mechanism / node) in change decomposition.
194///
195/// Distinct from [`VariableId`] at the type level so allocation orders cannot
196/// silently mix raw variables with coalition players.
197#[repr(transparent)]
198#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
199pub struct ComponentId(VariableId);
200
201impl ComponentId {
202    /// Wrap a variable as an attribution component (mechanism of that node).
203    #[must_use]
204    pub const fn from_variable(variable: VariableId) -> Self {
205        Self(variable)
206    }
207
208    /// Underlying variable id.
209    #[must_use]
210    pub const fn variable(self) -> VariableId {
211        self.0
212    }
213
214    /// Create from a raw dense variable index.
215    #[must_use]
216    pub const fn from_raw(raw: u32) -> Self {
217        Self(VariableId::from_raw(raw))
218    }
219
220    /// Underlying dense index.
221    #[must_use]
222    pub const fn raw(self) -> u32 {
223        self.0.raw()
224    }
225
226    /// Convert to `usize` for indexing.
227    #[must_use]
228    pub const fn as_usize(self) -> usize {
229        self.0.as_usize()
230    }
231}
232
233impl fmt::Display for ComponentId {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        write!(f, "C{}", self.0.raw())
236    }
237}
238
239impl From<VariableId> for ComponentId {
240    fn from(variable: VariableId) -> Self {
241        Self::from_variable(variable)
242    }
243}
244
245/// Stable handle for an immutable categorical domain stored in a schema.
246#[repr(transparent)]
247#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
248pub struct CategoryDomainId(u32);
249
250impl CategoryDomainId {
251    /// Create from a raw dense index.
252    #[must_use]
253    pub const fn from_raw(raw: u32) -> Self {
254        Self(raw)
255    }
256
257    /// Underlying dense index.
258    #[must_use]
259    pub const fn raw(self) -> u32 {
260        self.0
261    }
262}
263
264impl fmt::Display for CategoryDomainId {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        write!(f, "C{}", self.0)
267    }
268}
269
270/// Registered causal-query handle in incremental state / design objectives.
271#[repr(transparent)]
272#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
273pub struct QueryId(u32);
274
275impl QueryId {
276    /// Create from a raw dense index.
277    #[must_use]
278    pub const fn from_raw(raw: u32) -> Self {
279        Self(raw)
280    }
281
282    /// Underlying dense index.
283    #[must_use]
284    pub const fn raw(self) -> u32 {
285        self.0
286    }
287
288    /// Convert to `usize` for indexing.
289    #[must_use]
290    pub const fn as_usize(self) -> usize {
291        self.0 as usize
292    }
293}
294
295impl fmt::Display for QueryId {
296    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297        write!(f, "Q{}", self.0)
298    }
299}
300
301/// Registered model handle in design objectives / state model stores.
302#[repr(transparent)]
303#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
304pub struct ModelId(u32);
305
306impl ModelId {
307    /// Create from a raw dense index.
308    #[must_use]
309    pub const fn from_raw(raw: u32) -> Self {
310        Self(raw)
311    }
312
313    /// Underlying dense index.
314    #[must_use]
315    pub const fn raw(self) -> u32 {
316        self.0
317    }
318
319    /// Convert to `usize` for indexing.
320    #[must_use]
321    pub const fn as_usize(self) -> usize {
322        self.0 as usize
323    }
324}
325
326impl fmt::Display for ModelId {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        write!(f, "M{}", self.0)
329    }
330}
331
332/// Monotonic antecedent-state version.
333#[repr(transparent)]
334#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
335pub struct StateVersion(u64);
336
337impl StateVersion {
338    /// Initial version.
339    pub const ZERO: Self = Self(0);
340
341    /// Create from a raw counter.
342    #[must_use]
343    pub const fn from_raw(raw: u64) -> Self {
344        Self(raw)
345    }
346
347    /// Underlying counter.
348    #[must_use]
349    pub const fn raw(self) -> u64 {
350        self.0
351    }
352
353    /// Next version after an event application.
354    #[must_use]
355    pub const fn next(self) -> Self {
356        Self(self.0.wrapping_add(1))
357    }
358}
359
360impl fmt::Display for StateVersion {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        write!(f, "S{}", self.0)
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use core::mem::size_of;
370
371    #[test]
372    fn ids_are_copyable_and_compact() {
373        assert_eq!(size_of::<VariableId>(), 4);
374        assert_eq!(size_of::<EnvironmentId>(), 4);
375        assert_eq!(size_of::<DynamicRuleId>(), 4);
376        assert_eq!(size_of::<DistributionRef>(), 4);
377        assert_eq!(size_of::<RegimeId>(), 4);
378        assert_eq!(size_of::<Lag>(), 4);
379        assert_eq!(size_of::<CategoryDomainId>(), 4);
380        assert_eq!(size_of::<ComponentId>(), 4);
381        assert_eq!(size_of::<QueryId>(), 4);
382        assert_eq!(size_of::<ModelId>(), 4);
383        assert_eq!(size_of::<StateVersion>(), 8);
384    }
385
386    #[test]
387    fn lag_zero_is_contemporaneous() {
388        assert!(Lag::CONTEMPORANEOUS.is_contemporaneous());
389        assert!(!Lag::historical(NonZeroU32::new(1).expect("nonzero")).is_contemporaneous());
390    }
391}