Skip to content

Commit f7ec0f3

Browse files
committed
fix score computation
1 parent 6627d20 commit f7ec0f3

10 files changed

Lines changed: 629 additions & 311 deletions

File tree

inseeds/components/farming/ca_behaviour.py

Lines changed: 144 additions & 205 deletions
Large diffs are not rendered by default.

inseeds/components/farming/ca_country.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,65 @@ def pft_prices(self):
393393
"""
394394
return self._fao("_pft_prices")
395395

396+
# -------------------------------------------------------------------------
397+
# Country-Level Reference Values (for performance score normalization)
398+
# -------------------------------------------------------------------------
399+
400+
# Default reference values for LEVELS (overwritten each year in update())
401+
reference_yield_level: float = 1.0
402+
reference_soilc_level: float = 1.0
403+
reference_moisture_level: float = 1.0
404+
405+
# Default reference values for TRENDS (overwritten each year in update())
406+
# These represent "meaningful" trends for normalization, computed as
407+
# the standard deviation of trends across farmers (not mean, since mean
408+
# trend is often near zero and would cause division issues)
409+
reference_yield_trend: float = 1.0
410+
reference_soilc_trend: float = 1.0
411+
reference_moisture_trend: float = 1.0
412+
413+
def compute_reference_values(self):
414+
"""Compute country-mean reference values for performance normalization.
415+
416+
Called once per year in update() BEFORE farmers are updated.
417+
This ensures all farmers see consistent reference values and avoids
418+
repeated computation during performance comparisons.
419+
420+
Reference values for levels use country means.
421+
Reference values for trends use standard deviation (spread of trends),
422+
which provides a natural scale for "what counts as meaningful change".
423+
"""
424+
yields, soilcs, moistures = [], [], []
425+
yield_trends, soilc_trends, moisture_trends = [], [], []
426+
427+
for farmer in self.farmers:
428+
if hasattr(farmer, "cropyield"):
429+
yields.append(farmer.cropyield)
430+
if hasattr(farmer, "soilc"):
431+
soilcs.append(farmer.soilc)
432+
if hasattr(farmer, "root_moisture"):
433+
moistures.append(farmer.root_moisture)
434+
435+
# Collect trends from trackers
436+
tracker = farmer.behaviour.performance_tracker
437+
if tracker is not None:
438+
yield_trends.append(tracker.yield_trend)
439+
soilc_trends.append(tracker.soilc_trend)
440+
moisture_trends.append(tracker.moisture_trend)
441+
442+
# Level references: country mean (with fallback to 1.0)
443+
self.reference_yield_level = float(np.mean(yields)) if yields else 1.0
444+
self.reference_soilc_level = float(np.mean(soilcs)) if soilcs else 1.0
445+
self.reference_moisture_level = float(np.mean(moistures)) if moistures else 1.0
446+
447+
# Trend references: standard deviation of trends (what counts as meaningful)
448+
# Use std rather than mean because mean trend can be ~0 which breaks normalization
449+
# The std represents "typical variation in trends" - a natural scale
450+
# Minimum of 0.1 to avoid extreme values when all trends are identical
451+
self.reference_yield_trend = max(0.1, float(np.std(yield_trends))) if yield_trends else 1.0
452+
self.reference_soilc_trend = max(0.1, float(np.std(soilc_trends))) if soilc_trends else 1.0
453+
self.reference_moisture_trend = max(0.1, float(np.std(moisture_trends))) if moisture_trends else 1.0
454+
396455
# -------------------------------------------------------------------------
397456
# Country-Level Statistics (for non-local spreading)
398457
# -------------------------------------------------------------------------
@@ -459,6 +518,10 @@ def update(self, t):
459518
Computes country-level statistics BEFORE updating farmers to ensure
460519
all farmers see the same country-level data for this timestep.
461520
"""
521+
# Compute reference values for performance normalization
522+
# Must run BEFORE farmers update so they see consistent references
523+
self.compute_reference_values()
524+
462525
# Compute country-level statistics for non-local spreading
463526
# Must run BEFORE farmers update so they see current country stats
464527
self.compute_management_performance(t)

inseeds/components/farming/ca_farmer.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -440,12 +440,12 @@ def indicate_cover_crop_type(self):
440440
# Get environmental conditions from cell
441441
runoff = self.cell_runoff
442442
leaching_val = self.cell_leaching
443-
# fertilizer_val = self.cell_fertilizer
443+
fertilizer_val = self.cell_fertilizer
444444

445445
# Get thresholds from config
446446
cc = self.model.config.coupled_config.practice_dimensions.cover_crop
447447
leaching_limit = cc.leaching_high
448-
# fertilizer_limit = cc.fertilizer_high
448+
fertilizer_limit = cc.fertilizer_high
449449

450450
# Calculate leaching rate (normalized by runoff)
451451
leaching = leaching_val *1e3 / runoff if runoff > 0 else 0
@@ -455,7 +455,7 @@ def indicate_cover_crop_type(self):
455455
# -----------------------------------------------------------------
456456
# High leaching: soil has excess N that's being lost
457457
# → Use non-legume catch crop to capture nutrients
458-
if leaching >= leaching_limit: # and fertilizer_val >= fertilizer_limit:
458+
if leaching >= leaching_limit or fertilizer_val >= fertilizer_limit:
459459
return 1 # Non-legume
460460

461461
# Otherwise: soil may be N-limited
@@ -903,6 +903,9 @@ def update(self, t):
903903
if self.capital < self.min_capital:
904904
self.behaviour.transition_blocker = BLOCKER_CAPITAL_SURVIVAL
905905
self.behaviour.transition_driver = DRIVER_NONE
906+
# Still decrement observation counter so farmer can evaluate
907+
# promptly when they recover from survival mode (avoids limbo)
908+
self.behaviour.decrement_observation_years()
906909
return
907910

908911
# -----------------------------------------------------------------

0 commit comments

Comments
 (0)