ixa 2.0.0-beta2.4

A framework for building agent-based models
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
# Properties

Properties are the data attached to an entity. For example, a `Person` entity might have the properties `Age` and
`InfectionStatus`. In Ixa, the property's value type is also the property type: the implementation of the trait
`Property\<Person>` by the concrete property type is what ties that Rust type to the `Person` entity.

Never implement `Property\<E>` for an `Entity` type `E` directly. Instead, use one of the provided macros:

| Macro                      | Use case                                      |
|:---------------------------|:----------------------------------------------|
| `define_property!`         | Simple struct or enum property                |
| `impl_property!`           | Existing type                                 |
| `define_derived_property!` | Simple derived struct or enum property        |
| `impl_derived_property!`   | Existing type as a derived property           |
| `define_multi_property!`   | Joint index/query key for multiple properties |

## Properties Basics

### Defining Properties

Most model code should define properties with `define_property!`:

```rust
use ixa::prelude::*;

define_entity!(Person);

define_property!(struct Age(u8), Person);

define_property!(
    enum InfectionStatus {
        Susceptible,
        Infectious,
        Recovered,
    },
    Person,
    default_const = InfectionStatus::Susceptible
);
```

The first argument is a Rust type declaration. The second argument is the entity the property belongs to. The optional
`default_const = ...` argument gives the property a constant value for new entities that do not provide one explicitly.
Without it, every call to `add_entity` requires a value for the property.

More advanced use cases and options are covered in the sections below.

> [!INFO] The newtype idiom in Rust
>
> For properties whose values are essentially [primitive types]https://doc.rust-lang.org/rust-by-example/primitives.html like `bool` or `u64`, we always use the [newtype idiom]https://doc.rust-lang.org/rust-by-example/generics/new_types.html. A "newtype" is a tuple struct with a single field that wraps an existing type to give it a new, distinct identity:
>
> ```rust
> struct Age(u8);
> ```
>
> Even though `Age` is "just" a `u8` under the hood, the Rust compiler treats `Age` and `u8` as completely different
> types. You cannot accidentally pass a `u8` where an `Age` is expected, nor mix up an `Age` with some other newtype like
> `struct BirthYear(u8)`. This is exactly what we want for properties: in Ixa, each property is identified by its Rust
> type, so `Age` and `BirthYear` must be distinct types even when they happen to wrap the same primitive.
>
> To read the inner value out of a newtype, use the tuple field accessor `.0`. To produce a new value, wrap a primitive
> with the type's constructor:
>
> ```rust
> let age: Age = context.get_property(person_id);
> let new_age = Age(age.0 + 1);   // unwrap with `.0`, do arithmetic, re-wrap
> context.set_property(person_id, new_age);
> ```
>
> If unwrapping and re-wrapping becomes tedious, you can implement methods or operator traits like `Add` directly on
> your newtype so that model code can work with it more naturally. For a more thorough treatment of newtypes and what
> they're good for, see the chapter on
> [advanced types]https://doc.rust-lang.org/book/ch20-03-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction
> in *The Rust Book*.

### Property Initialization

