English · Português

cgelab

Você fornece uma matriz de contabilidade social. O pacote calibra um EGC aninhado padrão a partir dela, resolve cada período, percorre uma trajetória dinâmica recursiva e mede bem-estar — com as verificações contábeis que tornam qualquer disso crível já embutidas, em vez de deixadas ao usuário.

github.com/GustavoHM/cgelab

Python 3.11+ numpy, scipy, pandas Walras como verificação 5 fechamentos CET, IED, vários capitais conferido contra JuMP a 1.2e-11 MIT · v0.5.0

Três objetos separáveis, e mantê-los assim é o ponto

ObjetoDiz
SAMo que o ano-base foi
ModelSpeco que se supõe sobre substituição e forma funcional
Closurequais balanços são impostos e quem se ajusta

Resultados podem virar nos dois últimos, e por isso eles viajam junto com os resultados em vez de morar num ramo dentro de um solucionador.

O mercado do fator numerário não é resolvido. Fica para a lei de Walras, de sorte que o seu resíduo é uma verificação independente da contabilidade e não uma identidade que se mandou o solucionador satisfazer. É registrado em cada período.

Instalar

pip install cgelab
pip install "cgelab[parquet]"     # MCS em .parquet
pip install "cgelab[dsge]"        # investimento com expectativas, pelo dsgelab
pip install "cgelab @ git+https://github.com/GustavoHM/cgelab"   # o ramo main atual

Para desenvolver:

git clone https://github.com/GustavoHM/cgelab && cd cgelab
pip install -e ".[test]" && pytest
cgelab                       # roda a economia de brinquedo e imprime os diagnósticos
cgelab minha_mcs.parquet --sigma-armington 2.0 --sigma-va 0.5

Exemplos

Todos rodam como estão, logo depois do pip install: usam a economia de brinquedo que vem no pacote, escrita entrada por entrada para que o ano-base seja conhecido no papel. São os mesmos exemplos que a suíte de testes executa.

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

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, mercadoria a mercadoria
alt.real["hh_real_income"]           # renda real por domicílio
abs(alt.walras) < 1e-9               # a contabilidade continua fechando

3. Um gravame ao consumidor, e a quem volta 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 por 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

capital.hh_income[0] > percap.hh_income[0]           # o domicílio rico em capital ganha
sum(capital.hh_wedge_paid) == capital.wedge_revenue  # a identidade vale exatamente

Isso não é detalhe de implementação. A propriedade do capital é concentrada no topo: sob "capital" os pobres pagam o gravame nas suas compras e os ricos o recebem como renda, e parte de qualquer regressividade medida é fabricada por essa hipótese. A forma convexa existe para que o achado ganhe magnitude em vez de ser binário — existe em geral um peso em que o gradiente distributivo cruza o zero, e é essa fração que diz se o resultado tem folga.

A receita reciclada é uma série infinita: creditá-la à renda eleva o gasto, que eleva o gravame, que eleva a renda de novo. A demanda é afim no gasto, logo a série é somada em forma fechada — e iterá-la converge, mas deixa descontinuidades que arruínam 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 número a olhar primeiro
ref.capital_stock         # estoque, recuperado do custo de uso e não da MCS

Um resíduo de Walras que cresce ao longo da trajetória é a assinatura de um coeficiente que o bloco de preços viu e o de quantidades não. É invisível num período isolado e inconfundível num horizonte de vinte e cinco anos.

5. Bem-estar, sem confundir composição com bem-estar

ev = cge.equivalent_variation(ref, alt)
ev.ev              # valorado aos preços do cenário de referência
ev.ev_base         # valorado aos preços do ano-base
ev.ev_pct_account  # por CONTA — leia a nota abaixo antes de usar

Quando domicílios migram entre contas, a medida por conta reporta a migração como bem-estar. A economia de brinquedo tem uma variante com duas contas gêmeas — mesmas preferências, mesma taxa de poupança — justamente para mostrar isso:

sam, econ2 = toy.build_with_twin_accounts()   # hh_1, e hh_2 partido em hh_2a e hh_2b

migrado = econ2.hh_facshare.copy()           # move 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% — composição, reportada 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 zero que de fato está lá

ev_by_group soma rendas reais dentro do grupo antes de dividir, de modo que o conjunto de domicílios somado é o mesmo nos dois cenários e a razão não depende de quantos migraram. Toda conta precisa estar no mapeamento: descartar uma em silêncio subestimaria o grupo, e o erro pareceria um 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 mundo paga 20% mais pela indústria

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 sobe de 0,100
depois.pe[j] / depois.pd[j]        # 1,09: exportar agora rende mais que vender em casa
depois.exchange_rate               # 0,89: o câmbio se valoriza e absorve parte do choque

