1use std::borrow::Borrow;
14use std::ops::Range;
15
16use log::warn;
17use rand::Rng;
18
19use super::{EntitySetIterator, SourceSet};
20use crate::entity::{Entity, EntityId};
21use crate::random::{
22 count_and_sample_single_l_reservoir, sample_multiple_from_known_length,
23 sample_multiple_l_reservoir, sample_single_excluding_l_reservoir, sample_single_l_reservoir,
24};
25
26pub struct EntitySet<'a, E: Entity>(EntitySetInner<'a, E>);
28
29pub(super) enum EntitySetInner<'a, E: Entity> {
31 Source(SourceSet<'a, E>),
32 Union(Box<EntitySet<'a, E>>, Box<EntitySet<'a, E>>),
33 Intersection(Vec<EntitySet<'a, E>>),
34 Difference(Box<EntitySet<'a, E>>, Box<EntitySet<'a, E>>),
35}
36
37impl<'a, E: Entity> Clone for EntitySet<'a, E> {
38 fn clone(&self) -> Self {
39 Self(self.0.clone())
40 }
41}
42
43impl<'a, E: Entity> Clone for EntitySetInner<'a, E> {
44 fn clone(&self) -> Self {
45 match self {
46 Self::Source(source) => Self::Source(source.clone()),
47 Self::Union(left, right) => Self::Union(left.clone(), right.clone()),
48 Self::Intersection(sets) => Self::Intersection(sets.clone()),
49 Self::Difference(left, right) => Self::Difference(left.clone(), right.clone()),
50 }
51 }
52}
53
54impl<'a, E: Entity> Default for EntitySet<'a, E> {
55 fn default() -> Self {
56 Self::empty()
57 }
58}
59
60impl<'a, E: Entity> EntitySet<'a, E> {
61 pub(super) fn into_inner(self) -> EntitySetInner<'a, E> {
62 self.0
63 }
64
65 pub(super) fn is_source_leaf(&self) -> bool {
66 matches!(self.0, EntitySetInner::Source(_))
67 }
68
69 pub(super) fn into_source_leaf(self) -> Option<SourceSet<'a, E>> {
70 match self.0 {
71 EntitySetInner::Source(source) => Some(source),
72 _ => None,
73 }
74 }
75
76 #[must_use]
78 pub fn empty() -> Self {
79 EntitySet(EntitySetInner::Source(SourceSet::empty()))
80 }
81
82 pub(crate) fn from_source(source: SourceSet<'a, E>) -> Self {
84 EntitySet(EntitySetInner::Source(source))
85 }
86
87 pub(crate) fn from_intersection_sources(mut sources: Vec<SourceSet<'a, E>>) -> Self {
88 match sources.len() {
89 0 => return Self::empty(),
90 1 => return Self::from_source(sources.pop().unwrap()),
91 _ => {}
92 }
93
94 sources.sort_unstable_by_key(SourceSet::sort_key);
97
98 let sets = sources.into_iter().map(Self::from_source).collect();
99
100 EntitySet(EntitySetInner::Intersection(sets))
101 }
102
103 #[must_use]
104 pub fn union(self, other: Self) -> Self {
105 if self.structurally_eq(&other) {
107 return self;
108 }
109
110 if let (Some(a), Some(b)) = (self.as_range(), other.as_range()) {
112 if a.start <= b.end && b.start <= a.end {
113 return Self::from_source(SourceSet::population_range(
114 a.start.min(b.start)..a.end.max(b.end),
115 ));
116 }
117 }
118
119 match (self.is_empty(), other.is_empty()) {
121 (true, _) => return other,
122 (_, true) => return self,
123 _ => { }
124 }
125
126 let (left, right) = if self.sort_key() >= other.sort_key() {
128 (self, other)
129 } else {
130 (other, self)
131 };
132 EntitySet(EntitySetInner::Union(Box::new(left), Box::new(right)))
133 }
134
135 #[must_use]
136 pub fn intersection(self, other: Self) -> Self {
137 if self.structurally_eq(&other) {
139 return self;
140 }
141
142 if let (Some(a), Some(b)) = (self.as_range(), other.as_range()) {
144 let overlap = a.start.max(b.start)..a.end.min(b.end);
145 return if overlap.is_empty() {
146 Self::empty()
147 } else {
148 Self::from_source(SourceSet::population_range(overlap))
149 };
150 }
151
152 match (self.is_empty(), other.is_empty()) {
154 (true, _) => return self,
155 (_, true) => return other,
156 _ => { }
157 }
158
159 let mut sets = match self {
160 EntitySet(EntitySetInner::Intersection(sets)) => sets,
161 _ => vec![self],
162 };
163
164 sets.push(other);
165 sets.sort_unstable_by_key(EntitySet::sort_key);
168 EntitySet(EntitySetInner::Intersection(sets))
169 }
170
171 #[must_use]
172 pub fn difference(self, other: Self) -> Self {
173 if self.structurally_eq(&other) {
175 return Self::empty();
176 }
177
178 if let (Some(a), Some(b)) = (self.as_range(), other.as_range()) {
179 let overlap = a.start.max(b.start)..a.end.min(b.end);
180 if overlap.is_empty() {
182 return Self::from_source(SourceSet::population_range(a));
183 }
184 if overlap.start == a.start && overlap.end == a.end {
186 return Self::empty();
187 }
188 if overlap.start == a.start {
190 return Self::from_source(SourceSet::population_range(overlap.end..a.end));
191 }
192 if overlap.end == a.end {
194 return Self::from_source(SourceSet::population_range(a.start..overlap.start));
195 }
196 }
198
199 if self.is_empty() || other.is_empty() {
201 return self;
202 }
203
204 EntitySet(EntitySetInner::Difference(Box::new(self), Box::new(other)))
205 }
206
207 #[must_use]
209 pub fn contains(&self, entity_id: EntityId<E>) -> bool {
210 match self {
211 EntitySet(EntitySetInner::Source(source)) => source.contains(entity_id),
212 EntitySet(EntitySetInner::Union(a, b)) => {
213 a.contains(entity_id) || b.contains(entity_id)
214 }
215 EntitySet(EntitySetInner::Intersection(sets)) => {
216 sets.iter().all(|set| set.contains(entity_id))
217 }
218 EntitySet(EntitySetInner::Difference(a, b)) => {
219 a.contains(entity_id) && !b.contains(entity_id)
220 }
221 }
222 }
223
224 #[must_use]
226 pub fn to_owned_vec(self) -> Vec<EntityId<E>> {
227 self.into_iter().collect()
228 }
229
230 #[must_use]
239 pub fn sample_entity_excluding<R, X>(&self, rng: &mut R, excluded: X) -> Option<EntityId<E>>
240 where
241 R: Rng,
242 X: Borrow<EntityId<E>>,
243 {
244 let excluded = *excluded.borrow();
245 if let Some(n) = self.try_len() {
246 if n == 0 {
247 return None;
248 }
249 let p = rng.random_range(0..n as u32) as usize;
250 let candidate = self.try_nth(p)?;
251 if candidate != excluded {
252 return Some(candidate);
253 }
254 if n == 1 {
258 return None;
259 }
260 let k = rng.random_range(0..(n - 1) as u32) as usize;
261 let target = if k < p { k } else { k + 1 };
262 return self.try_nth(target);
263 }
264 sample_single_excluding_l_reservoir(rng, self.clone(), excluded)
265 }
266
267 #[must_use]
270 pub fn sample_entity<R>(&self, rng: &mut R) -> Option<EntityId<E>>
271 where
272 R: Rng,
273 {
274 if let Some(n) = self.try_len() {
275 if n == 0 {
276 warn!("Requested a sample entity from an empty population");
277 return None;
278 }
279 let index = rng.random_range(0..n as u32) as usize;
281 return self.try_nth(index);
282 }
283 sample_single_l_reservoir(rng, self.clone())
284 }
285
286 #[must_use]
290 pub fn count_and_sample_entity<R>(&self, rng: &mut R) -> (usize, Option<EntityId<E>>)
291 where
292 R: Rng,
293 {
294 if let Some(n) = self.try_len() {
295 if n == 0 {
296 return (0, None);
297 }
298 let index = rng.random_range(0..n as u32) as usize;
299 return (n, self.try_nth(index));
300 }
301 count_and_sample_single_l_reservoir(rng, self.clone())
302 }
303
304 #[must_use]
307 pub fn sample_entities<R>(&self, rng: &mut R, requested: usize) -> Vec<EntityId<E>>
308 where
309 R: Rng,
310 {
311 match self.try_len() {
312 Some(0) => {
313 warn!("Requested a sample of entities from an empty population");
314 vec![]
315 }
316 Some(_) => sample_multiple_from_known_length(rng, self.clone(), requested),
317 None => sample_multiple_l_reservoir(rng, self.clone(), requested),
318 }
319 }
320
321 #[must_use]
326 pub fn try_len(&self) -> Option<usize> {
327 match self {
328 EntitySet(EntitySetInner::Source(source)) => source.try_len(),
329 _ => None,
330 }
331 }
332
333 fn try_nth(&self, idx: usize) -> Option<EntityId<E>> {
335 match self {
336 EntitySet(EntitySetInner::Source(source)) => source.try_nth(idx),
337 _ => None,
338 }
339 }
340
341 fn as_range(&self) -> Option<Range<usize>> {
342 match self {
343 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(range))) => {
344 Some(range.clone())
345 }
346 _ => None,
347 }
348 }
349
350 fn is_empty(&self) -> bool {
351 match self {
352 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(range))) => {
353 range.is_empty()
354 }
355 _ => false,
356 }
357 }
358
359 fn sort_key(&self) -> (usize, u8) {
360 match self {
361 EntitySet(EntitySetInner::Source(source)) => source.sort_key(),
362 EntitySet(EntitySetInner::Union(left, right)) => {
363 let (left_upper, left_hint) = left.sort_key();
365 let (right_upper, right_hint) = right.sort_key();
366 (
367 left_upper.saturating_add(right_upper),
368 left_hint.min(right_hint),
369 )
370 }
371 EntitySet(EntitySetInner::Intersection(sets)) => {
372 let mut upper = usize::MAX;
373 let mut hint = 0u8;
374 for set in sets {
375 let (set_upper, set_hint) = set.sort_key();
376 upper = upper.min(set_upper);
377 hint = hint.saturating_add(set_hint);
378 }
379 if upper == usize::MAX {
380 upper = 0;
381 }
382 (upper, hint)
383 }
384 EntitySet(EntitySetInner::Difference(left, right)) => {
385 let (left_upper, left_hint) = left.sort_key();
386 let (_, right_hint) = right.sort_key();
387 (left_upper, left_hint.saturating_add(right_hint))
388 }
389 }
390 }
391
392 fn structurally_eq(&self, other: &Self) -> bool {
394 match (self, other) {
395 (EntitySet(EntitySetInner::Source(a)), EntitySet(EntitySetInner::Source(b))) => a == b,
396 (
397 EntitySet(EntitySetInner::Union(a1, a2)),
398 EntitySet(EntitySetInner::Union(b1, b2)),
399 )
400 | (
401 EntitySet(EntitySetInner::Difference(a1, a2)),
402 EntitySet(EntitySetInner::Difference(b1, b2)),
403 ) => a1.structurally_eq(b1) && a2.structurally_eq(b2),
404 (
405 EntitySet(EntitySetInner::Intersection(a_sets)),
406 EntitySet(EntitySetInner::Intersection(b_sets)),
407 ) => {
408 a_sets.len() == b_sets.len()
409 && a_sets
410 .iter()
411 .zip(b_sets.iter())
412 .all(|(a_set, b_set)| a_set.structurally_eq(b_set))
413 }
414 _ => false,
415 }
416 }
417}
418
419impl<'a, E: Entity> IntoIterator for EntitySet<'a, E> {
420 type Item = EntityId<E>;
421 type IntoIter = EntitySetIterator<'a, E>;
422
423 fn into_iter(self) -> Self::IntoIter {
424 EntitySetIterator::new(self)
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use rand::rngs::StdRng;
431 use rand::SeedableRng;
432
433 use super::*;
434 use crate::entity::ContextEntitiesExt;
435 use crate::hashing::IndexSet;
436 use crate::{
437 define_derived_property, define_entity, define_multi_property, define_property, with,
438 Context,
439 };
440
441 define_entity!(Person);
442 define_property!(struct Age(u8), Person);
443 define_property!(struct County(u32), Person, default_const = County(0));
444 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
445 define_multi_property!(Person, (Age, County));
446 define_multi_property!(Person, (County, Age));
447
448 fn finite_set(ids: &[usize]) -> IndexSet<EntityId<Person>> {
449 ids.iter()
450 .copied()
451 .map(EntityId::<Person>::new)
452 .collect::<IndexSet<_>>()
453 }
454
455 fn as_entity_set(set: &IndexSet<EntityId<Person>>) -> EntitySet<Person> {
456 EntitySet::from_source(SourceSet::IndexSet(set))
457 }
458
459 #[test]
460 fn from_source_empty_is_empty() {
461 let es = EntitySet::<Person>::empty();
462 assert_eq!(es.sort_key().0, 0);
463 for value in 0..10 {
464 assert!(!es.contains(EntityId::<Person>::new(value)));
465 }
466 }
467
468 #[test]
469 fn from_source_population_ranges() {
470 let population = EntitySet::from_source(SourceSet::<Person>::population_range(0..3));
471 assert!(population.contains(EntityId::<Person>::new(0)));
472 assert!(population.contains(EntityId::<Person>::new(2)));
473 assert!(!population.contains(EntityId::<Person>::new(3)));
474 assert_eq!(population.sort_key().0, 3);
475
476 let singleton = EntitySet::from_source(SourceSet::<Person>::singleton(EntityId::new(5)));
477 assert!(singleton.contains(EntityId::<Person>::new(5)));
478 assert!(!singleton.contains(EntityId::<Person>::new(4)));
479 assert_eq!(singleton.sort_key().0, 1);
480
481 let range = EntitySet::from_source(SourceSet::<Person>::population_range(2..5));
482 assert!(range.contains(EntityId::<Person>::new(2)));
483 assert!(range.contains(EntityId::<Person>::new(4)));
484 assert!(!range.contains(EntityId::<Person>::new(1)));
485 assert!(!range.contains(EntityId::<Person>::new(5)));
486 assert_eq!(range.try_len(), Some(3));
487 }
488
489 #[test]
490 fn union_basic_behavior_without_legacy_reductions() {
491 let a = finite_set(&[1, 2, 3]);
492 let e = EntitySet::<Person>::empty();
493 let u = EntitySet::from_source(SourceSet::<Person>::population_range(0..10));
494
495 let a_union_empty = as_entity_set(&a).union(e);
496 assert!(a_union_empty.contains(EntityId::<Person>::new(1)));
497 assert!(!a_union_empty.contains(EntityId::<Person>::new(4)));
498
499 let u_union_a = u.union(as_entity_set(&a));
500 assert!(u_union_a.contains(EntityId::<Person>::new(0)));
501 assert!(u_union_a.contains(EntityId::<Person>::new(9)));
502 assert!(!u_union_a.contains(EntityId::<Person>::new(10)));
503 }
504
505 #[test]
506 fn union_range_optimizations() {
507 let adjacent = EntitySet::from_source(SourceSet::<Person>::population_range(0..3)).union(
508 EntitySet::from_source(SourceSet::<Person>::population_range(3..6)),
509 );
510 assert!(matches!(
511 adjacent,
512 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(0..6)
513 ));
514
515 let overlapping = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
516 .union(EntitySet::from_source(
517 SourceSet::<Person>::population_range(4..8),
518 ));
519 assert!(matches!(
520 overlapping,
521 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(2..8)
522 ));
523
524 let disjoint = EntitySet::from_source(SourceSet::<Person>::singleton(EntityId::new(1)))
525 .union(EntitySet::from_source(SourceSet::<Person>::singleton(
526 EntityId::new(4),
527 )));
528 assert!(matches!(disjoint, EntitySet(EntitySetInner::Union(_, _))));
529 }
530
531 #[test]
532 fn intersection_range_optimizations() {
533 let overlap = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
534 .intersection(EntitySet::from_source(
535 SourceSet::<Person>::population_range(4..8),
536 ));
537 assert!(matches!(
538 overlap,
539 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(4..6)
540 ));
541
542 let empty = EntitySet::from_source(SourceSet::<Person>::population_range(1..3))
543 .intersection(EntitySet::from_source(
544 SourceSet::<Person>::population_range(5..7),
545 ));
546 assert!(matches!(
547 empty,
548 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(0..0)
549 ));
550
551 let indexed_ids = finite_set(&[1, 2, 3]);
552 let mixed = EntitySet::from_source(SourceSet::<Person>::singleton(EntityId::new(2)))
553 .intersection(as_entity_set(&indexed_ids));
554 assert!(mixed.contains(EntityId::<Person>::new(2)));
555 assert!(!mixed.contains(EntityId::<Person>::new(1)));
556 }
557
558 #[test]
559 fn difference_range_optimizations() {
560 let unchanged = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
561 .difference(EntitySet::from_source(
562 SourceSet::<Person>::population_range(8..10),
563 ));
564 assert!(matches!(
565 unchanged,
566 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(2..6)
567 ));
568
569 let empty = EntitySet::from_source(SourceSet::<Person>::population_range(2..6)).difference(
570 EntitySet::from_source(SourceSet::<Person>::population_range(1..7)),
571 );
572 assert!(matches!(
573 empty,
574 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(0..0)
575 ));
576
577 let trim_left = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
578 .difference(EntitySet::from_source(
579 SourceSet::<Person>::population_range(1..4),
580 ));
581 assert!(matches!(
582 trim_left,
583 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(4..6)
584 ));
585
586 let trim_right = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
587 .difference(EntitySet::from_source(
588 SourceSet::<Person>::population_range(4..8),
589 ));
590 assert!(matches!(
591 trim_right,
592 EntitySet(EntitySetInner::Source(SourceSet::PopulationRange(ref range))) if range == &(2..4)
593 ));
594
595 let split = EntitySet::from_source(SourceSet::<Person>::population_range(2..8)).difference(
596 EntitySet::from_source(SourceSet::<Person>::population_range(4..6)),
597 );
598 assert!(matches!(split, EntitySet(EntitySetInner::Difference(_, _))));
599 assert!(split.contains(EntityId::<Person>::new(2)));
600 assert!(split.contains(EntityId::<Person>::new(7)));
601 assert!(!split.contains(EntityId::<Person>::new(4)));
602 assert!(!split.contains(EntityId::<Person>::new(5)));
603 }
604
605 #[test]
606 fn difference_is_not_commutative() {
607 let a = finite_set(&[1, 2, 3]);
608 let b = finite_set(&[2, 3, 4]);
609
610 let d1 = as_entity_set(&a).difference(as_entity_set(&b));
611 let c = finite_set(&[2, 3, 4]);
612 let d = finite_set(&[1, 2, 3]);
613 let d2 = as_entity_set(&c).difference(as_entity_set(&d));
614
615 assert!(d1.contains(EntityId::<Person>::new(1)));
616 assert!(!d1.contains(EntityId::<Person>::new(4)));
617 assert!(d2.contains(EntityId::<Person>::new(4)));
618 assert!(!d2.contains(EntityId::<Person>::new(1)));
619 }
620
621 #[test]
622 fn sort_key_rules() {
623 let a = finite_set(&[1, 2]);
624 let b = finite_set(&[2, 3, 4]);
625
626 let union = as_entity_set(&a).union(as_entity_set(&b));
627 assert_eq!(union.sort_key(), (a.len() + b.len(), 3));
628
629 let intersection = as_entity_set(&a).intersection(as_entity_set(&b));
630 assert_eq!(intersection.sort_key(), (a.len().min(b.len()), 6));
631
632 let difference = as_entity_set(&a).difference(as_entity_set(&b));
633 assert_eq!(difference.sort_key(), (a.len(), 6));
634 }
635
636 #[test]
637 fn compound_expressions_membership() {
638 let a = finite_set(&[1, 2, 3, 4]);
639 let b = finite_set(&[3, 4, 5]);
640 let c = finite_set(&[10, 20]);
641 let d = finite_set(&[20]);
642
643 let union_of_intersections = as_entity_set(&a)
644 .intersection(as_entity_set(&b))
645 .union(as_entity_set(&c).intersection(as_entity_set(&d)));
646 assert!(union_of_intersections.contains(EntityId::<Person>::new(3)));
647 assert!(union_of_intersections.contains(EntityId::<Person>::new(4)));
648 assert!(union_of_intersections.contains(EntityId::<Person>::new(20)));
649 assert!(!union_of_intersections.contains(EntityId::<Person>::new(5)));
650
651 let a2 = finite_set(&[1, 2, 3]);
652 let b2 = finite_set(&[3, 4, 5]);
653 let a3 = finite_set(&[1, 2, 3]);
654 let law = as_entity_set(&a3).intersection(as_entity_set(&a2).union(as_entity_set(&b2)));
655 assert!(law.contains(EntityId::<Person>::new(1)));
656 assert!(law.contains(EntityId::<Person>::new(2)));
657 assert!(law.contains(EntityId::<Person>::new(3)));
658 assert!(!law.contains(EntityId::<Person>::new(4)));
659 }
660
661 #[test]
662 fn clone_preserves_composite_expression_behavior() {
663 let a = finite_set(&[1, 2, 3, 4]);
664 let b = finite_set(&[3, 4, 5]);
665 let c = finite_set(&[2]);
666
667 let set = as_entity_set(&a)
668 .difference(as_entity_set(&c))
669 .union(as_entity_set(&b));
670 let cloned = set.clone();
671
672 for value in 0..7 {
673 let entity_id = EntityId::<Person>::new(value);
674 assert_eq!(set.contains(entity_id), cloned.contains(entity_id));
675 }
676
677 assert_eq!(
678 set.into_iter().collect::<Vec<_>>(),
679 cloned.into_iter().collect::<Vec<_>>()
680 );
681 }
682
683 #[test]
684 fn population_zero_is_empty() {
685 let es = EntitySet::from_source(SourceSet::<Person>::empty());
686 assert_eq!(es.sort_key().0, 0);
687 assert!(!es.contains(EntityId::<Person>::new(0)));
688 }
689
690 #[test]
691 fn try_len_known_only_for_non_property_sources() {
692 let empty = EntitySet::<Person>::from_source(SourceSet::empty());
693 assert_eq!(empty.try_len(), Some(0));
694
695 let singleton = EntitySet::<Person>::from_source(SourceSet::singleton(EntityId::new(42)));
696 assert_eq!(singleton.try_len(), Some(1));
697
698 let population = EntitySet::<Person>::from_source(SourceSet::population_range(0..5));
699 assert_eq!(population.try_len(), Some(5));
700
701 let range = EntitySet::<Person>::from_source(SourceSet::population_range(4..9));
702 assert_eq!(range.try_len(), Some(5));
703
704 let index_data = [EntityId::new(1), EntityId::new(2), EntityId::new(3)]
705 .into_iter()
706 .collect::<IndexSet<_>>();
707 let indexed = EntitySet::<Person>::from_source(SourceSet::IndexSet(&index_data));
708 assert_eq!(indexed.try_len(), Some(3));
709
710 let mut context = Context::new();
711 context.add_entity(with!(Person, Age(10))).unwrap();
712 let property_source = SourceSet::<Person>::new(Age(10), &context).unwrap();
713 assert!(matches!(property_source, SourceSet::PropertySet(_)));
714 let property_set = EntitySet::<Person>::from_source(property_source);
715 assert_eq!(property_set.try_len(), None);
716
717 let composed = EntitySet::<Person>::from_source(SourceSet::population_range(0..3))
718 .difference(EntitySet::from_source(SourceSet::singleton(EntityId::new(
719 1,
720 ))));
721 assert_eq!(composed.try_len(), None);
722 }
723
724 #[test]
725 fn range_leaf_works_inside_composite_expressions() {
726 let indexed_ids = finite_set(&[1, 3, 5, 8]);
727 let indexed = as_entity_set(&indexed_ids);
728 let range = EntitySet::from_source(SourceSet::<Person>::population_range(2..8));
729
730 let intersection = range.intersection(indexed);
731 assert!(!intersection.contains(EntityId::new(1)));
732 assert!(intersection.contains(EntityId::new(3)));
733 assert!(intersection.contains(EntityId::new(5)));
734 assert!(!intersection.contains(EntityId::new(8)));
735 }
736
737 #[test]
738 fn clone_preserves_unindexed_concrete_property_query_results() {
739 let mut context = Context::new();
740 let p1 = context.add_entity(with!(Person, Age(10))).unwrap();
741 let p2 = context.add_entity(with!(Person, Age(10))).unwrap();
742 let _p3 = context.add_entity(with!(Person, Age(11))).unwrap();
743
744 let set = context.query::<Person, _>(with!(Person, Age(10)));
745 assert_eq!(set.try_len(), None);
746 let cloned = set.clone();
747
748 let mut iter = set.into_iter();
749 assert_eq!(iter.next(), Some(p1));
750 assert_eq!(iter.collect::<Vec<_>>(), vec![p2]);
751
752 assert!(cloned.contains(p1));
753 assert!(cloned.contains(p2));
754 assert_eq!(cloned.into_iter().collect::<Vec<_>>(), vec![p1, p2]);
755 }
756
757 #[test]
758 fn clone_preserves_unindexed_derived_property_query_results() {
759 let mut context = Context::new();
760 let _p1 = context.add_entity(with!(Person, Age(64))).unwrap();
761 let p2 = context.add_entity(with!(Person, Age(65))).unwrap();
762 let p3 = context.add_entity(with!(Person, Age(90))).unwrap();
763
764 let set = context.query::<Person, _>(with!(Person, Senior(true)));
765 assert_eq!(set.try_len(), None);
766 let cloned = set.clone();
767
768 assert!(set.contains(p2));
769 assert!(set.contains(p3));
770 assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![p2, p3]);
771 assert_eq!(cloned.into_iter().collect::<Vec<_>>(), vec![p2, p3]);
772 }
773
774 #[test]
775 fn union_of_same_unindexed_property_query_deduplicates_to_one_source() {
776 let mut context = Context::new();
777 let p1 = context.add_entity(with!(Person, Age(10))).unwrap();
778 let p2 = context.add_entity(with!(Person, Age(10))).unwrap();
779 let _p3 = context.add_entity(with!(Person, Age(11))).unwrap();
780
781 let query = context.query::<Person, _>(with!(Person, Age(10)));
782 let union = query.clone().union(query);
783
784 assert!(matches!(
785 union,
786 EntitySet(EntitySetInner::Source(SourceSet::PropertySet(_)))
787 ));
788 assert_eq!(union.into_iter().collect::<Vec<_>>(), vec![p1, p2]);
789 }
790
791 #[test]
792 fn union_of_equivalent_unindexed_multi_property_queries_deduplicates_to_one_source() {
793 let mut context = Context::new();
794 let matching = context
795 .add_entity(with!(Person, Age(28), County(7)))
796 .unwrap();
797 let _wrong_county = context
798 .add_entity(with!(Person, Age(28), County(8)))
799 .unwrap();
800 let _wrong_age = context
801 .add_entity(with!(Person, Age(29), County(7)))
802 .unwrap();
803
804 let age_county = context.query::<Person, _>(with!(Person, (Age(28), County(7))));
805 let county_age = context.query::<Person, _>(with!(Person, (County(7), Age(28))));
806 let union = age_county.union(county_age);
807
808 assert!(matches!(
809 union,
810 EntitySet(EntitySetInner::Source(SourceSet::PropertySet(_)))
811 ));
812 assert_eq!(union.into_iter().collect::<Vec<_>>(), vec![matching]);
813 }
814
815 #[test]
816 fn sample_entity_excluding_skips_excluded() {
817 let set = EntitySet::from_source(SourceSet::<Person>::PopulationRange(0..5));
818 let excluded = EntityId::<Person>::new(2);
819 let mut rng = StdRng::seed_from_u64(42);
820 for _ in 0..200 {
821 let sampled = set.sample_entity_excluding(&mut rng, excluded).unwrap();
822 assert_ne!(sampled, excluded);
823 assert!(sampled.0 < 5);
824 }
825 }
826
827 #[test]
828 fn sample_entity_excluding_returns_none_when_only_excluded_present() {
829 let only = EntityId::<Person>::new(7);
830 let single = finite_set(&[7]);
831 let mut rng = StdRng::seed_from_u64(42);
832 assert_eq!(
833 as_entity_set(&single).sample_entity_excluding(&mut rng, only),
834 None
835 );
836 }
837
838 #[test]
839 fn sample_entity_excluding_returns_none_on_empty() {
840 let mut rng = StdRng::seed_from_u64(42);
841 assert_eq!(
842 EntitySet::<Person>::empty()
843 .sample_entity_excluding(&mut rng, EntityId::<Person>::new(0)),
844 None
845 );
846 }
847
848 #[test]
849 fn sample_entity_excluding_excluded_not_in_set_uses_first_pick() {
850 let set = EntitySet::from_source(SourceSet::<Person>::PopulationRange(0..10));
853 let mut rng = StdRng::seed_from_u64(42);
854 for _ in 0..200 {
855 let sampled = set
856 .sample_entity_excluding(&mut rng, EntityId::<Person>::new(999))
857 .unwrap();
858 assert!(sampled.0 < 10);
859 }
860 }
861
862 #[test]
863 fn sample_entity_excluding_uniform_over_known_length() {
864 let excluded = EntityId::<Person>::new(7);
866 let set = EntitySet::from_source(SourceSet::<Person>::PopulationRange(0..20));
867 let num_runs = 50_000;
868 let mut counts = [0usize; 20];
869 let mut rng = StdRng::seed_from_u64(42);
870 for _ in 0..num_runs {
871 let id = set.sample_entity_excluding(&mut rng, excluded).unwrap();
872 counts[id.0] += 1;
873 }
874 assert_eq!(counts[excluded.0], 0);
875
876 let expected = num_runs as f64 / 19.0;
877 let chi_square: f64 = counts
878 .iter()
879 .enumerate()
880 .filter(|(i, _)| *i != excluded.0)
881 .map(|(_, &obs)| {
882 let diff = obs as f64 - expected;
883 diff * diff / expected
884 })
885 .sum();
886 assert!(chi_square < 42.31, "χ² = {chi_square}");
888 }
889}