def make_harvest(cfg: GovernedCommonsConfig):
"""Compliant delegates take ``min(desired, policy_target)``; Bernoulli defectors take
``desired``. Everyone scales down proportionally if total demand exceeds the stock.
``last_reward := last_harvest`` (a later sanction mechanism may overwrite it)."""
N = cfg.n_households
@transform(reads=[delegate_action, policy_target, resource_level,
cumulative_harvest, rng_key],
writes=[last_harvest, last_reward, cumulative_harvest,
resource_level, rng_key])
def harvest(state: GraphState) -> GraphState:
state, key = _split_key(state)
desired = state.node_attrs["delegate_action"]
target = state.global_attrs["policy_target"]
defect = jr.bernoulli(key, p=cfg.defect_prob, shape=(N,))
taken = jnp.where(defect, desired, jnp.minimum(desired, target))
R = state.global_attrs["resource_level"]
total = jnp.sum(taken)
scale = jnp.where(total > R, R / (total + 1e-8), 1.0)
actual = taken * scale
state = state.update_node_attrs("last_harvest", actual)
state = state.update_node_attrs("last_reward", actual)
state = state.update_node_attrs(
"cumulative_harvest", state.node_attrs["cumulative_harvest"] + actual)
return state.update_global_attr(
"resource_level", jnp.maximum(R - jnp.sum(actual), 0.0))
return harvest