Sob export_rule="cet" o produto se reparte entre mercado interno e exportação ao longo de uma fronteira de transformação, e o preço doméstico é o que faz a receita dessa fronteira igualar o custo unitário. Com participações fixas, o padrão, export_price é recusado em vez de ignorado em silêncio.

_, 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: vender mais desce pela curva de demanda do comprador
big.exports[j] / econ_big.exp0[j]    # 1.14: as exportações sobem 14%, contra 22% do tomador de preço

Uma região que vende boa parte de um produto não é tomadora de preço. ModelSpec.export_demand põe as suas exportações numa curva de demanda, E / E0 = (pWe / export_price)^(-η), a forma do lrgcge de Hosoe; import_supply faz o mesmo com 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 de imposto direto por domicílio, lida da MCS
econ_i.fac_row_share    # parcela de cada fator que pertence ao exterior
econ_i.foreign_sav      # 18: a 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 saem
ied[2030].hh_income.sum() < doado[2030].hh_income.sum()             # e a renda local é menor

O mesmo fluxo de capital, duas leituras. Como fdi, ele financia investimento e vira capital de propriedade estrangeira, cujo retorno sai pela conta corrente. Como mera poupança externa, o capital fica com os residentes e todo o ganho que produz é creditado a eles. É a diferença entre medir o efeito de um investimento estrangeiro e superestimá-lo.

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 só dela

g = cge.Growth(capital_factor=("f_cap", "f_kind"),
               depreciation={"f_cap": 0.05, "f_kind": 0.08},
               allocation=2.0)                     # o investimento segue o aluguel relativo

def economia(ano, t):                             # a indústria poupa 2% de insumo agrícola ao 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 por fator de capital
traj.worst_walras          # continua à precisão de máquina

Uma mudança que não é preço, tributo nem dotação (a adoção de uma tecnologia, um plano de financiamento) entra pelo argumento economy, que devolve a economia de cada ano. É o 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 vendo a mesma tecnologia.

9. Com a sua própria MCS

A economia de brinquedo existe para os exemplos rodarem. Com dados seus, o que muda são 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()                            # porta dura: nada roda sobre 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 dele fica para a 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": a elasticidade CET não tem fonte de insumo-produto
    household_demand="cobb_douglas",  # ou "les", com o parâmetro de Frisch
    negative_value_added="leontief",  # um excedente operacional negativo é dado, não erro
)
econ = cge.calibrate(sam, spec, population={"hh_d1": 8.3e6, "hh_d2": 8.3e6})

assert cge.base_year_reproduction(econ)["ok"]  # não prossiga sem isto
assert cge.homogeneity_test(econ)["ok"]
econ.diagnostics["unmodelled_flows"]           # células da MCS que o modelo não lê

Contas são encontradas por prefixo, ou por listas explícitas — que são a escolha mais segura numa MCS cuja nomenclatura não é sistemática, já que uma regra de prefixo que perde uma conta em silêncio produz um modelo que balanceia e está errado.

10. Multiplicadores, com todos os preços fixos

sam_toy, _ = toy.build()
m = cge.accounting_multipliers(sam_toy, spec=toy.spec())  # gov, investment, rest of world exogenous

m.M.loc["hh_2", "c_agr"]         # 0.546: o que uma unidade de demanda por agricultura soma à 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    pelos salários, com a realimentação de fora do caminho
# (c_agr, a_agr, f_cap, hh_2)  0.131  0.24

A mesma MCS lida como modelo a preço fixo: as quantidades respondem a uma injeção nas proporções do ano-base. leontief_multipliers deixa endógena só a produção, fixed_price_multipliers põe as famílias em propensões marginais, e linkages dá a tabela de setores-chave, para trás e para frente. Somadas sobre todos os caminhos elementares, as influências totais dão o multiplicador.

11. Contas

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: o PIB pelos dois lados, em qualquer equilíbrio
pi = cge.price_indices(taxed)
pi.loc["gdp", ["fisher", "quantity_fisher"]]  # 1.040, 0.9999: preços 4% acima, volume parado
cge.government_account(taxed)["saving"]   # 12.04, contra 5 no ano-base

O PIB a preços correntes pelos lados 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. A conta do governo é registrada pelo solver na solução, então a poupança dela é a 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()

