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
19pub trait ContextPeopleExt {
22 fn get_current_population(&self) -> usize;
24
25 fn add_person<T: InitializationList>(&mut self, props: T) -> Result<PersonId, IxaError>;
35
36 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 fn set_person_property<T: PersonProperty>(
50 &mut self,
51 person_id: PersonId,
52 _property: T,
53 value: T::Value,
54 );
55
56 fn index_property<T: PersonProperty>(&mut self, property: T);
64
65 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 fn query_people<Q: Query>(&self, query: Q) -> Vec<PersonId>;
88
89 fn query_people_count<Q: Query>(&self, query: Q) -> usize;
101
102 fn match_person<Q: Query>(&self, person_id: PersonId, query: Q) -> bool;
106
107 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 fn sample_person<R: RngId + 'static, Q: Query>(&self, rng_id: R, query: Q) -> Option<PersonId>
123 where
124 R::RngType: Rng;
125
126 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 data_container.check_initialization_list(&props)?;
150
151 let person_id = data_container.add_person();
154
155 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 if let Some(value) = *data_container.get_person_property_ref(person_id, property) {
176 return value;
177 }
178
179 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 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 for dep in &mut deps {
238 dep.dependency_changed(self, person_id, &mut dependency_event_callbacks);
239 }
240
241 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 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(), };
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 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 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 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 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 if let Some(multi_property_id) = query.multi_property_type_id() {
449 let container = self.get_data(PeoplePlugin);
450 if let Some(index) = container
452 .property_indexes
453 .borrow_mut()
454 .get_mut(&multi_property_id)
455 {
456 index.index_unindexed_people(self);
458
459 if let Some(people_set) = index.get_with_hash(query.multi_property_value_hash()) {
460 if people_set.len() <= requested {
463 return people_set.to_owned_vec();
464 }
465
466 return self.sample(rng_id, |rng| {
469 sample_multiple_from_known_length(rng, people_set, requested)
470 });
471 }
472 }
473 }
474
475 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 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 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 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 if let Some(multi_property_id) = query.multi_property_type_id() {
535 let container = self.get_data(PeoplePlugin);
536 if let Some(index) = container
538 .property_indexes
539 .borrow_mut()
540 .get_mut(&multi_property_id)
541 {
542 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 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 self.query_people_internal(
566 |person| {
567 position += 1;
568 if next_pick_position == position {
569 selected = Some(person);
570 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 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 if let Some(multi_property_id) = query.multi_property_type_id() {
601 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 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 data_container.register_index::<T>();
648 }
649
650 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 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 for (t, _) in &property_hashes_working_set {
679 data_container.index_unindexed_people_for_type_id(self, *t);
680 }
681
682 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 return;
692 }
693 } else {
694 unindexed.push((t, hash));
696 }
697 }
698
699 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 'outer: for person in to_check {
718 for index in &indexes {
720 if !index.contains(&person) {
721 continue 'outer;
722 }
723 }
724
725 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 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 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 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 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 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 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 let has_value = *people_data.get_person_property_ref(person, RunningShoes);
931 assert!(has_value.is_none());
932
933 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 },
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 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 assert!(context.sample_person(SampleRng2, (Age, 50)).is_none());
1145
1146 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 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 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 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 assert!(context.sample_people(SampleRng4, (Age, 50), 1).is_empty());
1288
1289 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}