Três objetos, mantidos separados de propósito
| Objeto | O que registra |
|---|---|
SAM | como era o ano-base |
ModelSpec | as hipóteses sobre substituição e forma funcional |
Closure | quais saldos são fixados e quem se ajusta |
Os resultados podem depender de forma decisiva dos dois últimos. Por isso, eles acompanham cada resultado, em vez de ficarem enterrados num desvio condicional dentro do solucionador.
O mercado do fator numerário fica fora do sistema resolvido. Ele é fechado pela lei de Walras, e assim o seu resíduo serve como verificação independente da contabilidade, e não como uma identidade que o solucionador foi instruído a satisfazer. O resíduo é registrado em todos os períodos.
Instalação
pip install cgelab
pip install "cgelab[parquet]" # MCS em .parquet
pip install "cgelab[dsge]" # investimento com expectativas, via dsgelab
pip install "cgelab @ git+https://github.com/GustavoHM/cgelab" # versão atual do ramo main
Para desenvolvimento:
git clone https://github.com/GustavoHM/cgelab && cd cgelab
pip install -e ".[test]" && pytest
cgelab # roda a economia didática e imprime os diagnósticos
cgelab minha_mcs.parquet --sigma-armington 2.0 --sigma-va 0.5
Exemplos
Todos rodam sem alteração logo após o pip install. Usam a economia
didática incluída no pacote, escrita entrada por entrada para que o ano-base possa ser conferido
no papel, e são os mesmos exemplos executados pela suíte de testes.
1. Da MCS ao equilíbrio
import cgelab as cge
from cgelab.examples import toy
sam, econ = toy.build() # 2 atividades, 2 mercadorias, 2 fatores, 2 domicílios
print(cge.report(econ)) # ano-base, Walras, homogeneidade
base = cge.solve_period(econ)
print(base.summary())
base.pq # preços ao consumidor
base.X # nível de cada atividade
base.walras # o mercado que o solucionador NÃO resolveu
cgelab diagnostics -- Economy(2 activities, 2 commodities, 2 households, 2 factors)
base-year reproduction : PASS (worst 3.55e-16)
imports 3.55e-16
exports 1.78e-16
activity_output 1.42e-16
commodity_output 1.42e-16
Walras residual : 0.00e+00 [f_lab]
price homogeneity : PASS (worst 4.44e-16)
equilibrium (baseline): numeraire=1; investment=savings_driven; government=real_fixed; foreign=fixed_saving
residual : 3.553e-16 (converged, cold)
Walras residual : 0.000e+00 [f_lab, not solved for]
price f_lab : 1.000000
price f_cap : 1.000000
exchange rate : 1.000000
savings : 50.0
base.pq
[1.048 1.049]
base.X
[100 200]
base.walras
0
2. Um contrafactual
choque = cge.Scenario(name="tributo +50%", product_tax=econ.ptax * 1.5)
alt = cge.solve_period(econ, scenario=choque)
alt.pq / base.pq - 1 # variação de preço de cada mercadoria
alt.real["hh_real_income"] # renda real por domicílio
abs(alt.walras) < 1e-9 # a contabilidade continua fechando
alt.pq / base.pq - 1
[0.03488 0.04116]
alt.real["hh_real_income"]
[78.01 45.91]
abs(alt.walras) < 1e-9
True
3. Uma cunha sobre o preço ao consumidor, e para quem vai a receita
W = cge.consumer_wedge(econ, {"c_ind": 0.08}) # 8% sobre uma mercadoria
capital = cge.solve_period(econ, scenario=cge.Scenario(
consumer_wedge=W, wedge_revenue_to="capital")) # devolve conforme a propriedade do capital
percap = cge.solve_period(econ, scenario=cge.Scenario(
consumer_wedge=W, wedge_revenue_to="lumpsum")) # devolve per capita
meio = cge.solve_period(econ, scenario=cge.Scenario(
consumer_wedge=W, wedge_revenue_to=0.5)) # combinação convexa das duas
capital.hh_income[0] > percap.hh_income[0] # o domicílio com mais capital ganha
abs(sum(capital.hh_wedge_paid) - capital.wedge_revenue) < 1e-12 # a identidade vale, a menos de arredondamento
capital.hh_income[0] > percap.hh_income[0]
True
abs(sum(capital.hh_wedge_paid) - capital.wedge_revenue) < 1e-12
True
A escolha não é detalhe de implementação. A propriedade do
capital se concentra no topo da distribuição. Com "capital", os pobres pagam a cunha
nas compras e os ricos a recebem como renda, de modo que parte de qualquer regressividade medida
decorre dessa hipótese. A combinação convexa permite tratar o achado como questão de grau, e não
de sim ou não: em geral existe um peso em que o gradiente distributivo passa por zero, e é a
distância até esse peso que indica a robustez do resultado.
A receita reciclada forma uma série infinita: creditada à renda, ela aumenta o gasto, que aumenta a arrecadação da cunha, que volta a aumentar a renda. Como a demanda é afim no gasto, a série tem soma em forma fechada. Iterá-la também converge, mas introduz descontinuidades que estragam a jacobiana do Newton externo.
4. Uma trajetória de 25 anos
crescimento = cge.Growth(labour=0.005, tfp=0.010,
depreciation=0.05, discount=0.05)
def politica(ano, t):
return cge.Scenario(consumer_wedge=W) if ano >= 2026 else cge.Scenario()
ref = cge.run_path(econ, years=range(2021, 2046), growth=crescimento)
alt = cge.run_path(econ, years=range(2021, 2046), growth=crescimento, scenario=politica)
ref.aggregates() # preços, absorção real, poupança, Walras: uma linha por ano
ref.worst_walras # o primeiro número a conferir
ref.capital_stock # estoque obtido do custo de uso, e não da MCS
ref.aggregates()
exchange_rate savings walras residual ... inv_real hh_sav_real gov_sav_real for_sav_real
year ...
2021 1 50 0 3.553e-16 ... 50 35 5 10
2022 0.972 50.83 -2.442e-15 2.292e-16 ... 52.28 36.08 5.92 10.29
2023 0.9446 51.66 -1.151e-15 7.105e-16 ... 54.67 37.2 6.888 10.58
2024 0.9179 52.49 -1.4e-15 2.141e-16 ... 57.14 38.35 7.907 10.89
2025 0.8917 53.31 -1.52e-15 2.066e-16 ... 59.72 39.54 8.978 11.2
2026 0.8663 54.13 -1.008e-15 1.993e-16 ... 62.41 40.78 10.1 11.53
... ... ... ... ... ... ... ... ... ...
2040 0.582 65.26 -1.88e-15 7.105e-16 ... 111.8 62.16 32.49 17.13
2041 0.5665 66.05 -1.52e-15 3.553e-16 ... 116.2 63.99 34.65 17.6
2042 0.5516 66.84 -2.676e-15 7.105e-16 ... 120.8 65.86 36.89 18.08
2043 0.5371 67.63 -2.778e-15 3.553e-16 ... 125.5 67.77 39.21 18.56
2044 0.5232 68.42 -1.843e-15 0 ... 130.4 69.72 41.63 19.06
2045 0.5098 69.21 -1.834e-15 7.105e-16 ... 135.4 71.7 44.14 19.56
ref.worst_walras
3.237e-15
ref.capital_stock
[600 620 641.3 663.9 687.8 713.2 739.9 768.1 797.8 829 861.7 896.1 932 969.7 1009 1050
1093 1137 1184 1232 1282 1334 1388 1445 1503]
Um resíduo de Walras que cresce ao longo da trajetória denuncia um coeficiente que entrou no bloco de preços mas não no de quantidades. Num período isolado, o problema passa despercebido; num horizonte de vinte e cinco anos, salta aos olhos.
5. Bem-estar sem confundir mudança de composição com ganho
ev = cge.equivalent_variation(ref, alt)
ultimo = ev[ev.year == 2045].set_index("account")
ultimo.ev # avaliada aos preços do cenário de referência
ultimo.ev_base # avaliada aos preços do ano-base
ultimo.ev_pct_account # por CONTA: leia a nota abaixo antes de usar
ultimo.ev
account
hh_1 -0.1148
hh_2 -0.5904
ultimo.ev_base
account
hh_1 -0.2125
hh_2 -1.091
ultimo.ev_pct_account
account
hh_1 -0.09563
hh_2 -1.004
Quando domicílios migram de uma conta para outra, a medida por conta registra a migração como se fosse variação de bem-estar. A economia didática tem uma variante com duas contas gêmeas, de mesmas preferências e mesma taxa de poupança, feita justamente para mostrar isso:
sam, econ2 = toy.build_with_twin_accounts() # hh_1, e hh_2 dividido em hh_2a e hh_2b
migrado = econ2.hh_facshare.copy() # transfere renda entre as gêmeas sem mudar o total
a, b = econ2.household("hh_2a"), econ2.household("hh_2b")
for f in range(econ2.n_fac):
tot = migrado[a, f] + migrado[b, f]
migrado[a, f], migrado[b, f] = 0.6 * tot, 0.4 * tot
r1 = cge.run_path(econ2, years=range(2021, 2024))
r2 = cge.run_path(econ2, years=range(2021, 2024),
scenario=cge.Scenario(factor_ownership=migrado))
ev2 = cge.equivalent_variation(r1, r2)
ev2.ev_pct_account.abs().max() # > 5%: mudança de composição registrada como bem-estar
grupos = {"hh_1": "g1", "hh_2a": "g2", "hh_2b": "g2"}
cge.ev_by_group(ev2, grupos).ev_pct.abs().max() # ~0: o valor correto
ev2.ev_pct_account.abs().max()
20
cge.ev_by_group(ev2, grupos).ev_pct.abs().max()
2.22e-14
ev_by_group soma as rendas reais dentro de cada grupo antes de dividir.
Assim, o conjunto de domicílios é o mesmo nos dois cenários, e a razão não depende de quantos
migraram. Todas as contas precisam constar do mapeamento: se uma fosse descartada sem aviso, o
grupo sairia subestimado e o erro passaria por resultado.
6. Exportações que respondem ao preço mundial
import numpy as np
_, econ_cet = toy.build(model_spec=toy.spec(export_rule="cet", sigma_cet=2.0))
j = econ_cet.commodity("c_ind")
pe = np.ones(econ_cet.n_com)
pe[j] = 1.2 # o resto do mundo paga 20% a mais pelo bem industrial
antes = cge.solve_period(econ_cet)
depois = cge.solve_period(econ_cet, scenario=cge.Scenario(export_price=pe))
depois.exports[j] / depois.Q[j] # 0,119: a parcela exportada era 0,100
depois.pe[j] / depois.pd[j] # 1,09: exportar passa a render mais que vender no mercado interno
depois.exchange_rate # 0,89: o câmbio se valoriza e absorve parte do choque
depois.exports[j] / depois.Q[j]
0.1185
depois.pe[j] / depois.pd[j]
1.089
depois.exchange_rate
0.895
Com export_rule="cet", a produção se divide entre mercado interno e exportação ao
longo de uma fronteira de transformação, e o preço doméstico se ajusta até que a receita nessa
fronteira iguale o custo unitário. Com participações fixas, que são o padrão, o pacote rejeita
export_price com um erro, em vez de ignorá-lo sem aviso.
_, econ_big = toy.build(model_spec=toy.spec(export_rule="cet", sigma_cet=2.0,
export_demand={"c_ind": 2.0}))
big = cge.solve_period(econ_big, scenario=cge.Scenario(export_price=pe))
big.world_export_price[j] # 1,12: para vender mais, o preço desce pela curva de demanda do comprador
big.exports[j] / econ_big.exp0[j] # 1,14: as exportações sobem 14%, contra 22% para um tomador de preço
big.world_export_price[j]
1.124
big.exports[j] / econ_big.exp0[j]
1.14
Uma região que responde por boa parte das vendas mundiais de um produto não é tomadora de
preço. ModelSpec.export_demand coloca as exportações dela sobre uma curva de demanda,
E / E0 = (pWe / export_price)^(-η), na forma usada no lrgcge de Hosoe;
import_supply faz o mesmo para o preço mundial de importação. No ano-base, as duas
curvas passam pelo ponto calibrado.
7. Impostos, transferências e capital estrangeiro
import dataclasses
_, econ_i = toy.build_with_institutions()
econ_i.hh_dtax # alíquota do imposto direto de cada domicílio, lida da MCS
econ_i.fac_row_share # parcela de cada fator que pertence ao exterior
econ_i.foreign_sav # 18: o saldo em conta corrente, e não só a balança comercial (10)
cresc = cge.Growth(labour=0.01, tfp=0.01)
anos = range(2021, 2031)
ied = cge.run_path(econ_i, years=anos, growth=cresc,
scenario=lambda ano, t: cge.Scenario(fdi={"f_cap": 3.0}))
doado = cge.run_path(econ_i, years=anos, growth=cresc,
economy=lambda ano, t: dataclasses.replace(
econ_i, foreign_sav=econ_i.foreign_sav + 3.0))
ied[2030].factor_income_abroad > doado[2030].factor_income_abroad # os lucros vão para o exterior
ied[2030].hh_income.sum() < doado[2030].hh_income.sum() # e a renda local é menor
econ_i.hh_dtax
[0.1154 0.03226]
econ_i.fac_row_share
[0 0.1667]
econ_i.foreign_sav
18
ied[2030].factor_income_abroad > doado[2030].factor_income_abroad
True
ied[2030].hh_income.sum() < doado[2030].hh_income.sum()
True
O mesmo fluxo de capital admite duas leituras. Lançado como fdi, ele financia
investimento e se torna capital de propriedade estrangeira, cuja remuneração sai pela conta
corrente. Lançado como simples poupança externa, o capital fica com os residentes, e todo o
rendimento que ele gera é creditado a eles. A primeira leitura mede o efeito do investimento
estrangeiro; a segunda o superestima.
8. Vários estoques de capital e uma tecnologia que muda com o tempo
_, econ_k = toy.build_with_specific_capital() # a indústria usa um capital específico
g = cge.Growth(capital_factor=("f_cap", "f_kind"),
depreciation={"f_cap": 0.05, "f_kind": 0.08},
allocation=2.0) # o investimento segue a remuneração relativa
def economia(ano, t): # a indústria usa 2% menos insumo agrícola a cada ano
io = econ_k.io.copy()
io[econ_k.commodity("c_agr"), econ_k.activity("a_ind")] *= 0.98 ** t
return dataclasses.replace(econ_k, io=io)
traj = cge.run_path(econ_k, years=range(2021, 2036), growth=g, economy=economia)
traj.capital_stock[-1] # um estoque para cada fator de capital
traj.worst_walras # continua na precisão de máquina
traj.capital_stock[-1]
[359.1 379.2]
traj.worst_walras
3.359e-15
Mudanças que não são de preço, de tributo nem de dotação, como a adoção de uma tecnologia ou
um plano de financiamento, entram pelo argumento economy, que devolve a economia de
cada ano. É disso que um cenário de transição energética precisa: os coeficientes técnicos mudam,
e os blocos de preço e de quantidade continuam enxergando a mesma tecnologia.
9. Com a sua própria MCS
A economia didática serve para que os exemplos rodem sem dados externos. Com dados próprios,
mudam apenas as duas primeiras linhas e o ModelSpec:
sam = cge.SAM.read("minha_mcs.parquet") # ou SAM.from_long(df) / SAM.from_square(df)
sam.check_balance() # bloqueante: nada roda com a MCS desbalanceada
spec = cge.ModelSpec(
activity_prefix="a_", commodity_prefix="c_", household_prefix="hh_",
factors=("f_lab", "f_cap"),
government="gov", investment="s_i", rest_of_world="row",
numeraire_factor="f_lab", # o mercado deste fator fica a cargo da lei de Walras
value_added="ces", sigma_va=0.5,
sigma_armington=2.0, # escalar, ou um dicionário por mercadoria
export_rule="fixed_shares", # ou "cet": as tabelas de insumo-produto não fornecem a elasticidade CET
household_demand="cobb_douglas", # ou "les", com o parâmetro de Frisch
negative_value_added="leontief", # excedente operacional negativo é um dado, não um erro
)
econ = cge.calibrate(sam, spec, population={"hh_d1": 8.3e6, "hh_d2": 8.3e6})
assert cge.base_year_reproduction(econ)["ok"] # não siga adiante sem isto
assert cge.homogeneity_test(econ)["ok"]
econ.diagnostics["unmodelled_flows"] # células da MCS que o modelo não lê
As contas são identificadas por prefixo ou por listas explícitas. Numa MCS de nomenclatura irregular, as listas são a opção mais segura: uma regra de prefixo que deixa escapar uma conta sem aviso produz um modelo que fecha e, ainda assim, está errado.
10. Multiplicadores a preços fixos
sam_toy, _ = toy.build()
m = cge.accounting_multipliers(sam_toy, spec=toy.spec()) # governo, investimento e resto do mundo exógenos
m.M.loc["hh_2", "c_agr"] # 0.546: quanto uma unidade de demanda por produto agrícola acrescenta à renda do domicílio 2
paths = cge.structural_paths(m, "c_agr", "hh_2", threshold=1e-3)
paths[["path", "total", "share"]].head(2)
# (c_agr, a_agr, f_lab, hh_2) 0.323 0.59 via salários, incluindo a realimentação fora do caminho
# (c_agr, a_agr, f_cap, hh_2) 0.131 0.24
m.M.loc["hh_2", "c_agr"]
0.5463
paths[["path", "total", "share"]].head(2)
path total share
0 (c_agr, a_agr, f_lab, hh_2) 0.3227 0.5907
1 (c_agr, a_agr, f_cap, hh_2) 0.1312 0.2401
Aqui a mesma MCS é lida como modelo de preços fixos: as quantidades respondem a uma injeção
nas proporções do ano-base. leontief_multipliers torna endógena apenas a produção;
fixed_price_multipliers usa propensões marginais para as famílias; e
linkages monta a tabela de setores-chave, com encadeamentos para trás e para a frente.
Somadas sobre todos os caminhos elementares, as influências totais reproduzem o multiplicador.
11. Contas nacionais
taxed = cge.solve_period(econ, scenario=cge.Scenario(product_tax=econ.ptax * 1.5))
na = cge.national_accounts(taxed) # o contrafactual do exemplo 2
na["gdp_expenditure"], na["gdp_income"] # 202.78 e 202.78: as duas óticas do PIB coincidem em qualquer equilíbrio
pi = cge.price_indices(taxed)
pi.loc["gdp", ["fisher", "quantity_fisher"]] # 1.040, 0.9999: preços 4% mais altos, volume estável
cge.government_account(taxed)["saving"] # 12.04, contra 5 no ano-base
na["gdp_expenditure"], na["gdp_income"]
(202.8, 202.8)
pi.loc["gdp", ["fisher", "quantity_fisher"]]
fisher 1.04
quantity_fisher 0.9999
cge.government_account(taxed)["saving"]
12.04
O pacote calcula o PIB a preços correntes pelas óticas da despesa e da renda, índices de Laspeyres, Paasche e Fisher para cada componente da demanda final, para o PIB e para os termos de troca, e a conta do governo por instrumento. Essa conta é registrada pelo solucionador junto com a solução, de modo que a poupança do governo que aparece nela é a mesma que financiou o investimento.
12. Quanto um resultado depende das elasticidades
run = cge.spec_sweep(sam, toy.spec(), scenario=cge.Scenario(product_tax=econ.ptax * 1.5),
measure=lambda base, alt: {"gdp_pct": 100 * (
cge.real_gdp(alt)["expenditure"] / cge.real_gdp(base)["expenditure"] - 1)})
params = {"sigma_va": cge.Uniform(0.3, 1.3), "sigma_armington": cge.Uniform(1.5, 3.5)}
cge.sensitivity(run, params).summary() # quadratura de Stroud: 4 pontos para 2 parâmetros
cge.sensitivity(run, params, method="monte_carlo", draws=40).summary()
cge.sensitivity(run, params).summary()
mean sd
gdp_pct -0.008932 0.001384
cge.sensitivity(run, params, method="monte_carlo", draws=40).summary()
mean sd q0.05 q0.5 q0.95 min max
gdp_pct -0.008932 0.00138 -0.01149 -0.008389 -0.007568 -0.012 -0.007453
Em cada ponto, o modelo é recalibrado e resolvido para o ano-base e para o cenário. Com
2n soluções, a regra de Stroud dá a média exata para funções de até grau três nos
parâmetros; o Monte Carlo acrescenta quantis. Valores da elasticidade de Armington próximos de 1
ficam de fora de propósito: com participações de exportação fixas, o câmbio é indeterminado em 1
e mal condicionado nas proximidades.
13. Quanto da variação cabe a cada choque
_, econ_tt = toy.build_with_trade_taxes()
reform = cge.Scenario(tariff=np.zeros(2), world_price=np.array([1.1, 0.95]),
product_tax=econ_tt.ptax * 1.2)
parts = cge.subtotals(econ_tt, cge.Scenario(), reform,
{"tariff": ["tariff"], "world": ["world_price"], "tax": ["product_tax"]},
measure=lambda p: {"gdp": cge.real_gdp(p)["expenditure"]})
parts[["tariff", "world", "tax", "change", "residual"]]
# gdp -0.723 -0.125 -0.007 -0.855 ~1e-10 as parcelas somam a variação total
parts[["tariff", "world", "tax", "change", "residual"]]
tariff world tax change residual
gdp -0.7235 -0.1245 -0.006899 -0.8549 -2.806e-10
É a decomposição que o GEMPACK chama de subtotals. Todos os choques avançam juntos ao longo do segmento que liga a referência à alternativa, e o termo de cada grupo é integrado separadamente; assim, as parcelas somam o total sem depender da ordem dos choques.
14. De onde vem uma variação de bem-estar
from cgelab.examples import hosoe
_, lrg = hosoe.build(large_country=True)
w = cge.welfare_decomposition(lrg, cge.Scenario(), cge.Scenario(tariff=np.zeros(2)),
closure=hosoe.CLOSURE)
w["allocative_tariff"], w["terms_of_trade"] # +0.054, -0.831: o ganho alocativo de zerar a tarifa, e o que um país grande perde nos termos de troca
w["absorption_households"] # +0.506: a VE de Hosoe para o mesmo experimento é +0,508
w["residual"] # ~1e-10: os termos somam a variação da absorção
w["allocative_tariff"], w["terms_of_trade"]
(0.05421, -0.8315)
w["absorption_households"]
0.506
w["residual"]
-3.304e-10
É a decomposição de Huff e Hertel, adaptada aos instrumentos deste modelo: dotações, tecnologia, um termo alocativo para cada distorção, termos de troca e conta externa, integrados ao longo da trajetória. A medida decomposta é a absorção real, incluindo governo e investimento; apenas a parcela das famílias é comparável a uma VE.
15. Precificação de carbono com reciclagem da receita
import pandas as pd
_, inst = toy.build_with_institutions()
fuel = pd.DataFrame(0.0, index=inst.coms, columns=inst.acts)
fuel.loc["c_ind", ["a_agr", "a_ind"]] = [3.0, 12.0] # toneladas queimadas na produção
green = cge.add_emissions(inst, intermediate=fuel, household={"c_ind": 5.0},
process={"a_ind": 2.0})
neutral = cge.Closure(government_target="revenue", government_adjusts="product_tax")
r = cge.solve_period(green, neutral, cge.Scenario(carbon_price=0.5))
cge.emissions(r)["total"] # 21.79, contra 22 no ano-base
r.tax_scale # 0.26: a receita do carbono financia um corte de 74% nas alíquotas sobre produtos
cge.emissions(r)["total"]
21.79
r.tax_scale
0.2648
As emissões formam uma conta satélite, calibrada por fonte a partir dos totais do ano-base. Nas atividades, o preço do carbono incide como imposto por unidade produzida, proporcional à intensidade de emissão; nas famílias, como imposto específico sobre o combustível. Com meta de receita, outro imposto se ajusta para manter a arrecadação, que neste exemplo é o imposto sobre produtos.
16. Uso de combustível que responde ao preço
_, nested = toy.build_with_institutions(model_spec=toy.spec(energy=["c_ind"], sigma_vae=0.6))
nested = cge.add_emissions(nested, intermediate=fuel, household={"c_ind": 5.0},
process={"a_ind": 2.0})
r2 = cge.solve_period(nested, neutral, cge.Scenario(carbon_price=0.5))
cge.emissions(r2)["total"] # 20.85, contra 21,79 com coeficientes fixos
r2.input_coefficients[1] # combustível por unidade de produto: 0,089 e 0,276, contra 0,095 e 0,286
cge.emissions(r2)["total"]
20.85
r2.input_coefficients[1]
[0.08882 0.2762]
Com ModelSpec.energy, cada atividade compra um agregado de energia que pode
substituir valor adicionado. O carbono de um combustível entra no preço que a atividade paga por
ele, e assim o mesmo preço de carbono altera não só quanto se produz, mas também como. Com as
duas elasticidades iguais a zero, o ninho reproduz o modelo Leontief na precisão de máquina.
17. Trabalho que se move só em parte
pools = pd.DataFrame([[30.0, 10.0], [10.0, 60.0]], index=["rural", "urbano"],
columns=["a_agr", "a_ind"]) # quem trabalhava onde no ano-base
spec_m = toy.spec(numeraire_factor="f_cap", factor_mobility={"f_lab": 2.0})
econ_m = cge.calibrate(sam_toy, spec_m, factor_pools={"f_lab": pools})
caro = cge.Scenario(world_price=np.array([1.0, 1.3])) # manufaturas importadas 30% mais caras
r_m = cge.solve_period(econ_m, scenario=caro)
r_m.factor_prices_by_activity[0] # o salário na agricultura e na indústria: 0,985 e 1,015
r_m.factor_pools["f_lab"]["price"] # o que cada pool ganha: 0,993 o rural, 1,010 o urbano
r_m.factor_prices_by_activity[0]
[0.9853 1.015]
r_m.factor_pools["f_lab"]["price"]
[0.9928 1.01]
Com factor_mobility, o trabalho se reparte entre as atividades por uma CET
de elasticidade 2, e cada atividade paga o seu salário. Com todos os fatores móveis, o mesmo
choque eleva o salário único para 1,004. Os pools de origem, uma matriz de proximidade, mostram
quem ganha: o pool urbano trabalha sobretudo na indústria, que o choque expande. Com elasticidade
zero, o modelo é o trabalho partido num fator por atividade, e
factor_mobility_form="logit" preserva o número de pessoas em vez da fronteira CET.
18. Um piso para o salário formal, e para onde vão os racionados
_, econ_s = toy.build_with_segmented_labour() # trabalho formal (indústria) e informal (lavoura)
piso = cge.LabourMarket(rigid={"f_for": 1.1}, informal="f_inf") # salário real formal 10% acima
r_s = cge.solve_period(econ_s, cge.Closure(labour_markets={"labour": piso}))
m = r_s.labour_markets["labour"]
m["employment"] # formal 66,2 e informal 43,8, eram 70 e 40
m["wage"] # 1,097 o formal, 0,864 o informal
m["household_employment"][0] # hh_1: 56,8 formal, 13,2 informal, eram 60 e 10
fila = cge.LabourMarket(rigid={"f_for": 1.1}, informal="f_inf",
unemployment="harris_todaro", unemployment_rate=0.08)
r_q = cge.solve_period(econ_s, cge.Closure(labour_markets={"labour": fila}))
r_q.labour_markets["labour"]["unemployment_rate"] # 0,196, era 0,08
spec_b = toy.spec(factors=("f_cap", "f_for", "f_inf"), numeraire_factor="f_cap",
labour_segments={"labour": ["f_for", "f_inf"]},
segment_mobility={"labour": 2.0}) # uma CET entre os segmentos
_, econ_b = toy.build_with_segmented_labour(model_spec=spec_b)
fila_propria = cge.LabourMarket(rigid={"f_for": 1.1}, unemployment_rate=0.08)
r_b = cge.solve_period(econ_b, cge.Closure(labour_markets={"labour": fila_propria}))
r_b.labour_markets["labour"]["queue"] # 18,6 na fila do emprego formal, eram 6,1
m["employment"]
[66.21 43.79]
m["wage"]
[1.097 0.8637]
m["household_employment"][0]
[56.75 13.25]
r_q.labour_markets["labour"]["unemployment_rate"]
0.1962
r_b.labour_markets["labour"]["queue"]
[18.59 0]
As duas contas de trabalho dividem uma população. No mercado dual, quem o setor formal deixa
de contratar trabalha no informal, e o salário informal cai; no Harris–Todaro, as pessoas
entram na fila do emprego formal até o salário informal igualar o formal esperado. Nos dois
casos, os empregos que o hh_1 perde reaparecem como trabalho informal ou desemprego
dele mesmo, e não como renda dos domicílios que já eram informais. Com
segment_mobility, os trabalhadores se repartem entre os segmentos por uma CET, o
salário informal se equilibra sozinho, e quem o setor formal não contrata fica na fila dele.
19. Pobreza e desigualdade nos domicílios de uma pesquisa
pesquisa = toy.survey() # doze domicílios sintéticos, escritos à mão
ref_s = cge.solve_period(econ_s)
alt_s = cge.solve_period(econ_s, cge.Closure(labour_markets={"labour": piso}))
micro = cge.microsimulate(pesquisa, ref_s, alt_s)
micro.reconciliation # salários formais do hh_1 escalados por 60/51 até a MCS
micro.calibration # pesos que movem o emprego formal como o modelo
cge.inequality(micro) # Gini de 0,531 para 0,556, Theil T de 0,527 para 0,577
cge.poverty(micro, 3.0) # proporção de pobres de 0,698 para 0,731, hiato de 0,459 para 0,490
micro.reconciliation
group source survey sam factor
0 hh_1 f_cap 32 45 1.406
1 hh_1 f_for 51 60 1.176
2 hh_1 f_inf 6.5 10 1.538
3 hh_2 f_cap 8.75 15 1.714
4 hh_2 f_for 10.5 10 0.9524
5 hh_2 f_inf 30.5 30 0.9836
micro.calibration
group constraint base target reached iterations
0 hh_1 households 7 7 7 5
1 hh_1 f_cap 45 45 45 5
2 hh_1 f_for 60 56.75 56.75 5
3 hh_1 f_inf 10 13.25 13.25 5
4 hh_2 households 11 11 11 4
5 hh_2 f_cap 15 15 15 4
6 hh_2 f_for 10 9.459 9.459 4
7 hh_2 f_inf 30 30.54 30.54 4
cge.inequality(micro)
ref alt change
gini 0.5307 0.5558 0.02518
theil_t 0.5269 0.5765 0.04967
theil_l 0.4877 0.5436 0.05598
ge_2 0.8169 0.8888 0.07193
atkinson_0.5 0.228 0.2496 0.02156
atkinson_1 0.3859 0.4194 0.03343
atkinson_2 0.5538 0.591 0.03719
cge.poverty(micro, 3.0)
ref alt change
line alpha
3.0 0 0.6978 0.7309 0.03307
1 0.4588 0.4899 0.03111
2 0.325 0.3529 0.02792
Cada domicílio da pesquisa pertence a uma conta da MCS: sua renda de cada fonte varia como a da conta, e o seu próprio orçamento define o seu índice de preços. Antes, a pesquisa é escalada à MCS, fonte por fonte, e os fatores são reportados em vez de escondidos. Quem perde o emprego formal entra pelos pesos: dentro de cada conta, eles são recalibrados para o emprego formal e informal da pesquisa variar como o do modelo, e é isso que move a proporção de pobres.
20. Pessoas que escolhem: microssimulação comportamental
pessoas = cge.microsimulate(pesquisa, ref_s, alt_s, persons=toy.persons()) # 42 pessoas sintéticas
pessoas.workers # hh_1: formal de 7,5 para 7,09, informal de 2,5 para 3,31
cge.inequality(pessoas).loc["gini"] # de 0,526 para 0,542
sorteado = cge.microsimulate(pesquisa, ref_s, alt_s, persons=toy.persons(), method="draws",
replications=200, seed=1)
cge.inequality(sorteado).loc["gini"] # de 0,526 para 0,544, erro-padrão 0,0008
pessoas.workers
group column workers ref alt
0 hh_1 f_for f_for 7.5 7.094
1 hh_1 f_inf f_inf 2.5 3.311
2 hh_1 inactive inactive 9.5 9.095
3 hh_1 unemployed unemployed 2.5 2.499
4 hh_2 f_for f_for 5 4.73
5 hh_2 f_inf f_inf 19 19.34
6 hh_2 inactive inactive 16.5 16.39
7 hh_2 unemployed unemployed 7 7.035
cge.inequality(pessoas).loc["gini"]
ref 0.5263
alt 0.5421
change 0.01584
cge.inequality(sorteado).loc["gini"]
ref 0.5263
alt 0.5437
alt_se 0.0007506
change 0.01743
Uma escolha ocupacional (logit multinomial) e equações de rendimento são estimadas nas pessoas, e o modelo chega a elas por interceptos deslocados, conta por conta, até o emprego e o rendimento por trabalhador variarem como o modelo diz, como em Bourguignon, Robilliard e Robinson (2005). O padrão usa a expectativa exata dos sorteios condicionais; os próprios sorteios dão uma média de Monte Carlo e o seu erro.
21. As pesquisas do IBGE
pesquisa, pessoas = cge.pnadc("PNADC_032023.txt", "input_PNADC_trimestral.txt",
segments={"formal": "f_for", "informal": "f_inf"},
group=minhas_contas)
domicilios = cge.pof("Dados_POF/", "Leitura dos Microdados - SAS.txt",
commodities=correspondencia, group=minhas_contas)
tru = cge.read_tru("68_tab1_2021.xls", "68_tab2_2021.xls") # PIB 2021: R$ 9.012.142 milhões
A PNAD Contínua e a POF são lidas com os próprios programas de leitura SAS do IBGE, então nenhuma posição de coluna é digitada à mão. Os trabalhadores são formais ou informais pela proxy do IBGE, e a despesa da POF é anualizada e deflacionada como fazem os programas de tabulação do IBGE. Os microdados são baixados do IBGE e nunca entram no repositório.
22. Uma MCS a partir das tabelas de recursos e usos
make = pd.DataFrame({"c_x": [100.0]}, index=["a_x"])
use = pd.DataFrame({"a_x": [0.0]}, index=["c_x"])
final = pd.DataFrame({"hh": [80.0], "s_i": [20.0]}, index=["c_x"])
va = pd.DataFrame({"a_x": [60.0, 40.0]}, index=["f_lab", "f_cap"])
sut = cge.SupplyUse(make=make, use=use, final=final, imports=pd.Series({"c_x": 0.0}),
value_added=va)
montada = cge.sut_to_sam(sut, source="tabelas escritas à mão")
montada.provenance[["source", "note", "total"]] # poupança das famílias: resíduo, 20
cge.base_year_reproduction(cge.calibrate(montada, cge.ModelSpec(households=["hh"])))["ok"]
montada.provenance[["source", "note", "total"]]
source note total
0 tabelas escritas à mão value added 100
1 tabelas escritas à mão output, basic prices 100
2 tabelas escritas à mão final demand: hh 80
3 tabelas escritas à mão final demand: s_i 20
4 assumed: all factor income to households factor income 100
5 residual household saving 20
6 structure empty account 0
cge.base_year_reproduction(cge.calibrate(montada, cge.ModelSpec(households=["hh"])))["ok"]
True
As tabelas de recursos e usos viram o bloco de produção e a demanda final; quem recebe a renda vem da Conta Econômica Integrada, aqui suposta, e a procedência diz isso. As poupanças são resíduos declarados, e tabelas que não fecham são recusadas onde falham.
23. Trabalho formal e informal, partido por atividade
parcelas = pd.DataFrame([[0.3, 0.8], [0.7, 0.2]], index=["f_for", "f_inf"],
columns=["a_agr", "a_ind"]) # de uma pesquisa: segment_shares(...)
partida = cge.split_factor(sam_toy, "f_lab", parcelas)
dois = cge.calibrate(partida, toy.spec(factors=("f_cap", "f_for", "f_inf"),
numeraire_factor="f_cap",
labour_segments={"labour": ["f_for", "f_inf"]}))
cge.solve_period(dois).residual # o ano-base, à precisão de máquina
cge.solve_period(dois).residual
3.384e-16
O trabalho é partido em formal e informal em cada atividade por parcelas de uma pesquisa, e as
duas contas viram um grupo de segmentos. Com um só salário, o modelo partido tem exatamente o
equilíbrio do fator único; um salário formal rígido é que faz a diferença.
split_households faz o mesmo com uma conta de famílias e os grupos de uma pesquisa.
24. Uma linha de base que atinge uma trajetória do PIB
pib = pd.Series([0.046, 0.029, 0.032, 0.025], index=range(2022, 2026)) # crescimento real
base = cge.calibrate_baseline(econ, years=range(2021, 2026), gdp=pib,
growth=cge.Growth(labour=0.01))
base.factor # a produtividade que atinge cada ano
politica = base.run(cge.Scenario(product_tax=econ.ptax * 1.2))
cge.real_gdp(politica[2025])["expenditure"] / base.achieved[2025] - 1
base.factor
2021 1
2022 1.017
2023 1.023
2024 1.031
2025 1.035
cge.real_gdp(politica[2025])["expenditure"] / base.achieved[2025] - 1
0.004055
O PIB real é dado, observado ou projetado, e a produtividade é a incógnita, resolvida ano a ano porque o capital de cada ano vem do investimento dos anteriores: um número por ano, ou um por atividade com o valor adicionado setorial. Uma política roda então com a produtividade calibrada, e o PIB se afasta da trajetória; sem política, a linha de base volta exatamente.
25. Capital putty-clay
safras = cge.split_vintages(sam_toy, 0.08) # 8% do capital de cada atividade é novo
argila = cge.calibrate(safras, toy.spec(factors=("f_lab", "f_cap", "f_cap_new"),
vintages={"f_cap": "f_cap_new"}))
trajetoria = cge.run_path(argila, years=range(2021, 2031),
scenario=cge.Scenario(world_price=np.array([1.0, 1.15])))
trajetoria.capital_by_pool["f_cap"].iloc[[0, -1]] # capital instalado, por atividade
trajetoria[2021].vintage_value_added # os feixes velho e novo
trajetoria.capital_by_pool["f_cap"].iloc[[0, -1]]
a_agr a_ind
2021 230 322
2030 300.2 444.9
trajetoria[2021].vintage_value_added
[[59.8 96.6]
[4.306 9.294]]
O capital instalado fica no seu setor e mantém a técnica com que foi construído; o capital novo vai para onde rende mais. Quando o preço da indústria sobe, só a safra nova acompanha no começo, então o produto responde menos que com capital móvel, e alcança à medida que a indústria instala mais do investimento de cada ano.
26. O mesmo choque sob quatro fechamentos, e em moeda
_, simples = toy.build()
choque = cge.Scenario(world_price=np.array([1.0, 1.2]))
pib_pct = lambda base, alt: {"pib_pct": 100 * (cge.real_gdp(alt)["expenditure"]
/ cge.real_gdp(base)["expenditure"] - 1)}
grade = cge.closure_grid(investment=["savings_driven", "fixed_real"],
foreign=["fixed_saving", "fixed_rate"])
varredura = cge.closure_sweep(simples, grade, scenario=choque, measure=pib_pct)
varredura.table
varredura.range()[["min", "max", "same_sign"]]
base, alt = cge.solve_period(simples), cge.solve_period(simples, scenario=choque)
cge.in_money(base, alt).loc["final_demand", ["change", "change_base_prices"]]
varredura.table
pib_pct
closure
default -0.1454
foreign=fixed_rate -0.4845
investment=fixed_real -0.1458
investment=fixed_real, foreign=fixed_rate -0.4978
varredura.range()[["min", "max", "same_sign"]]
min max same_sign
pib_pct -0.4978 -0.1454 True
cge.in_money(base, alt).loc["final_demand", ["change", "change_base_prices"]]
change change_base_prices
item
consumption -0.1189 0.3996
government -0.00778 0
investment -0.07754 -0.2573
exports -0.01755 0.05611
imports -0.01755 0.4819
gdp -0.2042 -0.2835
O fechamento que um resultado supõe é uma escolha, e dois fechamentos defensáveis podem
discordar. A varredura resolve a referência e o cenário sob cada um, diz quais combinações o
pacote recusa e por quê, e dá a amplitude. in_money põe a variação em níveis, a
preços correntes e do ano-base, na unidade da MCS vezes unit: uma MCS em milhões e
unit=1e-3 dão bilhões.
27. De onde vem a variância
res = cge.sobol(run, params, n=256, bootstrap=100) # o run e os parâmetros do exemplo 12
res.indices()
res.indices()
S1 S1_conf ST ST_conf
output parameter
gdp_pct sigma_va 0.001761 0.009473 0.003 0.0007993
sigma_armington 1.013 0.1872 1.012 0.1222
Os índices de Sobol repartem a variância de um resultado entre os parâmetros: S1
é a parcela que um parâmetro explica sozinho, ST soma as suas interações. O desenho é
o de Saltelli, com n (k + 2) soluções, e o SALib dá os mesmos índices no mesmo
desenho. Aqui a elasticidade de Armington carrega quase toda a variância do PIB real. Uma
estimativa pode passar de um por erro de amostragem; as semiamplitudes dizem de quanto, e mais
pontos de base as reduzem.
28. A MCS também é uma estimativa
sorteios = cge.sam_draws(sam_toy,
blocks={"intermediario": (["c_agr", "c_ind"], ["a_agr", "a_ind"])},
concentration=80.0, draws=8, seed=7,
balance_kwargs={"rel_tol": 1e-12, "abs_tol": 1e-10})
incerteza = cge.sam_sweep(sorteios, toy.spec(), measure=pib_pct, scenario=choque)
incerteza.points[["draw", "sam_distance", "pib_pct"]]
incerteza.summary()
incerteza.points[["draw", "sam_distance", "pib_pct"]]
draw sam_distance pib_pct
0 0 9.736e-05 -0.1447
1 1 7.543e-05 -0.146
2 2 0.0004869 -0.1437
3 3 0.001046 -0.1446
4 4 0.0007494 -0.1434
5 5 0.0004054 -0.1466
6 6 0.001005 -0.1448
7 7 4.359e-05 -0.1451
incerteza.summary()
mean sd q0.05 q0.5 q0.95 min max
pib_pct -0.1449 0.001002 -0.1464 -0.1447 -0.1435 -0.1466 -0.1434
O choque Dirichlet direto move o bloco intermediário; depois o RAS com preservação de sinal restaura o equilíbrio. Cada MCS é recalibrada e resolvida, e os pontos conservam seu peso e sua distância KL da original. O resumo é a distribuição do resultado sobre os sorteios da MCS, não apenas sobre as elasticidades.
29. O relatório de robustez completo
rel = cge.robustness_report(sam_toy, toy.spec(), scenario=choque, measure=pib_pct,
sam_blocks={"intermediario": (["c_agr", "c_ind"], ["a_agr", "a_ind"])},
draws=4, seed=7,
sam_kwargs={"balance_kwargs": {"rel_tol": 1e-12, "abs_tol": 1e-10}})
rel.tables["monte_carlo"]
rel.tables["oat"][["low", "central", "high"]]
rel.tables["monte_carlo"]
mean sd q0.05 q0.5 q0.95 min max
pib_pct -0.1395 0.04613 -0.1833 -0.1523 -0.07789 -0.1845 -0.06909
rel.tables["oat"][["low", "central", "high"]]
point low central high
output parameter
pib_pct sigma_va -0.145 -0.1454 -0.1455
sigma_armington NaN -0.1454 -0.224
Uma chamada reúne faixas um de cada vez, quadratura, Monte Carlo, quatro fechamentos,
sorteios da MCS e tabelas em moeda. Os resultados completos ficam em parts. Aqui o
ponto inferior de Armington é NaN porque vale exatamente um, que este fechamento recusa;
oat_failed registra o motivo. Figuras de tornado e distribuição estão disponíveis
com o extra opcional plot.
30. Um fechamento histórico limitado
import dataclasses
orcamento = simples.hh_budget.copy()
pesos = orcamento[0] * np.array([1.2, 1.0])
orcamento[0] = pesos / pesos.sum()
economia_observada = dataclasses.replace(simples, hh_budget=orcamento)
observado = cge.solve_period(economia_observada,
scenario=cge.Scenario(productivity=np.full(simples.n_act, 1.05)))
hist = cge.calibrate_historical(simples, years=[2021],
targets={"pib": (pd.Series({2021: cge.real_gdp(observado)["expenditure"]}),
lambda r: cge.real_gdp(r)["expenditure"]),
"alimento": (pd.Series({2021: observado.hh_quantity[0, 0]}),
lambda r: r.hh_quantity[0, 0])},
shifters={"pib": ("productivity",),
"alimento": ("preference", "hh_1", "c_agr")},
preference_reference={"hh_1": "c_ind"})
hist.achieved
hist.multiplier
hist.preferences["hh_1"]
hist.achieved
pib alimento
2021 210.8 34.42
hist.multiplier
a_agr a_ind
2021 1.05 1.05
hist.preferences["hh_1"]
c_agr c_ind
2021 1.2 1
O PIB e a demanda alimentar observados são os alvos; a produtividade e a preferência relativa
por alimento são as incógnitas, resolvidas em conjunto em log. A outra mercadoria fixa a escala
das preferências. Depois, hist.run(policy) mantém esses deslocadores e deixa os
alvos se moverem. calibrate_baseline usa as mesmas rotinas de raiz.
As três verificações
Não são formalidade. Um modelo que não passa na primeira não está calibrado.
# cge.report(econ) em toy.build_with_margins()
cgelab diagnostics -- Economy(3 activities, 3 commodities, 2 households, 2 factors)
base-year reproduction : PASS (worst 7.11e-16)
imports 7.11e-16
activity_output 4.26e-16
commodity_output 4.26e-16
exports 3.55e-16
Walras residual : -2.41e-16 [f_lab]
price homogeneity : PASS (worst 3.72e-16)
| Verificação | O que detecta |
|---|---|
| Reprodução do ano-base | Sem choque, o modelo precisa reproduzir a MCS: todos os preços iguais a um e todas as quantidades nos valores do ano-base. É essa verificação que flagra erros de unidade na calibração, como um preço de comprador tratado como preço básico ou um tributo aplicado sobre a base errada, antes que eles se diluam num resultado. |
| Lei de Walras | Um resíduo diferente de zero indica renda perdida ou criada em algum ponto do modelo, e um vazamento pequeno pode causar um erro grande. |
| Homogeneidade | Multiplicar o numerário por um escalar não pode alterar nenhuma variável real. Se alterar, alguma grandeza nominal expressa em unidades do numerário ficou fixa enquanto o nível de preços mudou: a poupança externa, uma meta de balança comercial, um tributo específico que entra de forma aditiva. Cada uma delas distorce alguma alocação real, e nenhuma aparece na saída. |
O que o pacote oferece
MCS
Leitura em formato longo ou quadrado, verificação de balanceamento que impede a execução quando falha e RAS com preservação de sinais, para as células negativas comuns em MCS reais.
Calibração
Coeficientes a preços básicos, bloco de margens, valor adicionado negativo, subsistência LES, atividades de conta própria e a lista das células da MCS que o modelo não lê.
Produção
Leontief entre o agregado intermediário e o valor adicionado, com CES, Cobb-Douglas ou Leontief no ninho de fatores.
Comércio
Armington CES; exportações por participações fixas ou por CET com preço mundial de exportação.
Instituições
Impostos diretos, transferências do governo, remessas, renda de fatores do governo e do exterior; a poupança externa é o saldo em conta corrente.
Dinâmica
Recursiva, com um ou vários estoques de capital, investimento alocado segundo o retorno, IED que gera capital de propriedade estrangeira e economia que pode mudar a cada ano.
Fechamentos
Investimento, governo e setor externo, cada um com a sua variável de ajuste. Cinco combinações testadas.
Bem-estar
VE e VC, com agregação por grupo imune a mudanças de composição e as duas bases de preço explícitas.
Réplica em JuMP
O mesmo equilíbrio escrito como modelo JuMP sem aninhamento, para conferir a álgebra com o Ipopt.
Uma cautela embutida no padrão
A elasticidade de transformação (CET) é o único parâmetro de um EGC padrão que as tabelas de
insumo-produto não fornecem. Com participações de exportação fixas, esse parâmetro fica
totalmente fora do cenário-base, e nenhum resultado principal depende de um valor arbitrado.
Quando o exercício exige que as exportações respondam a preço, export_rule="cet"
resolve a fronteira de transformação completa. É uma posição metodológica, e o padrão do pacote a
adota.
Validação
Testes com MCS escritas à mão
São oito economias didáticas com cada entrada escrita no código, de modo que os testes comparam o resultado com a aritmética, e não com uma saída anterior:
- o caso simples;
- uma com bloco de margens no padrão das contas nacionais (serviços de margem lançados na coluna de oferta das demais mercadorias, com contrapartida negativa na própria diagonal);
- uma com atividade de conta própria;
- uma com contas gêmeas, para o teste de composição;
- uma com impostos diretos, transferências, remessas e capital do governo e do exterior;
- uma com capital específico de um setor;
- uma com tarifa e imposto indireto sobre o produto incluídos nos preços;
- uma economia fechada de um bem com produção Cobb–Douglas, que é o modelo de crescimento estocástico usado para conferir a ponte com o dsgelab.
Os testes cobrem a reprodução do ano-base na precisão de máquina, a homogeneidade e a lei de Walras sob choque, em todas as combinações de fechamento e em cada ano de uma trajetória com crescimento. Cobrem também a identidade de receita da CET e a propriedade estrangeira do capital adquirido com IED. Todos os exemplos executáveis desta página também rodam na suíte.
Uma réplica independente em JuMP
to_jump escreve o mesmo equilíbrio como um modelo JuMP sem nenhum aninhamento: o
ponto fixo de preços, a série da reciclagem e a relação entre poupança e arrecadação viram
restrições comuns, que o Ipopt resolve a partir de um ponto inicial deslocado. Nos 50 casos
testados (margens, curvas de demanda de exportação e de oferta de importação, tarifa e imposto
sobre o produto incluídos nos preços, o fechamento de Hosoe, metas do governo, preço do carbono,
dois ninhos de energia, conta própria, CET, impostos diretos, transferências, capital
estrangeiro, IED, LES, mobilidade imperfeita de fatores, trabalho segmentado, safras de capital
e fechamentos alternativos), os dois solucionadores diferem em no máximo
1.2e-11. Quando os dados exportados são perturbados, a diferença sobe para valores
entre 1e-3 e 3e-1, o que mostra que a comparação detecta erros.
Comparação com o que já existe
A diferença, aqui, não é de opinião, e sim de escala.
| Capacidade | cgelab | pycge | PSLmodels/CGE | python-cge |
|---|---|---|---|---|
| Escala demonstrada | MCS aplicada ainda não pública | didática | MCS 16×18 4 setores | 2 bens |
| Origem | MCS aplicada | adaptação do splcge.gams | Hosoe et al. (2010) | Hosoe et al., p. 72 |
| Bloco de margens de comércio e transporte | sim | ? | — | — |
| Tratamento de valor adicionado negativo | sim | ? | — | — |
| Atividades de conta própria | sim | — | — | — |
| Fechamentos alternativos | 5 combinações | ? | — | — |
| Walras como verificação, não como equação | sim | ? | — | — |
| Reprodução do ano-base e homogeneidade automáticas | sim | parcial | — | — |
| CET resolvida, com preço mundial de exportação | sim | ? | sim | — |
| Impostos diretos, transferências, capital estrangeiro e IED | sim | ? | ? | ? |
| Dinâmica recursiva | vários capitais | — | — | — |
| Bem-estar (VE, VC, por grupo) | sim | — | — | — |
Quem trabalha com EGC aplicado usa GAMS ou GEMPACK, e com
razão: solucionadores dedicados, uma comunidade estabelecida, bases de dados como a GTAP. Os três
pacotes de EGC em Python que encontrei implementam o modelo do livro-texto de Hosoe, Gasawa e
Hashimoto (2010) ou o splcge da biblioteca do GAMS, com dois a quatro setores; o
README do PSLmodels/CGE descreve o projeto como
"muito preliminar" e pede que ele não seja citado.
O que se pode afirmar sobre o cgelab é algo restrito: para um EGC de escala
aplicada em Python, sem licença comercial e com as verificações contábeis embutidas, não
encontrei alternativa. Isso está longe de cobrir o que o GAMS cobre: não há modelos
multirregionais, nem estimação de elasticidades, nem trinta anos de uso acumulado. A tabela
completa, que distingue o que foi verificado no código do que só foi lido na documentação, está
em docs/comparison.pt-br.md.
Como citar
Se o pacote contribuiu para um resultado publicado, ele deve constar das referências. Software é um insumo metodológico como qualquer outro, e a versão importa: um número que só se reproduz com uma versão específica não pode ser reproduzido sem a indicação dela.
BibTeX
@software{cgelab_2026,
author = {Moreira, Gustavo Hoffmann},
title = {{cgelab}: a general toolkit for SAM-based computable general equilibrium models in Python},
year = {2026},
version = {0.6.0},
license = {MIT},
doi = {10.5281/zenodo.23614565},
url = {https://github.com/GustavoHM/cgelab},
note = {Pacote em Python}
}
ABNT
MOREIRA, Gustavo Hoffmann. cgelab: a general toolkit for SAM-based computable general equilibrium models in Python.
Versão 0.6.0. 2026. Software em Python. Zenodo. DOI: 10.5281/zenodo.23614565. Disponível em:
https://doi.org/10.5281/zenodo.23614565. Acesso em: [data].
No corpo do texto
Informe a versão junto com o resultado, e não apenas o nome do pacote:
O modelo foi calibrado e resolvido com o cgelab 0.6.0
(Moreira, 2026), em dinâmica recursiva de 2021 a 2045.
Cite a versão arquivada. Uma URL de repositório não serve
como referência permanente: o conteúdo muda e o link pode deixar de funcionar. Cada versão do
cgelab é arquivada no Zenodo, e o DOI que o Zenodo atribui a
ela é a referência a citar: a versão 0.6.0 é 10.5281/zenodo.23614565,
e o DOI conceitual 10.5281/zenodo.23458851
aponta sempre para a versão mais recente. O arquivo CITATION.cff, na raiz do
pacote, traz o ORCID do autor e o DOI conceitual no formato que o GitHub lê, e o botão "Cite this
repository" gera a entrada a partir dele.
As ferramentas em que o pacote se apoia
NumPy e SciPy pedem citação explicitamente e estão na base de tudo o que o pacote faz; o pandas lê e armazena a MCS. Se o resultado foi conferido com a réplica, cite também o JuMP e o Ipopt.
Documentação
A documentação detalhada está em inglês, exceto a comparação.
Guia de uso
Da MCS ao bem-estar, com as armadilhas de cada etapa.
Referência da API
Todas as funções, campos e formatos de array.
Notas de projeto
As decisões contábeis e os erros que cada uma evita.
Migrar um pipeline
Correspondência
entre os ajustes de um solucionador existente e Scenario e Closure.
Comparação
Como o pacote se situa diante dos outros EGC em Python e do GAMS, e onde fica atrás.
Mudanças
O que mudou de uma versão para outra.
Roadmap
O que ainda falta no pacote, e por que importa.
Replicações
Modelos publicados reconstruídos a partir das próprias MCS, e os pontos em que um resultado ainda não pode ser dado como replicado.