ixa/people/
context_extension.rs

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