Cada ponto recalibra e resolve o ano-base e o cenário. A regra de Stroud dá uma média exata até o grau três nos parâmetros com 2n soluções; o Monte Carlo acrescenta quantis. A elasticidade de Armington perto de 1 fica de fora de propósito: com parcelas de exportação fixas o câmbio não é determinado em 1, e é mal condicionado perto dele.

13. Que choque fez o quê

_, 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 partes somam a variação resolvida

A decomposição que o GEMPACK chama de subtotals. Todos os choques andam juntos pela reta da referência à alternativa, e o termo de cada grupo é integrado à parte, então as partes somam o total sem depender de ordem alguma.

14. De onde veio 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 com a tarifa, e o que um país grande paga por ele lá fora
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

A decomposição de Huff e Hertel, escrita para os instrumentos deste modelo: dotações, tecnologia, um termo alocativo por distorção, termos de troca e conta externa, integrados ao longo da trajetória. A medida é a absorção real, com governo e investimento. A parte das famílias é a que se compara a uma VE.

15. Um preço de carbono, com reciclagem

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, eram 22 no ano-base
r.tax_scale                       # 0.26: a receita do carbono paga um corte de 74% nas alíquotas sobre produtos

As emissões são uma conta satélite calibrada a partir dos totais do ano-base, por fonte. O preço do carbono é um imposto por unidade de produto, pela intensidade de emissão, nas atividades, e um imposto específico no combustível das famílias. Com meta de receita, outro imposto se move para manter a arrecadação; aqui, o imposto sobre produtos.

16. 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, eram 0,095 e 0,286

Com ModelSpec.energy, cada atividade compra um agregado de energia que substitui valor adicionado. O carbono de um combustível entra no preço que aquela atividade paga por ele, então o mesmo preço de carbono muda como se produz, não só quanto. Com as duas elasticidades em zero, o ninho é o modelo Leontief, à precisão de máquina.

As três portarias

Não são formalidades. Um modelo que falha a 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)
PortariaO que pega
Reprodução do ano-baseSem choque, o modelo tem de devolver a MCS: todo preço em um, toda quantidade no seu valor de base. É o que pega um erro de unidade na calibração — um preço de comprador tratado como preço básico, um tributo lançado sobre a base errada — antes que ele seja absorvido num resultado.
Lei de WalrasResíduo não nulo significa renda sendo perdida ou fabricada em algum lugar, e o tamanho do vazamento não limita o tamanho do erro que causa.
HomogeneidadeEscalar o numerário tem de deixar toda magnitude real intacta. Falha significa que uma magnitude nominal denominada no numerário ficou presa enquanto o nível de preços se moveu — poupança externa, meta de balança comercial, um tributo específico que entra aditivamente. Cada uma desloca uma alocação real, e nenhuma é visível na saída.

O que tem na caixa

MCS

Leitura em formato longo ou quadrado, verificação de balanço como porta dura e RAS que preserva sinais, para as células negativas que MCS reais têm.

Calibração

Coeficientes em 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 agregado intermediário e 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 é a conta corrente.

Dinâmica

Recursiva, com um ou vários estoques de capital, investimento repartido pelo retorno, IED que cria capital estrangeiro e economia por 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 à composição e as duas réguas de preço explicitadas.

Réplica em JuMP

O mesmo equilíbrio escrito como modelo JuMP sem aninhamento, para conferir a álgebra com o Ipopt.

Uma advertência que o padrão codifica

A elasticidade de transformação (CET) é o único parâmetro de um EGC padrão que as fontes de insumo-produto não fornecem. Fixar participações de exportação mantém esse parâmetro fora do cenário-base por inteiro, de modo que nenhum número de destaque repouse sobre um valor inventado. export_rule="cet" resolve a fronteira de transformação por inteiro, para quando o exercício precisa que as exportações respondam a preço. É uma posição metodológica, e o padrão a codifica.

Validação

Testes sobre MCS escritas à mão

Oito economias de brinquedo com cada entrada escrita no código, de modo que a comparação é contra aritmética e não contra saída anterior:

  • o caso simples;
  • um com bloco de margens de contabilidade nacional (serviços de margem vendidos na coluna de oferta das demais mercadorias, contra uma entrada negativa na própria diagonal);
  • um com atividade de conta própria;
  • um com contas gêmeas, para o teste de composição;
  • um com impostos diretos, transferências, remessas e capital do governo e do exterior;
  • um com capital específico de setor;
  • um com tarifa e imposto indireto sobre o produto dentro dos preços;
  • uma economia fechada de um bem com produção Cobb–Douglas, que é o modelo de crescimento estocástico contra o qual a ponte com o dsgelab é conferida.

