ixa/entity/
context_extension.rs

1use std::any::{Any, TypeId};
2use std::hash::Hash;
3
4use crate::entity::entity_set::{EntitySet, EntitySetIterator, SourceSet};
5use crate::entity::events::{EntityCreatedEvent, PartialPropertyChangeEvent};
6use crate::entity::index::{IndexCountResult, IndexSetResult, PropertyIndexType};
7use crate::entity::property::Property;
8use crate::entity::property_list::PropertyList;
9use crate::entity::query::Query;
10use crate::entity::value_change_counter::StratifiedValueChangeCounter;
11use crate::entity::{Entity, EntityId, PopulationIterator};
12use crate::rand::Rng;
13use crate::random::sample_multiple_from_known_length;
14use crate::{warn, Context, ContextRandomExt, ExecutionPhase, IxaError, RngId};
15
16fn handle_periodic_value_change_count_event<E, PL, P, F>(
17    context: &mut Context,
18    period: f64,
19    counter_id: usize,
20    handler: F,
21) where
22    E: Entity,
23    PL: PropertyList<E> + Eq + Hash,
24    P: Property<E> + Eq + Hash,
25    F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
26{
27    let mut counter = {
28        let property_value_store = context.get_property_value_store_mut::<E, P>();
29        let slot = property_value_store
30            .value_change_counters
31            .get_mut(counter_id)
32            .unwrap_or_else(|| {
33                panic!(
34                    "No value change counter found for property {} with counter_id {}",
35                    P::name(),
36                    counter_id
37                )
38            });
39        std::mem::replace(
40            slot.get_mut(),
41            Box::new(StratifiedValueChangeCounter::<E, PL, P>::new()),
42        )
43    };
44
45    {
46        let counter = counter
47            .as_any_mut()
48            .downcast_mut::<StratifiedValueChangeCounter<E, PL, P>>()
49            .unwrap_or_else(|| {
50                panic!(
51                    "Value change counter for property {} and counter_id {} had unexpected type",
52                    P::name(),
53                    counter_id
54                )
55            });
56
57        handler(context, counter);
58        counter.clear();
59    }
60
61    {
62        let property_value_store = context.get_property_value_store_mut::<E, P>();
63        let slot = property_value_store
64            .value_change_counters
65            .get_mut(counter_id)
66            .unwrap_or_else(|| {
67                panic!(
68                    "No value change counter found for property {} with counter_id {}",
69                    P::name(),
70                    counter_id
71                )
72            });
73
74        // Swap back the cleared counter to retain its allocated capacity.
75        let _ = std::mem::replace(slot.get_mut(), counter);
76    }
77
78    if context.remaining_plan_count() == 0 {
79        return;
80    }
81
82    let next_time = context.get_current_time() + period;
83    context.add_plan_with_phase(
84        next_time,
85        move |context| {
86            handle_periodic_value_change_count_event::<E, PL, P, F>(
87                context, period, counter_id, handler,
88            );
89        },
90        ExecutionPhase::Last,
91    );
92}
93
94/// A trait extension for [`Context`] that exposes entity-related
95/// functionality.
96pub trait ContextEntitiesExt {
97    fn add_entity<E: Entity, PL: PropertyList<E>>(
98        &mut self,
99        property_list: PL,
100    ) -> Result<EntityId<E>, IxaError>;
101
102    /// Fetches the property value set for the given `entity_id`.
103    ///
104    /// The easiest way to call this method is by assigning it to a variable with an explicit type:
105    /// ```rust, ignore
106    /// let vaccine_status: VaccineStatus = context.get_property(entity_id);
107    /// ```
108    fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
109
110    /// Sets the value of the given property. This method unconditionally emits a `PropertyChangeEvent`.
111    fn set_property<E: Entity, P: Property<E>>(
112        &mut self,
113        entity_id: EntityId<E>,
114        property_value: P,
115    );
116
117    /// Enables full indexing of property values for the property `P`.
118    ///
119    /// This method is called with the turbo-fish syntax:
120    ///     `context.index_property::<Person, Age>()`
121    ///
122    /// This method both enables the index and catches it up to the current population.
123    fn index_property<E: Entity, P: Property<E>>(&mut self);
124
125    /// Enables value-count indexing of property values for the property `P`.
126    ///
127    /// If the property already has a full index, that index is left unchanged, as it
128    /// already supports value-count queries.
129    fn index_property_counts<E: Entity, P: Property<E>>(&mut self);
130
131    /// Tracks periodic value change counts for a newly created counter.
132    ///
133    /// Also panics if `period` is not finite and strictly positive.
134    ///
135    /// Recording starts at `ExecutionPhase::First` at simulation start time. The
136    /// first report runs at simulation start time in `ExecutionPhase::Last`, then at
137    /// each subsequent `start_time + k * period`. After the handler returns, the
138    /// matched counter is cleared.
139    ///
140    /// ```rust,ignore
141    /// context.track_periodic_value_change_counts::<Person, (InfectionStatus,), Age>(
142    ///     30.0,
143    ///     |_context, counter| {
144    ///         let _ = counter;
145    ///     },
146    /// );
147    /// ```
148    fn track_periodic_value_change_counts<E, PL, P, F>(&mut self, period: f64, handler: F)
149    where
150        E: Entity,
151        PL: PropertyList<E> + Eq + Hash,
152        P: Property<E> + Eq + Hash,
153        F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static;
154
155    /// Checks if a property `P` is indexed.
156    ///
157    /// This method is called with the turbo-fish syntax:
158    ///     `context.index_property::<Person, Age>()`
159    ///
160    /// This method can return `true` even if `context.index_property::<P>()` has never been called. For example,
161    /// if a multi-property is indexed, all equivalent multi-properties are automatically also indexed, as they
162    /// share a single index.
163    #[cfg(test)]
164    fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool;
165
166    /// This method gives client code direct access to the query result as an `EntitySet`.
167    /// This is especially efficient for indexed queries, as this method can reduce to wrapping
168    /// a single indexed source.
169    fn with_query_results<'a, E: Entity, Q: Query<E>>(
170        &'a self,
171        query: Q,
172        callback: &mut dyn FnMut(EntitySet<'a, E>),
173    );
174
175    /// Gives the count of distinct entity IDs satisfying the query. This is especially
176    /// efficient for indexed queries.
177    ///
178    /// Supplying an empty query `()` is equivalent to calling `get_entity_count::<E>()`.
179    fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize;
180
181    /// Sample a single entity uniformly from the query results. Returns `None` if the
182    /// query's result set is empty.
183    ///
184    /// To sample from the entire population, pass in the empty query `()`.
185    fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
186    where
187        E: Entity,
188        Q: Query<E>,
189        R: RngId + 'static,
190        R::RngType: Rng;
191
192    /// Count query results and sample a single entity uniformly from them.
193    ///
194    /// Returns `(count, sample)`, where `sample` is `None` iff `count == 0`.
195    /// To sample from the entire population, pass in the empty query `()`.
196    fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
197    where
198        E: Entity,
199        Q: Query<E>,
200        R: RngId + 'static,
201        R::RngType: Rng;
202
203    /// Sample up to `requested` entities uniformly from the query results. If the
204    /// query's result set has fewer than `requested` entities, the entire result
205    /// set is returned.
206    ///
207    /// To sample from the entire population, pass in the empty query `()`.
208    fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
209    where
210        E: Entity,
211        Q: Query<E>,
212        R: RngId + 'static,
213        R::RngType: Rng;
214
215    /// Returns a total count of all created entities of type `E`.
216    fn get_entity_count<E: Entity>(&self) -> usize;
217
218    /// Returns an iterator over all created entities of type `E`.
219    fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E>;
220
221    /// Generates an `EntitySet` representing the query results.
222    fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E>;
223
224    /// Generates an iterator over the results of the query.
225    fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E>;
226
227    /// Determines if the given person matches this query.
228    fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool;
229
230    /// Removes all `EntityId`s from the given vector that do not match the given query.
231    fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q);
232}
233
234impl ContextEntitiesExt for Context {
235    fn add_entity<E: Entity, PL: PropertyList<E>>(
236        &mut self,
237        property_list: PL,
238    ) -> Result<EntityId<E>, IxaError> {
239        // Check that the properties in the list are distinct.
240        PL::validate()?;
241
242        // Check that all required properties are present.
243        if !PL::contains_required_properties() {
244            return Err(IxaError::MissingRequiredInitializationProperties);
245        }
246
247        // Now that we know we will succeed, we create the entity.
248        let new_entity_id = self.entity_store.new_entity_id::<E>();
249
250        // Assign the properties in the list to the new entity.
251        // This does not generate a property change event.
252        property_list.set_values_for_new_entity(
253            new_entity_id,
254            self.entity_store.get_property_store_mut::<E>(),
255        );
256
257        // Keep all enabled indexes caught up as entities are created.
258        let context_ptr: *const Context = self;
259        let property_store = self.entity_store.get_property_store_mut::<E>();
260        // SAFETY: We create a shared `&Context` for read-only property access while mutably
261        // borrowing the property store to update index internals.
262        unsafe {
263            property_store.index_unindexed_entities_for_all_properties(&*context_ptr);
264        }
265
266        // Emit an `EntityCreatedEvent<Entity>`.
267        self.emit_event(EntityCreatedEvent::<E>::new(new_entity_id));
268
269        Ok(new_entity_id)
270    }
271
272    fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P {
273        if P::is_derived() {
274            P::compute_derived(self, entity_id)
275        } else {
276            let property_store = self.get_property_value_store::<E, P>();
277            property_store.get(entity_id)
278        }
279    }
280
281    fn set_property<E: Entity, P: Property<E>>(
282        &mut self,
283        entity_id: EntityId<E>,
284        property_value: P,
285    ) {
286        debug_assert!(!P::is_derived(), "cannot set a derived property");
287
288        // The algorithm is as follows:
289        // 1. Snapshot previous values for the main property and its dependents by creating
290        //    `PartialPropertyChangeEvent` instances.
291        // 2. Set the new value of the main property in the property store.
292        // 3. Emit each partial event; during emission each event computes the current value,
293        //    updates its index (remove old/add new), and emits a `PropertyChangeEvent`.
294
295        // We need two passes over the dependents: one pass to compute all the old values and
296        // another to compute all the new values. We group the steps for each dependent (and, it
297        // turns out, for the main property `P` as well) into two parts:
298        //  1. Before setting the main property `P`, factored out into
299        //     `self.property_store.create_partial_property_change`
300        //  2. After setting the main property `P`, factored out into
301        //     `PartialPropertyChangeEvent::emit_in_context`
302
303        // We decided not to do the following check:
304        // ```rust
305        // let previous_value = { self.get_property_value_store::<E, P>().get(entity_id) };
306        // if property_value == previous_value {
307        //     return;
308        // }
309        // ```
310        // The reasoning is:
311        // - It should be rare that we ever set a property to its present value.
312        // - It's not a significant burden on client code to check `property_value == previous_value` on
313        //   their own if they need to.
314        // - There may be use cases for listening to "writes" that don't actually change values.
315
316        let mut dependents: Vec<Box<dyn PartialPropertyChangeEvent>> = vec![];
317
318        // Immutable: Collect the previous value to create partial property change events
319        {
320            let property_store = self.entity_store.get_property_store::<E>();
321
322            // Create the partial property change for this value.
323            dependents.push(property_store.create_partial_property_change(
324                P::id(),
325                entity_id,
326                self,
327            ));
328            // Now create partial property change events for each dependent.
329            for dependent_idx in P::dependents() {
330                dependents.push(property_store.create_partial_property_change(
331                    *dependent_idx,
332                    entity_id,
333                    self,
334                ));
335            }
336        }
337
338        // Update the value
339        let property_value_store = self.get_property_value_store::<E, P>();
340        property_value_store.set(entity_id, property_value);
341
342        // Mutable: After updating the value, we update its dependents, removing old values and
343        // storing the new values in their respective indexes, and emit the property change event.
344        for dependent in dependents.into_iter() {
345            dependent.emit_in_context(self)
346        }
347    }
348
349    fn index_property<E: Entity, P: Property<E>>(&mut self) {
350        let property_id = P::index_id();
351        let context_ptr: *const Context = self;
352        let property_store = self.entity_store.get_property_store_mut::<E>();
353        property_store.set_property_indexed::<P>(PropertyIndexType::FullIndex);
354        // SAFETY: This only creates a shared reference to `Context` while mutably borrowing
355        // the property store to update index internals.
356        unsafe {
357            property_store.index_unindexed_entities_for_property_id(&*context_ptr, property_id);
358        }
359    }
360
361    fn index_property_counts<E: Entity, P: Property<E>>(&mut self) {
362        let property_store = self.entity_store.get_property_store_mut::<E>();
363        let current_index_type = property_store.get::<P>().index_type();
364        if current_index_type != PropertyIndexType::FullIndex {
365            property_store.set_property_indexed::<P>(PropertyIndexType::ValueCountIndex);
366        }
367    }
368
369    fn track_periodic_value_change_counts<E, PL, P, F>(&mut self, period: f64, handler: F)
370    where
371        E: Entity,
372        PL: PropertyList<E> + Eq + Hash,
373        P: Property<E> + Eq + Hash,
374        F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
375    {
376        assert!(
377            period > 0.0 && !period.is_nan() && !period.is_infinite(),
378            "Period must be greater than 0"
379        );
380        let start_time = self.get_start_time().unwrap_or(0.0);
381        self.add_plan_with_phase(
382            start_time,
383            move |context| {
384                // We create the counter at simulation start so initialization-time
385                // property writes are never recorded.
386                let counter_id = context
387                    .entity_store
388                    .get_property_store_mut::<E>()
389                    .create_value_change_counter::<PL, P>();
390
391                // We defer the first handler plan until now because it needs
392                // `counter_id`, and it must run in `ExecutionPhase::Last`.
393                context.add_plan_with_phase(
394                    context.get_current_time(),
395                    move |context| {
396                        handle_periodic_value_change_count_event::<E, PL, P, F>(
397                            context, period, counter_id, handler,
398                        );
399                    },
400                    ExecutionPhase::Last,
401                );
402            },
403            ExecutionPhase::First,
404        );
405    }
406
407    #[cfg(test)]
408    fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool {
409        let property_store = self.entity_store.get_property_store::<E>();
410        property_store.is_property_indexed::<P>()
411    }
412
413    fn with_query_results<'a, E: Entity, Q: Query<E>>(
414        &'a self,
415        query: Q,
416        callback: &mut dyn FnMut(EntitySet<'a, E>),
417    ) {
418        // The fast path for indexed queries.
419
420        // This mirrors the indexed case in `SourceSet<'a, E>::new()` and `Query::new_query_result`.
421        // The difference is, we access the index set if we find it.
422        if let Some(multi_property_id) = query.multi_property_id() {
423            let property_store = self.entity_store.get_property_store::<E>();
424            match property_store.get_index_set_with_hash_for_property_id(
425                multi_property_id,
426                query.multi_property_value_hash(),
427            ) {
428                IndexSetResult::Set(people_set) => {
429                    callback(EntitySet::from_source(SourceSet::IndexSet(people_set)));
430                    return;
431                }
432                IndexSetResult::Empty => {
433                    callback(EntitySet::empty());
434                    return;
435                }
436                IndexSetResult::Unsupported => {}
437            }
438            // If the property is not indexed, we fall through.
439        }
440
441        // Special case the empty query, which creates a set containing the entire population.
442        if query.type_id() == TypeId::of::<()>() {
443            warn!("Called Context::with_query_results() with an empty query. Prefer Context::get_entity_iterator::<E>() for working with the entire population.");
444            callback(EntitySet::from_source(SourceSet::Population(
445                self.get_entity_count::<E>(),
446            )));
447            return;
448        }
449
450        // The slow path of computing the full query set.
451        warn!("Called Context::with_query_results() with an unindexed query. It's almost always better to use Context::query_result_iterator() for unindexed queries.");
452
453        // Fall back to the query's `EntitySet`.
454        callback(self.query(query));
455    }
456
457    fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize {
458        // The fast path for indexed queries.
459        //
460        // This mirrors the indexed case in `SourceSet<'a, E>::new()` and `Query::new_query_result`.
461        if let Some(multi_property_id) = query.multi_property_id() {
462            let property_store = self.entity_store.get_property_store::<E>();
463            match property_store.get_index_count_with_hash_for_property_id(
464                multi_property_id,
465                query.multi_property_value_hash(),
466            ) {
467                IndexCountResult::Count(count) => return count,
468                IndexCountResult::Unsupported => {}
469            }
470            // If the property is not indexed, we fall through.
471        }
472
473        self.query_result_iterator(query).count()
474    }
475    fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
476    where
477        E: Entity,
478        Q: Query<E>,
479        R: RngId + 'static,
480        R::RngType: Rng,
481    {
482        if query.type_id() == TypeId::of::<()>() {
483            let population = self.get_entity_count::<E>();
484            return self.sample(rng_id, move |rng| {
485                if population == 0 {
486                    warn!("Requested a sample entity from an empty population");
487                    return None;
488                }
489                let index = if population <= u32::MAX as usize {
490                    rng.random_range(0..population as u32) as usize
491                } else {
492                    rng.random_range(0..population)
493                };
494                Some(EntityId::new(index))
495            });
496        }
497
498        let query_result = self.query_result_iterator(query);
499        self.sample(rng_id, move |rng| query_result.sample_entity(rng))
500    }
501
502    fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
503    where
504        E: Entity,
505        Q: Query<E>,
506        R: RngId + 'static,
507        R::RngType: Rng,
508    {
509        if query.type_id() == TypeId::of::<()>() {
510            let population = self.get_entity_count::<E>();
511            return self.sample(rng_id, move |rng| {
512                if population == 0 {
513                    return (0, None);
514                }
515                let index = if population <= u32::MAX as usize {
516                    rng.random_range(0..population as u32) as usize
517                } else {
518                    rng.random_range(0..population)
519                };
520                (population, Some(EntityId::new(index)))
521            });
522        }
523
524        let query_result = self.query_result_iterator(query);
525        self.sample(rng_id, move |rng| query_result.count_and_sample_entity(rng))
526    }
527
528    fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
529    where
530        E: Entity,
531        Q: Query<E>,
532        R: RngId + 'static,
533        R::RngType: Rng,
534    {
535        if query.type_id() == TypeId::of::<()>() {
536            let population = self.get_entity_count::<E>();
537            return self.sample(rng_id, move |rng| {
538                if population == 0 {
539                    warn!("Requested a sample of entities from an empty population");
540                    return vec![];
541                }
542                if n >= population {
543                    return PopulationIterator::<E>::new(population).collect();
544                }
545                sample_multiple_from_known_length(rng, PopulationIterator::<E>::new(population), n)
546            });
547        }
548
549        let query_result = self.query_result_iterator(query);
550        self.sample(rng_id, move |rng| query_result.sample_entities(rng, n))
551    }
552
553    fn get_entity_count<E: Entity>(&self) -> usize {
554        self.entity_store.get_entity_count::<E>()
555    }
556
557    fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
558        self.entity_store.get_entity_iterator::<E>()
559    }
560
561    fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E> {
562        query.new_query_result(self)
563    }
564
565    fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E> {
566        query.new_query_result_iterator(self)
567    }
568
569    fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool {
570        query.match_entity(entity_id, self)
571    }
572
573    fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q) {
574        query.filter_entities(entities, self);
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use std::cell::RefCell;
581    use std::rc::Rc;
582
583    use serde::Serialize;
584
585    use super::*;
586    use crate::hashing::IndexSet;
587    use crate::prelude::PropertyChangeEvent;
588    use crate::{
589        define_derived_property, define_entity, define_multi_property, define_property, define_rng,
590        impl_property,
591    };
592
593    define_entity!(Animal);
594    define_property!(struct Legs(u8), Animal, default_const = Legs(4));
595    define_rng!(EntityContextTestRng);
596
597    define_entity!(Person);
598
599    define_property!(struct Age(u8), Person);
600
601    #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)]
602    struct CounterValue(u8);
603    impl_property!(CounterValue, Person, default_const = CounterValue(0));
604
605    #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)]
606    struct CounterStratum(bool);
607    impl_property!(
608        CounterStratum,
609        Person,
610        default_const = CounterStratum(false)
611    );
612
613    define_property!(
614        enum InfectionStatus {
615            Susceptible,
616            Infected,
617            Recovered,
618        },
619        Person,
620        default_const = InfectionStatus::Susceptible
621    );
622
623    define_property!(
624        struct Vaccinated(bool),
625        Person,
626        default_const = Vaccinated(false)
627    );
628
629    define_derived_property!(
630        enum AgeGroup {
631            Child,
632            Adult,
633            Senior,
634        },
635        Person,
636        [Age],
637        |age| {
638            if age.0 <= 18 {
639                AgeGroup::Child
640            } else if age.0 <= 65 {
641                AgeGroup::Adult
642            } else {
643                AgeGroup::Senior
644            }
645        }
646    );
647
648    define_derived_property!(
649        enum RiskLevel {
650            Low,
651            Medium,
652            High,
653        },
654        Person,
655        [AgeGroup, Vaccinated, InfectionStatus],
656        |age_group, vaccinated, infection_status| {
657            match (age_group, vaccinated, infection_status) {
658                (AgeGroup::Senior, Vaccinated(false), InfectionStatus::Susceptible) => {
659                    RiskLevel::High
660                }
661                (_, Vaccinated(false), InfectionStatus::Susceptible) => RiskLevel::Medium,
662                _ => RiskLevel::Low,
663            }
664        }
665    );
666
667    // ToDo(RobertJacobsonCDC): Enable this once #691 is resolved, https://github.com/CDCgov/ixa/issues/691.
668    // define_global_property!(GlobalDummy, u8);
669    // define_derived_property!(
670    //     struct MyDerivedProperty(u8),
671    //     Person,
672    //     [Age],
673    //     [GlobalDummy],
674    //     |age, global_dummy| {
675    //         MyDerivedProperty(age.0 + global_dummy)
676    //     }
677    // );
678
679    // Derived properties in a diamond dependency relationship
680    define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
681    define_property!(struct IsSwimmer(bool), Person, default_const = IsSwimmer(false));
682    define_derived_property!(
683        struct AdultRunner(bool),
684        Person,
685        [AgeGroup, IsRunner],
686        | age_group, is_runner | {
687            AdultRunner(
688                age_group == AgeGroup::Adult
689                && is_runner.0
690            )
691        }
692    );
693    define_derived_property!(
694        struct AdultSwimmer(bool),
695        Person,
696        [AgeGroup, IsSwimmer],
697        | age_group, is_swimmer | {
698            AdultSwimmer(
699                age_group == AgeGroup::Adult
700                && is_swimmer.0
701            )
702        }
703    );
704    define_derived_property!(
705        struct AdultAthlete(bool),
706        Person,
707        [AdultSwimmer, AdultRunner],
708        | adult_swimmer, adult_runner | {
709            AdultAthlete(
710                adult_swimmer.0 || adult_runner.0
711            )
712        }
713    );
714
715    #[test]
716    fn add_and_count_entities() {
717        let mut context = Context::new();
718
719        let _person1 = context
720            .add_entity((Age(12), InfectionStatus::Susceptible, Vaccinated(true)))
721            .unwrap();
722        assert_eq!(context.get_entity_count::<Person>(), 1);
723
724        let _person2 = context.add_entity((Age(34), Vaccinated(true))).unwrap();
725        assert_eq!(context.get_entity_count::<Person>(), 2);
726
727        // Age is the only required property
728        let _person3 = context.add_entity((Age(120),)).unwrap();
729        assert_eq!(context.get_entity_count::<Person>(), 3);
730    }
731
732    #[test]
733    fn add_entity_with_zst() {
734        let mut context = Context::new();
735        let animal = context.add_entity(Animal).unwrap();
736        assert_eq!(context.get_entity_count::<Animal>(), 1);
737        assert_eq!(context.get_property::<Animal, Legs>(animal), Legs(4));
738    }
739
740    // Helper for index tests
741    #[derive(Copy, Clone, Debug)]
742    enum IndexMode {
743        Unindexed,
744        FullIndex,
745        ValueCountIndex,
746    }
747
748    // Returns `(context, existing_value, missing_value)`
749    fn setup_context_for_index_tests(index_mode: IndexMode) -> (Context, Age, Age) {
750        let mut context = Context::new();
751        match index_mode {
752            IndexMode::Unindexed => {}
753            IndexMode::FullIndex => context.index_property::<Person, Age>(),
754            IndexMode::ValueCountIndex => context.index_property_counts::<Person, Age>(),
755        }
756
757        let existing_value = Age(12);
758        let missing_value = Age(99);
759
760        let _ = context.add_entity((existing_value,)).unwrap();
761        let _ = context.add_entity((existing_value,)).unwrap();
762
763        (context, existing_value, missing_value)
764    }
765
766    #[test]
767    fn query_results_respect_index_modes() {
768        let modes = [
769            IndexMode::Unindexed,
770            IndexMode::FullIndex,
771            IndexMode::ValueCountIndex,
772        ];
773
774        for mode in modes {
775            let (context, existing_value, missing_value) = setup_context_for_index_tests(mode);
776
777            let mut existing_len = 0;
778            context.with_query_results((existing_value,), &mut |people_set| {
779                existing_len = people_set.into_iter().count();
780            });
781            assert_eq!(existing_len, 2, "Wrong length for {mode:?}");
782
783            let mut missing_len = 0;
784            context.with_query_results((missing_value,), &mut |people_set| {
785                missing_len = people_set.into_iter().count();
786            });
787            assert_eq!(missing_len, 0);
788
789            let existing_count = context.query_result_iterator((existing_value,)).count();
790            assert_eq!(existing_count, 2);
791
792            let missing_count = context.query_result_iterator((missing_value,)).count();
793            assert_eq!(missing_count, 0);
794
795            assert_eq!(context.query_entity_count((existing_value,)), 2);
796            assert_eq!(context.query_entity_count((missing_value,)), 0);
797        }
798    }
799
800    #[test]
801    fn add_an_entity_without_required_properties() {
802        let mut context = Context::new();
803        let result = context.add_entity((InfectionStatus::Susceptible, Vaccinated(true)));
804
805        assert!(matches!(
806            result,
807            Err(crate::IxaError::MissingRequiredInitializationProperties)
808        ));
809    }
810
811    #[test]
812    fn new_entities_have_default_values() {
813        let mut context = Context::new();
814
815        // Create a person with required Age property
816        let person = context.add_entity((Age(25),)).unwrap();
817
818        // Retrieve and check their values
819        let age: Age = context.get_property(person);
820        assert_eq!(age, Age(25));
821        let infection_status: InfectionStatus = context.get_property(person);
822        assert_eq!(infection_status, InfectionStatus::Susceptible);
823        let vaccinated: Vaccinated = context.get_property(person);
824        assert_eq!(vaccinated, Vaccinated(false));
825
826        // Change them
827        context.set_property(person, Age(26));
828        context.set_property(person, InfectionStatus::Infected);
829        context.set_property(person, Vaccinated(true));
830
831        // Retrieve and check their values
832        let age: Age = context.get_property(person);
833        assert_eq!(age, Age(26));
834        let infection_status: InfectionStatus = context.get_property(person);
835        assert_eq!(infection_status, InfectionStatus::Infected);
836        let vaccinated: Vaccinated = context.get_property(person);
837        assert_eq!(vaccinated, Vaccinated(true));
838    }
839
840    #[test]
841    fn get_and_set_property_explicit() {
842        let mut context = Context::new();
843
844        // Create a person with explicit property values
845        let person = context
846            .add_entity((Age(25), InfectionStatus::Recovered, Vaccinated(true)))
847            .unwrap();
848
849        // Retrieve and check their values
850        let age: Age = context.get_property(person);
851        assert_eq!(age, Age(25));
852        let infection_status: InfectionStatus = context.get_property(person);
853        assert_eq!(infection_status, InfectionStatus::Recovered);
854        let vaccinated: Vaccinated = context.get_property(person);
855        assert_eq!(vaccinated, Vaccinated(true));
856
857        // Change them
858        context.set_property(person, Age(26));
859        context.set_property(person, InfectionStatus::Infected);
860        context.set_property(person, Vaccinated(false));
861
862        // Retrieve and check their values
863        let age: Age = context.get_property(person);
864        assert_eq!(age, Age(26));
865        let infection_status: InfectionStatus = context.get_property(person);
866        assert_eq!(infection_status, InfectionStatus::Infected);
867        let vaccinated: Vaccinated = context.get_property(person);
868        assert_eq!(vaccinated, Vaccinated(false));
869    }
870
871    #[test]
872    fn count_entities() {
873        let mut context = Context::new();
874
875        assert_eq!(context.get_entity_count::<Animal>(), 0);
876        assert_eq!(context.get_entity_count::<Person>(), 0);
877
878        // Create entities of different kinds
879        for _ in 0..7 {
880            let _: PersonId = context.add_entity((Age(25),)).unwrap();
881        }
882        for _ in 0..5 {
883            let _: AnimalId = context.add_entity((Legs(2),)).unwrap();
884        }
885
886        assert_eq!(context.get_entity_count::<Animal>(), 5);
887        assert_eq!(context.get_entity_count::<Person>(), 7);
888
889        let _: PersonId = context.add_entity((Age(30),)).unwrap();
890        let _: AnimalId = context.add_entity((Legs(8),)).unwrap();
891
892        assert_eq!(context.get_entity_count::<Animal>(), 6);
893        assert_eq!(context.get_entity_count::<Person>(), 8);
894    }
895
896    #[test]
897    fn count_and_sample_entity_empty_query_fast_path() {
898        let mut context = Context::new();
899        context.init_random(42);
900        for age in [10u8, 20, 30] {
901            let _: PersonId = context.add_entity((Age(age),)).unwrap();
902        }
903
904        let (count, sampled) =
905            context.count_and_sample_entity::<Person, _, _>(EntityContextTestRng, ());
906        assert_eq!(count, 3);
907        assert!(sampled.is_some());
908    }
909
910    #[test]
911    fn count_and_sample_entity_unindexed_derived_query() {
912        let mut context = Context::new();
913        context.init_random(43);
914        for age in [10u8, 20, 30, 80] {
915            let _: PersonId = context.add_entity((Age(age),)).unwrap();
916        }
917
918        let query = (AgeGroup::Adult,);
919        let expected_count = context.query_entity_count(query);
920        let (count, sampled) = context.count_and_sample_entity(EntityContextTestRng, query);
921        assert_eq!(count, expected_count);
922        assert_eq!(sampled.is_some(), count > 0);
923        if let Some(entity_id) = sampled {
924            assert!(context.match_entity(entity_id, query));
925        }
926    }
927
928    #[test]
929    fn get_derived_property_multiple_deps() {
930        let mut context = Context::new();
931
932        let expected_high_id: PersonId = context
933            .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible))
934            .unwrap();
935        let expected_med_id: PersonId = context
936            .add_entity((Age(30), Vaccinated(false), InfectionStatus::Susceptible))
937            .unwrap();
938        let expected_low_id: PersonId = context
939            .add_entity((Age(3), Vaccinated(true), InfectionStatus::Recovered))
940            .unwrap();
941
942        let actual_high: RiskLevel = context.get_property(expected_high_id);
943        assert_eq!(actual_high, RiskLevel::High);
944        let actual_med: RiskLevel = context.get_property(expected_med_id);
945        assert_eq!(actual_med, RiskLevel::Medium);
946        let actual_low: RiskLevel = context.get_property(expected_low_id);
947        assert_eq!(actual_low, RiskLevel::Low);
948    }
949
950    #[test]
951    fn listen_to_derived_property_change_event() {
952        let mut context = Context::new();
953
954        let expected_high_id = PersonId::new(0);
955
956        // Listen for derived property change events and record how many times it fires
957        // For `RiskLevel`
958        let risk_flag = Rc::new(RefCell::new(0));
959        let risk_flag_clone = risk_flag.clone();
960        context.subscribe_to_event(
961            move |_context, event: PropertyChangeEvent<Person, RiskLevel>| {
962                assert_eq!(event.entity_id, expected_high_id);
963                assert_eq!(event.previous, RiskLevel::High);
964                assert_eq!(event.current, RiskLevel::Medium);
965                *risk_flag_clone.borrow_mut() += 1;
966            },
967        );
968        // For `AgeGroup`
969        let age_group_flag = Rc::new(RefCell::new(0));
970        let age_group_flag_clone = age_group_flag.clone();
971        context.subscribe_to_event(
972            move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
973                assert_eq!(event.entity_id, expected_high_id);
974                assert_eq!(event.previous, AgeGroup::Senior);
975                assert_eq!(event.current, AgeGroup::Adult);
976                *age_group_flag_clone.borrow_mut() += 1;
977            },
978        );
979
980        // Should not emit change events
981        let expected_high_id: PersonId = context
982            .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible))
983            .unwrap();
984
985        // Should emit change events
986        context.set_property(expected_high_id, Age(20));
987
988        // Execute queued event handlers
989        context.execute();
990        // Should have exactly one event recorded
991        assert_eq!(*risk_flag.borrow(), 1);
992        assert_eq!(*age_group_flag.borrow(), 1);
993    }
994
995    /*
996    ToDo(RobertJacobsonCDC): Enable this once #691 is resolved, https://github.com/CDCgov/ixa/issues/691.
997
998    #[test]
999    fn get_derived_property_with_globals() {
1000        let mut context = Context::new();
1001
1002        context.set_global_property_value(GlobalDummy, 18).unwrap();
1003        let child = context.add_entity((Age(17),)).unwrap();
1004        let adult = context.add_entity((Age(19),)).unwrap();
1005
1006        let child_computed: MyDerivedProperty = context.get_property(child);
1007        assert_eq!(child_computed, MyDerivedProperty(17+18));
1008
1009        let adult_computed: MyDerivedProperty = context.get_property(adult);
1010        assert_eq!(adult_computed, MyDerivedProperty(19+18));
1011    }
1012    */
1013
1014    #[test]
1015    fn observe_diamond_property_change() {
1016        let mut context = Context::new();
1017        let person = context.add_entity((Age(17), IsSwimmer(true))).unwrap();
1018
1019        let is_adult_athlete: AdultAthlete = context.get_property(person);
1020        assert!(!is_adult_athlete.0);
1021
1022        let flag = Rc::new(RefCell::new(0));
1023        let flag_clone = flag.clone();
1024        context.subscribe_to_event(
1025            move |_context, event: PropertyChangeEvent<Person, AdultAthlete>| {
1026                assert_eq!(event.entity_id, person);
1027                assert_eq!(event.previous, AdultAthlete(false));
1028                assert_eq!(event.current, AdultAthlete(true));
1029                *flag_clone.borrow_mut() += 1;
1030            },
1031        );
1032
1033        context.set_property(person, Age(20));
1034        // Make sure the derived property is what we expect.
1035        let is_adult_athlete: AdultAthlete = context.get_property(person);
1036        assert!(is_adult_athlete.0);
1037
1038        // Execute queued event handlers
1039        context.execute();
1040        // Should have exactly one event recorded
1041        assert_eq!(*flag.borrow(), 1);
1042    }
1043
1044    // Tests related to queries and indexing
1045
1046    define_multi_property!((InfectionStatus, Vaccinated), Person);
1047    define_multi_property!((Vaccinated, InfectionStatus), Person);
1048
1049    #[test]
1050    fn with_query_results_finds_multi_index() {
1051        use crate::rand::seq::IndexedRandom;
1052        let mut rng = crate::rand::rng();
1053        let mut context = Context::new();
1054
1055        for _ in 0..10_000usize {
1056            let infection_status = *[
1057                InfectionStatus::Susceptible,
1058                InfectionStatus::Infected,
1059                InfectionStatus::Recovered,
1060            ]
1061            .choose(&mut rng)
1062            .unwrap();
1063            let vaccination_status: bool = rng.random_bool(0.5);
1064            let age: u8 = rng.random_range(0..100);
1065            context
1066                .add_entity((Age(age), infection_status, Vaccinated(vaccination_status)))
1067                .unwrap();
1068        }
1069        context.index_property::<Person, InfectionStatusVaccinated>();
1070        // Force an index build by running a query.
1071        let _ = context.query_result_iterator((InfectionStatus::Susceptible, Vaccinated(true)));
1072
1073        // Capture the set given by `with_query_results`.
1074        let mut result_entities: IndexSet<EntityId<Person>> = IndexSet::default();
1075        context.with_query_results(
1076            (InfectionStatus::Susceptible, Vaccinated(true)),
1077            &mut |result_set| {
1078                result_entities = result_set.into_iter().collect::<IndexSet<_>>();
1079            },
1080        );
1081
1082        // Check that the order doesn't matter.
1083        assert_eq!(
1084            InfectionStatusVaccinated::index_id(),
1085            VaccinatedInfectionStatus::index_id()
1086        );
1087        assert_eq!(
1088            InfectionStatusVaccinated::index_id(),
1089            (InfectionStatus::Susceptible, Vaccinated(true))
1090                .multi_property_id()
1091                .unwrap()
1092        );
1093
1094        // Check if it matches the expected bucket.
1095        let index_id = InfectionStatusVaccinated::index_id();
1096
1097        let property_store = context.entity_store.get_property_store::<Person>();
1098        let property_value_store = property_store.get_with_id(index_id);
1099        let bucket: &IndexSet<EntityId<Person>> = property_value_store
1100            .get_index_set_with_hash(
1101                (InfectionStatus::Susceptible, Vaccinated(true)).multi_property_value_hash(),
1102            )
1103            .unwrap();
1104
1105        let expected_entities = bucket.iter().copied().collect::<IndexSet<_>>();
1106        assert_eq!(expected_entities, result_entities);
1107    }
1108
1109    #[test]
1110    fn query_returns_entity_set_and_query_result_iterator_remains_compatible() {
1111        let mut context = Context::new();
1112        let p1 = context
1113            .add_entity((Age(21), InfectionStatus::Susceptible, Vaccinated(true)))
1114            .unwrap();
1115        let _p2 = context
1116            .add_entity((Age(22), InfectionStatus::Susceptible, Vaccinated(false)))
1117            .unwrap();
1118        let p3 = context
1119            .add_entity((Age(23), InfectionStatus::Infected, Vaccinated(true)))
1120            .unwrap();
1121
1122        let query = (Vaccinated(true),);
1123
1124        let from_set = context
1125            .query::<Person, _>(query)
1126            .into_iter()
1127            .collect::<IndexSet<_>>();
1128        let from_iterator = context
1129            .query_result_iterator(query)
1130            .collect::<IndexSet<_>>();
1131
1132        assert_eq!(from_set, from_iterator);
1133        assert!(from_set.contains(&p1));
1134        assert!(from_set.contains(&p3));
1135        assert_eq!(from_set.len(), 2);
1136    }
1137
1138    #[test]
1139    fn set_property_correctly_maintains_index() {
1140        let mut context = Context::new();
1141        context.index_property::<Person, InfectionStatus>();
1142        context.index_property::<Person, AgeGroup>();
1143
1144        let person1 = context.add_entity((Age(22),)).unwrap();
1145        let person2 = context.add_entity((Age(22),)).unwrap();
1146        for _ in 0..4 {
1147            let _: PersonId = context.add_entity((Age(22),)).unwrap();
1148        }
1149
1150        // Check non-derived property index is correctly maintained
1151        assert_eq!(
1152            context.query_entity_count((InfectionStatus::Susceptible,)),
1153            6
1154        );
1155        assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0);
1156        assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0);
1157
1158        context.set_property(person1, InfectionStatus::Infected);
1159
1160        assert_eq!(
1161            context.query_entity_count((InfectionStatus::Susceptible,)),
1162            5
1163        );
1164        assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 1);
1165        assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0);
1166
1167        context.set_property(person1, InfectionStatus::Recovered);
1168
1169        assert_eq!(
1170            context.query_entity_count((InfectionStatus::Susceptible,)),
1171            5
1172        );
1173        assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0);
1174        assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 1);
1175
1176        // Check derived property index is correctly maintained.
1177        assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0);
1178        assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 6);
1179        assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0);
1180
1181        context.set_property(person2, Age(12));
1182
1183        assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1);
1184        assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 5);
1185        assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0);
1186
1187        context.set_property(person1, Age(75));
1188
1189        assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1);
1190        assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4);
1191        assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 1);
1192
1193        context.set_property(person2, Age(77));
1194
1195        assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0);
1196        assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4);
1197        assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 2);
1198    }
1199
1200    #[test]
1201    fn query_unindexed_default_properties() {
1202        let mut context = Context::new();
1203
1204        // Half will have the default value.
1205        for idx in 0..10 {
1206            if idx % 2 == 0 {
1207                context.add_entity((Age(22),)).unwrap();
1208            } else {
1209                context
1210                    .add_entity((Age(22), InfectionStatus::Recovered))
1211                    .unwrap();
1212            }
1213        }
1214        // The tail also has the default value
1215        for _ in 0..10 {
1216            let _: PersonId = context.add_entity((Age(22),)).unwrap();
1217        }
1218
1219        assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 5);
1220        assert_eq!(
1221            context.query_entity_count((InfectionStatus::Susceptible,)),
1222            15
1223        );
1224    }
1225
1226    #[test]
1227    fn query_unindexed_derived_properties() {
1228        let mut context = Context::new();
1229
1230        for _ in 0..10 {
1231            let _: PersonId = context.add_entity((Age(22),)).unwrap();
1232        }
1233
1234        assert_eq!(context.query_entity_count((AdultAthlete(false),)), 10);
1235    }
1236
1237    #[test]
1238    fn track_periodic_value_change_counts_uses_distinct_counters() {
1239        let mut context = Context::new();
1240
1241        context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1242            1.0,
1243            move |_context, _counter| {},
1244        );
1245
1246        context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1247            1.0,
1248            move |_context, _counter| {},
1249        );
1250
1251        let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1252        assert_eq!(property_value_store.value_change_counters.len(), 0);
1253
1254        context.add_plan(0.5, Context::shutdown);
1255        context.execute();
1256
1257        let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1258        assert_eq!(property_value_store.value_change_counters.len(), 2);
1259    }
1260
1261    #[test]
1262    fn value_change_counter_updates_on_true_transitions() {
1263        let mut context = Context::new();
1264        let observed = Rc::new(RefCell::new(Vec::<(usize, usize)>::new()));
1265        let observed_clone = observed.clone();
1266
1267        context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1268            observed_clone.borrow_mut().push((
1269                counter.get_count((CounterStratum(true),), CounterValue(1)),
1270                counter.get_count((CounterStratum(true),), CounterValue(2)),
1271            ));
1272        });
1273
1274        let person = context
1275            .add_entity((Age(10), CounterValue(0), CounterStratum(true)))
1276            .unwrap();
1277        context.add_plan(0.1, move |context| {
1278            context.set_property(person, CounterValue(1));
1279            context.set_property(person, CounterValue(1));
1280            context.set_property(person, CounterValue(2));
1281        });
1282
1283        context.execute();
1284        assert_eq!(*observed.borrow(), vec![(0, 0), (1, 1)]);
1285    }
1286
1287    #[test]
1288    fn periodic_value_change_counts_report_and_clear() {
1289        let mut context = Context::new();
1290        let person = context
1291            .add_entity((Age(10), CounterValue(0), CounterStratum(true)))
1292            .unwrap();
1293
1294        let observed = Rc::new(RefCell::new(Vec::<usize>::new()));
1295        let observed_clone = observed.clone();
1296
1297        context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1298            observed_clone
1299                .borrow_mut()
1300                .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1301        });
1302
1303        context.add_plan(0.5, move |context| {
1304            context.set_property(person, CounterValue(1));
1305        });
1306        context.add_plan(1.5, move |context| {
1307            context.set_property(person, CounterValue(1));
1308        });
1309
1310        context.execute();
1311        assert_eq!(*observed.borrow(), vec![0, 1, 0]);
1312    }
1313
1314    #[test]
1315    fn periodic_value_change_counts_start_time_and_phase_behavior() {
1316        let mut context = Context::new();
1317        context.set_start_time(-2.0);
1318
1319        let person = context
1320            .add_entity((Age(10), CounterValue(0), CounterStratum(true)))
1321            .unwrap();
1322
1323        let observed_times = Rc::new(RefCell::new(Vec::<f64>::new()));
1324        let observed_counts = Rc::new(RefCell::new(Vec::<usize>::new()));
1325        let observed_times_clone = observed_times.clone();
1326        let observed_counts_clone = observed_counts.clone();
1327
1328        context.track_periodic_value_change_counts(1.0, move |context, counter| {
1329            observed_times_clone
1330                .borrow_mut()
1331                .push(context.get_current_time());
1332            observed_counts_clone
1333                .borrow_mut()
1334                .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1335        });
1336
1337        context.add_plan_with_phase(
1338            -2.0,
1339            move |context| {
1340                context.set_property(person, CounterValue(1));
1341            },
1342            ExecutionPhase::Normal,
1343        );
1344        context.add_plan(0.0, |_| {});
1345
1346        context.execute();
1347
1348        assert_eq!(*observed_times.borrow(), vec![-2.0, -1.0, 0.0]);
1349        assert_eq!(*observed_counts.borrow(), vec![1, 0, 0]);
1350    }
1351}