Skip to content
Open
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
6 changes: 4 additions & 2 deletions .github/workflows/build_macrocalib.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name: Build and test calibration package

on:
pull_request:
# Run on PRs to any branch
branches:
- main
push:
branches: [ main ]
branches:
- main

jobs:
build:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build_macrodata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: Build and test data package

on:
pull_request:
# Run on PRs to any branch
branches:
- main
push:
branches:
- main
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/build_macromodel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: Build and test model package

on:
pull_request:
# Run on PRs to any branch
branches:
- main
push:
branches:
- main
Expand Down
98 changes: 73 additions & 25 deletions macromodel/agents/firms/firms.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,24 +495,29 @@ def set_targets(
estimated_inflation: Expected inflation rate
current_good_prices (np.ndarray): Industry-level average prices
"""
self.ts.limiting_intermediate_inputs.append(
self.functions["production"].compute_limiting_intermediate_inputs_stock(
intermediate_inputs_productivity_matrix=self.get_effective_intermediate_coefficients(),
intermediate_inputs_stock=self.ts.current("intermediate_inputs_stock"),
intermediate_inputs_utilisation_rate=self.intermediate_inputs_utilisation_rate,
goods_criticality_matrix=self.goods_criticality_matrix,
substitution_bundle_matrix=self.substitution_bundles,
)
# Calculate limiting inputs and apply TFP multiplier.
# TFP scales effective capacity from inputs, so limits should also scale with TFP.
# This ensures target-setting accounts for TFP-enhanced production capacity.
# Without this, production targets are capped at initial levels even when TFP grows.
tfp_multiplier = self.states["tfp_multiplier"]

limiting_intermediate = self.functions["production"].compute_limiting_intermediate_inputs_stock(
intermediate_inputs_productivity_matrix=self.get_effective_intermediate_coefficients(),
intermediate_inputs_stock=self.ts.current("intermediate_inputs_stock"),
intermediate_inputs_utilisation_rate=self.intermediate_inputs_utilisation_rate,
goods_criticality_matrix=self.goods_criticality_matrix,
substitution_bundle_matrix=self.substitution_bundles,
)
self.ts.limiting_capital_inputs.append(
self.functions["production"].compute_limiting_capital_inputs_stock(
capital_inputs_productivity_matrix=self.get_effective_capital_coefficients(),
capital_inputs_stock=self.ts.current("capital_inputs_stock"),
capital_inputs_utilisation_rate=self.capital_inputs_utilisation_rate,
goods_criticality_matrix=self.goods_criticality_matrix,
substitution_bundle_matrix=self.substitution_bundles,
)
self.ts.limiting_intermediate_inputs.append(limiting_intermediate * tfp_multiplier)

limiting_capital = self.functions["production"].compute_limiting_capital_inputs_stock(
capital_inputs_productivity_matrix=self.get_effective_capital_coefficients(),
capital_inputs_stock=self.ts.current("capital_inputs_stock"),
capital_inputs_utilisation_rate=self.capital_inputs_utilisation_rate,
goods_criticality_matrix=self.goods_criticality_matrix,
substitution_bundle_matrix=self.substitution_bundles,
)
self.ts.limiting_capital_inputs.append(limiting_capital * tfp_multiplier)
self.ts.target_production.append(
self.compute_target_production(
bank_overdraft_rate_on_firm_deposits=bank_overdraft_rate_on_firm_deposits,
Expand Down Expand Up @@ -590,6 +595,7 @@ def plan_productivity_investment(
input_usage=self.ts.current("used_intermediate_inputs"),
current_tech_multipliers=self.states["intermediate_tech_multipliers"],
substitution_bundle_matrix=self.substitution_bundles,
firm_industries=self.states["Industry"],
)

return total_investment, tfp_investment, technical_investment
Expand Down Expand Up @@ -1267,18 +1273,60 @@ def prepare_buying_goods(

# Target capital inputs
if assume_zero_growth:
self.ts.target_capital_inputs.append(self.ts.initial("target_capital_inputs"))
target_capital_inputs = self.ts.initial("target_capital_inputs")
else:
self.ts.target_capital_inputs.append(
self.functions["target_capital_inputs"].compute_target_capital_inputs(
unconstrained_target_capital_inputs=self.ts.current("unconstrained_target_capital_inputs"),
target_long_term_credit=self.ts.current("target_long_term_credit"),
received_long_term_credit=self.ts.current("received_long_term_credit"),
previous_good_prices=previous_good_prices,
expected_inflation=expected_inflation,
)
target_capital_inputs = self.functions["target_capital_inputs"].compute_target_capital_inputs(
unconstrained_target_capital_inputs=self.ts.current("unconstrained_target_capital_inputs"),
target_long_term_credit=self.ts.current("target_long_term_credit"),
received_long_term_credit=self.ts.current("received_long_term_credit"),
previous_good_prices=previous_good_prices,
expected_inflation=expected_inflation,
)

# [TFP_DEBUG] Add planned productivity investment as additional capital demand.
# This makes productivity investment a forward-looking target rather than a residual.
if len(self.ts.planned_productivity_investment) > 0:
planned_investment = self.ts.current("planned_productivity_investment")
if planned_investment is not None and np.any(planned_investment > 0):
import logging

logger = logging.getLogger(__name__)
logger.debug(
f"[TFP_DEBUG] prepare_buying_goods: Adding planned_productivity_investment "
f"(sum={np.sum(planned_investment):.2e}) to capital demand"
)

target_capital_inputs = target_capital_inputs.copy()
expected_prices = (1 + expected_inflation) * previous_good_prices
safe_prices = np.maximum(expected_prices, 1e-12)

# Distribute productivity investment across industries based on existing capital weights
target_totals = target_capital_inputs.sum(axis=1, keepdims=True)
weights = np.divide(
target_capital_inputs,
target_totals,
out=np.zeros_like(target_capital_inputs),
where=target_totals > 0,
)
# For firms with no existing capital targets, distribute evenly
if np.any(target_totals == 0):
weights = np.where(
target_totals == 0,
1.0 / self.n_industries,
weights,
)

# Convert monetary investment to real quantities at expected prices
extra_capital_qty = (planned_investment[:, None] * weights) / safe_prices
target_capital_inputs += extra_capital_qty

logger.debug(
f"[TFP_DEBUG] prepare_buying_goods: Added {np.sum(extra_capital_qty):.2e} "
f"real units of capital from productivity investment"
)

self.ts.target_capital_inputs.append(target_capital_inputs)

# Setting total real amount of goods to buy
self.set_goods_to_buy(self.ts.current("target_intermediate_inputs") + self.ts.current("target_capital_inputs"))

Expand Down
8 changes: 3 additions & 5 deletions macromodel/agents/firms/func/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,19 @@ def compute_production(
Returns:
np.ndarray: Feasible production levels by firm
"""
# Limiting stock is already TFP-scaled in set_targets()
limiting_stock = self.compute_limiting_stock(
current_limiting_intermediate_inputs,
current_limiting_capital_inputs,
)

# Apply TFP multiplier if provided
# Apply TFP multiplier to labour only (limiting stock is pre-scaled)
if tfp_multiplier is not None:
# TFP scales effective capacity from inputs
effective_labour = current_labour_inputs * tfp_multiplier
effective_limiting_stock = limiting_stock * tfp_multiplier
else:
effective_labour = current_labour_inputs
effective_limiting_stock = limiting_stock

return np.amin([desired_production, effective_labour, effective_limiting_stock], axis=0)
return np.amin([desired_production, effective_labour, limiting_stock], axis=0)

@abstractmethod
def compute_limiting_intermediate_inputs_stock(
Expand Down
34 changes: 34 additions & 0 deletions macromodel/agents/firms/func/productivity_growth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import logging
from abc import ABC, abstractmethod

import numpy as np

logger = logging.getLogger(__name__)


class ProductivityGrowth(ABC):
"""Abstract base class for computing Total Factor Productivity (TFP) growth.
Expand Down Expand Up @@ -134,6 +137,19 @@ def compute_tfp_growth(
# Base growth applies to all firms
tfp_growth = np.full_like(current_tfp, base_growth_rate)

# Handle empty arrays early
if len(current_tfp) == 0:
return tfp_growth

# [TFP_DEBUG] Log input parameters
logger.debug(
"[TFP_DEBUG] SimpleTFPGrowth.compute_tfp_growth called: "
f"base_growth_rate={base_growth_rate}, investment_elasticity={investment_elasticity}, "
f"investment_effectiveness={self.investment_effectiveness}, "
f"current_tfp(mean)={np.mean(current_tfp):.6f}, production(sum)={np.sum(production):.2e}, "
f"productivity_investment(sum)={np.sum(productivity_investment):.2e}"
)

# Add investment-driven growth where production > 0
positive_production = production > 0
if np.any(positive_production):
Expand All @@ -146,6 +162,12 @@ def compute_tfp_growth(
positive_investment = productivity_investment > 0
valid_firms = positive_production & positive_investment

# [TFP_DEBUG] Log firm counts
logger.debug(
f"[TFP_DEBUG] Firms with positive_production={np.sum(positive_production)}, "
f"positive_investment={np.sum(positive_investment)}, valid_firms={np.sum(valid_firms)}"
)

if np.any(valid_firms):
investment_intensity[valid_firms] = productivity_investment[valid_firms] / production[valid_firms]

Expand All @@ -156,6 +178,18 @@ def compute_tfp_growth(

tfp_growth += investment_contribution

# [TFP_DEBUG] Log investment contribution
logger.debug(
f"[TFP_DEBUG] investment_intensity(mean of valid)={np.mean(investment_intensity[valid_firms]):.6f}, "
f"investment_contribution(mean)={np.mean(investment_contribution):.6f}, "
f"tfp_growth(mean)={np.mean(tfp_growth):.6f}"
)

# [TFP_DEBUG] Log final result
logger.debug(
f"[TFP_DEBUG] Final tfp_growth: mean={np.mean(tfp_growth):.6f}, min={np.min(tfp_growth):.6f}, max={np.max(tfp_growth):.6f}"
)

return tfp_growth


Expand Down
Loading
Loading