EU 2011, 100 m, GeoDms 20.19.1.m on main @ 735cad7.
/Analysis is handed 445 356 960 persons and writes 443 008 640. 2 348 320 people are in the input map and in no output map. It is not rounding and it is not spread thinly: it is fully accounted for by grid cells whose balancing factor A_i ends at exactly 0.
| measured over the EU run |
value |
sum(Checks/P_r), the ARDECO input |
445 356 960 |
sum(Result/X_asr/Total), the output |
443 008 640 |
| difference |
2 348 320 |
sum(P_i) over the cells where Result/A_i == 0 |
2 348 314 |
LAUs with Checks/Qr_0_and_Pr_gt0 |
930 |
of those, LAUs where Share_LT15_LAU + Share_15_64_LAU + Share_GE65_LAU == 0 |
928 |
The 6-person gap between rows 3 and 4 is float32 rounding over 97 428 sums; per country the two agree to within a person (FR -2 318 976 vs -2 318 974, DE -26 632 vs -26 635, EL -1 980 vs -1 980, LT -667 vs -667, everything else nets to zero).
Mechanism
Checks/Share_<broad>_LAU is the per-LAU age structure that E_ib falls back on for 1 km cells where ESTAT has no age data:
attribute<Share> Share_LT15_LAU (r) := (sum(<raw census pop, LT15>, LAU_rel) / Q_asr/Total)[Share];
The numerator is the raw census from Population/AGEGROUP5_SEX_perLAU; the denominator is Q_asr/Total, which already carries the NUTS3 fallback of #12. For a LAU with no census the numerator is 0 while the denominator is P_r > 0, so all three broad shares come out 0. That is the case for 928 of the 930 LAUs on the fallback.
E_ib then hands 0 to every cell in those LAUs where ESTAT_Age_0_and_with_ARDECO_pop holds (158 728 cells EU-wide), and:
A_i_raw = P_i / sum_as(E_is * E_ib * B_asr) -> division by zero
A_i = MakeDefined(A_i_raw, 0f) -> 0
X_asi = E_is * E_ib * A_i * B_asr -> 0
so the cell's population disappears. The NUTS3 fallback repairs the target Q_asr but not the shares E_ib uses, and the two disagree in exactly the LAUs where the fallback was needed.
Where it lands
47 LAUs lose more than 100 persons, and 43 of them have POP_2021 = 0 in the LAU gpkg:
| GISCO_ID |
lost |
input P_r |
POP_2021 |
LAU_NAME |
FR_75056 |
2 318 836 |
2 318 836 |
2 187 526 |
Paris (#19) |
DE_03153504 |
2 111 |
3 757 |
0 |
Harz (Landkreis Goslar), gemfr. Gebiet |
DE_09188451 |
1 800 |
3 532 |
0 |
Starnberger See |
EL_99010000 |
1 798 |
1 798 |
1 811 |
Ψευδοκοινοτητα Αγιο Ορος (Αυτοδιοίκητο) |
DE_09184452 |
1 532 |
2 535 |
0 |
Forstenrieder Park |
DE_09184454 |
1 442 |
2 231 |
0 |
Grünwalder Forst |
DE_03159501 |
1 205 |
3 610 |
0 |
Harz (Landkreis Göttingen), gemfr. Geb. |
DE_01053105 |
1 072 |
2 066 |
0 |
Sachsenwald (Forstgutsbez.),gemfr.Geb. |
The German entries are almost all gemeindefreie Gebiete - forests and lakes such as Starnberger See, Chiemsee, Ammersee and Perlacher Forst - that genuinely have no inhabitants. There the census is right and it is the ARDECO 100 m disaggregation that puts some 27 000 people in them. Dropping those is defensible; doing it silently is not, and the same code path is what loses Paris.
Suggestion
- Derive
Share_<broad>_LAU from Q_asr rather than from the raw census, so a LAU on the NUTS3 fallback gets the fallback's age structure instead of zeros.
- Add
A_i == 0 && P_i > 0 next to Checks/Qr_0_and_Pr_gt0, so a run can state how many people it dropped.
- Decide separately what should happen to ARDECO population in LAUs the census says are empty: reallocate over the neighbours, or accept the loss explicitly.
Reproducing
Measured with a Diag container appended to a scratch copy of the config, which writes per_country.csv, lau_lost.csv and unmatched_census_ids.csv. The two items that carry the finding:
attribute<Person> P_in_A0_cells := sum(/Analysis/Result/A_i == 0f ? /Analysis/P_i : 0f[Person], /Analysis/i/r_rel);
attribute<Share> share_sum_LAU := /Analysis/Checks/Share_LT15_LAU + /Analysis/Checks/Share_15_64_LAU + /Analysis/Checks/Share_GE65_LAU;
Happy to add the whole container to the repo as cfg/main/Diagnostics.dms if that is wanted.
EU 2011, 100 m, GeoDms 20.19.1.m on
main@ 735cad7./Analysisis handed 445 356 960 persons and writes 443 008 640. 2 348 320 people are in the input map and in no output map. It is not rounding and it is not spread thinly: it is fully accounted for by grid cells whose balancing factorA_iends at exactly 0.sum(Checks/P_r), the ARDECO inputsum(Result/X_asr/Total), the outputsum(P_i)over the cells whereResult/A_i == 0Checks/Qr_0_and_Pr_gt0Share_LT15_LAU + Share_15_64_LAU + Share_GE65_LAU == 0The 6-person gap between rows 3 and 4 is float32 rounding over 97 428 sums; per country the two agree to within a person (FR -2 318 976 vs -2 318 974, DE -26 632 vs -26 635, EL -1 980 vs -1 980, LT -667 vs -667, everything else nets to zero).
Mechanism
Checks/Share_<broad>_LAUis the per-LAU age structure thatE_ibfalls back on for 1 km cells where ESTAT has no age data:The numerator is the raw census from
Population/AGEGROUP5_SEX_perLAU; the denominator isQ_asr/Total, which already carries the NUTS3 fallback of #12. For a LAU with no census the numerator is 0 while the denominator isP_r > 0, so all three broad shares come out 0. That is the case for 928 of the 930 LAUs on the fallback.E_ibthen hands 0 to every cell in those LAUs whereESTAT_Age_0_and_with_ARDECO_popholds (158 728 cells EU-wide), and:so the cell's population disappears. The NUTS3 fallback repairs the target
Q_asrbut not the sharesE_ibuses, and the two disagree in exactly the LAUs where the fallback was needed.Where it lands
47 LAUs lose more than 100 persons, and 43 of them have
POP_2021 = 0in the LAU gpkg:P_rPOP_2021FR_75056DE_03153504DE_09188451EL_99010000DE_09184452DE_09184454DE_03159501DE_01053105The German entries are almost all gemeindefreie Gebiete - forests and lakes such as Starnberger See, Chiemsee, Ammersee and Perlacher Forst - that genuinely have no inhabitants. There the census is right and it is the ARDECO 100 m disaggregation that puts some 27 000 people in them. Dropping those is defensible; doing it silently is not, and the same code path is what loses Paris.
Suggestion
Share_<broad>_LAUfromQ_asrrather than from the raw census, so a LAU on the NUTS3 fallback gets the fallback's age structure instead of zeros.A_i == 0 && P_i > 0next toChecks/Qr_0_and_Pr_gt0, so a run can state how many people it dropped.Reproducing
Measured with a
Diagcontainer appended to a scratch copy of the config, which writesper_country.csv,lau_lost.csvandunmatched_census_ids.csv. The two items that carry the finding:Happy to add the whole container to the repo as
cfg/main/Diagnostics.dmsif that is wanted.