Os testes cobrem reprodução do ano-base à precisão de máquina, homogeneidade, e Walras sob choque em todas as combinações de fechamento e em cada data de uma trajetória em crescimento. Cobrem também a identidade de receita da CET e a propriedade estrangeira do capital comprado com IED. E todo exemplo executável desta página roda na suíte.

Uma réplica independente em JuMP

to_jump escreve o mesmo equilíbrio como modelo JuMP em que nada é aninhado: o ponto fixo de preços, a série da reciclagem e a relação entre poupança e arrecadação são restrições comuns, e o Ipopt as resolve de um ponto de partida deslocado. Em 34 casos (margens, curvas de demanda de exportação e de oferta de importação, tarifa e imposto sobre o produto dentro dos 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 e fechamentos alternativos), os dois resolvedores concordam a no máximo 1.2e-11. Perturbar os dados exportados faz a comparação divergir de 1e-3 a 3e-1, o que mostra que ela enxerga erro.

Como se compara ao que já existe

Aqui a diferença não é de opinião: é de escala.

CapacidadecgelabpycgePSLmodels/CGEpython-cge
Escala demonstradaMCS aplicada
ainda não pública
didáticaMCS 16×18
4 setores
2 bens
OrigemMCS aplicadaporte do splcge.gamsHosoe et al. (2010)Hosoe et al., p. 72
Bloco de margens de comércio e transportesim?——
Valor adicionado negativo tratadosim?——
Atividades de conta própriasim———
Fechamentos alternativos5 combinações?——
Walras como verificação, não equaçãosim?——
Reprodução do ano-base e homogeneidade automáticassimparcial——
CET resolvida, com preço mundial de exportaçãosim?sim—
Impostos diretos, transferências, capital estrangeiro e IEDsim???
Dinâmica recursivavários capitais—sim—
Bem-estar (VE, VC, por grupo)sim———

Quem faz EGC aplicado usa GAMS ou GEMPACK, e com bons motivos: resolvedores dedicados, comunidade, bases como a GTAP. Os três pacotes de EGC em Python que encontrei são implementações do modelo-texto de Hosoe, Gasawa e Hashimoto (2010) ou do splcge da biblioteca do GAMS — dois a quatro setores; o PSLmodels/CGE diz de si mesmo, no README, que é "muito preliminar" e pede que não seja citado.

A afirmação defensável do cgelab é estreita: se você quer um EGC de escala aplicada em Python, sem licença comercial e com as portarias contábeis embutidas, não achei alternativa. Isso não é o mesmo que cobrir o que um GAMS cobre — não há múltiplas regiões, nem estimação de elasticidades, nem trinta anos de uso. A tabela completa, com o que foi verificado lendo código e o que apenas se leu na documentação, está em docs/comparison.pt-br.md.

Como citar

Se o pacote entrou num resultado publicado, ele entra nas referências. Software é um insumo metodológico como qualquer outro, e a versão importa: um número que só se reproduz numa versão específica não é reproduzível sem ela.

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.5.0},
  license = {MIT},
  doi     = {10.5281/zenodo.23458852},
  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.5.0. 2026. Software em Python. Zenodo. DOI: 10.5281/zenodo.23458852. Disponível em:
https://doi.org/10.5281/zenodo.23458852. Acesso em: [data].

No corpo do texto

Cite a versão junto do resultado, não só o nome:

O modelo foi calibrado e resolvido com o cgelab 0.5.0 (Moreira, 2026), em dinâmica recursiva de 2021 a 2045.

Cite a versão arquivada. Uma URL de repositório não é uma referência arquivável: o conteúdo muda e o link morre. 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.5.0 é 10.5281/zenodo.23458852, e o DOI conceitual 10.5281/zenodo.23458851 aponta sempre para a versão mais recente. O arquivo CITATION.cff na raiz do pacote, que traz o ORCID do autor e o DOI conceitual, está no formato que o GitHub lê, e o botão "Cite this repository" fornece a entrada a partir dele.

E as ferramentas que este pacote usa

NumPy e SciPy pedem citação explicitamente e sustentam tudo o que está aqui; o pandas lê e guarda a MCS. Se o resultado foi conferido pela réplica, entram 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

Toda função, todo campo, toda forma de array.

Notas de projeto

As decisões contábeis e os erros que cada uma evita.

Portar um pipeline

Tabela de tradução de um solucionador existente para Scenario e Closure.

Comparação

Onde o pacote está frente aos outros EGC em Python e ao GAMS, e onde não ganha.

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 refeitos a partir da matriz de contabilidade social deles, e onde um resultado ainda não pode ser reivindicado.