Skip to content

VectorSurveillanceEventCoordinator

The VectorSurveillanceEventCoordinator coordinator class samples the vector population at regular intervals and reports allele frequencies or genome fractions. This coordinator is designed to simulate vector surveillance activities such as mosquito trapping and genetic testing, and to trigger campaign events based on the results. It is configured with a Counter object that specifies the species, gender, sample size, and counting method, and a Responder object that can broadcast an event each time a survey is completed. Sampling is controlled by trigger events: the coordinator begins sampling when an event from Start_Trigger_Condition_List is received, and stops when an event from Stop_Trigger_Condition_List is received or the Duration expires.

The coordinator delegates its response logic to an embedded Python script, dtk_vector_surveillance.py, which must be placed in the simulation working directory. Each time the coordinator samples the vector population, it calls the respond() function in this script, passing the sampled data. The respond() function processes the data and returns a list of coordinator-level event names to broadcast. These events can then trigger other campaign events such as mosquito releases or intervention distributions.

Embedded Python: dtk_vector_surveillance.py

The dtk_vector_surveillance.py file provides three callback functions that the VectorSurveillanceEventCoordinator calls during the simulation. The file must be placed in the simulation working directory alongside the campaign and configuration files.

Required function: respond()

def respond(time, responder_id, coordinator_name, num_vectors_sampled, list_data_names, list_data_values):

This is the main callback, called each time any VectorSurveillanceEventCoordinator in the simulation completes a sampling event. It receives the surveillance results and must return a list of coordinator-level event name strings to broadcast. If no events should be broadcast, return an empty list.

Parameters:

Parameter Type Description
time float The simulation time (in days) when the sampling occurred.
responder_id int A unique integer ID assigned to this responder instance when the coordinator was created. IDs are assigned in order of coordinator creation.
coordinator_name string The Coordinator_Name of the VectorSurveillanceEventCoordinator that performed this sampling. Use this to differentiate between multiple coordinators.
num_vectors_sampled int The number of vectors that were actually sampled (may be less than requested if the population is small).
list_data_names list[str] When Count_Type is ALLELE_FREQ: a list of all allele names present in the vector population (e.g., ["a0", "a1"]). When Count_Type is GENOME_FRACTION: a list of all possible genome strings (e.g., ["X-a0:X-a0", "X-a0:X-a1", "X-a1:X-a1"]). Genomes that are equivalent under allele reordering are grouped together.
list_data_values list[float] The fraction corresponding to each entry in list_data_names. When Count_Type is ALLELE_FREQ: the frequency of each allele at its locus in the sampled population (accounts for two allele copies per vector). When Count_Type is GENOME_FRACTION: the fraction of each genome in the sampled population.

Returns: A list[str] of coordinator-level event names to broadcast. These events must correspond to events used in the campaign file or be defined in Custom_Coordinator_Events in the simulation configuration. If an event name is not recognized, the simulation will fail.

Note

Because all VectorSurveillanceEventCoordinator instances in the simulation share the same respond() function, use the coordinator_name parameter to route logic to the correct coordinator. Assign unique Coordinator_Name values to each coordinator instance to make this straightforward.

Optional function: create_responder()

def create_responder(responder_id, coordinator_name):

Called once when each VectorSurveillanceEventCoordinator is created. Use this to initialize any per-coordinator state (e.g., creating Python objects, opening log files). If your respond() function is stateless, this function can be a no-op or omitted.

Parameters:

Parameter Type Description
responder_id int The unique ID assigned to this responder instance.
coordinator_name string The Coordinator_Name of the coordinator being created.

Optional function: delete_responder()

def delete_responder(responder_id, coordinator_name):

Called when a VectorSurveillanceEventCoordinator expires (after Duration elapses). Use this for cleanup of per-coordinator state. If your respond() function is stateless, this function can be a no-op or omitted.

Parameters:

Parameter Type Description
responder_id int The unique ID of the responder being deleted.
coordinator_name string The Coordinator_Name of the coordinator being deleted.

Example: dtk_vector_surveillance.py

