Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add age-specific mitigation #742

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions config/nodemon/dev.json
Expand Up @@ -17,6 +17,7 @@
"postcss.config.js",
"schemas",
"stylelint.config.js",
"tools/generateTypes.ts",
"tsconfig.json",
"tslint.json",
"webpack.config.js",
Expand Down
1 change: 1 addition & 0 deletions config/nodemon/prod.json
Expand Up @@ -19,6 +19,7 @@
"src",
"static",
"stylelint.config.js",
"tools/generateTypes.ts",
"tsconfig.json",
"tslint.json",
"webpack.config.js",
Expand Down
22 changes: 22 additions & 0 deletions schemas/MitigationAgeSpecificDatum.yml
@@ -0,0 +1,22 @@
$schema: http://json-schema.org/draft-07/schema
$id: MitigationAgeSpecificDatum
title: MitigationAgeSpecificDatum

type: object

additionalProperties: false

required:
- ageGroup
- enabled
- transmissionReduction

properties:
ageGroup:
$ref: 'AgeGroup#'

enabled:
type: boolean

transmissionReduction:
$ref: 'PercentageRange#'
6 changes: 6 additions & 0 deletions schemas/MitigationInterval.yml
Expand Up @@ -11,6 +11,7 @@ required:
- color
- timeRange
- transmissionReduction
- mitigationAgeSpecificData

properties:
name:
Expand All @@ -25,3 +26,8 @@ properties:

transmissionReduction:
$ref: 'PercentageRange#'

mitigationAgeSpecificData:
type: array
items:
$ref: 'MitigationAgeSpecificDatum#'
2 changes: 1 addition & 1 deletion schemas/_SchemaVer.yml
Expand Up @@ -3,4 +3,4 @@ $id: _SchemaVer
title: _SchemaVer

type: string
const: 2.0.0
const: 2.1.0
17 changes: 12 additions & 5 deletions src/algorithms/initialize.ts
Expand Up @@ -9,6 +9,10 @@ const monthToDay = (m: number) => {
return m * 30 + 15
}

export function sum(arr: number[]): number {
return arr.reduce((a, b) => a + b, 0)
}

const jan2020 = new Date('2020-01-01').valueOf() // time in ms

export function withUncertainty(scenario: ScenarioFlat): boolean {
Expand All @@ -33,13 +37,15 @@ export const msPerDay = 1000 * 60 * 60 * 24
*/
export function infectionRate(
time: number,
avgInfectionRate: number,
infectedFractionByAge: number[],
contactMatrix: number[][],
peakMonth: number,
seasonalForcing: number,
): number {
): number[] {
// this is super hacky
const phase = ((time - jan2020) / msPerDay / 365 - monthToDay(peakMonth) / 365) * 2 * Math.PI
return avgInfectionRate * (1 + seasonalForcing * Math.cos(phase))
const baseValue = 1 + seasonalForcing * Math.cos(phase)
return contactMatrix.map((v) => baseValue * sum(v.map((d, i) => d * infectedFractionByAge[i])))
}

export function getPopulationParams(
Expand Down Expand Up @@ -88,6 +94,7 @@ export function getPopulationParams(
isolated: [],
},
rate: {
contactMatrix: Object.keys(AgeGroup).map(() => Object.keys(AgeGroup).map(() => 1.0)),
latency: 1 / latencyDays,
infection: () => -Infinity, // Dummy infectionRate function. This is set below.
recovery: [],
Expand Down Expand Up @@ -145,8 +152,8 @@ export function getPopulationParams(
const avgInfectionRate = tmpR0 / infectiousPeriodDays

const containment = containmentRealization.length > 1 ? containmentRealization[i] : containmentRealization[0]
elt.rate.infection = (time: number) =>
containment(time) * infectionRate(time, avgInfectionRate, peakMonth, seasonalForcing)
elt.rate.infection = (time: number, infectedFraction: number[]) =>
containment(time) * infectionRate(time, infectedFraction, elt.rate.contactMatrix, avgInfectionRate, peakMonth, seasonalForcing)

return elt
})
Expand Down
15 changes: 6 additions & 9 deletions src/algorithms/model.ts
Expand Up @@ -7,7 +7,7 @@ import {
ExportedTimePoint,
} from './types/Result.types'

import { msPerDay } from './initialize'
import { msPerDay, sum } from './initialize'

const eulerStep = 0.5
export const eulerStepsPerDay = Math.round(1 / eulerStep)
Expand Down Expand Up @@ -73,10 +73,6 @@ function stepODE(pop: SimulationTimePoint, P: ModelParams, dt: number): Simulati
return state
}

export function sum(arr: number[]): number {
return arr.reduce((a, b) => a + b, 0)
}

export function gz(x: number): number {
return x > 0 ? x : 0
}
Expand Down Expand Up @@ -294,16 +290,17 @@ function fluxes(time: number, pop: SimulationTimePoint, P: ModelParams): StateFl
}

