ixa/people/
context_extension.rs

1use crate::people::index::{
2    get_and_register_multi_property_index, get_multi_property_hash, Index, IndexValue,
3};
4use crate::people::query::Query;
5use crate::people::{index, InitializationList, PeoplePlugin, PersonPropertyHolder};
6use crate::{
7    Context, ContextRandomExt, IxaError, PersonCreatedEvent, PersonId, PersonProperty,
8    PersonPropertyChangeEvent, RngId, Tabulator,
9};
10use crate::{HashMap, HashMapExt, HashSet, HashSetExt};
11use rand::Rng;
12use std::any::TypeId;
13use std::cell::Ref;
14
15use crate::people::methods::Methods;
16
17/// A trait extension for [`Context`] that exposes the people
18/// functionality.
19pub trait ContextPeopleExt {
20    /// Returns the current population size
21    fn get_current_population(&self) -> usize;
22
23    /// Creates a new person. The caller must supply initial values
24    /// for all non-derived properties that don't have a default or an initializer.
25    /// Note that although this technically takes any type that implements
26    /// [`InitializationList`] it is best to take advantage of the provided
27    /// syntax that implements [`InitializationList`] for tuples, such as:
28    /// `let person = context.add_person((Age, 42)).unwrap();`
29    ///
30    /// # Errors
31    /// Will return [`IxaError`] if a required initializer is not provided.
32    fn add_person<T: InitializationList>(&mut self, props: T) -> Result<PersonId, IxaError>;
33
34    /// Given a `PersonId` returns the value of a defined person property,
35    /// initializing it if it hasn't been set yet. If no initializer is
36    /// provided, and the property is not set this will panic, as long
37    /// as the property has been set or subscribed to at least once before.
38    /// Otherwise, Ixa doesn't know about the property.
39    fn get_person_property<T: PersonProperty>(&self, person_id: PersonId, _property: T)
40        -> T::Value;
41
42    #[doc(hidden)]
43    fn register_property<T: PersonProperty>(&self);
44
45    /// Given a [`PersonId`], sets the value of a defined person property
46    /// Panics if the property is not initialized. Fires a change event.
47    fn set_person_property<T: PersonProperty>(
48        &mut self,
49        person_id: PersonId,
50        _property: T,
51        value: T::Value,
52    );
53
54    /// Create an index for property `T`.
55    ///
56    /// If an index is available [`Context::query_people()`] will use it, so this is
57    /// intended to allow faster querying of commonly used properties.
58    /// Ixa may choose to create an index for its own reasons even if
59    /// [`Context::index_property()`] is not called, so this function just ensures
60    /// that one is created.
61    fn index_property<T: PersonProperty>(&mut self, property: T);
62
63    /// Query for all people matching a given set of criteria.
64    ///
65    /// [`Context::query_people()`] takes any type that implements [Query],
66    /// but instead of implementing query yourself it is best
67    /// to use the automatic syntax that implements [Query] for
68    /// a tuple of pairs of (property, value), like so:
69    /// `context.query_people(((Age, 30), (Gender, Female)))`.
70    fn query_people<T: Query>(&self, q: T) -> Vec<PersonId>;
71
72    /// Get the count of all people matching a given set of criteria.
73    ///
74    /// [`Context::query_people_count()`] takes any type that implements [Query],
75    /// but instead of implementing query yourself it is best
76    /// to use the automatic syntax that implements [Query] for
77    /// a tuple of pairs of (property, value), like so:
78    /// `context.query_people(((Age, 30), (Gender, Female)))`.
79    ///
80    /// This is intended to be slightly faster than [`Context::query_people()`]
81    /// because it does not need to allocate a list. We haven't actually
82    /// measured it, so the difference may be modest if any.
83    fn query_people_count<T: Query>(&self, q: T) -> usize;
84
85    /// Determine whether a person matches a given expression.
86    ///
87    /// The syntax here is the same as with [`Context::query_people()`].
88    fn match_person<T: Query>(&self, person_id: PersonId, q: T) -> bool;
89
90    /// Similar to `match_person`, but more efficient, it removes people
91    /// from a list who do not match the given query. Note that this
92    /// method modifies the vector in-place, so it is up to the caller
93    /// to clone the vector if they don't want to modify their original
94    /// vector.
95    fn filter_people<T: Query>(&self, people: &mut Vec<PersonId>, q: T);
96    fn tabulate_person_properties<T: Tabulator, F>(&self, tabulator: &T, print_fn: F)
97    where
98        F: Fn(&Context, &[String], usize);
99
100    /// Randomly sample a person from the population of people who match the query.
101    /// Returns None if no people match the query.
102    ///
103    /// The syntax here is the same as with [`Context::query_people()`].
104    ///
105    fn sample_person<R: RngId + 'static, T: Query>(&self, rng_id: R, query: T) -> Option<PersonId>
106    where
107        R::RngType: Rng;
108
109    /// Randomly sample a list of people from the population of people who match the query.
110    /// Returns an empty list if no people match the query.
111    ///
112    /// The syntax here is the same as with [`Context::query_people()`].
113    fn sample_people<R: RngId + 'static, T: Query>(
114        &self,
115        rng_id: R,
116        query: T,
117        n: usize,
118    ) -> Vec<PersonId>
119    where
120        R::RngType: Rng;
121}
122impl ContextPeopleExt for Context {
123    fn get_current_population(&self) -> usize {
124        self.get_data_container(PeoplePlugin)
125            .map_or(0, |data_container| data_container.current_population)
126    }
127
128    fn add_person<T: InitializationList>(&mut self, props: T) -> Result<PersonId, IxaError> {
129        let data_container = self.get_data_container_mut(PeoplePlugin);
130        // Verify that every property that was supposed to be provided
131        // actually was.
132        data_container.check_initialization_list(&props)?;
133
134        // Actually add the person. Nothing can fail after this point because
135        // it would leave the person in an inconsistent state.
136        let person_id = data_container.add_person();
137
138        // Initialize the properties. We set |is_initializing| to prevent
139        // set_person_property() from generating an event.
140        data_container.is_initializing = true;
141        props.set_properties(self, person_id);
142        let data_container = self.get_data_container_mut(PeoplePlugin);
143        data_container.is_initializing = false;
144
145        self.emit_event(PersonCreatedEvent { person_id });
146        Ok(person_id)
147    }
148
149    fn get_person_property<T: PersonProperty>(&self, person_id: PersonId, property: T) -> T::Value {
150        let data_container = self.get_data_container(PeoplePlugin)
151            .expect("PeoplePlugin is not initialized; make sure you add a person before accessing properties");
152        self.register_property::<T>();
153
154        if T::is_derived() {
155            return T::compute(self, person_id);
156        }
157
158        // Attempt to retrieve the existing value
159        if let Some(value) = *data_container.get_person_property_ref(person_id, property) {
160            return value;
161        }
162
163        // Initialize the property. This does not fire a change event
164        let initialized_value = T::compute(self, person_id);
165        data_container.set_person_property(person_id, property, initialized_value);
166
167        initialized_value
168    }
169
170    #[allow(clippy::single_match_else)]
171    fn set_person_property<T: PersonProperty>(
172        &mut self,
173        person_id: PersonId,
174        property: T,
175        value: T::Value,
176    ) {
177        self.register_property::<T>();
178
179        assert!(!T::is_derived(), "Cannot set a derived property");
180
181        // This function can be called in two separate modes:
182        //
183        // 1. As a regular API function, in which case we want to
184        //    emit an event and notify dependencies.
185        // 2. Internally as part of initialization during add_person()
186        //    in which case no events are emitted.
187        //
188        // Which mode it is is determined by the data_container.is_initializing
189        // property, which is set by add_person. This is complicated but
190        // necessary because the initialization functions are called by
191        // a per-PersonProperty closure generated by a macro and so are
192        // outside of the crate, but we don't want to expose a public
193        // initialize_person_property() function.
194        //
195        // Temporarily remove dependency properties since we need mutable references
196        // to self during callback execution
197        let initializing = self
198            .get_data_container(PeoplePlugin)
199            .unwrap()
200            .is_initializing;
201
202        let (previous_value, deps_temp) = if initializing {
203            (None, None)
204        } else {
205            let previous_value = self.get_person_property(person_id, property);
206            if previous_value != value {
207                self.remove_from_index_maybe(person_id, property);
208            }
209
210            (
211                Some(previous_value),
212                self.get_data_container(PeoplePlugin)
213                    .unwrap()
214                    .dependency_map
215                    .borrow_mut()
216                    .get_mut(&TypeId::of::<T>())
217                    .map(std::mem::take),
218            )
219        };
220
221        let mut dependency_event_callbacks = Vec::new();
222        if let Some(mut deps) = deps_temp {
223            // If there are dependencies, set up a bunch of callbacks with the
224            // current value
225            for dep in &mut deps {
226                dep.dependency_changed(self, person_id, &mut dependency_event_callbacks);
227            }
228
229            // Put the dependency list back in
230            let data_container = self.get_data_container(PeoplePlugin).unwrap();
231            let mut dependencies = data_container.dependency_map.borrow_mut();
232            dependencies.insert(TypeId::of::<T>(), deps);
233        }
234
235        // Update the main property and send a change event
236        let data_container = self.get_data_container(PeoplePlugin).unwrap();
237        data_container.set_person_property(person_id, property, value);
238
239        if !initializing {
240            if previous_value.unwrap() != value {
241                self.add_to_index_maybe(person_id, property);
242            }
243
244            let change_event: PersonPropertyChangeEvent<T> = PersonPropertyChangeEvent {
245                person_id,
246                current: value,
247                previous: previous_value.unwrap(), // This muse be Some() of !initializing
248            };
249            self.emit_event(change_event);
250        }
251
252        for callback in dependency_event_callbacks {
253            callback(self);
254        }
255    }
256
257    fn index_property<T: PersonProperty>(&mut self, _property: T) {
258        // Ensure that the data container exists
259        {
260            let _ = self.get_data_container_mut(PeoplePlugin);
261        }
262
263        self.register_property::<T>();
264
265        let data_container = self.get_data_container(PeoplePlugin).unwrap();
266        let mut index = data_container
267            .get_index_ref_mut_by_prop(T::get_instance())
268            .unwrap();
269        if index.lookup.is_none() {
270            index.lookup = Some(HashMap::new());
271        }
272    }
273
274    fn query_people<T: Query>(&self, q: T) -> Vec<PersonId> {
275        // Special case the situation where nobody exists.
276        if self.get_data_container(PeoplePlugin).is_none() {
277            return Vec::new();
278        }
279
280        T::setup(&q, self);
281        let mut result = Vec::new();
282        self.query_people_internal(
283            |person| {
284                result.push(person);
285            },
286            q.get_query(),
287        );
288        result
289    }
290
291    fn query_people_count<T: Query>(&self, q: T) -> usize {
292        // Special case the situation where nobody exists.
293        if self.get_data_container(PeoplePlugin).is_none() {
294            return 0;
295        }
296
297        T::setup(&q, self);
298        let mut count: usize = 0;
299        self.query_people_internal(
300            |_person| {
301                count += 1;
302            },
303            q.get_query(),
304        );
305        count
306    }
307
308    fn match_person<T: Query>(&self, person_id: PersonId, q: T) -> bool {
309        T::setup(&q, self);
310        // This cannot fail because someone must have been made by now.
311        let data_container = self.get_data_container(PeoplePlugin).unwrap();
312
313        let query = q.get_query();
314
315        for (t, hash) in &query {
316            let methods = data_container.get_methods(*t);
317            if *hash != (*methods.indexer)(self, person_id) {
318                return false;
319            }
320        }
321        true
322    }
323
324    fn filter_people<T: Query>(&self, people: &mut Vec<PersonId>, q: T) {
325        T::setup(&q, self);
326        let data_container = self.get_data_container(PeoplePlugin).unwrap();
327        for (t, hash) in q.get_query() {
328            let methods = data_container.get_methods(t);
329            people.retain(|person_id| hash == (*methods.indexer)(self, *person_id));
330            if people.is_empty() {
331                break;
332            }
333        }
334    }
335
336    fn register_property<T: PersonProperty>(&self) {
337        let data_container = self.get_data_container(PeoplePlugin).
338            expect("PeoplePlugin is not initialized; make sure you add a person before accessing properties");
339        if data_container
340            .registered_properties
341            .borrow()
342            .contains(&TypeId::of::<T>())
343        {
344            return;
345        }
346
347        let instance = T::get_instance();
348
349        // In order to avoid borrowing recursively, we must register dependencies first.
350        if instance.is_derived() {
351            T::register_dependencies(self);
352
353            let dependencies = instance.non_derived_dependencies();
354            for dependency in dependencies {
355                let mut dependency_map = data_container.dependency_map.borrow_mut();
356                let derived_prop_list = dependency_map.entry(dependency).or_default();
357                derived_prop_list.push(Box::new(instance));
358            }
359        }
360
361        // TODO<ryl8@cdc.gov>: We create an index for every property in order to
362        // support the get_person_property_by_name() function used in external_api.rs,
363        // but ideally this shouldn't be necessary.
364        data_container
365            .methods
366            .borrow_mut()
367            .insert(TypeId::of::<T>(), Methods::new::<T>());
368        data_container
369            .people_types
370            .borrow_mut()
371            .insert(T::name().to_string(), TypeId::of::<T>());
372        data_container
373            .registered_properties
374            .borrow_mut()
375            .insert(TypeId::of::<T>());
376
377        self.register_indexer::<T>();
378    }
379
380    fn tabulate_person_properties<T: Tabulator, F>(&self, tabulator: &T, print_fn: F)
381    where
382        F: Fn(&Context, &[String], usize),
383    {
384        let type_ids = tabulator.get_typelist();
385        tabulator.setup(self).unwrap();
386
387        if let Some(data_container) = self.get_data_container(PeoplePlugin) {
388            for type_id in &type_ids {
389                let mut index = data_container.get_index_ref_mut(*type_id).unwrap();
390                if index.lookup.is_none() {
391                    // Start indexing this property if it's not already
392                    // indexed.
393                    index.lookup = Some(HashMap::new());
394                }
395
396                let methods = data_container.get_methods(*type_id);
397                index.index_unindexed_people(self, &methods);
398            }
399        } else {
400            // If there is no data container then there are no people.
401            return;
402        }
403
404        // Now process each index
405        let index_container = self
406            .get_data_container(PeoplePlugin)
407            .unwrap()
408            .property_indexes
409            .borrow();
410
411        let indices = type_ids
412            .iter()
413            .filter_map(|t| index_container.get(t))
414            .collect::<Vec<&Index>>();
415
416        index::process_indices(
417            self,
418            indices.as_slice(),
419            &mut Vec::new(),
420            &HashSet::new(),
421            &print_fn,
422        );
423    }
424
425    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
426    fn sample_people<R: RngId + 'static, T: Query>(
427        &self,
428        rng_id: R,
429        query: T,
430        n: usize,
431    ) -> Vec<PersonId>
432    where
433        R::RngType: Rng,
434    {
435        if self.get_current_population() == 0 {
436            return Vec::new();
437        }
438        let requested = std::cmp::min(n, self.get_current_population());
439        // Special case the empty query because we can do it in O(1).
440        if query.get_query().is_empty() {
441            let mut selected = HashSet::new();
442            while selected.len() < requested {
443                let result = self.sample_range(rng_id, 0..self.get_current_population());
444                selected.insert(PersonId(result));
445            }
446            return selected.into_iter().collect();
447        }
448
449        T::setup(&query, self);
450
451        // This function implements "Algorithm L" from KIM-HUNG LI
452        // Reservoir-Sampling Algorithms of Time Complexity O(n(1 + log(N/n)))
453        // https://dl.acm.org/doi/pdf/10.1145/198429.198435
454        // Temporary variables.
455        let mut w: f64 = self.sample_range(rng_id, 0.0..1.0);
456        let mut ctr: usize = 0;
457        let mut i: usize = 1;
458        let mut selected = Vec::new();
459
460        self.query_people_internal(
461            |person| {
462                ctr += 1;
463                if i == ctr {
464                    if selected.len() == requested {
465                        let to_remove = self.sample_range(rng_id, 0..selected.len());
466                        selected.swap_remove(to_remove);
467                    }
468                    selected.push(person);
469                    if selected.len() == requested {
470                        i += (f64::ln(self.sample_range(rng_id, 0.0..1.0)) / f64::ln(1.0 - w))
471                            .floor() as usize
472                            + 1;
473                        w *= self.sample_range(rng_id, 0.0..1.0);
474                    } else {
475                        i += 1;
476                    }
477                }
478            },
479            query.get_query(),
480        );
481
482        selected
483    }
484
485    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
486    fn sample_person<R: RngId + 'static, T: Query>(&self, rng_id: R, query: T) -> Option<PersonId>
487    where
488        R::RngType: Rng,
489    {
490        if self.get_current_population() == 0 {
491            return None;
492        }
493
494        // Special case the empty query because we can do it in O(1).
495        if query.get_query().is_empty() {
496            let result = self.sample_range(rng_id, 0..self.get_current_population());
497            return Some(PersonId(result));
498        }
499
500        T::setup(&query, self);
501
502        // This function implements "Algorithm L" from KIM-HUNG LI
503        // Reservoir-Sampling Algorithms of Time Complexity O(n(1 + log(N/n)))
504        // https://dl.acm.org/doi/pdf/10.1145/198429.198435
505        // Temporary variables.
506        let mut selected: Option<PersonId> = None;
507        let mut w: f64 = self.sample_range(rng_id, 0.0..1.0);
508        let mut ctr: usize = 0;
509        let mut i: usize = 1;
510
511        self.query_people_internal(
512            |person| {
513                ctr += 1;
514                if i == ctr {
515                    selected = Some(person);
516                    i += (f64::ln(self.sample_range(rng_id, 0.0..1.0)) / f64::ln(1.0 - w)).floor()
517                        as usize
518                        + 1;
519                    w *= self.sample_range(rng_id, 0.0..1.0);
520                }
521            },
522            query.get_query(),
523        );
524
525        selected
526    }
527}
528
529pub trait ContextPeopleExtInternal {
530    fn register_indexer<T: PersonProperty>(&self);
531    fn add_to_index_maybe<T: PersonProperty>(&mut self, person_id: PersonId, property: T);
532    fn remove_from_index_maybe<T: PersonProperty>(&mut self, person_id: PersonId, property: T);
533    fn query_people_internal(
534        &self,
535        accumulator: impl FnMut(PersonId),
536        property_hashes: Vec<(TypeId, IndexValue)>,
537    );
538}
539
540impl ContextPeopleExtInternal for Context {
541    fn register_indexer<T: PersonProperty>(&self) {
542        {
543            let data_container = self.get_data_container(PeoplePlugin).unwrap();
544
545            let property_indexes = data_container.property_indexes.borrow_mut();
546            if property_indexes.contains_key(&TypeId::of::<T>()) {
547                return; // Index already exists, do nothing
548            }
549        }
550
551        // If it doesn't exist, insert the new index
552        let index = Index::new(self, T::get_instance());
553        let data_container = self.get_data_container(PeoplePlugin).unwrap();
554        let mut property_indexes = data_container.property_indexes.borrow_mut();
555        property_indexes.insert(TypeId::of::<T>(), index);
556    }
557
558    fn add_to_index_maybe<T: PersonProperty>(&mut self, person_id: PersonId, property: T) {
559        let data_container = self.get_data_container(PeoplePlugin).unwrap();
560
561        if let Some(mut index) = data_container.get_index_ref_mut_by_prop(property) {
562            let methods = data_container.get_methods(TypeId::of::<T>());
563            if index.lookup.is_some() {
564                index.add_person(self, &methods, person_id);
565            }
566        }
567    }
568
569    fn remove_from_index_maybe<T: PersonProperty>(&mut self, person_id: PersonId, property: T) {
570        let data_container = self.get_data_container(PeoplePlugin).unwrap();
571
572        if let Some(mut index) = data_container.get_index_ref_mut_by_prop(property) {
573            let methods = data_container.get_methods(TypeId::of::<T>());
574            if index.lookup.is_some() {
575                index.remove_person(self, &methods, person_id);
576            }
577        }
578    }
579
580    fn query_people_internal(
581        &self,
582        mut accumulator: impl FnMut(PersonId),
583        property_hashes: Vec<(TypeId, IndexValue)>,
584    ) {
585        let mut indexes = Vec::<Ref<HashSet<PersonId>>>::new();
586        let mut unindexed = Vec::<(TypeId, IndexValue)>::new();
587        let data_container = self.get_data_container(PeoplePlugin)
588            .expect("PeoplePlugin is not initialized; make sure you add a person before accessing properties");
589
590        let mut property_hashs_working_set = property_hashes.clone();
591        // intercept multi-property queries
592        if property_hashs_working_set.len() > 1 {
593            if let Some(combined_index) =
594                get_and_register_multi_property_index(&property_hashes, self)
595            {
596                let combined_hash = get_multi_property_hash(&property_hashes);
597                property_hashs_working_set = vec![(combined_index, combined_hash)];
598            }
599        }
600
601        // 1. Walk through each property and update the indexes.
602        for (t, _) in &property_hashs_working_set {
603            let mut index = data_container.get_index_ref_mut(*t).unwrap();
604            let methods = data_container.get_methods(*t);
605            index.index_unindexed_people(self, &methods);
606        }
607
608        // 2. Collect the index entry corresponding to the value.
609        for (t, hash) in property_hashs_working_set {
610            let index = data_container.get_index_ref(t).unwrap();
611            if let Ok(lookup) = Ref::filter_map(index, |x| x.lookup.as_ref()) {
612                if let Ok(matching_people) =
613                    Ref::filter_map(lookup, |x| x.get(&hash).map(|entry| &entry.1))
614                {
615                    indexes.push(matching_people);
616                } else {
617                    // This is empty and so the intersection will
618                    // also be empty.
619                    return;
620                }
621            } else {
622                // No index, so we'll get to this after.
623                unindexed.push((t, hash));
624            }
625        }
626
627        // 3. Create an iterator over people, based one either:
628        //    (1) the smallest index if there is one.
629        //    (2) the overall population if there are no indices.
630
631        let holder: Ref<HashSet<PersonId>>;
632        let to_check: Box<dyn Iterator<Item = PersonId>> = if indexes.is_empty() {
633            Box::new(data_container.people_iterator())
634        } else {
635            indexes.sort_by_key(|x| x.len());
636
637            holder = indexes.remove(0);
638            Box::new(holder.iter().copied())
639        };
640
641        // 4. Walk over the iterator and add people to the result
642        // iff:
643        //    (1) they exist in all the indexes
644        //    (2) they match the unindexed properties
645        'outer: for person in to_check {
646            // (1) check all the indexes
647            for index in &indexes {
648                if !index.contains(&person) {
649                    continue 'outer;
650                }
651            }
652
653            // (2) check the unindexed properties
654            for (t, hash) in &unindexed {
655                let methods = data_container.get_methods(*t);
656                if *hash != (*methods.indexer)(self, person) {
657                    continue 'outer;
658                }
659            }
660
661            // This matches.
662            accumulator(person);
663        }
664    }
665}
666
667#[cfg(test)]
668mod tests {
669    use crate::people::{PeoplePlugin, PersonPropertyHolder};
670    use crate::random::{define_rng, ContextRandomExt};
671    use crate::{
672        define_derived_property, define_global_property, define_person_property,
673        define_person_property_with_default, Context, ContextGlobalPropertiesExt, ContextPeopleExt,
674        IxaError, PersonId, PersonPropertyChangeEvent,
675    };
676    use serde_derive::Serialize;
677    use std::any::TypeId;
678    use std::cell::RefCell;
679    use std::rc::Rc;
680
681    define_person_property!(Age, u8);
682    #[derive(Serialize, Copy, Clone, Debug, PartialEq, Eq)]
683    pub enum AgeGroupValue {
684        Child,
685        Adult,
686    }
687    define_global_property!(ThresholdP, u8);
688    define_derived_property!(IsEligible, bool, [Age], [ThresholdP], |age, threshold| {
689        age >= threshold
690    });
691
692    // This isn't used, it's just testing for a compile error.
693    define_derived_property!(
694        NotUsed,
695        bool,
696        [Age],
697        [ThresholdP, ThresholdP],
698        |age, threshold, threshold2| { age >= threshold && age <= threshold2 }
699    );
700
701    define_derived_property!(AgeGroup, AgeGroupValue, [Age], |age| {
702        if age < 18 {
703            AgeGroupValue::Child
704        } else {
705            AgeGroupValue::Adult
706        }
707    });
708
709    #[derive(Serialize, Copy, Clone, PartialEq, Eq, Debug)]
710    pub enum RiskCategoryValue {
711        High,
712        Low,
713    }
714
715    define_person_property!(RiskCategory, RiskCategoryValue);
716    define_person_property_with_default!(IsRunner, bool, false);
717    define_person_property!(RunningShoes, u8, |context: &Context, person: PersonId| {
718        let is_runner = context.get_person_property(person, IsRunner);
719        if is_runner {
720            4
721        } else {
722            0
723        }
724    });
725    define_derived_property!(AdultRunner, bool, [IsRunner, Age], |is_runner, age| {
726        is_runner && age >= 18
727    });
728    define_derived_property!(
729        SeniorRunner,
730        bool,
731        [AdultRunner, Age],
732        |adult_runner, age| { adult_runner && age >= 65 }
733    );
734    define_person_property_with_default!(IsSwimmer, bool, false);
735    define_derived_property!(AdultSwimmer, bool, [IsSwimmer, Age], |is_swimmer, age| {
736        is_swimmer && age >= 18
737    });
738    define_derived_property!(
739        AdultAthlete,
740        bool,
741        [AdultRunner, AdultSwimmer],
742        |adult_runner, adult_swimmer| { adult_runner || adult_swimmer }
743    );
744
745    #[test]
746    fn set_get_properties() {
747        let mut context = Context::new();
748
749        let person = context.add_person((Age, 42)).unwrap();
750        assert_eq!(context.get_person_property(person, Age), 42);
751    }
752
753    #[allow(clippy::should_panic_without_expect)]
754    #[test]
755    #[should_panic]
756    fn get_uninitialized_property_panics() {
757        let mut context = Context::new();
758        let person = context.add_person(()).unwrap();
759        context.get_person_property(person, Age);
760    }
761
762    #[test]
763    fn get_current_population() {
764        let mut context = Context::new();
765        assert_eq!(context.get_current_population(), 0);
766        for _ in 0..3 {
767            context.add_person(()).unwrap();
768        }
769        assert_eq!(context.get_current_population(), 3);
770    }
771
772    #[test]
773    fn add_person() {
774        let mut context = Context::new();
775
776        let person_id = context
777            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
778            .unwrap();
779        assert_eq!(context.get_person_property(person_id, Age), 42);
780        assert_eq!(
781            context.get_person_property(person_id, RiskCategory),
782            RiskCategoryValue::Low
783        );
784    }
785
786    #[test]
787    fn add_person_with_initialize() {
788        let mut context = Context::new();
789
790        let person_id = context
791            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
792            .unwrap();
793        assert_eq!(context.get_person_property(person_id, Age), 42);
794        assert_eq!(
795            context.get_person_property(person_id, RiskCategory),
796            RiskCategoryValue::Low
797        );
798    }
799
800    #[test]
801    fn add_person_with_initialize_missing() {
802        let mut context = Context::new();
803
804        context.add_person((Age, 10)).unwrap();
805        // Fails because we don't provide a value for Age
806        assert!(matches!(context.add_person(()), Err(IxaError::IxaError(_))));
807    }
808
809    #[test]
810    fn add_person_with_initialize_missing_first() {
811        let mut context = Context::new();
812
813        // Succeeds because context doesn't know about any properties
814        // yet.
815        context.add_person(()).unwrap();
816    }
817
818    #[test]
819    fn add_person_with_initialize_missing_with_default() {
820        let mut context = Context::new();
821
822        context.add_person((IsRunner, true)).unwrap();
823        // Succeeds because |IsRunner| has a default.
824        context.add_person(()).unwrap();
825    }
826
827    #[test]
828    fn person_debug_display() {
829        let mut context = Context::new();
830
831        let person_id = context.add_person(()).unwrap();
832        assert_eq!(format!("{person_id}"), "0");
833        assert_eq!(format!("{person_id:?}"), "Person 0");
834    }
835
836    #[test]
837    fn add_person_initializers() {
838        let mut context = Context::new();
839        let person_id = context.add_person(()).unwrap();
840
841        assert_eq!(context.get_person_property(person_id, RunningShoes), 0);
842        assert!(!context.get_person_property(person_id, IsRunner));
843    }
844
845    #[test]
846    fn property_initialization_is_lazy() {
847        let mut context = Context::new();
848        let person = context.add_person((IsRunner, true)).unwrap();
849        let people_data = context.get_data_container_mut(PeoplePlugin);
850
851        // Verify we haven't initialized the property yet
852        let has_value = *people_data.get_person_property_ref(person, RunningShoes);
853        assert!(has_value.is_none());
854
855        // This should initialize it
856        let value = context.get_person_property(person, RunningShoes);
857        assert_eq!(value, 4);
858    }
859
860    #[test]
861    fn initialize_without_initializer_succeeds() {
862        let mut context = Context::new();
863        context
864            .add_person((RiskCategory, RiskCategoryValue::High))
865            .unwrap();
866    }
867
868    #[test]
869    #[should_panic(expected = "Property not initialized when person created")]
870    fn set_without_initializer_panics() {
871        let mut context = Context::new();
872        let person_id = context.add_person(()).unwrap();
873        context.set_person_property(person_id, RiskCategory, RiskCategoryValue::High);
874    }
875
876    #[test]
877    #[should_panic(expected = "Property not initialized when person created")]
878    fn get_without_initializer_panics() {
879        let mut context = Context::new();
880        let person_id = context.add_person(()).unwrap();
881        context.get_person_property(person_id, RiskCategory);
882    }
883
884    #[test]
885    fn get_person_property_returns_correct_value() {
886        let mut context = Context::new();
887        let person = context.add_person((Age, 10)).unwrap();
888        assert_eq!(
889            context.get_person_property(person, AgeGroup),
890            AgeGroupValue::Child
891        );
892    }
893
894    #[test]
895    fn get_person_property_changes_correctly() {
896        let mut context = Context::new();
897        let person = context.add_person((Age, 17)).unwrap();
898        assert_eq!(
899            context.get_person_property(person, AgeGroup),
900            AgeGroupValue::Child
901        );
902        context.set_person_property(person, Age, 18);
903        assert_eq!(
904            context.get_person_property(person, AgeGroup),
905            AgeGroupValue::Adult
906        );
907    }
908
909    #[test]
910    fn get_derived_property_multiple_deps() {
911        let mut context = Context::new();
912        let person = context.add_person(((Age, 17), (IsRunner, true))).unwrap();
913        let flag = Rc::new(RefCell::new(false));
914        let flag_clone = flag.clone();
915        context.subscribe_to_event(
916            move |_context, event: PersonPropertyChangeEvent<AdultRunner>| {
917                assert_eq!(event.person_id.0, 0);
918                assert!(!event.previous);
919                assert!(event.current);
920                *flag_clone.borrow_mut() = true;
921            },
922        );
923        context.set_person_property(person, Age, 18);
924        context.execute();
925        assert!(*flag.borrow());
926    }
927
928    #[test]
929    fn register_derived_only_once() {
930        let mut context = Context::new();
931        let person = context.add_person(((Age, 17), (IsRunner, true))).unwrap();
932
933        let flag = Rc::new(RefCell::new(0));
934        let flag_clone = flag.clone();
935        context.subscribe_to_event(
936            move |_context, _event: PersonPropertyChangeEvent<AdultRunner>| {
937                *flag_clone.borrow_mut() += 1;
938            },
939        );
940        context.subscribe_to_event(
941            move |_context, _event: PersonPropertyChangeEvent<AdultRunner>| {
942                // Make sure that we don't register multiple times
943            },
944        );
945        context.set_person_property(person, Age, 18);
946        context.execute();
947        assert_eq!(*flag.borrow(), 1);
948    }
949
950    #[test]
951    fn test_resolve_dependencies() {
952        let mut actual = SeniorRunner.non_derived_dependencies();
953        let mut expected = vec![TypeId::of::<Age>(), TypeId::of::<IsRunner>()];
954        actual.sort();
955        expected.sort();
956        assert_eq!(actual, expected);
957    }
958
959    #[test]
960    fn get_derived_property_dependent_on_another_derived() {
961        let mut context = Context::new();
962        let person = context.add_person(((Age, 88), (IsRunner, false))).unwrap();
963        let flag = Rc::new(RefCell::new(0));
964        let flag_clone = flag.clone();
965        assert!(!context.get_person_property(person, SeniorRunner));
966        context.subscribe_to_event(
967            move |_context, event: PersonPropertyChangeEvent<SeniorRunner>| {
968                assert_eq!(event.person_id.0, 0);
969                assert!(!event.previous);
970                assert!(event.current);
971                *flag_clone.borrow_mut() += 1;
972            },
973        );
974        context.set_person_property(person, IsRunner, true);
975        context.execute();
976        assert_eq!(*flag.borrow(), 1);
977    }
978
979    #[test]
980    fn get_derived_property_diamond_dependencies() {
981        let mut context = Context::new();
982        let person = context.add_person(((Age, 17), (IsSwimmer, true))).unwrap();
983
984        let flag = Rc::new(RefCell::new(0));
985        let flag_clone = flag.clone();
986        assert!(!context.get_person_property(person, AdultAthlete));
987        context.subscribe_to_event(
988            move |_context, event: PersonPropertyChangeEvent<AdultAthlete>| {
989                assert_eq!(event.person_id.0, 0);
990                assert!(!event.previous);
991                assert!(event.current);
992                *flag_clone.borrow_mut() += 1;
993            },
994        );
995        context.set_person_property(person, Age, 18);
996        context.execute();
997        assert_eq!(*flag.borrow(), 1);
998    }
999
1000    #[test]
1001    fn get_derived_property_with_globals() {
1002        let mut context = Context::new();
1003        context.set_global_property_value(ThresholdP, 18).unwrap();
1004        let child = context.add_person((Age, 17)).unwrap();
1005        let adult = context.add_person((Age, 19)).unwrap();
1006        assert!(!context.get_person_property(child, IsEligible));
1007        assert!(context.get_person_property(adult, IsEligible));
1008    }
1009
1010    #[test]
1011    fn text_match_person() {
1012        let mut context = Context::new();
1013        let person = context
1014            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
1015            .unwrap();
1016        assert!(context.match_person(person, ((Age, 42), (RiskCategory, RiskCategoryValue::High))));
1017        assert!(!context.match_person(person, ((Age, 43), (RiskCategory, RiskCategoryValue::High))));
1018        assert!(!context.match_person(person, ((Age, 42), (RiskCategory, RiskCategoryValue::Low))));
1019    }
1020
1021    #[test]
1022    fn test_filter_people() {
1023        let mut context = Context::new();
1024        let _ = context.add_person((Age, 40)).unwrap();
1025        let _ = context.add_person((Age, 42)).unwrap();
1026        let _ = context.add_person((Age, 42)).unwrap();
1027        let mut all_people = context.query_people(());
1028
1029        let mut result = all_people.clone();
1030        context.filter_people(&mut result, (Age, 42));
1031        assert_eq!(result.len(), 2);
1032
1033        context.filter_people(&mut all_people, (Age, 43));
1034        assert!(all_people.is_empty());
1035    }
1036
1037    #[test]
1038    fn test_sample_person_simple() {
1039        define_rng!(SampleRng1);
1040        let mut context = Context::new();
1041        context.init_random(42);
1042        assert!(context.sample_person(SampleRng1, ()).is_none());
1043        let person = context.add_person(()).unwrap();
1044        assert_eq!(context.sample_person(SampleRng1, ()).unwrap(), person);
1045    }
1046
1047    #[test]
1048    fn test_sample_person_distribution() {
1049        define_rng!(SampleRng2);
1050
1051        let mut context = Context::new();
1052        context.init_random(42);
1053
1054        // Test an empty query.
1055        assert!(context.sample_person(SampleRng2, ()).is_none());
1056        let person1 = context.add_person((Age, 10)).unwrap();
1057        let person2 = context.add_person((Age, 10)).unwrap();
1058        let person3 = context.add_person((Age, 10)).unwrap();
1059        let person4 = context.add_person((Age, 30)).unwrap();
1060
1061        // Test a non-matching query.
1062        assert!(context.sample_person(SampleRng2, (Age, 50)).is_none());
1063
1064        // See that the simple query always returns person4
1065        for _ in 0..10 {
1066            assert_eq!(
1067                context.sample_person(SampleRng2, (Age, 30)).unwrap(),
1068                person4
1069            );
1070        }
1071
1072        let mut count_p1: usize = 0;
1073        let mut count_p2: usize = 0;
1074        let mut count_p3: usize = 0;
1075        for _ in 0..30000 {
1076            let p = context.sample_person(SampleRng2, (Age, 10)).unwrap();
1077            if p == person1 {
1078                count_p1 += 1;
1079            } else if p == person2 {
1080                count_p2 += 1;
1081            } else if p == person3 {
1082                count_p3 += 1;
1083            } else {
1084                panic!("Unexpected person");
1085            }
1086        }
1087
1088        // The chance of any of these being more unbalanced than this is ~10^{-4}
1089        assert!(count_p1 >= 8700);
1090        assert!(count_p2 >= 8700);
1091        assert!(count_p3 >= 8700);
1092    }
1093
1094    #[test]
1095    fn test_sample_people_distribution() {
1096        define_rng!(SampleRng5);
1097
1098        let mut context = Context::new();
1099        context.init_random(66);
1100
1101        // Test an empty query.
1102        assert!(context.sample_person(SampleRng5, ()).is_none());
1103        let person1 = context.add_person((Age, 10)).unwrap();
1104        let person2 = context.add_person((Age, 10)).unwrap();
1105        let person3 = context.add_person((Age, 10)).unwrap();
1106        let person4 = context.add_person((Age, 44)).unwrap();
1107        let person5 = context.add_person((Age, 10)).unwrap();
1108        let person6 = context.add_person((Age, 10)).unwrap();
1109        let person7 = context.add_person((Age, 22)).unwrap();
1110        let person8 = context.add_person((Age, 10)).unwrap();
1111
1112        let mut count_p1: usize = 0;
1113        let mut count_p2: usize = 0;
1114        let mut count_p3: usize = 0;
1115        let mut count_p5: usize = 0;
1116        let mut count_p6: usize = 0;
1117        let mut count_p8: usize = 0;
1118        for _ in 0..60000 {
1119            let p = context.sample_people(SampleRng5, (Age, 10), 2);
1120            if p.contains(&person1) {
1121                count_p1 += 1;
1122            }
1123            if p.contains(&person2) {
1124                count_p2 += 1;
1125            }
1126            if p.contains(&person3) {
1127                count_p3 += 1;
1128            }
1129            if p.contains(&person5) {
1130                count_p5 += 1;
1131            }
1132            if p.contains(&person6) {
1133                count_p6 += 1;
1134            }
1135            if p.contains(&person8) {
1136                count_p8 += 1;
1137            }
1138            if p.contains(&person4) || p.contains(&person7) {
1139                println!("Unexpected person in sample: {:?}", p);
1140                panic!("Unexpected person");
1141            }
1142        }
1143
1144        // The chance of any of these being more unbalanced than this is ~10^{-4}
1145        assert!(count_p1 >= 8700);
1146        assert!(count_p2 >= 8700);
1147        assert!(count_p3 >= 8700);
1148        assert!(count_p5 >= 8700);
1149        assert!(count_p6 >= 8700);
1150        assert!(count_p8 >= 8700);
1151    }
1152
1153    #[test]
1154    fn test_sample_people_simple() {
1155        define_rng!(SampleRng3);
1156        let mut context = Context::new();
1157        context.init_random(42);
1158        let people0 = context.sample_people(SampleRng3, (), 1);
1159        assert_eq!(people0.len(), 0);
1160        let person1 = context.add_person(()).unwrap();
1161        let person2 = context.add_person(()).unwrap();
1162        let person3 = context.add_person(()).unwrap();
1163
1164        let people1 = context.sample_people(SampleRng3, (), 1);
1165        assert_eq!(people1.len(), 1);
1166        assert!(
1167            people1.contains(&person1) || people1.contains(&person2) || people1.contains(&person3)
1168        );
1169
1170        let people2 = context.sample_people(SampleRng3, (), 2);
1171        assert_eq!(people2.len(), 2);
1172
1173        let people3 = context.sample_people(SampleRng3, (), 3);
1174        assert_eq!(people3.len(), 3);
1175
1176        let people4 = context.sample_people(SampleRng3, (), 4);
1177        assert_eq!(people4.len(), 3);
1178    }
1179
1180    #[test]
1181    fn test_sample_people() {
1182        define_rng!(SampleRng4);
1183        let mut context = Context::new();
1184        context.init_random(42);
1185        let person1 = context
1186            .add_person(((Age, 40), (RiskCategory, RiskCategoryValue::High)))
1187            .unwrap();
1188        let _ = context
1189            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
1190            .unwrap();
1191        let person3 = context
1192            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
1193            .unwrap();
1194        let _ = context
1195            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
1196            .unwrap();
1197        let _ = context
1198            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::High)))
1199            .unwrap();
1200        let person6 = context
1201            .add_person(((Age, 42), (RiskCategory, RiskCategoryValue::Low)))
1202            .unwrap();
1203
1204        // Test a non-matching query.
1205        assert!(context.sample_people(SampleRng4, (Age, 50), 1).is_empty());
1206
1207        // See that the simple query always returns person4
1208        for _ in 0..10 {
1209            assert!(context
1210                .sample_people(SampleRng4, (Age, 40), 1)
1211                .contains(&person1));
1212        }
1213
1214        let people1 = context.sample_people(SampleRng4, (Age, 40), 2);
1215        assert_eq!(people1.len(), 1);
1216        assert!(people1.contains(&person1));
1217
1218        let people2 = context.sample_people(SampleRng4, (Age, 42), 2);
1219        assert_eq!(people2.len(), 2);
1220        assert!(!people2.contains(&person1));
1221
1222        let people3 = context.sample_people(
1223            SampleRng4,
1224            ((Age, 42), (RiskCategory, RiskCategoryValue::High)),
1225            2,
1226        );
1227        assert_eq!(people3.len(), 2);
1228        assert!(
1229            !people3.contains(&person1)
1230                && !people3.contains(&person3)
1231                && !people3.contains(&person6)
1232        );
1233    }
1234
1235    mod property_initialization_queries {
1236        use super::*;
1237
1238        define_rng!(PropertyInitRng);
1239        define_person_property_with_default!(SimplePropWithDefault, u8, 1);
1240        define_derived_property!(DerivedOnce, u8, [SimplePropWithDefault], |n| n * 2);
1241        define_derived_property!(DerivedTwice, bool, [DerivedOnce], |n| n == 2);
1242
1243        #[test]
1244        fn test_query_derived_property_not_initialized() {
1245            let mut context = Context::new();
1246            context.init_random(42);
1247            let person = context.add_person(()).unwrap();
1248            assert_eq!(
1249                context
1250                    .sample_person(PropertyInitRng, (DerivedOnce, 2))
1251                    .unwrap(),
1252                person
1253            );
1254        }
1255
1256        #[test]
1257        fn test_query_derived_property_not_initialized_two_levels() {
1258            let mut context = Context::new();
1259            context.init_random(42);
1260            let person = context.add_person(()).unwrap();
1261            assert_eq!(
1262                context
1263                    .sample_person(PropertyInitRng, (DerivedTwice, true))
1264                    .unwrap(),
1265                person
1266            );
1267        }
1268    }
1269}