diff --git a/src/main/java/org/mitre/synthea/engine/Generator.java b/src/main/java/org/mitre/synthea/engine/Generator.java index fe294283a4..1a7f2c5fad 100644 --- a/src/main/java/org/mitre/synthea/engine/Generator.java +++ b/src/main/java/org/mitre/synthea/engine/Generator.java @@ -19,7 +19,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -36,6 +35,7 @@ import org.mitre.synthea.export.CDWExporter; import org.mitre.synthea.export.Exporter; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.helpers.TransitionMetrics; import org.mitre.synthea.helpers.Utilities; @@ -56,7 +56,7 @@ /** * Generator creates a population by running the generic modules each timestep per Person. */ -public class Generator implements RandomNumberGenerator { +public class Generator { /** * Unique ID for this instance of the Generator. @@ -64,7 +64,8 @@ public class Generator implements RandomNumberGenerator { */ public final UUID id = UUID.randomUUID(); public GeneratorOptions options; - private Random random; + private DefaultRandomNumberGenerator populationRandom; + private DefaultRandomNumberGenerator clinicianRandom; public long timestep; public long stop; public long referenceTime; @@ -218,7 +219,8 @@ private void init() { CDWExporter.getInstance().setKeyStart((stateIndex * 1_000_000) + 1); } - this.random = new Random(options.seed); + this.populationRandom = new DefaultRandomNumberGenerator(options.seed); + this.clinicianRandom = new DefaultRandomNumberGenerator(options.clinicianSeed); this.timestep = Long.parseLong(Config.get("generate.timestep")); this.stop = options.endTime; this.referenceTime = options.referenceTime; @@ -262,7 +264,7 @@ private void init() { } // initialize hospitals - Provider.loadProviders(location, options.clinicianSeed); + Provider.loadProviders(location, this.clinicianRandom); // Initialize Payers Payer.loadPayers(location); // ensure modules load early @@ -366,7 +368,7 @@ public void run() { // Generate patients up to the specified population size. for (int i = 0; i < this.options.population; i++) { final int index = i; - final long seed = this.random.nextLong(); + final long seed = this.populationRandom.randLong(); threadPool.submit(() -> generatePerson(index, seed)); } } @@ -398,6 +400,8 @@ public void run() { System.out.printf("Records: total=%d, alive=%d, dead=%d\n", totalGeneratedPopulation.get(), stats.get("alive").get(), stats.get("dead").get()); + System.out.printf("RNG=%d\n", this.populationRandom.getCount()); + System.out.printf("Clinician RNG=%d\n", this.clinicianRandom.getCount()); if (this.metrics != null) { metrics.printStats(totalGeneratedPopulation.get(), Module.getModules(getModulePredicate())); @@ -440,6 +444,7 @@ public List importFixedPatientDemographicsFile() { * @param index Target index in the whole set of people to generate * @return generated Person */ + @Deprecated public Person generatePerson(int index) { // System.currentTimeMillis is not unique enough long personSeed = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; @@ -461,16 +466,15 @@ public Person generatePerson(int index) { */ public Person generatePerson(int index, long personSeed) { - Person person = null; + Person person = new Person(personSeed); try { int tryNumber = 0; // Number of tries to create these demographics - Random randomForDemographics = new Random(personSeed); - Map demoAttributes = randomDemographics(randomForDemographics); + Map demoAttributes = randomDemographics(person); if (this.recordGroups != null) { // Pick fixed demographics if a fixed demographics record file is used. - demoAttributes = pickFixedDemographics(index, random); + demoAttributes = pickFixedDemographics(index, person); } boolean patientMeetsCriteria; @@ -509,7 +513,7 @@ public Person generatePerson(int index, long personSeed) { // when we want to export this patient, but keep trying to produce one meeting criteria if (!check.exportAnyway()) { // rotate the seed so the next attempt gets a consistent but different one - personSeed = randomForDemographics.nextLong(); + personSeed = person.randLong(); continue; // skip the other stuff if the patient doesn't meet our goals // note that this skips ahead to the while check @@ -521,7 +525,7 @@ public Person generatePerson(int index, long personSeed) { if (!isAlive) { // rotate the seed so the next attempt gets a consistent but different one - personSeed = randomForDemographics.nextLong(); + personSeed = person.randLong(); // if we've tried and failed > 10 times to generate someone over age 90 // and the options allow for ages as low as 85 @@ -529,11 +533,11 @@ public Person generatePerson(int index, long personSeed) { if (tryNumber > 10 && (int)person.attributes.get(TARGET_AGE) > 90 && (!options.ageSpecified || options.minAge <= 85)) { // pick a new target age between 85 and 90 - int newTargetAge = randomForDemographics.nextInt(5) + 85; + int newTargetAge = person.randInt(5) + 85; // the final age bracket is 85-110, but our patients rarely break 100 // so reducing a target age to 85-90 shouldn't affect numbers too much demoAttributes.put(TARGET_AGE, newTargetAge); - long birthdate = birthdateFromTargetAge(newTargetAge, randomForDemographics); + long birthdate = birthdateFromTargetAge(newTargetAge, person); demoAttributes.put(Person.BIRTHDATE, birthdate); } } @@ -705,7 +709,7 @@ public void updatePerson(Person person) { * @param random The random number generator to use. * @return demographics */ - public Map randomDemographics(Random random) { + public Map randomDemographics(RandomNumberGenerator random) { Demographics city = location.randomCity(random); Map demoAttributes = pickDemographics(random, city); return demoAttributes; @@ -722,11 +726,12 @@ private synchronized void writeToConsole(Person person, int index, long time, bo // this is synchronized to ensure all lines for a single person are always printed // consecutively String deceased = isAlive ? "" : "DECEASED"; - System.out.format("%d -- %s (%d y/o %s) %s, %s %s\n", index + 1, + System.out.format("%d -- %s (%d y/o %s) %s, %s %s (%d)\n", index + 1, person.attributes.get(Person.NAME), person.ageInYears(time), person.attributes.get(Person.GENDER), person.attributes.get(Person.CITY), person.attributes.get(Person.STATE), - deceased); + deceased, + person.getCount()); if (this.logLevel.equals("detailed")) { System.out.println("ATTRIBUTES"); @@ -750,7 +755,7 @@ private synchronized void writeToConsole(Person person, int index, long time, bo * @param city The city to base the demographics off of. * @return the person's picked demographics. */ - private Map pickDemographics(Random random, Demographics city) { + private Map pickDemographics(RandomNumberGenerator random, Demographics city) { // Output map of the generated demographc data. Map demographicsOutput = new HashMap<>(); @@ -794,7 +799,7 @@ private Map pickDemographics(Random random, Demographics city) { double povertyRatio = city.povertyRatio(income); demographicsOutput.put(Person.POVERTY_RATIO, povertyRatio); - double occupation = random.nextDouble(); + double occupation = random.rand(); demographicsOutput.put(Person.OCCUPATION_LEVEL, occupation); double sesScore = city.socioeconomicScore(incomeLevel, educationLevel, occupation); @@ -809,7 +814,7 @@ private Map pickDemographics(Random random, Demographics city) { int targetAge; if (options.ageSpecified) { targetAge = - (int) (options.minAge + ((options.maxAge - options.minAge) * random.nextDouble())); + (int) (options.minAge + ((options.maxAge - options.minAge) * random.rand())); } else { targetAge = city.pickAge(random); } @@ -827,7 +832,7 @@ private Map pickDemographics(Random random, Demographics city) { * @param index The index to use. * @param random Random object. */ - private Map pickFixedDemographics(int index, Random random) { + private Map pickFixedDemographics(int index, RandomNumberGenerator random) { // Get the first FixedRecord from the current RecordGroup FixedRecordGroup recordGroup = this.recordGroups.get(index); @@ -861,11 +866,11 @@ private Map pickFixedDemographics(int index, Random random) { * @param random A random object. * @return */ - private long birthdateFromTargetAge(long targetAge, Random random) { + private long birthdateFromTargetAge(long targetAge, RandomNumberGenerator random) { 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())); + (long) (earliestBirthdate + ((latestBirthdate - earliestBirthdate) * random.rand())); } /** @@ -908,52 +913,10 @@ private Predicate getModulePredicate() { } /** - * Returns a random double. - */ - public double rand() { - return random.nextDouble(); - } - - /** - * Returns a random boolean. - */ - public boolean randBoolean() { - return random.nextBoolean(); - } - - /** - * Returns a random integer. - */ - public int randInt() { - return random.nextInt(); - } - - /** - * Returns a random integer in the given bound. + * Get the seeded random number generator used by this Generator. + * @return the random number generator. */ - public int randInt(int bound) { - return random.nextInt(bound); + public RandomNumberGenerator getRandomizer() { + return this.populationRandom; } - - /** - * Returns a double from a normal distribution. - */ - public double randGaussian() { - return random.nextGaussian(); - } - - /** - * Return a random long. - */ - public long randLong() { - return random.nextLong(); - } - - /** - * Return a random UUID. - */ - public UUID randUUID() { - return new UUID(randLong(), randLong()); - } - } diff --git a/src/main/java/org/mitre/synthea/export/Exporter.java b/src/main/java/org/mitre/synthea/export/Exporter.java index cc11a57ccf..e37f7d8259 100644 --- a/src/main/java/org/mitre/synthea/export/Exporter.java +++ b/src/main/java/org/mitre/synthea/export/Exporter.java @@ -434,20 +434,20 @@ public static void runPostCompletionExports(Generator generator, ExporterRuntime // Before we force bulk data to be off... try { - FhirGroupExporterR4.exportAndSave(generator, generator.stop); + FhirGroupExporterR4.exportAndSave(generator.getRandomizer(), generator.stop); } catch (Exception e) { e.printStackTrace(); } Config.set("exporter.fhir.bulk_data", "false"); try { - HospitalExporterR4.export(generator, generator.stop); + HospitalExporterR4.export(generator.getRandomizer(), generator.stop); } catch (Exception e) { e.printStackTrace(); } try { - FhirPractitionerExporterR4.export(generator, generator.stop); + FhirPractitionerExporterR4.export(generator.getRandomizer(), generator.stop); } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/org/mitre/synthea/export/FhirDstu2.java b/src/main/java/org/mitre/synthea/export/FhirDstu2.java index 1ebd0cdcc2..583c80b526 100644 --- a/src/main/java/org/mitre/synthea/export/FhirDstu2.java +++ b/src/main/java/org/mitre/synthea/export/FhirDstu2.java @@ -478,7 +478,7 @@ private static Entry basicInfo(Person person, Bundle bundle, long stopTime) { String generatedBySynthea = "Generated by Synthea." + "Version identifier: " + Utilities.SYNTHEA_VERSION + " . " - + " Person seed: " + person.seed + + " Person seed: " + person.getSeed() + " Population seed: " + person.populationSeed; patientResource.setText(new NarrativeDt( diff --git a/src/main/java/org/mitre/synthea/export/FhirR4.java b/src/main/java/org/mitre/synthea/export/FhirR4.java index a45f6d17fa..68cccf88dd 100644 --- a/src/main/java/org/mitre/synthea/export/FhirR4.java +++ b/src/main/java/org/mitre/synthea/export/FhirR4.java @@ -623,7 +623,7 @@ private static BundleEntryComponent basicInfo(Person person, Bundle bundle, long String generatedBySynthea = "Generated by Synthea." + "Version identifier: " + Utilities.SYNTHEA_VERSION + " . " - + " Person seed: " + person.seed + + " Person seed: " + person.getSeed() + " Population seed: " + person.populationSeed; patientResource.setText(new Narrative().setStatus(NarrativeStatus.GENERATED) diff --git a/src/main/java/org/mitre/synthea/export/FhirStu3.java b/src/main/java/org/mitre/synthea/export/FhirStu3.java index 86b7561f08..6aff9c2842 100644 --- a/src/main/java/org/mitre/synthea/export/FhirStu3.java +++ b/src/main/java/org/mitre/synthea/export/FhirStu3.java @@ -572,7 +572,7 @@ private static BundleEntryComponent basicInfo(Person person, Bundle bundle, long String generatedBySynthea = "Generated by Synthea." + "Version identifier: " + Utilities.SYNTHEA_VERSION + " . " - + " Person seed: " + person.seed + + " Person seed: " + person.getSeed() + " Population seed: " + person.populationSeed; patientResource.setText(new Narrative().setStatus(NarrativeStatus.GENERATED) diff --git a/src/main/java/org/mitre/synthea/export/JSONExporter.java b/src/main/java/org/mitre/synthea/export/JSONExporter.java index 5ab12305c5..b6ff7c18a0 100644 --- a/src/main/java/org/mitre/synthea/export/JSONExporter.java +++ b/src/main/java/org/mitre/synthea/export/JSONExporter.java @@ -76,7 +76,7 @@ public PersonSerializer(boolean includeModuleHistory) { @Override public JsonElement serialize(Person src, Type typeOfSrc, JsonSerializationContext context) { JsonObject personOut = new JsonObject(); - personOut.add("seed", new JsonPrimitive(src.seed)); + personOut.add("seed", new JsonPrimitive(src.getSeed())); personOut.add("lastUpdated", new JsonPrimitive(src.lastUpdated)); personOut.add("coverage", context.serialize(src.coverage)); JsonObject attributes = new JsonObject(); diff --git a/src/main/java/org/mitre/synthea/export/ValueSetCodeResolver.java b/src/main/java/org/mitre/synthea/export/ValueSetCodeResolver.java index b0e9e315ff..735fd2bfeb 100644 --- a/src/main/java/org/mitre/synthea/export/ValueSetCodeResolver.java +++ b/src/main/java/org/mitre/synthea/export/ValueSetCodeResolver.java @@ -158,7 +158,7 @@ private Code resolveCode(@Nullable Code code) { return null; } return code.valueSet != null - ? RandomCodeGenerator.getCode(code.valueSet, person.seed, code) + ? RandomCodeGenerator.getCode(code.valueSet, person.getSeed(), code) : code; } diff --git a/src/main/java/org/mitre/synthea/helpers/DefaultRandomNumberGenerator.java b/src/main/java/org/mitre/synthea/helpers/DefaultRandomNumberGenerator.java new file mode 100644 index 0000000000..562c71bfef --- /dev/null +++ b/src/main/java/org/mitre/synthea/helpers/DefaultRandomNumberGenerator.java @@ -0,0 +1,79 @@ +package org.mitre.synthea.helpers; + +import java.io.Serializable; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A default implementation of the RandomNumberGenerator interface. + * The goal is to isolate sources of randomness by consolidating the use of java.util.Random + * or other sources of randomness for traceability. + */ +public class DefaultRandomNumberGenerator implements RandomNumberGenerator, Serializable { + + private long seed; + private Random random; + private AtomicLong count; + + /** + * Create a new default random number generator. + * @param seed The random number generator seed. + */ + public DefaultRandomNumberGenerator(long seed) { + this.seed = seed; + random = new Random(this.seed); + count = new AtomicLong(); + } + + public long getSeed() { + return this.seed; + } + + @Override + public double rand() { + count.incrementAndGet(); + return random.nextDouble(); + } + + @Override + public boolean randBoolean() { + count.incrementAndGet(); + return random.nextBoolean(); + } + + @Override + public double randGaussian() { + count.incrementAndGet(); + return random.nextGaussian(); + } + + @Override + public int randInt() { + count.incrementAndGet(); + return random.nextInt(); + } + + @Override + public int randInt(int bound) { + count.incrementAndGet(); + return random.nextInt(bound); + } + + @Override + public long randLong() { + count.incrementAndGet(); + return random.nextLong(); + } + + @Override + public UUID randUUID() { + return new UUID(randLong(), randLong()); + } + + @Override + public long getCount() { + return count.get(); + } + +} diff --git a/src/main/java/org/mitre/synthea/helpers/RandomCollection.java b/src/main/java/org/mitre/synthea/helpers/RandomCollection.java index b857069190..d2884d0c75 100644 --- a/src/main/java/org/mitre/synthea/helpers/RandomCollection.java +++ b/src/main/java/org/mitre/synthea/helpers/RandomCollection.java @@ -3,7 +3,6 @@ import java.io.Serializable; import java.util.Map.Entry; import java.util.NavigableMap; -import java.util.Random; import java.util.TreeMap; /** @@ -29,18 +28,6 @@ public void add(double weight, E result) { map.put(total, result); } - /** - * Select an item from the collection at random by the weight of the items. - * Selecting an item from one draw, does not remove the item from the collection - * for subsequent draws. In other words, an item can be selected repeatedly if - * the weights are severely imbalanced. - * @param random - Random object. - * @return a random item from the collection weighted by the item weights. - */ - public E next(Random random) { - return next(random.nextDouble() * total); - } - /** * Select an item from the collection at random by the weight of the items. * Selecting an item from one draw, does not remove the item from the collection diff --git a/src/main/java/org/mitre/synthea/helpers/RandomNumberGenerator.java b/src/main/java/org/mitre/synthea/helpers/RandomNumberGenerator.java index c21f02f505..73f115c534 100644 --- a/src/main/java/org/mitre/synthea/helpers/RandomNumberGenerator.java +++ b/src/main/java/org/mitre/synthea/helpers/RandomNumberGenerator.java @@ -101,4 +101,10 @@ public default double rand(int[] range) { /** Return a random UUID. */ public UUID randUUID(); + + /** Return how many times this RNG was called. */ + public long getCount(); + + /** Return the seed. */ + public long getSeed(); } diff --git a/src/main/java/org/mitre/synthea/modules/LifecycleModule.java b/src/main/java/org/mitre/synthea/modules/LifecycleModule.java index 2905247bce..67b841b1bf 100644 --- a/src/main/java/org/mitre/synthea/modules/LifecycleModule.java +++ b/src/main/java/org/mitre/synthea/modules/LifecycleModule.java @@ -184,7 +184,7 @@ public static void birth(Person person, long time) { attributes.put(Person.ACTIVE_WEIGHT_MANAGEMENT, false); // TODO: Why are the percentiles a vital sign? Sounds more like an attribute? double heightPercentile = person.rand(); - PediatricGrowthTrajectory pgt = new PediatricGrowthTrajectory(person.seed, time); + PediatricGrowthTrajectory pgt = new PediatricGrowthTrajectory(person.getSeed(), time); double weightPercentile = pgt.reverseWeightPercentile(gender, heightPercentile); person.setVitalSign(VitalSign.HEIGHT_PERCENTILE, heightPercentile); person.setVitalSign(VitalSign.WEIGHT_PERCENTILE, weightPercentile); diff --git a/src/main/java/org/mitre/synthea/world/agents/Clinician.java b/src/main/java/org/mitre/synthea/world/agents/Clinician.java index 9c09ca3e8b..a987709b77 100644 --- a/src/main/java/org/mitre/synthea/world/agents/Clinician.java +++ b/src/main/java/org/mitre/synthea/world/agents/Clinician.java @@ -4,10 +4,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Map; -import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.world.geography.quadtree.QuadTreeElement; public class Clinician implements Serializable, QuadTreeElement { @@ -35,8 +35,6 @@ public class Clinician implements Serializable, QuadTreeElement { public static final String ZIP = "zip"; public static final String LOCATION = "location"; - - public final Random random; public final long identifier; public final String uuid; public final String npi; @@ -53,12 +51,11 @@ public class Clinician implements Serializable, QuadTreeElement { * @param identifier The clinician's organizational unique identifier. * @param organization The organization this clinician belongs to. May be null. */ - public Clinician(long clinicianSeed, Random clinicianRand, + public Clinician(long clinicianSeed, RandomNumberGenerator clinicianRand, long identifier, Provider organization) { String base = clinicianSeed + ":" + identifier + ":" - + organization.id + ":" + clinicianRand.nextLong(); + + organization.id + ":" + clinicianRand.randLong(); this.uuid = UUID.nameUUIDFromBytes(base.getBytes()).toString(); - this.random = clinicianRand; this.identifier = identifier; this.npi = toClinicianNPI(this.identifier); this.organization = organization; @@ -100,10 +97,6 @@ public String getFullname() { return prefix + " " + name; } - public double rand() { - return random.nextDouble(); - } - public Map getAttributes() { return attributes; } @@ -128,19 +121,13 @@ public int getEncounterCount() { return encounters; } - public int randInt(int bound) { - return random.nextInt(bound); - } - @Override public double getX() { - // TODO Auto-generated method stub return getLonLat().getX(); } @Override public double getY() { - // TODO Auto-generated method stub return getLonLat().getY(); } 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 8375170578..1c157b3fe2 100644 --- a/src/main/java/org/mitre/synthea/world/agents/Person.java +++ b/src/main/java/org/mitre/synthea/world/agents/Person.java @@ -13,7 +13,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -24,6 +23,7 @@ import org.mitre.synthea.engine.State; import org.mitre.synthea.helpers.Config; import org.mitre.synthea.helpers.ConstantValueGenerator; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.helpers.Utilities; import org.mitre.synthea.helpers.ValueGenerator; @@ -101,8 +101,7 @@ public class Person implements Serializable, RandomNumberGenerator, QuadTreeElem public static final String BLINDNESS = "blindness"; private static final String LAST_MONTH_PAID = "last_month_paid"; - private final Random random; - public final long seed; + private final DefaultRandomNumberGenerator random; public long populationSeed; /** * Tracks the last time that the person was updated over a serialize/deserialize. @@ -145,8 +144,7 @@ public class Person implements Serializable, RandomNumberGenerator, QuadTreeElem * Person constructor. */ public Person(long seed) { - this.seed = seed; - random = new Random(seed); + random = new DefaultRandomNumberGenerator(seed); attributes = new ConcurrentHashMap(); vitalSigns = new ConcurrentHashMap(); symptoms = new ConcurrentHashMap(); @@ -173,49 +171,59 @@ record = defaultRecord; * Returns a random double. */ public double rand() { - return random.nextDouble(); + return random.rand(); } /** * Returns a random boolean. */ public boolean randBoolean() { - return random.nextBoolean(); + return random.randBoolean(); } /** * Returns a random integer. */ public int randInt() { - return random.nextInt(); + return random.randInt(); } /** * Returns a random integer in the given bound. */ public int randInt(int bound) { - return random.nextInt(bound); + return random.randInt(bound); } /** * Returns a double from a normal distribution. */ public double randGaussian() { - return random.nextGaussian(); + return random.randGaussian(); } /** * Return a random long. */ public long randLong() { - return random.nextLong(); + return random.randLong(); } /** * Return a random UUID. */ public UUID randUUID() { - return new UUID(randLong(), randLong()); + return random.randUUID(); + } + + @Override + public long getCount() { + return random.getCount(); + } + + @Override + public long getSeed() { + return random.getSeed(); } /** diff --git a/src/main/java/org/mitre/synthea/world/agents/Provider.java b/src/main/java/org/mitre/synthea/world/agents/Provider.java index 3cf7a04051..270f75eb0a 100644 --- a/src/main/java/org/mitre/synthea/world/agents/Provider.java +++ b/src/main/java/org/mitre/synthea/world/agents/Provider.java @@ -15,13 +15,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.mitre.synthea.export.JSONSkip; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.helpers.SimpleCSV; import org.mitre.synthea.helpers.Utilities; @@ -325,7 +325,7 @@ private static QuadTree generateQuadTree() { * Load into cache the list of providers for a state. * @param location the state being loaded. */ - public static void loadProviders(Location location, long clinicianSeed) { + public static void loadProviders(Location location, DefaultRandomNumberGenerator random) { if (!statesLoaded.contains(location.state) || !statesLoaded.contains(Location.getAbbreviation(location.state)) || !statesLoaded.contains(Location.getStateName(location.state))) { @@ -337,30 +337,30 @@ public static void loadProviders(Location location, long clinicianSeed) { String hospitalFile = Config.get("generate.providers.hospitals.default_file"); loadProviders(location, hospitalFile, ProviderType.HOSPITAL, servicesProvided, - clinicianSeed, false); + random, false); String ihsHospitalFile = Config.get("generate.providers.ihs.hospitals.default_file"); loadProviders(location, ihsHospitalFile, ProviderType.IHS, servicesProvided, - clinicianSeed, true); + random, true); servicesProvided.add(EncounterType.WELLNESS); String vaFile = Config.get("generate.providers.veterans.default_file"); - loadProviders(location, vaFile, ProviderType.VETERAN, servicesProvided, clinicianSeed, + loadProviders(location, vaFile, ProviderType.VETERAN, servicesProvided, random, false); servicesProvided.clear(); servicesProvided.add(EncounterType.WELLNESS); String primaryCareFile = Config.get("generate.providers.primarycare.default_file"); loadProviders(location, primaryCareFile, ProviderType.PRIMARY, servicesProvided, - clinicianSeed, false); + random, false); String ihsPCFile = Config.get("generate.providers.ihs.primarycare.default_file"); - loadProviders(location, ihsPCFile, ProviderType.IHS, servicesProvided, clinicianSeed, true); + loadProviders(location, ihsPCFile, ProviderType.IHS, servicesProvided, random, true); servicesProvided.clear(); servicesProvided.add(EncounterType.URGENTCARE); String urgentcareFile = Config.get("generate.providers.urgentcare.default_file"); loadProviders(location, urgentcareFile, ProviderType.URGENT, servicesProvided, - clinicianSeed, false); + random, false); statesLoaded.add(location.state); statesLoaded.add(Location.getAbbreviation(location.state)); @@ -377,19 +377,19 @@ public static void loadProviders(Location location, long clinicianSeed) { servicesProvided.add(EncounterType.HOME); String homeHealthFile = Config.get("generate.providers.homehealth.default_file"); loadProviders(location, homeHealthFile, ProviderType.HOME_HEALTH, servicesProvided, - clinicianSeed, true); + random, true); servicesProvided.clear(); servicesProvided.add(EncounterType.HOSPICE); String hospiceFile = Config.get("generate.providers.hospice.default_file"); loadProviders(location, hospiceFile, ProviderType.HOSPICE, servicesProvided, - clinicianSeed, true); + random, true); servicesProvided.clear(); servicesProvided.add(EncounterType.SNF); String nursingFile = Config.get("generate.providers.nursing.default_file"); loadProviders(location, nursingFile, ProviderType.NURSING, servicesProvided, - clinicianSeed, true); + random, true); } catch (IOException e) { System.err.println("WARNING: unable to load optional providers in: " + location.state); } @@ -404,12 +404,12 @@ public static void loadProviders(Location location, long clinicianSeed) { * @param filename Location of the file, relative to src/main/resources * @param providerType ProviderType * @param servicesProvided Set of services provided by these facilities - * @param clinicianSeed random seed for clinicians + * @param random Source of randomness for provider generation * @param optional if true the function will silently ignore a null or empty filename * @throws IOException if the file cannot be read */ public static void loadProviders(Location location, String filename, - ProviderType providerType, Set servicesProvided, long clinicianSeed, + ProviderType providerType, Set servicesProvided, RandomNumberGenerator random, boolean optional) throws IOException { if (optional && (filename == null || filename.length() == 0)) { @@ -418,7 +418,6 @@ public static void loadProviders(Location location, String filename, String resource = Utilities.readResource(filename); Iterator> csv = SimpleCSV.parseLineByLine(resource); - Random clinicianRand = new Random(clinicianSeed); while (csv.hasNext()) { Map row = csv.next(); @@ -454,7 +453,7 @@ public static void loadProviders(Location location, String filename, || row.get("hasSpecialties").equalsIgnoreCase("false")) { parsed.clinicianMap.put(ClinicianSpecialty.GENERAL_PRACTICE, parsed.generateClinicianList(1, ClinicianSpecialty.GENERAL_PRACTICE, - clinicianSeed, clinicianRand)); + random)); } else { for (String specialty : ClinicianSpecialty.getSpecialties()) { String specialtyCount = row.get(specialty); @@ -462,13 +461,13 @@ public static void loadProviders(Location location, String filename, && !specialtyCount.trim().equals("0")) { parsed.clinicianMap.put(specialty, parsed.generateClinicianList(Integer.parseInt(row.get(specialty)), specialty, - clinicianSeed, clinicianRand)); + random)); } } if (row.get(ClinicianSpecialty.GENERAL_PRACTICE).equals("0")) { parsed.clinicianMap.put(ClinicianSpecialty.GENERAL_PRACTICE, parsed.generateClinicianList(1, ClinicianSpecialty.GENERAL_PRACTICE, - clinicianSeed, clinicianRand)); + random)); } } @@ -491,12 +490,12 @@ public static void loadProviders(Location location, String filename, * @return */ private ArrayList generateClinicianList(int numClinicians, String specialty, - long clinicianSeed, Random clinicianRand) { + RandomNumberGenerator random) { ArrayList clinicians = new ArrayList(); for (int i = 0; i < numClinicians; i++) { Clinician clinician = null; - clinician = generateClinician(clinicianSeed, clinicianRand, - Long.parseLong(loaded + "" + i)); + clinician = generateClinician(random.getSeed(), + Long.parseLong(loaded + "" + i), random); clinician.attributes.put(Clinician.SPECIALTY, specialty); clinicians.add(clinician); } @@ -510,22 +509,22 @@ private ArrayList generateClinicianList(int numClinicians, String spe * Seed for the random clinician * @return generated Clinician */ - private Clinician generateClinician(long clinicianSeed, Random clinicianRand, - long clinicianIdentifier) { + private Clinician generateClinician(long clinicianSeed, long clinicianIdentifier, + RandomNumberGenerator random) { Clinician clinician = null; try { Person doc = new Person(clinicianIdentifier); Demographics cityDemographics = location.randomCity(doc); Map out = new HashMap<>(); - String race = cityDemographics.pickRace(clinicianRand); + String race = cityDemographics.pickRace(random); out.put(Person.RACE, race); - String ethnicity = cityDemographics.pickEthnicity(clinicianRand); + String ethnicity = cityDemographics.pickEthnicity(random); out.put(Person.ETHNICITY, ethnicity); String language = cityDemographics.languageFromRaceAndEthnicity(race, ethnicity, - clinicianRand); + random); out.put(Person.FIRST_LANGUAGE, language); - String gender = cityDemographics.pickGender(clinicianRand); + String gender = cityDemographics.pickGender(random); if (gender.equalsIgnoreCase("male") || gender.equalsIgnoreCase("M")) { gender = "M"; } else { @@ -533,7 +532,7 @@ private Clinician generateClinician(long clinicianSeed, Random clinicianRand, } out.put(Person.GENDER, gender); - clinician = new Clinician(clinicianSeed, clinicianRand, clinicianIdentifier, this); + clinician = new Clinician(clinicianSeed, doc, clinicianIdentifier, this); clinician.attributes.putAll(out); clinician.attributes.put(Person.ADDRESS, address); clinician.attributes.put(Person.CITY, city); @@ -679,5 +678,4 @@ public double getY() { public Point2D.Double getLonLat() { return coordinates; } - } diff --git a/src/main/java/org/mitre/synthea/world/geography/Demographics.java b/src/main/java/org/mitre/synthea/world/geography/Demographics.java index ba1a7491f9..74735c9a23 100644 --- a/src/main/java/org/mitre/synthea/world/geography/Demographics.java +++ b/src/main/java/org/mitre/synthea/world/geography/Demographics.java @@ -9,10 +9,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; import org.mitre.synthea.helpers.Config; import org.mitre.synthea.helpers.RandomCollection; +import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.helpers.SimpleCSV; import org.mitre.synthea.helpers.Utilities; @@ -48,7 +48,7 @@ public class Demographics implements Comparable, Serializable { * @param random random to use * @return the age in years */ - public int pickAge(Random random) { + public int pickAge(RandomNumberGenerator random) { // lazy-load in case this randomcollection isn't necessary if (ageDistribution == null) { ageDistribution = buildRandomCollectionFromMap(ages); @@ -68,7 +68,7 @@ public int pickAge(Random random) { // nextInt is normally exclusive of the top value, // so add 1 to make it inclusive - return random.nextInt((high - low) + 1) + low; + return random.randInt((high - low) + 1) + low; } /** @@ -76,7 +76,7 @@ public int pickAge(Random random) { * @param random random to use * @return the gender */ - public String pickGender(Random random) { + public String pickGender(RandomNumberGenerator random) { // lazy-load in case this randomcollection isn't necessary if (genderDistribution == null) { @@ -96,7 +96,7 @@ public String pickGender(Random random) { * @param random random to use * @return the race */ - public String pickRace(Random random) { + public String pickRace(RandomNumberGenerator random) { // lazy-load in case this random collection isn't necessary if (raceDistribution == null) { raceDistribution = buildRandomCollectionFromMap(race); @@ -118,7 +118,7 @@ public String pickRace(Random random) { * @param random random to use * @return "hispanic" or "nonhispanic" */ - public String pickEthnicity(Random random) { + public String pickEthnicity(RandomNumberGenerator random) { if (ethnicityDistribution == null) { ethnicityDistribution = new RandomCollection(); ethnicityDistribution.add(ethnicity, "hispanic"); @@ -136,7 +136,8 @@ public String pickEthnicity(Random random) { * @param random random to use * @return the language spoken */ - public String languageFromRaceAndEthnicity(String race, String ethnicity, Random random) { + public String languageFromRaceAndEthnicity(String race, String ethnicity, + RandomNumberGenerator random) { if (ethnicity.equals("hispanic")) { RandomCollection hispanicLanguageUsage = new RandomCollection<>(); // https://factfinder.census.gov/faces/tableservices/jsf/pages/productview.xhtml?pid=ACS_17_5YR_B16006&prodType=table @@ -235,7 +236,7 @@ public String languageFromRaceAndEthnicity(String race, String ethnicity, Random * @param random the random to use * @return the income */ - public int pickIncome(Random random) { + public int pickIncome(RandomNumberGenerator random) { // lazy-load in case this randomcollection isn't necessary if (incomeDistribution == null) { Map tempIncome = new HashMap<>(income); @@ -259,7 +260,7 @@ public int pickIncome(Random random) { // nextInt is normally exclusive of the top value, // so add 1 to make it inclusive - return random.nextInt((high - low) + 1) + low; + return random.randInt((high - low) + 1) + low; } /** @@ -296,7 +297,7 @@ public double povertyRatio(int income) { /** * Return a random education level based on statistics. */ - public String pickEducation(Random random) { + public String pickEducation(RandomNumberGenerator random) { // lazy-load in case this randomcollection isn't necessary if (educationDistribution == null) { educationDistribution = buildRandomCollectionFromMap(education); @@ -308,7 +309,7 @@ public String pickEducation(Random random) { /** * Return a random number between the configured bounds for a specified education level. */ - public double educationLevel(String level, Random random) { + public double educationLevel(String level, RandomNumberGenerator random) { double lessThanHsMin = Config.getAsDouble( "generate.demographics.socioeconomic.education.less_than_hs.min", 0.0); double lessThanHsMax = Config.getAsDouble( @@ -328,21 +329,18 @@ public double educationLevel(String level, Random random) { switch (level) { case "less_than_hs": - return rand(random, lessThanHsMin, lessThanHsMax); + return random.rand(lessThanHsMin, lessThanHsMax); case "hs_degree": - return rand(random, hsDegreeMin, hsDegreeMax); + return random.rand(hsDegreeMin, hsDegreeMax); case "some_college": - return rand(random, someCollegeMin, someCollegeMax); + return random.rand(someCollegeMin, someCollegeMax); case "bs_degree": - return rand(random, bsDegreeMin, bsDegreeMax); + return random.rand(bsDegreeMin, bsDegreeMax); default: return 0.0; } } - private static double rand(Random r, double low, double high) { - return (low + ((high - low) * r.nextDouble())); - } /** * Calculate the socio-economic score for the supplied parameters. diff --git a/src/main/java/org/mitre/synthea/world/geography/Location.java b/src/main/java/org/mitre/synthea/world/geography/Location.java index a165672e7a..cc2b99c233 100644 --- a/src/main/java/org/mitre/synthea/world/geography/Location.java +++ b/src/main/java/org/mitre/synthea/world/geography/Location.java @@ -11,7 +11,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Random; import org.apache.commons.lang3.ArrayUtils; import org.mitre.synthea.export.JSONSkip; @@ -19,7 +18,6 @@ import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.helpers.SimpleCSV; import org.mitre.synthea.helpers.Utilities; -import org.mitre.synthea.world.agents.Clinician; import org.mitre.synthea.world.agents.Person; public class Location implements Serializable { @@ -243,24 +241,6 @@ public Demographics randomCity(RandomNumberGenerator random) { return demographics.get(randomCityId(random)); } - /** - * Pick the name of a random city from the current "world". - * @param random The source of randomness. - * @return Demographics of a random city. - */ - public Demographics randomCity(Random random) { - if (city != null) { - // if we're only generating one city at a time, just use the largest entry for that one city - if (fixedCity == null) { - fixedCity = demographics.values().stream() - .filter(d -> d.city.equalsIgnoreCase(city)) - .sorted().findFirst().get(); - } - return fixedCity; - } - return demographics.get(randomCityId(random)); - } - /** * Pick a random city name, weighted by population. * @param random the source of randomness @@ -291,21 +271,6 @@ private String randomCityId(RandomNumberGenerator random) { throw new RuntimeException("Unable to select a random city id."); } - private String randomCityId(Random random) { - long targetPop = (long) (random.nextDouble() * totalPopulation); - - for (Map.Entry city : populationByCityId.entrySet()) { - targetPop -= city.getValue(); - - if (targetPop < 0) { - return city.getKey(); - } - } - - // should never happen - throw new RuntimeException("Unable to select a random city id."); - } - /** * Pick a random birth place, weighted by population. * @param random the source of randomness diff --git a/src/test/java/org/mitre/synthea/engine/GeneratorTest.java b/src/test/java/org/mitre/synthea/engine/GeneratorTest.java index 8f284275e9..e9480cf054 100644 --- a/src/test/java/org/mitre/synthea/engine/GeneratorTest.java +++ b/src/test/java/org/mitre/synthea/engine/GeneratorTest.java @@ -25,6 +25,8 @@ import org.mitre.synthea.export.Exporter; import org.mitre.synthea.export.Exporter.SupportedFhirVersion; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; +import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.helpers.Utilities; import org.mitre.synthea.world.agents.Payer; import org.mitre.synthea.world.agents.Person; @@ -272,8 +274,8 @@ public void testUpdateAfterCreation() throws Exception { Person[] people = new Person[NUM_RECS]; for (int i = 0; i < NUM_RECS; i++) { long personSeed = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; - Random randomForDemographics = new Random(personSeed); - Map demoAttributes = generator.randomDemographics(randomForDemographics); + RandomNumberGenerator random = new DefaultRandomNumberGenerator(personSeed); + Map demoAttributes = generator.randomDemographics(random); people[i] = generator.createPerson(personSeed, demoAttributes); generator.recordPerson(people[i], i); } @@ -333,8 +335,8 @@ public void testUpdateAfterCreationAndSerialization() throws Exception { Person[] people = new Person[NUM_RECS]; for (int i = 0; i < NUM_RECS; i++) { long personSeed = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; - Random randomForDemographics = new Random(personSeed); - Map demoAttributes = generator.randomDemographics(randomForDemographics); + RandomNumberGenerator random = new DefaultRandomNumberGenerator(personSeed); + Map demoAttributes = generator.randomDemographics(random); people[i] = generator.createPerson(personSeed, demoAttributes); //generator.recordPerson(people[i], i); } diff --git a/src/test/java/org/mitre/synthea/export/BB2RIFExporterTest.java b/src/test/java/org/mitre/synthea/export/BB2RIFExporterTest.java index c69f4f8ae9..ab456ce14f 100644 --- a/src/test/java/org/mitre/synthea/export/BB2RIFExporterTest.java +++ b/src/test/java/org/mitre/synthea/export/BB2RIFExporterTest.java @@ -37,6 +37,7 @@ import org.mitre.synthea.export.BB2RIFStructure.DME; import org.mitre.synthea.export.BB2RIFStructure.INPATIENT; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.helpers.SimpleCSV; import org.mitre.synthea.helpers.Utilities; @@ -77,7 +78,7 @@ public void testBB2Export() throws Exception { Generator generator = new Generator(generatorOpts, exportOpts); generator.options.overflow = false; for (int i = 0; i < numberOfPeople; i++) { - generator.generatePerson(i); + generator.generatePerson(i, i); } // Adding post completion exports to generate organizations and providers CSV files Exporter.runPostCompletionExports(generator, exportOpts); @@ -222,13 +223,11 @@ public void testCodeMapper() { } catch (IOException | IllegalArgumentException e) { return; } - Exporter.ExporterRuntimeOptions exportOpts = new Exporter.ExporterRuntimeOptions(); - Generator.GeneratorOptions generatorOpts = new Generator.GeneratorOptions(); - Generator generator = new Generator(generatorOpts, exportOpts); + RandomNumberGenerator random = new DefaultRandomNumberGenerator(0); CodeMapper mapper = new CodeMapper("condition_code_map.json"); assertTrue(mapper.canMap("10509002")); - assertEquals("J20.9", mapper.map("10509002", generator)); - assertEquals("J209", mapper.map("10509002", generator, true)); + assertEquals("J20.9", mapper.map("10509002", random)); + assertEquals("J209", mapper.map("10509002", random, true)); assertFalse(mapper.canMap("not a code")); } diff --git a/src/test/java/org/mitre/synthea/export/CodeResolveAndExportTest.java b/src/test/java/org/mitre/synthea/export/CodeResolveAndExportTest.java index 5402c47bb2..f5d16d6ac1 100644 --- a/src/test/java/org/mitre/synthea/export/CodeResolveAndExportTest.java +++ b/src/test/java/org/mitre/synthea/export/CodeResolveAndExportTest.java @@ -52,6 +52,7 @@ import org.mitre.synthea.world.agents.Payer; import org.mitre.synthea.world.agents.Person; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord.Code; import org.mitre.synthea.world.concepts.HealthRecord.Encounter; import org.mitre.synthea.world.concepts.HealthRecord.EncounterType; @@ -121,7 +122,7 @@ public void setUp() throws Exception { Generator.DEFAULT_STATE = Config.get("test_state.default", "Massachusetts"); Location location = new Location(Generator.DEFAULT_STATE, null); location.assignPoint(person, location.randomCityName(person)); - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, ProviderTest.providerRandom); Payer.clear(); Config.set("generate.payers.insurance_companies.default_file", diff --git a/src/test/java/org/mitre/synthea/export/ExportBreakerTest.java b/src/test/java/org/mitre/synthea/export/ExportBreakerTest.java index 0904ecb9d7..68415daef6 100644 --- a/src/test/java/org/mitre/synthea/export/ExportBreakerTest.java +++ b/src/test/java/org/mitre/synthea/export/ExportBreakerTest.java @@ -49,7 +49,7 @@ public void runUntilBreaking() throws Exception { final int personIndex = i; service.submit(() -> { try { - Person p = generator.generatePerson(personIndex); + Person p = generator.generatePerson(personIndex,personIndex); // Export work goes here String ccdaXml = CCDAExporter.export(p, System.currentTimeMillis()); InputStream inputStream = IOUtils.toInputStream(ccdaXml, "UTF-8"); diff --git a/src/test/java/org/mitre/synthea/export/ExporterTest.java b/src/test/java/org/mitre/synthea/export/ExporterTest.java index c5ca50f025..fdfb46d363 100644 --- a/src/test/java/org/mitre/synthea/export/ExporterTest.java +++ b/src/test/java/org/mitre/synthea/export/ExporterTest.java @@ -13,6 +13,7 @@ import org.mitre.synthea.world.agents.Payer; import org.mitre.synthea.world.agents.Person; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord; import org.mitre.synthea.world.concepts.HealthRecord.Code; import org.mitre.synthea.world.concepts.HealthRecord.Encounter; @@ -48,7 +49,7 @@ public void setup() throws Exception { Generator.DEFAULT_STATE = Config.get("test_state.default", "Massachusetts"); Location location = new Location(Generator.DEFAULT_STATE, null); location.assignPoint(patient, location.randomCityName(patient)); - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, ProviderTest.providerRandom); record = patient.record; // Ensure Person's Payer is not null. Payer.loadNoInsurance(); diff --git a/src/test/java/org/mitre/synthea/export/HospitalExporterTestDstu2.java b/src/test/java/org/mitre/synthea/export/HospitalExporterTestDstu2.java index 5a98708c3d..e53f55d1ce 100644 --- a/src/test/java/org/mitre/synthea/export/HospitalExporterTestDstu2.java +++ b/src/test/java/org/mitre/synthea/export/HospitalExporterTestDstu2.java @@ -20,6 +20,7 @@ import org.mitre.synthea.engine.Generator; import org.mitre.synthea.helpers.Config; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord.EncounterType; import org.mitre.synthea.world.geography.Location; @@ -44,7 +45,7 @@ public void testFHIRExport() throws Exception { Generator.DEFAULT_STATE = Config.get("test_state.default", "Massachusetts"); Location location = new Location(Generator.DEFAULT_STATE, null); Provider.clear(); - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, ProviderTest.providerRandom); assertNotNull(Provider.getProviderList()); assertFalse(Provider.getProviderList().isEmpty()); diff --git a/src/test/java/org/mitre/synthea/export/HospitalExporterTestR4.java b/src/test/java/org/mitre/synthea/export/HospitalExporterTestR4.java index 00f49dbbcb..cf488a60cd 100644 --- a/src/test/java/org/mitre/synthea/export/HospitalExporterTestR4.java +++ b/src/test/java/org/mitre/synthea/export/HospitalExporterTestR4.java @@ -19,7 +19,9 @@ import org.mitre.synthea.TestHelper; import org.mitre.synthea.engine.Generator; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord.EncounterType; import org.mitre.synthea.world.geography.Location; @@ -44,12 +46,12 @@ public void testFHIRExport() throws Exception { Generator.DEFAULT_STATE = Config.get("test_state.default", "Massachusetts"); Location location = new Location(Generator.DEFAULT_STATE, null); Provider.clear(); - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, ProviderTest.providerRandom); assertNotNull(Provider.getProviderList()); assertFalse(Provider.getProviderList().isEmpty()); Provider.getProviderList().get(0).incrementEncounters(EncounterType.WELLNESS, 0); - HospitalExporterR4.export(new Generator(), 0L); + HospitalExporterR4.export(new DefaultRandomNumberGenerator(0L), 0L); File expectedExportFolder = tempOutputFolder.toPath().resolve("fhir").toFile(); assertTrue(expectedExportFolder.exists() && expectedExportFolder.isDirectory()); diff --git a/src/test/java/org/mitre/synthea/export/HospitalExporterTestStu3.java b/src/test/java/org/mitre/synthea/export/HospitalExporterTestStu3.java index e9c26089d5..6201e9ab4c 100644 --- a/src/test/java/org/mitre/synthea/export/HospitalExporterTestStu3.java +++ b/src/test/java/org/mitre/synthea/export/HospitalExporterTestStu3.java @@ -20,6 +20,7 @@ import org.mitre.synthea.engine.Generator; import org.mitre.synthea.helpers.Config; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord.EncounterType; import org.mitre.synthea.world.geography.Location; @@ -44,7 +45,7 @@ public void testFHIRExport() throws Exception { Generator.DEFAULT_STATE = Config.get("test_state.default", "Massachusetts"); Location location = new Location(Generator.DEFAULT_STATE, null); Provider.clear(); - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, ProviderTest.providerRandom); assertNotNull(Provider.getProviderList()); assertFalse(Provider.getProviderList().isEmpty()); diff --git a/src/test/java/org/mitre/synthea/export/ValueSetCodeResolverTest.java b/src/test/java/org/mitre/synthea/export/ValueSetCodeResolverTest.java index 8516a8a05b..43df66085f 100644 --- a/src/test/java/org/mitre/synthea/export/ValueSetCodeResolverTest.java +++ b/src/test/java/org/mitre/synthea/export/ValueSetCodeResolverTest.java @@ -25,6 +25,7 @@ import org.mitre.synthea.world.agents.Payer; import org.mitre.synthea.world.agents.Person; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord.CarePlan; import org.mitre.synthea.world.concepts.HealthRecord.Code; import org.mitre.synthea.world.concepts.HealthRecord.Encounter; @@ -69,7 +70,7 @@ public void setUp() throws Exception { Generator.DEFAULT_STATE = Config.get("test_state.default", "Massachusetts"); Location location = new Location(Generator.DEFAULT_STATE, null); location.assignPoint(person, location.randomCityName(person)); - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, ProviderTest.providerRandom); Payer.clear(); Config.set("generate.payers.insurance_companies.default_file", diff --git a/src/test/java/org/mitre/synthea/helpers/RandomCollectionTest.java b/src/test/java/org/mitre/synthea/helpers/RandomCollectionTest.java index 97c6f7d0d8..b625a276ac 100644 --- a/src/test/java/org/mitre/synthea/helpers/RandomCollectionTest.java +++ b/src/test/java/org/mitre/synthea/helpers/RandomCollectionTest.java @@ -1,6 +1,6 @@ package org.mitre.synthea.helpers; -import java.util.Random; +import java.util.UUID; import org.junit.Assert; import org.junit.Test; @@ -8,13 +8,13 @@ public class RandomCollectionTest { @SuppressWarnings("serial") - private class Fixed extends Random { + private class Fixed implements RandomNumberGenerator { // nextDouble returns value between 0.0 (inclusive) and 1.0 (exclusive) private final double[] values = {0.0, 0.5, 0.999}; private int index = 0; @Override - public double nextDouble() { + public double rand() { double returnValue = values[index]; index++; if (index >= values.length) { @@ -22,6 +22,46 @@ public double nextDouble() { } return returnValue; } + + @Override + public boolean randBoolean() { + return false; + } + + @Override + public double randGaussian() { + return 0; + } + + @Override + public int randInt() { + return 0; + } + + @Override + public int randInt(int bound) { + return 0; + } + + @Override + public long randLong() { + return 0; + } + + @Override + public UUID randUUID() { + return null; + } + + @Override + public long getCount() { + return 0; + } + + @Override + public long getSeed() { + return 0; + } } @Test @@ -31,7 +71,7 @@ public void testZeroWeights() { rc.add(1.0, "black"); rc.add(0.0, "asian"); - Random random = new Random(); + RandomNumberGenerator random = new DefaultRandomNumberGenerator(0); for (int i = 0; i < 10; i++) { String randomString = rc.next(random); Assert.assertEquals("black", randomString); @@ -43,7 +83,7 @@ public void testFixed() { Fixed fixed = new Fixed(); double[] expected = {0.0, 0.5, 0.999, 0.0, 0.5, 0.999, 0.0, 0.5, 0.999}; for (int i = 0; i < expected.length; i++) { - Assert.assertTrue(fixed.nextDouble() == expected[i]); + Assert.assertTrue(fixed.rand() == expected[i]); } } diff --git a/src/test/java/org/mitre/synthea/modules/EncounterModuleTest.java b/src/test/java/org/mitre/synthea/modules/EncounterModuleTest.java index 949d4c57ab..593ebf4aa9 100644 --- a/src/test/java/org/mitre/synthea/modules/EncounterModuleTest.java +++ b/src/test/java/org/mitre/synthea/modules/EncounterModuleTest.java @@ -11,6 +11,7 @@ import org.mitre.synthea.world.agents.Payer; import org.mitre.synthea.world.agents.Person; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord.Encounter; import org.mitre.synthea.world.geography.Location; @@ -34,7 +35,7 @@ public static void setup() throws Exception { String testState = Config.get("test_state.default", "Massachusetts"); location = new Location(testState, null); location.assignPoint(person, location.randomCityName(person)); - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, ProviderTest.providerRandom); module = new EncounterModule(); // Ensure Person's Payer is not null. Payer.loadNoInsurance(); diff --git a/src/test/java/org/mitre/synthea/modules/covid/C19ImmunizationModuleTest.java b/src/test/java/org/mitre/synthea/modules/covid/C19ImmunizationModuleTest.java index 2b4502a5ad..76d3aa6718 100644 --- a/src/test/java/org/mitre/synthea/modules/covid/C19ImmunizationModuleTest.java +++ b/src/test/java/org/mitre/synthea/modules/covid/C19ImmunizationModuleTest.java @@ -14,6 +14,7 @@ import org.mitre.synthea.world.agents.Payer; import org.mitre.synthea.world.agents.Person; import org.mitre.synthea.world.agents.Provider; +import org.mitre.synthea.world.agents.ProviderTest; import org.mitre.synthea.world.concepts.HealthRecord; import org.mitre.synthea.world.geography.Location; @@ -64,7 +65,7 @@ public void vaccinate() { person.attributes.put(Person.BIRTHDATE, birthday); person.attributes.put(C19ImmunizationModule.C19_VACCINE, C19Vaccine.EUASet.PFIZER); here.assignPoint(person, "Billerica"); - Provider.loadProviders(here, 1L); + Provider.loadProviders(here, ProviderTest.providerRandom); person.setProvider(HealthRecord.EncounterType.OUTPATIENT, Provider.findService(person, HealthRecord.EncounterType.OUTPATIENT, decemberFifteenth)); Payer.loadPayers(here); diff --git a/src/test/java/org/mitre/synthea/world/agents/PersonTest.java b/src/test/java/org/mitre/synthea/world/agents/PersonTest.java index 2945a0cce6..7914cf145f 100644 --- a/src/test/java/org/mitre/synthea/world/agents/PersonTest.java +++ b/src/test/java/org/mitre/synthea/world/agents/PersonTest.java @@ -15,7 +15,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.TimeZone; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -31,6 +30,8 @@ import org.mitre.synthea.engine.Generator.GeneratorOptions; import org.mitre.synthea.export.Exporter; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; +import org.mitre.synthea.helpers.RandomNumberGenerator; import org.mitre.synthea.world.concepts.VitalSign; public class PersonTest { @@ -95,16 +96,15 @@ public void testSerializationAndDeserialization() throws Exception { opts.minAge = 50; opts.maxAge = 100; Generator generator = new Generator(opts); - int personSeed = 0; - Random randomForDemographics = new Random(personSeed); - Map demoAttributes = generator.randomDemographics(randomForDemographics); + RandomNumberGenerator random = new DefaultRandomNumberGenerator(0); + Map demoAttributes = generator.randomDemographics(random); Person original = generator.createPerson(0, demoAttributes); Person rehydrated = serializeAndDeserialize(original); // Compare the original to the serialized+deserialized version assertEquals(original.randInt(), rehydrated.randInt()); - assertEquals(original.seed, rehydrated.seed); + assertEquals(original.getSeed(), rehydrated.getSeed()); assertEquals(original.populationSeed, rehydrated.populationSeed); assertEquals(original.symptoms.keySet(), rehydrated.symptoms.keySet()); assertEquals( diff --git a/src/test/java/org/mitre/synthea/world/agents/ProviderTest.java b/src/test/java/org/mitre/synthea/world/agents/ProviderTest.java index 4e7e0699ee..44fb7d604d 100644 --- a/src/test/java/org/mitre/synthea/world/agents/ProviderTest.java +++ b/src/test/java/org/mitre/synthea/world/agents/ProviderTest.java @@ -16,6 +16,7 @@ import org.junit.Test; import org.mitre.synthea.TestHelper; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; import org.mitre.synthea.world.agents.Provider.ProviderType; import org.mitre.synthea.world.concepts.HealthRecord.EncounterType; import org.mitre.synthea.world.geography.Location; @@ -24,6 +25,8 @@ public class ProviderTest { private static Location location; private static Location city; + public static final DefaultRandomNumberGenerator providerRandom = + new DefaultRandomNumberGenerator(1L); /** * Setup test suite. @@ -47,21 +50,21 @@ public void clearProviders() { @Test public void testLoadProvidersByAbbreviation() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Assert.assertNotNull(Provider.getProviderList()); Assert.assertFalse(Provider.getProviderList().isEmpty()); } @Test public void testLoadProvidersByStateName() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Assert.assertNotNull(Provider.getProviderList()); Assert.assertFalse(Provider.getProviderList().isEmpty()); } @Test public void testGenerateClinicianByAbbreviation() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Assert.assertNotNull(Provider.getProviderList()); Assert.assertFalse(Provider.getProviderList().isEmpty()); Provider provider = Provider.getProviderList().get(0); @@ -72,7 +75,7 @@ public void testGenerateClinicianByAbbreviation() { @Test public void testGenerateClinicianByState() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Assert.assertNotNull(Provider.getProviderList()); Assert.assertFalse(Provider.getProviderList().isEmpty()); Provider provider = Provider.getProviderList().get(0); @@ -83,7 +86,7 @@ public void testGenerateClinicianByState() { @Test public void testAllFacilitiesHaveAnId() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); for (Provider p : Provider.getProviderList()) { Assert.assertNotNull(p.name + " has a null ID.", p.id); } @@ -91,7 +94,7 @@ public void testAllFacilitiesHaveAnId() { @Test public void testNearestInpatientInState() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Person person = new Person(0L); location.assignPoint(person, location.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.INPATIENT, 0); @@ -100,7 +103,7 @@ public void testNearestInpatientInState() { @Test public void testNearestAmbulatoryInState() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Person person = new Person(0L); location.assignPoint(person, location.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.AMBULATORY, 0); @@ -109,7 +112,7 @@ public void testNearestAmbulatoryInState() { @Test public void testNearestWellnessInState() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Person person = new Person(0L); location.assignPoint(person, location.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.WELLNESS, 0); @@ -118,7 +121,7 @@ public void testNearestWellnessInState() { @Test public void testNearestEmergencyInState() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Person person = new Person(0L); location.assignPoint(person, location.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.EMERGENCY, 0); @@ -132,7 +135,7 @@ public void testNearestEmergencyInDC() { // coordinate. People in the same city have more or less the same // coordinate as emergency hospitals. Location capital = new Location("District of Columbia", null); - Provider.loadProviders(capital, 1L); + Provider.loadProviders(capital, providerRandom); Person person = new Person(0L); capital.assignPoint(person, capital.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.EMERGENCY, 0); @@ -141,7 +144,7 @@ public void testNearestEmergencyInDC() { @Test public void testNearestUrgentCareInState() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Person person = new Person(0L); location.assignPoint(person, location.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.URGENTCARE, 0); @@ -150,7 +153,7 @@ public void testNearestUrgentCareInState() { @Test public void testNearestInpatientInCity() { - Provider.loadProviders(city, 1L); + Provider.loadProviders(city, providerRandom); Person person = new Person(0L); city.assignPoint(person, city.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.INPATIENT, 0); @@ -159,7 +162,7 @@ public void testNearestInpatientInCity() { @Test public void testNearestAmbulatoryInCity() { - Provider.loadProviders(city, 1L); + Provider.loadProviders(city, providerRandom); Person person = new Person(0L); city.assignPoint(person, city.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.AMBULATORY, 0); @@ -168,7 +171,7 @@ public void testNearestAmbulatoryInCity() { @Test public void testNearestWellnessInCity() { - Provider.loadProviders(city, 1L); + Provider.loadProviders(city, providerRandom); Person person = new Person(0L); city.assignPoint(person, city.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.WELLNESS, 0); @@ -177,7 +180,7 @@ public void testNearestWellnessInCity() { @Test public void testNearestEmergencyInCity() { - Provider.loadProviders(city, 1L); + Provider.loadProviders(city, providerRandom); Person person = new Person(0L); city.assignPoint(person, city.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.EMERGENCY, 0); @@ -186,7 +189,7 @@ public void testNearestEmergencyInCity() { @Test public void testNearestUrgentCareInCity() { - Provider.loadProviders(city, 1L); + Provider.loadProviders(city, providerRandom); Person person = new Person(0L); city.assignPoint(person, city.randomCityName(person)); Provider provider = Provider.findService(person, EncounterType.URGENTCARE, 0); @@ -196,7 +199,7 @@ public void testNearestUrgentCareInCity() { @Ignore("VA Facilities are not guaranteed to exist with international configurations.") @Test public void testVaFacilityOnlyAcceptsVeteran() { - Provider.loadProviders(location, 1L); + Provider.loadProviders(location, providerRandom); Provider vaProvider = Provider.getProviderList() .stream() .filter(p -> ProviderType.VETERAN.equals(p.type)) @@ -224,7 +227,7 @@ public void testAllFiles() throws Exception { try { Provider.clear(); Provider.loadProviders(location, "providers/" + t.getFileName(), - ProviderType.HOSPITAL, providerServices, 1L, false); + ProviderType.HOSPITAL, providerServices, providerRandom, false); } catch (Exception e) { throw new RuntimeException("Failed to load provider file " + t, e); } diff --git a/src/test/java/org/mitre/synthea/world/geography/DemographicsTest.java b/src/test/java/org/mitre/synthea/world/geography/DemographicsTest.java index 4d41a997e9..1051641990 100644 --- a/src/test/java/org/mitre/synthea/world/geography/DemographicsTest.java +++ b/src/test/java/org/mitre/synthea/world/geography/DemographicsTest.java @@ -6,17 +6,17 @@ import java.io.IOException; import java.util.HashMap; -import java.util.Random; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.mitre.synthea.helpers.Config; +import org.mitre.synthea.helpers.DefaultRandomNumberGenerator; public class DemographicsTest { public static String demographicsFile; public static Demographics philly; - public static Random random; + public static DefaultRandomNumberGenerator random; /** * Set up the demographics to use for testing. @@ -28,7 +28,7 @@ public static void setUp() throws IOException { Config.set("generate.demographics.default_file", "geography/test_demographics.csv"); Table pa = Demographics.load("Pennsylvania"); philly = (Demographics) pa.get("Pennsylvania", "27237"); - random = new Random(0); + random = new DefaultRandomNumberGenerator(0); } @AfterClass