// Compute all fluxes (apart from overflow states) barring no hospital bed constraints
const fracInfected = sum(pop.current.infectious) / P.populationServed
const infectionPressure = P.rate.infection(
time,
pop.current.infectious.map((d) => d / P.populationServed),
)

for (let age = 0; age < pop.current.infectious.length; age++) {
// Initialize all multi-faceted states with internal arrays
flux.exposed[age] = Array(pop.current.exposed[age].length)

// Susceptible -> Exposed
flux.susceptible[age] =
P.importsPerDay[age] +
(1 - P.frac.isolated[age]) * P.rate.infection(time) * pop.current.susceptible[age] * fracInfected
flux.susceptible[age] = P.importsPerDay[age] + infectionPressure[age] * pop.current.susceptible[age]

// Exposed -> Internal -> Infectious
pop.current.exposed[age].forEach((exposed, i, exposedArray) => {
Expand Down
4 changes: 4 additions & 0 deletions src/algorithms/run.ts
Expand Up @@ -34,6 +34,10 @@ function simulate(
}

export async function run({ params, severity, ageDistribution }: RunParams): Promise<AlgorithmResult> {
console.log(
JSON.stringify({ 'params.mitigationIntervals.mitigationAgeSpecificData': params.mitigationIntervals }, null, 2),
)

const tMin: number = new Date(params.simulationTimeRange.begin).getTime()
const tMax: number = new Date(params.simulationTimeRange.end).getTime()
const ageGroups = ageDistribution.map((d) => d.ageGroup)
Expand Down
5 changes: 4 additions & 1 deletion src/algorithms/types/Param.types.ts
Expand Up @@ -6,6 +6,7 @@ import type {
ScenarioDatum as ScenarioDatumExternal,
ScenarioDatumMitigation as ScenarioDatumMitigationExternal,
MitigationInterval as MitigationIntervalExternal,
MitigationAgeSpecificDatum as MitigationAgeSpecificDatumExternal,
ScenarioDatumEpidemiological,
ScenarioDatumPopulation,
ScenarioDatumSimulation,
Expand All @@ -18,6 +19,7 @@ import type {
ScenarioDatum,
MitigationInterval,
ScenarioDatumMitigation,
MitigationAgeSpecificDatum,
} from './restricted/ScenarioDatum'

export type {
Expand All @@ -37,7 +39,7 @@ export type { DateRange, NumericRangeNonNegative, PercentageRange } from '../../

export { Convert, AgeGroup } from '../../.generated/latest/types'

export type { ScenarioData, ScenarioDatum, MitigationInterval, ScenarioDatumMitigation }
export type { ScenarioData, ScenarioDatum, MitigationInterval, ScenarioDatumMitigation, MitigationAgeSpecificDatum }

export type { ScenarioDatumEpidemiological, ScenarioDatumPopulation, ScenarioDatumSimulation }

Expand All @@ -47,6 +49,7 @@ export type {
ScenarioDatumExternal,
ScenarioDatumMitigationExternal,
MitigationIntervalExternal,
MitigationAgeSpecificDatumExternal,
}

export interface ScenarioParameters {
Expand Down
3 changes: 2 additions & 1 deletion src/algorithms/types/Result.types.ts
Expand Up @@ -56,8 +56,9 @@ export interface ModelFracs {
}

export interface ModelRates {
contactMatrix: number[][]
latency: number
infection: (t: number) => number
infection: (t: number) => number[]
recovery: number[]
severe: number[]
discharge: number[]
Expand Down
41 changes: 34 additions & 7 deletions src/algorithms/types/convert.ts
Expand Up @@ -2,14 +2,27 @@ import { omit } from 'lodash'

import { uuidv4 } from '../../helpers/uuid'

import { MitigationInterval, MitigationIntervalExternal, ScenarioDatum, ScenarioDatumExternal } from './Param.types'
import {
MitigationAgeSpecificDatum,
MitigationAgeSpecificDatumExternal,
MitigationInterval,
MitigationIntervalExternal,
ScenarioDatum,
ScenarioDatumExternal,
} from './Param.types'

export function addId(interval: MitigationIntervalExternal): MitigationInterval {
return { ...interval, id: uuidv4() }
export function mitigtaionIntervalAgeSpecificToInternal(
mitigationAgeSpecificData: MitigationAgeSpecificDatumExternal[],
): MitigationAgeSpecificDatum[] {
return mitigationAgeSpecificData.map((datum) => ({ ...datum, id: uuidv4() } as MitigationAgeSpecificDatum))
}

export function removeId(interval: MitigationInterval): MitigationIntervalExternal {
return omit(interval, 'id')
export function mitigtaionIntervalToInternal(interval: MitigationIntervalExternal): MitigationInterval {
return {
...interval,
id: uuidv4(),
mitigationAgeSpecificData: mitigtaionIntervalAgeSpecificToInternal(interval.mitigationAgeSpecificData),
}
}

export function toInternal(scenario: ScenarioDatumExternal): ScenarioDatum {
Expand All @@ -18,18 +31,32 @@ export function toInternal(scenario: ScenarioDatumExternal): ScenarioDatum {
...scenario,
mitigation: {
...scenario.mitigation,
mitigationIntervals: mitigationIntervals.map(addId),
mitigationIntervals: mitigationIntervals.map(mitigtaionIntervalToInternal),
},
}
}

export function mitigtaionIntervalAgeSpecificToExternal(
mitigationAgeSpecificData: MitigationAgeSpecificDatum[],
): MitigationAgeSpecificDatumExternal[] {
return mitigationAgeSpecificData.map((datum) => omit(datum, 'id'))
}

export function mitigationIntervalToExternal(interval: MitigationInterval): MitigationIntervalExternal {
const intervalWithoutId = omit(interval, 'id')
return {
...intervalWithoutId,
mitigationAgeSpecificData: mitigtaionIntervalAgeSpecificToExternal(intervalWithoutId.mitigationAgeSpecificData),
}
}

export function toExternal(scenario: ScenarioDatum): ScenarioDatumExternal {
const { mitigationIntervals } = scenario.mitigation
return {
...scenario,
mitigation: {
...scenario.mitigation,
mitigationIntervals: mitigationIntervals.map(removeId),
mitigationIntervals: mitigationIntervals.map(mitigationIntervalToExternal),
},
}
}
6 changes: 6 additions & 0 deletions src/algorithms/types/restricted/ScenarioDatum.ts
Expand Up @@ -7,10 +7,16 @@

import type { Merge, StrictOmit } from 'ts-essentials'
import type { UUIDv4 } from '../../../helpers/uuid'
import { MitigationAgeSpecificDatumExternal } from '../Param.types'
import type { MitigationIntervalExternal, ScenarioDatumExternal, ScenarioDatumMitigationExternal } from '../Param.types'

export interface MitigationAgeSpecificDatum extends MitigationAgeSpecificDatumExternal {
id: UUIDv4
}

export interface MitigationInterval extends MitigationIntervalExternal {
id: UUIDv4
mitigationAgeSpecificData: MitigationAgeSpecificDatum[]
}

export interface ScenarioDatumMitigationInternalMutable extends ScenarioDatumMitigationExternal {
Expand Down
1 change: 1 addition & 0 deletions src/algorithms/utils/suggestNextMitigationInterval.ts
Expand Up @@ -17,6 +17,7 @@ export function suggestNextMitigationInterval(): MitigationInterval {
name: `Intervention from ${tMinMoment.format('D MMM YYYY')}`,
timeRange,
transmissionReduction: { begin: 10, end: 30 },
mitigationAgeSpecificData: [],
}

const color = createColor(interval)
Expand Down