Skip to content

Commit 89aa966

Browse files
committed
Merge branch 'recreation'
2 parents 4e8efd7 + f99ed3b commit 89aa966

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+861
-304
lines changed

src/main/java/App.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import java.io.File;
22
import java.io.FileNotFoundException;
33
import java.io.IOException;
4+
import java.text.SimpleDateFormat;
45
import java.util.Arrays;
56
import java.util.LinkedList;
67
import java.util.Queue;
8+
import java.util.TimeZone;
79

810
import org.mitre.synthea.engine.Generator;
911
import org.mitre.synthea.engine.Module;
@@ -20,6 +22,7 @@ public class App {
2022
public static void usage() {
2123
System.out.println("Usage: run_synthea [options] [state [city]]");
2224
System.out.println("Options: [-s seed] [-cs clinicianSeed] [-p populationSize]");
25+
System.out.println(" [-r referenceDate as YYYYMMDD]");
2326
System.out.println(" [-g gender] [-a minAge-maxAge]");
2427
System.out.println(" [-o overflowPopulation]");
2528
System.out.println(" [-m moduleFileWildcardList]");
@@ -69,6 +72,11 @@ public static void main(String[] args) throws Exception {
6972
} else if (currArg.equalsIgnoreCase("-cs")) {
7073
String value = argsQ.poll();
7174
options.clinicianSeed = Long.parseLong(value);
75+
} else if (currArg.equalsIgnoreCase("-r")) {
76+
String value = argsQ.poll();
77+
SimpleDateFormat format = new SimpleDateFormat("YYYYMMDD");
78+
format.setTimeZone(TimeZone.getTimeZone("UTC"));
79+
options.referenceTime = format.parse(value).getTime();
7280
} else if (currArg.equalsIgnoreCase("-p")) {
7381
String value = argsQ.poll();
7482
options.population = Integer.parseInt(value);

src/main/java/org/mitre/synthea/editors/GrowthDataErrorsEditor.java

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import com.google.gson.Gson;
44

55
import java.util.List;
6-
import java.util.Random;
76
import java.util.stream.Collectors;
87

98
import org.mitre.synthea.engine.HealthRecordEditor;
9+
import org.mitre.synthea.helpers.RandomNumberGenerator;
1010
import org.mitre.synthea.helpers.Utilities;
1111
import org.mitre.synthea.world.agents.Person;
1212
import org.mitre.synthea.world.concepts.HealthRecord;
@@ -86,34 +86,32 @@ public boolean shouldRun(Person person, HealthRecord record, long time) {
8686
* @param person The Synthea person to check on whether the module should be run
8787
* @param encounters The encounters that took place during the last time step of the simulation
8888
* @param time The current time in the simulation
89-
* @param random Random generator that should be used when randomness is needed
9089
*/
91-
public void process(Person person, List<HealthRecord.Encounter> encounters, long time,
92-
Random random) {
90+
public void process(Person person, List<HealthRecord.Encounter> encounters, long time) {
9391
List<HealthRecord.Encounter> encountersWithWeights =
9492
encountersWithObservationsOfCode(encounters, WEIGHT_LOINC_CODE);
9593
encountersWithWeights.forEach(e -> {
96-
if (random.nextDouble() <= config.weightUnitErrorRate) {
94+
if (person.rand() <= config.weightUnitErrorRate) {
9795
introduceWeightUnitError(e);
9896
recalculateBMI(e);
9997
}
100-
if (random.nextDouble() <= config.weightTransposeErrorRate) {
98+
if (person.rand() <= config.weightTransposeErrorRate) {
10199
introduceTransposeError(e, "weight");
102100
recalculateBMI(e);
103101
}
104-
if (random.nextDouble() <= config.weightSwitchErrorRate) {
102+
if (person.rand() <= config.weightSwitchErrorRate) {
105103
introduceWeightSwitchError(e);
106104
recalculateBMI(e);
107105
}
108-
if (random.nextDouble() <= config.weightExtremeErrorRate) {
106+
if (person.rand() <= config.weightExtremeErrorRate) {
109107
introduceWeightExtremeError(e);
110108
recalculateBMI(e);
111109
}
112-
if (random.nextDouble() <= config.weightDuplicateErrorRate) {
113-
introduceWeightDuplicateError(e, random);
110+
if (person.rand() <= config.weightDuplicateErrorRate) {
111+
introduceWeightDuplicateError(e, person);
114112
recalculateBMI(e);
115113
}
116-
if (random.nextDouble() <= config.weightCarriedForwardErrorRate) {
114+
if (person.rand() <= config.weightCarriedForwardErrorRate) {
117115
introduceWeightCarriedForwardError(e);
118116
recalculateBMI(e);
119117
}
@@ -122,31 +120,31 @@ public void process(Person person, List<HealthRecord.Encounter> encounters, long
122120
List<HealthRecord.Encounter> encountersWithHeights =
123121
encountersWithObservationsOfCode(encounters, HEIGHT_LOINC_CODE);
124122
encountersWithHeights.forEach(e -> {
125-
if (random.nextDouble() <= config.heightUnitErrorRate) {
123+
if (person.rand() <= config.heightUnitErrorRate) {
126124
introduceHeightUnitError(e);
127125
recalculateBMI(e);
128126
}
129-
if (random.nextDouble() <= config.heightTransposeErrorRate) {
127+
if (person.rand() <= config.heightTransposeErrorRate) {
130128
introduceTransposeError(e, "height");
131129
recalculateBMI(e);
132130
}
133-
if (random.nextDouble() <= config.heightSwitchErrorRate) {
131+
if (person.rand() <= config.heightSwitchErrorRate) {
134132
introduceHeightSwitchError(e);
135133
recalculateBMI(e);
136134
}
137-
if (random.nextDouble() <= config.heightExtremeErrorRate) {
135+
if (person.rand() <= config.heightExtremeErrorRate) {
138136
introduceHeightExtremeError(e);
139137
recalculateBMI(e);
140138
}
141-
if (random.nextDouble() <= config.heightAbsoluteErrorRate) {
142-
introduceHeightAbsoluteError(e, random);
139+
if (person.rand() <= config.heightAbsoluteErrorRate) {
140+
introduceHeightAbsoluteError(e, person);
143141
recalculateBMI(e);
144142
}
145-
if (random.nextDouble() <= config.heightDuplicateErrorRate) {
146-
introduceHeightDuplicateError(e, random);
143+
if (person.rand() <= config.heightDuplicateErrorRate) {
144+
introduceHeightDuplicateError(e, person);
147145
recalculateBMI(e);
148146
}
149-
if (random.nextDouble() <= config.heightCarriedForwardErrorRate) {
147+
if (person.rand() <= config.heightCarriedForwardErrorRate) {
150148
introduceHeightCarriedForwardError(e);
151149
recalculateBMI(e);
152150
}
@@ -273,10 +271,11 @@ public static void introduceHeightExtremeError(HealthRecord.Encounter encounter)
273271
* shoes before getting measured.
274272
* @param encounter The encounter that contains the observation
275273
*/
276-
public static void introduceHeightAbsoluteError(HealthRecord.Encounter encounter, Random random) {
274+
public static void introduceHeightAbsoluteError(HealthRecord.Encounter encounter,
275+
RandomNumberGenerator random) {
277276
HealthRecord.Observation htObs = heightObservation(encounter);
278277
double heightValue = (Double) htObs.value;
279-
double additionalAbsolute = random.nextDouble() * 3;
278+
double additionalAbsolute = random.rand() * 3;
280279
htObs.value = heightValue - (3 + additionalAbsolute);
281280
}
282281

@@ -285,10 +284,10 @@ public static void introduceHeightAbsoluteError(HealthRecord.Encounter encounter
285284
* @param encounter The encounter that contains the observation
286285
*/
287286
public static void introduceWeightDuplicateError(HealthRecord.Encounter encounter,
288-
Random random) {
287+
RandomNumberGenerator random) {
289288
HealthRecord.Observation wtObs = weightObservation(encounter);
290289
double weightValue = (Double) wtObs.value;
291-
double jitter = random.nextDouble() - 0.5;
290+
double jitter = random.rand() - 0.5;
292291
HealthRecord.Observation newObs =
293292
encounter.addObservation(wtObs.start, wtObs.type, weightValue + jitter, "Body Weight");
294293
newObs.category = "vital-signs";
@@ -300,10 +299,10 @@ public static void introduceWeightDuplicateError(HealthRecord.Encounter encounte
300299
* @param encounter The encounter that contains the observation
301300
*/
302301
public static void introduceHeightDuplicateError(HealthRecord.Encounter encounter,
303-
Random random) {
302+
RandomNumberGenerator random) {
304303
HealthRecord.Observation htObs = heightObservation(encounter);
305304
double heightValue = (Double) htObs.value;
306-
double jitter = random.nextDouble() - 0.5;
305+
double jitter = random.rand() - 0.5;
307306
HealthRecord.Observation newObs = encounter.addObservation(htObs.start, htObs.type,
308307
heightValue + jitter, "Body Height");
309308
newObs.category = "vital-signs";

src/main/java/org/mitre/synthea/engine/Generator.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class Generator {
5252
private Random random;
5353
public long timestep;
5454
public long stop;
55+
public long referenceTime;
5556
public Map<String, AtomicInteger> stats;
5657
public Location location;
5758
private AtomicInteger totalGeneratedPopulation;
@@ -108,6 +109,8 @@ public static class GeneratorOptions {
108109
* value of -1 will evolve the population to the current system time.
109110
*/
110111
public int daysToTravelForward = -1;
112+
/** Reference Time when to start Synthea. By default equal to the current system time. */
113+
public long referenceTime = seed;
111114
}
112115

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

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

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

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

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

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

471477
long time = person.lastUpdated;
472478
while (person.alive(time) && time < stop) {
473-
474479
healthInsuranceModule.process(person, time + timestep);
475480
encounterModule.process(person, time);
476481

477482
Iterator<Module> iter = person.currentModules.iterator();
478483
while (iter.hasNext()) {
479484
Module module = iter.next();
480-
// System.out.format("Processing module %s\n", module.name);
485+
481486
if (module.process(person, time)) {
482-
// System.out.format("Removing module %s\n", module.name);
483487
iter.remove(); // this module has completed/terminated.
484488
}
485489
}
486490
encounterModule.endEncounterModuleEncounters(person, time);
487491
person.lastUpdated = time;
488-
HealthRecordEditors.getInstance().executeAll(
489-
person, person.record, time, timestep, person.random);
492+
HealthRecordEditors.getInstance().executeAll(person, person.record, time, timestep);
490493
time += timestep;
491494
}
492495

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

518521
/**
519522
* Create a set of random demographics.
520-
* @param seed The random seed to use
523+
* @param random The random number generator to use.
521524
* @return demographics
522525
*/
523-
public Map<String, Object> randomDemographics(Random seed) {
524-
Demographics city = location.randomCity(seed);
525-
Map<String, Object> demoAttributes = pickDemographics(seed, city);
526+
public Map<String, Object> randomDemographics(Random random) {
527+
Demographics city = location.randomCity(random);
528+
Map<String, Object> demoAttributes = pickDemographics(random, city);
526529
return demoAttributes;
527530
}
528531

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

652655
private long birthdateFromTargetAge(long targetAge, Random random) {
653-
long earliestBirthdate = stop - TimeUnit.DAYS.toMillis((targetAge + 1) * 365L + 1);
654-
long latestBirthdate = stop - TimeUnit.DAYS.toMillis(targetAge * 365L);
656+
long earliestBirthdate = referenceTime - TimeUnit.DAYS.toMillis((targetAge + 1) * 365L + 1);
657+
long latestBirthdate = referenceTime - TimeUnit.DAYS.toMillis(targetAge * 365L);
655658
return
656659
(long) (earliestBirthdate + ((latestBirthdate - earliestBirthdate) * random.nextDouble()));
657660
}

src/main/java/org/mitre/synthea/engine/HealthRecordEditor.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.mitre.synthea.engine;
22

33
import java.util.List;
4-
import java.util.Random;
54

65
import org.mitre.synthea.world.agents.Person;
76
import org.mitre.synthea.world.concepts.HealthRecord;
@@ -45,7 +44,6 @@ public interface HealthRecordEditor {
4544
* @param person The Synthea person that will have their HealthRecord edited
4645
* @param encounters The encounters that took place during the last time step of the simulation
4746
* @param time The current time in the simulation
48-
* @param random Random generator that should be used when randomness is needed
4947
*/
50-
void process(Person person, List<HealthRecord.Encounter> encounters, long time, Random random);
48+
void process(Person person, List<HealthRecord.Encounter> encounters, long time);
5149
}

src/main/java/org/mitre/synthea/engine/HealthRecordEditors.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5-
import java.util.Random;
65
import java.util.stream.Collectors;
76

87
import org.mitre.synthea.world.agents.Person;
@@ -45,21 +44,20 @@ public void registerEditor(HealthRecordEditor editor) {
4544
* <p>
4645
* It's unlikely that this method should be called by anything outside of Generator.
4746
* </p>
48-
* @param person The Person to run on
47+
* @param person The Person to run on, and the source of randomness
4948
* @param record The HealthRecord to potentially modify
5049
* @param time The current time in the simulation
5150
* @param step The time step for the simulation
52-
* @param random The source of randomness that modules should use
5351
*/
54-
public void executeAll(Person person, HealthRecord record, long time, long step, Random random) {
52+
public void executeAll(Person person, HealthRecord record, long time, long step) {
5553
if (this.registeredEditors.size() > 0) {
5654
long start = time - step;
5755
List<HealthRecord.Encounter> encountersThisStep = record.encounters.stream()
5856
.filter(e -> e.start >= start)
5957
.collect(Collectors.toList());
6058
this.registeredEditors.forEach(m -> {
6159
if (m.shouldRun(person, record, time)) {
62-
m.process(person, encountersThisStep, time, random);
60+
m.process(person, encountersThisStep, time);
6361
}
6462
});
6563
}

src/main/java/org/mitre/synthea/engine/Module.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
* across the population, it is important that States are cloned before they are executed.
5353
* This keeps the "master" copy of the module clean.
5454
*/
55-
public class Module implements Serializable {
55+
public class Module implements Cloneable, Serializable {
5656

5757
private static final Configuration JSON_PATH_CONFIG = Configuration.builder()
5858
.jsonProvider(new GsonJsonProvider())
@@ -284,6 +284,23 @@ public Module(JsonObject definition, boolean submodule) throws Exception {
284284
}
285285
}
286286

287+
/**
288+
* Clone this module. Never provide the original.
289+
*/
290+
public Module clone() {
291+
Module clone = new Module();
292+
clone.name = this.name;
293+
clone.submodule = this.submodule;
294+
clone.remarks = this.remarks;
295+
if (this.states != null) {
296+
clone.states = new ConcurrentHashMap<String, State>();
297+
for (String key : this.states.keySet()) {
298+
clone.states.put(key, this.states.get(key).clone());
299+
}
300+
}
301+
return clone;
302+
}
303+
287304
/**
288305
* Process this Module with the given Person at the specified time within the simulation.
289306
*
@@ -423,7 +440,7 @@ public synchronized Module get() {
423440
if (fault != null) {
424441
throw new RuntimeException(fault);
425442
}
426-
return module;
443+
return module.clone();
427444
}
428445
}
429446
}

0 commit comments

Comments
 (0)