You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Hard Constraints Not Enforcing Maximum Household Weights When USE_HARD_CONSTRAINTS = True #221
TfNSW, RSG, and WSP collaborated on the Sydney ABM model development in 2026. As part of this project, we upgraded PopulationSim to v0.10 and enabled USE_HARD_CONSTRAINTS in the model settings. Based on our configuration:
We therefore expected the maximum allowable household weight to be approximately 51 × 50 = 2,550
However, the final synthesised population contains household weights as high as 53,910, which is more than 20 times the expected cap.
This suggests that the hard constraint mechanism may not be enforcing the maximum expansion factor as intended.
Why This Matters
This is potentially an important limitation for users who apply PopulationSim to expand Household Travel Survey (HTS) data.
Very large record weights can result in a small number of survey households representing a disproportionately large share of the expanded data. This may:
Increase sensitivity to individual records.
Reduce robustness of validation and calibration results.
Undermine one of the key purposes of applying weight caps.
@jfdman
Following our email discussion in February 2026, I've opened this issue to capture the behaviour we're seeing with USE_HARD_CONSTRAINTS and household weight limits. Please add any additional background or details that would be helpful for the developers.
Hi @amsamimi let me know if you want to meet and live-debug, or if you discovered anything new since last week. Otherwise, my leading hunch is that multi-processing is somehow silently passing False to hard constraints so might be worth a quick test with only single thread.
Hi @nick-fournier
I sent a meeting invite for Tuesday at 9:30 AM Sydney time to nick.fournier@rsginc.com. Did you get that?
We can discuss early test strategies and plan our next steps. I'm also available to run tests and explore options to help speed up this fix.
@amsamimi I dug up a few other possible things. I'm actually not so sure it's multi-processing. Two issues are relatively minor and the third is mostly sloppy engineering but not necessarily wrong. Fixing the first two are easy but the third will have a lot of touch points.
The upper bound gets rounded to a whole number (balancing/wrappers.py:88). It rounds the cap and never lets it go below 1. With big survey weights that's ~1% over. With small ones it blows up — a weight of 0.1 gets a cap of 10x. The rounding does nothing useful; the integerizer takes floats and does its own rounding later. Probably not your issue, but is a correctness thing.
Grouped weights are labelled as household weights (steps/setup_data_structures.py:288). When GROUP_BY_INCIDENCE_SIGNATURE is on, identical households get collapsed into groups, but the group id gets stored in a column named hh_id. Join that back to households and you get nonsense. This might be what you're seeing, but not sure. Worth a quick test with GROUP_BY_INCIDENCE_SIGNATURE: False
Grouping also truncates the weights to integers (same file, line 270). There's an astype(np.int64) on the whole table that was meant for the join keys, but it hits sample_weight too. Loses 0.3% of total weight in our example, much more if the survey weights are small.
1 and 3 are easy fixes. 2 is a bigger lift since the column name is being intentionally reused on purpose in a few places. We would need to define a generic column id that isn't overloaded hh_id, but that changes the schema contract for every past weighting, which maybe isn't that bad so long as people don't run first pass with version 0.10 and then run re-pop on version 0.11 or something.
Activity
amsamimi commentedon Aug 18, 2026
@jfdman
Following our email discussion in February 2026, I've opened this issue to capture the behaviour we're seeing with USE_HARD_CONSTRAINTS and household weight limits. Please add any additional background or details that would be helpful for the developers.
FYI @jpn-- @joecastiglione @asiripanich @dhensle @nick-fournier
nick-fournier commentedon Aug 24, 2026
Hi @amsamimi let me know if you want to meet and live-debug, or if you discovered anything new since last week. Otherwise, my leading hunch is that multi-processing is somehow silently passing False to hard constraints so might be worth a quick test with only single thread.
amsamimi commentedon Aug 24, 2026
Hi @nick-fournier
I sent a meeting invite for Tuesday at 9:30 AM Sydney time to nick.fournier@rsginc.com. Did you get that?
We can discuss early test strategies and plan our next steps. I'm also available to run tests and explore options to help speed up this fix.
nick-fournier commentedon Aug 24, 2026
Ah, @amsamimi my current email is nfournier@bayareametro.gov
nick-fournier commentedon Aug 25, 2026
@amsamimi I dug up a few other possible things. I'm actually not so sure it's multi-processing. Two issues are relatively minor and the third is mostly sloppy engineering but not necessarily wrong. Fixing the first two are easy but the third will have a lot of touch points.
The upper bound gets rounded to a whole number (
balancing/wrappers.py:88). It rounds the cap and never lets it go below 1. With big survey weights that's ~1% over. With small ones it blows up — a weight of 0.1 gets a cap of 10x. The rounding does nothing useful; the integerizer takes floats and does its own rounding later. Probably not your issue, but is a correctness thing.Grouped weights are labelled as household weights (
steps/setup_data_structures.py:288). WhenGROUP_BY_INCIDENCE_SIGNATUREis on, identical households get collapsed into groups, but the group id gets stored in a column namedhh_id. Join that back to households and you get nonsense. This might be what you're seeing, but not sure. Worth a quick test withGROUP_BY_INCIDENCE_SIGNATURE: FalseGrouping also truncates the weights to integers (same file, line 270). There's an
astype(np.int64)on the whole table that was meant for the join keys, but it hitssample_weighttoo. Loses 0.3% of total weight in our example, much more if the survey weights are small.1 and 3 are easy fixes. 2 is a bigger lift since the column name is being intentionally reused on purpose in a few places. We would need to define a generic column id that isn't overloaded
hh_id, but that changes the schema contract for every past weighting, which maybe isn't that bad so long as people don't run first pass with version 0.10 and then run re-pop on version 0.11 or something.