Every property of an entity instance must have a value. (See the section [Optional Properties](#optional-properties) for how to deal with
properties that are not always present.) How a property is initialized depends on how the property is defined and on
the property values supplied to `add_entity`.
Every property has one of three initialization behaviors:

| Kind       | How to define it                       | How new entities get a value                                  |
| ---------- | -------------------------------------- | ------------------------------------------------------------- |
| Explicit   | Omit `default_const = ...`             | The value must be supplied to `add_entity` with `with!`       |
| Constant   | Add `default_const = ...`              | The default is used unless a value is supplied with `with!`   |
| Derived    | Use `define_derived_property!`         | The value is computed from other properties and cannot be set |

Derived properties are covered in the section [Derived Properties](#derived-properties). They are computed from other
properties by definition, so it doesn't make sense to initialize them explicitly.

An explicit property must be provided when the entity is created:

```rust
define_property!(struct Age(u8), Person);

let person = context.add_entity(with!(Person, Age(42)))?;
```

A property with a default constant can be omitted at entity creation:

```rust
define_property!(
    enum InfectionStatus {
        Susceptible,
        Infectious,
        Recovered,
    },
    Person,
    default_const = InfectionStatus::Susceptible
);

let person = context.add_entity(Person)?;
```

You can still override a property's initial value constant:

```rust
let person = context.add_entity(with!(Person, InfectionStatus::Infectious))?;
```

The word "constant" refers to the fact that the default value is not itself computed—it is a static value provided in
the property definition. The property itself, however, can still be overwritten after the fact with
`context.set_property` regardless of how it was initialized.

### Getting and Setting Properties

Once an entity exists, use `get_property` and `set_property`:

```rust
// The person with ID `person_id` had a birthday.
let age: Age = context.get_property(person_id);
context.set_property(person_id, Age(age.0 + 1));
```

Derived properties are computed from their dependencies and update when those dependencies change. Therefore, you
cannot set a derived property directly with `set_property`.

You can also query by property values with `with!`:

```rust
// Get the set of all people who are infectious.
let infectious = context.query_result_iterator(with!(
    Person,
    InfectionStatus::Infectious
));
```

For performance-sensitive queries, see the chapter on
[Indexing](indexing.md).

### Optional Properties

Sometimes you want a property that does not always have a meaningful value.
The idiomatic way to do this is to use a type of the form `struct MyProperty(Option\<ValueType>)`.
For example, you might store the time of a person's last vaccination
as `struct LastVaccination(Option\<f64>)`.

```rust
define_property!(
    struct LastVaccination(Option<f64>),
    Person,
    impl_hash_eq = both,
    default_const = LastVaccination(None)
);
```

Notice that we provided a default of `LastVaccination(None)`, which stands for "no value set". We also provide
`impl_hash_eq = both` because our type contains an `f64`; see the section on [Floating Point Types and Implementing `Eq`
and `Hash`](#floating-point-types-and-implementing-eq-and-hash) for more details.

This is such a common pattern that Ixa detects the `Option` and provides a custom "display function" for it
for writing values to reports and diagnostics; see the section on
[Custom Display and Option Properties](#custom-display-and-option-properties).

## Custom Display and Option Properties

Ixa uses each property's display implementation in places where a property value
is rendered as text, including reports and diagnostics. By default,
`impl_property!` uses the property's `Debug` representation.

> [!INFO] The `Display` and `Debug` traits in Rust
>
> Rust types implement the `Display` and `Debug` traits to provide a textual representation of the type. `Debug` can be
> automatically `derive`d, shows a value in a developer-focused way, and is typically used for things like error
> messages or logs. `Display` shows a value in a user-facing way. You write it yourself to produce clean, readable
> output.
>
> In the context of Ixa properties, a property's "display function" is the function that is used to render a property
> for reports. We use the type's `Debug` representation by default because it can be automatically `derive`d for most
> types. Properties do not need to implement `Display` even if you supply a custom display function, although it can be
> useful to do so.

The `define_property!` macro automatically detects the case of a struct wrapping
a single `Option\<T>` field and provides a custom display function:

```rust
define_property!(
    struct DiagnosisDay(Option<u32>),
    Person,
    default_const = DiagnosisDay(None)
);
```

Without this special treatment, the values would display as:

| Value                    | Display                    |
| :----------------------- | :------------------------- |
| `DiagnosisDay(Some(14))` | `"DiagnosisDay(Some(14))"` |
| `DiagnosisDay(None)`     | `"DiagnosisDay(None)"`     |

The macro overrides this to display the values as:

| Value                    | Display  |
| :----------------------- | :------- |
| `DiagnosisDay(Some(14))` | `"14"`   |
| `DiagnosisDay(None)`     | `"None"` |

You can supply your own display function override for any property form using
`define_property!` as follows:

```rust
define_property!(
    enum InfectionStatus {
        Susceptible,
        Infectious,
        Recovered,
    },
    Person,
    default_const = InfectionStatus::Susceptible,
    display_impl = |status: &InfectionStatus| match status {
        InfectionStatus::Susceptible => "S".to_string(),
        InfectionStatus::Infectious => "I".to_string(),
        InfectionStatus::Recovered => "R".to_string(),
    }
);
```

Since the `display_impl` argument is attached to the `impl Property\<Entity> for ConcretePropertyType`, a single
`ConcretePropertyType` can have different display functions for different entities for which it is a property.

## When to Use `impl_property!`

Start with `define_property!` when the property can be expressed as one of its
supported type forms: a tuple struct, a named-field struct, or a simple enum:

```rust
define_property!(struct Age(u8), Person);

define_property!(
    struct Location {
        county: u16,
        tract: u32,
    },
    Person
);

define_property!(
    enum RiskGroup {
        Low,
        Medium,
        High,
    },
    Person
);
```

For these forms, `define_property!` creates the type, makes it public, adds the
standard derives Ixa needs, and then calls `impl_property!` for you. The
standard derives are `Debug`, `PartialEq`, `Eq`, `Hash`, `Clone`, `Copy`, and
`serde::Serialize`. The `serde::Serialize` trait is not strictly required, but
it is derived for convenience and compatibility with the reporting system.

Use `impl_property!` when the type already exists or when the type declaration
needs syntax that `define_property!` does not support. The common reasons are:

- You require a different set of derives than `define_property!` generates.
- You need an unsupported type form, such as a type with attributes, generic
  parameters, or more complex Rust syntax.
- You want to use the same Rust type as a property for multiple different
  entity types.

When you use `impl_property!`, you are responsible for making sure the type
implements the traits Ixa requires: `Copy`, `Clone`, `Debug`, `PartialEq`,
`Eq`, and `Hash`.

### Example: You require different derives

If a property type needs derives beyond the standard set generated by
`define_property!`, define the type yourself and then use `impl_property!`.
For example, a property loaded from external data may need `Deserialize`:

```rust
#[derive(
    Copy,
    Clone,
    Debug,
    PartialEq,
    Eq,
    Hash,
    serde::Serialize,
    serde::Deserialize,
)]
pub struct Age(pub u8);

impl_property!(Age, Person);
```

### Example: You need an unsupported type form

`define_property!` intentionally supports a small set of common type
declarations. If the declaration needs field attributes, variant attributes,
generics, non-public fields, or other syntax outside those forms, write the type
directly.

For example, `define_property!` cannot attach a field-level serde attribute:

```rust
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize)]
pub struct HouseholdCode {
    #[serde(rename = "household")]
    pub value: u32,
}

impl_property!(
    HouseholdCode,
    Person,
    default_const = HouseholdCode { value: 0 }
);
```

Another common example is an enum that needs `Default`, because Rust requires
the default variant to be marked with `#[default]`:

```rust
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub enum InfectionStatus {
    #[default]
    Susceptible,
    Infectious,
    Recovered,
}

impl_property!(
    InfectionStatus,
    Person,
    default_const = InfectionStatus::Susceptible
);
```

### Example: You want the same type on multiple entities

`define_property!` defines a new type and implements it as a property for one
entity. If you want the same Rust type to be a property of more than one entity,
use `define_property!` for the first entity, then call `impl_property!` for
each additional entity:

```rust
define_entity!(Person);
define_entity!(Group);

define_property!(
    enum InfectionKind {
        Respiratory,
        Genetic,
    },
    Person,
    default_const = InfectionKind::Respiratory
);

impl_property!(
    InfectionKind,
    Group,
    default_const = InfectionKind::Genetic
);
```

## Derived Properties

A derived property is computed from other properties instead of stored directly.
Use `define_derived_property!` for the common case:

```rust
define_property!(struct Age(u8), Person);

define_derived_property!(
    enum AgeGroup {
        Child,
        Adult,
        Senior,
    },
    Person,
    [Age],
    |age| {
        if age.0 < 18 {
            AgeGroup::Child
        } else if age.0 < 65 {
            AgeGroup::Adult
        } else {
            AgeGroup::Senior
        }
    }
);
```

The dependency list tells Ixa which stored properties affect the derived value.
When one of those dependencies changes, Ixa can update indexes and property
change events for the derived property.

Derived properties can depend on more than one property:

```rust
define_property!(struct Vaccinated(bool), Person, default_const = Vaccinated(false));

define_derived_property!(
    struct HighPriority(bool),
    Person,
    [Age, Vaccinated],
    |age, vaccinated| HighPriority(age.0 >= 65 && !vaccinated.0)
);
```

Derived properties can also depend on global properties. Put global dependencies
in a second bracketed list after the entity-property dependencies:

```rust
define_global_property!(AdultAge, u8);

define_derived_property!(
    struct IsAdult(bool),
    Person,
    [Age],
    [AdultAge],
    |age, adult_age| IsAdult(age.0 >= *adult_age)
);
```

Use `impl_derived_property!` when the derived property type already exists, just
as you would use `impl_property!` instead of `define_property!` for a
non-derived property.

## Floating Point Types and Implementing `Eq` and `Hash`

Properties participate in equality and hashing, especially when they are
indexed or queried. Plain `f64` and `f32` fields do not implement `Eq` and
`Hash`, so the default derives are not enough for properties that contain
floating-point values.

> [!INFO] Implementing `PartialEq` and `Eq` in Rust
>
> Properties need to implement `Eq`. In practice, this actually means implementing `PartialEq`. In fact, the `Eq` trait
> is just a marker triat—it has no methods! The `Eq` trait is a guarantee by the author that the implementation of
> `PartialEq` [is reflexive]https://doc.rust-lang.org/std/cmp/trait.Eq.html. Rust also requires that `PartialEq`
> is symmetric and transitive.

There are three reasonable ways to handle this.

1. [Let Ixa generate equality and hashing for you]#let-ixa-generate-equality-and-hashing.
2. [Implement equality and hashing yourself]#implement-equality-and-hashing-yourself.
3. [Use an alternative floating-point type]#use-an-alternative-floating-point-type.

### Let Ixa generate equality and hashing

Pass `impl_eq_hash = both` as the first optional argument to
`define_property!` or `define_derived_property!`:

```rust
define_property!(
    struct Weight(f64),
    Person,
    impl_eq_hash = both,
    default_const = Weight(0.0)
);
```

This is the shortest option when you want to keep the property as an `f64` and
you are comfortable with Ixa's generated equality and hashing behavior. It is a
good fit for simple measured quantities where model code still wants direct
access to a floating-point value.

You can also use only `impl_eq_hash = Eq` or only `impl_eq_hash = Hash` when
one trait can still be derived but the other needs Ixa's generated
implementation. Floating-point properties usually need `both`.

The generated implementations are reasonably efficient, but they are not optimal.
If performance is absolutely critical, use either of the other options.

### Implement equality and hashing yourself

Pass `impl_eq_hash = neither` when you want `define_property!` to create the
type but you want to provide `PartialEq` / `Eq` and `Hash` yourself:

```rust
use std::hash::{Hash, Hasher};

define_property!(
    struct Weight(f64),
    Person,
    impl_eq_hash = neither,
    default_const = Weight(0.0)
);

impl PartialEq for Weight {
    fn eq(&self, other: &Self) -> bool {
        self.0.to_bits() == other.0.to_bits()
    }
}

impl Eq for Weight {}

impl Hash for Weight {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.to_bits().hash(state);
    }
}
```

Choose this option when you want to use `f32` / `f64` instead of `OrderedFloat` or equivalent alternative float type
and performance is absolutely crucial. It is usually straightforward to implement `PartialEq` and `Hash` for
floating-point types, but you should ensure that your implementations satisfy the property that equal values have the
same hash:

```rust
if a == b {
    assert_eq!(hash(a), hash(b));
}
```

For sensible equality semantics, equality should also be reflexive, symmetric, and transitive as well.

### Use an alternative floating-point type

Use a wrapper type from a crate such as
[`decorum`](https://crates.io/crates/decorum) or
[`ordered-float`](https://crates.io/crates/ordered-float) when you want a type
that gives floating-point values a total ordering and implements the traits Ixa
needs:

```rust
use ordered_float::NotNan;

// A type alias is always a good idea here. It allows you to swap out the underlying 
// type without having to change the rest of your code.
pub type Float = NotNan<f64>;

define_property!(
    struct Weight(Float),
    Person,
    default_const = Weight(Float::new(0.0).unwrap())
);
```

With these types, `define_property!` can just `derive` the `PartialEq`, `Eq`, and `Hash` traits it needs, and
performance of these derived implementations is usually optimal. This option is especially attractive when you want to
restrict your type only to the real numbers, or only to the extended real numbers (infinities but not NaNs), giving you
a numeric type with exactly the mathematical semantics you want.

The trade-off is that model code works with the wrapper type instead of a bare `f64`, and while these libraries do what
they can to alleviate friction, having to convert to and from primitive `f64` values is often unavoidable. This is
really the only downside. The good news is, this conversion is usually only cosmetic: the compiler usually optimizes it
away.

## Canonical Values

Sometimes the value you want model code to use is not the value you want Ixa to store in indexes. The `canonical_value`,
`make_canonical`, and `make_uncanonical` options let you define a standard representation for internal indexing and
querying that is different from the external value you want to expose to model code:

```rust
define_entity!(WeatherStation);

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct DegreesFahrenheit(pub i16);

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct DegreesCelsius(pub i16);

impl_property!(
    DegreesFahrenheit,
    WeatherStation,
    canonical_value = DegreesCelsius,
    make_canonical = |value: DegreesFahrenheit| {
        DegreesCelsius(((value.0 - 32) * 5) / 9)
    },
    make_uncanonical = |value: DegreesCelsius| {
        DegreesFahrenheit((value.0 * 9) / 5 + 32)
    },
    display_impl = |value: &DegreesFahrenheit| format!("{} F", value.0)
);
```

The canonical value must satisfy the same equality and hashing requirements as
other property values because it is used directly by indexes.

> [!INFO] Canonical Values and Multi-Properties
>
> Multi-properties use the canonical value mechanism internally so that two tuples
> with the same component properties but having a different component ordering can
> share the same index. A multi-property's canonical value is the tuple of properties
> in lexicographic order. This is all transparent to model code.

## Multi-Properties

A multi-property is a derived tuple of several properties. Its main purpose is
to support efficient multi-property indexes and queries:

```rust
define_property!(struct Age(u8), Person);
define_property!(
    enum InfectionStatus {
        Susceptible,
        Infectious,
        Recovered,
    },
    Person,
    default_const = InfectionStatus::Susceptible
);

define_multi_property!((Age, InfectionStatus), Person);

context.index_property::<Person, AgeInfectionStatus>();
```

Use the underlying property names in `define_multi_property!`, not type aliases. For a deeper discussion of when to
create multi-property indexes, see [Indexing](indexing.md). Because the components of a multi-property are already
required to be properties, multi-properties usually "just work".

## Troubleshooting

### `f64` does not implement `Eq`

If you define a property containing an `f64` with the default macro form:

```rust
define_property!(struct Weight(f64), Person);
```

you may see an error like:

```text
error[E0277]: the trait bound `f64: std::cmp::Eq` is not satisfied
```

or:

```text
the trait `std::cmp::Eq` is not implemented for `f64`
```

This happens because `define_property!` normally derives `Eq` and `Hash`, but
Rust floating-point types do not implement those traits. See
[Floating Point Types and Implementing `Eq` and `Hash`](#floating-point-types-and-implementing-eq-and-hash)
for the full discussion. In short, choose one of these strategies:

- [Let Ixa generate equality and hashing]#let-ixa-generate-equality-and-hashing
  with `impl_eq_hash = both`.
- [Implement equality and hashing yourself]#implement-equality-and-hashing-yourself
  with `impl_eq_hash = neither`, plus manual `PartialEq`, `Eq`, and `Hash` implementations.
- [Use an alternative floating-point type]#use-an-alternative-floating-point-type,
  such as `ordered_float::NotNan\<f64>` or a type from `decorum`.