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

Add functionality for extracting households #144

Merged
merged 3 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: minor
changes:
added:
- Simulation helper to extract individual households from a microsimulation.
110 changes: 110 additions & 0 deletions policyengine_core/simulations/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1182,3 +1182,113 @@
new_value = alt_sim.calculate(variable, period)
difference = new_value - original_value
return difference / delta

def sample_person(self) -> dict:
"""
Sample a person from the simulation. Returns a situation JSON with their inputs (including their containing entities).

Returns:
dict: A dictionary containing the person's values.
"""
person_count = self.persons.count
index = np.random.randint(person_count)
return self.extract_person(index)

Check warning on line 1195 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1193-L1195

Added lines #L1193 - L1195 were not covered by tests

def extract_person(
self,
index: int = 0,
exclude_entities: tuple = ("state",),
) -> dict:
"""
Extract a person from the simulation. Returns a situation JSON with their inputs (including their containing entities).

Args:
index (int): The index of the person to extract.

Returns:
dict: A dictionary containing the person's values.
"""
situation = {}
people_indices = []
people_indices_by_entity = {}

Check warning on line 1213 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1211-L1213

Added lines #L1211 - L1213 were not covered by tests

for population in self.populations.values():
entity = population.entity

Check warning on line 1216 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1216

Added line #L1216 was not covered by tests
if (
not population.entity.is_person
and entity.key not in exclude_entities
):
situation[entity.plural] = {

Check warning on line 1221 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1221

Added line #L1221 was not covered by tests
entity.key: {
"members": [],
},
}
group_index = population.members_entity_id[index]

Check warning on line 1226 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1226

Added line #L1226 was not covered by tests
other_people_indices = [
index
for index in range(len(population.members_entity_id))
if population.members_entity_id[index] == group_index
]

people_indices.extend(other_people_indices)
people_indices = list(set(people_indices))
people_indices_by_entity[entity.key] = other_people_indices

Check warning on line 1235 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1233-L1235

Added lines #L1233 - L1235 were not covered by tests
for variable in self.input_variables:
if (
self.tax_benefit_system.get_variable(
variable
).entity.key
== entity.key
):
known_periods = self.get_holder(

Check warning on line 1243 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1243

Added line #L1243 was not covered by tests
variable
).get_known_periods()
if len(known_periods) > 0:
value = self.get_holder(variable).get_array(

Check warning on line 1247 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1247

Added line #L1247 was not covered by tests
known_periods[0]
)[group_index]
situation[entity.plural][entity.key][variable] = {

Check warning on line 1250 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1250

Added line #L1250 was not covered by tests
str(known_periods[0]): value
}

person = self.populations["person"].entity
situation[person.plural] = {}

Check warning on line 1255 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1254-L1255

Added lines #L1254 - L1255 were not covered by tests
for person_index in people_indices:
person_name = f"{person.key}_{person_index + 1}"

Check warning on line 1257 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1257

Added line #L1257 was not covered by tests
for entity_key in people_indices_by_entity:
entity = self.populations[entity_key].entity

Check warning on line 1259 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1259

Added line #L1259 was not covered by tests
if person_index in people_indices_by_entity[entity.key]:
situation[entity.plural][entity.key]["members"].append(

Check warning on line 1261 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1261

Added line #L1261 was not covered by tests
person_name
)
situation[person.plural][person_name] = {}

Check warning on line 1264 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1264

Added line #L1264 was not covered by tests
for variable in self.input_variables:
if (
self.tax_benefit_system.get_variable(variable).entity.key
== person.key
):
known_periods = self.get_holder(

Check warning on line 1270 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1270

Added line #L1270 was not covered by tests
variable
).get_known_periods()
if len(known_periods) > 0:
value = self.get_holder(variable).get_array(

Check warning on line 1274 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1274

Added line #L1274 was not covered by tests
known_periods[0]
)[person_index]
situation[person.plural][person_name][variable] = {

Check warning on line 1277 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1277

Added line #L1277 was not covered by tests
str(known_periods[0]): value
}

return json.loads(json.dumps(situation, cls=NpEncoder))

Check warning on line 1281 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1281

Added line #L1281 was not covered by tests


class NpEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)

Check warning on line 1287 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1287

Added line #L1287 was not covered by tests
if isinstance(obj, np.floating):
return float(obj)

Check warning on line 1289 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1289

Added line #L1289 was not covered by tests
if isinstance(obj, np.bool_):
return bool(obj)

Check warning on line 1291 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1291

Added line #L1291 was not covered by tests
if isinstance(obj, np.ndarray):
return obj.tolist()
return str(obj)

Check warning on line 1294 in policyengine_core/simulations/simulation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/simulations/simulation.py#L1293-L1294

Added lines #L1293 - L1294 were not covered by tests
2 changes: 2 additions & 0 deletions policyengine_core/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from policyengine_core.enums import EnumArray

from .test_from_situation import generate_test_from_situation


def assert_near(
value,
Expand Down
23 changes: 23 additions & 0 deletions policyengine_core/tools/test_from_situation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import yaml
from pathlib import Path
import numpy as np
import json


def generate_test_from_situation(situation: dict, file_path: str):
"""Generate a test from a situation.

Args:
situation (dict): The situation to generate the test from.
test_name (str): The name of the test.
"""

yaml_contents = [

Check warning on line 15 in policyengine_core/tools/test_from_situation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/tools/test_from_situation.py#L15

Added line #L15 was not covered by tests
{
"input": situation,
"output": {},
}
]

with open(Path(file_path), "w+") as f:
yaml.dump(yaml_contents, f)

Check warning on line 23 in policyengine_core/tools/test_from_situation.py

View check run for this annotation

Codecov / codecov/patch

policyengine_core/tools/test_from_situation.py#L23

Added line #L23 was not covered by tests