├── MANIFEST.in ├── june ├── configs │ ├── defaults │ │ ├── groups │ │ │ ├── schools.yaml │ │ │ ├── care_home.yaml │ │ │ ├── households.yaml │ │ │ ├── hospitals.yaml │ │ │ ├── companies.yaml │ │ │ ├── travel │ │ │ │ ├── commute.yaml │ │ │ │ └── mode_of_transport.yaml │ │ │ └── leisure │ │ │ │ ├── cinemas.yaml │ │ │ │ ├── gyms.yaml │ │ │ │ ├── pubs.yaml │ │ │ │ ├── groceries.yaml │ │ │ │ └── visits.yaml │ │ ├── epidemiology │ │ │ ├── infection │ │ │ │ ├── InfectionXNExp.yaml │ │ │ │ ├── symptoms │ │ │ │ │ ├── SymptomsConstant.yaml │ │ │ │ │ ├── SymptomsStep.yaml │ │ │ │ │ ├── SymptomsGaussian.yaml │ │ │ │ │ └── SymptomsTanh.yaml │ │ │ │ ├── InfectionConstant.yaml │ │ │ │ └── transmission │ │ │ │ │ ├── TransmissionConstant.yaml │ │ │ │ │ ├── correction_nature.yaml │ │ │ │ │ ├── b117.yaml │ │ │ │ │ ├── nature.yaml │ │ │ │ │ ├── covid19.yaml │ │ │ │ │ └── XNExp.yaml │ │ │ └── vaccines │ │ │ │ ├── vaccination_campaigns.yaml │ │ │ │ └── vaccines.yaml │ │ ├── rail_travel.yaml │ │ ├── distributors │ │ │ ├── hospital_distributor.yaml │ │ │ ├── school_distributor.yaml │ │ │ ├── worker_distributor.yaml │ │ │ └── household_distributor.yaml │ │ ├── travel │ │ │ └── city_stations.yaml │ │ ├── event │ │ │ └── events.yaml │ │ ├── demography │ │ │ └── ethnicity_encodings.yaml │ │ └── policy │ │ │ └── company_closure.yaml │ ├── logging.yaml │ ├── tests │ │ ├── transmission │ │ │ ├── test_transmission_constant.yaml │ │ │ ├── test_transmission_symptoms.yaml │ │ │ └── test_transmission_lognormal.yaml │ │ ├── test_simulator_no_leisure.yaml │ │ ├── groups │ │ │ └── make_subgroups_test_interaction.yaml │ │ ├── test_checkpoint_config.yaml │ │ ├── test_simulator_simple.yaml │ │ ├── test_simulator.yaml │ │ └── tracker │ │ │ ├── test_simulator.yaml │ │ │ ├── tracker_test_config.yaml │ │ │ └── tracker_test_interaction.yaml │ ├── config_stochastic.yaml │ ├── config_example.yaml │ └── config_example_7.yaml ├── activity │ └── __init__.py ├── domains │ ├── __init__.py │ └── domain.py ├── tracker │ └── __init__.py ├── interaction │ └── __init__.py ├── demography │ └── __init__.py ├── epidemiology │ ├── __init__.py │ ├── infection │ │ ├── health_index │ │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── symptom_tag.py │ │ ├── immunity.py │ │ └── symptoms.py │ ├── vaccines │ │ └── __init__.py │ └── infection_seed │ │ ├── __init__.py │ │ └── clustered_infection_seed.py ├── records │ ├── __init__.py │ └── helper_records_writer.py ├── event │ ├── __init__.py │ ├── incidence_setter.py │ ├── mutation.py │ └── event.py ├── groups │ ├── group │ │ ├── __init__.py │ │ ├── abstract.py │ │ ├── external.py │ │ ├── subgroup.py │ │ └── supergroup.py │ ├── travel │ │ ├── __init__.py │ │ └── transport.py │ ├── cemetery.py │ ├── leisure │ │ ├── __init__.py │ │ ├── gym.py │ │ ├── pub.py │ │ ├── cinema.py │ │ └── grocery.py │ └── __init__.py ├── exc.py ├── utils │ ├── __init__.py │ ├── numba_random.py │ ├── profiler.py │ ├── distances.py │ ├── readers.py │ └── parse_probabilities.py ├── hdf5_savers │ ├── infection_savers │ │ ├── __init__.py │ │ └── immunity_saver.py │ ├── utils.py │ └── __init__.py ├── geography │ └── __init__.py ├── distributors │ ├── __init__.py │ └── company_distributor.py ├── data_formatting │ ├── census_data │ │ ├── care_homes.py │ │ ├── n_students.py │ │ ├── n_people_in_comunal.py │ │ └── household_composition_houses.py │ ├── seed │ │ └── reformat_seed.py │ ├── time_series │ │ ├── process_confirmed_cases.py │ │ └── process_hospitalisations.py │ ├── google_api │ │ ├── README.md │ │ ├── msoa_search_cleaning.py │ │ └── msoa_search.py │ └── hospitals │ │ └── format_trusts.py ├── __init__.py ├── policy │ ├── __init__.py │ ├── regional_compliance.py │ └── interaction_policies.py ├── paths.py └── logging.py ├── test_june ├── pytest.ini ├── unit │ ├── geography │ │ ├── stations.csv │ │ ├── cities.csv │ │ ├── test_city.py │ │ └── test_station.py │ ├── epidemiology │ │ ├── infection_seed │ │ │ ├── cases_per_region.csv │ │ │ ├── exact_num_cases_world.csv │ │ │ ├── exact_num_cases_per_region.csv │ │ │ └── test_cases_distributor.py │ │ └── infection │ │ │ ├── test_immunity.py │ │ │ ├── test_data_to_rates.py │ │ │ └── test_transmission.py │ ├── files │ │ └── config │ │ │ ├── general.ini │ │ │ └── json_priors │ │ │ ├── june.json │ │ │ └── epidemiology.json │ ├── event │ │ ├── test_events.py │ │ ├── test_mutations.py │ │ └── test_incidence_setter.py │ ├── groups │ │ ├── leisure │ │ │ ├── test_pubs.py │ │ │ └── test_social_venues.py │ │ ├── test_carehomes.py │ │ ├── test_groups.py │ │ ├── test_supergroups.py │ │ ├── test_universities.py │ │ ├── test_households.py │ │ ├── test_make_subgroups.py │ │ ├── test_hospitals.py │ │ ├── test_schools.py │ │ ├── test_companies.py │ │ └── test_travel_old.py │ ├── interaction │ │ ├── interaction_test_config.yaml │ │ └── test_susceptibility.py │ ├── distributors │ │ ├── test_university_distributor.py │ │ ├── test_company_distributor.py │ │ └── test_hospital_distributor.py │ ├── test_world.py │ ├── policy │ │ ├── test_policy.py │ │ ├── test_regional_compliance.py │ │ └── test_medical_care_policies.py │ ├── test_timer.py │ └── test_fullrun.py ├── __init__.py └── conftest.py ├── docs ├── index.md └── about.md ├── scripts ├── run_june_tests.py ├── get_june_plotting_data.sh ├── get_westeros_tutorial_data.sh └── get_june_data.sh ├── setup.cfg ├── .gitignore ├── codecov.yml ├── example_scripts ├── README.md ├── config_simulation.yaml └── create_world.py ├── CONTRIBUTING ├── setup.py ├── requirements.txt └── .github └── workflows └── check.yml /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include june/configs * 2 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/schools.yaml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test_june/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=./ --cov-report xml 3 | -------------------------------------------------------------------------------- /june/activity/__init__.py: -------------------------------------------------------------------------------- 1 | from .activity_manager import ActivityManager, activity_hierarchy 2 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/InfectionXNExp.yaml: -------------------------------------------------------------------------------- 1 | transmission_type: xnexp 2 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/symptoms/SymptomsConstant.yaml: -------------------------------------------------------------------------------- 1 | recovery_rate: 0.2 2 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/care_home.yaml: -------------------------------------------------------------------------------- 1 | n_residents_per_worker: 10 2 | workers_sector: Q 3 | -------------------------------------------------------------------------------- /test_june/unit/geography/stations.csv: -------------------------------------------------------------------------------- 1 | super_area,station 2 | l1,King's Cross 3 | l2,Victoria 4 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/InfectionConstant.yaml: -------------------------------------------------------------------------------- 1 | transmission_type: constant 2 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/households.yaml: -------------------------------------------------------------------------------- 1 | households: 2 | same_sex_couple_ratio: 0.1 3 | 4 | 5 | -------------------------------------------------------------------------------- /june/configs/defaults/rail_travel.yaml: -------------------------------------------------------------------------------- 1 | London damping factor: 0.4 2 | 3 | non-London damping factor: 0.1 -------------------------------------------------------------------------------- /june/domains/__init__.py: -------------------------------------------------------------------------------- 1 | from .domain import Domain 2 | from .domain_decomposition import DomainSplitter 3 | -------------------------------------------------------------------------------- /june/tracker/__init__.py: -------------------------------------------------------------------------------- 1 | from .tracker import Tracker 2 | 3 | # from .interactive_group import InteractiveGroup 4 | -------------------------------------------------------------------------------- /june/interaction/__init__.py: -------------------------------------------------------------------------------- 1 | from .interaction import Interaction 2 | 3 | # from .interactive_group import InteractiveGroup 4 | -------------------------------------------------------------------------------- /test_june/unit/epidemiology/infection_seed/cases_per_region.csv: -------------------------------------------------------------------------------- 1 | date,age,London 2 | 2021-06-26,0-50,0.5 3 | 2021-06-26,50-100,0.2 4 | -------------------------------------------------------------------------------- /test_june/unit/epidemiology/infection_seed/exact_num_cases_world.csv: -------------------------------------------------------------------------------- 1 | date,age,all 2 | 2020-03-01,0-50,40 3 | 2020-03-01,50-100,15 4 | -------------------------------------------------------------------------------- /june/demography/__init__.py: -------------------------------------------------------------------------------- 1 | from .person import Person, Activities 2 | from .demography import Demography, Population, AgeSexGenerator 3 | -------------------------------------------------------------------------------- /test_june/unit/files/config/general.ini: -------------------------------------------------------------------------------- 1 | [epidemiology] 2 | symptoms_class = SymptomsConstant 3 | transmission_class = TransmissionConstant -------------------------------------------------------------------------------- /june/configs/defaults/distributors/hospital_distributor.yaml: -------------------------------------------------------------------------------- 1 | medic_min_age: 25 2 | patients_per_medic: 10 3 | healthcare_sector_label: "Q" 4 | -------------------------------------------------------------------------------- /june/epidemiology/__init__.py: -------------------------------------------------------------------------------- 1 | # from .epidemiology import Epidemiology 2 | from .vaccines import Vaccine, Vaccines, VaccinationCampaigns 3 | -------------------------------------------------------------------------------- /test_june/unit/epidemiology/infection_seed/exact_num_cases_per_region.csv: -------------------------------------------------------------------------------- 1 | date,age,Durham 2 | 2020-03-01,0-50,40 3 | 2020-03-01,50-100,15 4 | -------------------------------------------------------------------------------- /test_june/unit/geography/cities.csv: -------------------------------------------------------------------------------- 1 | super_area,city 2 | a1,Durham 3 | a2,Durham 4 | b1,Newcastle 5 | c1,Leeds 6 | c2,Leeds 7 | c3,Leeds 8 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # JUNE: open-source individual-based epidemiology simulation 2 | 3 | ## About 4 | See [here](about.md) for more info about JUNE. 5 | -------------------------------------------------------------------------------- /june/epidemiology/infection/health_index/__init__.py: -------------------------------------------------------------------------------- 1 | from .data_to_rates import Data2Rates 2 | from .health_index import HealthIndexGenerator 3 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/hospitals.yaml: -------------------------------------------------------------------------------- 1 | # taken from Google drive ICU beds about 10/350 2 | icu_fraction: 0.028 3 | neighbour_hospitals: 5 4 | 5 | -------------------------------------------------------------------------------- /scripts/run_june_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import june # for data 3 | 4 | from test_june import run_all_tests 5 | 6 | run_all_tests() 7 | -------------------------------------------------------------------------------- /june/records/__init__.py: -------------------------------------------------------------------------------- 1 | from .records_writer import Record 2 | from .records_writer import combine_records 3 | from .records_reader import RecordReader 4 | -------------------------------------------------------------------------------- /june/epidemiology/vaccines/__init__.py: -------------------------------------------------------------------------------- 1 | from .vaccines import Vaccine, Vaccines 2 | from .vaccination_campaign import VaccinationCampaign, VaccinationCampaigns 3 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/transmission/TransmissionConstant.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | "constant" 3 | probability: 4 | type: constant 5 | value: 0.1 6 | -------------------------------------------------------------------------------- /june/event/__init__.py: -------------------------------------------------------------------------------- 1 | from .event import Event, Events 2 | from .domestic_care import DomesticCare 3 | from .mutation import Mutation 4 | from .incidence_setter import IncidenceSetter 5 | -------------------------------------------------------------------------------- /june/groups/group/__init__.py: -------------------------------------------------------------------------------- 1 | from .abstract import AbstractGroup 2 | from .subgroup import Subgroup 3 | from .supergroup import Supergroup 4 | from .external import ExternalSubgroup, ExternalGroup 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-complexity = 54 3 | exclude = .git,.venv,__pycache__,june_plots 4 | max-line-length = 88 5 | select = C,E,B,B950 6 | extend-ignore = E203, E501, E731 7 | per-file-ignores = __init__.py:F401 -------------------------------------------------------------------------------- /test_june/__init__.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from pathlib import Path 3 | import pytest 4 | 5 | test_path = Path(__file__).parent 6 | 7 | 8 | def run_all_tests(): 9 | pytest.main(["-x", f"{test_path}"]) 10 | -------------------------------------------------------------------------------- /scripts/get_june_plotting_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | data_release="june_plotting_data_1.0.zip" 3 | wget --user=access --password=d0wn10@d$ "http://virgodb.dur.ac.uk/downloads/dc-quer1/$data_release" 4 | unzip $data_release 5 | rm $data_release 6 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/symptoms/SymptomsStep.yaml: -------------------------------------------------------------------------------- 1 | time_offset: 2 | distribution: constant 3 | parameters: 4 | value: 2 5 | end_time: 6 | distribution: constant 7 | parameters: 8 | value: 5 9 | 10 | -------------------------------------------------------------------------------- /scripts/get_westeros_tutorial_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | data_release="westeros_tutorial_data.zip" 3 | wget --user=access --password=d0wn10@d$ "http://virgodb.dur.ac.uk/downloads/dc-quer1/$data_release" 4 | unzip $data_release 5 | rm $data_release 6 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/symptoms/SymptomsGaussian.yaml: -------------------------------------------------------------------------------- 1 | mean_time: 2 | distribution: constant 3 | parameters: 4 | value: 1 5 | 6 | sigma_time: 7 | distribution: constant 8 | parameters: 9 | value: 3 10 | -------------------------------------------------------------------------------- /test_june/conftest.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from os import remove 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture(autouse=True, scope="session") 8 | def remove_log_files(): 9 | yield 10 | for file in glob("*.log*"): 11 | remove(file) 12 | -------------------------------------------------------------------------------- /scripts/get_june_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # old release data_release="june_input_data_1.0.zip" 3 | # new release 4 | data_release="data_private.zip" 5 | wget --user=access --password=d0wn10@d$ "https://june.phyip3.dur.ac.uk/downloads/$data_release" 6 | unzip $data_release 7 | rm $data_release 8 | -------------------------------------------------------------------------------- /june/groups/travel/__init__.py: -------------------------------------------------------------------------------- 1 | from .mode_of_transport import ModeOfTransport, ModeOfTransportGenerator 2 | from .travel import Travel 3 | from .transport import ( 4 | Transport, 5 | Transports, 6 | CityTransport, 7 | CityTransports, 8 | InterCityTransport, 9 | InterCityTransports, 10 | ) 11 | -------------------------------------------------------------------------------- /june/exc.py: -------------------------------------------------------------------------------- 1 | class GroupException(Exception): 2 | pass 3 | 4 | 5 | class PolicyError(BaseException): 6 | pass 7 | 8 | 9 | class HospitalError(BaseException): 10 | pass 11 | 12 | 13 | class SimulatorError(BaseException): 14 | pass 15 | 16 | 17 | class InteractionError(BaseException): 18 | pass 19 | -------------------------------------------------------------------------------- /june/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .parse_probabilities import ( 2 | parse_age_probabilities, 3 | parse_prevalence_comorbidities_in_reference_population, 4 | read_comorbidity_csv, 5 | convert_comorbidities_prevalence_to_dict, 6 | ) 7 | from .numba_random import random_choice_numba 8 | from .readers import read_date, str_to_class 9 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/symptoms/SymptomsTanh.yaml: -------------------------------------------------------------------------------- 1 | max_time: 2 | distribution: constant 3 | parameters: 4 | value: 2 5 | 6 | onset_time: 7 | distribution: constant 8 | parameters: 9 | value: 0.5 10 | 11 | end_time: 12 | distribution: constant 13 | parameters: 14 | value: 15 15 | 16 | -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | This is the offical repository of JUNE, named after [June Almeida](https://en.wikipedia.org/wiki/June_Almeida), who was the female Scottish virologist that first identified the coronavirus group of viruses. Check out the [release paper](https://www.medrxiv.org/content/10.1101/2020.12.15.20248246v1) for a physical description of the model. 2 | -------------------------------------------------------------------------------- /june/groups/cemetery.py: -------------------------------------------------------------------------------- 1 | from june.groups import Supergroup, Group 2 | 3 | 4 | class Cemetery(Group): 5 | def add(self, person): 6 | self[0].people.append(person) 7 | 8 | 9 | class Cemeteries(Supergroup): 10 | def __init__(self): 11 | super().__init__([Cemetery()]) 12 | 13 | def get_nearest(self, person): 14 | return self.members[0] 15 | -------------------------------------------------------------------------------- /june/hdf5_savers/infection_savers/__init__.py: -------------------------------------------------------------------------------- 1 | from .transmission_saver import save_transmissions_to_hdf5, load_transmissions_from_hdf5 2 | from .symptoms_saver import save_symptoms_to_hdf5, load_symptoms_from_hdf5 3 | from .infection_saver import save_infections_to_hdf5, load_infections_from_hdf5 4 | from .immunity_saver import save_immunities_to_hdf5, load_immunities_from_hdf5 5 | -------------------------------------------------------------------------------- /june/epidemiology/infection_seed/__init__.py: -------------------------------------------------------------------------------- 1 | from .observed_to_cases import Observed2Cases 2 | from .infection_seed import InfectionSeed, InfectionSeeds 3 | from .clustered_infection_seed import ClusteredInfectionSeed 4 | from .cases_distributor import CasesDistributor 5 | from .exact_num_infection_seed import ( 6 | ExactNumInfectionSeed, 7 | ExactNumClusteredInfectionSeed, 8 | ) 9 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/companies.yaml: -------------------------------------------------------------------------------- 1 | #sector_betas: 2 | # A: 1. 3 | # B: 0.9 4 | # C: 1. 5 | # D: 1. 6 | # E: 1. 7 | # F: 1. 8 | # G: 0.75 9 | # H: 0.5 10 | # I: 1. 11 | # J: 0.7 12 | # K: 0.5 13 | # L: 0.3 14 | # M: 0.3 15 | # N: 0.2 16 | # O: 0.5 17 | # P: 1. 18 | # Q: 1. 19 | # R: 0.7 20 | # S: 0.8 21 | # T: 1. 22 | # U: 1. 23 | -------------------------------------------------------------------------------- /june/utils/numba_random.py: -------------------------------------------------------------------------------- 1 | """ 2 | A few numbaised useful functions for random sampling. 3 | """ 4 | from numba import jit 5 | from random import random 6 | import numpy as np 7 | 8 | 9 | @jit(nopython=True) 10 | def random_choice_numba(arr, prob): 11 | """ 12 | Fast implementation of np.random.choice 13 | """ 14 | return arr[np.searchsorted(np.cumsum(prob), random(), side="right")] 15 | -------------------------------------------------------------------------------- /june/geography/__init__.py: -------------------------------------------------------------------------------- 1 | from .geography import ( 2 | Area, 3 | SuperArea, 4 | Areas, 5 | SuperAreas, 6 | Geography, 7 | ExternalSuperArea, 8 | Region, 9 | Regions, 10 | ) 11 | from .city import City, Cities, ExternalCity 12 | from .station import ( 13 | Station, 14 | Stations, 15 | CityStation, 16 | InterCityStation, 17 | ExternalCityStation, 18 | ExternalInterCityStation, 19 | ) 20 | -------------------------------------------------------------------------------- /june/configs/defaults/travel/city_stations.yaml: -------------------------------------------------------------------------------- 1 | number_of_inter_city_stations: # each city also acts like an station for internal commuters. 2 | "Birmingham": 4 3 | "Bristol": 4 4 | "Brighton and Hove": 4 5 | "Cambridge": 4 6 | "Cardiff": 4 7 | "Leeds": 4 8 | "Leicester": 4 9 | "Liverpool": 4 10 | "London": 8 11 | "Manchester": 4 12 | "Newcastle upon Tyne": 4 13 | "Nottingham": 4 14 | "Reading": 4 15 | "Sheffield": 4 16 | -------------------------------------------------------------------------------- /june/distributors/__init__.py: -------------------------------------------------------------------------------- 1 | from .worker_distributor import WorkerDistributor, load_sex_per_sector, load_workflow_df 2 | from .care_home_distributor import CareHomeDistributor 3 | from .company_distributor import CompanyDistributor 4 | from .household_distributor import HouseholdDistributor 5 | from .hospital_distributor import HospitalDistributor 6 | from .school_distributor import SchoolDistributor 7 | from .university_distributor import UniversityDistributor 8 | -------------------------------------------------------------------------------- /test_june/unit/epidemiology/infection/test_immunity.py: -------------------------------------------------------------------------------- 1 | from june.epidemiology.infection import Immunity 2 | 3 | 4 | class TestImmunity: 5 | def test_immunity(self): 6 | susceptibility_dict = {1: 0.3} 7 | immunity = Immunity(susceptibility_dict) 8 | assert immunity.susceptibility_dict[1] == 0.3 9 | immunity.add_immunity([123]) 10 | assert immunity.is_immune(123) is True 11 | assert immunity.susceptibility_dict[123] == 0.0 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .envr 2 | .coverage 3 | coverage.xml 4 | test_june/unit/results/ 5 | .DS_Store 6 | test_june/results/ 7 | .idea/ 8 | data/ 9 | *__pycache__* 10 | *.ipynb_checkpoints* 11 | *.pyc 12 | *.pkl 13 | *.egg-info 14 | .env 15 | cookie 16 | data.zip 17 | __MACOSX 18 | *.log 19 | *.hdf5 20 | box_results/ 21 | build/ 22 | dist/ 23 | *.prof 24 | *.svg 25 | test_results 26 | .venv 27 | **/tester_* 28 | **/results/** 29 | 30 | post_checkpoint_results/ 31 | pre_checkpoint_results 32 | -------------------------------------------------------------------------------- /test_june/unit/event/test_events.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from june.event import Event 3 | 4 | 5 | def test__event_dates(): 6 | event = Event(start_time="2020-01-05", end_time="2020-12-05") 7 | assert event.start_time.strftime("%Y-%m-%d") == "2020-01-05" 8 | assert event.end_time.strftime("%Y-%m-%d") == "2020-12-05" 9 | assert event.is_active(datetime.datetime.strptime("2020-03-05", "%Y-%m-%d")) 10 | assert not event.is_active(datetime.datetime.strptime("2030-03-05", "%Y-%m-%d")) 11 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/travel/commute.yaml: -------------------------------------------------------------------------------- 1 | # seats per passenger 2 | # data from TAble RAI0201 of the Depatment of Transport statistics (2018) 3 | seats_per_passenger: 4 | Birmingham: 1.14 5 | "Brighton and Hove": 2.34 6 | Bristol: 1.64 7 | Cambridge: 2.04 8 | Cardiff: 1.64 9 | Leeds: 1.13 10 | Leicester: 1.79 11 | Liverpool: 1.5 12 | London: 1.00 13 | Manchester: 1.37 14 | "Newcastle upon Tyne": 2.28 15 | Nottingham: 1.55 16 | Reading: 1.98 17 | Sheffield: 1.63 18 | 19 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: no 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "70...100" 8 | status: 9 | project: 10 | default: 11 | threshold: 10% 12 | 13 | parsers: 14 | gcov: 15 | branch_detection: 16 | conditional: yes 17 | loop: yes 18 | method: no 19 | macro: no 20 | 21 | comment: 22 | layout: "reach,diff,flags,tree" 23 | behavior: default 24 | require_changes: no 25 | 26 | ignore: 27 | - "june_tests/**/*" 28 | -------------------------------------------------------------------------------- /june/groups/leisure/__init__.py: -------------------------------------------------------------------------------- 1 | from .social_venue import SocialVenue, SocialVenues, SocialVenueError 2 | from .social_venue_distributor import SocialVenueDistributor 3 | from .pub import Pub, Pubs, PubDistributor 4 | from .cinema import Cinema, Cinemas, CinemaDistributor 5 | from .grocery import Groceries, Grocery, GroceryDistributor 6 | from .gym import Gym, Gyms, GymDistributor 7 | from .residence_visits import ResidenceVisitsDistributor 8 | from .leisure import Leisure, generate_leisure_for_world, generate_leisure_for_config 9 | -------------------------------------------------------------------------------- /test_june/unit/groups/leisure/test_pubs.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from june.geography import Geography 4 | from june.groups.leisure import Pubs 5 | 6 | 7 | @pytest.fixture(name="geography") 8 | def make_geography(): 9 | geography = Geography.from_file({"super_area": ["E02005103"]}) 10 | return geography 11 | 12 | 13 | class TestPubs: 14 | def test__create_pubs_in_geography(self, geography): 15 | pubs = Pubs.for_geography(geography) 16 | assert len(pubs) == 179 17 | return pubs 18 | -------------------------------------------------------------------------------- /example_scripts/README.md: -------------------------------------------------------------------------------- 1 | # Instructions 2 | 1. First run ``create_world.py`` to create a world and save it to ``.hdf5`` format. You can edit 3 | the script to create the region you want. 4 | 5 | 2. If you used the default settings, the world will now have been saved in ``tests.hdf5``. 6 | We can now run a simulation using the ``run_simulation.py`` script. If called serially, 7 | eg, ``python run_simulation.py`` it will just run in serial, it can be run in parallel doing 8 | ``mpirun -np X python run_simulation.py`` where X is the number of cores. 9 | -------------------------------------------------------------------------------- /test_june/unit/interaction/interaction_test_config.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_groups: 4 | residence: ['households'] 5 | 6 | time: 7 | total_days: 60 8 | initial_day: "2020-03-01" 9 | step_duration: 10 | weekday: 11 | 0: 12 12 | 1: 12 13 | weekend: 14 | 0: 12 15 | 1: 12 16 | step_activities: 17 | weekday: 18 | 0: ['residence'] 19 | 1: ['residence'] 20 | weekend: 21 | 0: ['residence'] 22 | 1: ['residence'] 23 | 24 | -------------------------------------------------------------------------------- /june/configs/defaults/distributors/school_distributor.yaml: -------------------------------------------------------------------------------- 1 | age_range: [0,19] # numbers correspond to age groups defined by NOMIS dataset. 2 | mandatory_age_range: [5,18] 3 | neighbour_schools: 10 4 | sector: 'P' # NOMIS name convention 5 | sub_sector: ["teacher_secondary", "teacher_primary"] 6 | # ratios source https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/183364/DFE-RR169.pdf 7 | teacher_student_ratio_primary: 21 8 | teacher_student_ratio_secondary: 16 9 | teacher_min_age: 21 10 | max_classroom_size: 40 11 | -------------------------------------------------------------------------------- /june/configs/logging.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | disable_existing_loggers: False 3 | formatters: 4 | simple: 5 | format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 6 | 7 | handlers: 8 | console: 9 | class: logging.StreamHandler 10 | level: DEBUG 11 | formatter: simple 12 | stream: ext://sys.stdout 13 | 14 | loggers: 15 | my_module: 16 | level: ERROR 17 | handlers: [console] 18 | propagate: no 19 | 20 | root: 21 | level: INFO 22 | handlers: [console] # files handler disabled as they pose problems when doing multiple runs 23 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | - We use [black](https://github.com/psf/black) for coding style. 2 | - We use flake8 to detect errors on Git push, we recommend you do this on your code before creating a pull request. 3 | - License is available in LICENSE, by submitting code you are agreeing to publish your code with that license. 4 | - Please make a pull request for any changes which you wish to add 5 | - For imports which are just for type checks, to avoid cyclic dependencies, please surround the imports with: 6 | 7 | 8 | from typing import TYPE_CHECKING 9 | if TYPE_CHECKING: 10 | import ... -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/transmission/correction_nature.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | 'gamma' 3 | max_infectiousness: 4 | type: lognormal 5 | s: 0.5 6 | loc: 0.0 7 | scale: 1. 8 | shape: 9 | type: constant 10 | value : 97.2 11 | rate: 12 | type: constant 13 | value : 3.72 14 | shift: 15 | type: constant 16 | value: -25.6 17 | asymptomatic_infectious_factor: 18 | type: constant 19 | value: 0.5 20 | mild_infectious_factor: 21 | type: constant 22 | value: 1. 23 | 24 | -------------------------------------------------------------------------------- /june/configs/tests/transmission/test_transmission_constant.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | 'gamma' 3 | max_infectiousness: 4 | type: constant 5 | value: 1. 6 | shape: 7 | type: normal 8 | loc: 1.56 9 | scale: 0.08 10 | rate: 11 | type: normal 12 | loc: 0.53 13 | scale: 0.03 14 | shift: 15 | type: normal 16 | loc: -2.12 17 | scale: 0.1 18 | asymptomatic_infectious_factor: 19 | type: constant 20 | value: 1. 21 | mild_infectious_factor: 22 | type: constant 23 | value: 1. 24 | 25 | -------------------------------------------------------------------------------- /june/configs/tests/transmission/test_transmission_symptoms.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | 'gamma' 3 | max_infectiousness: 4 | type: constant 5 | value: 1. 6 | shape: 7 | type: normal 8 | loc: 1.56 9 | scale: 0.08 10 | rate: 11 | type: normal 12 | loc: 0.53 13 | scale: 0.03 14 | shift: 15 | type: normal 16 | loc: -2.12 17 | scale: 0.1 18 | asymptomatic_infectious_factor: 19 | type: constant 20 | value: 0.29 21 | mild_infectious_factor: 22 | type: constant 23 | value: 0.48 24 | 25 | -------------------------------------------------------------------------------- /june/configs/tests/test_simulator_no_leisure.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_groups: 4 | medical_facility: ['hospitals'] 5 | primary_activity: ['schools', 'companies', 'universities'] 6 | residence: ['households', ] 7 | 8 | time: 9 | total_days: 10 10 | initial_day: '2020-03-01' 11 | step_duration: 12 | weekday: 13 | 0: 24 14 | weekend: 15 | 0: 24 16 | step_activities: 17 | weekday: 18 | 0: ['medical_facility', 'primary_activity', 'residence'] 19 | weekend: 20 | 0: ['medical_facility', 'residence'] 21 | -------------------------------------------------------------------------------- /june/configs/tests/transmission/test_transmission_lognormal.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | 'gamma' 3 | max_infectiousness: 4 | type: lognormal 5 | s: 0.5 6 | loc: 0.0 7 | scale: 1. 8 | shape: 9 | type: normal 10 | loc: 1.56 11 | scale: 0.08 12 | rate: 13 | type: normal 14 | loc: 0.53 15 | scale: 0.03 16 | shift: 17 | type: normal 18 | loc: -2.12 19 | scale: 0.1 20 | asymptomatic_infectious_factor: 21 | type: constant 22 | value: 1. 23 | mild_infectious_factor: 24 | type: constant 25 | value: 1. 26 | 27 | -------------------------------------------------------------------------------- /june/epidemiology/infection/__init__.py: -------------------------------------------------------------------------------- 1 | from .infection import Infection, Covid19, B117, B16172 2 | from .immunity import Immunity 3 | from .infection_selector import InfectionSelector, InfectionSelectors 4 | from .trajectory_maker import TrajectoryMakers 5 | from .health_index.health_index import HealthIndexGenerator 6 | from .health_index.data_to_rates import Data2Rates 7 | from .symptom_tag import SymptomTag 8 | from .symptoms import Symptoms 9 | from .transmission import Transmission, TransmissionConstant, TransmissionGamma 10 | from .transmission_xnexp import TransmissionXNExp 11 | from .immunity_setter import ImmunitySetter 12 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/transmission/b117.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | 'gamma' 3 | max_infectiousness: 4 | type: lognormal 5 | s: 0.5 6 | loc: 0.0 7 | scale: 1.7 8 | shape: 9 | type: normal 10 | loc: 1.56 11 | scale: 0.08 12 | rate: 13 | type: normal 14 | loc: 0.53 15 | scale: 0.03 16 | shift: 17 | type: normal 18 | loc: -2.12 19 | scale: 0.1 20 | asymptomatic_infectious_factor: 21 | type: constant 22 | value: 0.5 23 | mild_infectious_factor: 24 | type: constant 25 | value: 1. 26 | 27 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/transmission/nature.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | 'gamma' 3 | max_infectiousness: 4 | type: lognormal 5 | s: 0.5 6 | loc: 0.0 7 | scale: 1. 8 | shape: 9 | type: normal 10 | loc: 1.56 11 | scale: 0.08 12 | rate: 13 | type: normal 14 | loc: 0.53 15 | scale: 0.03 16 | shift: 17 | type: normal 18 | loc: -2.12 19 | scale: 0.1 20 | asymptomatic_infectious_factor: 21 | type: constant 22 | value: 0.5 23 | mild_infectious_factor: 24 | type: constant 25 | value: 1. 26 | 27 | -------------------------------------------------------------------------------- /june/configs/tests/groups/make_subgroups_test_interaction.yaml: -------------------------------------------------------------------------------- 1 | alpha_physical: 2. 2 | 3 | betas: 4 | mock_group: 0.42941 5 | 6 | 7 | contact_matrices: 8 | mock_group: 9 | contacts: [[1., 2., 3.], [4., 5., 4.], [3., 2., 1.]] 10 | proportion_physical: [[5, 4., 2.], [2, 1., 2.], [3., 4., 5.]] 11 | characteristic_time: 8 # in hours 12 | type: "Age" 13 | bins: [0,18,60,100] 14 | test_group: 15 | contacts: [[1., 2., 3.], [4., 5., 4.], [3., 2., 1.]] 16 | proportion_physical: [[5, 4., 2.], [2, 1., 2.], [3., 4., 5.]] 17 | characteristic_time: 8 # in hours 18 | type: "Age" 19 | bins: [0,18,60,100] 20 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/transmission/covid19.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | 'gamma' 3 | max_infectiousness: 4 | type: lognormal 5 | s: 0.5 6 | loc: 0.0 7 | scale: 1. 8 | shape: 9 | type: normal 10 | loc: 1.56 11 | scale: 0.08 12 | rate: 13 | type: normal 14 | loc: 0.53 15 | scale: 0.03 16 | shift: 17 | type: normal 18 | loc: -2.12 19 | scale: 0.1 20 | asymptomatic_infectious_factor: 21 | type: constant 22 | value: 0.5 23 | mild_infectious_factor: 24 | type: constant 25 | value: 1. 26 | 27 | -------------------------------------------------------------------------------- /june/data_formatting/census_data/care_homes.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from june import paths 4 | 5 | raw_path = f"{paths.data_path}/census_data/output_area/" 6 | processed_path = f"{paths.data_path}/processed/census_data/output_area/" 7 | 8 | carehome_df = pd.read_csv(raw_path / "communal_people.csv") 9 | carehome_df.set_index(carehome_df["geography"], inplace=True) 10 | 11 | carehome_df = carehome_df[[col for col in carehome_df.columns if "Care home" in col]] 12 | all_care_homes = carehome_df.sum(axis=1) 13 | print(all_care_homes) 14 | assert len(all_care_homes) == 181408 15 | all_care_homes.to_csv(processed_path / "carehomes.csv") 16 | -------------------------------------------------------------------------------- /june/configs/tests/test_checkpoint_config.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_super_groups: 4 | residence: ['households'] 5 | 6 | time: 7 | total_days: 25 8 | initial_day: "2020-03-01" 9 | step_duration: 10 | weekday: 11 | 0: 12 12 | 1: 12 13 | weekend: 14 | 0: 12 15 | 1: 12 16 | step_activities: 17 | weekday: 18 | 0: ['medical_facility', 'residence'] 19 | 1: ['medical_facility', 'residence'] 20 | weekend: 21 | 0: ['medical_facility', 'residence'] 22 | 1: ['medical_facility', 'residence'] 23 | 24 | checkpoint_save_dates: 25 | 2020-03-25 26 | -------------------------------------------------------------------------------- /june/configs/config_stochastic.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | logger: 4 | save_path: results 5 | 6 | world: 7 | zone: test # NorthEast # available are all UK regions, and EnglandWales together. 8 | 9 | time: 10 | total_days: 20 11 | step_duration: 12 | weekday: 13 | 1: 8 # first time step duration in hours 14 | 2: 16 15 | weekend: 16 | 1: 24 17 | step_active_groups: 18 | weekday: 19 | 1: ['schools'] # active groups during first time step 20 | 2: [] 21 | weekend: 22 | 1: [] 23 | 24 | infection: 25 | asymptomatic_ratio: 0.4 26 | 27 | interaction: 28 | type: stochastic 29 | 30 | -------------------------------------------------------------------------------- /june/data_formatting/seed/reformat_seed.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from june import paths 3 | 4 | raw_path = paths.data_path / "seed/" 5 | processed_path = paths.data_path / "processed/seed/" 6 | 7 | seed_df = pd.read_csv(raw_path / "Seeding_March_10days.csv", index_col=0) 8 | 9 | seed_df = seed_df.drop(columns=["Trust", "Code"]) 10 | seed_df = seed_df.rename(columns={"NHS England Region": "region"}) 11 | n_cases_region = seed_df.groupby("region").sum() 12 | n_cases_region.loc["London"] = ( 13 | n_cases_region.loc["London"] + n_cases_region.loc["London "] 14 | ) 15 | n_cases_region = n_cases_region.drop("London ") 16 | 17 | n_cases_region.to_csv(processed_path / "n_cases_region.csv") 18 | -------------------------------------------------------------------------------- /june/groups/leisure/gym.py: -------------------------------------------------------------------------------- 1 | from .social_venue import SocialVenue, SocialVenues 2 | from .social_venue_distributor import SocialVenueDistributor 3 | from june.paths import data_path, configs_path 4 | 5 | default_gym_coordinates_filename = data_path / "input/leisure/gyms_per_super_area.csv" 6 | default_config_filename = configs_path / "defaults/groups/leisure/gyms.yaml" 7 | 8 | 9 | class Gym(SocialVenue): 10 | max_size = 300 11 | pass 12 | 13 | 14 | class Gyms(SocialVenues): 15 | venue_class = Gym 16 | default_coordinates_filename = default_gym_coordinates_filename 17 | 18 | 19 | class GymDistributor(SocialVenueDistributor): 20 | default_config_filename = default_config_filename 21 | -------------------------------------------------------------------------------- /june/groups/leisure/pub.py: -------------------------------------------------------------------------------- 1 | from .social_venue import SocialVenue, SocialVenues 2 | from .social_venue_distributor import SocialVenueDistributor 3 | from june.paths import data_path, configs_path 4 | 5 | default_pub_coordinates_filename = data_path / "input/leisure/pubs_per_super_area.csv" 6 | default_config_filename = configs_path / "defaults/groups/leisure/pubs.yaml" 7 | 8 | 9 | class Pub(SocialVenue): 10 | max_size = 100 11 | pass 12 | 13 | 14 | class Pubs(SocialVenues): 15 | venue_class = Pub 16 | default_coordinates_filename = default_pub_coordinates_filename 17 | 18 | 19 | class PubDistributor(SocialVenueDistributor): 20 | default_config_filename = default_config_filename 21 | -------------------------------------------------------------------------------- /june/utils/profiler.py: -------------------------------------------------------------------------------- 1 | import cProfile 2 | from june.mpi_setup import mpi_rank, mpi_comm 3 | 4 | 5 | # a decorator for profiling 6 | def profile(filename=None, comm=mpi_comm): 7 | def prof_decorator(f): 8 | def wrap_f(*args, **kwargs): 9 | pr = cProfile.Profile() 10 | pr.enable() 11 | result = f(*args, **kwargs) 12 | pr.disable() 13 | 14 | if filename is None: 15 | pr.print_stats() 16 | else: 17 | filename_r = filename + ".{}".format(mpi_rank) 18 | pr.dump_stats(filename_r) 19 | 20 | return result 21 | 22 | return wrap_f 23 | 24 | return prof_decorator 25 | -------------------------------------------------------------------------------- /june/configs/tests/test_simulator_simple.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_groups: 4 | medical_facility: ['hospitals'] 5 | primary_activity: ['schools', 'companies', 'universities'] 6 | leisure: ['pubs','cinemas'] 7 | residence: ['households', ] 8 | 9 | time: 10 | total_days: 10 11 | initial_day: '2020-03-01' 12 | step_duration: 13 | weekday: 14 | 0: 12 15 | 1: 12 16 | weekend: 17 | 0: 24 18 | step_activities: 19 | weekday: 20 | 0: ['medical_facility', 'leisure','residence'] 21 | 1: ['medical_facility', 'primary_activity', 'residence'] 22 | weekend: 23 | 0: ['medical_facility', 'residence'] 24 | 25 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/infection/transmission/XNExp.yaml: -------------------------------------------------------------------------------- 1 | type: 2 | "xnexp" 3 | max_probability: 4 | type: lognormal 5 | s: 0.5 6 | loc: 0.0 7 | scale: 1. 8 | norm_time: 9 | type: constant 10 | value: 1. 11 | alpha: 12 | type: constant 13 | value: 1.5 14 | 15 | smearing_time_first_infectious: 16 | type: normal 17 | loc: -2. 18 | scale: 0.5 19 | smearing_peak_position: 20 | type: normal 21 | loc: -0.7 22 | scale: 0.4 23 | asymptomatic_infectious_factor: 24 | type: constant 25 | value: 0.33 26 | mild_infectious_factor: 27 | type: constant 28 | value: 0.72 29 | 30 | -------------------------------------------------------------------------------- /june/groups/leisure/cinema.py: -------------------------------------------------------------------------------- 1 | from .social_venue import SocialVenue, SocialVenues 2 | from .social_venue_distributor import SocialVenueDistributor 3 | from june.paths import data_path, configs_path 4 | 5 | default_cinemas_coordinates_filename = ( 6 | data_path / "input/leisure/cinemas_per_super_area.csv" 7 | ) 8 | default_config_filename = configs_path / "defaults/groups/leisure/cinemas.yaml" 9 | 10 | 11 | class Cinema(SocialVenue): 12 | max_size = 1000 13 | 14 | 15 | class Cinemas(SocialVenues): 16 | venue_class = Cinema 17 | default_coordinates_filename = default_cinemas_coordinates_filename 18 | 19 | 20 | class CinemaDistributor(SocialVenueDistributor): 21 | default_config_filename = default_config_filename 22 | -------------------------------------------------------------------------------- /june/groups/leisure/grocery.py: -------------------------------------------------------------------------------- 1 | from .social_venue import SocialVenue, SocialVenues 2 | from .social_venue_distributor import SocialVenueDistributor 3 | from june.paths import data_path, configs_path 4 | 5 | default_config_filename = configs_path / "defaults/groups/leisure/groceries.yaml" 6 | default_groceries_coordinates_filename = ( 7 | data_path / "input/leisure/groceries_per_super_area.csv" 8 | ) 9 | 10 | 11 | class Grocery(SocialVenue): 12 | max_size = 200 13 | 14 | 15 | class Groceries(SocialVenues): 16 | venue_class = Grocery 17 | default_coordinates_filename = default_groceries_coordinates_filename 18 | 19 | 20 | class GroceryDistributor(SocialVenueDistributor): 21 | default_config_filename = default_config_filename 22 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_carehomes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from june import paths 3 | from june.groups.care_home import CareHome 4 | 5 | default_config_file = paths.configs_path / "defaults/groups/carehome.yaml" 6 | 7 | 8 | class TestCareHome: 9 | @pytest.fixture(name="carehome") 10 | def create_carehome(self): 11 | return CareHome(n_residents=30, n_workers=8, area="asd") 12 | 13 | def test__carehome_grouptype(self, carehome): 14 | assert carehome.SubgroupType.workers == 0 15 | assert carehome.SubgroupType.residents == 1 16 | assert carehome.SubgroupType.visitors == 2 17 | assert carehome.n_residents == 30 18 | assert carehome.area == "asd" 19 | assert carehome.n_workers == 8 20 | -------------------------------------------------------------------------------- /june/groups/__init__.py: -------------------------------------------------------------------------------- 1 | from .group.group import Group 2 | from .group import AbstractGroup, Subgroup, Supergroup, ExternalSubgroup, ExternalGroup 3 | from .boundary import Boundary 4 | from .care_home import CareHome, CareHomes 5 | from .cemetery import Cemetery, Cemeteries 6 | from .company import Company, Companies, InteractiveCompany 7 | from .hospital import ( 8 | Hospital, 9 | Hospitals, 10 | MedicalFacility, 11 | MedicalFacilities, 12 | ExternalHospital, 13 | ) 14 | from .household import Household, Households, InteractiveHousehold 15 | from .school import School, Schools, InteractiveSchool 16 | from .university import University, Universities 17 | from .leisure import Pub, Pubs, Grocery, Groceries, Cinema, Cinemas, Leisure, Gym, Gyms 18 | -------------------------------------------------------------------------------- /june/configs/defaults/distributors/worker_distributor.yaml: -------------------------------------------------------------------------------- 1 | age_range: [18, 64] 2 | sub_sector_ratio: 3 | P: # education 4 | m: 0.02492507 # male 5 | f: 0.02726422 # female 6 | Q: # healthcare 7 | m: 0.02498335 8 | f: 0.02024475 9 | 10 | sub_sector_distr: 11 | P: 12 | label: ["teacher_secondary", "teacher_primary"] 13 | m: [0.72526887, 0.27473113] 14 | f: [0.40169687, 0.59830313] 15 | Q: 16 | label: ["doctor", "nurse"] 17 | m: [0.65350126, 0.34649874] 18 | f: [0.16103136, 0.83896864] 19 | 20 | # home = work from home 21 | # bind = send to random SuperArea within simulated geography 22 | non_geographical_work_location: 23 | OD0000001: "home" 24 | OD0000002: "home" 25 | OD0000003: "bind" 26 | OD0000004: "bind" 27 | -------------------------------------------------------------------------------- /june/configs/defaults/distributors/household_distributor.yaml: -------------------------------------------------------------------------------- 1 | kid_max_age : 17 2 | student_min_age : 18 3 | student_max_age : 25 4 | old_min_age : 65 5 | old_max_age : 99 6 | adult_min_age : 18 7 | adult_max_age : 64 8 | young_adult_min_age : 18 9 | young_adult_max_age : 35 10 | max_age_to_be_parent : 64 11 | max_household_size : 8 12 | allowed_household_compositions : [ 13 | "1 0 >=0 1 0", 14 | ">=2 0 >=0 1 0", 15 | "1 0 >=0 2 0", 16 | ">=2 0 >=0 2 0", 17 | "1 0 >=0 >=1 >=0", 18 | ">=2 0 >=0 >=1 >=0", 19 | "0 0 0 0 >=2", 20 | "0 >=1 0 0 0", 21 | "0 0 0 0 1", 22 | "0 0 0 0 2", 23 | "0 0 0 1 0", 24 | "0 0 0 2 0", 25 | "0 0 >=1 1 0", 26 | "0 0 >=1 2 0", 27 | "0 0 >=0 >=0 >=0", 28 | ">=0 >=0 >=0 >=0 >=0", 29 | ] 30 | ignore_orphans: False 31 | -------------------------------------------------------------------------------- /june/data_formatting/time_series/process_confirmed_cases.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from june import paths 3 | 4 | raw_path = paths.data_path / "time_series/" 5 | processed_path = paths.data_path / "processed/time_series/" 6 | 7 | confirmed_cases_df = pd.read_csv(raw_path / "coronavirus-cases_latest.csv", index_col=0) 8 | mask = confirmed_cases_df["Area type"] == "Region" 9 | confirmed_cases_df = confirmed_cases_df[mask] 10 | confirmed_cases_df = confirmed_cases_df[["Specimen date", "Daily lab-confirmed cases"]] 11 | confirmed_cases_df.reset_index(inplace=True) 12 | confirmed_cases_df = confirmed_cases_df.set_index("Specimen date") 13 | confirmed_cases_df = confirmed_cases_df.pivot( 14 | columns="Area name", values="Daily lab-confirmed cases" 15 | ) 16 | confirmed_cases_df.to_csv(processed_path / "n_confirmed_cases.csv") 17 | -------------------------------------------------------------------------------- /june/epidemiology/infection/symptom_tag.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class SymptomTag(IntEnum): 5 | """ 6 | A tag for the symptoms exhibited by a person. 7 | 8 | Higher numbers are more severe. 9 | 0 - 5 correspond to indices in the health index array. 10 | """ 11 | 12 | recovered = -3 13 | healthy = -2 14 | exposed = -1 15 | asymptomatic = 0 16 | mild = 1 17 | severe = 2 18 | hospitalised = 3 19 | intensive_care = 4 20 | dead_home = 5 21 | dead_hospital = 6 22 | dead_icu = 7 23 | 24 | @classmethod 25 | def from_string(cls, string: str) -> "SymptomTag": 26 | for item in SymptomTag: 27 | if item.name == string: 28 | return item 29 | raise AssertionError(f"{string} is not the name of a SymptomTag") 30 | -------------------------------------------------------------------------------- /june/configs/tests/test_simulator.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_groups: 4 | primary_activity: ['schools', 'companies', 'universities'] 5 | leisure: ['pubs', 'cinemas', 'groceries', 'household_visits', "care_home_visits"] 6 | residence: ['households', 'care_homes'] 7 | commute: ['city_transports', 'inter_city_transports'] 8 | medical_facility: ['hospitals'] 9 | 10 | time: 11 | total_days: 30 12 | initial_day: '2020-03-01' 13 | step_duration: 14 | weekday: 15 | 0: 12 16 | 1: 12 17 | weekend: 18 | 0: 24 19 | step_activities: 20 | weekday: 21 | 0: ['medical_facility', 'commute', 'residence'] 22 | 1: ['medical_facility', 'primary_activity', 'residence'] 23 | weekend: 24 | 0: ['medical_facility', 'leisure', 'residence'] 25 | 26 | -------------------------------------------------------------------------------- /june/data_formatting/time_series/process_hospitalisations.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from june import paths 3 | 4 | raw_path = paths.data_path / "time_series/" 5 | processed_path = paths.data_path / "processed/time_series/" 6 | 7 | hosp_df = pd.read_csv(raw_path / "COVID_output_pivot_v3.csv", sep=",", skiprows=1) 8 | 9 | hosp_df.set_index("ReportingPeriod", inplace=True) 10 | hosp_df.index = pd.to_datetime(hosp_df.index) 11 | hosp_df["covid_admissions"] = hosp_df[ 12 | ["SIT008_Total", "SIT009_Total", "SIT009_Suspected"] 13 | ].sum(axis=1) 14 | hosp_df = hosp_df.groupby([hosp_df.index, "Region_Name"]).sum() 15 | hosp_df = hosp_df[["covid_admissions"]].reset_index() 16 | hosp_df = hosp_df.pivot( 17 | index="ReportingPeriod", columns="Region_Name", values="covid_admissions" 18 | ) 19 | hosp_df.to_csv(processed_path / "hospital_admissions_region.csv") 20 | -------------------------------------------------------------------------------- /june/configs/tests/tracker/test_simulator.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_groups: 4 | primary_activity: ['schools', 'companies', 'universities'] 5 | leisure: ['pubs', 'cinemas', 'groceries', 'household_visits', "care_home_visits"] 6 | residence: ['households', 'care_homes'] 7 | commute: ['city_transports', 'inter_city_transports'] 8 | medical_facility: ['hospitals'] 9 | 10 | time: 11 | total_days: 30 12 | initial_day: '2020-03-01' 13 | step_duration: 14 | weekday: 15 | 0: 12 16 | 1: 12 17 | weekend: 18 | 0: 24 19 | step_activities: 20 | weekday: 21 | 0: ['medical_facility', 'commute', 'residence'] 22 | 1: ['medical_facility', 'primary_activity', 'residence'] 23 | weekend: 24 | 0: ['medical_facility', 'leisure', 'residence'] 25 | 26 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/travel/mode_of_transport.yaml: -------------------------------------------------------------------------------- 1 | - description: Work mainly at or from home 2 | is_public: false 3 | - description: Underground, metro, light rail, tram 4 | is_public: true 5 | - description: Train 6 | is_public: true 7 | - description: Bus, minibus or coach 8 | is_public: true 9 | - description: Taxi 10 | is_public: false 11 | - description: Motorcycle, scooter or moped 12 | is_public: false 13 | - description: Driving a car or van 14 | is_public: false 15 | - description: Passenger in a car or van 16 | is_public: false 17 | - description: Bicycle 18 | is_public: false 19 | - description: On foot 20 | is_public: false 21 | - description: Other method of travel to work 22 | is_public: false 23 | # Note : this is disabled as we only distribute this among workers. 24 | #- description: Not in employment 25 | #is_public: false 26 | -------------------------------------------------------------------------------- /june/data_formatting/census_data/n_students.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from june import paths 4 | 5 | raw_path = f"{paths.data_path}/census_data/output_area/" 6 | processed_path = f"{paths.data_path}/processed/census_data/output_area/" 7 | 8 | household_composition_people = pd.read_csv( 9 | raw_path / "household_composition_people.csv" 10 | ) 11 | 12 | household_composition_people.set_index("geography", inplace=True) 13 | household_composition_people = household_composition_people.filter( 14 | regex="All full-time students" 15 | ) 16 | 17 | household_composition_people = household_composition_people.rename( 18 | {household_composition_people.columns[0]: "n_students"}, axis=1 19 | ) 20 | 21 | household_composition_people.index.name = "output_area" 22 | 23 | print(household_composition_people) 24 | household_composition_people.to_csv(processed_path / "n_students.csv") 25 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_groups.py: -------------------------------------------------------------------------------- 1 | from june.demography.person import Person 2 | from june.groups.care_home import CareHome 3 | from june.groups.household import Household 4 | 5 | 6 | class TestGroup: 7 | def test_group_types(self): 8 | group = Household() 9 | group.add(Person.from_attributes(), group.SubgroupType.adults) 10 | assert group[group.SubgroupType.adults].size == 1 11 | 12 | def test_ids(self): 13 | household_1 = Household() 14 | household_2 = Household() 15 | care_home_1 = CareHome(None, None, None) 16 | care_home_2 = CareHome(None, None, None) 17 | 18 | assert household_2.id == household_1.id + 1 19 | assert household_1.name == f"Household_{household_1.id:05d}" 20 | 21 | assert care_home_2.id == care_home_1.id + 1 22 | assert care_home_1.name == f"CareHome_{care_home_1.id:05d}" 23 | -------------------------------------------------------------------------------- /june/__init__.py: -------------------------------------------------------------------------------- 1 | import logging.config 2 | import os 3 | 4 | import yaml 5 | 6 | from june import paths 7 | from . import demography 8 | from . import distributors 9 | from . import groups 10 | from . import interaction 11 | from . import simulator 12 | from . import activity 13 | from .demography import Person 14 | from .exc import GroupException 15 | from .time import Timer 16 | from .world import World 17 | 18 | default_logging_config_filename = paths.configs_path / "logging.yaml" 19 | 20 | if os.path.isfile(default_logging_config_filename): 21 | with open(default_logging_config_filename, "rt") as f: 22 | log_config = yaml.safe_load(f.read()) 23 | logging.config.dictConfig(log_config) 24 | else: 25 | print("The logging config file does not exist.") 26 | log_file = os.path.join("./", "world_creation.log") 27 | logging.basicConfig(filename=log_file, level=logging.DEBUG) 28 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_supergroups.py: -------------------------------------------------------------------------------- 1 | from june.groups import Supergroup 2 | from june.groups import Group 3 | from june.demography import Person 4 | from enum import IntEnum 5 | import pytest 6 | 7 | 8 | class MockSupergroup(Supergroup): 9 | def __init__(self, groups): 10 | super().__init__(groups) 11 | 12 | 13 | class MockGroup(Group): 14 | class SubgroupType(IntEnum): 15 | A = 0 16 | B = 1 17 | 18 | def __init__(self): 19 | super().__init__() 20 | 21 | 22 | @pytest.fixture(name="super_group", scope="module") 23 | def make_supergroup(): 24 | groups_list = [MockGroup() for _ in range(10)] 25 | super_group = MockSupergroup(groups_list) 26 | return super_group 27 | 28 | 29 | def test__create_supergroup(super_group): 30 | assert len(super_group) == 10 31 | assert super_group.group_type == "MockSupergroup" 32 | return super_group 33 | -------------------------------------------------------------------------------- /june/configs/defaults/event/events.yaml: -------------------------------------------------------------------------------- 1 | domestic_care: 2 | start_time: 1000-01-01 3 | end_time: 9999-01-01 4 | needs_care_probabilities: 5 | 0-65: 0 6 | 65-70: 0.3 #0.15 7 | 70-75: 0.36 #0.18 8 | 75-80: 0.38 #0.19 9 | 80-85: 0.56 #0.28 10 | 85-100: 0.72 #0.36 11 | # source : https://www.ageuk.org.uk/globalassets/age-uk/documents/reports-and-publications/reports-and-briefings/health--wellbeing/age_uk_briefing_state_of_health_and_care_of_older_people_july2019.pdf 12 | 13 | mutation: 14 | start_time: 2020-11-10 15 | end_time: 2020-11-11 16 | mutation_id: 37224668 17 | regional_probabilities: 18 | "South East": 0.16 19 | "East of England": 0.11 20 | "London": 0.11 21 | "South West" : 0.05 22 | "East Midlands" : 0.01 23 | "West Midlands" : 0.01 24 | "North West" : 0.01 25 | "Yorkshire and The Humber": 0.01 26 | "North East" : 0.01 27 | 28 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_universities.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from june.groups import University, Universities 3 | from june.geography import Area, Areas, SuperArea, SuperAreas 4 | 5 | 6 | def test__university_init(): 7 | university = University(coordinates=np.array([1, 2]), n_students_max=500) 8 | assert (university.coordinates == np.array([1, 2])).all() 9 | assert university.n_students_max == 500 10 | 11 | 12 | def test__university_for_super_areas(): 13 | super_area = SuperArea(name="durham", areas=None, coordinates=[54.768, -1.571868]) 14 | area = Area( 15 | name="durham_central", super_area=super_area, coordinates=super_area.coordinates 16 | ) 17 | areas = Areas([area]) 18 | super_area.areas = areas 19 | SuperAreas([super_area]) 20 | unis = Universities.for_areas(areas) 21 | durham_uni = unis[0] 22 | assert durham_uni.n_students_max == 19025 23 | -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/vaccines/vaccination_campaigns.yaml: -------------------------------------------------------------------------------- 1 | # Groups 1-4 Pfizer/BioNTech 2 | 1: 3 | start_time: 2020-12-01 4 | end_time: 2020-12-20 5 | days_to_next_dose: [0,14] 6 | group_by: 'residence' 7 | group_type: 'care_home' 8 | group_coverage: 0.3 9 | vaccine_type: 'Pfizer' 10 | dose_numbers: [0,1] 11 | 2: 12 | start_time: 2020-12-01 13 | end_time: 2020-12-20 14 | days_to_next_dose: [0,14] 15 | group_by: 'residence' 16 | group_type: 'care_home' 17 | group_coverage: 0.3 18 | vaccine_type: 'AstraZeneca' 19 | dose_numbers: [0,1] 20 | 21 | 3: 22 | start_time: 2021-02-25 23 | end_time: 2021-02-27 24 | days_to_next_dose: [0] 25 | group_by: 'residence' 26 | group_type: 'care_home' 27 | group_coverage: 1. 28 | vaccine_type: 'Pfizer' 29 | dose_numbers: [2] 30 | last_dose_type: ['Pfizer', 'AstraZeneca'] 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test_june/unit/distributors/test_university_distributor.py: -------------------------------------------------------------------------------- 1 | from june.distributors import UniversityDistributor 2 | from june.groups import Universities 3 | from june.geography import Geography 4 | from june.world import generate_world_from_geography 5 | 6 | import pytest 7 | 8 | 9 | @pytest.fixture(name="world") 10 | def create_world(): 11 | geography = Geography.from_file( 12 | {"super_area": ["E02004314", "E02004315", "E02004313"]} 13 | ) 14 | world = generate_world_from_geography(geography, include_households=True) 15 | return world 16 | 17 | 18 | def test__students_go_to_uni(world): 19 | universities = Universities.for_areas(world.areas) 20 | durham = universities[0] 21 | university_distributor = UniversityDistributor(universities) 22 | university_distributor.distribute_students_to_universities( 23 | areas=world.areas, people=world.people 24 | ) 25 | assert durham.n_students > 6000 26 | -------------------------------------------------------------------------------- /june/data_formatting/google_api/README.md: -------------------------------------------------------------------------------- 1 | # Distributions 2 | 3 | This directory is for run-once simulations that may need to be initialised to generate the data and distributions required. 4 | 5 | ## Google Maps API 6 | 7 | We use the Google Maps API to scrape data from across the UK for various statistics. To run this you will need an API key to run this which must be saved in a `.txt` file. 8 | 9 | ### Setting up an API key 10 | 11 | To set up an API key, go to the [Getting Started](https://developers.google.com/maps/gmp-get-started) page and follow this instructions. You will need a Places API key. 12 | 13 | 14 | ### Running over MSOAs 15 | 16 | `msoa_search.py` will run a search over all MSOAs by using the centroids of the MSOA and search over all nearby locations. Run `msoa_seach.py --help` to find out hot to run this. The search works by searching over types. Find out which types can be selected [here](https://developers.google.com/places/supported_types) -------------------------------------------------------------------------------- /june/data_formatting/census_data/n_people_in_comunal.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from june import paths 4 | 5 | raw_path = f"{paths.data_path}/census_data/output_area/" 6 | processed_path = f"{paths.data_path}/processed/census_data/output_area/" 7 | 8 | comunal = pd.read_csv(raw_path / "communal_people.csv") 9 | 10 | comunal.set_index("geography", inplace=True) 11 | all_comunal_df = comunal[[col for col in comunal.columns if "All categories" in col]] 12 | carehome_df = comunal[[col for col in comunal.columns if "Care home" in col]] 13 | carehome_df = carehome_df.sum(axis=1) 14 | comunal = all_comunal_df[all_comunal_df.columns[0]] - carehome_df 15 | 16 | assert ( 17 | comunal.sum() + carehome_df.sum() == all_comunal_df[all_comunal_df.columns[0]].sum() 18 | ) 19 | 20 | # comunal = comunal.rename( 21 | # {comunal.columns[0]: 'n_people_in_communal'}, 22 | # axis=1 23 | # ) 24 | 25 | comunal.index.name = "output_area" 26 | 27 | assert len(comunal) == 181408 28 | comunal.to_csv(processed_path / "n_people_in_communal.csv") 29 | -------------------------------------------------------------------------------- /june/utils/distances.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | 4 | earth_radius = 6371 # km 5 | 6 | 7 | def haversine_distance(origin, destination): 8 | """ 9 | Taken from https://gist.github.com/rochacbruno/2883505 10 | # Author: Wayne Dyck 11 | """ 12 | lat1, lon1 = origin 13 | lat2, lon2 = destination 14 | 15 | dlat = math.radians(lat2 - lat1) 16 | dlon = math.radians(lon2 - lon1) 17 | a = math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos( 18 | math.radians(lat1) 19 | ) * math.cos(math.radians(lat2)) * math.sin(dlon / 2) * math.sin(dlon / 2) 20 | c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) 21 | d = earth_radius * c 22 | return d 23 | 24 | 25 | def add_distance_to_lat_lon(latitude, longitude, x, y): 26 | """ 27 | Given a latitude and a longitude (in degrees), and two distances (x, y) in km, adds those distances 28 | to lat and lon 29 | """ 30 | lat2 = latitude + 180 * y / (earth_radius * np.pi) 31 | lon2 = longitude + 180 * x / (earth_radius * np.pi * np.cos(latitude)) 32 | return lat2, lon2 33 | -------------------------------------------------------------------------------- /june/policy/__init__.py: -------------------------------------------------------------------------------- 1 | from .policy import ( 2 | Policy, 3 | Policies, 4 | PolicyCollection, 5 | ) # , regional_compliance_is_active 6 | from .interaction_policies import ( 7 | InteractionPolicy, 8 | InteractionPolicies, 9 | SocialDistancing, 10 | MaskWearing, 11 | ) 12 | from .leisure_policies import ( 13 | LeisurePolicy, 14 | LeisurePolicies, 15 | CloseLeisureVenue, 16 | ChangeLeisureProbability, 17 | ChangeVisitsProbability, 18 | ) 19 | from .individual_policies import ( 20 | IndividualPolicy, 21 | IndividualPolicies, 22 | StayHome, 23 | SevereSymptomsStayHome, 24 | Quarantine, 25 | SchoolQuarantine, 26 | Shielding, 27 | CloseCompanies, 28 | CloseSchools, 29 | CloseUniversities, 30 | LimitLongCommute, 31 | ) 32 | 33 | from .medical_care_policies import ( 34 | MedicalCarePolicy, 35 | MedicalCarePolicies, 36 | Hospitalisation, 37 | ) 38 | 39 | from .regional_compliance import ( 40 | RegionalCompliance, 41 | RegionalCompliances, 42 | TieredLockdown, 43 | TieredLockdowns, 44 | ) 45 | -------------------------------------------------------------------------------- /june/groups/group/abstract.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod, ABC 2 | 3 | 4 | class AbstractGroup(ABC): 5 | """ 6 | Represents properties common to groups and subgroups. 7 | 8 | Both groups and subgroups comprise people in known states of health. 9 | """ 10 | 11 | @property 12 | @abstractmethod 13 | def susceptible(self): 14 | pass 15 | 16 | @property 17 | @abstractmethod 18 | def infected(self): 19 | pass 20 | 21 | @property 22 | @abstractmethod 23 | def recovered(self): 24 | pass 25 | 26 | @property 27 | @abstractmethod 28 | def in_hospital(self): 29 | pass 30 | 31 | @property 32 | @abstractmethod 33 | def dead(self): 34 | pass 35 | 36 | @property 37 | def size(self): 38 | return len(self.people) 39 | 40 | @property 41 | def size_susceptible(self): 42 | return len(self.susceptible) 43 | 44 | @property 45 | def size_infected(self): 46 | return len(self.infected) 47 | 48 | @property 49 | def size_recovered(self): 50 | return len(self.recovered) 51 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/leisure/cinemas.yaml: -------------------------------------------------------------------------------- 1 | times_per_week: 2 | weekday: 3 | male: 4 | 0-9: 0.0 5 | 9-15: 0.027 6 | 15-19: 0.006 7 | 19-31: 0.016 8 | 31-51: 0.009 9 | 51-66: 0.009 10 | 66-86: 0.015 11 | 86-100: 0.0 12 | female: 13 | 0-9: 0.112 14 | 9-15: 0.033 15 | 15-19: 0.099 16 | 19-31: 0.033 17 | 31-51: 0.014 18 | 51-66: 0.031 19 | 66-86: 0.008 20 | 86-100: 0.0 21 | weekend: 22 | male: 23 | 0-9: 0.019 24 | 9-15: 0.013 25 | 15-19: 0.0 26 | 19-31: 0.0 27 | 31-51: 0.009 28 | 51-66: 0.004 29 | 66-86: 0.01 30 | 86-100: 0.0 31 | female: 32 | 0-9: 0.033 33 | 9-15: 0.014 34 | 15-19: 0.014 35 | 19-31: 0.012 36 | 31-51: 0.011 37 | 51-66: 0.01 38 | 66-86: 0.008 39 | 86-100: 0.0 40 | hours_per_day: 41 | weekday: 42 | male: 43 | 0-65: 3 44 | 65-100: 11 45 | female: 46 | 0-65: 3 47 | 65-100: 11 48 | weekend: 49 | male: 50 | 0-100: 12 51 | female: 52 | 0-100: 12 53 | drags_household_probability: 0 54 | neighbours_to_consider: 5 55 | maximum_distance: 15 56 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages, Extension 3 | from setuptools.command.install import install 4 | import subprocess 5 | import os 6 | from os.path import abspath, dirname, join 7 | from glob import glob 8 | 9 | this_dir = abspath(dirname(__file__)) 10 | with open(join(this_dir, "LICENSE")) as f: 11 | license = f.read() 12 | 13 | with open(join(this_dir, "README.md"), encoding="utf-8") as file: 14 | long_description = file.read() 15 | 16 | with open(join(this_dir, "requirements.txt")) as f: 17 | requirements = f.read().split("\n") 18 | 19 | scripts = glob("scripts/*.py") + glob("scripts/*.sh") 20 | 21 | setup( 22 | name="june", 23 | version="1.2.0", 24 | description="A framework for high resolution Agent Based Modelling.", 25 | url="https://github.com/idas-durham/june", 26 | long_description_content_type="text/markdown", 27 | long_description=long_description, 28 | scripts=scripts, 29 | author="IDAS-Durham", 30 | author_email="arnauq@protonmail.com", 31 | license="GPLv3 license", 32 | install_requires=requirements, 33 | packages=find_packages(exclude=["docs"]), 34 | include_package_data=True, 35 | ) 36 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/leisure/gyms.yaml: -------------------------------------------------------------------------------- 1 | times_per_week: 2 | weekday: 3 | male: 4 | 0-9: 0.124 5 | 9-15: 0.231 6 | 15-19: 0.587 7 | 19-31: 0.402 8 | 31-51: 0.274 9 | 51-66: 0.268 10 | 66-86: 0.103 11 | 86-100: 0.019 12 | female: 13 | 0-9: 0.404 14 | 9-15: 0.367 15 | 15-19: 0.168 16 | 19-31: 0.213 17 | 31-51: 0.18 18 | 51-66: 0.184 19 | 66-86: 0.036 20 | 86-100: 0.011 21 | weekend: 22 | male: 23 | 0-9: 0.185 24 | 9-15: 0.19 25 | 15-19: 0.237 26 | 19-31: 0.145 27 | 31-51: 0.13 28 | 51-66: 0.146 29 | 66-86: 0.089 30 | 86-100: 0.0 31 | female: 32 | 0-9: 0.074 33 | 9-15: 0.132 34 | 15-19: 0.044 35 | 19-31: 0.067 36 | 31-51: 0.077 37 | 51-66: 0.041 38 | 66-86: 0.031 39 | 86-100: 0.004 40 | hours_per_day: 41 | weekday: 42 | male: 43 | 0-65: 3 44 | 65-100: 11 45 | female: 46 | 0-65: 3 47 | 65-100: 11 48 | weekend: 49 | male: 50 | 0-100: 12 51 | female: 52 | 0-100: 12 53 | drags_household_probability: 0 54 | neighbours_to_consider: 7 55 | maximum_distance: 10 56 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/leisure/pubs.yaml: -------------------------------------------------------------------------------- 1 | times_per_week: 2 | weekday: 3 | male: 4 | 0-9: 0.032 5 | 9-15: 0.106 6 | 15-19: 0.126 7 | 19-31: 0.738 8 | 31-51: 0.421 9 | 51-66: 0.533 10 | 66-86: 0.15 11 | 86-100: 0.033 12 | female: 13 | 0-9: 0.135 14 | 9-15: 0.147 15 | 15-19: 0.358 16 | 19-31: 0.544 17 | 31-51: 0.4 18 | 51-66: 0.409 19 | 66-86: 0.101 20 | 86-100: 0.02 21 | weekend: 22 | male: 23 | 0-9: 0.038 24 | 9-15: 0.101 25 | 15-19: 0.106 26 | 19-31: 0.321 27 | 31-51: 0.262 28 | 51-66: 0.304 29 | 66-86: 0.176 30 | 86-100: 0.063 31 | female: 32 | 0-9: 0.043 33 | 9-15: 0.081 34 | 15-19: 0.141 35 | 19-31: 0.251 36 | 31-51: 0.231 37 | 51-66: 0.18 38 | 66-86: 0.146 39 | 86-100: 0.06 40 | hours_per_day: 41 | weekday: 42 | male: 43 | 0-65: 3 44 | 65-100: 11 45 | female: 46 | 0-65: 3 47 | 65-100: 11 48 | weekend: 49 | male: 50 | 0-100: 12 51 | female: 52 | 0-100: 12 53 | drags_household_probability: 0 54 | neighbours_to_consider: 7 55 | maximum_distance: 10 56 | -------------------------------------------------------------------------------- /june/data_formatting/hospitals/format_trusts.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from june import paths 3 | 4 | raw_path = paths.data_path 5 | processed_path = paths.data_path / "input/hospitals/" 6 | 7 | hospitals_df = pd.read_csv(raw_path / "hospital_data/option1_trusts.csv") 8 | area_translation_df = pd.read_csv( 9 | raw_path / "census_data/area_code_translations/areas_mapping.csv" 10 | ) 11 | area_translation_df = area_translation_df[["postcode", "oa", "msoa"]] 12 | area_translation_df.set_index("postcode", inplace=True) 13 | postcodes_coord = pd.read_csv( 14 | raw_path / "geographical_data/ukpostcodes_coordinates.csv" 15 | ) 16 | postcodes_coord.set_index("postcode", inplace=True) 17 | hospitals_df = hospitals_df[ 18 | ["Code", "Regular beds", "Intensive care beds (MV+ITU+IDU)", "Postcode"] 19 | ] 20 | hospitals_df.set_index("Postcode", inplace=True) 21 | hospitals_df.columns = ["code", "beds", "icu_beds"] 22 | 23 | hospitals_df = hospitals_df.join(area_translation_df) 24 | hospitals_df.columns = ["code", "beds", "icu_beds", "area", "super_area"] 25 | hospitals_df = hospitals_df.join(postcodes_coord) 26 | hospitals_df.set_index("super_area", inplace=True) 27 | hospitals_df.to_csv(processed_path / "trusts.csv") 28 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/leisure/groceries.yaml: -------------------------------------------------------------------------------- 1 | times_per_week: 2 | weekday: 3 | male: 4 | 0-9: 0.143 5 | 9-15: 0.196 6 | 15-19: 0.215 7 | 19-31: 0.267 8 | 31-51: 0.381 9 | 51-66: 0.395 10 | 66-86: 0.196 11 | 86-100: 0.065 12 | female: 13 | 0-9: 0.236 14 | 9-15: 0.263 15 | 15-19: 0.324 16 | 19-31: 0.492 17 | 31-51: 0.62 18 | 51-66: 0.815 19 | 66-86: 0.246 20 | 86-100: 0.091 21 | weekend: 22 | male: 23 | 0-9: 0.107 24 | 9-15: 0.114 25 | 15-19: 0.111 26 | 19-31: 0.174 27 | 31-51: 0.201 28 | 51-66: 0.245 29 | 66-86: 0.175 30 | 86-100: 0.082 31 | female: 32 | 0-9: 0.111 33 | 9-15: 0.199 34 | 15-19: 0.19 35 | 19-31: 0.229 36 | 31-51: 0.266 37 | 51-66: 0.281 38 | 66-86: 0.229 39 | 86-100: 0.15 40 | hours_per_day: 41 | weekday: 42 | male: 43 | 0-65: 3 44 | 65-100: 11 45 | female: 46 | 0-65: 3 47 | 65-100: 11 48 | weekend: 49 | male: 50 | 0-100: 12 51 | female: 52 | 0-100: 12 53 | drags_household_probability: 0 54 | neighbours_to_consider: 15 55 | maximum_distance: 10 56 | -------------------------------------------------------------------------------- /june/groups/group/external.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | from june.demography.person import Person 3 | 4 | 5 | class ExternalGroup: 6 | external = True 7 | __slots__ = "spec", "id", "domain_id" 8 | 9 | def __init__(self, id, spec, domain_id): 10 | self.spec = spec 11 | self.id = id 12 | self.domain_id = domain_id 13 | 14 | def clear(self): 15 | pass 16 | 17 | def get_leisure_subgroup(self, person, subgroup_type, to_send_abroad): 18 | return ExternalSubgroup(group=self, subgroup_type=subgroup_type) 19 | 20 | 21 | class ExternalSubgroup: 22 | external = True 23 | __slots__ = ("subgroup_type", "group") 24 | """ 25 | This is a place holder group for groups that live in other domains. 26 | """ 27 | 28 | def __init__(self, group, subgroup_type): 29 | self.group = group 30 | self.subgroup_type = subgroup_type 31 | 32 | @property 33 | def group_id(self): 34 | return self.group.id 35 | 36 | @property 37 | def domain_id(self): 38 | return self.group.domain_id 39 | 40 | def clear(self): 41 | pass 42 | 43 | @property 44 | def spec(self): 45 | return self.group.spec 46 | -------------------------------------------------------------------------------- /june/utils/readers.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | import importlib 3 | import datetime 4 | 5 | 6 | def read_date(date: Union[str, datetime.datetime]) -> datetime.datetime: 7 | """ 8 | Read date in two possible formats, either string or datetime.date, both 9 | are translated into datetime.datetime to be used by the simulator 10 | 11 | Parameters 12 | ---------- 13 | date: 14 | date to translate into datetime.datetime 15 | 16 | Returns 17 | ------- 18 | date in datetime format 19 | """ 20 | if type(date) is str: 21 | return datetime.datetime.strptime(date, "%Y-%m-%d") 22 | elif isinstance(date, datetime.date): 23 | return datetime.datetime.combine(date, datetime.datetime.min.time()) 24 | else: 25 | raise TypeError("date must be a string or a datetime.date object") 26 | 27 | 28 | def str_to_class(classname, base_policy_modules=("june.policy",)): 29 | for module_name in base_policy_modules: 30 | try: 31 | module = importlib.import_module(module_name) 32 | return getattr(module, classname) 33 | except AttributeError: 34 | continue 35 | raise ValueError(f"Cannot find policy {classname} in paths!") 36 | -------------------------------------------------------------------------------- /june/hdf5_savers/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def read_dataset(dataset, index1=None, index2=None): 5 | if index1 is None: 6 | index1 = 0 7 | if index2 is None: 8 | index2 = dataset.len() 9 | dataset_shape = dataset.shape 10 | if len(dataset_shape) > 1: 11 | load_shape = [index2 - index1] + list(dataset_shape[1:]) 12 | else: 13 | load_shape = index2 - index1 14 | ret = np.empty(load_shape, dtype=dataset.dtype) 15 | dataset.read_direct(ret, np.s_[index1:index2], np.s_[0 : index2 - index1]) 16 | return ret 17 | 18 | 19 | def write_dataset(group, dataset_name, data, index1=None, index2=None): 20 | if dataset_name not in group: 21 | if len(data.shape) > 1: 22 | maxshape = (None, *data.shape[1:]) 23 | else: 24 | maxshape = (None,) 25 | group.create_dataset(dataset_name, data=data, maxshape=maxshape) 26 | else: 27 | if len(data.shape) > 1: 28 | newshape = (group[dataset_name].shape[0] + data.shape[0], *data.shape[1:]) 29 | else: 30 | newshape = (group[dataset_name].shape[0] + data.shape[0],) 31 | group[dataset_name].resize(newshape) 32 | group[dataset_name][index1:index2] = data 33 | -------------------------------------------------------------------------------- /june/configs/tests/tracker/tracker_test_config.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_super_groups: 4 | medical_facility: ["hospitals"] 5 | primary_activity: ['schools', 'companies', "universities"] 6 | leisure: ['pubs', 'cinemas', 'groceries','household_visits', "care_home_visits"] 7 | residence: ['households', 'care_homes'] 8 | commute: ['city_transports', 'inter_city_transports'] 9 | 10 | time: 11 | total_days: 3 12 | initial_day: "2020-03-01" 13 | step_duration: 14 | weekday: 15 | 0: 4 16 | 1: 4 17 | 2: 4 18 | 3: 12 19 | weekend: 20 | 0: 4 21 | 1: 4 22 | 2: 4 23 | 3: 12 24 | step_activities: 25 | weekday: 26 | 0: ['medical_facility', 'leisure', 'residence'] 27 | 1: ['medical_facility', 'leisure', 'residence'] 28 | 2: ['medical_facility', 'leisure', 'residence'] 29 | 3: ['medical_facility', 'residence'] 30 | weekend: 31 | 0: ['medical_facility', 'leisure', 'residence'] 32 | 1: ['medical_facility', 'leisure', 'residence'] 33 | 2: ['medical_facility', 'leisure', 'residence'] 34 | 3: ['medical_facility', 'residence'] 35 | 36 | checkpoint_save_dates: 37 | 2020-03-15 38 | -------------------------------------------------------------------------------- /june/records/helper_records_writer.py: -------------------------------------------------------------------------------- 1 | import tables 2 | 3 | 4 | def _get_description_for_event( 5 | int_names, 6 | float_names, 7 | str_names, 8 | int_size=32, 9 | float_size=32, 10 | str_size=20, 11 | timestamp=True, 12 | ): 13 | int_constructor = tables.Int64Col 14 | if int_size == 32: 15 | int_constructor = tables.Int32Col 16 | elif int_size not in (32, 64): 17 | raise "int_size must be left unspecified, or should equal 32 or 64" 18 | float_constructor = tables.Float32Col 19 | if float_size == 64: 20 | float_constructor = tables.Float64Col 21 | elif float_size not in (32, 64): 22 | raise "float_size must be left unspecified, or should equal 32 or 64" 23 | str_constructor = tables.StringCol 24 | description = {} 25 | pos = 0 26 | if timestamp: 27 | description["timestamp"] = tables.StringCol(itemsize=10, pos=pos) 28 | pos += 1 29 | for n in int_names: 30 | description[n] = int_constructor(pos=pos) 31 | pos += 1 32 | for n in float_names: 33 | description[n] = float_constructor(pos=pos) 34 | pos += 1 35 | for n in str_names: 36 | description[n] = str_constructor(itemsize=str_size, pos=pos) 37 | pos += 1 38 | return description 39 | -------------------------------------------------------------------------------- /test_june/unit/epidemiology/infection/test_data_to_rates.py: -------------------------------------------------------------------------------- 1 | from june import paths 2 | from june.epidemiology.infection.health_index.data_to_rates import ( 3 | read_comorbidity_csv, 4 | convert_comorbidities_prevalence_to_dict, 5 | ) 6 | import pytest 7 | 8 | 9 | def test__parse_comorbidity_prevalence(): 10 | male_filename = paths.data_path / "input/demography/uk_male_comorbidities.csv" 11 | female_filename = paths.data_path / "input/demography/uk_female_comorbidities.csv" 12 | prevalence_female = read_comorbidity_csv(female_filename) 13 | prevalence_male = read_comorbidity_csv(male_filename) 14 | for value in prevalence_female.sum(axis=1): 15 | assert value == pytest.approx(1.0) 16 | for value in prevalence_male.sum(axis=1): 17 | assert value == pytest.approx(1.0) 18 | 19 | prevalence_dict = convert_comorbidities_prevalence_to_dict( 20 | prevalence_female, prevalence_male 21 | ) 22 | assert prevalence_dict["sickle_cell"]["m"]["0-4"] == pytest.approx( 23 | 3.92152e-05, rel=0.2 24 | ) 25 | assert prevalence_dict["tuberculosis"]["f"]["4-9"] == pytest.approx( 26 | 5.99818e-05, rel=0.2 27 | ) 28 | assert prevalence_dict["tuberculosis"]["f"]["4-9"] == pytest.approx( 29 | 5.99818e-05, rel=0.2 30 | ) 31 | -------------------------------------------------------------------------------- /test_june/unit/groups/leisure/test_social_venues.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from june.groups.leisure import SocialVenues 3 | from june.geography import Geography 4 | 5 | 6 | def test__social_venue_from_coordinates(): 7 | super_areas = ["E02004935", "E02004940"] 8 | geo = Geography.from_file({"super_area": super_areas}) 9 | coordinate_list = np.array([[51.752179, -0.334667], [51.741485, -0.336645]]) 10 | social_venues = SocialVenues.from_coordinates( 11 | coordinate_list, super_areas=geo.super_areas 12 | ) 13 | social_venues.add_to_areas(geo.areas) 14 | assert len(social_venues) == 2 15 | assert social_venues[0].super_area == geo.super_areas[0] 16 | assert social_venues[1].super_area == geo.super_areas[1] 17 | 18 | 19 | def test__get_closest_venues(): 20 | coordinate_list = np.array([[51.752179, -0.334667], [51.741485, -0.336645]]) 21 | 22 | social_venues = SocialVenues.from_coordinates(coordinate_list, super_areas=None) 23 | social_venues.make_tree() 24 | venue = social_venues.get_closest_venues([50, 0])[0] 25 | assert venue == social_venues[1] 26 | 27 | venues_in_radius = social_venues.get_venues_in_radius([51.7, -0.33], 10) 28 | assert venues_in_radius[0] == social_venues[1] 29 | assert venues_in_radius[1] == social_venues[0] 30 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_households.py: -------------------------------------------------------------------------------- 1 | from june.groups import Household, Households 2 | from june.demography import Person 3 | 4 | 5 | def test__households_adding(): 6 | household = Household() 7 | household2 = Household() 8 | household3 = Household() 9 | households1 = Households([household]) 10 | households2 = Households([household2, household3]) 11 | households3 = households1 + households2 12 | assert households3.members == [household, household2, household3] 13 | 14 | 15 | def test__household_mates(): 16 | 17 | house = Household() 18 | person1 = Person.from_attributes() 19 | house.add(person1, subgroup_type=house.SubgroupType.kids) 20 | assert house.residents[0] == person1 21 | person2 = Person.from_attributes() 22 | person3 = Person.from_attributes() 23 | house.add(person2) 24 | house.add(person3) 25 | assert person1 in person1.housemates 26 | assert person2 in person1.housemates 27 | assert person3 in person1.housemates 28 | 29 | 30 | def test__being_visited_flag(): 31 | house = Household() 32 | person = Person.from_attributes() 33 | assert not house.being_visited 34 | house.add(person, activity="leisure") 35 | assert house.being_visited 36 | house.being_visited = False 37 | house.add(person) 38 | assert not house.being_visited 39 | -------------------------------------------------------------------------------- /june/configs/defaults/groups/leisure/visits.yaml: -------------------------------------------------------------------------------- 1 | times_per_week: 2 | weekday: 3 | male: 4 | 0-9: 2.312 5 | 9-15: 1.715 6 | 15-19: 1.62 7 | 19-31: 1.382 8 | 31-51: 0.629 9 | 51-66: 0.819 10 | 66-86: 0.799 11 | 86-100: 0.473 12 | female: 13 | 0-9: 2.331 14 | 9-15: 1.598 15 | 15-19: 2.618 16 | 19-31: 1.37 17 | 31-51: 0.73 18 | 51-66: 1.095 19 | 66-86: 1.113 20 | 86-100: 0.323 21 | weekend: 22 | male: 23 | 0-9: 1.124 24 | 9-15: 1.107 25 | 15-19: 1.01 26 | 19-31: 0.875 27 | 31-51: 0.444 28 | 51-66: 0.456 29 | 66-86: 0.366 30 | 86-100: 0.149 31 | female: 32 | 0-9: 1.485 33 | 9-15: 1.314 34 | 15-19: 1.215 35 | 19-31: 1.036 36 | 31-51: 0.518 37 | 51-66: 0.517 38 | 66-86: 0.447 39 | 86-100: 0.202 40 | hours_per_day: 41 | weekday: 42 | male: 43 | 0-65: 3 44 | 65-100: 3 45 | female: 46 | 0-65: 3 47 | 65-100: 3 48 | weekend: 49 | male: 50 | 0-100: 12 51 | female: 52 | 0-100: 12 53 | drags_household_probability: 0 54 | residence_type_probabilities: 55 | # if a person can visit both, 56 | # with what probability will 57 | # that person visit one or the 58 | # other. 59 | household: 0.66 60 | care_home: 0.34 61 | -------------------------------------------------------------------------------- /example_scripts/config_simulation.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_super_groups: 4 | medical_facility: ["hospitals"] 5 | primary_activity: ['schools', 'companies', "universities"] 6 | leisure: ['pubs', 'cinemas', 'gyms','groceries', "care_home_visits", "household_visits"] 7 | residence: ['households', 'care_homes'] 8 | commute: ['city_transports', 'inter_city_transports'] 9 | 10 | time: 11 | total_days: 100 12 | initial_day: "2020-02-28" 13 | step_duration: 14 | weekday: 15 | 0: 1 16 | 1: 8 17 | 2: 1 18 | 3: 3 19 | 4: 11 20 | weekend: 21 | 0: 4 22 | 1: 4 23 | 2: 4 24 | 3: 12 25 | step_activities: 26 | weekday: 27 | 0: ['medical_facility', 'commute', 'residence'] 28 | 1: ['medical_facility', 'primary_activity', 'leisure', 'residence'] 29 | 2: ['medical_facility', 'commute', 'residence'] 30 | 3: ['medical_facility', 'leisure', 'residence'] 31 | 4: ['medical_facility', 'residence'] 32 | weekend: 33 | 0: ['medical_facility', 'leisure', 'residence'] 34 | 1: ['medical_facility', 'leisure', 'residence'] 35 | 2: ['medical_facility', 'leisure', 'residence'] 36 | 3: ['medical_facility', 'residence'] 37 | 38 | checkpoint_dates: 39 | 2020-04-01 40 | -------------------------------------------------------------------------------- /june/configs/config_example.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_super_groups: 4 | medical_facility: ["hospitals"] 5 | primary_activity: ['schools', 'companies', "universities"] 6 | leisure: ['pubs', 'cinemas', 'groceries', 'gyms', "care_home_visits", "household_visits"] 7 | residence: ['households', 'care_homes'] 8 | commute: ['city_transports', 'inter_city_transports'] 9 | 10 | time: 11 | total_days: 5 12 | initial_day: "2020-03-01 9:00" 13 | step_duration: 14 | weekday: 15 | 0: 1 16 | 1: 8 17 | 2: 1 18 | 3: 3 19 | 4: 11 20 | weekend: 21 | 0: 4 22 | 1: 4 23 | 2: 4 24 | 3: 12 25 | step_activities: 26 | weekday: 27 | 0: ['medical_facility', 'residence', 'commute'] 28 | 1: ['medical_facility', 'primary_activity', 'leisure', 'residence'] 29 | 2: ['medical_facility', 'residence', 'commute'] 30 | 3: ['medical_facility', 'leisure', 'residence'] 31 | 4: ['medical_facility', 'residence'] 32 | weekend: 33 | 0: ['medical_facility', 'leisure', 'residence'] 34 | 1: ['medical_facility', 'leisure', 'residence'] 35 | 2: ['medical_facility', 'leisure', 'residence'] 36 | 3: ['medical_facility', 'residence'] 37 | 38 | checkpoint_save_dates: 39 | 2020-03-15 40 | -------------------------------------------------------------------------------- /june/configs/config_example_7.yaml: -------------------------------------------------------------------------------- 1 | title: Covid configuration example 2 | 3 | activity_to_super_groups: 4 | medical_facility: ["hospitals"] 5 | primary_activity: ['schools', 'companies', "universities"] 6 | leisure: ['pubs', 'cinemas', 'groceries', 'gyms', "care_home_visits", "household_visits"] 7 | residence: ['households', 'care_homes'] 8 | commute: ['city_transports', 'inter_city_transports'] 9 | 10 | time: 11 | total_days: 7 12 | initial_day: "2020-03-01 9:00" 13 | step_duration: 14 | weekday: 15 | 0: 1 16 | 1: 8 17 | 2: 1 18 | 3: 3 19 | 4: 11 20 | weekend: 21 | 0: 4 22 | 1: 4 23 | 2: 4 24 | 3: 12 25 | step_activities: 26 | weekday: 27 | 0: ['medical_facility', 'residence', 'commute'] 28 | 1: ['medical_facility', 'primary_activity', 'leisure', 'residence'] 29 | 2: ['medical_facility', 'residence', 'commute'] 30 | 3: ['medical_facility', 'leisure', 'residence'] 31 | 4: ['medical_facility', 'residence'] 32 | weekend: 33 | 0: ['medical_facility', 'leisure', 'residence'] 34 | 1: ['medical_facility', 'leisure', 'residence'] 35 | 2: ['medical_facility', 'leisure', 'residence'] 36 | 3: ['medical_facility', 'residence'] 37 | 38 | checkpoint_save_dates: 39 | 2020-03-15 40 | -------------------------------------------------------------------------------- /june/groups/travel/transport.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | from typing import List 3 | 4 | from june.groups import Group, Supergroup 5 | 6 | 7 | class Transport(Group): 8 | """ 9 | A class representing a transport unit. 10 | """ 11 | 12 | # class SubgroupType(IntEnum): 13 | # passengers = 0 14 | 15 | def __init__(self, station): 16 | super().__init__() 17 | self.station = station 18 | 19 | @property 20 | def area(self): 21 | return self.station.super_area.areas[0] 22 | 23 | @property 24 | def super_area(self): 25 | return self.station.super_area 26 | 27 | @property 28 | def coordinates(self): 29 | return self.area.coordinates 30 | 31 | 32 | class Transports(Supergroup): 33 | """ 34 | A collection of transport units. 35 | """ 36 | 37 | def __init__(self, transports: List[Transport]): 38 | super().__init__(transports) 39 | 40 | 41 | class CityTransport(Transport): 42 | """ 43 | Inner city transport 44 | """ 45 | 46 | 47 | class CityTransports(Transports): 48 | 49 | """ 50 | Inner city transports 51 | """ 52 | 53 | venue_class = CityTransport 54 | 55 | 56 | class InterCityTransport(Transport): 57 | """ 58 | Transport between cities. 59 | """ 60 | 61 | 62 | class InterCityTransports(Transports): 63 | """ 64 | Inter city transports 65 | """ 66 | 67 | venue_class = InterCityTransport 68 | -------------------------------------------------------------------------------- /test_june/unit/test_world.py: -------------------------------------------------------------------------------- 1 | from june.geography import Geography 2 | from june.world import generate_world_from_geography 3 | from june.groups import Schools, Hospitals, Companies, Households, Cemeteries, CareHomes 4 | 5 | 6 | def test__onearea_world(geography): 7 | geography = Geography.from_file(filter_key={"area": ["E00088544"]}) 8 | world = generate_world_from_geography(geography) 9 | assert hasattr(world, "households") 10 | assert isinstance(world.households, Households) 11 | assert len(world.areas) == 1 12 | assert len(world.super_areas) == 1 13 | assert world.super_areas.members[0].name == "E02003616" 14 | assert len(world.areas.members[0].people) == 362 15 | assert len(world.households) <= 148 16 | 17 | 18 | def test__world_has_everything(world): 19 | assert isinstance(world.schools, Schools) 20 | assert isinstance(world.cemeteries, Cemeteries) 21 | assert isinstance(world.companies, Companies) 22 | assert isinstance(world.households, Households) 23 | assert isinstance(world.hospitals, Hospitals) 24 | assert isinstance(world.care_homes, CareHomes) 25 | assert isinstance(world.companies, Companies) 26 | 27 | 28 | def test__people_in_world_right_subgroups(world): 29 | dummy_people = world.people.members[:40] 30 | 31 | for dummy_person in dummy_people: 32 | for subgroup in dummy_person.subgroups.iter(): 33 | if subgroup is not None: 34 | assert dummy_person in subgroup.people 35 | -------------------------------------------------------------------------------- /test_june/unit/policy/test_policy.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | from june.geography import Geography 7 | from june.interaction import Interaction 8 | from june.epidemiology.infection.infection_selector import InfectionSelector 9 | from june.policy import Policy 10 | 11 | 12 | path_pwd = Path(__file__) 13 | dir_pwd = path_pwd.parent 14 | constant_config = ( 15 | dir_pwd.parent.parent.parent / "configs/defaults/infection/InfectionXNExp.yaml" 16 | ) 17 | 18 | 19 | @pytest.fixture(name="selector", scope="module") 20 | def create_selector(): 21 | selector = InfectionSelector.from_file(config_filename=constant_config) 22 | selector.recovery_rate = 0.05 23 | selector.transmission_probability = 0.7 24 | return selector 25 | 26 | 27 | @pytest.fixture(name="interaction", scope="module") 28 | def create_interaction(): 29 | interaction = Interaction.from_file() 30 | return interaction 31 | 32 | 33 | @pytest.fixture(name="super_area", scope="module") 34 | def create_geography(): 35 | g = Geography.from_file(filter_key={"super_area": ["E02002559"]}) 36 | return g.super_areas.members[0] 37 | 38 | 39 | class TestPolicy: 40 | def test__is_active(self): 41 | policy = Policy(start_time="2020-5-6", end_time="2020-6-6") 42 | assert policy.is_active(datetime(2020, 5, 6)) 43 | assert policy.is_active(datetime(2020, 6, 5)) 44 | assert not policy.is_active(datetime(2020, 6, 6)) 45 | -------------------------------------------------------------------------------- /june/domains/domain.py: -------------------------------------------------------------------------------- 1 | from itertools import count 2 | 3 | from june.hdf5_savers import generate_domain_from_hdf5 4 | 5 | 6 | class Domain: 7 | """ 8 | The idea is that the world is divided in domains, which are just collections of super areas with 9 | people living/working/doing leisure in them. 10 | 11 | If we think as domains as sets, then world is the union of all domains, and each domain can have 12 | a non-zero intersection with other domains (some people can work and live in different domains). 13 | 14 | Domains are sent to MPI core to perfom calculation, and communcation between the processes is 15 | required to transfer the infection status of people. 16 | """ 17 | 18 | _id = count() 19 | 20 | def __init__(self, id: int = None): 21 | if id is None: 22 | self.id = next(self._id) 23 | self.id = id 24 | 25 | def __iter__(self): 26 | return iter(self.super_areas) 27 | 28 | @classmethod 29 | def from_hdf5( 30 | cls, 31 | domain_id, 32 | super_areas_to_domain_dict: dict, 33 | hdf5_file_path: str, 34 | interaction_config: str = None, 35 | ): 36 | domain = generate_domain_from_hdf5( 37 | domain_id=domain_id, 38 | super_areas_to_domain_dict=super_areas_to_domain_dict, 39 | file_path=hdf5_file_path, 40 | interaction_config=interaction_config, 41 | ) 42 | domain.id = domain_id 43 | return domain 44 | -------------------------------------------------------------------------------- /june/configs/defaults/demography/ethnicity_encodings.yaml: -------------------------------------------------------------------------------- 1 | ## these are taken directly from https://www.nomisweb.co.uk/census/2011/lc2101ew, 2 | ## encodings [letter][number] allow for person.ethnicity[0] to get the "broad" groupings. 3 | 4 | # All categories: Ethnic group 5 | # White: Total 6 | # English/Welsh/Scottish/Northern Irish/British 7 | # Irish 8 | # Gypsy or Irish Traveller 9 | # Other White 10 | # Mixed/multiple ethnic group: Total 11 | # White and Black Caribbean 12 | # White and Black African 13 | # White and Asian 14 | # Other Mixed 15 | # Asian/Asian British: Total 16 | # Indian 17 | # Pakistani 18 | # Bangladeshi 19 | # Chinese 20 | # Other Asian 21 | # Black/African/Caribbean/Black British: Total 22 | # African 23 | # Caribbean 24 | # Other Black 25 | # Other ethnic group: Total 26 | # Arab 27 | # Any other ethnic group 28 | 29 | A: 30 | broad: "White" 31 | 1: "English/Welsh/Scottish/Northern Irish/British" 32 | 2: "Irish" 33 | 3: "Gypsy or Irish Traveller" 34 | 4: "Other White" 35 | B: 36 | broad: Mixed/multiple ethnic group 37 | 1: "White and Black Caribbean" 38 | 2: "White and Black African" 39 | 3: "White and Asian" 40 | 4: "Other Mixed" 41 | C: 42 | broad: "Asian/Asian British" 43 | 1: "Indian" 44 | 2: "Pakistani" 45 | 3: "Bangladeshi" 46 | 4: "Chinese" 47 | 5: "Other Asian" 48 | D: 49 | broad: "Black/African/Caribbean/Black British" 50 | 1: "African" 51 | 2: "Caribbean" 52 | 3: "Other Black" 53 | E: 54 | broad: "Other ethnic group" 55 | 1: "Arab" 56 | 2: "Any other ethnic group" 57 | -------------------------------------------------------------------------------- /test_june/unit/geography/test_city.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from june.geography import City, Cities, SuperArea, SuperAreas 4 | 5 | city_test_file = Path(__file__).parent / "cities.csv" 6 | 7 | 8 | class TestCity: 9 | def test__city_setup(self): 10 | city = City(name="Durham", super_areas=["A1", "A2"]) 11 | assert city.name == "Durham" 12 | assert city.super_areas == ["A1", "A2"] 13 | 14 | def test__city_setup_from_file(self): 15 | city = City.from_file(name="Durham", city_super_areas_filename=city_test_file) 16 | assert list(city.super_areas) == ["a1", "a2"] 17 | city = City.from_file( 18 | name="Newcastle", city_super_areas_filename=city_test_file 19 | ) 20 | assert list(city.super_areas) == ["b1"] 21 | city = City.from_file(name="Leeds", city_super_areas_filename=city_test_file) 22 | assert list(city.super_areas) == ["c1", "c2", "c3"] 23 | 24 | def test__cities_for_super_areas(self): 25 | super_areas = SuperAreas( 26 | [ 27 | SuperArea(name="c1", coordinates=[1, 2]), 28 | SuperArea(name="c2", coordinates=[3, 4]), 29 | ] 30 | ) 31 | cities = Cities.for_super_areas( 32 | super_areas, city_super_areas_filename=city_test_file 33 | ) 34 | assert cities[0].name == "Leeds" 35 | assert super_areas[0].city == cities[0] 36 | assert super_areas[1].city == cities[0] 37 | assert list(cities[0].super_areas) == ["c1", "c2"] 38 | -------------------------------------------------------------------------------- /june/epidemiology/infection/immunity.py: -------------------------------------------------------------------------------- 1 | class Immunity: 2 | """ 3 | This class stores the "medical record" of the person, 4 | indicating which infections the person has recovered from. 5 | """ 6 | 7 | __slots__ = "susceptibility_dict", "effective_multiplier_dict" 8 | 9 | def __init__( 10 | self, susceptibility_dict: dict = None, effective_multiplier_dict: dict = None 11 | ): 12 | if susceptibility_dict: 13 | self.susceptibility_dict = susceptibility_dict 14 | else: 15 | self.susceptibility_dict = {} 16 | if effective_multiplier_dict: 17 | self.effective_multiplier_dict = effective_multiplier_dict 18 | else: 19 | self.effective_multiplier_dict = {} 20 | 21 | def add_immunity(self, infection_ids): 22 | for infection_id in infection_ids: 23 | self.susceptibility_dict[infection_id] = 0.0 24 | 25 | def add_multiplier(self, infection_id, multiplier): 26 | self.effective_multiplier_dict[infection_id] = multiplier 27 | 28 | def get_susceptibility(self, infection_id): 29 | return self.susceptibility_dict.get(infection_id, 1.0) 30 | 31 | def get_effective_multiplier(self, infection_id): 32 | return self.effective_multiplier_dict.get(infection_id, 1.0) 33 | 34 | def serialize(self): 35 | return ( 36 | list(self.susceptibility_dict.keys()), 37 | list(self.susceptibility_dict.values()), 38 | ) 39 | 40 | def is_immune(self, infection_id): 41 | return self.susceptibility_dict.get(infection_id, 1.0) == 0.0 42 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | affine==2.4.0 2 | anyio==4.8.0 3 | attrs==25.3.0 4 | blosc2==3.0.0 5 | certifi==2024.12.14 6 | charset-normalizer==3.4.1 7 | click==8.1.8 8 | click-plugins==1.1.1 9 | cligj==0.7.2 10 | colorama==0.4.6 11 | contextily==1.6.2 12 | contourpy==1.3.1 13 | coverage==7.6.10 14 | cycler==0.12.1 15 | et_xmlfile==2.0.0 16 | fonttools==4.55.6 17 | geographiclib==2.0 18 | geopandas==1.0.1 19 | geopy==2.4.1 20 | h11==0.14.0 21 | h5py==3.12.1 22 | httpcore==1.0.7 23 | httpx==0.28.1 24 | idna==3.10 25 | iniconfig==2.0.0 26 | joblib==1.4.2 27 | kiwisolver==1.4.8 28 | llvmlite==0.44.0 29 | matplotlib==3.10.0 30 | mercantile==1.2.1 31 | mpi4py==4.1.0 32 | msgpack==1.1.0 33 | munkres==1.1.4 34 | ndindex==1.9.2 35 | networkx==3.4.2 36 | numba==0.61.0 37 | numexpr==2.10.2 38 | numpy==2.1.3 39 | openpyxl==3.1.5 40 | packaging==24.2 41 | pandas==2.2.3 42 | pillow==11.1.0 43 | plotly==5.24.1 44 | pluggy==1.5.0 45 | py-cpuinfo==9.0.0 46 | pyogrio==0.10.0 47 | pyparsing==3.2.1 48 | pyproj==3.7.1 49 | pytest==8.3.4 50 | pytest-cov==6.0.0 51 | hypothesis==6.135.14 52 | python-dateutil==2.9.0.post0 53 | pytz==2024.2 54 | PyYAML==6.0.2 55 | rasterio==1.4.3 56 | recordclass==0.22.1 57 | requests==2.32.3 58 | responses==0.25.6 59 | rtree==1.4.0 60 | scikit-learn==1.6.1 61 | scipy==1.15.1 62 | score-clustering==0.1.4 63 | seaborn==0.13.2 64 | setuptools==75.1.0 65 | shapely==2.0.7 66 | six==1.17.0 67 | sniffio==1.3.1 68 | sortedcontainers==2.4.0 69 | tables==3.10.2 70 | tenacity==9.0.0 71 | threadpoolctl==3.5.0 72 | tornado==6.4.2 73 | tqdm==4.67.1 74 | typing_extensions==4.12.2 75 | tzdata==2025.1 76 | unicodedata2==16.0.0 77 | urllib3==2.3.0 78 | wheel==0.44.0 79 | xyzservices==2025.1.0 80 | -------------------------------------------------------------------------------- /test_june/unit/files/config/json_priors/june.json: -------------------------------------------------------------------------------- 1 | { 2 | "infection": { 3 | "symptoms": { 4 | "SymptomsConstant": { 5 | "health_index": { 6 | "type": "Uniform", 7 | "lower_limit": 0.0, 8 | "upper_limit": 1.0, 9 | "width_modifier": { 10 | "type": "Absolute", 11 | "value": 0.2 12 | }, 13 | "gaussian_limits": { 14 | "lower": 0.0, 15 | "upper": 1.0 16 | } 17 | }, 18 | "recovery_rate": { 19 | "type": "Uniform", 20 | "lower_limit": 0.0, 21 | "upper_limit": 1.0, 22 | "width_modifier": { 23 | "type": "Absolute", 24 | "value": 0.2 25 | }, 26 | "gaussian_limits": { 27 | "lower": 0.0, 28 | "upper": 1.0 29 | } 30 | } 31 | } 32 | }, 33 | "transmission": { 34 | "TransmissionConstant.probability": { 35 | "type": "Uniform", 36 | "lower_limit": 0.0, 37 | "upper_limit": 1.0, 38 | "width_modifier": { 39 | "type": "Absolute", 40 | "value": 0.2 41 | }, 42 | "gaussian_limits": { 43 | "lower": 0.0, 44 | "upper": 1.0 45 | } 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /june/configs/defaults/epidemiology/vaccines/vaccines.yaml: -------------------------------------------------------------------------------- 1 | Pfizer: 2 | days_administered_to_effective: [5,7,5] 3 | days_effective_to_waning: [1,1,1] 4 | days_waning: [1,1,1] 5 | waning_factor: 1. 6 | sterilisation_efficacies: 7 | - Covid19: 8 | 0-100: 0.52 9 | - Covid19: 10 | 0-100: 0.95 11 | - Covid19: 12 | 0-100: 0.98 13 | symptomatic_efficacies: 14 | - Covid19: 15 | 0-100: 0.52 16 | - Covid19: 17 | 0-100: 0.95 18 | - Covid19: 19 | 0-100: 0.98 20 | 21 | AstraZeneca: 22 | days_administered_to_effective: [5,7,5] 23 | days_effective_to_waning: [1,1,1] 24 | days_waning: [1,1,1] 25 | waning_factor: 1. 26 | sterilisation_efficacies: 27 | - Covid19: 28 | 0-100: 0.32 29 | - Covid19: 30 | 0-100: 0.75 31 | - Covid19: 32 | 0-100: 0.88 33 | symptomatic_efficacies: 34 | - Covid19: 35 | 0-100: 0.32 36 | - Covid19: 37 | 0-100: 0.75 38 | - Covid19: 39 | 0-100: 0.88 40 | 41 | 42 | Test: 43 | days_administered_to_effective: [1,2,10] 44 | days_effective_to_waning: [1,1,1] 45 | days_waning: [1,1,1] 46 | waning_factor: 1. 47 | sterilisation_efficacies: 48 | - Delta: 49 | 0-100: 0.3 50 | Omicron: 51 | 0-100: 0.2 52 | - Delta: 53 | 0-100: 0.7 54 | Omicron: 55 | 0-100: 0.2 56 | - Delta: 57 | 0-100: 0.9 58 | Omicron: 59 | 0-100: 0.8 60 | 61 | symptomatic_efficacies: 62 | - Delta: 63 | 0-100: 0.3 64 | Omicron: 65 | 0-100: 0.5 66 | - Delta: 67 | 0-100: 0.7 68 | Omicron: 69 | 0-100: 0.2 70 | - Delta: 71 | 0-100: 0.7 72 | Omicron: 73 | 0-100: 0.1 74 | -------------------------------------------------------------------------------- /test_june/unit/test_timer.py: -------------------------------------------------------------------------------- 1 | from june.time import Timer 2 | 3 | 4 | def test_initial_parameters(): 5 | timer = Timer(initial_day="2020-03-10", total_days=10) 6 | assert timer.shift == 0 7 | assert timer.is_weekend is False 8 | assert timer.day_of_week == "Tuesday" 9 | assert timer.date_str == "2020-03-10" 10 | 11 | 12 | def test_time_is_passing(): 13 | timer = Timer(initial_day="2020-03-10", total_days=10) 14 | assert timer.now == 0 15 | next(timer) 16 | assert timer.now == 0.5 17 | assert timer.previous_date == timer.initial_date 18 | next(timer) 19 | assert timer.now == 1.0 20 | 21 | 22 | def test_time_reset(): 23 | timer = Timer(initial_day="2020-03-10", total_days=10) 24 | start_time = timer.initial_date 25 | assert timer.date_str == "2020-03-10" 26 | next(timer) 27 | next(timer) 28 | assert timer.date_str == "2020-03-11" 29 | next(timer) 30 | next(timer) 31 | assert timer.day == 2 32 | assert timer.date_str == "2020-03-12" 33 | timer.reset() 34 | assert timer.day == 0 35 | assert timer.shift == 0 36 | assert timer.previous_date == start_time 37 | assert timer.date_str == "2020-03-10" 38 | next(timer) 39 | next(timer) 40 | next(timer) 41 | next(timer) 42 | assert timer.day == 2 43 | 44 | 45 | def test_weekend_transition(): 46 | timer = Timer(initial_day="2020-03-10", total_days=10) 47 | for _ in range(0, 8): # 5 days for 3 time steps per day 48 | next(timer) 49 | assert timer.is_weekend is True 50 | assert timer.activities == ("residence",) 51 | next(timer) 52 | assert timer.is_weekend is True 53 | assert timer.activities == ("residence",) 54 | next(timer) 55 | assert timer.is_weekend is False 56 | assert timer.activities == ("primary_activity", "residence") 57 | # a second test 58 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Run linting and sanity checks 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: [3.8, 3.9] 18 | 19 | steps: 20 | 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v1 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install system dependencies 28 | run: | 29 | sudo apt-get update -y 30 | sudo apt-get install -y openmpi-bin 31 | sudo apt install -y libhdf5-dev 32 | sudo apt install -y libopenmpi-dev 33 | 34 | - name: Install JUNE 35 | run: | 36 | python -m pip install --upgrade pip 37 | python setup.py install 38 | 39 | - name: Lint with black and flake8 40 | run: | 41 | pip install flake8 42 | pip install black 43 | black --check june 44 | black --check test_june 45 | # stop the build if there are Python syntax errors or undefined names 46 | # flake8 . --count --show-source --statistics 47 | # flake8 . 48 | flake8 june 49 | flake8 test_june 50 | 51 | - name: Get JUNE data 52 | run: bash scripts/get_june_data.sh 53 | 54 | - name: Test with pytest 55 | run: | 56 | pytest test_june --cov=june --cov-report xml 57 | 58 | - name: Upload coverage to Codecov 59 | uses: codecov/codecov-action@v2 60 | with: 61 | token: ${{secrets.CODECOV_TOKEN}} 62 | verbose: true 63 | 64 | - name: Test all cells in ipynb for errors 65 | run: | 66 | pip install jupyter 67 | jupyter nbconvert --NotebookClient.kernel_name=python3 --to notebook --inplace --execute Notebooks/quickstart.ipynb 68 | -------------------------------------------------------------------------------- /june/hdf5_savers/__init__.py: -------------------------------------------------------------------------------- 1 | from .population_saver import ( 2 | save_population_to_hdf5, 3 | load_population_from_hdf5, 4 | restore_population_properties_from_hdf5, 5 | ) 6 | from .household_saver import ( 7 | save_households_to_hdf5, 8 | load_households_from_hdf5, 9 | restore_households_properties_from_hdf5, 10 | ) 11 | from .carehome_saver import ( 12 | save_care_homes_to_hdf5, 13 | load_care_homes_from_hdf5, 14 | restore_care_homes_properties_from_hdf5, 15 | ) 16 | from .school_saver import ( 17 | save_schools_to_hdf5, 18 | load_schools_from_hdf5, 19 | restore_school_properties_from_hdf5, 20 | ) 21 | from .company_saver import ( 22 | save_companies_to_hdf5, 23 | load_companies_from_hdf5, 24 | restore_companies_properties_from_hdf5, 25 | ) 26 | from .geography_saver import ( 27 | save_geography_to_hdf5, 28 | load_geography_from_hdf5, 29 | restore_geography_properties_from_hdf5, 30 | ) 31 | from .hospital_saver import ( 32 | save_hospitals_to_hdf5, 33 | load_hospitals_from_hdf5, 34 | restore_hospital_properties_from_hdf5, 35 | ) 36 | from .commute_saver import ( 37 | save_cities_to_hdf5, 38 | save_stations_to_hdf5, 39 | load_cities_from_hdf5, 40 | load_stations_from_hdf5, 41 | restore_cities_and_stations_properties_from_hdf5, 42 | ) 43 | from .university_saver import ( 44 | save_universities_to_hdf5, 45 | load_universities_from_hdf5, 46 | restore_universities_properties_from_hdf5, 47 | ) 48 | from .leisure_saver import ( 49 | save_social_venues_to_hdf5, 50 | load_social_venues_from_hdf5, 51 | restore_social_venues_properties_from_hdf5, 52 | ) 53 | from .domain_data_saver import ( 54 | save_data_for_domain_decomposition, 55 | load_data_for_domain_decomposition, 56 | ) 57 | 58 | from .infection_savers import * # noqa 59 | 60 | # important this needs to be last: 61 | from .world_saver import ( 62 | generate_world_from_hdf5, 63 | save_world_to_hdf5, 64 | generate_domain_from_hdf5, 65 | ) 66 | -------------------------------------------------------------------------------- /june/groups/group/subgroup.py: -------------------------------------------------------------------------------- 1 | from june.demography.person import Person 2 | from .abstract import AbstractGroup 3 | from typing import List 4 | 5 | 6 | class Subgroup(AbstractGroup): 7 | external = False 8 | __slots__ = ("group", "subgroup_type", "people") 9 | 10 | def __init__(self, group, subgroup_type: int): 11 | """ 12 | A group within a group. For example, children in a household. 13 | """ 14 | self.group = group 15 | self.subgroup_type = subgroup_type 16 | self.people = [] 17 | 18 | def _collate(self, attribute: str) -> List[Person]: 19 | return [person for person in self.people if getattr(person, attribute)] 20 | 21 | @property 22 | def spec(self): 23 | return self.group.spec 24 | 25 | @property 26 | def infected(self): 27 | return self._collate("infected") 28 | 29 | @property 30 | def susceptible(self): 31 | return self._collate("susceptible") 32 | 33 | @property 34 | def recovered(self): 35 | return self._collate("recovered") 36 | 37 | @property 38 | def dead(self): 39 | return self._collate("dead") 40 | 41 | @property 42 | def in_hospital(self): 43 | return self._collate("in_hospital") 44 | 45 | def __contains__(self, item): 46 | return item in self.people 47 | 48 | def __iter__(self): 49 | return iter(self.people) 50 | 51 | def __len__(self): 52 | return len(self.people) 53 | 54 | def clear(self): 55 | self.people = [] 56 | 57 | @property 58 | def contains_people(self) -> bool: 59 | """ 60 | Whether or not the group contains people. 61 | """ 62 | return len(self.people) > 0 63 | 64 | def append(self, person: Person): 65 | """ 66 | Add a person to this group 67 | """ 68 | self.people.append(person) 69 | person.busy = True 70 | 71 | def remove(self, person: Person): 72 | self.people.remove(person) 73 | person.busy = False 74 | 75 | def __getitem__(self, item): 76 | return list(self.people)[item] 77 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_make_subgroups.py: -------------------------------------------------------------------------------- 1 | from june.groups import Supergroup 2 | from june.groups import Group 3 | from june.demography import Person 4 | from enum import IntEnum 5 | import pytest 6 | from june import paths 7 | import itertools 8 | 9 | 10 | from june.groups.group import make_subgroups 11 | 12 | interaction_config = ( 13 | paths.configs_path / "tests/groups/make_subgroups_test_interaction.yaml" 14 | ) 15 | 16 | 17 | class MockGroup(Group): 18 | def __init__(self): 19 | super().__init__() 20 | 21 | 22 | class MockSupergroup(Supergroup): 23 | venue_class = MockGroup 24 | 25 | def __init__(self, groups): 26 | super().__init__(groups) 27 | 28 | 29 | @pytest.fixture(name="super_group_default", scope="module") 30 | def make_supergroup_default(): 31 | MockSupergroup.get_interaction(interaction_config) 32 | groups_list = [MockSupergroup.venue_class() for _ in range(10)] 33 | super_group_default = MockSupergroup(groups_list) 34 | return super_group_default 35 | 36 | 37 | @pytest.fixture(name="super_group", scope="module") 38 | def make_supergroup(): 39 | MockSupergroup.get_interaction(interaction_config) 40 | groups_list = [MockSupergroup.venue_class() for _ in range(10)] 41 | super_group = MockSupergroup(groups_list) 42 | return super_group 43 | 44 | 45 | def test__make_subgroups_defualt(super_group_default): 46 | assert super_group_default[0].subgroup_type == "Age" 47 | assert super_group_default[0].subgroup_bins == [0, 18, 60, 100] 48 | assert super_group_default[0].subgroup_labels == ["A", "B", "C"] 49 | 50 | 51 | def test__make_subgroups(super_group): 52 | assert super_group[0].subgroup_type == "Age" 53 | assert super_group[0].subgroup_bins == [0, 18, 60, 100] 54 | assert super_group[0].subgroup_labels == ["A", "B", "C"] 55 | 56 | 57 | def test_excel_cols(): 58 | assert list(itertools.islice(make_subgroups.SubgroupParams().excel_cols(), 10)) == [ 59 | "A", 60 | "B", 61 | "C", 62 | "D", 63 | "E", 64 | "F", 65 | "G", 66 | "H", 67 | "I", 68 | "J", 69 | ] 70 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_hospitals.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import pytest 3 | import pandas as pd 4 | from june.geography import Geography 5 | 6 | from june.groups import Hospitals 7 | from june.epidemiology.infection import InfectionSelector 8 | from june.paths import data_path 9 | 10 | 11 | path_pwd = Path(__file__) 12 | dir_pwd = path_pwd.parent 13 | 14 | 15 | @pytest.fixture(name="hospitals", scope="module") 16 | def create_hospitals(): 17 | return Hospitals.from_file(filename=data_path / "input/hospitals/trusts.csv") 18 | 19 | 20 | @pytest.fixture(name="hospitals_df", scope="module") 21 | def create_hospitals_df(): 22 | return pd.read_csv(data_path / "input/hospitals/trusts.csv") 23 | 24 | 25 | def test__total_number_hospitals_is_correct(hospitals, hospitals_df): 26 | assert len(hospitals.members) == len(hospitals_df) 27 | 28 | 29 | @pytest.mark.parametrize("index", [2, 3]) 30 | def test__given_hospital_finds_itself_as_closest(hospitals, hospitals_df, index): 31 | 32 | closest_idx = hospitals.get_closest_hospitals_idx( 33 | hospitals_df[["latitude", "longitude"]].iloc[index].values, k=10 34 | ) 35 | 36 | closest_hospital_idx = closest_idx[0] 37 | assert hospitals.members[closest_hospital_idx] == hospitals.members[index] 38 | 39 | 40 | @pytest.fixture(name="selector", scope="module") 41 | def create_selector(): 42 | selector = InfectionSelector.from_file() 43 | selector.recovery_rate = 0.05 44 | selector.transmission_probability = 0.7 45 | return selector 46 | 47 | 48 | class MockArea: 49 | def __init__(self, coordinates): 50 | self.coordinates = coordinates 51 | 52 | 53 | def test__initialize_hospitals_from_geography(): 54 | geography = Geography.from_file({"super_area": ["E02003282", "E02005560"]}) 55 | hospitals = Hospitals.for_geography(geography) 56 | assert len(hospitals.members) == 2 57 | assert hospitals.members[1].super_area.name == "E02005560" 58 | assert hospitals.members[0].super_area.name == "E02003282" 59 | assert hospitals.members[1].n_beds + hospitals.members[1].n_icu_beds == 468 + 41 60 | assert hospitals.members[0].n_beds + hospitals.members[0].n_icu_beds == 2115 + 296 61 | assert hospitals.members[0].trust_code == "RAJ" 62 | -------------------------------------------------------------------------------- /test_june/unit/test_fullrun.py: -------------------------------------------------------------------------------- 1 | from june.simulator import Simulator 2 | from june.interaction import Interaction 3 | from june.epidemiology.infection import InfectionSelectors, Immunity 4 | from june.epidemiology.infection_seed import InfectionSeed 5 | from june.epidemiology.epidemiology import Epidemiology 6 | from june.groups.travel import Travel 7 | from june.policy import Policies 8 | from june.records import Record 9 | from june.groups.leisure import generate_leisure_for_config 10 | from june import paths 11 | 12 | 13 | selector_config = paths.configs_path / "defaults/infection/InfectionConstant.yaml" 14 | test_config = paths.configs_path / "tests/test_simulator.yaml" 15 | interaction_config = paths.configs_path / "tests/interaction.yaml" 16 | 17 | 18 | def test__full_run(dummy_world, selector, test_results): 19 | world = dummy_world 20 | # restore health status of people 21 | for person in world.people: 22 | person.infection = None 23 | person.immunity = Immunity() 24 | person.dead = False 25 | travel = Travel() 26 | leisure = generate_leisure_for_config( 27 | world=dummy_world, config_filename=test_config 28 | ) 29 | interaction = Interaction.from_file(config_filename=interaction_config) 30 | record = Record(record_path=test_results / "results") 31 | policies = Policies.from_file() 32 | selectors = InfectionSelectors([selector]) 33 | epidemiology = Epidemiology(infection_selectors=selectors) 34 | 35 | sim = Simulator.from_file( 36 | world=world, 37 | interaction=interaction, 38 | epidemiology=epidemiology, 39 | config_filename=test_config, 40 | leisure=leisure, 41 | travel=travel, 42 | policies=policies, 43 | record=record, 44 | ) 45 | seed = InfectionSeed.from_uniform_cases( 46 | world=sim.world, 47 | infection_selector=selector, 48 | cases_per_capita=0.01, 49 | date=sim.timer.date_str, 50 | seed_past_infections=True, 51 | ) 52 | seed.unleash_virus_per_day(date=sim.timer.date, time=0) 53 | sim.run() 54 | for region in world.regions: 55 | region.policy["local_closed_venues"] = set() 56 | region.policy["global_closed_venues"] = set() 57 | -------------------------------------------------------------------------------- /test_june/unit/epidemiology/infection/test_transmission.py: -------------------------------------------------------------------------------- 1 | from june.epidemiology.infection import transmission as trans 2 | import scipy.stats 3 | import numpy as np 4 | import os 5 | import pytest 6 | 7 | directory = os.path.dirname(os.path.realpath(__file__)) 8 | 9 | 10 | class TestTransmission: 11 | def test__update_probability_at_time(self): 12 | 13 | transmission = trans.TransmissionConstant(probability=0.3) 14 | 15 | assert transmission.probability == 0.3 16 | 17 | 18 | class TestTransmissionGamma: 19 | def test__update_probability_at_time(self): 20 | max_infectiousness = 4.0 21 | shift = 3.0 22 | shape = 3.0 23 | rate = 2.0 24 | transmission = trans.TransmissionGamma( 25 | max_infectiousness=max_infectiousness, shape=shape, rate=rate, shift=shift 26 | ) 27 | transmission.update_infection_probability((shape - 1) / rate + shift) 28 | avg_gamma = trans.TransmissionGamma( 29 | max_infectiousness=1.0, shape=shape, rate=rate, shift=shift 30 | ) 31 | avg_gamma.update_infection_probability(avg_gamma.time_at_maximum_infectivity) 32 | true_avg_peak_infectivity = avg_gamma.probability 33 | 34 | assert transmission.probability / true_avg_peak_infectivity == pytest.approx( 35 | max_infectiousness, rel=0.01 36 | ) 37 | 38 | @pytest.mark.parametrize("x", [0.0, 1, 3, 5]) 39 | @pytest.mark.parametrize("a", [1, 3, 5]) 40 | @pytest.mark.parametrize("loc", [0, -3, 3]) 41 | @pytest.mark.parametrize("scale", [1, 3, 5]) 42 | def test__gamma_pdf_implementation(self, x, a, loc, scale): 43 | scipy_gamma = scipy.stats.gamma(a=a, loc=loc, scale=scale) 44 | assert trans.gamma_pdf(x, a=a, loc=loc, scale=scale) == pytest.approx( 45 | scipy_gamma.pdf(x), rel=0.001 46 | ) 47 | 48 | def test__gamma_pdf_vectorized( 49 | self, 50 | ): 51 | x = np.linspace(0.0, 10.0, 100) 52 | a = 1.0 53 | loc = 1.0 54 | scale = 1.0 55 | scipy_gamma = scipy.stats.gamma(a=a, loc=loc, scale=scale) 56 | np.testing.assert_allclose( 57 | trans.gamma_pdf_vectorized(x, a=a, loc=loc, scale=scale), scipy_gamma.pdf(x) 58 | ) 59 | -------------------------------------------------------------------------------- /test_june/unit/policy/test_regional_compliance.py: -------------------------------------------------------------------------------- 1 | from june.geography import Region, Regions 2 | from june.policy import ( 3 | RegionalCompliance, 4 | RegionalCompliances, 5 | TieredLockdown, 6 | TieredLockdowns, 7 | ) 8 | 9 | 10 | class TestSetRegionCompliance: 11 | def test__set_compliance_to_region(self): 12 | regional_compliance = RegionalCompliance( 13 | start_time="2020-05-01", 14 | end_time="2020-09-01", 15 | compliances_per_region={"London": 1.5}, 16 | ) 17 | regional_compliances = RegionalCompliances([regional_compliance]) 18 | region = Region(name="London") 19 | regions = Regions([region]) 20 | regional_compliances.apply(regions=regions, date="2020-05-05") 21 | assert region.regional_compliance == 1.5 22 | regional_compliances.apply(regions=regions, date="2020-05-01") 23 | assert region.regional_compliance == 1.5 24 | regional_compliances.apply(regions=regions, date="2020-01-05") 25 | assert region.regional_compliance == 1.0 26 | regional_compliances.apply(regions=regions, date="2021-01-05") 27 | assert region.regional_compliance == 1.0 28 | regional_compliances.apply(regions=regions, date="2020-09-01") 29 | assert region.regional_compliance == 1.0 30 | 31 | 32 | class TestSetTiers: 33 | def test__set_lockdowntiers(self): 34 | tiered_lockdown = TieredLockdown( 35 | start_time="2020-05-01", 36 | end_time="2020-09-01", 37 | tiers_per_region={"London": 2.0}, 38 | ) 39 | tiered_lockdowns = TieredLockdowns([tiered_lockdown]) 40 | region = Region(name="London") 41 | regions = Regions([region]) 42 | tiered_lockdowns.apply(regions=regions, date="2020-05-05") 43 | assert region.policy["lockdown_tier"] == 2 44 | tiered_lockdowns.apply(regions=regions, date="2020-05-01") 45 | assert region.policy["lockdown_tier"] == 2 46 | tiered_lockdowns.apply(regions=regions, date="2020-01-05") 47 | assert region.policy["lockdown_tier"] is None 48 | tiered_lockdowns.apply(regions=regions, date="2021-01-05") 49 | assert region.policy["lockdown_tier"] is None 50 | tiered_lockdowns.apply(regions=regions, date="2020-09-01") 51 | assert region.policy["lockdown_tier"] is None 52 | -------------------------------------------------------------------------------- /example_scripts/create_world.py: -------------------------------------------------------------------------------- 1 | from june.geography import Geography 2 | from june.groups import Hospitals, Schools, Companies, CareHomes, Universities 3 | from june.groups.leisure import ( 4 | Pubs, 5 | Cinemas, 6 | Groceries, 7 | Gyms, 8 | generate_leisure_for_config, 9 | ) 10 | from june.groups.travel import Travel 11 | from june.world import generate_world_from_geography 12 | import time 13 | import numpy as np 14 | 15 | # load london super areas 16 | london_areas = np.loadtxt("./london_areas.txt", dtype=np.str_)[40:60] 17 | 18 | # add King's cross area for station 19 | if "E00004734" not in london_areas: 20 | london_areas = np.append(london_areas, "E02000187") 21 | 22 | # add some people commuting from Cambridge 23 | london_areas = np.concatenate((london_areas, ["E02003719", "E02003720", "E02003721"])) 24 | # 25 | # add Bath as well to have a city with no stations 26 | london_areas = np.concatenate( 27 | (london_areas, ["E02002988", "E02002989", "E02002990", "E02002991", "E02002992"]) 28 | ) 29 | 30 | t1 = time.time() 31 | 32 | # default config path 33 | config_path = "./config_simulation.yaml" 34 | 35 | # define geography, let's run the first 20 super areas of london 36 | geography = Geography.from_file({"super_area": london_areas}) 37 | # geography = Geography.from_file({"region": ["North East"]}) 38 | 39 | # add buildings 40 | geography.hospitals = Hospitals.for_geography(geography) 41 | geography.companies = Companies.for_geography(geography) 42 | geography.schools = Schools.for_geography(geography) 43 | geography.universities = Universities.for_geography(geography) 44 | geography.care_homes = CareHomes.for_geography(geography) 45 | # generate world 46 | world = generate_world_from_geography(geography, include_households=True) 47 | 48 | # some leisure activities 49 | world.pubs = Pubs.for_geography(geography) 50 | world.cinemas = Cinemas.for_geography(geography) 51 | world.groceries = Groceries.for_geography(geography) 52 | world.gyms = Gyms.for_geography(geography) 53 | leisure = generate_leisure_for_config(world, config_filename=config_path) 54 | leisure.distribute_social_venues_to_areas( 55 | areas=world.areas, super_areas=world.super_areas 56 | ) # this assigns possible social venues to people. 57 | travel = Travel() 58 | travel.initialise_commute(world) 59 | t2 = time.time() 60 | print(f"Took {t2 -t1} seconds to run.") 61 | # save the world to hdf5 to load it later 62 | world.to_hdf5("tests.hdf5") 63 | print("Done :)") 64 | -------------------------------------------------------------------------------- /june/data_formatting/google_api/msoa_search_cleaning.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import argparse 4 | 5 | 6 | def parse(): 7 | """ 8 | Parse input arguments 9 | """ 10 | parser = argparse.ArgumentParser( 11 | description="Clean Google Maps API data pulled using msoa_search.py" 12 | ) 13 | 14 | parser.add_argument( 15 | "--type", 16 | dest="location_type", 17 | help="Google maps type being selected (found on Google Cloud documentation)", 18 | type=str, 19 | ) 20 | 21 | parser.add_argument( 22 | "--msoa_coord_dir", 23 | dest="msoa_coord", 24 | help="directory containing MSOA centroids - assume also where file will be saved to", 25 | type=str, 26 | ) 27 | 28 | args = parser.parse_args() 29 | 30 | return args 31 | 32 | 33 | def clean(region_file, msoa_file): 34 | 35 | msoa = [] 36 | latitude = [] 37 | longitude = [] 38 | name = [] 39 | for idx, i in enumerate(region_file): 40 | for j in i: 41 | for k in j[0]: 42 | latitude.append(k["lat"]) 43 | longitude.append(k["lng"]) 44 | for k in j[1]: 45 | name.append(k) 46 | msoa.append(list(msoa_file["MSOA11CD"])[idx]) 47 | 48 | data = {"lat": latitude, "lon": longitude, "name": name, "msoa": msoa} 49 | df = pd.DataFrame(data) 50 | 51 | df_drop = df.drop_duplicates(subset=["lat", "lon"], keep="first") 52 | 53 | return df_drop 54 | 55 | 56 | if __name__ == "__main__": 57 | 58 | args = parse() 59 | 60 | regions = [ 61 | "Yorkshire", 62 | "London", 63 | "Wales", 64 | "EastMidlands", 65 | "WestMidlands", 66 | "SouthEast", 67 | "SouthWest", 68 | "NorthEast", 69 | "NorthWest", 70 | "East", 71 | ] 72 | for region in regions: 73 | print("Working on region: {}".format(region)) 74 | region_file = np.load( 75 | "{}/outs_{}_{}.npy".format(args.msoa_coord, args.location_type, region), 76 | allow_pickle=True, 77 | ) 78 | msoa_file = pd.read_csv( 79 | "{}/msoa_coordinates_{}.csv".format(args.msoa_coord, region) 80 | ) 81 | df_clean = clean(region_file, msoa_file) 82 | df_clean.to_csv( 83 | "{}/outs_{}_{}_clean.csv".format( 84 | args.msoa_coord, args.location_type, region 85 | ) 86 | ) 87 | -------------------------------------------------------------------------------- /test_june/unit/distributors/test_company_distributor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from june.geography import SuperArea 3 | from june.groups import Company 4 | from june.demography import Person 5 | from june.distributors import CompanyDistributor 6 | 7 | # TODO: This test shouldn't use from goegraphy! Create a world that has those characteristics 8 | 9 | 10 | @pytest.fixture(name="super_area") 11 | def make_super_area(): 12 | super_area = SuperArea() 13 | for i in range(3): 14 | super_area.companies.append(Company(sector=i, n_workers_max=i)) 15 | person = Person.from_attributes() 16 | person.sector = i 17 | super_area.workers.append(person) 18 | return super_area 19 | 20 | 21 | def test__company_distributor(super_area): 22 | cd = CompanyDistributor() 23 | cd.distribute_adults_to_companies_in_super_area(super_area) 24 | for company in super_area.companies: 25 | assert len(company.people) == 1 26 | assert list(company.people)[0].sector == company.sector 27 | 28 | 29 | def test__company_and_work_super_area(full_world): 30 | has_people = False 31 | for person in full_world.people: 32 | if person.work_super_area is not None: 33 | has_people = True 34 | assert person.work_super_area == person.primary_activity.group.super_area 35 | assert has_people 36 | 37 | 38 | class TestLockdownStatus: 39 | def test__lockdown_status_random(self, full_world): 40 | found_worker = False 41 | found_child = False 42 | for person in full_world.areas[0].people: 43 | if person.age > 18: 44 | worker = person 45 | found_worker = True 46 | elif person.age < 18: 47 | child = person 48 | found_child = True 49 | if found_worker and found_child: 50 | break 51 | 52 | assert worker.lockdown_status is not None 53 | assert child.lockdown_status is None 54 | 55 | def test__lockdown_status_teacher(self, full_world): 56 | teacher = full_world.schools[0].teachers.people[0] 57 | assert teacher.lockdown_status == "key_worker" 58 | 59 | def test__lockdown_status_medic(self, full_world): 60 | medic = full_world.hospitals[0].people[0] 61 | assert medic.lockdown_status == "key_worker" 62 | 63 | def test__lockdown_status_care_home(self, full_world): 64 | care_home_worker = full_world.care_homes[0].people[0] 65 | assert care_home_worker.lockdown_status == "key_worker" 66 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_schools.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from june.geography import Geography 4 | from june.demography import Person 5 | from june.groups import School, Schools 6 | from recordclass import dataobject 7 | 8 | 9 | class Activities(dataobject): 10 | residence: None 11 | primary_activity: None 12 | medical_facility: None 13 | commute: None 14 | rail_travel: None 15 | leisure: None 16 | 17 | def iter(self): 18 | return [getattr(self, activity) for activity in self.__fields__] 19 | 20 | 21 | @pytest.fixture(name="geo_schools", scope="module") 22 | def area_name(): 23 | geography = Geography.from_file(filter_key={"super_area": ["E02004935"]}) 24 | return geography 25 | 26 | 27 | class TestSchool: 28 | @pytest.fixture(name="school") 29 | def create_school(self): 30 | return School(coordinates=(1.0, 1.0), n_pupils_max=467, age_min=6, age_max=8) 31 | 32 | def test__school_grouptype(self, school): 33 | assert school.SubgroupType.teachers == 0 34 | assert school.SubgroupType.students == 1 35 | 36 | def test__empty_school(self, school): 37 | assert len(school.teachers.people) == 0 38 | for subgroup in school.subgroups[1:]: 39 | assert len(subgroup.people) == 0 40 | 41 | def test__filling_school(self, school): 42 | person = Person( 43 | sex="f", age=7, subgroups=Activities(None, None, None, None, None, None) 44 | ) 45 | 46 | school.add(person) 47 | assert bool(school.subgroups[2].people) is True 48 | 49 | 50 | class TestSchools: 51 | def test__creating_schools_from_file(self, geo_schools): 52 | Schools.from_file(areas=geo_schools.areas) 53 | 54 | def test_creating_schools_for_areas(self, geo_schools): 55 | Schools.for_areas(geo_schools.areas) 56 | 57 | @pytest.fixture(name="schools", scope="module") 58 | def test__creating_schools_for_geography(self, geo_schools): 59 | return Schools.for_geography(geo_schools) 60 | 61 | def test__school_nr_for_geography(self, schools): 62 | assert len(schools) == 4 63 | 64 | def test__school_is_closest_to_itself(self, schools): 65 | school = schools.members[0] 66 | age = int(0.5 * (school.age_min + school.age_max)) 67 | closest_school = schools.get_closest_schools(age, school.coordinates, 1) 68 | closest_school = schools.members[ 69 | schools.school_agegroup_to_global_indices.get(age)[closest_school[0]] 70 | ] 71 | assert closest_school == school 72 | -------------------------------------------------------------------------------- /june/event/incidence_setter.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Dict 2 | import datetime 3 | from random import sample, choices 4 | 5 | from .event import Event 6 | 7 | 8 | class IncidenceSetter(Event): 9 | """ 10 | This Event is used to set a specific incidence per region at some point in the code. 11 | It can be used to correct, based on data, the current epidemiological state of the code. 12 | The added infection types are sampled from the currrent ones. 13 | """ 14 | 15 | def __init__( 16 | self, 17 | start_time: Union[str, datetime.datetime], 18 | end_time: Union[str, datetime.datetime], 19 | incidence_per_region: Dict[str, float], 20 | ): 21 | super().__init__(start_time=start_time, end_time=end_time) 22 | self.incidence_per_region = incidence_per_region 23 | 24 | def initialise(self, world): 25 | pass 26 | 27 | def apply(self, world, simulator, activities=None, day_type=None): 28 | selectors = simulator.epidemiology.infection_selectors 29 | for region in world.regions: 30 | if region.name in self.incidence_per_region: 31 | target_incidence = self.incidence_per_region[region.name] 32 | people = region.people 33 | infected_people = [person for person in people if person.infected] 34 | incidence = len(infected_people) / len(people) 35 | if incidence > target_incidence: 36 | n_to_remove = int((incidence - target_incidence) * len(people)) 37 | to_cure = sample(infected_people, n_to_remove) 38 | for person in to_cure: 39 | person.infection = None 40 | elif incidence < target_incidence: 41 | n_to_add = int((target_incidence - incidence) * len(people)) 42 | to_infect = sample(people, k=2 * n_to_add) 43 | infected = choices(infected_people, k=2 * n_to_add) 44 | counter = 0 45 | for person, infected_ref in zip(to_infect, infected): 46 | if person.infected: 47 | continue 48 | counter += 1 49 | selectors.infect_person_at_time( 50 | person, 51 | simulator.timer.now, 52 | infected_ref.infection.infection_id(), 53 | ) 54 | if counter == n_to_add: 55 | break 56 | -------------------------------------------------------------------------------- /test_june/unit/geography/test_station.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import numpy as np 3 | 4 | from june.geography import SuperArea, SuperAreas, Station, Stations, City 5 | from june.geography.station import CityStation, InterCityStation 6 | 7 | super_stations_test_file = Path(__file__).parent / "stations.csv" 8 | 9 | 10 | class TestStations: 11 | def test__stations_setup(self): 12 | station = Station(city="Barcelona", super_area=SuperArea(name="b1")) 13 | assert station.city == "Barcelona" 14 | assert station.super_area.name == "b1" 15 | 16 | def test__stations_for_city_center(self): 17 | super_areas = SuperAreas( 18 | [ 19 | SuperArea(name="b1", coordinates=[0, 0]), 20 | SuperArea(name="b2", coordinates=[1, 0]), 21 | SuperArea(name="b3", coordinates=[0, 1]), 22 | SuperArea(name="b4", coordinates=[-1, 0]), 23 | SuperArea(name="b5", coordinates=[0, -1]), 24 | ], 25 | ball_tree=True, 26 | ) 27 | city = City(name="Barcelona", coordinates=[0, 0], super_area=super_areas[0]) 28 | city_stations = Stations.from_city_center( 29 | city=city, 30 | number_of_stations=4, 31 | distance_to_city_center=500, 32 | super_areas=super_areas, 33 | type="city_station", 34 | ) 35 | assert len(city_stations) == 4 36 | for st in city_stations: 37 | assert isinstance(st, CityStation) 38 | station_super_areas = [] 39 | for station in city_stations: 40 | station_super_areas.append(station.super_area.name) 41 | assert station.city == "Barcelona" 42 | assert station.super_area.name in ["b1", "b2", "b3", "b4", "b5"] 43 | assert len(np.unique(station_super_areas)) == 4 44 | city_stations._construct_ball_tree() 45 | station = city_stations.get_closest_station([0.1, 0]) 46 | assert station.coordinates[0] == 1 47 | assert station.coordinates[1] == 0 48 | station = city_stations.get_closest_station([-50, -10]) 49 | assert station.coordinates[0] == -1 50 | assert station.coordinates[1] == 0 51 | inter_city_stations = Stations.from_city_center( 52 | city=city, 53 | number_of_stations=4, 54 | distance_to_city_center=500, 55 | super_areas=super_areas, 56 | type="inter_city_station", 57 | ) 58 | for st in inter_city_stations: 59 | assert isinstance(st, InterCityStation) 60 | -------------------------------------------------------------------------------- /june/event/mutation.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Union, Dict 3 | from random import random 4 | 5 | from june.epidemiology.infection import B117 6 | from .event import Event 7 | 8 | 9 | class Mutation(Event): 10 | """ 11 | This events aims to reproduce a mutation effect. 12 | It was originally implemented to model the new Covid19 variant 13 | detected in the UK around November 2020. The idea is that a percentage 14 | of the active infections (which can vary region to region) is converted 15 | to the new variant, with different epidemiological charactersitics. 16 | Note: currently we only change the infection transmission charactersitics, 17 | but leaving the symptoms trajectory intact. 18 | 19 | Parameters 20 | ---------- 21 | start_time 22 | time from when the event is active (default is always) 23 | end_time 24 | time when the event ends (default is always) 25 | regional_probabilities 26 | fraction of current infections that will be transformed to the new variant 27 | mutation_id 28 | unique id of the new mutation. These are generated with an adler32 encoding on 29 | the name. 30 | Covid19: 170852960 31 | B117: 37224668 32 | """ 33 | 34 | def __init__( 35 | self, 36 | start_time: Union[str, datetime.datetime], 37 | end_time: Union[str, datetime.datetime], 38 | regional_probabilities: Dict[str, float], 39 | mutation_id=B117.infection_id(), 40 | ): 41 | super().__init__(start_time=start_time, end_time=end_time) 42 | self.regional_probabilities = regional_probabilities 43 | self.mutation_id = mutation_id 44 | 45 | def initialise(self, world=None): 46 | pass 47 | 48 | def apply(self, world, simulator, activities=None, day_type=None): 49 | selector = simulator.epidemiology.infection_selectors.infection_id_to_selector[ 50 | self.mutation_id 51 | ] 52 | for person in world.people: 53 | if person.infected: 54 | probability = self.regional_probabilities.get(person.region.name, 0) 55 | if random() < probability: 56 | new_infection = selector._make_infection( 57 | person, time=person.infection.start_time 58 | ) 59 | new_infection.time_of_testing = person.infection.time_of_testing 60 | new_infection.start_time = person.infection.start_time 61 | new_infection.symptoms = person.infection.symptoms 62 | person.infection = new_infection 63 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_companies.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import pytest 5 | import numpy as np 6 | from collections import defaultdict 7 | 8 | from june.geography import Geography 9 | from june.demography import Person 10 | from june.groups.company import Company, Companies 11 | 12 | 13 | default_data_path = ( 14 | Path(os.path.abspath(__file__)).parent.parent.parent.parent 15 | / "data/processed/census_data/company_data/" 16 | ) 17 | 18 | 19 | @pytest.fixture(name="super_area_companies", scope="module") 20 | def create_geography(): 21 | g = Geography.from_file(filter_key={"super_area": ["E02002559"]}) 22 | return g.super_areas.members[0] 23 | 24 | 25 | @pytest.fixture(name="person") 26 | def create_person(): 27 | return Person.from_attributes(sex="m", age=44) 28 | 29 | 30 | class TestCompany: 31 | @pytest.fixture(name="company") 32 | def create_company(self, super_area_companies): 33 | return Company(super_area=super_area_companies, n_workers_max=115, sector="Q") 34 | 35 | def test__company_grouptype(self, company): 36 | assert company.SubgroupType.workers == 0 37 | 38 | def test__empty_company(self, company): 39 | assert len(company.people) == 0 40 | 41 | def test__filling_company(self, person, company): 42 | company.add(person) 43 | assert list(company.people)[0] == person 44 | 45 | def test__person_is_employed(self, person, company): 46 | company.add(person) 47 | assert ( 48 | person.primary_activity == company.subgroups[company.SubgroupType.workers] 49 | ) 50 | 51 | 52 | @pytest.fixture(name="companies_example") 53 | def create_companies(super_area_companies): 54 | companies = Companies.for_super_areas([super_area_companies]) 55 | return companies 56 | 57 | 58 | def test__company_sizes(companies_example): 59 | assert len(companies_example) == 450 60 | sizes_dict = defaultdict(int) 61 | bins = [0, 10, 20, 50, 100, 250, 500, 1000, 1500] 62 | for company in companies_example: 63 | size = company.n_workers_max 64 | idx = np.searchsorted(bins, size) - 1 65 | sizes_dict[idx] += 1 66 | assert np.isclose(sizes_dict[0], 400, atol=10) 67 | assert np.isclose(sizes_dict[1], 30, atol=10) 68 | assert np.isclose(sizes_dict[2], 10, atol=10) 69 | assert np.isclose(sizes_dict[3], 0, atol=5) 70 | assert np.isclose(sizes_dict[4], 5, atol=6) 71 | 72 | 73 | def test__company_ids(companies_example, super_area_companies): 74 | for company_id, company in companies_example.members_by_id.items(): 75 | assert company.id == company_id 76 | for company in companies_example: 77 | assert company.super_area == super_area_companies 78 | -------------------------------------------------------------------------------- /test_june/unit/epidemiology/infection_seed/test_cases_distributor.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | from pytest import fixture 4 | 5 | from june.epidemiology.infection_seed import CasesDistributor 6 | 7 | 8 | class TestCasesDistributor: 9 | @fixture(name="super_area_region") 10 | def make_super_area_region(self): 11 | data = [["a1", "East of England"], ["a2", "East of England"]] 12 | ret = pd.DataFrame(data=data, columns=["super_area", "region"]) 13 | return ret 14 | 15 | @fixture(name="residents_by_super_area") 16 | def make_area_super_area_region(self): 17 | data = [["a1", 100], ["a2", 200]] 18 | ret = pd.DataFrame(data=data, columns=["super_area", "n_residents"]) 19 | return ret 20 | 21 | @fixture(name="cases_per_region_per_day") 22 | def make_cases_per_region_per_day(self): 23 | index = ["2020-03-01", "2020-03-02"] 24 | ret = pd.DataFrame(index=index) 25 | ret["East of England"] = [600, 1200] 26 | return ret 27 | 28 | def test__from_regional_cases( 29 | self, super_area_region, residents_by_super_area, cases_per_region_per_day 30 | ): 31 | 32 | cd = CasesDistributor.from_regional_cases( 33 | cases_per_day_region=cases_per_region_per_day, 34 | super_area_to_region=super_area_region, 35 | residents_per_super_area=residents_by_super_area, 36 | ) 37 | cases_per_super_area = cd.cases_per_super_area 38 | assert np.allclose( 39 | cases_per_super_area.loc[:, "a1"].values, 40 | np.array([200, 400], dtype=np.float64), 41 | rtol=0.25, 42 | ) 43 | assert np.allclose( 44 | cases_per_super_area.loc[:, "a2"].values, 45 | np.array([400, 800], dtype=np.float64), 46 | rtol=0.25, 47 | ) 48 | 49 | def test__from_national_cases( 50 | self, super_area_region, residents_by_super_area, cases_per_region_per_day 51 | ): 52 | index = ["2020-03-01", "2020-03-02"] 53 | cases_per_day = pd.DataFrame(index=index) 54 | cases_per_day["N_cases"] = [600, 1200] 55 | 56 | cd = CasesDistributor.from_national_cases( 57 | cases_per_day=cases_per_day, 58 | super_area_to_region=super_area_region, 59 | residents_per_super_area=residents_by_super_area, 60 | ) 61 | cases_per_super_area = cd.cases_per_super_area 62 | assert np.allclose( 63 | cases_per_super_area.loc[:, "a1"].values, 64 | np.array([200, 400], dtype=np.float64), 65 | rtol=0.25, 66 | ) 67 | assert np.allclose( 68 | cases_per_super_area.loc[:, "a2"].values, 69 | np.array([400, 800], dtype=np.float64), 70 | rtol=0.25, 71 | ) 72 | -------------------------------------------------------------------------------- /june/distributors/company_distributor.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import logging 3 | import numpy as np 4 | from random import randint 5 | 6 | 7 | logger = logging.getLogger("company_distributor") 8 | 9 | """ 10 | This file contains routines to attribute people with different characteristics 11 | according to census data. 12 | """ 13 | 14 | 15 | class CompanyDistributor: 16 | """ 17 | Distributes workers that are not yet working in key company sectors 18 | (e.g. such as schools and hospitals) to companies. This assumes that 19 | the WorkerDistributor has already been run to allocate workers in 20 | a super_area 21 | """ 22 | 23 | def __init__(self): 24 | """Get all companies within SuperArea""" 25 | 26 | def distribute_adults_to_companies_in_super_areas(self, super_areas): 27 | logger.info("Distributing workers to companies") 28 | for i, super_area in enumerate(super_areas): 29 | if i % 100 == 0: 30 | logger.info( 31 | f"Distributed workers to companies in {i} of {len(super_areas)} super areas." 32 | ) 33 | self.distribute_adults_to_companies_in_super_area(super_area) 34 | logger.info("Workers distributed to companies") 35 | 36 | def distribute_adults_to_companies_in_super_area(self, super_area): 37 | """ 38 | Looks for all workers and companies in the super area and matches 39 | them 40 | """ 41 | company_dict = defaultdict(list) 42 | full_idx = defaultdict(int) 43 | unallocated_workers = [] 44 | for company in super_area.companies: 45 | company_dict[company.sector].append(company) 46 | full_idx[company.sector] = 0 47 | 48 | for worker in super_area.workers: 49 | if worker.primary_activity is not None: 50 | continue 51 | if company_dict[worker.sector]: 52 | if full_idx[worker.sector] >= len(company_dict[worker.sector]): 53 | idx = randint(0, len(company_dict[worker.sector]) - 1) 54 | company = company_dict[worker.sector][idx] 55 | # company = np.random.choice(company_dict[worker.sector]) 56 | else: 57 | company = company_dict[worker.sector][0] 58 | if company.n_workers >= company.n_workers_max: 59 | full_idx[company.sector] += 1 60 | company.add(worker) 61 | else: 62 | unallocated_workers.append(worker) 63 | 64 | if unallocated_workers: 65 | companies_for_unallocated = np.random.choice( 66 | super_area.companies, len(unallocated_workers) 67 | ) 68 | for worker, company in zip(unallocated_workers, companies_for_unallocated): 69 | company.add(worker) 70 | -------------------------------------------------------------------------------- /june/policy/regional_compliance.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from .policy import Policy, PolicyCollection, read_date 4 | from june.geography import Regions 5 | 6 | 7 | class RegionalCompliance(Policy): 8 | policy_type = "regional_compliance" 9 | 10 | def __init__(self, start_time: str, end_time: str, compliances_per_region: dict): 11 | super().__init__(start_time=start_time, end_time=end_time) 12 | self.compliances_per_region = compliances_per_region 13 | 14 | def apply(self, date: datetime, regions: Regions): 15 | date = read_date(date) 16 | if self.is_active(date): 17 | for region in regions: 18 | region.regional_compliance = self.compliances_per_region[region.name] 19 | 20 | 21 | class RegionalCompliances(PolicyCollection): 22 | policy_type = "regional_compliance" 23 | 24 | def apply(self, date: datetime, regions: Regions): 25 | # before applying compliances, reset all of them to 1.0 26 | if self.policies: 27 | for region in regions: 28 | region.regional_compliance = 1.0 29 | for policy in self.policies: 30 | policy.apply(date=date, regions=regions) 31 | 32 | 33 | class TieredLockdown(Policy): 34 | policy_type = "tiered_lockdown" 35 | 36 | def __init__(self, start_time: str, end_time: str, tiers_per_region: dict): 37 | super().__init__(start_time=start_time, end_time=end_time) 38 | self.tiers_per_region = tiers_per_region 39 | 40 | def apply(self, date: datetime, regions: Regions): 41 | date = read_date(date) 42 | if self.is_active(date): 43 | for region in regions: 44 | lockdown_tier = int(self.tiers_per_region[region.name]) 45 | region.policy["lockdown_tier"] = lockdown_tier 46 | if lockdown_tier == 2: 47 | region.policy["local_closed_venues"].update("residence_visits") 48 | elif lockdown_tier == 3: 49 | region.policy["local_closed_venues"].update( 50 | set(("cinema", "residence_visits")) 51 | ) 52 | elif lockdown_tier == 4: 53 | region.policy["local_closed_venues"].update( 54 | set(("pub", "cinema", "gym", "residence_visits")) 55 | ) 56 | 57 | 58 | class TieredLockdowns(PolicyCollection): 59 | policy_type = "tiered_lockdown" 60 | 61 | def apply(self, date: datetime, regions: Regions): 62 | # before applying compliances, reset all of them to None and empty sets 63 | if self.policies: 64 | for region in regions: 65 | region.policy["lockdown_tier"] = None 66 | region.policy["local_closed_venues"] = set() 67 | for policy in self.policies: 68 | policy.apply(date=date, regions=regions) 69 | -------------------------------------------------------------------------------- /june/epidemiology/infection/symptoms.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | 3 | import numpy as np 4 | from .symptom_tag import SymptomTag 5 | from .trajectory_maker import TrajectoryMakers 6 | 7 | dead_tags = (SymptomTag.dead_home, SymptomTag.dead_hospital, SymptomTag.dead_icu) 8 | 9 | 10 | class Symptoms: 11 | __slots__ = ( 12 | "tag", 13 | "max_tag", 14 | "max_severity", 15 | "trajectory", 16 | "stage", 17 | "time_of_symptoms_onset", 18 | ) 19 | """ 20 | Class to represent the symptoms of a person. The symptoms class composes the 21 | ``Infection`` class alongside with the ``Transmission`` class. Once infected, 22 | a person is assigned a symptoms trajectory according to a health index generated 23 | by the ``HealthIndexGenerator``. A trajectory is a collection of symptom tags with 24 | characteristic timings. 25 | """ 26 | 27 | def __init__(self, health_index=None): 28 | self.max_tag = None 29 | self.tag = SymptomTag.exposed 30 | self.max_severity = random() 31 | self.trajectory = self._make_symptom_trajectory( 32 | health_index 33 | ) # this also sets max_tag 34 | self.stage = 0 35 | self.time_of_symptoms_onset = self._compute_time_from_infection_to_symptoms() 36 | 37 | def _compute_time_from_infection_to_symptoms(self): 38 | symptoms_onset = 0 39 | for completion_time, tag in self.trajectory: 40 | symptoms_onset += completion_time 41 | if tag == SymptomTag.mild: 42 | break 43 | elif tag == SymptomTag.asymptomatic: 44 | return None 45 | return symptoms_onset 46 | 47 | def _make_symptom_trajectory(self, health_index): 48 | if health_index is None: 49 | return [(0, SymptomTag(0))] 50 | trajectory_maker = TrajectoryMakers.from_file() 51 | index_max_symptoms_tag = np.searchsorted(health_index, self.max_severity) 52 | self.max_tag = SymptomTag(index_max_symptoms_tag) 53 | return trajectory_maker[self.max_tag] 54 | 55 | def update_trajectory_stage(self, time_from_infection): 56 | """ 57 | Updates the current symptom tag from the symptoms trajectory, 58 | given how much time has passed since the person was infected. 59 | 60 | Parameters 61 | ---------- 62 | time_from_infection: float 63 | Time in days since the person got infected. 64 | """ 65 | if time_from_infection > self.trajectory[self.stage + 1][0]: 66 | self.stage += 1 67 | self.tag = self.trajectory[self.stage][1] 68 | 69 | @property 70 | def time_exposed(self): 71 | return self.trajectory[1][0] 72 | 73 | @property 74 | def recovered(self): 75 | return self.tag == SymptomTag.recovered 76 | 77 | @property 78 | def dead(self): 79 | return self.tag in dead_tags 80 | -------------------------------------------------------------------------------- /june/policy/interaction_policies.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from .policy import Policy, PolicyCollection 4 | from june.interaction import Interaction 5 | from collections import defaultdict 6 | 7 | 8 | class InteractionPolicy(Policy): 9 | policy_type = "interaction" 10 | 11 | 12 | class InteractionPolicies(PolicyCollection): 13 | policy_type = "interaction" 14 | 15 | def apply(self, date: datetime, interaction: Interaction): 16 | active_policies = self.get_active(date) 17 | beta_reductions = defaultdict(lambda: 1.0) 18 | for policy in active_policies: 19 | beta_reductions_dict = policy.apply() 20 | for group in beta_reductions_dict: 21 | beta_reductions[group] *= beta_reductions_dict[group] 22 | interaction.beta_reductions = beta_reductions 23 | 24 | 25 | class SocialDistancing(InteractionPolicy): 26 | policy_subtype = "beta_factor" 27 | 28 | def __init__(self, start_time: str, end_time: str, beta_factors: dict = None): 29 | super().__init__(start_time, end_time) 30 | self.beta_factors = beta_factors 31 | 32 | def apply(self): 33 | """ 34 | Implement social distancing policy 35 | 36 | ----------- 37 | Parameters: 38 | betas: e.g. (dict) from DefaultInteraction, e.g. DefaultInteraction.from_file(selector=selector).beta 39 | 40 | Assumptions: 41 | - Currently we assume that social distancing is implemented first and this affects all 42 | interactions and intensities globally 43 | - Currently we assume that the changes are not group dependent 44 | TODO: 45 | - Implement structure for people to adhere to social distancing with a certain compliance 46 | - Check per group in config file 47 | """ 48 | return self.beta_factors 49 | 50 | 51 | class MaskWearing(InteractionPolicy): 52 | policy_subtype = "beta_factor" 53 | 54 | def __init__( 55 | self, 56 | start_time: str, 57 | end_time: str, 58 | compliance: float, 59 | beta_factor: float, 60 | mask_probabilities: dict = None, 61 | ): 62 | super().__init__(start_time, end_time) 63 | self.compliance = compliance 64 | self.beta_factor = beta_factor 65 | self.mask_probabilities = mask_probabilities 66 | 67 | def apply(self): 68 | """ 69 | Implement mask wearing policy 70 | 71 | ----------- 72 | Parameters: 73 | betas: e.g. (dict) from DefaultInteraction, e.g. DefaultInteraction.from_file(selector=selector).beta 74 | 75 | Assumptions: 76 | - Currently we assume that mask wearing is implemented in a similar way to social distanding 77 | but with a mean field effect in beta reduction 78 | - Currently we assume that the changes are group dependent 79 | """ 80 | ret = {} 81 | for key, value in self.mask_probabilities.items(): 82 | ret[key] = 1 - (value * self.compliance * (1 - self.beta_factor)) 83 | return ret 84 | -------------------------------------------------------------------------------- /june/paths.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import subprocess 4 | from pathlib import Path 5 | from sys import argv 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | project_directory = Path(os.path.abspath(__file__)).parent 10 | 11 | working_directory = Path(os.getcwd()) 12 | 13 | working_directory_parent = working_directory.parent 14 | 15 | 16 | def find_default(name: str, look_in_package=True) -> Path: 17 | """ 18 | Get a default path when no command line argument is passed. 19 | 20 | - First attempt to find the folder in the current working directory. 21 | - If it is not found there then try the directory in which June lives. 22 | - Finally, try the directory above the current working directory. This 23 | is for the build pipeline. 24 | 25 | This means that tests will find the configuration regardless of whether 26 | they are run together or individually. 27 | 28 | Parameters 29 | ---------- 30 | name 31 | The name of some folder 32 | 33 | Returns 34 | ------- 35 | The full path to that directory 36 | """ 37 | directories_to_look = [working_directory, working_directory_parent] 38 | if look_in_package: 39 | directories_to_look.append(project_directory) 40 | directories_to_look.append(project_directory.parent) 41 | for directory in directories_to_look: 42 | path = directory / name 43 | if os.path.exists(path): 44 | return path 45 | raise FileNotFoundError(f"Could not find a default path for {name}") 46 | 47 | 48 | def path_for_name(name: str, look_in_package=True) -> Path: 49 | """ 50 | Get a path input using a flag when the program is run. 51 | 52 | If no such argument is given default to the directory above 53 | the june with the name of the flag appended. 54 | 55 | e.g. --data indicates where the data folder is and defaults 56 | to june/../data 57 | 58 | Parameters 59 | ---------- 60 | name 61 | A string such as "data" which corresponds to the flag --data 62 | 63 | Returns 64 | ------- 65 | A path 66 | """ 67 | flag = f"--{name}" 68 | try: 69 | path = Path(argv[argv.index(flag) + 1]) 70 | if not path.exists(): 71 | raise FileNotFoundError(f"No such folder {path}") 72 | except (IndexError, ValueError): 73 | path = find_default(name, look_in_package=look_in_package) 74 | logger.warning(f"No {flag} argument given - defaulting to:\n{path}") 75 | 76 | return path 77 | 78 | 79 | try: 80 | data_path = path_for_name("data", look_in_package=True) 81 | except FileNotFoundError: 82 | answer = input( 83 | "I couldn't find any data folder, do you want me to download it for you? (y/N) " 84 | ) 85 | if answer == "y": 86 | script_path = Path(__file__).parent.parent / "scripts" / "get_june_data.sh" 87 | with open(script_path, "rb") as file: 88 | script = file.read() 89 | rc = subprocess.call(script, shell=True) 90 | data_path = path_for_name("data", look_in_package=True) 91 | 92 | configs_path = path_for_name("configs") 93 | -------------------------------------------------------------------------------- /june/event/event.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | import yaml 3 | import datetime 4 | import logging 5 | from typing import Union, List 6 | 7 | from june.utils import read_date, str_to_class 8 | from june.paths import configs_path 9 | from june.mpi_setup import mpi_rank 10 | 11 | default_config_filename = configs_path / "defaults/event/events.yaml" 12 | logger = logging.getLogger("events") 13 | if mpi_rank > 0: 14 | logger.propagate = False 15 | 16 | 17 | class Event(ABC): 18 | """ 19 | This class represents an event. An event is a sequence of actions to the world, 20 | that can happen at the beginning of each time step during a defined period of time. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | start_time: Union[str, datetime.datetime], 26 | end_time: Union[str, datetime.datetime], 27 | ): 28 | self.start_time = read_date(start_time) 29 | self.end_time = read_date(end_time) 30 | 31 | def is_active(self, date: datetime.datetime): 32 | return self.start_time <= date < self.end_time 33 | 34 | def initialise(self, world): 35 | raise NotImplementedError 36 | 37 | def apply(self, world, simulator, activities, day_type): 38 | raise NotImplementedError 39 | 40 | 41 | class Events: 42 | def __init__(self, events=None): 43 | self.events = events 44 | 45 | @classmethod 46 | def from_file( 47 | cls, config_file=default_config_filename, base_event_modules=("june.event",) 48 | ): 49 | with open(config_file) as f: 50 | config = yaml.load(f, Loader=yaml.FullLoader) or {} 51 | events = [] 52 | for event, event_data in config.items(): 53 | camel_case_key = "".join(x.capitalize() or "_" for x in event.split("_")) 54 | if "start_time" not in event_data: 55 | for event_i, event_data_i in event_data.items(): 56 | if ( 57 | "start_time" not in event_data_i.keys() 58 | or "end_time" not in event_data_i.keys() 59 | ): 60 | raise ValueError("event config file not valid.") 61 | events.append( 62 | str_to_class(camel_case_key, base_event_modules)(**event_data_i) 63 | ) 64 | else: 65 | events.append( 66 | str_to_class(camel_case_key, base_event_modules)(**event_data) 67 | ) 68 | return cls(events) 69 | 70 | def init_events(self, world): 71 | logger.info("Initialising events...") 72 | for event in self.events: 73 | event.initialise(world=world) 74 | logger.info(f"Event {event.__class__.__name__} initialised") 75 | 76 | def apply(self, date, world, simulator, activities: List[str], day_type: bool): 77 | for event in self.events: 78 | if event.is_active(date=date): 79 | event.apply( 80 | world=world, 81 | simulator=simulator, 82 | activities=activities, 83 | day_type=day_type, 84 | ) 85 | -------------------------------------------------------------------------------- /test_june/unit/distributors/test_hospital_distributor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from june.distributors import HospitalDistributor 4 | from june.geography import Geography 5 | from june.groups import Hospital, Hospitals 6 | from june.demography.person import Person 7 | 8 | 9 | @pytest.fixture(name="young_medic") 10 | def make_medic_young(): 11 | medic = Person.from_attributes(age=18) 12 | medic.sector = "Q" 13 | medic.sub_sector = "Hospital" 14 | return medic 15 | 16 | 17 | @pytest.fixture(name="old_medic") 18 | def make_medic_old(): 19 | medic = Person.from_attributes(age=40) 20 | medic.sector = "Q" 21 | medic.sub_sector = "Hospital" 22 | return medic 23 | 24 | 25 | @pytest.fixture(name="geography_hospital") 26 | def make_geography(young_medic, old_medic): 27 | geography = Geography.from_file({"super_area": ["E02003999", "E02006764"]}) 28 | for _ in range(200): 29 | geography.super_areas.members[0].add_worker(young_medic) 30 | geography.super_areas.members[0].areas[0].add(young_medic) 31 | for _ in range(200): 32 | geography.super_areas.members[0].add_worker(old_medic) 33 | geography.super_areas.members[0].areas[0].add(old_medic) 34 | return geography 35 | 36 | 37 | @pytest.fixture(name="hospitals") 38 | def make_hospitals(geography_hospital): 39 | super_area_test = geography_hospital.super_areas.members[0] 40 | hospitals = [ 41 | Hospital( 42 | n_beds=40, 43 | n_icu_beds=5, 44 | area=super_area_test.areas[0], 45 | coordinates=super_area_test.coordinates, 46 | ), 47 | Hospital( 48 | n_beds=80, 49 | n_icu_beds=20, 50 | area=super_area_test.areas[0], 51 | coordinates=super_area_test.coordinates, 52 | ), 53 | ] 54 | return Hospitals(hospitals) 55 | 56 | 57 | def test__distribution_of_medics(geography_hospital, hospitals): 58 | geography_hospital.hospitals = hospitals 59 | hospital_distributor = HospitalDistributor( 60 | hospitals, medic_min_age=25, patients_per_medic=10, healthcare_sector_label="Q" 61 | ) 62 | hospital_distributor.distribute_medics_to_super_areas( 63 | geography_hospital.super_areas 64 | ) 65 | for hospital in hospitals: 66 | patients = hospital.n_beds + hospital.n_icu_beds 67 | medics = hospital.subgroups[hospital.SubgroupType.workers].people 68 | for medic in medics: 69 | assert medic.age >= hospital_distributor.medic_min_age 70 | assert len(medics) == patients // hospital_distributor.patients_per_medic 71 | 72 | 73 | def test__distribution_of_medics_from_world(geography_hospital, hospitals): 74 | 75 | hospital_distributor = HospitalDistributor( 76 | hospitals, medic_min_age=20, patients_per_medic=10 77 | ) 78 | hospital_distributor.distribute_medics_from_world( 79 | geography_hospital.super_areas.members[0].people 80 | ) 81 | for hospital in hospitals: 82 | patients = hospital.n_beds + hospital.n_icu_beds 83 | medics = hospital.subgroups[hospital.SubgroupType.workers].people 84 | for medic in medics: 85 | assert medic.age >= hospital_distributor.medic_min_age 86 | assert len(medics) == patients // hospital_distributor.patients_per_medic 87 | -------------------------------------------------------------------------------- /june/data_formatting/google_api/msoa_search.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import argparse 4 | 5 | from gmapi import APICall 6 | 7 | 8 | class MSOASearch: 9 | """ 10 | Functions for running Google Maps API by region at the MSOA level for a given type 11 | 12 | More information on Google types can be found here: https://developers.google.com/places/supported_types 13 | """ 14 | 15 | def __init__(self): 16 | self.args = self.parse() 17 | 18 | def parse(self): 19 | """ 20 | Parse input arguments 21 | """ 22 | parser = argparse.ArgumentParser( 23 | description="Run the Google Maps API by region at the MSOA level for a given type" 24 | ) 25 | parser.add_argument( 26 | "--apikey_file", 27 | dest="apikey_file", 28 | help="location of txt file containing apikey", 29 | type=str, 30 | ) 31 | 32 | parser.add_argument( 33 | "--type", 34 | dest="location_type", 35 | help="Google maps type being selected (found on Google Cloud documentation)", 36 | type=str, 37 | ) 38 | 39 | parser.add_argument( 40 | "--msoa_coord_dir", 41 | dest="msoa_coord", 42 | help="directory containing MSOA centroids - assume also where file will be saved to", 43 | type=str, 44 | ) 45 | 46 | args = parser.parse_args() 47 | 48 | return args 49 | 50 | def get_msoas_type(self, apikey, msoas): 51 | """ 52 | For a given type, call Google Maps API to search for type 53 | 54 | Note: Currently the radius is fixed at the average required to cover the whole of England and Wales 55 | """ 56 | self.apikey = apikey 57 | apicall = APICall(self.apikey) 58 | 59 | coordinates = [] 60 | for i in range(len(msoas)): 61 | coordinates.append((msoas["Y"][i], msoas["X"][i])) 62 | outs = [] 63 | for i in range(len(coordinates)): 64 | out = apicall.nearby_search_loop( 65 | location=(coordinates[i][0], coordinates[i][1]), 66 | radius=4600, 67 | location_type=self.args.location_type, 68 | ) 69 | outs.append(out) 70 | # print (out) 71 | 72 | return outs 73 | 74 | 75 | if __name__ == "__main__": 76 | 77 | msoasearch = MSOASearch() 78 | 79 | with open(msoasearch.args.apikey_file, "r") as f: 80 | api = f.read() 81 | apikey = api.split("\n")[0] 82 | 83 | regions = [ 84 | "East" 85 | ] # , 'SouthEast', 'SouthWest', 'NorthEast', 'NorthWest', 'Yorkshire','London', 'Wales', 'EastMidlands', 'WestMidlands'] 86 | 87 | for region in regions: 88 | print("Working on region: {}".format(region)) 89 | msoas = pd.read_csv( 90 | "{}/msoa_coordinates_{}.csv".format(msoasearch.args.msoa_coord, region) 91 | ) 92 | outs = msoasearch.get_msoas_type(apikey, msoas) 93 | np.save( 94 | "{}/outs_{}_{}.npy".format( 95 | msoasearch.args.msoa_coord, msoasearch.args.location_type, region 96 | ), 97 | outs, 98 | allow_pickle=True, 99 | ) 100 | -------------------------------------------------------------------------------- /test_june/unit/policy/test_medical_care_policies.py: -------------------------------------------------------------------------------- 1 | from june.epidemiology.infection import SymptomTag 2 | from june.policy import Policies, Hospitalisation 3 | 4 | 5 | def test__hospitalise_the_sick(setup_policy_world, selector): 6 | world, pupil, student, worker, sim = setup_policy_world 7 | hospitalisation = Hospitalisation() 8 | policies = Policies([hospitalisation]) 9 | sim.activity_manager.policies = policies 10 | sim.epidemiology.set_medical_care( 11 | world=world, activity_manager=sim.activity_manager 12 | ) 13 | selector.infect_person_at_time(worker, 0.0) 14 | worker.infection.symptoms.tag = SymptomTag.hospitalised 15 | assert worker.infection.should_be_in_hospital 16 | sim.epidemiology.update_health_status(world, 0.0, 0.0) 17 | assert worker.medical_facility is not None 18 | sim.activity_manager.move_people_to_active_subgroups( 19 | ["medical_facility", "residence"] 20 | ) 21 | assert worker in worker.medical_facility.people 22 | sim.clear_world() 23 | 24 | 25 | def test__move_people_from_hospital_to_icu(setup_policy_world, selector): 26 | world, pupil, student, worker, sim = setup_policy_world 27 | hospital = world.hospitals[0] 28 | selector.infect_person_at_time(worker, 0.0) 29 | hospitalisation = Hospitalisation() 30 | policies = Policies([hospitalisation]) 31 | sim.activity_manager.policies = policies 32 | sim.epidemiology.set_medical_care( 33 | world=world, activity_manager=sim.activity_manager 34 | ) 35 | worker.infection.symptoms.tag = SymptomTag.hospitalised 36 | assert worker.infection.should_be_in_hospital 37 | sim.epidemiology.update_health_status(world, 0.0, 0.0) 38 | assert worker.medical_facility == hospital[hospital.SubgroupType.patients] 39 | sim.clear_world() 40 | worker.infection.symptoms.tag = SymptomTag.intensive_care 41 | sim.epidemiology.update_health_status(world, 0.0, 0.0) 42 | hospital = worker.medical_facility.group 43 | sim.activity_manager.move_people_to_active_subgroups( 44 | ["medical_facility", "residence"] 45 | ) 46 | assert worker.medical_facility == hospital[hospital.SubgroupType.icu_patients] 47 | sim.clear_world() 48 | 49 | 50 | def test__move_people_from_icu_to_hospital(setup_policy_world, selector): 51 | world, pupil, student, worker, sim = setup_policy_world 52 | selector.infect_person_at_time(worker, 0.0) 53 | hospitalisation = Hospitalisation() 54 | policies = Policies([hospitalisation]) 55 | sim.activity_manager.policies = policies 56 | sim.epidemiology.set_medical_care( 57 | world=world, activity_manager=sim.activity_manager 58 | ) 59 | worker.infection.symptoms.tag = SymptomTag.intensive_care 60 | assert worker.infection.should_be_in_hospital 61 | hospital = world.hospitals[0] 62 | sim.epidemiology.update_health_status(world, 0.0, 0.0) 63 | assert worker.medical_facility == hospital[hospital.SubgroupType.icu_patients] 64 | sim.clear_world() 65 | worker.infection.symptoms.tag = SymptomTag.hospitalised 66 | sim.epidemiology.update_health_status(world, 0.0, 0.0) 67 | hospital = worker.medical_facility.group 68 | sim.activity_manager.move_people_to_active_subgroups( 69 | ["medical_facility", "residence"] 70 | ) 71 | assert worker.medical_facility == hospital[hospital.SubgroupType.patients] 72 | sim.clear_world() 73 | -------------------------------------------------------------------------------- /june/groups/group/supergroup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from collections import OrderedDict 3 | 4 | from june.groups.group.make_subgroups import SubgroupParams 5 | import numpy as np 6 | 7 | 8 | class Supergroup: 9 | """ 10 | A group containing a collection of groups of the same specification, 11 | like households, carehomes, etc. 12 | This class is meant to be used as template to inherit from, and it 13 | integrates basic functionality like iteration, etc. 14 | It also includes a method to delete information about people in the 15 | groups. 16 | """ 17 | 18 | def __init__(self, members): 19 | self.group_type = self.__class__.__name__ 20 | self.spec = self.get_spec() 21 | self.members = members 22 | self.members_by_id = self._make_member_ids_dict(members) 23 | 24 | def _make_member_ids_dict(self, members): 25 | """ 26 | Makes a dictionary with the ids of the members. 27 | """ 28 | ret = OrderedDict() 29 | for member in members: 30 | ret[member.id] = member 31 | return ret 32 | 33 | def __iter__(self): 34 | return iter(self.members) 35 | 36 | def __len__(self): 37 | return len(self.members) 38 | 39 | def __getitem__(self, item): 40 | return self.members[item] 41 | 42 | def get_from_id(self, id): 43 | return self.members_by_id[id] 44 | 45 | def __add__(self, supergroup: "Supergroup"): 46 | for group in supergroup: 47 | self.add(group) 48 | return self 49 | 50 | def clear(self): 51 | self.members_by_id.clear() 52 | 53 | def add(self, group): 54 | self.members_by_id[group.id] = group 55 | self.members.append(group) 56 | 57 | @property 58 | def member_ids(self): 59 | return list(self.members_by_id.keys()) 60 | 61 | def get_spec(self) -> str: 62 | """ 63 | Returns the speciailization of the super group. 64 | """ 65 | return re.sub(r"(? 0: 72 | num = random() * cum_scores[-1] 73 | idx = np.searchsorted(cum_scores, num) 74 | household = households[idx] 75 | if household.id in seeded_households: 76 | continue 77 | for person in household.residents: 78 | if person.immunity.get_susceptibility(infection_id) > 0: 79 | self.infect_person(person=person, time=time, record=record) 80 | if time < 0: 81 | self.bring_infection_up_to_date( 82 | person=person, time_from_infection=-time, record=record 83 | ) 84 | total_to_infect -= 1 85 | if total_to_infect < 1: 86 | return 87 | seeded_households.add(household.id) 88 | -------------------------------------------------------------------------------- /test_june/unit/groups/test_travel_old.py: -------------------------------------------------------------------------------- 1 | # import pytest 2 | # 3 | # from june import World 4 | # from june.geography import Geography, Area 5 | # from june.demography import Person, Demography 6 | # from june.distributors import WorkerDistributor 7 | # from june.commute import CommuteGenerator 8 | # from june.groups import ( 9 | # CommuteCity, 10 | # CommuteCities, 11 | # CommuteCityDistributor, 12 | # CommuteHub, 13 | # CommuteHubs, 14 | # CommuteHubDistributor, 15 | # CommuteUnit, 16 | # CommuteUnits, 17 | # CommuteUnitDistributor, 18 | # CommuteCityUnit, 19 | # CommuteCityUnits, 20 | # CommuteCityUnitDistributor, 21 | # ) 22 | # from june.groups import ( 23 | # TravelCity, 24 | # TravelCities, 25 | # TravelCityDistributor, 26 | # TravelUnit, 27 | # TravelUnits, 28 | # TravelUnitDistributor, 29 | # ) 30 | # from june.world import generate_world_from_geography 31 | # 32 | # 33 | # class TestTravel: 34 | # @pytest.fixture(name="super_area_commute_nc") 35 | # def super_area_name_nc(self): 36 | # # return ['E02001731', 'E02001729', 'E02001688', 'E02001689', 'E02001736', 37 | # # 'E02001720', 'E02001724', 'E02001730', 'E02006841', 'E02001691', 38 | # # 'E02001713', 'E02001712', 'E02001694', 'E02006842', 'E02001723', 39 | # # 'E02001715', 'E02001710', 'E02001692', 'E02001734', 'E02001709'] 40 | # return ["E02001731", "E02001729"] 41 | # 42 | # @pytest.fixture(name="geography_commute_nc") 43 | # def create_geography_nc(self, super_area_commute_nc): 44 | # geography = Geography.from_file({"super_area": super_area_commute_nc}) 45 | # return geography 46 | # 47 | # @pytest.fixture(name="world_nc") 48 | # def create_world_nc(self, geography_commute_nc): 49 | # world = generate_world_from_geography( 50 | # geography_commute_nc, include_households=False, include_commute=False 51 | # ) 52 | # 53 | # return world 54 | # 55 | # @pytest.fixture(name="commutecities_nc") 56 | # def create_commute_setup(self, world_nc): 57 | # commutecities = CommuteCities.for_super_areas(world_nc.super_areas) 58 | # assert len(commutecities.members) == 11 59 | # 60 | # return commutecities 61 | # 62 | # def test__travel_all(self, world_nc, commutecities_nc): 63 | # travelcities = TravelCities(commutecities_nc) 64 | # travelcities.init_cities() 65 | # assert len(travelcities.members) == 11 66 | # 67 | # travelcity_distributor = TravelCityDistributor( 68 | # travelcities.members, world_nc.super_areas.members 69 | # ) 70 | # travelcity_distributor.distribute_msoas() 71 | # 72 | # travelunits = TravelUnits() 73 | # travelunit_distributor = TravelUnitDistributor( 74 | # travelcities.members, travelunits.members 75 | # ) 76 | # travelunit_distributor.from_file() 77 | # travelunit_distributor.distribute_people_out() 78 | # assert len(travelunits.members) > 0 79 | # 80 | # people = 0 81 | # for i in travelunits.members: 82 | # no_pass = i.no_passengers 83 | # people += no_pass 84 | # 85 | # arrive = 0 86 | # for city in travelcities.members: 87 | # arrive += len(city.arrived) 88 | # 89 | # assert people == arrive 90 | # 91 | # travelunit_distributor.distribute_people_back() 92 | # assert len(travelunits.members) > 0 93 | # 94 | # people = 0 95 | # for i in travelunits.members: 96 | # no_pass = i.no_passengers 97 | # people += no_pass 98 | # 99 | # assert people == arrive 100 | -------------------------------------------------------------------------------- /june/utils/parse_probabilities.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from itertools import chain 4 | 5 | 6 | def parse_age_probabilities(age_dict: dict, fill_value=0): 7 | """ 8 | Parses the age probability dictionaries into an array. 9 | """ 10 | if age_dict is None: 11 | return [0], [0] 12 | bins = [] 13 | probabilities = [] 14 | for age_range in age_dict: 15 | age_range_split = age_range.split("-") 16 | if len(age_range_split) == 1: 17 | raise NotImplementedError("Please give age ranges as intervals") 18 | else: 19 | bins.append(int(age_range_split[0])) 20 | bins.append(int(age_range_split[1])) 21 | probabilities.append(age_dict[age_range]) 22 | sorting_idx = np.argsort(bins[::2]) 23 | bins = list( 24 | chain.from_iterable([bins[2 * idx], bins[2 * idx + 1]] for idx in sorting_idx) 25 | ) 26 | probabilities = np.array(probabilities)[sorting_idx] 27 | probabilities_binned = [] 28 | for prob in probabilities: 29 | probabilities_binned.append(fill_value) 30 | probabilities_binned.append(prob) 31 | probabilities_binned.append(fill_value) 32 | probabilities_per_age = [] 33 | for age in range(100): 34 | idx = np.searchsorted(bins, age + 1) # we do +1 to include the lower boundary 35 | probabilities_per_age.append(probabilities_binned[idx]) 36 | return probabilities_per_age 37 | 38 | 39 | def parse_opens(dict: dict, fill_value=0): 40 | """ 41 | Parses the opening time dictionary into an array 42 | """ 43 | daytype = list(dict.keys()) 44 | bins = {} 45 | for day_i in daytype: 46 | bins[day_i] = [] 47 | hour_range_split = dict[day_i].split("-") 48 | if len(hour_range_split) == 1: 49 | raise NotImplementedError("Please give open times as intervals") 50 | else: 51 | bins[day_i].append(int(hour_range_split[0])) 52 | bins[day_i].append(int(hour_range_split[1])) 53 | return bins 54 | 55 | 56 | def read_comorbidity_csv(filename: str): 57 | comorbidity_df = pd.read_csv(filename, index_col=0) 58 | column_names = [f"0-{comorbidity_df.columns[0]}"] 59 | for i in range(len(comorbidity_df.columns) - 1): 60 | column_names.append( 61 | f"{comorbidity_df.columns[i]}-{comorbidity_df.columns[i+1]}" 62 | ) 63 | comorbidity_df.columns = column_names 64 | for column in comorbidity_df.columns: 65 | no_comorbidity = comorbidity_df[column].loc["no_condition"] 66 | should_have_comorbidity = 1 - no_comorbidity 67 | has_comorbidity = np.sum(comorbidity_df[column]) - no_comorbidity 68 | comorbidity_df[column].iloc[:-1] *= should_have_comorbidity / has_comorbidity 69 | 70 | return comorbidity_df.T 71 | 72 | 73 | def convert_comorbidities_prevalence_to_dict(prevalence_female, prevalence_male): 74 | prevalence_reference_population = {} 75 | for comorbidity in prevalence_female.columns: 76 | prevalence_reference_population[comorbidity] = { 77 | "f": prevalence_female[comorbidity].to_dict(), 78 | "m": prevalence_male[comorbidity].to_dict(), 79 | } 80 | return prevalence_reference_population 81 | 82 | 83 | def parse_prevalence_comorbidities_in_reference_population( 84 | comorbidity_prevalence_reference_population, 85 | ): 86 | parsed_comorbidity_prevalence = {} 87 | for comorbidity, values in comorbidity_prevalence_reference_population.items(): 88 | parsed_comorbidity_prevalence[comorbidity] = { 89 | "f": parse_age_probabilities(values["f"]), 90 | "m": parse_age_probabilities(values["m"]), 91 | } 92 | return parsed_comorbidity_prevalence 93 | -------------------------------------------------------------------------------- /test_june/unit/files/config/json_priors/epidemiology.json: -------------------------------------------------------------------------------- 1 | { 2 | "SymptomsConstant": { 3 | "recovery_rate": { 4 | "type": "Gaussian", 5 | "lower_limit": "0.0", 6 | "upper_limit": "1.0", 7 | "width_modifier": { 8 | "type": "Absolute", 9 | "value": 0.05 10 | }, 11 | "mean": 0.2, 12 | "sigma": 0.1 13 | } 14 | }, 15 | "SymptomsGaussian": { 16 | "mean_time": { 17 | "type": "Gaussian", 18 | "lower_limit": "0.0", 19 | "upper_limit": "inf", 20 | "width_modifier": { 21 | "type": "Absolute", 22 | "value": 0.05 23 | }, 24 | "mean": 0.1, 25 | "sigma": 0.1 26 | }, 27 | "sigma_time": { 28 | "type": "Gaussian", 29 | "lower_limit": "0.0", 30 | "upper_limit": "inf", 31 | "width_modifier": { 32 | "type": "Absolute", 33 | "value": 0.05 34 | }, 35 | "mean": 3.0, 36 | "sigma": 0.1 37 | }, 38 | "recovery_rate": { 39 | "type": "Gaussian", 40 | "lower_limit": "0.0", 41 | "upper_limit": "1.0", 42 | "width_modifier": { 43 | "type": "Absolute", 44 | "value": 0.05 45 | }, 46 | "mean": 0.2, 47 | "sigma": 0.1 48 | } 49 | }, 50 | "SymptomsStep": { 51 | "time_offset": { 52 | "type": "Gaussian", 53 | "lower_limit": "0.0", 54 | "upper_limit": "inf", 55 | "width_modifier": { 56 | "type": "Absolute", 57 | "value": 0.05 58 | }, 59 | "mean": 2.0, 60 | "sigma": 0.1 61 | }, 62 | "end_time": { 63 | "type": "Gaussian", 64 | "lower_limit": "0.0", 65 | "upper_limit": "inf", 66 | "width_modifier": { 67 | "type": "Absolute", 68 | "value": 0.05 69 | }, 70 | "mean": 5.0, 71 | "sigma": 0.1 72 | } 73 | }, 74 | "SymptomsTanh": { 75 | "max_time": { 76 | "type": "Gaussian", 77 | "lower_limit": "0.0", 78 | "upper_limit": "inf", 79 | "width_modifier": { 80 | "type": "Absolute", 81 | "value": 0.05 82 | }, 83 | "mean": 2.0, 84 | "sigma": 0.1 85 | }, 86 | "onset_time": { 87 | "type": "Gaussian", 88 | "lower_limit": "0.0", 89 | "upper_limit": "inf", 90 | "width_modifier": { 91 | "type": "Absolute", 92 | "value": 0.05 93 | }, 94 | "mean": 0.5, 95 | "sigma": 0.1 96 | }, 97 | "end_time": { 98 | "type": "Gaussian", 99 | "lower_limit": "0.0", 100 | "upper_limit": "inf", 101 | "width_modifier": { 102 | "type": "Absolute", 103 | "value": 0.05 104 | }, 105 | "mean": 15.0, 106 | "sigma": 0.1 107 | } 108 | }, 109 | "Transmission": { 110 | "probability": { 111 | "type": "Gaussian", 112 | "lower_limit": "0.0", 113 | "upper_limit": "1.0", 114 | "width_modifier": { 115 | "type": "Absolute", 116 | "value": 0.05 117 | }, 118 | "mean": 0.3, 119 | "sigma": 0.1 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /june/hdf5_savers/infection_savers/immunity_saver.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import h5py 3 | from typing import List 4 | 5 | from june.hdf5_savers.utils import read_dataset 6 | from june.epidemiology.infection import Immunity 7 | 8 | int_vlen_type = h5py.vlen_dtype(np.dtype("int64")) 9 | float_vlen_type = h5py.vlen_dtype(np.dtype("float64")) 10 | 11 | nan_integer = -999 12 | nan_float = -999.0 13 | 14 | 15 | def save_immunities_to_hdf5(hdf5_file_path: str, immunities: List[Immunity]): 16 | """ 17 | Saves infections data to hdf5. 18 | 19 | Parameters 20 | ---------- 21 | hdf5_file_path 22 | hdf5 path to save symptoms 23 | immunities 24 | list of Immunity objects 25 | chunk_size 26 | number of hdf5 chunks to use while saving 27 | """ 28 | with h5py.File(hdf5_file_path, "a") as f: 29 | g = f.create_group("immunities") 30 | n_immunities = len(immunities) 31 | g.attrs["n_immunities"] = n_immunities 32 | if n_immunities == 0: 33 | return 34 | susc_infection_ids = [] 35 | susc_susceptibilities = [] 36 | lengths = [] 37 | for imm in immunities: 38 | inf_ids = [] 39 | suscs = [] 40 | for key, value in imm.susceptibility_dict.items(): 41 | inf_ids.append(key) 42 | suscs.append(value) 43 | if len(inf_ids) == 0: 44 | inf_ids = [nan_integer] 45 | suscs = [nan_float] 46 | susc_infection_ids.append(np.array(inf_ids, dtype=np.int64)) 47 | susc_susceptibilities.append(np.array(suscs, dtype=np.float64)) 48 | lengths.append(len(suscs)) 49 | if len(np.unique(lengths)) > 1: 50 | susc_infection_ids = np.array(susc_infection_ids, dtype=int_vlen_type) 51 | susc_susceptibilities = np.array( 52 | susc_susceptibilities, dtype=float_vlen_type 53 | ) 54 | else: 55 | susc_infection_ids = np.array(susc_infection_ids, dtype=np.int64) 56 | susc_susceptibilities = np.array(susc_susceptibilities, dtype=np.float64) 57 | g.create_dataset("susc_infection_ids", data=susc_infection_ids) 58 | g.create_dataset("susc_susceptibilities", data=susc_susceptibilities) 59 | 60 | 61 | def load_immunities_from_hdf5(hdf5_file_path: str, chunk_size=50000): 62 | """ 63 | Loads immunities data from hdf5. 64 | 65 | Parameters 66 | ---------- 67 | hdf5_file_path 68 | hdf5 path to load from 69 | chunk_size 70 | number of hdf5 chunks to use while loading 71 | """ 72 | immunities = [] 73 | with h5py.File(hdf5_file_path, "r") as f: 74 | g = f["immunities"] 75 | n_immunities = g.attrs["n_immunities"] 76 | if n_immunities == 0: 77 | return [] 78 | n_chunks = int(np.ceil(n_immunities / chunk_size)) 79 | for chunk in range(n_chunks): 80 | idx1 = chunk * chunk_size 81 | idx2 = min((chunk + 1) * chunk_size, n_immunities) 82 | susc_infection_ids = read_dataset(g["susc_infection_ids"], idx1, idx2) 83 | susc_susceptibilities = read_dataset(g["susc_susceptibilities"], idx1, idx2) 84 | length = idx2 - idx1 85 | for k in range(length): 86 | if susc_infection_ids[k][0] == nan_integer: 87 | immunity = Immunity() 88 | else: 89 | susceptibilities_dict = { 90 | key: value 91 | for key, value in zip( 92 | susc_infection_ids[k], susc_susceptibilities[k] 93 | ) 94 | } 95 | immunity = Immunity(susceptibilities_dict) 96 | immunities.append(immunity) 97 | return immunities 98 | -------------------------------------------------------------------------------- /test_june/unit/event/test_mutations.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | 4 | from june.demography import Person 5 | from june.epidemiology.infection import ( 6 | B117, 7 | Covid19, 8 | InfectionSelector, 9 | InfectionSelectors, 10 | ) 11 | from june.epidemiology.epidemiology import Epidemiology 12 | from june.event import Mutation 13 | 14 | 15 | class MockRegion: 16 | def __init__(self, name): 17 | self.name = name 18 | 19 | 20 | class MockArea: 21 | def __init__(self, super_area): 22 | self.super_area = super_area 23 | 24 | 25 | class MockSuperArea: 26 | def __init__(self, region): 27 | self.region = region 28 | 29 | 30 | class MockSimulator: 31 | def __init__(self, epidemiology): 32 | self.epidemiology = epidemiology 33 | 34 | 35 | class MockWorld: 36 | def __init__(self, people): 37 | self.people = people 38 | 39 | 40 | @pytest.fixture(name="c19_selector") 41 | def covid19_selector(health_index_generator): 42 | return InfectionSelector( 43 | health_index_generator=health_index_generator, infection_class=Covid19 44 | ) 45 | 46 | 47 | @pytest.fixture(name="c20_selector") 48 | def covid20_selector(health_index_generator): 49 | return InfectionSelector( 50 | infection_class=B117, health_index_generator=health_index_generator 51 | ) 52 | 53 | 54 | class TestMutations: 55 | @pytest.fixture(name="people") 56 | def create_pop(self, c19_selector): 57 | people = [] 58 | london = MockRegion("London") 59 | london_sa = MockSuperArea(region=london) 60 | ne = MockRegion("North East") 61 | ne_sa = MockSuperArea(region=ne) 62 | for i in range(0, 1000): 63 | person = Person.from_attributes() 64 | if i % 2 == 0: 65 | person.area = MockArea(super_area=london_sa) 66 | else: 67 | person.area = MockArea(super_area=ne_sa) 68 | people.append(person) 69 | for person in people: 70 | c19_selector.infect_person_at_time(person, 0) 71 | return people 72 | 73 | def test_mutation(self, people, c19_selector, c20_selector): 74 | infection_selectors = InfectionSelectors([c19_selector, c20_selector]) 75 | epidemiology = Epidemiology(infection_selectors=infection_selectors) 76 | simulator = MockSimulator(epidemiology) 77 | world = MockWorld(people=people) 78 | mutation = Mutation( 79 | start_time="2020-11-01", 80 | end_time="2020-11-02", 81 | mutation_id=B117.infection_id(), 82 | regional_probabilities={"London": 0.5, "North East": 0.01}, 83 | ) 84 | mutation.initialise() 85 | mutation.apply(world=world, simulator=simulator) 86 | c19_london = 0 87 | c19_ne = 0 88 | c20_london = 0 89 | c20_ne = 0 90 | for person in world.people: 91 | if person.infection.infection_id() == Covid19.infection_id(): 92 | assert person.infection.__class__.__name__ == "Covid19" 93 | if person.region.name == "London": 94 | c19_london += 1 95 | else: 96 | assert person.region.name == "North East" 97 | c19_ne += 1 98 | else: 99 | assert person.infection.infection_id() == B117.infection_id() 100 | assert person.infection.__class__.__name__ == "B117" 101 | if person.region.name == "London": 102 | c20_london += 1 103 | else: 104 | assert person.region.name == "North East" 105 | c20_ne += 1 106 | assert np.isclose(c20_london / (c20_london + c19_london), 0.5, rtol=1e-1) 107 | assert np.isclose(c20_ne / (c20_ne + c19_ne), 0.01, atol=0.01) 108 | -------------------------------------------------------------------------------- /june/data_formatting/census_data/household_composition_houses.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | import pandas as pd 5 | 6 | from june import paths 7 | 8 | raw_path = f"{paths.data_path}/census_data/output_area/" 9 | processed_path = f"{paths.data_path}/processed/census_data/output_area/" 10 | 11 | 12 | def filter_and_sum(df, in_column): 13 | if len(in_column) > 1: 14 | df = df.filter(regex="|".join(in_column)) 15 | else: 16 | df = df.filter(regex=in_column[0]) 17 | return df.sum(axis=1) 18 | 19 | 20 | df = pd.read_csv(raw_path / "household_houses.csv", index_col=0) 21 | 22 | # All England and Wales data 23 | assert len(df) == 181408 24 | 25 | df.set_index("geography", inplace=True) 26 | df.drop(columns=["date", "geography code"], inplace=True) 27 | 28 | df = df[ 29 | [col for col in df.columns if "Total" not in col and "All categories" not in col] 30 | ] 31 | 32 | encoding_households = pd.DataFrame() 33 | 34 | encoding_households["0 0 0 0 1"] = filter_and_sum(df, ["One person household: Aged 65"]) 35 | encoding_households["0 0 0 1 0"] = filter_and_sum(df, ["One person household: Other"]) 36 | encoding_households["0 0 0 0 2"] = filter_and_sum( 37 | df, ["One family only: All aged 65 and over"] 38 | ) 39 | encoding_households["0 0 0 2 0"] = filter_and_sum(df, ["No children"]) 40 | 41 | encoding_households["1 0 >=0 2 0"] = filter_and_sum( 42 | df, 43 | ["Married couple: One dependent child", "Cohabiting couple: One dependent child"], 44 | ) 45 | 46 | encoding_households[">=2 0 >=0 2 0"] = filter_and_sum( 47 | df, 48 | [ 49 | "Married couple: Two or more dependent", 50 | "Cohabiting couple: Two or more dependent", 51 | ], 52 | ) 53 | encoding_households["0 0 >=1 2 0"] = filter_and_sum( 54 | df, 55 | [ 56 | "Married couple: All children non-dependent", 57 | "Cohabiting couple: All children non-dependent", 58 | ], 59 | ) 60 | encoding_households["1 0 >=0 1 0"] = filter_and_sum( 61 | df, ["Lone parent: One dependent child"] 62 | ) 63 | encoding_households[">=2 0 >=0 1 0"] = filter_and_sum( 64 | df, ["Lone parent: Two or more dependent children"] 65 | ) 66 | encoding_households["0 0 >=1 1 0"] = filter_and_sum( 67 | df, ["Lone parent: All children non-dependent"] 68 | ) 69 | 70 | encoding_households["1 0 >=0 >=1 >=0"] = filter_and_sum( 71 | df, ["Other household types: With one dependent child"] 72 | ) 73 | encoding_households[">=2 0 >=0 >=1 >=0"] = filter_and_sum( 74 | df, ["Other household types: With two"] 75 | ) 76 | encoding_households["0 >=1 0 0 0"] = filter_and_sum(df, ["All full-time students"]) 77 | encoding_households["0 0 0 0 >=2"] = filter_and_sum( 78 | df, ["Other household types: All aged 65 and over"] 79 | ) 80 | encoding_households["0 0 >=0 >=0 >=0"] = filter_and_sum( 81 | df, ["Other household types: Other"] 82 | ) 83 | 84 | encoding_households.index.name = "output_area" 85 | 86 | np.testing.assert_array_equal( 87 | encoding_households.sum(axis=1).values, df.sum(axis=1).values 88 | ) 89 | # comunal establishments 90 | comunal_df = pd.read_csv(os.path.join(raw_path, "communal_houses.csv")) 91 | comunal_df.set_index(comunal_df["geography"], inplace=True) 92 | 93 | all_comunal_df = comunal_df[ 94 | [col for col in comunal_df.columns if "All categories" in col] 95 | ] 96 | carehome_df = comunal_df[[col for col in comunal_df.columns if "Care home" in col]] 97 | carehome_df = carehome_df.sum(axis=1) 98 | comunal_not_carehome_df = all_comunal_df[all_comunal_df.columns[0]] - carehome_df 99 | 100 | assert ( 101 | comunal_not_carehome_df.sum() + carehome_df.sum() 102 | == all_comunal_df[all_comunal_df.columns[0]].sum() 103 | ) 104 | 105 | encoding_households[">=0 >=0 >=0 >=0 >=0"] = comunal_not_carehome_df 106 | encoding_households.to_csv( 107 | os.path.join(processed_path, "minimum_household_composition.csv") 108 | ) 109 | -------------------------------------------------------------------------------- /test_june/unit/interaction/test_susceptibility.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | from random import randint 4 | 5 | from june.interaction import Interaction 6 | from june.demography import Population, Person 7 | from june.groups import Company 8 | from june.epidemiology.infection import Infection, TransmissionConstant 9 | 10 | 11 | class TestSusceptibilityHasAnEffect: 12 | @pytest.fixture(name="simulation_setup") 13 | def setup_group(self): 14 | company = Company() 15 | population = Population([]) 16 | n_kids = 50 17 | n_adults = 50 18 | for _ in range(n_kids): 19 | population.add(Person.from_attributes(age=randint(0, 12))) 20 | for _ in range(n_adults): 21 | population.add(Person.from_attributes(age=randint(13, 100))) 22 | for person in population: 23 | company.add(person) 24 | # infect one kid and one adult 25 | kid = population[0] 26 | assert kid.age <= 12 27 | adult = population[-1] 28 | assert adult.age >= 13 29 | kid.infection = Infection( 30 | symptoms=None, transmission=TransmissionConstant(probability=0.2) 31 | ) 32 | adult.infection = Infection( 33 | symptoms=None, transmission=TransmissionConstant(probability=0.2) 34 | ) 35 | return company, population 36 | 37 | def run_interaction(self, simulation_setup, interaction): 38 | """ 39 | With uniform susc. number of infected adults and kids should be the same. 40 | """ 41 | group, population = simulation_setup 42 | n_infected_adults_list = [] 43 | n_infected_kids_list = [] 44 | for _ in range(1000): 45 | infected_ids, _, group_size = interaction.time_step_for_group( 46 | group=group, delta_time=10 47 | ) 48 | n_infected_adults = len( 49 | [ 50 | person 51 | for person in population 52 | if person.id in infected_ids and person.age <= 12 53 | ] 54 | ) 55 | n_infected_kids = len( 56 | [ 57 | person 58 | for person in population 59 | if person.id in infected_ids and person.age > 12 60 | ] 61 | ) 62 | n_infected_adults_list.append(n_infected_adults) 63 | n_infected_kids_list.append(n_infected_kids) 64 | return np.mean(n_infected_kids_list), np.mean(n_infected_adults_list) 65 | 66 | def test__run_uniform_susceptibility(self, simulation_setup): 67 | contact_matrices = { 68 | "company": { 69 | "contacts": [[1]], 70 | "proportion_physical": [[1]], 71 | "characteristic_time": 8, 72 | } 73 | } 74 | interaction = Interaction( 75 | betas={"company": 1}, alpha_physical=1.0, contact_matrices=contact_matrices 76 | ) 77 | n_kids_inf, n_adults_inf = self.run_interaction( 78 | interaction=interaction, simulation_setup=simulation_setup 79 | ) 80 | assert n_kids_inf > 0 81 | assert n_adults_inf > 0 82 | assert np.isclose(n_kids_inf, n_adults_inf, rtol=0.05) 83 | 84 | def test__run_different_susceptibility(self, simulation_setup): 85 | group, population = simulation_setup 86 | interaction = Interaction( 87 | betas={"company": 1}, alpha_physical=1.0, contact_matrices=None 88 | ) 89 | for person in population: 90 | if person.age < 13: 91 | person.immunity.susceptibility_dict[Infection.infection_id()] = 0.5 92 | n_kids_inf, n_adults_inf = self.run_interaction( 93 | interaction=interaction, simulation_setup=simulation_setup 94 | ) 95 | assert n_kids_inf > 0 96 | assert n_adults_inf > 0 97 | assert np.isclose(0.5 * n_kids_inf, n_adults_inf, rtol=0.05) 98 | -------------------------------------------------------------------------------- /test_june/unit/event/test_incidence_setter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | 4 | from june.event import IncidenceSetter 5 | from june.world import World 6 | from june.geography import Area, SuperArea, Areas, SuperAreas, Region, Regions 7 | from june.demography import Person, Population 8 | 9 | incidence_per_region = {"London": 0.1, "North East": 0.01} 10 | 11 | 12 | class TestIncidenceSetter: 13 | @pytest.fixture(name="world") 14 | def setup(self): 15 | world = World() 16 | 17 | london_area = Area() 18 | london_super_area = SuperArea(areas=[london_area]) 19 | london = Region(name="London", super_areas=[london_super_area]) 20 | 21 | ne_area = Area() 22 | ne_super_area = SuperArea(areas=[ne_area]) 23 | ne = Region(name="North East", super_areas=[ne_super_area]) 24 | people = [] 25 | 26 | for i in range(1000): 27 | person = Person.from_attributes() 28 | london_area.add(person) 29 | people.append(person) 30 | 31 | for i in range(1000): 32 | person = Person.from_attributes() 33 | ne_area.add(person) 34 | people.append(person) 35 | 36 | world.areas = Areas([ne_area, london_area], ball_tree=False) 37 | world.super_areas = SuperAreas( 38 | [ne_super_area, london_super_area], ball_tree=False 39 | ) 40 | world.regions = Regions([london, ne]) 41 | world.people = Population(people) 42 | return world 43 | 44 | def test__removing_infections(self, world, policy_simulator): 45 | selector = policy_simulator.epidemiology.infection_selectors[0] 46 | # infect everyone 47 | for person in world.people: 48 | selector.infect_person_at_time(person, 0.0) 49 | 50 | setter = IncidenceSetter( 51 | start_time="2020-03-01", 52 | end_time="2020-03-02", 53 | incidence_per_region=incidence_per_region, 54 | ) 55 | setter.apply(world, policy_simulator) 56 | london = world.regions.get_from_name("London") 57 | ne = world.regions.get_from_name("North East") 58 | 59 | # infected london 60 | infected = 0 61 | for person in london.people: 62 | if person.infected: 63 | infected += 1 64 | assert np.isclose(infected, 0.1 * len(london.people), rtol=1e-2, atol=0) 65 | 66 | # infected north east 67 | infected = 0 68 | for person in ne.people: 69 | if person.infected: 70 | infected += 1 71 | assert np.isclose(infected, 0.01 * len(ne.people), rtol=1e-2, atol=0) 72 | 73 | def test__adding_infections(self, world, policy_simulator): 74 | selector = policy_simulator.epidemiology.infection_selectors[0] 75 | selector.infect_person_at_time( 76 | world.regions.get_from_name("London").people[0], 0.0 77 | ) 78 | selector.infect_person_at_time( 79 | world.regions.get_from_name("North East").people[0], 0.0 80 | ) 81 | setter = IncidenceSetter( 82 | start_time="2020-03-01", 83 | end_time="2020-03-02", 84 | incidence_per_region=incidence_per_region, 85 | ) 86 | setter.apply(world, policy_simulator) 87 | 88 | london = world.regions.get_from_name("London") 89 | ne = world.regions.get_from_name("North East") 90 | 91 | # infected london 92 | infected = 0 93 | for person in london.people: 94 | if person.infected: 95 | infected += 1 96 | assert np.isclose(infected, 0.1 * len(london.people), rtol=1e-2, atol=0) 97 | 98 | # infected north east 99 | infected = 0 100 | for person in ne.people: 101 | if person.infected: 102 | infected += 1 103 | assert np.isclose(infected, 0.01 * len(ne.people), rtol=1e-2, atol=0) 104 | --------------------------------------------------------------------------------