ixa/people/
context_extension.rs

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