Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ log = "0.4"
env_logger = "0.11"
fast-math = "0.1.1"
sha2 = "0.10"
half = { version = "2.4", features = ["serde"] }
112 changes: 64 additions & 48 deletions src/rules/mod.rs

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions src/simulation/journey_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// resistance, and clearance trajectories for a single journey.

use crate::simulation::population::{
Individual, ResistanceMechanism, BACTERIA_LIST, DRUG_SHORT_NAMES, INFECTION_EPS,
load_float, Individual, ResistanceMechanism, BACTERIA_LIST, DRUG_SHORT_NAMES, INFECTION_EPS,
MICROBIOME_MAJORITY_THRESHOLD,
};
use rand::rngs::SmallRng;
Expand Down Expand Up @@ -652,15 +652,16 @@ impl JourneyLogger {
.iter()
.enumerate()
.filter(|(idx, _)| {
let resistance_value = individual.resistances[primary_bacteria_idx][*idx].any_r;
let resistance_value =
load_float(individual.resistances[primary_bacteria_idx][*idx].any_r);
let drug_active = individual.cur_use_drug[*idx]
|| individual.cur_level_drug[*idx] > INFECTION_EPS;
resistance_value > 0.0 || drug_active
})
.map(|(idx, &drug_name)| {
(
drug_name.to_string(),
individual.resistances[primary_bacteria_idx][idx].any_r,
load_float(individual.resistances[primary_bacteria_idx][idx].any_r),
)
})
.collect();
Expand All @@ -684,7 +685,7 @@ impl JourneyLogger {
.map(|(idx, &drug_name)| {
(
drug_name.to_string(),
individual.resistances[primary_bacteria_idx][idx].activity_r,
load_float(individual.resistances[primary_bacteria_idx][idx].activity_r),
)
})
.collect();
Expand Down Expand Up @@ -722,7 +723,8 @@ impl JourneyLogger {

let mut resistances_microbiome_r: Vec<(String, f64)> = Vec::new();
for (idx, &drug_name) in DRUG_SHORT_NAMES.iter().enumerate() {
let microbiome_value = individual.resistances[primary_bacteria_idx][idx].microbiome_r;
let microbiome_value =
load_float(individual.resistances[primary_bacteria_idx][idx].microbiome_r);
if primary_microbiome_present || microbiome_value > 0.0 {
resistances_microbiome_r.push((drug_name.to_string(), microbiome_value));
}
Expand All @@ -741,7 +743,7 @@ impl JourneyLogger {
let mut majority_max = 0.0;

for resistance in resistances {
let value = resistance.microbiome_r;
let value = load_float(resistance.microbiome_r);
if value >= MICROBIOME_MAJORITY_THRESHOLD {
if value > majority_max {
majority_max = value;
Expand Down Expand Up @@ -1226,7 +1228,8 @@ impl JourneyLogger {
.find(|(name, _)| name == drug_name)
.map(|(_, value)| *value)
.unwrap_or(0.0);
let current_any = individual.resistances[primary_bacteria_idx][drug_idx].any_r;
let current_any =
load_float(individual.resistances[primary_bacteria_idx][drug_idx].any_r);

let any_increased = current_any > prev_any + RESISTANCE_EPSILON;

Expand Down
89 changes: 79 additions & 10 deletions src/simulation/population.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,74 @@ use std::fmt;
// Minimum infection/drug level threshold; values below this are treated as cleared to avoid floating-point noise.
pub const INFECTION_EPS: f64 = 0.001;

pub trait StoredFloatRepr: Copy {
fn store(value: f64) -> Self;
fn load(self) -> f64;
}

impl StoredFloatRepr for f64 {
#[inline]
fn store(value: f64) -> Self {
value
}

#[inline]
fn load(self) -> f64 {
self
}
}

impl StoredFloatRepr for half::f16 {
#[inline]
fn store(value: f64) -> Self {
half::f16::from_f64(value)
}

#[inline]
fn load(self) -> f64 {
self.to_f64()
}
}

#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct BoundedResistanceU16(u16);

impl StoredFloatRepr for BoundedResistanceU16 {
#[inline]
fn store(value: f64) -> Self {
let bounded = if value.is_finite() {
value.clamp(0.0, 1.0)
} else {
0.0
};
BoundedResistanceU16((bounded * u16::MAX as f64).round() as u16)
}

#[inline]
fn load(self) -> f64 {
self.0 as f64 / u16::MAX as f64
}
}

impl fmt::Display for BoundedResistanceU16 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.load().fmt(f)
}
}

pub type StoredBoundedResistanceFloat = BoundedResistanceU16;
pub type StoredActivityResistanceFloat = half::f16;

#[inline]
pub fn store_float<T: StoredFloatRepr>(value: f64) -> T {
T::store(value)
}

#[inline]
pub fn load_float<T: StoredFloatRepr>(value: T) -> f64 {
value.load()
}

/// Specific resistance mechanisms that can be present in bacteria
/// These mechanism bitmasks are the source of infection, majority-strain, and
/// microbiome resistance projections.
Expand Down Expand Up @@ -1254,20 +1322,20 @@ impl Region {
pub struct Resistance {
/// Resistance level in colonizing (carriage) bacteria. Range: 0.0-1.0.
/// Derived from mechanism_microbiome via the multiplicative product formula.
pub microbiome_r: f64,
pub microbiome_r: StoredBoundedResistanceFloat,

/// Resistance as would be detected by laboratory testing. Range: 0.0-1.0.
/// May differ from actual resistance due to test sensitivity/specificity.
pub test_r: f64,
pub test_r: StoredBoundedResistanceFloat,

/// Effective resistance for drug activity calculations. Range: 0.0-1.0.
/// Takes into account mechanism-specific effects on drug binding/activity.
pub activity_r: f64,
pub activity_r: StoredActivityResistanceFloat,

/// Primary resistance level - resistance present in ANY infected bacteria. Range: 0.0-1.0.
/// Derived from mechanism_any via the multiplicative product formula:
/// any_r = 1 - product(1 - enhancement_multiplier) over all present mechanisms.
pub any_r: f64,
pub any_r: StoredBoundedResistanceFloat,
}

pub const MICROBIOME_RESISTANCE_LEVEL_COUNT: usize = 4;
Expand Down Expand Up @@ -1787,10 +1855,10 @@ impl Individual {
let mut drug_resistances = Vec::with_capacity(num_drugs);
for _ in 0..num_drugs {
drug_resistances.push(Resistance {
microbiome_r: 0.0,
test_r: 0.0,
activity_r: 0.0,
any_r: 0.0,
microbiome_r: store_float(0.0),
test_r: store_float(0.0),
activity_r: store_float(0.0),
any_r: store_float(0.0),
});
}
resistances.push(drug_resistances);
Expand Down Expand Up @@ -1935,9 +2003,10 @@ impl Individual {

if let Some(resistances) = self.resistances.get(bacteria_idx) {
for resistance in resistances {
if resistance.microbiome_r > 0.0 {
let microbiome_r = load_float(resistance.microbiome_r);
if microbiome_r > 0.0 {
has_resistance = true;
if resistance.microbiome_r >= threshold {
if microbiome_r >= threshold {
has_majority = true;
break;
}
Expand Down
Loading
Loading