Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recreation #756

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 8 additions & 0 deletions src/main/java/App.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.TimeZone;

import org.mitre.synthea.engine.Generator;
import org.mitre.synthea.engine.Module;
Expand All @@ -20,6 +22,7 @@ public class App {
public static void usage() {
System.out.println("Usage: run_synthea [options] [state [city]]");
System.out.println("Options: [-s seed] [-cs clinicianSeed] [-p populationSize]");
System.out.println(" [-r referenceDate as YYYYMMDD]");
System.out.println(" [-g gender] [-a minAge-maxAge]");
System.out.println(" [-o overflowPopulation]");
System.out.println(" [-m moduleFileWildcardList]");
Expand Down Expand Up @@ -69,6 +72,11 @@ public static void main(String[] args) throws Exception {
} else if (currArg.equalsIgnoreCase("-cs")) {
String value = argsQ.poll();
options.clinicianSeed = Long.parseLong(value);
} else if (currArg.equalsIgnoreCase("-r")) {
String value = argsQ.poll();
SimpleDateFormat format = new SimpleDateFormat("YYYYMMDD");
format.setTimeZone(TimeZone.getTimeZone("UTC"));
options.referenceTime = format.parse(value).getTime();
} else if (currArg.equalsIgnoreCase("-p")) {
String value = argsQ.poll();
options.population = Integer.parseInt(value);
Expand Down
50 changes: 24 additions & 26 deletions src/main/java/org/mitre/synthea/editors/GrowthDataErrorsEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.google.gson.Gson;

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

import org.mitre.synthea.engine.HealthRecordEditor;
Expand Down Expand Up @@ -86,34 +85,32 @@ public boolean shouldRun(Person person, HealthRecord record, long time) {
* @param person The Synthea person to check on whether the module should be run
* @param encounters The encounters that took place during the last time step of the simulation
* @param time The current time in the simulation
* @param random Random generator that should be used when randomness is needed
*/
public void process(Person person, List<HealthRecord.Encounter> encounters, long time,
Random random) {
public void process(Person person, List<HealthRecord.Encounter> encounters, long time) {
List<HealthRecord.Encounter> encountersWithWeights =
encountersWithObservationsOfCode(encounters, WEIGHT_LOINC_CODE);
encountersWithWeights.forEach(e -> {
if (random.nextDouble() <= config.weightUnitErrorRate) {
if (person.rand() <= config.weightUnitErrorRate) {
introduceWeightUnitError(e);
recalculateBMI(e);
}
if (random.nextDouble() <= config.weightTransposeErrorRate) {
if (person.rand() <= config.weightTransposeErrorRate) {
introduceTransposeError(e, "weight");
recalculateBMI(e);
}
if (random.nextDouble() <= config.weightSwitchErrorRate) {
if (person.rand() <= config.weightSwitchErrorRate) {
introduceWeightSwitchError(e);
recalculateBMI(e);
}
if (random.nextDouble() <= config.weightExtremeErrorRate) {
if (person.rand() <= config.weightExtremeErrorRate) {
introduceWeightExtremeError(e);
recalculateBMI(e);
}
if (random.nextDouble() <= config.weightDuplicateErrorRate) {
introduceWeightDuplicateError(e, random);
if (person.rand() <= config.weightDuplicateErrorRate) {
introduceWeightDuplicateError(e, person);
recalculateBMI(e);
}
if (random.nextDouble() <= config.weightCarriedForwardErrorRate) {
if (person.rand() <= config.weightCarriedForwardErrorRate) {
introduceWeightCarriedForwardError(e);
recalculateBMI(e);
}
Expand All @@ -122,31 +119,31 @@ public void process(Person person, List<HealthRecord.Encounter> encounters, long
List<HealthRecord.Encounter> encountersWithHeights =
encountersWithObservationsOfCode(encounters, HEIGHT_LOINC_CODE);
encountersWithHeights.forEach(e -> {
if (random.nextDouble() <= config.heightUnitErrorRate) {
if (person.rand() <= config.heightUnitErrorRate) {
introduceHeightUnitError(e);
recalculateBMI(e);
}
if (random.nextDouble() <= config.heightTransposeErrorRate) {
if (person.rand() <= config.heightTransposeErrorRate) {
introduceTransposeError(e, "height");
recalculateBMI(e);
}
if (random.nextDouble() <= config.heightSwitchErrorRate) {
if (person.rand() <= config.heightSwitchErrorRate) {
introduceHeightSwitchError(e);
recalculateBMI(e);
}
if (random.nextDouble() <= config.heightExtremeErrorRate) {
if (person.rand() <= config.heightExtremeErrorRate) {
introduceHeightExtremeError(e);
recalculateBMI(e);
}
if (random.nextDouble() <= config.heightAbsoluteErrorRate) {
introduceHeightAbsoluteError(e, random);
if (person.rand() <= config.heightAbsoluteErrorRate) {
introduceHeightAbsoluteError(e, person);
recalculateBMI(e);
}
if (random.nextDouble() <= config.heightDuplicateErrorRate) {
introduceHeightDuplicateError(e, random);
if (person.rand() <= config.heightDuplicateErrorRate) {
introduceHeightDuplicateError(e, person);
recalculateBMI(e);
}
if (random.nextDouble() <= config.heightCarriedForwardErrorRate) {
if (person.rand() <= config.heightCarriedForwardErrorRate) {
introduceHeightCarriedForwardError(e);
recalculateBMI(e);
}
Expand Down Expand Up @@ -273,10 +270,11 @@ public static void introduceHeightExtremeError(HealthRecord.Encounter encounter)
* shoes before getting measured.
* @param encounter The encounter that contains the observation
*/
public static void introduceHeightAbsoluteError(HealthRecord.Encounter encounter, Random random) {
public static void introduceHeightAbsoluteError(HealthRecord.Encounter encounter,
Person person) {
HealthRecord.Observation htObs = heightObservation(encounter);
double heightValue = (Double) htObs.value;
double additionalAbsolute = random.nextDouble() * 3;
double additionalAbsolute = person.rand() * 3;
htObs.value = heightValue - (3 + additionalAbsolute);
}

Expand All @@ -285,10 +283,10 @@ public static void introduceHeightAbsoluteError(HealthRecord.Encounter encounter
* @param encounter The encounter that contains the observation
*/
public static void introduceWeightDuplicateError(HealthRecord.Encounter encounter,
Random random) {
Person person) {
HealthRecord.Observation wtObs = weightObservation(encounter);
double weightValue = (Double) wtObs.value;
double jitter = random.nextDouble() - 0.5;
double jitter = person.rand() - 0.5;
HealthRecord.Observation newObs =
encounter.addObservation(wtObs.start, wtObs.type, weightValue + jitter, "Body Weight");
newObs.category = "vital-signs";
Expand All @@ -300,10 +298,10 @@ public static void introduceWeightDuplicateError(HealthRecord.Encounter encounte
* @param encounter The encounter that contains the observation
*/
public static void introduceHeightDuplicateError(HealthRecord.Encounter encounter,
Random random) {
Person person) {
HealthRecord.Observation htObs = heightObservation(encounter);
double heightValue = (Double) htObs.value;
double jitter = random.nextDouble() - 0.5;
double jitter = person.rand() - 0.5;
HealthRecord.Observation newObs = encounter.addObservation(htObs.start, htObs.type,
heightValue + jitter, "Body Height");
newObs.category = "vital-signs";
Expand Down
35 changes: 19 additions & 16 deletions src/main/java/org/mitre/synthea/engine/Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class Generator {
private Random random;
public long timestep;
public long stop;
public long referenceTime;
public Map<String, AtomicInteger> stats;
public Location location;
private AtomicInteger totalGeneratedPopulation;
Expand Down Expand Up @@ -108,6 +109,8 @@ public static class GeneratorOptions {
* value of -1 will evolve the population to the current system time.
*/
public int daysToTravelForward = -1;
/** Reference Time when to start Synthea. By default equal to the current system time. */
public long referenceTime = seed;
}

/**
Expand Down Expand Up @@ -197,6 +200,7 @@ private void init() {
this.random = new Random(options.seed);
this.timestep = Long.parseLong(Config.get("generate.timestep"));
this.stop = System.currentTimeMillis();
this.referenceTime = options.referenceTime;

this.location = new Location(options.state, options.city);

Expand Down Expand Up @@ -241,8 +245,10 @@ private void init() {
locationName = options.city + ", " + options.state;
}
System.out.println("Running with options:");
System.out.println(String.format("Population: %d\nSeed: %d\nProvider Seed:%d\nLocation: %s",
options.population, options.seed, options.clinicianSeed, locationName));
System.out.println(String.format(
"Population: %d\nSeed: %d\nProvider Seed:%d\nReference Time: %d\nLocation: %s",
options.population, options.seed, options.clinicianSeed, options.referenceTime,
locationName));
System.out.println(String.format("Min Age: %d\nMax Age: %d",
options.minAge, options.maxAge));
if (options.gender != null) {
Expand Down Expand Up @@ -393,15 +399,15 @@ public Person generatePerson(int index, long personSeed) {

if (isAlive && onlyDeadPatients) {
// rotate the seed so the next attempt gets a consistent but different one
personSeed = new Random(personSeed).nextLong();
personSeed = randomForDemographics.nextLong();
continue;
// skip the other stuff if the patient is alive and we only want dead patients
// note that this skips ahead to the while check and doesn't automatically re-loop
}

if (!isAlive && onlyAlivePatients) {
// rotate the seed so the next attempt gets a consistent but different one
personSeed = new Random(personSeed).nextLong();
personSeed = randomForDemographics.nextLong();
continue;
// skip the other stuff if the patient is dead and we only want alive patients
// note that this skips ahead to the while check and doesn't automatically re-loop
Expand All @@ -412,7 +418,7 @@ public Person generatePerson(int index, long personSeed) {
tryNumber++;
if (!isAlive) {
// rotate the seed so the next attempt gets a consistent but different one
personSeed = new Random(personSeed).nextLong();
personSeed = randomForDemographics.nextLong();

// if we've tried and failed > 10 times to generate someone over age 90
// and the options allow for ages as low as 85
Expand Down Expand Up @@ -470,23 +476,20 @@ public void updatePerson(Person person) {

long time = person.lastUpdated;
while (person.alive(time) && time < stop) {

healthInsuranceModule.process(person, time + timestep);
encounterModule.process(person, time);

Iterator<Module> iter = person.currentModules.iterator();
while (iter.hasNext()) {
Module module = iter.next();
// System.out.format("Processing module %s\n", module.name);

if (module.process(person, time)) {
// System.out.format("Removing module %s\n", module.name);
iter.remove(); // this module has completed/terminated.
}
}
encounterModule.endEncounterModuleEncounters(person, time);
person.lastUpdated = time;
HealthRecordEditors.getInstance().executeAll(
person, person.record, time, timestep, person.random);
HealthRecordEditors.getInstance().executeAll(person, person.record, time, timestep);
time += timestep;
}

Expand Down Expand Up @@ -517,12 +520,12 @@ public Person createPerson(long personSeed, Map<String, Object> demoAttributes)

/**
* Create a set of random demographics.
* @param seed The random seed to use
* @param random The random number generator to use.
* @return demographics
*/
public Map<String, Object> randomDemographics(Random seed) {
Demographics city = location.randomCity(seed);
Map<String, Object> demoAttributes = pickDemographics(seed, city);
public Map<String, Object> randomDemographics(Random random) {
Demographics city = location.randomCity(random);
Map<String, Object> demoAttributes = pickDemographics(random, city);
return demoAttributes;
}

Expand Down Expand Up @@ -650,8 +653,8 @@ private Map<String, Object> pickDemographics(Random random, Demographics city) {
}

private long birthdateFromTargetAge(long targetAge, Random random) {
long earliestBirthdate = stop - TimeUnit.DAYS.toMillis((targetAge + 1) * 365L + 1);
long latestBirthdate = stop - TimeUnit.DAYS.toMillis(targetAge * 365L);
long earliestBirthdate = referenceTime - TimeUnit.DAYS.toMillis((targetAge + 1) * 365L + 1);
long latestBirthdate = referenceTime - TimeUnit.DAYS.toMillis(targetAge * 365L);
return
(long) (earliestBirthdate + ((latestBirthdate - earliestBirthdate) * random.nextDouble()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.mitre.synthea.engine;

import java.util.List;
import java.util.Random;

import org.mitre.synthea.world.agents.Person;
import org.mitre.synthea.world.concepts.HealthRecord;
Expand Down Expand Up @@ -45,7 +44,6 @@ public interface HealthRecordEditor {
* @param person The Synthea person that will have their HealthRecord edited
* @param encounters The encounters that took place during the last time step of the simulation
* @param time The current time in the simulation
* @param random Random generator that should be used when randomness is needed
*/
void process(Person person, List<HealthRecord.Encounter> encounters, long time, Random random);
void process(Person person, List<HealthRecord.Encounter> encounters, long time);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

import org.mitre.synthea.world.agents.Person;
Expand Down Expand Up @@ -45,21 +44,20 @@ public void registerEditor(HealthRecordEditor editor) {
* <p>
* It's unlikely that this method should be called by anything outside of Generator.
* </p>
* @param person The Person to run on
* @param person The Person to run on, and the source of randomness
* @param record The HealthRecord to potentially modify
* @param time The current time in the simulation
* @param step The time step for the simulation
* @param random The source of randomness that modules should use
*/
public void executeAll(Person person, HealthRecord record, long time, long step, Random random) {
public void executeAll(Person person, HealthRecord record, long time, long step) {
if (this.registeredEditors.size() > 0) {
long start = time - step;
List<HealthRecord.Encounter> encountersThisStep = record.encounters.stream()
.filter(e -> e.start >= start)
.collect(Collectors.toList());
this.registeredEditors.forEach(m -> {
if (m.shouldRun(person, record, time)) {
m.process(person, encountersThisStep, time, random);
m.process(person, encountersThisStep, time);
}
});
}
Expand Down
21 changes: 19 additions & 2 deletions src/main/java/org/mitre/synthea/engine/Module.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
* across the population, it is important that States are cloned before they are executed.
* This keeps the "master" copy of the module clean.
*/
public class Module implements Serializable {
public class Module implements Cloneable, Serializable {

private static final Configuration JSON_PATH_CONFIG = Configuration.builder()
.jsonProvider(new GsonJsonProvider())
Expand Down Expand Up @@ -284,6 +284,23 @@ public Module(JsonObject definition, boolean submodule) throws Exception {
}
}

/**
* Clone this module. Never provide the original.
*/
public Module clone() {
Module clone = new Module();
clone.name = this.name;
clone.submodule = this.submodule;
clone.remarks = this.remarks;
clone.states = new ConcurrentHashMap<String, State>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you could move this inside the if block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

if (this.states != null) {
for (String key : this.states.keySet()) {
clone.states.put(key, this.states.get(key).clone());
}
}
return clone;
}

/**
* Process this Module with the given Person at the specified time within the simulation.
*
Expand Down Expand Up @@ -423,7 +440,7 @@ public synchronized Module get() {
if (fault != null) {
throw new RuntimeException(fault);
}
return module;
return module.clone();
dehall marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Loading