The following example demonstrates a dtk_vector_surveillance.py that handles two coordinators: one monitoring allele frequencies (Frequency_Counter) and one monitoring genome fractions (Genome_Counter). It writes CSV logs of the surveillance data and broadcasts events to trigger mosquito releases when certain thresholds are met.

#!/usr/bin/python

import csv

header_not_needed = []


def write_csv_report(time, coordinator_name, num_vectors_sampled,
                     list_data_names, list_data_values, filename=None):
    """Write surveillance data to a CSV file, creating the header on first call."""
    if not filename:
        filename = f"{coordinator_name}_py_log.csv"
    with open(filename, "a") as csv_log:
        line = f"{time}, {coordinator_name}, {num_vectors_sampled}"
        for i in range(len(list_data_values)):
            line += f",{round(list_data_values[i], 5)}"
        if coordinator_name not in header_not_needed:
            header = "time, coordinator_name, num_vectors_sampled"
            for i in range(len(list_data_names)):
                header += f",{list_data_names[i]}"
            csv_log.write(header + "\n")
            header_not_needed.append(coordinator_name)
        csv_log.write(line + "\n")


def create_responder(responder_id, coordinator_name):
    # Called when VectorSurveillanceEventCoordinator is created.
    # Initialize per-coordinator state here if needed.
    print(f"py: creating responder: {responder_id} - {coordinator_name}")


def delete_responder(responder_id, coordinator_name):
    # Called when VectorSurveillanceEventCoordinator expires.
    # Clean up per-coordinator state here if needed.
    print(f"py: deleting responder: {responder_id} - {coordinator_name}")


def respond(time, responder_id, coordinator_name, num_vectors_sampled,
            list_data_names, list_data_values):
    """
    Called each time any VectorSurveillanceEventCoordinator samples the vectors.
    Returns a list of coordinator-level event names to broadcast.
    """
    event_names = []

    if coordinator_name == "Frequency_Counter":
        # ALLELE_FREQ mode: check individual allele frequencies
        for i in range(len(list_data_names)):
            if (list_data_names[i] == "a1") and (list_data_values[i] < 0.3):
                event_names.append("Release_More_Mosquitoes_a1a1")
        write_csv_report(time, coordinator_name, num_vectors_sampled,
                         list_data_names, list_data_values, filename="freq_log.csv")

    elif coordinator_name == "Genome_Counter":
        # GENOME_FRACTION mode: use a dict for multi-genome threshold logic
        write_csv_report(time, coordinator_name, num_vectors_sampled,
                         list_data_names, list_data_values)
        data = dict(zip(list_data_names, list_data_values))
        genome1 = "X-a0-b0:X-a0-b0"
        genome2 = "X-a0-b1:X-a0-b1"
        genome3 = "X-a0-b0:X-a0-b1"  # grouped with "X-a0-b1:X-a0-b0"
        if data[genome1] > 0.4:
            event_names.append("Release_ind_Events")
        if data[genome3] > data[genome2] or data[genome2] > 0.03:
            event_names.append("Release_More_Mosquitoes_a1b1")

    return event_names

Parameters

Note

Parameters are case-sensitive. For Boolean parameters, set to 1 for true or 0 for false. Minimum, maximum, or default values of "NA" indicate that those values are not applicable for that parameter.

EMOD does not use true defaults; that is, if the dependency relationships indicate that a parameter is required, you must supply a value for it. However, many of the tools used to work with EMOD will use the default values provided below.

JSON format does not permit comments, but you can add "dummy" parameters to add contextual information to your files. Any keys that are not EMOD parameter names will be ignored by the model.

The table below describes all possible parameters with which this class can be configured. The JSON example that follows shows one potential configuration.

