diff --git a/src/main/java/org/mitre/synthea/engine/ExpressedConditionRecord.java b/src/main/java/org/mitre/synthea/engine/ExpressedConditionRecord.java index ee80763a3b..0fa2a76f54 100644 --- a/src/main/java/org/mitre/synthea/engine/ExpressedConditionRecord.java +++ b/src/main/java/org/mitre/synthea/engine/ExpressedConditionRecord.java @@ -3,10 +3,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import org.mitre.synthea.engine.ExpressedSymptom.SymptomInfo; import org.mitre.synthea.engine.ExpressedSymptom.SymptomSource; @@ -140,8 +140,8 @@ public class ModuleConditions implements Cloneable, Serializable { */ public ModuleConditions(String source) { this.source = source; - onsetConditions = new ConcurrentHashMap(); - state2conditionMapping = new ConcurrentHashMap(); + onsetConditions = new HashMap(); + state2conditionMapping = new HashMap(); } /** @@ -231,7 +231,7 @@ public ConditionWithSymptoms(String name, Long onsetTime, Long endTime) { this.conditionName = name; this.onsetTime = onsetTime; this.endTime = endTime; - this.symptoms = new ConcurrentHashMap>(); + this.symptoms = new HashMap>(); } /** @@ -295,7 +295,7 @@ public Map> getSymptoms() { public ExpressedConditionRecord(Person person) { this.person = person; - sources = new ConcurrentHashMap(); + sources = new HashMap(); } /** @@ -384,7 +384,7 @@ public void onConditionEnd(String module, String condition, long time) { public Map> getConditionSymptoms() { Map symptoms = person.getExpressedSymptoms(); Map> result; - result = new ConcurrentHashMap>(); + result = new HashMap>(); for (String module : sources.keySet()) { ModuleConditions moduleConditions = sources.get(module); for (String condition : moduleConditions.getOnsetConditions().keySet()) { diff --git a/src/main/java/org/mitre/synthea/engine/ExpressedSymptom.java b/src/main/java/org/mitre/synthea/engine/ExpressedSymptom.java index 4ada789157..24cd353e2f 100644 --- a/src/main/java/org/mitre/synthea/engine/ExpressedSymptom.java +++ b/src/main/java/org/mitre/synthea/engine/ExpressedSymptom.java @@ -1,8 +1,8 @@ package org.mitre.synthea.engine; import java.io.Serializable; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; public class ExpressedSymptom implements Cloneable, Serializable { @@ -64,7 +64,7 @@ public class SymptomSource implements Cloneable, Serializable { */ public SymptomSource(String source) { this.source = source; - timeInfos = new ConcurrentHashMap(); + timeInfos = new HashMap(); resolved = false; lastUpdateTime = null; } @@ -114,8 +114,9 @@ public void addInfo(String cause, long time, int value, Boolean addressed) { * Get the current value of the symptom. */ public Integer getCurrentValue() { - if (timeInfos.containsKey(lastUpdateTime)) { - return timeInfos.get(lastUpdateTime).getValue(); + final SymptomInfo timeInfo = timeInfos.get(lastUpdateTime); + if (timeInfo != null) { + return timeInfo.getValue(); } return null; } @@ -134,7 +135,7 @@ public Map getTimeInfos() { public ExpressedSymptom(String name) { this.name = name; - sources = new ConcurrentHashMap(); + sources = new HashMap(); } /** @@ -165,12 +166,13 @@ public void onSet(String module, String cause, long time, int value, Boolean add */ public int getSymptom() { int max = 0; - for (String module : sources.keySet()) { - Integer value = sources.get(module).getCurrentValue(); - Boolean isResolved = sources.get(module).isResolved(); - if (value != null && value.intValue() > max && !isResolved) { - max = value.intValue(); - } + for (SymptomSource module : sources.values()) { + if(!module.isResolved()) { + Integer value = module.getCurrentValue(); + if (value != null && value.intValue() > max) { + max = value.intValue(); + } + } } return max; } diff --git a/src/main/java/org/mitre/synthea/engine/Logic.java b/src/main/java/org/mitre/synthea/engine/Logic.java index 324e54b39b..d6aab50113 100644 --- a/src/main/java/org/mitre/synthea/engine/Logic.java +++ b/src/main/java/org/mitre/synthea/engine/Logic.java @@ -300,7 +300,12 @@ private abstract static class GroupedCondition extends Logic { public static class And extends GroupedCondition { @Override public boolean test(Person person, long time) { - return conditions.stream().allMatch(c -> c.test(person, time)); + for(Logic condition: conditions) { + if (!condition.test(person, time)) + return false; + } + + return true; } } diff --git a/src/main/java/org/mitre/synthea/engine/Module.java b/src/main/java/org/mitre/synthea/engine/Module.java index 443a297a35..c09a7df32f 100644 --- a/src/main/java/org/mitre/synthea/engine/Module.java +++ b/src/main/java/org/mitre/synthea/engine/Module.java @@ -34,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.mitre.synthea.helpers.Config; import org.mitre.synthea.helpers.Utilities; @@ -301,7 +302,7 @@ public Module(JsonObject definition, boolean submodule) throws Exception { states.put(entry.getKey(), state); } } - + /** * Clone this module. Never provide the original. */ @@ -311,10 +312,12 @@ public Module clone() { clone.submodule = this.submodule; clone.remarks = this.remarks; if (this.states != null) { - clone.states = new ConcurrentHashMap(); - for (String key : this.states.keySet()) { - clone.states.put(key, this.states.get(key).clone()); - } + clone.states = this.states.entrySet().stream() + .collect(Collectors.toMap( + e -> e.getKey(), + e -> e.getValue().clone(), + (oldValue, newValue) -> newValue, + ConcurrentHashMap::new)); } return clone; } @@ -423,11 +426,13 @@ public Collection getStateNames() { */ public static class ModuleSupplier implements Supplier { + private final Object getLock = new Object(); + public final boolean core; public final boolean submodule; public final String path; - private boolean loaded; + private volatile boolean loaded; private Callable loader; private Module module; private Throwable fault; @@ -461,17 +466,21 @@ public ModuleSupplier(Module module) { } @Override - public synchronized Module get() { + public Module get() { if (!loaded) { - try { - module = loader.call(); - } catch (Throwable e) { - e.printStackTrace(); - fault = e; - } finally { - loaded = true; - loader = null; - } + synchronized(getLock) { + if (!loaded) { + try { + module = loader.call(); + } catch (Throwable e) { + e.printStackTrace(); + fault = e; + } finally { + loaded = true; + loader = null; + } + } + } } if (fault != null) { throw new RuntimeException(fault); diff --git a/src/main/java/org/mitre/synthea/engine/State.java b/src/main/java/org/mitre/synthea/engine/State.java index 55e454ebd6..99fa909e18 100644 --- a/src/main/java/org/mitre/synthea/engine/State.java +++ b/src/main/java/org/mitre/synthea/engine/State.java @@ -14,6 +14,7 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.math.ode.DerivativeException; @@ -1724,10 +1725,9 @@ public ObservationGroup clone() { // we need to ensure we deep clone the list // (otherwise as this gets passed around, the same objects are used for different patients // which causes weird and unexpected results) - List cloneObs = new ArrayList<>(observations.size()); - for (Observation o : observations) { - cloneObs.add(o.clone()); - } + List cloneObs = observations.stream() + .map(Observation::clone) + .collect(Collectors.toCollection(ArrayList::new)); clone.observations = cloneObs; return clone; diff --git a/src/main/java/org/mitre/synthea/helpers/Utilities.java b/src/main/java/org/mitre/synthea/helpers/Utilities.java index dfb1d849f9..0ed6501021 100644 --- a/src/main/java/org/mitre/synthea/helpers/Utilities.java +++ b/src/main/java/org/mitre/synthea/helpers/Utilities.java @@ -23,6 +23,16 @@ import org.mitre.synthea.world.concepts.HealthRecord.Code; public class Utilities { + + private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC"); + + private static final ThreadLocal threadLocalCalendar = new ThreadLocal(){ + @Override + protected Calendar initialValue() { + return Calendar.getInstance(UTC_TIME_ZONE); + } + }; + /** * Convert a quantity of time in a specified units into milliseconds. * @@ -94,7 +104,7 @@ public static long convertCalendarYearsToTime(int years) { * Get the year of a Unix timestamp. */ public static int getYear(long time) { - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Calendar calendar = threadLocalCalendar.get(); calendar.setTimeInMillis(time); return calendar.get(Calendar.YEAR); } @@ -103,7 +113,7 @@ public static int getYear(long time) { * Get the month of a Unix timestamp. */ public static int getMonth(long time) { - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Calendar calendar = threadLocalCalendar.get(); calendar.setTimeInMillis(time); return calendar.get(Calendar.MONTH) + 1; } @@ -133,11 +143,20 @@ public static Object primitive(JsonPrimitive p) { return retVal; } + private static double timestepCache = Double.NaN; + + private static double getTimestep() { + if(timestepCache == Double.NaN) + timestepCache = Double.parseDouble(Config.get("generate.timestep")); + + return timestepCache; + } + /** * Calculates 1 - (1-risk)^(currTimeStepInMS/originalPeriodInMS). */ public static double convertRiskToTimestep(double risk, double originalPeriodInMS) { - double currTimeStepInMS = Double.parseDouble(Config.get("generate.timestep")); + double currTimeStepInMS = getTimestep(); return convertRiskToTimestep(risk, originalPeriodInMS, currTimeStepInMS); } diff --git a/src/main/java/org/mitre/synthea/modules/LifecycleModule.java b/src/main/java/org/mitre/synthea/modules/LifecycleModule.java index 665eae69cd..4e46877517 100644 --- a/src/main/java/org/mitre/synthea/modules/LifecycleModule.java +++ b/src/main/java/org/mitre/synthea/modules/LifecycleModule.java @@ -602,6 +602,15 @@ private static Map createDrugImpactsMap() { private static final double[] RESPIRATION_RATE_NORMAL = BiometricsConfig.doubles("respiratory.respiration_rate.normal"); + private static long timestepCache = Long.MIN_VALUE; + + private static long getTimestep() { + if(timestepCache == Long.MIN_VALUE) + timestepCache = Long.parseLong(Config.get("generate.timestep")); + + return timestepCache; + } + /** * Calculate this person's vital signs, * based on their conditions, medications, body composition, etc. @@ -743,7 +752,7 @@ private static void calculateVitalSigns(Person person, long time) { person.setVitalSign(VitalSign.CARBON_DIOXIDE, person.rand(CO2_RANGE)); person.setVitalSign(VitalSign.SODIUM, person.rand(SODIUM_RANGE)); - long timestep = Long.parseLong(Config.get("generate.timestep")); + long timestep = getTimestep(); double heartStart = person.rand(HEART_RATE_NORMAL); double heartEnd = person.rand(HEART_RATE_NORMAL); person.setVitalSign(VitalSign.HEART_RATE, diff --git a/src/main/java/org/mitre/synthea/world/agents/Person.java b/src/main/java/org/mitre/synthea/world/agents/Person.java index 60aba3e64e..562b31f165 100644 --- a/src/main/java/org/mitre/synthea/world/agents/Person.java +++ b/src/main/java/org/mitre/synthea/world/agents/Person.java @@ -2,8 +2,6 @@ import java.awt.geom.Point2D; import java.io.Serializable; -import java.math.BigDecimal; -import java.math.RoundingMode; import java.time.Instant; import java.time.LocalDate; import java.time.Period; @@ -16,7 +14,6 @@ import java.util.Random; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import org.mitre.synthea.engine.ExpressedConditionRecord; import org.mitre.synthea.engine.ExpressedSymptom; @@ -150,17 +147,17 @@ public class Person implements Serializable, RandomNumberGenerator, QuadTreeElem public Person(long seed) { this.seed = seed; random = new Random(seed); - attributes = new ConcurrentHashMap(); - vitalSigns = new ConcurrentHashMap(); - symptoms = new ConcurrentHashMap(); + attributes = new HashMap(); + vitalSigns = new HashMap(); + symptoms = new HashMap(); /* initialized the onsetConditions field */ onsetConditionRecord = new ExpressedConditionRecord(this); /* Chronic Medications which will be renewed at each Wellness Encounter */ - chronicMedications = new ConcurrentHashMap(); + chronicMedications = new HashMap(); hasMultipleRecords = Config.getAsBoolean("exporter.split_records", false); if (hasMultipleRecords) { - records = new ConcurrentHashMap(); + records = new HashMap(); } defaultRecord = new HealthRecord(this); lossOfCareEnabled = @@ -204,11 +201,29 @@ public int randInt(int bound) { return random.nextInt(bound); } + private boolean haveNextNextGaussian = false; + private double nextNextGaussian; + /** * Returns a double from a normal distribution. */ public double randGaussian() { - return random.nextGaussian(); + // See Knuth, ACP, Section 3.4.1 Algorithm C. + if (haveNextNextGaussian) { + haveNextNextGaussian = false; + return nextNextGaussian; + } else { + double v1, v2, s; + do { + v1 = 2 * random.nextDouble() - 1; // between -1 and 1 + v2 = 2 * random.nextDouble() - 1; // between -1 and 1 + s = v1 * v1 + v2 * v2; + } while (s >= 1 || s == 0); + double multiplier = Math.sqrt(-2 * Math.log(s)/s); + nextNextGaussian = v2 * multiplier; + haveNextNextGaussian = true; + return v1 * multiplier; + } } /** @@ -228,16 +243,23 @@ public UUID randUUID() { /** * Returns a person's age in Period form. */ - public Period age(long time) { - Period age = Period.ZERO; + private Period ageCache = null; + private long ageCacheTime = Long.MIN_VALUE; - if (attributes.containsKey(BIRTHDATE)) { - LocalDate now = Instant.ofEpochMilli(time).atZone(timeZone).toLocalDate(); - LocalDate birthdate = Instant.ofEpochMilli((long) attributes.get(BIRTHDATE)) - .atZone(timeZone).toLocalDate(); - age = Period.between(birthdate, now); + public Period age(long time) { + if(ageCacheTime != time) { + ageCacheTime = time; + ageCache = Period.ZERO; + + if (attributes.containsKey(BIRTHDATE)) { + LocalDate now = Instant.ofEpochMilli(time).atZone(timeZone).toLocalDate(); + LocalDate birthdate = Instant.ofEpochMilli((long) attributes.get(BIRTHDATE)) + .atZone(timeZone).toLocalDate(); + ageCache = Period.between(birthdate, now); + } } - return age; + + return ageCache; } /** @@ -324,12 +346,26 @@ public T getAttribute(String key, Class type, T defaultValue) { /** * Returns whether a person is alive at the given time. */ - public boolean alive(long time) { - boolean born = attributes.containsKey(Person.BIRTHDATE); - Long died = (Long) attributes.get(Person.DEATHDATE); - return (born && (died == null || died > time)); - } - + private Boolean aliveCacheValue = null; + private long aliveCacheTime = Long.MIN_VALUE; + + public boolean alive(long time) { + if (aliveCacheTime != time || null == aliveCacheValue) { + aliveCacheTime = time; + aliveCacheValue = calcAlive(time); + } + return aliveCacheValue.booleanValue(); + } + + private boolean calcAlive(long time) { + boolean born = attributes.containsKey(Person.BIRTHDATE); + if (born) { + Long died = (Long) attributes.get(Person.DEATHDATE); + return (died == null || died > time); + } + return false; + } + /** * Get the expressed symptoms. */ @@ -389,8 +425,8 @@ public Long getSymptomLastUpdatedTime(String module, String symptom) { */ public int getSymptom(String type) { int max = 0; - if (symptoms.containsKey(type)) { - ExpressedSymptom expressedSymptom = symptoms.get(type); + ExpressedSymptom expressedSymptom = symptoms.get(type); + if(null != expressedSymptom){ max = expressedSymptom.getSymptom(); } return max; @@ -459,15 +495,14 @@ public Double getVitalSign(VitalSign vitalSign, long time) { default: decimalPlaces = 2; } - Double retVal = value; - try { - retVal = BigDecimal.valueOf(value) - .setScale(decimalPlaces, RoundingMode.HALF_UP) - .doubleValue(); + + try { + final double factor = Math.pow(10, decimalPlaces); + value = Math.floor(((value * factor * 10) + 5) / 10) / factor; } catch (NumberFormatException e) { // Ignore, value was NaN or infinity. } - return retVal; + return value; } public void setVitalSign(VitalSign vitalSign, ValueGenerator valueGenerator) { diff --git a/src/main/java/org/mitre/synthea/world/concepts/GrowthChart.java b/src/main/java/org/mitre/synthea/world/concepts/GrowthChart.java index 7db70d96d3..0ddeee3f96 100644 --- a/src/main/java/org/mitre/synthea/world/concepts/GrowthChart.java +++ b/src/main/java/org/mitre/synthea/world/concepts/GrowthChart.java @@ -121,17 +121,15 @@ public static double calculateZScore(double percentile) { return -1 * Math.sqrt(2) * Erf.erfcInv(2 * percentile); } + private static final NormalDistribution NORMAL_DISTRIBUTION = new NormalDistribution(); + /** * Convert a z-score into a percentile. * @param zscore The ZScore to find the percentile for * @return percentile - 0.0 - 1.0 */ public static double zscoreToPercentile(double zscore) { - double percentile = 0; - - NormalDistribution dist = new NormalDistribution(); - percentile = dist.cumulativeProbability(zscore); - return percentile; + return NORMAL_DISTRIBUTION.cumulativeProbability(zscore); } /** diff --git a/src/main/java/org/mitre/synthea/world/concepts/HealthRecord.java b/src/main/java/org/mitre/synthea/world/concepts/HealthRecord.java index 0a33d0a99c..146083f665 100644 --- a/src/main/java/org/mitre/synthea/world/concepts/HealthRecord.java +++ b/src/main/java/org/mitre/synthea/world/concepts/HealthRecord.java @@ -1126,7 +1126,8 @@ private void chronicMedicationEnd(String type) { } public boolean medicationActive(String type) { - return present.containsKey(type) && ((Medication) present.get(type)).stop == 0L; + final Entry medication = present.get(type); + return medication != null && ((Medication) medication).stop == 0L; } /**