The following classes can be used to enhance the selection of people when distributing
interventions. Most event coordinators and node-level interventions that distribute
interventions to people have a parameter called Targeting_Config. This allows you to
not only target individuals based on their gender, age, and IndividualProperties
(See NodeProperties and IndividualProperties parameters for more information), but
also on things such as whether or not they have a particular intervention or are in
a relationship.
Below is the JSON for a simple example where we want to distribute a vaccine to 20%
of the people that do not already have the vaccine on the 100th day of the simulation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | {
"class": "CampaignEvent",
"Start_Day": 100,
"Nodeset_Config": {
"class": "NodeSetAll"
},
"Event_Coordinator_Config": {
"class": "StandardInterventionDistributionEventCoordinator",
"Target_Demographic": "Everyone",
"Demographic_Coverage": 0.2,
"Targeting_Config": {
"class": "HasIntervention",
"Is_Equal_To": 0,
"Intervention_Name": "MyVaccine"
},
"Intervention_Config": {
"class": "SimpleVaccine",
"Intervention_Name" : "MyVaccine",
"Cost_To_Consumer": 1,
"Vaccine_Take": 1,
"Vaccine_Type": "AcquisitionBlocking",
"Waning_Config": {
"class": "WaningEffectConstant",
"Initial_Effect" : 1.0
}
}
}
}
|
Below is a slightly more complicated example where we want to distribute a diagnostic
to people that are either high risk or have not been vaccinated.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 | {
"class": "CampaignEvent",
"Start_Day": 100,
"Nodeset_Config": {
"class": "NodeSetAll"
},
"Event_Coordinator_Config": {
"class": "StandardInterventionDistributionEventCoordinator",
"Target_Demographic": "Everyone",
"Demographic_Coverage": 0.2,
"Targeting_Config": {
"class" : "TargetingLogic",
"Logic" : [
[
{
"class": "HasIntervention",
"Is_Equal_To": 0,
"Intervention_Name": "MyVaccine"
}
],
[
{
"class": "HasIP",
"Is_Equal_To": 1,
"IP_Key_Value": "Risk:HIGH"
}
]
]
},
"Intervention_Config": {
"class": "SimpleDiagnostic",
"Treatment_Fraction": 1.0,
"Base_Sensitivity": 1.0,
"Base_Specificity": 1.0,
"Event_Or_Config": "Event",
"Positive_Diagnosis_Event": "TestedPositive"
}
}
}
|
The classes of emodpy are intended to make it easier for users to create complex logic and
reduce the burden of trying to create this complex logic in JSON. Below is the python
configuration logic for the two examples above:
| # Example 1: Does not have MyVaccine
targeting_config = ~HasIntervention( intervention_name="MyVaccine" )
# Example 2: Does not have MyVaccine OR is high risk
targeting_config = ~HasIntervention( intervention_name="MyVaccine" ) | HasIP( ip_key_value="Risk:HIGH" )
|
Notice that this logic uses the bitwise operators instead of the logical operators.
Python does not allow you to override the logical operators so the bitwise operators
were the next best thing to allow simple notation. The bitwise operators are:
- '~' - use instead of "not" to logically invert the logical check
- '&' - use instead of "and" to logically AND two logical checks
- '|' - use instead of "or" to logically OR two logical checks
- '^' - XOR - NOT SUPPORTED
- '<<' - Left Shift - NOT SUPPORTED
- '>>' - Right Shift - NOT SUPPORTED
The order of operations for bitwise operators is the same as for logical operators.
For the operators we support, the following order of operations is followed:
- Parentheses
- '~' - NOT
- '&' - AND
- '|' - OR
Please note that the bitwise operations should not change objects directly. You expect
them to return a new object with the operation. For example, if you have A_prime = ~A,
then you expect A_prime to be the inverse of A but you don't expect A to have changed.
AbstractTargetingConfig
Bases: ABC
The AbstractTargetingConfig is defines the interface that all targeting config
classes must implement. This class is needed to tie the TargetingLogic and
BaseTargetingConfig classes together.
The subclass is responsible for setting the name of the EMOD class.
This name does not need to be the same as the python class, but it must
match what is used in EMOD.
is_equal_to
This is a parameter in all of EMOD's Targeting_Config classes. The check
performed by the class is compared with the value of this parameter.
For example, if using HasIP with ip_key_value = "Risk:HIGH" and
is_equal_to = 0, individuals who do NOT have Risk = HIGH will be selected.
If is_equal_to = 1, then individuals who DO have Risk = HIGH will be selected.
Source code in emodpy/utils/targeting_config.py
| class AbstractTargetingConfig(ABC):
"""
The AbstractTargetingConfig is defines the interface that all targeting config
classes must implement. This class is needed to tie the TargetingLogic and
BaseTargetingConfig classes together.
class_name: The subclass is responsible for setting the name of the EMOD class.
This name does not need to be the same as the python class, but it must
match what is used in EMOD.
is_equal_to:
This is a parameter in all of EMOD's Targeting_Config classes. The check
performed by the class is compared with the value of this parameter.
For example, if using HasIP with ip_key_value = "Risk:HIGH" and
is_equal_to = 0, individuals who do NOT have Risk = HIGH will be selected.
If is_equal_to = 1, then individuals who DO have Risk = HIGH will be selected.
"""
def __init__(self):
self.class_name = "Unknown"
self.is_equal_to = 1
@abstractmethod
def __eq__(self, other):
"""
Return true if the 'other' object has the same set and values of internal variables.
"""
pass
def __invert__(self):
"""
Return a new object with the reverse equality using the '~' operator.
"""
copy_obj = copy.deepcopy(self)
copy_obj.is_equal_to = 0 if copy_obj.is_equal_to == 1 else 1
return copy_obj
@abstractmethod
def __and__(self, right):
"""
Return a new object that contains the logical and'ing of this object with
the object on the right of the '&' operand.
"""
pass
@abstractmethod
def __or__(self, right):
"""
Return a new object that contains the logical or'ing of this object with
the object on the right of the '|' operand.
"""
pass
def __xor__(self, value):
"""
NOT SUPPORTED
"""
raise ValueError("The '^' (XOR) operator is not supported.")
def __lshift__(self, value):
"""
NOT SUPPORTED
"""
raise ValueError("The '<<' (left shift) operator is not supported.")
def __rshift__(self, value):
"""
NOT SUPPORTED
"""
raise ValueError("The '>>' (right shift) operator is not supported.")
def to_schema_dict(self, campaign):
"""
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(ReadOnlyDict): Dict object created by schema_to_class
"""
tc_obj = s2c.get_class_with_defaults(self.class_name, schema_json=campaign.get_schema())
tc_obj.Is_Equal_To = self.is_equal_to
return tc_obj
def _clean_dict(self, read_only_dict):
"""
Convert the ReadOnlyDict to a standard dictionary and strip extra stuff
like 'schema' and 'explicits' so that it looks like the JSON for EMOD.
"""
ret_dict = dict(read_only_dict)
ret_dict.pop("schema")
if "explicits" in ret_dict:
ret_dict.pop("explicits")
if "implicits" in ret_dict:
ret_dict.pop("implicits")
return ret_dict
def to_simple_dict(self, campaign):
"""
Return a plain/simple dictionary of the expected JSON for EMOD. The main
purpose of this is for validation in testing. We need the ability to see
that the logic written in python is translated to the JSON correctly.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(dict): A dictionary containing the data for EMOD.
"""
tc_obj = self.to_schema_dict(campaign)
tc_dict = self._clean_dict(tc_obj)
return tc_dict
|
__and__(right)
abstractmethod
Return a new object that contains the logical and'ing of this object with
the object on the right of the '&' operand.
Source code in emodpy/utils/targeting_config.py
| @abstractmethod
def __and__(self, right):
"""
Return a new object that contains the logical and'ing of this object with
the object on the right of the '&' operand.
"""
pass
|
__eq__(other)
abstractmethod
Return true if the 'other' object has the same set and values of internal variables.
Source code in emodpy/utils/targeting_config.py
| @abstractmethod
def __eq__(self, other):
"""
Return true if the 'other' object has the same set and values of internal variables.
"""
pass
|
__invert__()
Return a new object with the reverse equality using the '~' operator.
Source code in emodpy/utils/targeting_config.py
| def __invert__(self):
"""
Return a new object with the reverse equality using the '~' operator.
"""
copy_obj = copy.deepcopy(self)
copy_obj.is_equal_to = 0 if copy_obj.is_equal_to == 1 else 1
return copy_obj
|
__lshift__(value)
NOT SUPPORTED
Source code in emodpy/utils/targeting_config.py
| def __lshift__(self, value):
"""
NOT SUPPORTED
"""
raise ValueError("The '<<' (left shift) operator is not supported.")
|
__or__(right)
abstractmethod
Return a new object that contains the logical or'ing of this object with
the object on the right of the '|' operand.
Source code in emodpy/utils/targeting_config.py
| @abstractmethod
def __or__(self, right):
"""
Return a new object that contains the logical or'ing of this object with
the object on the right of the '|' operand.
"""
pass
|
__rshift__(value)
NOT SUPPORTED
Source code in emodpy/utils/targeting_config.py
| def __rshift__(self, value):
"""
NOT SUPPORTED
"""
raise ValueError("The '>>' (right shift) operator is not supported.")
|
__xor__(value)
NOT SUPPORTED
Source code in emodpy/utils/targeting_config.py
| def __xor__(self, value):
"""
NOT SUPPORTED
"""
raise ValueError("The '^' (XOR) operator is not supported.")
|
to_schema_dict(campaign)
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Parameters:
| Name |
Type |
Description |
Default |
campaign
|
api_campaign
|
The campaign module that has the path to the schema
|
required
|
Returns:
| Type |
Description |
ReadOnlyDict
|
Dict object created by schema_to_class
|
Source code in emodpy/utils/targeting_config.py
| def to_schema_dict(self, campaign):
"""
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(ReadOnlyDict): Dict object created by schema_to_class
"""
tc_obj = s2c.get_class_with_defaults(self.class_name, schema_json=campaign.get_schema())
tc_obj.Is_Equal_To = self.is_equal_to
return tc_obj
|
to_simple_dict(campaign)
Return a plain/simple dictionary of the expected JSON for EMOD. The main
purpose of this is for validation in testing. We need the ability to see
that the logic written in python is translated to the JSON correctly.
Parameters:
| Name |
Type |
Description |
Default |
campaign
|
api_campaign
|
The campaign module that has the path to the schema
|
required
|
Returns:
| Type |
Description |
dict
|
A dictionary containing the data for EMOD.
|
Source code in emodpy/utils/targeting_config.py
| def to_simple_dict(self, campaign):
"""
Return a plain/simple dictionary of the expected JSON for EMOD. The main
purpose of this is for validation in testing. We need the ability to see
that the logic written in python is translated to the JSON correctly.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(dict): A dictionary containing the data for EMOD.
"""
tc_obj = self.to_schema_dict(campaign)
tc_dict = self._clean_dict(tc_obj)
return tc_dict
|
BaseTargetingConfig
Bases: AbstractTargetingConfig
The BaseTargetingConfig class should used as the base class for all of the
Targeting_Config classes. The main job of the subclasses is to maintain
the extra data needed by the class in EMOD to perform the check. For example,
HasIP needs to know the IP key:value so that in EMOD the class can check if
the individual has the given IP. HasIP is responsible for making sure it
is translated in the EMOD configuration.
Source code in emodpy/utils/targeting_config.py
| class BaseTargetingConfig(AbstractTargetingConfig):
"""
The BaseTargetingConfig class should used as the base class for all of the
Targeting_Config classes. The main job of the subclasses is to maintain
the extra data needed by the class in EMOD to perform the check. For example,
HasIP needs to know the IP key:value so that in EMOD the class can check if
the individual has the given IP. HasIP is responsible for making sure it
is translated in the EMOD configuration.
"""
def __init__(self):
super().__init__()
def __eq__(self, other):
"""
Return true if the 'other' object has the same set and values of internal variables.
"""
if not isinstance(other, BaseTargetingConfig):
return False
else:
return self.__dict__ == other.__dict__
def __and__(self, right):
"""
Return a new object that contains the logical and'ing of this object with
the object on the right of the '&' operand.
"""
if not isinstance(right, AbstractTargetingConfig):
raise ValueError("The object on the right of the '&' is not of type 'AbstractTargetingConfig'.")
elif isinstance(right, _TargetingLogic) and right.is_equal_to == 1:
# See !!!Inversion and TargetingLogic!!! above
return right.pre_and(self)
else:
tl = _TargetingLogic(is_and=True, left=self, right=right)
return tl
def __or__(self, right):
"""
Return a new object that contains the logical or'ing of this object with
the object on the right of the '|' operand.
"""
if not isinstance(right, AbstractTargetingConfig):
raise ValueError("The object on the right of the '|' is not of type 'AbstractTargetingConfig'.")
elif isinstance(right, _TargetingLogic) and right.is_equal_to == 1:
# See !!!Inversion and TargetingLogic!!! above
return right.pre_or(self)
else:
tl = _TargetingLogic(is_and=False, left=self, right=right)
return tl
|
__and__(right)
Return a new object that contains the logical and'ing of this object with
the object on the right of the '&' operand.
Source code in emodpy/utils/targeting_config.py
| def __and__(self, right):
"""
Return a new object that contains the logical and'ing of this object with
the object on the right of the '&' operand.
"""
if not isinstance(right, AbstractTargetingConfig):
raise ValueError("The object on the right of the '&' is not of type 'AbstractTargetingConfig'.")
elif isinstance(right, _TargetingLogic) and right.is_equal_to == 1:
# See !!!Inversion and TargetingLogic!!! above
return right.pre_and(self)
else:
tl = _TargetingLogic(is_and=True, left=self, right=right)
return tl
|
__eq__(other)
Return true if the 'other' object has the same set and values of internal variables.
Source code in emodpy/utils/targeting_config.py
| def __eq__(self, other):
"""
Return true if the 'other' object has the same set and values of internal variables.
"""
if not isinstance(other, BaseTargetingConfig):
return False
else:
return self.__dict__ == other.__dict__
|
__or__(right)
Return a new object that contains the logical or'ing of this object with
the object on the right of the '|' operand.
Source code in emodpy/utils/targeting_config.py
| def __or__(self, right):
"""
Return a new object that contains the logical or'ing of this object with
the object on the right of the '|' operand.
"""
if not isinstance(right, AbstractTargetingConfig):
raise ValueError("The object on the right of the '|' is not of type 'AbstractTargetingConfig'.")
elif isinstance(right, _TargetingLogic) and right.is_equal_to == 1:
# See !!!Inversion and TargetingLogic!!! above
return right.pre_or(self)
else:
tl = _TargetingLogic(is_and=False, left=self, right=right)
return tl
|
HasIP
Bases: BaseTargetingConfig
This determines if the person has a particular value of a particular IndividualProperties (IP).
This is especially needed when determining if a partner has a particular IP
(see emodpy-hiv.utils.targeting_config.HasRelationship).
An IndividualProperties Key:Value pair where the key/property name and one of its
values is separated by a colon (':'). This cannot be an empty string.
Source code in emodpy/utils/targeting_config.py
| class HasIP(BaseTargetingConfig):
"""
This determines if the person has a particular value of a particular IndividualProperties (IP).
This is especially needed when determining if a partner has a particular IP
(see emodpy-hiv.utils.targeting_config.HasRelationship).
ip_key_value: An IndividualProperties Key:Value pair where the key/property name and one of its
values is separated by a colon (':'). This cannot be an empty string.
"""
def __init__(self, ip_key_value):
super().__init__()
self.class_name = "HasIP"
if not ip_key_value:
raise ValueError("'ip_key_value' must be a non-zero length string.")
self.ip_key_value = validate_key_value_pair(ip_key_value)
def to_schema_dict(self, campaign):
"""
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(ReadOnlyDict): Dict object created by schema_to_class
"""
tc_obj = super().to_schema_dict(campaign)
tc_obj.IP_Key_Value = self.ip_key_value
return tc_obj
|
to_schema_dict(campaign)
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Parameters:
| Name |
Type |
Description |
Default |
campaign
|
api_campaign
|
The campaign module that has the path to the schema
|
required
|
Returns:
| Type |
Description |
ReadOnlyDict
|
Dict object created by schema_to_class
|
Source code in emodpy/utils/targeting_config.py
| def to_schema_dict(self, campaign):
"""
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(ReadOnlyDict): Dict object created by schema_to_class
"""
tc_obj = super().to_schema_dict(campaign)
tc_obj.IP_Key_Value = self.ip_key_value
return tc_obj
|
HasIntervention
Bases: BaseTargetingConfig
This check determines whether or not the individual has an intervention with the given name.
This will only work for interventions that persist like SimpleVaccine and DelayedIntervention.
It will not work for interventions like BroadcastEvent since it does not persist.
The name of the intervention the person should have. This cannot be an empty
string but should be either the name of the intervention class or the name given to the
intervention of interest. EMOD does not verify that this name exists or is used in your
campaign.
Source code in emodpy/utils/targeting_config.py
| class HasIntervention(BaseTargetingConfig):
"""
This check determines whether or not the individual has an intervention with the given name.
This will only work for interventions that persist like SimpleVaccine and DelayedIntervention.
It will not work for interventions like BroadcastEvent since it does not persist.
intervention_name: The name of the intervention the person should have. This cannot be an empty
string but should be either the name of the intervention class or the name given to the
intervention of interest. EMOD does not verify that this name exists or is used in your
campaign.
"""
def __init__(self, intervention_name):
super().__init__()
self.class_name = "HasIntervention"
if not intervention_name:
raise ValueError("'intervention_name' must be a non-zero length string.")
self.intervention_name = validate_intervention_name(intervention_name)
def to_schema_dict(self, campaign):
"""
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(ReadOnlyDict): Dict object created by schema_to_class
"""
tc_obj = super().to_schema_dict(campaign)
tc_obj.Intervention_Name = self.intervention_name
return tc_obj
|
to_schema_dict(campaign)
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Parameters:
| Name |
Type |
Description |
Default |
campaign
|
api_campaign
|
The campaign module that has the path to the schema
|
required
|
Returns:
| Type |
Description |
ReadOnlyDict
|
Dict object created by schema_to_class
|
Source code in emodpy/utils/targeting_config.py
| def to_schema_dict(self, campaign):
"""
Create the ReadOnlyDict object representation of this Targeting_Config logic.
This is the dictionary used to generate the JSON for EMOD.
Args:
campaign (api_campaign): The campaign module that has the path to the schema
Returns:
(ReadOnlyDict): Dict object created by schema_to_class
"""
tc_obj = super().to_schema_dict(campaign)
tc_obj.Intervention_Name = self.intervention_name
return tc_obj
|
IsPregnant
Bases: BaseTargetingConfig
Select the individual based on whether or not they are pregnant.
Source code in emodpy/utils/targeting_config.py
| class IsPregnant(BaseTargetingConfig):
"""
Select the individual based on whether or not they are pregnant.
"""
def __init__(self):
super().__init__()
self.class_name = "IsPregnant"
|