Parameter Type Min Max Default Description
Coordinator_Name string NA NA VectorSurveillanceEventCoordinator The name of the event coordinator, which is useful in output reports such as ReportCoordinatorEventRecorder.csv and ReportSurveillanceEventRecorder.csv. EMOD does not ensure that this name is unique.
Count_Type enum NA NA ALLELE_FREQ The attribute to count in the mosquitoes being sampled. Possible values are:
ALLELE_FREQ
Calculates the frequency of every allele in the sampled population. This accounts for the occurrences of the allele where there can be two per vector (i.e. you can get 0, 1, or 2 from each vector).
GENOME_FRACTION
Calculates the fraction of each (grouped by similar) genome in the sampled population.
Counter json object NA NA NA Configuration dictionary of the sampling parameters for the vector population. This object uses the VectorCounter type which includes parameters for species, gender, count type, sample size, and update period.
Duration float -1 3.40282E+38 -1 The number of days from the creation of the coordinator until coordinator expires. Once this number of days has passed, the event coordinator will unregister for events and expire. A value of -1 (the default) keeps the coordinator running indefinitely.
Gender enum NA NA VECTOR_FEMALE The sex of the vectors to sample. Possible values are:
VECTOR_MALE
VECTOR_FEMALE
* VECTOR_BOTH_GENDERS
Responder json object NA NA NA A JSON object for specifying additional parameters on how the coordinator reacts to the counting of the vectors. This is in addition to the coordinator broadcasting the coordinator events returned by the user's Python code.
Sample_Size_Constant float 0 3.40282E+38 6 The number of vectors to sample when Sample_Size_Distribution is set to CONSTANT_DISTRIBUTION. If the population is less than this number, then the entire population will be selected.
Sample_Size_Distribution enum NA NA NOT_INITIALIZED The distribution type to use for determining the number of vectors in the sample for each sampling. If the population is less than the drawn number, then the entire population will be selected.
Possible values are:
NOT_INITIALIZED
No distribution set.
CONSTANT_DISTRIBUTION
Use the same value for each sampling. Set Sample_Size_Constant.
UNIFORM_DISTRIBUTION
Use a uniform distribution with a given minimum and maximum. Set Sample_Size_Max and Sample_Size_Min.
GAUSSIAN_DISTRIBUTION
The distribution is Gaussian (or normal). Values are resampled to ensure >= 0. Set Sample_Size_Gaussian_Mean and Sample_Size_Gaussian_Std_Dev.
EXPONENTIAL_DISTRIBUTION
The distribution is exponential with a given mean. Set Sample_Size_Exponential.
WEIBULL_DISTRIBUTION
Use a Weibull distribution with a given shape and scale. Set Sample_Size_Kappa and Sample_Size_Lambda.
LOG_NORMAL_DISTRIBUTION
Use a log-normal distribution with a given mean and width. Set Sample_Size_Log_Normal_Mu and Sample_Size_Log_Normal_Sigma.
POISSON_DISTRIBUTION
Use a Poisson distribution with a given mean. Set Sample_Size_Poisson_Mean.
DUAL_CONSTANT_DISTRIBUTION
Use a distribution where some instances are set to a value of zero and the rest to a given value. Set Sample_Size_Proportion_0 and Sample_Size_Peak_2_Value. This distribution does not use the parameters set for CONSTANT_DISTRIBUTION.
DUAL_EXPONENTIAL_DISTRIBUTION
Use two exponential distributions with given means. Set Sample_Size_Mean_1, Sample_Size_Mean_2, and Sample_Size_Proportion_1. This distribution does not use the parameters set for EXPONENTIAL_DISTRIBUTION.
Sample_Size_Exponential float 0 3.40282E+38 6 The mean sample size when Sample_Size_Distribution is set to EXPONENTIAL_DISTRIBUTION.
Sample_Size_Gaussian_Mean float 0 3.40282E+38 6 The mean sample size when Sample_Size_Distribution is set to GAUSSIAN_DISTRIBUTION.
Sample_Size_Gaussian_Std_Dev float 1.17549E-38 3.40282E+38 1 The standard deviation of the sample size when Sample_Size_Distribution is set to GAUSSIAN_DISTRIBUTION.
Sample_Size_Kappa float 1.17549E-38 3.40282E+38 1 The shape value for the sample size when Sample_Size_Distribution is set to WEIBULL_DISTRIBUTION.
Sample_Size_Lambda float 1.17549E-38 3.40282E+38 1 The scale value for the sample size when Sample_Size_Distribution is set to WEIBULL_DISTRIBUTION.
Sample_Size_Log_Normal_Mu float -3.40282E+38 3.40282E+38 6 The mean of the sample size when Sample_Size_Distribution is set to LOG_NORMAL_DISTRIBUTION.
Sample_Size_Log_Normal_Sigma float -3.40282E+38 3.40282E+38 1 The width of the sample size when Sample_Size_Distribution is set to LOG_NORMAL_DISTRIBUTION.
Sample_Size_Max float 0 3.40282E+38 1 The maximum sample size when Sample_Size_Distribution is set to UNIFORM_DISTRIBUTION.
Sample_Size_Mean_1 float 1.17549E-38 3.40282E+38 1 The mean of the first exponential distribution when Sample_Size_Distribution is set to DUAL_EXPONENTIAL_DISTRIBUTION.
Sample_Size_Mean_2 float 1.17549E-38 3.40282E+38 1 The mean of the second exponential distribution when Sample_Size_Distribution is set to DUAL_EXPONENTIAL_DISTRIBUTION.
Sample_Size_Min float 0 3.40282E+38 0 The minimum sample size when Sample_Size_Distribution is set to UNIFORM_DISTRIBUTION.
Sample_Size_Peak_2_Value float 0 3.40282E+38 1 The sample size value to assign to the remaining instances when Sample_Size_Distribution is set to DUAL_CONSTANT_DISTRIBUTION.
Sample_Size_Poisson_Mean float 0 3.40282E+38 6 The mean sample size when Sample_Size_Distribution is set to POISSON_DISTRIBUTION.
Sample_Size_Proportion_0 float 0 1 1 The proportion of instances to assign a value of zero when Sample_Size_Distribution is set to DUAL_CONSTANT_DISTRIBUTION.
Sample_Size_Proportion_1 float 0 1 1 The proportion of instances in the first exponential distribution when Sample_Size_Distribution is set to DUAL_EXPONENTIAL_DISTRIBUTION.
Species string NA NA UNINITIALIZED STRING The name of the vector species to sample. This must match a species name defined in the Vector_Species_Params configuration parameters.
Start_Trigger_Condition_List array of strings NA NA [] A list of coordinator events that when heard will start the VectorSurveillanceEventCoordinator sampling every Update_Period, starting at the time the trigger is received. The events must be defined in Custom_Coordinator_Events in the simulation configuration file.
Stop_Trigger_Condition_List array of strings NA NA [] A list of coordinator events that when heard will cause the Counter to stop sampling and will keep the Responder from responding. The coordinator does not expire until the Duration has expired. The list can be empty. The events must be defined in Custom_Coordinator_Events in the simulation configuration file.
Survey_Completed_Event string NA NA "" Optional coordinator-level event that will be broadcast every time the VectorSurveillanceEventCoordinator surveys the vector population. The event must be defined in Custom_Coordinator_Events in the simulation configuration file.
Update_Period float 0 999999 30 The number of days between sampling of the mosquito population. If the mosquitoes are sampled on day 1 and the period is 30, then the next sample will be taken on day 31.
{
    "Use_Defaults": 1,
    "Events": [
        {
            "class": "CampaignEvent",
            "Start_Day": 1,
            "Nodeset_Config": {
                "class": "NodeSetAll"
            },
            "Event_Coordinator_Config": {
                "class": "VectorSurveillanceEventCoordinator",
                "Coordinator_Name": "Allele_Frequency_Monitor",
                "Duration": -1,
                "Start_Trigger_Condition_List": [
                    "Start_Vector_Surveillance"
                ],
                "Stop_Trigger_Condition_List": [],
                "Counter": {
                    "Count_Type": "ALLELE_FREQ",
                    "Species": "gambiae",
                    "Gender": "VECTOR_FEMALE",
                    "Sample_Size_Distribution": "CONSTANT_DISTRIBUTION",
                    "Sample_Size_Constant": 100,
                    "Update_Period": 30
                },
                "Responder": {
                    "Survey_Completed_Event": "Vector_Survey_Done"
                }
            }
        }
    ]
}