Skip to content

Macroeconomics

Macroeconomics: growth, business cycles, investment, international trade, and mortal monetary economies.

BusinessElement(label, visual=None, *, money=0.0, loans=0.0, stock=0.0, wage=4.0, price=1.0, owner=-1.0, workers=1.0, vacancies=1.0, revenue=0.0, interest=0.003, repayment=0.004, adjustment=0.05, stock_limit=30.0)

Bases: Element

Firm on a patch that harvests with its workers, pays wages with money it borrows, and sells food in its shop.

Every step the workers pool their labour on the firm's patch, the firm pays each the wage, borrowing what it lacks from the bank, and pays the interest and a share of the principal of its loans. A firm that sold out raises its price, and one with a stock above stock_limit lowers it and lays a worker off. Otherwise a firm posts vacancies up to the workforce that harvests the whole capacity of its patch, raises the wage while a vacancy went unfilled, and lowers it while its sales fall short of its wage bill. Profits above twice the wage bill go to the owner, and a firm without a living owner passes to one of its workers.

Parameters:

Name Type Description Default
label str

The label of the firm or the group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
money Annotated[float, StateSpec(quantity=money)]

The money the firm holds.

0.0
loans Annotated[float, StateSpec(quantity=credit)]

The loans of the firm, negative for its debt.

0.0
stock Annotated[float, StateSpec(domain=closed_open(0, oo))]

The food in the shop.

0.0
wage Annotated[float, StateSpec(domain=closed_open(0, oo))]

The wage of a worker.

4.0
price Annotated[float, StateSpec(domain=open(0, oo))]

The price of a unit of food.

1.0
owner Annotated[float, StateSpec(reference=people)]

The id of the owner.

-1.0
workers Annotated[float, StateSpec(domain=NN)]

The number of workers in the step.

1.0
vacancies Annotated[float, StateSpec(domain=NN)]

The open vacancies.

1.0
revenue Annotated[float, StateSpec(domain=closed_open(0, oo))]

The sales of the step.

0.0
interest Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The interest rate of loans per step.

0.003
repayment Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of the principal repaid per step.

0.004
adjustment Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The relative step of a change of price or wage.

0.05
stock_limit Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The stock above which the firm lowers its price and lays off.

30.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.macroeconomics import BusinessElement
>>> GroupElement('businesses', element=BusinessElement, size=2, variable=True).size
2
Source code in src/skcomplex/models/economics/macroeconomics/_business.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    money: Annotated[float, StateSpec(quantity='money')] = 0.0,
    loans: Annotated[float, StateSpec(quantity='credit')] = 0.0,
    stock: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    wage: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 4.0,
    price: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 1.0,
    owner: Annotated[float, StateSpec(reference='people')] = -1.0,
    workers: Annotated[float, StateSpec(domain=NN)] = 1.0,
    vacancies: Annotated[float, StateSpec(domain=NN)] = 1.0,
    revenue: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    interest: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.003,
    repayment: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.004,
    adjustment: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
    stock_limit: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 30.0,
) -> None:
    super().__init__(label, visual=visual)
    self.money = money
    self.loans = loans
    self.stock = stock
    self.wage = wage
    self.price = price
    self.owner = owner
    self.workers = workers
    self.vacancies = vacancies
    self.revenue = revenue
    self.interest = interest
    self.repayment = repayment
    self.adjustment = adjustment
    self.stock_limit = stock_limit

produce(agent, model)

Harvest with the workers, pay wages and the bank, adjust, and pay a dividend to the owner.

Parameters:

Name Type Description Default
agent BusinessView

The firm.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/economics/macroeconomics/_business.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def produce(self, agent: BusinessView, model: ModelView) -> None:
    """Harvest with the workers, pay wages and the bank, adjust, and pay a dividend to the owner.

    Args:
        agent:
            The firm.

        model:
            The model.
    """
    people = BusinessElement._people_by_id(model)
    workers = [person for person in people.values() if person.employer == agent.id]
    bank = model.agents('bank', CentralBankView)[0]
    patch = SettlerElement._patch_here(agent)
    sold_out = agent.stock <= 0
    for worker in workers:
        worker.move_to(agent.location)
    labour = sum(worker.labour for worker in workers)
    harvest = min(patch.food, max(labour - patch.resistance, 0.0))
    patch.food -= harvest
    agent.stock += harvest
    bill = agent.wage * len(workers)
    self._borrow(agent, bank, bill - agent.money)
    for worker in workers:
        transfer(agent, worker, agent.wage, 'money')
        worker.harvested = worker.need
    self._service(agent, bank)
    self._adjust(agent, workers, patch, sold_out)
    agent.workers, agent.revenue = float(len(workers)), 0.0
    owner = agent.referenced('owner')
    if not isinstance(owner, SettlerView) and workers:
        owner = workers[0]
        agent.owner = float(owner.id)
    if isinstance(owner, SettlerView) and agent.money > 2 * bill:
        transfer(agent, owner, agent.money - 2 * bill, 'money')

CapitalStockElement(label, visual=None, *, K=100.0, investment_fraction=0.2, output_per_capital=1 / 3, lifetime=15.0)

Bases: Element

Capital stock that reinvests a fraction of its output and depreciates over its lifetime.

Parameters:

Name Type Description Default
label str

The label of the stock.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
K Annotated[object, StateSpec(domain=closed_open(0, oo))]

The capital stock.

100.0
investment_fraction Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The fraction of the output that is reinvested.

0.2
output_per_capital Annotated[object, ParameterSpec(domain=open(0, oo))]

The output per unit of capital and unit time.

1 / 3
lifetime Annotated[object, ParameterSpec(domain=open(0, oo))]

The average lifetime of the capital.

15.0

Examples:

>>> from skcomplex.models.economics.macroeconomics import CapitalStockElement
>>> CapitalStockElement('economy', lifetime=20.0).lifetime
20.0
Source code in src/skcomplex/models/economics/macroeconomics/_capital.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    K: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 100.0,
    investment_fraction: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
    output_per_capital: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1 / 3,
    lifetime: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 15.0,
) -> None:
    super().__init__(label, visual=visual)
    self.K = K
    self.investment_fraction = investment_fraction
    self.output_per_capital = output_per_capital
    self.lifetime = lifetime

CentralBankElement(label, visual=None, *, money=0.0, loans=0.0)

Bases: Element

Bank that creates money as loans and pays its interest income to a random person as a dividend.

The transfer dividends of the model pays the income to a random person, or to a random firm when nobody lives.

The money it issued, a negative holding, and the loans it holds sum to zero after every step, so every unit of money in the economy is matched by a loan.

Parameters:

Name Type Description Default
label str

The label of the bank or the group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
money Annotated[float, StateSpec(quantity=money)]

The money of the bank, the negative of the money it issued plus its income of the step.

0.0
loans Annotated[float, StateSpec(quantity=credit)]

The loans the bank holds.

0.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.macroeconomics import CentralBankElement
>>> GroupElement('bank', element=CentralBankElement, size=1).size
1
Source code in src/skcomplex/models/economics/macroeconomics/_central_bank.py
45
46
47
48
49
50
51
52
53
54
55
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    money: Annotated[float, StateSpec(quantity='money')] = 0.0,
    loans: Annotated[float, StateSpec(quantity='credit')] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.money = money
    self.loans = loans

get_income(agent, holder, model) staticmethod

Return the income of the step that the bank pays as a dividend, its money plus its loans.

Parameters:

Name Type Description Default
agent CentralBankView

The bank.

required
holder AgentView

The person, or the firm when nobody lives, that receives the dividend.

required
model ModelView

The model view of the dividends.

required

Returns:

Name Type Description
income float

The income.

Source code in src/skcomplex/models/economics/macroeconomics/_central_bank.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@staticmethod
def get_income(agent: CentralBankView, holder: AgentView, model: ModelView) -> float:
    """Return the income of the step that the bank pays as a dividend, its money plus its loans.

    Args:
        agent:
            The bank.

        holder:
            The person, or the firm when nobody lives, that receives the dividend.

        model:
            The model view of the dividends.

    Returns:
        income:
            The income.
    """
    return agent.money + agent.loans

CircuitInteraction(label, *, households, firms, bank)

Bases: TransferInteraction

Circuit of money between households, firms, and a bank, one period per step.

The flows of a period are the legs of one transfer between the deposits of the sectors, consumption, wages, interest, dividends, and profits, lending from the environment, and repayments to it, each reading the parameters of the sectors. Every leg is named by its flow, so the reporters <label>.<flow> record it.

Parameters:

Name Type Description Default
label str

The label of the circuit.

required
households Annotated[str, RoleSpec()]

The path of the households.

required
firms Annotated[str, RoleSpec()]

The path of the firms.

required
bank Annotated[str, RoleSpec()]

The path of the bank.

required

Examples:

>>> from skcomplex.models.economics.macroeconomics import CircuitInteraction
>>> CircuitInteraction('circuit', households='households', firms='firms', bank='bank').bank
'bank'
Source code in src/skcomplex/models/economics/macroeconomics/_circuit.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def __init__(
    self,
    label: str,
    *,
    households: Annotated[str, RoleSpec()],
    firms: Annotated[str, RoleSpec()],
    bank: Annotated[str, RoleSpec()],
) -> None:
    super().__init__(
        label,
        source=households,
        target=firms,
        state='households.deposits',
        into='firms.deposits',
        amount=lambda s: CircuitInteraction._consumption(s.households, s.firms),
        flow='consumption',
        legs=[
            ('firms.deposits', 'households.deposits', lambda s: s.firms.wage_share * s.firms.sales, 'wages'),
            ('firms.deposits', 'bank.deposits', lambda s: s.bank.interest * s.firms.loans, 'interest'),
            ('bank.deposits', 'households.deposits', lambda s: s.bank.payout * s.bank.deposits, 'dividends'),
            (
                'firms.deposits',
                'households.deposits',
                lambda s: s.firms.distribution * s.firms.deposits,
                'profits',
            ),
            (
                Source('lending'),
                'firms.deposits',
                lambda s: CircuitInteraction._lending(s.firms, s.bank),
                'lending',
            ),
            ('firms.deposits', Sink('repayment'), lambda s: s.firms.repayment * s.firms.loans, 'repayments'),
        ],
    )
    self.households = households
    self.firms = firms
    self.bank = bank

CommonsInteraction(label, *, people)

Bases: Interaction

Harvest of the hunter-gatherers, who pool the labour of the adults on a patch and share the food equally.

The adults of a patch harvest min(food, labour - resistance), and everyone on the patch, child or adult, gets an equal share.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
people Annotated[str, RoleSpec()]

The path of the group of people.

required

Examples:

>>> from skcomplex.models.economics.macroeconomics import CommonsInteraction
>>> CommonsInteraction('commons', people='people').people
'people'
Source code in src/skcomplex/models/economics/macroeconomics/_commons.py
38
39
40
41
42
43
44
45
def __init__(
    self,
    label: str,
    *,
    people: Annotated[str, RoleSpec()],
) -> None:
    super().__init__(label)
    self.people = people

harvest(model)

Harvest every occupied patch and share the food among the people on it.

Parameters:

Name Type Description Default
model ModelView

The model view of the interaction.

required
Source code in src/skcomplex/models/economics/macroeconomics/_commons.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def harvest(self, model: ModelView) -> None:
    """Harvest every occupied patch and share the food among the people on it.

    Args:
        model:
            The model view of the interaction.
    """
    cells: dict[tuple[int, ...], list[SettlerView]] = {}
    for person in model.agents('people', SettlerView):
        cells.setdefault(SettlerElement._cell(person), []).append(person)
    for group in cells.values():
        patch = SettlerElement._patch_here(group[0])
        labour = sum(person.labour for person in group if person.age >= person.maturity)
        harvest = min(patch.food, max(labour - patch.resistance, 0.0))
        patch.food -= harvest
        for person in group:
            person.food += harvest / len(group)
            person.harvested = harvest / len(group)

CreditMarketInteraction(label, *, requesters, providers, rule=RandomSearchSpec(), limit='limit', quantity='quantity', allowance='allowance', transfers=(), phase=None, on_match=None)

Bases: MatchingInteraction[ModelView]

Credit market that also reports the spread of the rates firms pay over the cheapest bank's rate.

The firms request loans and the banks provide them. The spread is the mean of r_i / r_min - 1 over the borrowing firms, the dispersion of rates that a market with costly search leaves and that switching to cheaper lenders erodes.

Parameters:

Name Type Description Default
label str

The label of the market.

required
requesters Annotated[str, RoleSpec()]

The path of the group of firms.

required
providers Annotated[str, RoleSpec()]

The path of the group of banks.

required
rule MatchingRule | Pairing

The spec of the algorithm that matches firms with banks, such as PartnerSwitchSpec().

RandomSearchSpec()
limit str | tuple[str, str]

The state with the rate of both sides, or a pair of the firms' and the banks' states.

'limit'
quantity str | tuple[str, str]

The state with the loan of both sides, or a pair of the firms' and the banks' states.

'quantity'
allowance str

The state of a firm with what it can spend.

'allowance'
transfers Sequence[tuple[str, int]]

The states every loan moves, each with the sign -1 for a payment of the firm or 1 for a delivery to it.

()
phase str | None

The phase the market clears in, or None for the default phase.

None
on_match Callable[[MatchSpec, ModelT], None] | None

A function of every loan and the model view, run after the transfers, or None.

None

Examples:

>>> from skcomplex.models.economics.macroeconomics import CreditMarketInteraction
>>> CreditMarketInteraction('credit', requesters='firms', providers='banks').reports[-1]
'spread'
Source code in src/skcomplex/core/interactions/_matching.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def __init__(
    self,
    label: str,
    *,
    requesters: Annotated[str, RoleSpec()],
    providers: Annotated[str, RoleSpec()],
    rule: MatchingRule | Pairing = RandomSearchSpec(),
    limit: str | tuple[str, str] = 'limit',
    quantity: str | tuple[str, str] = 'quantity',
    allowance: str = 'allowance',
    transfers: Sequence[tuple[str, int]] = (),
    phase: str | None = None,
    on_match: Callable[[MatchSpec, ModelT], None] | None = None,
) -> None:
    super().__init__(label)
    self.requesters = requesters
    self.providers = providers
    self.rule = rule
    self.limit = limit
    self.quantity = quantity
    self.allowance = allowance
    self.transfers = transfers
    self.phase = phase
    self.on_match = on_match

DepositorElement(label, visual=None, *, bank=-1, deposit=10.0, loan=0.0, spending=0.1, repayment=0.05)

Bases: Element

Customer of a bank, with a deposit at the bank its reference state bank names and a loan from it.

The model pays a share of the deposit to a random customer, which moves deposits and as many reserves from the payer's bank to the payee's bank, and repays a share of the loan from the deposit, which cancels as much deposit and loan at the customer's bank and destroys broad money.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
bank Annotated[float, StateSpec(domain=ZZ, reference=banks)]

The id of the customer's bank in the group banks, given initially as its position in the group.

-1
deposit Annotated[float, StateSpec(domain=closed_open(0, oo))]

The deposit at the bank.

10.0
loan Annotated[float, StateSpec(domain=closed_open(0, oo))]

The loan owed to the bank.

0.0
spending Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of the deposit paid to a random customer every step.

0.1
repayment Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of the loan repaid every step.

0.05

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.macroeconomics import DepositorElement
>>> GroupElement('customers', element=DepositorElement, size=10, values={'deposit': 5.0}).size
10
Source code in src/skcomplex/models/economics/macroeconomics/_depositor.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    bank: Annotated[float, StateSpec(domain=ZZ, reference='banks')] = -1,
    deposit: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 10.0,
    loan: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    spending: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    repayment: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
) -> None:
    super().__init__(label, visual=visual)
    self.bank = bank
    self.deposit = deposit
    self.loan = loan
    self.spending = spending
    self.repayment = repayment

get_payment(payer, payee, model) staticmethod

Return the payment of a customer to another, a share spending of its deposit.

Parameters:

Name Type Description Default
payer DepositorView

The customer who pays.

required
payee DepositorView

The customer who is paid.

required
model ModelView

The model view of the payments.

required

Returns:

Name Type Description
amount float

The payment.

Source code in src/skcomplex/models/economics/macroeconomics/_depositor.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@staticmethod
def get_payment(payer: DepositorView, payee: DepositorView, model: ModelView) -> float:
    """Return the payment of a customer to another, a share `spending` of its deposit.

    Args:
        payer:
            The customer who pays.

        payee:
            The customer who is paid.

        model:
            The model view of the payments.

    Returns:
        amount:
            The payment.
    """
    return payer.spending * payer.deposit

get_repayment(customer, same, model) staticmethod

Return the repayment of a customer, a share repayment of its loan, at most its deposit.

Parameters:

Name Type Description Default
customer DepositorView

The customer who repays.

required
same DepositorView

The customer again, the target of a transfer within members.

required
model ModelView

The model view of the repayments.

required

Returns:

Name Type Description
amount float

The repayment.

Source code in src/skcomplex/models/economics/macroeconomics/_depositor.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@staticmethod
def get_repayment(customer: DepositorView, same: DepositorView, model: ModelView) -> float:
    """Return the repayment of a customer, a share `repayment` of its loan, at most its deposit.

    Args:
        customer:
            The customer who repays.

        same:
            The customer again, the target of a transfer within members.

        model:
            The model view of the repayments.

    Returns:
        amount:
            The repayment.
    """
    return min(customer.repayment * customer.loan, customer.deposit)

EconomyElement(label, visual=None, *, k=1.0)

Bases: Element

Economy described by its capital per worker.

Parameters:

Name Type Description Default
label str

The label of the economy.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
k Annotated[float, StateSpec(domain=closed_open(0, oo))]

The initial capital per worker.

1.0

Examples:

>>> from skcomplex.models.economics.macroeconomics import EconomyElement
>>> EconomyElement('country', k=1.0).k
1.0
Source code in src/skcomplex/models/economics/macroeconomics/_economy.py
34
35
36
37
38
39
40
41
42
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    k: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.k = k

EstateInteraction(label, *, people)

Bases: Interaction

Settlement of the estates of the dead, who leave their money and loans to an heir, with a census of the step.

The money and the loans of a dead person go to a random living person of its patch, or of anywhere. When nobody lives to inherit them, the dead person stays as an estate that holds them. The reporters births, starvations, and old_age count the events of a step.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
people Annotated[str, RoleSpec()]

The path of the group of people.

required

Examples:

>>> from skcomplex.models.economics.macroeconomics import EstateInteraction
>>> EstateInteraction('census', people='people').reports
('births', 'starvations', 'old_age')
Source code in src/skcomplex/models/economics/macroeconomics/_estate.py
42
43
44
45
46
47
48
49
def __init__(
    self,
    label: str,
    *,
    people: Annotated[str, RoleSpec()],
) -> None:
    super().__init__(label)
    self.people = people

count(model)

Count the births and deaths of the step and remove the dead.

Parameters:

Name Type Description Default
model ModelView

The model view of the interaction.

required
Source code in src/skcomplex/models/economics/macroeconomics/_estate.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def count(self, model: ModelView) -> None:
    """Count the births and deaths of the step and remove the dead.

    Args:
        model:
            The model view of the interaction.
    """
    people = model.agents('people', SettlerView)
    living = [person for person in people if person.fate == ALIVE]
    dead = [person for person in people if person.fate != ALIVE]
    model.report('births', float(sum(person.age == 0 for person in living)))
    model.report('starvations', float(sum(person.fate == STARVED for person in dead)))
    model.report('old_age', float(sum(person.fate == AGED for person in dead)))
    for person in dead:
        self._settle(person, living, model)

FirmElement(label, visual=None, *, net_worth=10.0, demand=0.0, bid=1.0, loan=0.0, rate=0.0, partner=-1, gross_return=0.0, injected=0.0, defaulted=0, leverage=1.0, mean_return=0.03, volatility=0.3, entry=10.0)

Bases: Element

Firm that borrows in proportion to its net worth, invests at a random return, and defaults on negative worth.

Every step the firm asks for leverage times its net worth from its bank, invests its net worth and the loan at the return mean_return + volatility * z, and pays the interest. When its net worth becomes negative it repays what it can, the bank writes off the rest, and a new firm with the net worth entry replaces it.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
net_worth Annotated[float, StateSpec(domain=closed_open(0, oo))]

The initial net worth.

10.0
demand Annotated[float, StateSpec(domain=closed_open(0, oo))]

The loan still wanted in the current step.

0.0
bid Annotated[float, StateSpec(domain=closed_open(0, oo))]

The highest interest rate the firm pays.

1.0
loan Annotated[float, StateSpec(domain=closed_open(0, oo))]

The loan of the current step.

0.0
rate Annotated[float, StateSpec(domain=closed_open(0, oo))]

The interest rate of the loan, zero without a loan.

0.0
partner Annotated[float, StateSpec(domain=ZZ, reference=banks)]

The id of the bank of the firm, -1 before its first loan.

-1
gross_return Annotated[float, StateSpec()]

The return on the capital of the last step.

0.0
injected Annotated[float, StateSpec(domain=closed_open(0, oo))]

The net worth of an entrant that replaced the firm in the last step, zero otherwise.

0.0
defaulted Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))]

Whether the firm defaulted in the last step.

0
leverage Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The ratio of the loan wanted to the net worth.

1.0
mean_return Annotated[float, ParameterSpec()]

The mean return on capital.

0.03
volatility Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The standard deviation of the return on capital.

0.3
entry Annotated[float, ParameterSpec(domain=open(0, oo))]

The net worth of an entrant.

10.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.macroeconomics import FirmElement
>>> GroupElement('firms', element=FirmElement, size=10, values={'net_worth': 5.0}).size
10
Source code in src/skcomplex/models/economics/macroeconomics/_firm.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    net_worth: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 10.0,
    demand: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    bid: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    loan: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    rate: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    partner: Annotated[float, StateSpec(domain=ZZ, reference='banks')] = -1,
    gross_return: Annotated[float, StateSpec()] = 0.0,
    injected: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    defaulted: Annotated[float, StateSpec(domain=FiniteEnumeratedSet([0, 1]))] = 0,
    leverage: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    mean_return: Annotated[float, ParameterSpec()] = 0.03,
    volatility: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
    entry: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
) -> None:
    super().__init__(label, visual=visual)
    self.net_worth = net_worth
    self.demand = demand
    self.bid = bid
    self.loan = loan
    self.rate = rate
    self.partner = partner
    self.gross_return = gross_return
    self.injected = injected
    self.defaulted = defaulted
    self.leverage = leverage
    self.mean_return = mean_return
    self.volatility = volatility
    self.entry = entry

get_default() staticmethod

Return the states a firm sets after its return, those of an entrant for a firm that defaults.

Returns:

Name Type Description
default tuple[tuple[str, Callable[[FirmView, ModelView], float]], ...]

The name of every state and the function of its value, of the firm and the model view.

Source code in src/skcomplex/models/economics/macroeconomics/_firm.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
@staticmethod
def get_default() -> tuple[tuple[str, Callable[[FirmView, ModelView], float]], ...]:
    """Return the states a firm sets after its return, those of an entrant for a firm that defaults.

    Returns:
        default:
            The name of every state and the function of its value, of the firm and the model view.
    """
    worth = FirmElement.get_worth
    return (
        ('injected', lambda agent, model: agent.entry if worth(agent) < 0 else agent.injected),
        ('defaulted', lambda agent, model: 1.0 if worth(agent) < 0 else agent.defaulted),
        ('partner', lambda agent, model: -1.0 if worth(agent) < 0 else agent.partner),
        ('net_worth', lambda agent, model: agent.entry if worth(agent) < 0 else worth(agent)),
    )

get_plan() staticmethod

Return the states a firm sets in the plan of a step, a loan request of leverage times its net worth.

Returns:

Name Type Description
plan tuple[tuple[str, Callable[[FirmView, ModelView], float]], ...]

The name of every state and the function of its value, of the firm and the model view.

Source code in src/skcomplex/models/economics/macroeconomics/_firm.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
@staticmethod
def get_plan() -> tuple[tuple[str, Callable[[FirmView, ModelView], float]], ...]:
    """Return the states a firm sets in the plan of a step, a loan request of `leverage` times its net worth.

    Returns:
        plan:
            The name of every state and the function of its value, of the firm and the model view.
    """
    return (
        ('demand', lambda agent, model: agent.leverage * agent.net_worth),
        ('loan', lambda agent, model: 0.0),
        ('rate', lambda agent, model: 0.0),
        ('defaulted', lambda agent, model: 0.0),
        ('injected', lambda agent, model: 0.0),
    )

get_return(agent, model) staticmethod

Return the return on the net worth and the loan invested, at a normal rate of return of at least -1.

Parameters:

Name Type Description Default
agent FirmView

The firm.

required
model ModelView

The model view of the investment.

required

Returns:

Name Type Description
gross_return float

The return of the step.

Source code in src/skcomplex/models/economics/macroeconomics/_firm.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@staticmethod
def get_return(agent: FirmView, model: ModelView) -> float:
    """Return the return on the net worth and the loan invested, at a normal rate of return of at least -1.

    Args:
        agent:
            The firm.

        model:
            The model view of the investment.

    Returns:
        gross_return:
            The return of the step.
    """
    draw = agent.mean_return + agent.volatility * model.rng.standard_normal()
    return (agent.net_worth + agent.loan) * max(draw, -1.0)

get_worth(agent) staticmethod

Return the net worth after the return and the interest, negative for a firm that defaults.

Parameters:

Name Type Description Default
agent FirmView

The firm.

required

Returns:

Name Type Description
worth float

The net worth before a default.

Source code in src/skcomplex/models/economics/macroeconomics/_firm.py
140
141
142
143
144
145
146
147
148
149
150
151
152
@staticmethod
def get_worth(agent: FirmView) -> float:
    """Return the net worth after the return and the interest, negative for a firm that defaults.

    Args:
        agent:
            The firm.

    Returns:
        worth:
            The net worth before a default.
    """
    return agent.net_worth + agent.gross_return - agent.loan * agent.rate

FirmsElement(label, visual=None, *, deposits=20.0, loans=50.0, sales=40.0, wage_share=0.7, distribution=0.3, repayment=0.08)

Bases: Element

Firms sector, with its deposits, its loans, and the sales of the last period, which pays wages and profits.

Parameters:

Name Type Description Default
label str

The label of the sector.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
deposits Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=money)]

The deposits of the firms, which are money.

20.0
loans Annotated[object, StateSpec(domain=closed_open(0, oo))]

The loans of the firms.

50.0
sales Annotated[object, StateSpec(domain=closed_open(0, oo))]

The sales of the last period.

40.0
wage_share Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of sales paid as wages.

0.7
distribution Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of the firms' deposits distributed as profits per period.

0.3
repayment Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of loans repaid per period.

0.08

Examples:

>>> from skcomplex.models.economics.macroeconomics import FirmsElement
>>> FirmsElement('firms').loans
50.0
Source code in src/skcomplex/models/economics/macroeconomics/_firms_element.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    deposits: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='money')] = 20.0,
    loans: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 50.0,
    sales: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 40.0,
    wage_share: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.7,
    distribution: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
    repayment: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.08,
) -> None:
    super().__init__(label, visual=visual)
    self.deposits = deposits
    self.loans = loans
    self.sales = sales
    self.wage_share = wage_share
    self.distribution = distribution
    self.repayment = repayment

FoodMarketInteraction(label, *, requesters, providers, rule=RandomSearchSpec(), limit='limit', quantity='quantity', allowance='allowance', transfers=(), phase=None, on_match=None)

Bases: MatchingInteraction[ModelView]

Food market in which people short of food, in random order, buy from the cheapest stocked shop within sight.

A matching of the people, who request their shortfall of food for reserve steps within the budget of their money, with the firms, which provide their stock at their price. Every purchase moves the money to the firm, which books it as revenue, and the food to the buyer. Build it with PartnerSpec(sight, 'score', score=FoodMarketInteraction.score, clearing='provider'), whose score prefers the lowest price. Besides the reporters of a matching, the reporter gdp sums the value of the sales of a step.

Examples:

>>> from skcomplex.specs import PartnerSpec
>>> from skcomplex.models.economics.macroeconomics import FoodMarketInteraction
>>> rule = PartnerSpec(5.0, 'score', score=FoodMarketInteraction.score, clearing='provider')
>>> FoodMarketInteraction('shops', requesters='people', providers='businesses', rule=rule).reports[-1]
'gdp'
Source code in src/skcomplex/core/interactions/_matching.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def __init__(
    self,
    label: str,
    *,
    requesters: Annotated[str, RoleSpec()],
    providers: Annotated[str, RoleSpec()],
    rule: MatchingRule | Pairing = RandomSearchSpec(),
    limit: str | tuple[str, str] = 'limit',
    quantity: str | tuple[str, str] = 'quantity',
    allowance: str = 'allowance',
    transfers: Sequence[tuple[str, int]] = (),
    phase: str | None = None,
    on_match: Callable[[MatchSpec, ModelT], None] | None = None,
) -> None:
    super().__init__(label)
    self.requesters = requesters
    self.providers = providers
    self.rule = rule
    self.limit = limit
    self.quantity = quantity
    self.allowance = allowance
    self.transfers = transfers
    self.phase = phase
    self.on_match = on_match

score(person, firm) staticmethod

Return the score of a firm for a person, higher for a lower price.

Parameters:

Name Type Description Default
person object

The person that buys.

required
firm object

The firm that sells.

required

Returns:

Name Type Description
score float

Minus the price of the firm.

Source code in src/skcomplex/models/economics/macroeconomics/_food_market.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@staticmethod
def score(person: object, firm: object) -> float:
    """Return the score of a firm for a person, higher for a lower price.

    Args:
        person:
            The person that buys.

        firm:
            The firm that sells.

    Returns:
        score:
            Minus the price of the firm.
    """
    del person
    return -float(firm.price) if isinstance(firm, BusinessView) else -math.inf

sell(match, model) staticmethod

Book the value of a sale as revenue of the firm.

Parameters:

Name Type Description Default
match MatchSpec

The match of a buyer with a firm.

required
model ModelView

The model view of the market.

required
Source code in src/skcomplex/models/economics/macroeconomics/_food_market.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@staticmethod
def sell(match: MatchSpec, model: ModelView) -> None:
    """Book the value of a sale as revenue of the firm.

    Args:
        match:
            The match of a buyer with a firm.

        model:
            The model view of the market.
    """
    del model
    if isinstance(match.provider, BusinessView):
        match.provider.revenue += match.quantity * match.value

HouseholdsElement(label, visual=None, *, deposits=60.0, alpha_income=0.6, alpha_wealth=0.4)

Bases: Element

Households sector, whose deposits are money, and which consumes out of its wages and its deposits.

Parameters:

Name Type Description Default
label str

The label of the sector.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
deposits Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=money)]

The deposits of the households.

60.0
alpha_income Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The propensity to consume out of wages.

0.6
alpha_wealth Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The propensity to consume out of deposits.

0.4

Examples:

>>> from skcomplex.models.economics.macroeconomics import HouseholdsElement
>>> HouseholdsElement('households').deposits
60.0
Source code in src/skcomplex/models/economics/macroeconomics/_households.py
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    deposits: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='money')] = 60.0,
    alpha_income: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.6,
    alpha_wealth: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.4,
) -> None:
    super().__init__(label, visual=visual)
    self.deposits = deposits
    self.alpha_income = alpha_income
    self.alpha_wealth = alpha_wealth

IncomeElement(label, visual=None, *, Y=110.0, Y_lag1=100.0, c=0.8, v=1.5, G=20.0)

Bases: Element

Economy described by its national income, which keeps the income of the previous period.

Parameters:

Name Type Description Default
label str

The label of the economy.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
Y Annotated[object, StateSpec(lags=1, initial=(100.0,))]

The income of the current period.

110.0
Y_lag1 float

The income of the previous period.

100.0
c Annotated[object, ParameterSpec()]

The marginal propensity to consume.

0.8
v Annotated[object, ParameterSpec()]

The accelerator, the investment response to the change in consumption.

1.5
G Annotated[object, ParameterSpec()]

The government spending.

20.0

Examples:

>>> from skcomplex.models.economics.macroeconomics import IncomeElement
>>> IncomeElement('economy', c=0.5).c
0.5
Source code in src/skcomplex/models/economics/macroeconomics/_income.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    Y: Annotated[object, StateSpec(lags=1, initial=(100.0,))] = 110.0,
    Y_lag1: float = 100.0,
    c: Annotated[object, ParameterSpec()] = 0.8,
    v: Annotated[object, ParameterSpec()] = 1.5,
    G: Annotated[object, ParameterSpec()] = 20.0,
) -> None:
    super().__init__(label, visual=visual)
    self.Y = Y
    self.Y_lag1 = Y_lag1
    self.c = c
    self.v = v
    self.G = G

InvestingFirmElement(label, visual=None, *, A=1.0, K=1.0, Z=0.0, B=0.0, Y=0.0, P=0.0, gamma=1.1, phi=0.1, r=0.1, pbar=0.01)

Bases: Element

Firm of the toy business-cycle model, which invests a share of its last profit and sells at a random price.

Every period the firm invests I = gamma * Z, accumulates capital K = K + I, produces Y = phi * K, borrows B = max(K - A, 0), sells at the price P = pbar + 2 U(0, 1), earns Z = P * Y - r * K, and adds the profit to its net worth A. A firm whose net worth turns negative is replaced by an entrant with A = K = 1 and Z = 0.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
A Annotated[float, StateSpec()]

The initial net worth.

1.0
K Annotated[float, StateSpec(domain=closed_open(0, oo))]

The initial capital.

1.0
Z Annotated[float, StateSpec()]

The initial profit.

0.0
B Annotated[float, StateSpec(domain=closed_open(0, oo))]

The initial debt.

0.0
Y Annotated[float, StateSpec(domain=closed_open(0, oo))]

The initial production.

0.0
P Annotated[float, StateSpec(domain=closed_open(0, oo))]

The initial price.

0.0
gamma Annotated[float, ParameterSpec(domain=open(0, oo))]

The investment accelerator.

1.1
phi Annotated[float, ParameterSpec(domain=open(0, oo))]

The productivity of capital.

0.1
r Annotated[float, ParameterSpec(domain=open(0, oo))]

The interest rate.

0.1
pbar Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The constant part of the price.

0.01

Examples:

>>> from skcomplex.models.economics.macroeconomics import InvestingFirmElement
>>> GroupElement('firms', element=InvestingFirmElement, size=10, values={'gamma': 1.2}).size
10
Source code in src/skcomplex/models/economics/macroeconomics/_firms.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    A: Annotated[float, StateSpec()] = 1.0,
    K: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    Z: Annotated[float, StateSpec()] = 0.0,
    B: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    Y: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    P: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    gamma: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.1,
    phi: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
    r: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
    pbar: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.01,
) -> None:
    super().__init__(label, visual=visual)
    self.A = A
    self.K = K
    self.Z = Z
    self.B = B
    self.Y = Y
    self.P = P
    self.gamma = gamma
    self.phi = phi
    self.r = r
    self.pbar = pbar

get_price(agent, model) staticmethod

Return a random price, pbar + 2 U(0, 1).

Parameters:

Name Type Description Default
agent InvestingFirmView

The firm.

required
model ModelView

The model view of the pricing.

required

Returns:

Name Type Description
price float

The price of the period.

Source code in src/skcomplex/models/economics/macroeconomics/_firms.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@staticmethod
def get_price(agent: InvestingFirmView, model: ModelView) -> float:
    """Return a random price, `pbar + 2 U(0, 1)`.

    Args:
        agent:
            The firm.

        model:
            The model view of the pricing.

    Returns:
        price:
            The price of the period.
    """
    return agent.pbar + 2.0 * float(model.rng.random())

get_steps() staticmethod

Return the steps of a period in their order, each a label, a state, and the function of its next value.

A firm invests, produces, borrows, sells, earns, adds the profit to its net worth, and, when the net worth turns negative, is replaced by an entrant, whose profit, capital, and net worth the last three steps reset.

Returns:

Name Type Description
steps tuple[tuple[str, str, Callable[[InvestingFirmView, ModelView], float]], ...]

The label of every step, the state it sets, and the function of its next value of the firm and the model view.

Source code in src/skcomplex/models/economics/macroeconomics/_firms.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@staticmethod
def get_steps() -> tuple[tuple[str, str, Callable[[InvestingFirmView, ModelView], float]], ...]:
    """Return the steps of a period in their order, each a label, a state, and the function of its next value.

    A firm invests, produces, borrows, sells, earns, adds the profit to its net worth, and, when the net worth
    turns negative, is replaced by an entrant, whose profit, capital, and net worth the last three steps reset.

    Returns:
        steps:
            The label of every step, the state it sets, and the function of its next value of the firm and the
            model view.
    """
    return (
        ('investment', 'K', lambda agent, model: agent.K + agent.gamma * agent.Z),
        ('production', 'Y', lambda agent, model: agent.phi * agent.K),
        ('borrowing', 'B', lambda agent, model: max(agent.K - agent.A, 0.0)),
        ('pricing', 'P', InvestingFirmElement.get_price),
        ('profit', 'Z', lambda agent, model: agent.P * agent.Y - agent.r * agent.K),
        ('accumulation', 'A', lambda agent, model: agent.A + agent.Z),
        ('entry_profit', 'Z', lambda agent, model: 0.0 if agent.A < 0 else agent.Z),
        ('entry_capital', 'K', lambda agent, model: 1.0 if agent.A < 0 else agent.K),
        ('entry_worth', 'A', lambda agent, model: 1.0 if agent.A < 0 else agent.A),
    )

LabourMarketInteraction(label, *, requesters, providers, rule=RandomSearchSpec(), limit='limit', quantity='quantity', allowance='allowance', transfers=(), phase=None, on_match=None)

Bases: MatchingInteraction[ModelView]

Labour market in which unemployed adults, in random order, take the best-paying vacancy within their sight.

A matching of the people, who request a job, with the firms, which provide their vacancies: a person whose hireable limit admits a job takes the firm of the highest wage within its sight that still has a vacancy, and moves to it. Build it with PartnerSpec(sight, 'score', score=LabourMarketInteraction.score, clearing='provider').

Examples:

>>> from skcomplex.specs import PartnerSpec
>>> from skcomplex.models.economics.macroeconomics import LabourMarketInteraction
>>> LabourMarketInteraction(
...     'jobs',
...     requesters='people',
...     providers='businesses',
...     rule=PartnerSpec(5.0, 'score', score=LabourMarketInteraction.score, clearing='provider'),
...     limit=('hireable', 'limit'),
...     quantity=('quantity', 'vacancies'),
...     on_match=LabourMarketInteraction.hire,
... ).check().providers
'businesses'
Source code in src/skcomplex/core/interactions/_matching.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def __init__(
    self,
    label: str,
    *,
    requesters: Annotated[str, RoleSpec()],
    providers: Annotated[str, RoleSpec()],
    rule: MatchingRule | Pairing = RandomSearchSpec(),
    limit: str | tuple[str, str] = 'limit',
    quantity: str | tuple[str, str] = 'quantity',
    allowance: str = 'allowance',
    transfers: Sequence[tuple[str, int]] = (),
    phase: str | None = None,
    on_match: Callable[[MatchSpec, ModelT], None] | None = None,
) -> None:
    super().__init__(label)
    self.requesters = requesters
    self.providers = providers
    self.rule = rule
    self.limit = limit
    self.quantity = quantity
    self.allowance = allowance
    self.transfers = transfers
    self.phase = phase
    self.on_match = on_match

hire(match, model) staticmethod

Employ a matched person at its firm and move it there.

Parameters:

Name Type Description Default
match MatchSpec

The match of a person with a firm.

required
model ModelView

The model view of the market.

required
Source code in src/skcomplex/models/economics/macroeconomics/_labour_market.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@staticmethod
def hire(match: MatchSpec, model: ModelView) -> None:
    """Employ a matched person at its firm and move it there.

    Args:
        match:
            The match of a person with a firm.

        model:
            The model view of the market.
    """
    del model
    person, firm = match.requester, match.provider
    if isinstance(person, SettlerView) and isinstance(firm, BusinessView):
        person.employer = float(firm.id)
        person.move_to(firm.location)

score(person, firm) staticmethod

Return the wage of a firm, the score by which a person prefers it.

Parameters:

Name Type Description Default
person object

The person who seeks a job.

required
firm object

The firm with a vacancy.

required

Returns:

Name Type Description
score float

The wage of the firm.

Source code in src/skcomplex/models/economics/macroeconomics/_labour_market.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@staticmethod
def score(person: object, firm: object) -> float:
    """Return the wage of a firm, the score by which a person prefers it.

    Args:
        person:
            The person who seeks a job.

        firm:
            The firm with a vacancy.

    Returns:
        score:
            The wage of the firm.
    """
    del person
    return float(firm.wage) if isinstance(firm, BusinessView) else -math.inf

LenderElement(label, visual=None, *, equity=100.0, ask=0.0, supply=0.0, lent=0.0, losses=0.0, alpha=0.1, capital_ratio=0.1)

Bases: Element

Bank that lends up to a multiple of its equity at a rate that falls with its equity.

The bank asks the rate alpha * equity ** -alpha of Delli Gatti et al. (2010), so better capitalised banks lend cheaper, and it offers loans up to equity / capital_ratio. The maps of its settlement read the firms bound to their role sources, whose interest and defaults reach the bank they name in their reference state partner.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
equity Annotated[float, StateSpec()]

The initial equity.

100.0
ask Annotated[float, StateSpec(domain=closed_open(0, oo))]

The interest rate the bank asks.

0.0
supply Annotated[float, StateSpec(domain=closed_open(0, oo))]

The loans the bank can still grant in the current step.

0.0
lent Annotated[float, StateSpec(domain=closed_open(0, oo))]

The loans granted in the current step.

0.0
losses Annotated[float, StateSpec(domain=closed_open(0, oo))]

The loans written off in the current step.

0.0
alpha Annotated[float, ParameterSpec(domain=open(0, oo))]

The elasticity of the interest rate to the equity.

0.1
capital_ratio Annotated[float, ParameterSpec(domain=open(0, oo))]

The smallest ratio of equity to loans.

0.1

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.macroeconomics import LenderElement
>>> GroupElement('banks', element=LenderElement, size=5, values={'equity': 100.0}).size
5
Source code in src/skcomplex/models/economics/macroeconomics/_lender.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    equity: Annotated[float, StateSpec()] = 100.0,
    ask: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    supply: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    lent: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    losses: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    alpha: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
    capital_ratio: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
) -> None:
    super().__init__(label, visual=visual)
    self.equity = equity
    self.ask = ask
    self.supply = supply
    self.lent = lent
    self.losses = losses
    self.alpha = alpha
    self.capital_ratio = capital_ratio

get_income(agent, model) staticmethod

Return the interest a bank earns in the step, less what its defaulting borrowers cannot repay.

Parameters:

Name Type Description Default
agent LenderView

The bank.

required
model ModelView

The model view of the settlement.

required

Returns:

Name Type Description
income float

The change of the equity.

Source code in src/skcomplex/models/economics/macroeconomics/_lender.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
@staticmethod
def get_income(agent: LenderView, model: ModelView) -> float:
    """Return the interest a bank earns in the step, less what its defaulting borrowers cannot repay.

    Args:
        agent:
            The bank.

        model:
            The model view of the settlement.

    Returns:
        income:
            The change of the equity.
    """
    borrowers = LenderElement._get_borrowers(agent, model)
    return sum(firm.loan * firm.rate + min(FirmElement.get_worth(firm), 0.0) for firm in borrowers)

get_losses(agent, model) staticmethod

Return the loans of a bank written off in the step.

Parameters:

Name Type Description Default
agent LenderView

The bank.

required
model ModelView

The model view of the settlement.

required

Returns:

Name Type Description
losses float

The change of the losses.

Source code in src/skcomplex/models/economics/macroeconomics/_lender.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
@staticmethod
def get_losses(agent: LenderView, model: ModelView) -> float:
    """Return the loans of a bank written off in the step.

    Args:
        agent:
            The bank.

        model:
            The model view of the settlement.

    Returns:
        losses:
            The change of the losses.
    """
    borrowers = LenderElement._get_borrowers(agent, model)
    return sum(max(-FirmElement.get_worth(firm), 0.0) for firm in borrowers)

get_quote() staticmethod

Return the states a bank sets in the plan of a step, its rate and lending capacity from its equity.

Returns:

Name Type Description
quote tuple[tuple[str, Callable[[LenderView, ModelView], float]], ...]

The name of every state and the function of its value, of the bank and the model view.

Source code in src/skcomplex/models/economics/macroeconomics/_lender.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
@staticmethod
def get_quote() -> tuple[tuple[str, Callable[[LenderView, ModelView], float]], ...]:
    """Return the states a bank sets in the plan of a step, its rate and lending capacity from its equity.

    Returns:
        quote:
            The name of every state and the function of its value, of the bank and the model view.
    """
    return (
        ('ask', lambda agent, model: agent.alpha * max(agent.equity, 1e-6) ** -agent.alpha),
        ('supply', lambda agent, model: max(agent.equity, 0.0) / agent.capital_ratio),
        ('lent', lambda agent, model: 0.0),
        ('losses', lambda agent, model: 0.0),
    )

MonetaryBankElement(label, visual=None, *, deposits=10.0, interest=0.03, payout=0.5, credit=0.1)

Bases: Element

Bank of a stock-flow consistent economy, whose own funds are money, and which lends and charges interest.

Parameters:

Name Type Description Default
label str

The label of the bank.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
deposits Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=money)]

The own funds of the bank.

10.0
interest Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The interest rate on loans per period.

0.03
payout Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of the bank's funds paid out as dividends per period.

0.5
credit Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The new loans per unit of sales.

0.1

Examples:

>>> from skcomplex.models.economics.macroeconomics import MonetaryBankElement
>>> MonetaryBankElement('bank').deposits
10.0
Source code in src/skcomplex/models/economics/macroeconomics/_monetary_bank.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    deposits: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='money')] = 10.0,
    interest: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.03,
    payout: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    credit: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
) -> None:
    super().__init__(label, visual=visual)
    self.deposits = deposits
    self.interest = interest
    self.payout = payout
    self.credit = credit

PlotElement(label, visual=None, *, food=0.0, capacity=10.0, resistance=0.0, regrowth=1.0)

Bases: Element

Patch of land whose food grows back by a share of its capacity every step, harvested against a resistance.

The regrowth is a RegrowthInteraction of the group of patches, relative to the capacity.

Parameters:

Name Type Description Default
label str

The label of the patch or the site group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
food Annotated[float, StateSpec(domain=closed_open(0, oo))]

The food on the patch.

0.0
capacity Annotated[float, StateSpec(domain=closed_open(0, oo))]

The most food the patch holds.

10.0
resistance Annotated[float, StateSpec(domain=closed_open(0, oo))]

The labour lost to the resistance of the patch before any food is harvested.

0.0
regrowth Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The share of the capacity that grows back in a step.

1.0

Examples:

>>> from skcomplex.models.economics.macroeconomics import PlotElement
>>> PlotElement('patch').regrowth
1.0
Source code in src/skcomplex/models/economics/macroeconomics/_plot.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    food: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    capacity: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 10.0,
    resistance: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    regrowth: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.food = food
    self.capacity = capacity
    self.resistance = resistance
    self.regrowth = regrowth

PolicySwitchInteraction(label, *, rule=None, phase=None, ratio=0.1, new_ratio=0.1, change_at=0.0, banks='banks')

Bases: FeedbackInteraction[ModelView]

Policy switch of a central bank, which moves the reserve ratio of the banks from ratio to new_ratio.

The ratio switches at the time change_at.

Parameters:

Name Type Description Default
label str

The label of the institution.

required
rule Callable[[ModelView], None] | None

Unused, the regulator overrides act.

None
phase str | None

The phase the regulator acts in, or None for the default phase.

None
ratio Annotated[float, ParameterSpec(domain=open(0, oo))]

The reserve ratio before the change.

0.1
new_ratio Annotated[float, ParameterSpec(domain=open(0, oo))]

The reserve ratio after the change.

0.1
change_at Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The time of the change.

0.0
banks str

The path of the group of banks.

'banks'

Examples:

>>> from skcomplex.models.economics.macroeconomics import PolicySwitchInteraction
>>> PolicySwitchInteraction('regulator', ratio=0.1, new_ratio=0.2, change_at=100.0).new_ratio
0.2
Source code in src/skcomplex/models/economics/macroeconomics/_policy_switch.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(
    self,
    label: str,
    *,
    rule: Callable[[ModelView], None] | None = None,
    phase: str | None = None,
    ratio: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
    new_ratio: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
    change_at: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    banks: str = 'banks',
) -> None:
    super().__init__(label, rule=rule, phase=phase)
    self.ratio = ratio
    self.new_ratio = new_ratio
    self.change_at = change_at
    self.banks = banks

act(model)

Set the reserve ratio of the banks from the schedule.

Parameters:

Name Type Description Default
model ModelView

The model view of the regulator.

required
Source code in src/skcomplex/models/economics/macroeconomics/_policy_switch.py
67
68
69
70
71
72
73
74
75
76
77
78
def act(self, model: ModelView) -> None:
    """Set the reserve ratio of the banks from the schedule.

    Args:
        model:
            The model view of the regulator.
    """
    before = model.time < model.parameter('change_at')
    ratio = model.parameter('ratio') if before else model.parameter('new_ratio')
    name = f'{self.banks}.reserve_ratio'
    if model.parameters[name] != ratio:
        model.set_parameter(name, ratio)

RamseyEconomyElement(label, visual=None, *, k=1.0, c=0.5, A=1.0, alpha=0.3, delta=0.05, rho=0.03, v=0.7)

Bases: Element

Economy that splits its output between consumption and capital, after Ramsey, Cass, and Koopmans.

Capital follows k' = A k^alpha - delta k - c, a rate interaction of the factory, under the consumption c, a bounded control input. The planner maximises the discounted utility ∫ exp(-rho t) log(c) dt plus the discounted value exp(-rho T) v k(T) of the capital left at the horizon T. Pontryagin's principle gives the Euler equation c' / c = alpha A k^(alpha - 1) - delta - rho, so capital approaches the modified golden rule alpha A k*^(alpha - 1) = delta + rho along the saddle path.

Parameters:

Name Type Description Default
label str

The label of the economy.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
k Annotated[float, StateSpec(domain=open(0, oo))]

The initial capital per worker.

1.0
c Annotated[float, ParameterSpec(adaptive=True, bounds=(MIN_CONSUMPTION, MAX_CONSUMPTION))]

The consumption per worker, the control input.

0.5
A Annotated[float, ParameterSpec(domain=open(0, oo))]

The total factor productivity.

1.0
alpha Annotated[float, ParameterSpec(domain=open(0, oo))]

The capital share of output.

0.3
delta Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The depreciation rate.

0.05
rho Annotated[float, ParameterSpec(domain=open(0, oo))]

The discount rate.

0.03
v Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The value per unit of capital left at the horizon.

0.7

Examples:

>>> from skcomplex.models.economics.macroeconomics import RamseyEconomyElement
>>> RamseyEconomyElement('economy', k=2.0).k
2.0
Source code in src/skcomplex/models/economics/macroeconomics/_optimal_growth.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    k: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 1.0,
    c: Annotated[float, ParameterSpec(adaptive=True, bounds=(MIN_CONSUMPTION, MAX_CONSUMPTION))] = 0.5,
    A: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    alpha: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.3,
    delta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
    rho: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.03,
    v: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.7,
) -> None:
    super().__init__(label, visual=visual)
    self.k = k
    self.c = c
    self.A = A
    self.alpha = alpha
    self.delta = delta
    self.rho = rho
    self.v = v

ReserveBankElement(label, visual=None, *, reserves=0.0, deposits=0.0, loans=0.0, reserve_ratio=0.1, customers='customers')

Bases: Element

Bank that lends its reserves in excess of the required share of its deposits to one of its customers.

The borrower spends the loan at once, and the payee, a random customer, deposits it at its own bank, whose reserves and deposits grow by the loan. The customers of a bank are those whose reference state bank names it.

Parameters:

Name Type Description Default
label str

The label of the agent or group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
reserves Annotated[float, StateSpec()]

The reserves of the bank.

0.0
deposits Annotated[float, StateSpec(domain=closed_open(0, oo))]

The deposits of its customers.

0.0
loans Annotated[float, StateSpec(domain=closed_open(0, oo))]

The loans to its customers.

0.0
reserve_ratio Annotated[float, ParameterSpec(domain=open(0, oo))]

The required ratio of reserves to deposits.

0.1
customers str

The path of the group of customers.

'customers'

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.macroeconomics import ReserveBankElement
>>> GroupElement('banks', element=ReserveBankElement, size=3, values={'reserves': 10.0}).size
3
Source code in src/skcomplex/models/economics/macroeconomics/_reserve_bank.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    reserves: Annotated[float, StateSpec()] = 0.0,
    deposits: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    loans: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    reserve_ratio: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.1,
    customers: str = 'customers',
) -> None:
    super().__init__(label, visual=visual)
    self.reserves = reserves
    self.deposits = deposits
    self.loans = loans
    self.reserve_ratio = reserve_ratio
    self.customers = customers

lend(agent, model)

Lend the excess reserves to a random customer, whose payment is redeposited at a random customer's bank.

Parameters:

Name Type Description Default
agent ReserveBankView

The bank.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/economics/macroeconomics/_reserve_bank.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def lend(self, agent: ReserveBankView, model: ModelView) -> None:
    """Lend the excess reserves to a random customer, whose payment is redeposited at a random customer's bank.

    Args:
        agent:
            The bank.

        model:
            The model.
    """
    excess = agent.reserves - model.parameter('reserve_ratio') * agent.deposits
    customers = model.agents(self.customers, DepositorView)
    own = [customer for customer in customers if customer.referenced('bank') is agent]
    if excess <= TOLERANCE or not own:
        return
    borrower = own[int(model.rng.integers(len(own)))]
    payee = customers[int(model.rng.integers(len(customers)))]
    bank = payee.referenced('bank')
    if not isinstance(bank, ReserveBankView):
        return
    borrower.loan += excess
    agent.loans += excess
    agent.reserves -= excess
    payee.deposit += excess
    bank.deposits += excess
    bank.reserves += excess

SettlerElement(label, visual=None, *, food=10.0, money=0.0, loans=0.0, age=20.0, parent=-1.0, employer=-1.0, harvested=0.0, born=-100.0, fate=ALIVE, fertility=0.5, exploration=0.5, enterprise=0.5, restlessness=0.1, labour=5.0, need=2.0, maturity=15.0, lifespan=85.0, spacing=2.0, endowment=5.0, mutation=0.05, sight=5.0, reserve=5.0, monetary=0.0)

Bases: Element

Mortal person who eats, ages, raises children, moves, and, in the monetary economy, works and founds firms.

Every step a person eats need food and starves when its food runs out, and past lifespan it dies of old age with probability 1/7. A child takes need food from its parent and follows it. A fed adult has a child with the probability fertility, at most once every spacing steps, and gives it endowment food. The child inherits the four propensities, each moved by a normal mutation and kept in [0, 1]. A person that harvested too little and holds less than reserve steps of food moves, far with the probability exploration. In the monetary economy an unemployed adult on a patch with food but no firm founds one with the probability enterprise, and a worker quits with the probability restlessness.

Parameters:

Name Type Description Default
label str

The label of the person or the group.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
food Annotated[float, StateSpec()]

The food the person holds.

10.0
money Annotated[float, StateSpec(quantity=money)]

The money the person holds.

0.0
loans Annotated[float, StateSpec(quantity=credit)]

The loans the person holds, negative for a debt taken over from a firm.

0.0
age Annotated[float, StateSpec(domain=NN)]

The age in steps.

20.0
parent Annotated[float, StateSpec(reference=people)]

The id of the parent, or -1.

-1.0
employer Annotated[float, StateSpec(reference=businesses)]

The id of the firm that employs the person, or -1.

-1.0
harvested Annotated[float, StateSpec()]

The food the person harvested or earned in the step.

0.0
born Annotated[float, StateSpec()]

The step of the person's last child.

-100.0
fate Annotated[float, StateSpec()]

0 while alive, 1 after starving, and 2 after dying of old age, until the census removes the person.

ALIVE
fertility Annotated[float, StateSpec(domain=closed_open(0, oo))]

The probability of a child in a step.

0.5
exploration Annotated[float, StateSpec(domain=closed_open(0, oo))]

The probability of moving far.

0.5
enterprise Annotated[float, StateSpec(domain=closed_open(0, oo))]

The probability of founding a firm.

0.5
restlessness Annotated[float, StateSpec(domain=closed_open(0, oo))]

The probability of quitting a job.

0.1
labour Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The labour of an adult in a step.

5.0
need Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The food a person eats in a step.

2.0
maturity Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The age of adulthood.

15.0
lifespan Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The age after which death by old age is possible.

85.0
spacing Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The fewest steps between two children.

2.0
endowment Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The food a parent gives a newborn child.

5.0
mutation Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The standard deviation of the mutation of a propensity.

0.05
sight Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The distance within which a person sees firms and explores.

5.0
reserve Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The steps of food a person buys up to, and below which a person that harvests too little moves.

5.0
monetary Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

One in the monetary economy, zero among hunter-gatherers.

0.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.macroeconomics import SettlerElement
>>> GroupElement('people', element=SettlerElement, size=3, variable=True).size
3
Source code in src/skcomplex/models/economics/macroeconomics/_settler.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    food: Annotated[float, StateSpec()] = 10.0,
    money: Annotated[float, StateSpec(quantity='money')] = 0.0,
    loans: Annotated[float, StateSpec(quantity='credit')] = 0.0,
    age: Annotated[float, StateSpec(domain=NN)] = 20.0,
    parent: Annotated[float, StateSpec(reference='people')] = -1.0,
    employer: Annotated[float, StateSpec(reference='businesses')] = -1.0,
    harvested: Annotated[float, StateSpec()] = 0.0,
    born: Annotated[float, StateSpec()] = -100.0,
    fate: Annotated[float, StateSpec()] = ALIVE,
    fertility: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    exploration: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    enterprise: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    restlessness: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    labour: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
    need: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
    maturity: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 15.0,
    lifespan: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 85.0,
    spacing: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
    endowment: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
    mutation: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
    sight: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
    reserve: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
    monetary: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.food = food
    self.money = money
    self.loans = loans
    self.age = age
    self.parent = parent
    self.employer = employer
    self.harvested = harvested
    self.born = born
    self.fate = fate
    self.fertility = fertility
    self.exploration = exploration
    self.enterprise = enterprise
    self.restlessness = restlessness
    self.labour = labour
    self.need = need
    self.maturity = maturity
    self.lifespan = lifespan
    self.spacing = spacing
    self.endowment = endowment
    self.mutation = mutation
    self.sight = sight
    self.reserve = reserve
    self.monetary = monetary

live(agent, model)

Age, eat, die, have children, and move or work.

Parameters:

Name Type Description Default
agent SettlerView

The person.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/economics/macroeconomics/_settler.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def live(self, agent: SettlerView, model: ModelView) -> None:
    """Age, eat, die, have children, and move or work.

    Args:
        agent:
            The person.

        model:
            The model.
    """
    agent.age += 1
    adult = agent.age >= agent.maturity
    if not adult:
        self._feed_child(agent, model)
    agent.food -= agent.need
    if agent.food < 0:
        agent.fate = STARVED
        return
    if agent.age > agent.lifespan and model.rng.random() < OLD_AGE_DEATH:
        agent.fate = AGED
        return
    if not adult:
        return
    self._breed(agent, model)
    employed = agent.monetary > 0 and self._work_or_found(agent, model)
    if not employed and agent.harvested < agent.need and agent.food < agent.reserve * agent.need:
        SettlerElement._wander(agent, model, agent.sight)
    agent.harvested = 0.0

SolowGrowthInteraction(label, *, target, s=0.2, alpha=0.3, delta=0.05)

Bases: RateInteraction

Solow growth of the capital per worker k of a target, dk/dt = s * k**alpha - delta * k.

A rate interaction on the state k whose rate is the saving of a Cobb-Douglas output minus depreciation.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the economy.

required
s Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The saving rate.

0.2
alpha Annotated[float, ParameterSpec(domain=open(0, oo))]

The output elasticity of capital.

0.3
delta Annotated[float, ParameterSpec(domain=open(0, oo))]

The depreciation rate.

0.05

Examples:

>>> from skcomplex.models.economics.macroeconomics import SolowGrowthInteraction
>>> SolowGrowthInteraction('solow', target='country', s=0.3).s
0.3
Source code in src/skcomplex/models/economics/macroeconomics/_solow_growth.py
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    s: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
    alpha: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.3,
    delta: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.05,
) -> None:
    super().__init__(label, target=target, state='k')
    self.s = s
    self.alpha = alpha
    self.delta = delta

TradingEconomyElement(label, visual=None, *, E=0.885, X=32.0, M=34.0, PH=1.2, home_price=1.2, inflation=0.1, world_price=1.0, exports=32.0, imports=34.0, export_price=1.062, import_price=1.13, export_elasticity=-1.0, import_elasticity=-1.0, floating=1.0, import_share=0.34, demand=52.0, government=50.0)

Bases: Element

Open economy that trades with the World at an exchange rate, floating or fixed.

Its states are the exchange rate E in World currency per unit of home currency, the export and import volumes X and M, and the home price PH; its parameters are the prices, volumes, and elasticities of the balanced start, the rise inflation of home prices, whether the rate floats, and the shares of the output.

Parameters:

Name Type Description Default
label str

The label of the economy.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
E Annotated[object, StateSpec(domain=open(0, oo))]

The exchange rate.

0.885
X Annotated[object, StateSpec(domain=closed_open(0, oo))]

The export volume.

32.0
M Annotated[object, StateSpec(domain=closed_open(0, oo))]

The import volume.

34.0
PH Annotated[object, StateSpec(domain=open(0, oo))]

The home price of home-produced goods.

1.2
home_price Annotated[object, ParameterSpec(domain=open(0, oo))]

The home price before the rise.

1.2
inflation Annotated[object, ParameterSpec()]

The relative rise of the home prices.

0.1
world_price Annotated[object, ParameterSpec(domain=open(0, oo))]

The world price level.

1.0
exports Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The export volume of the balanced start.

32.0
imports Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The import volume of the balanced start.

34.0
export_price Annotated[object, ParameterSpec(domain=open(0, oo))]

The World price of the exports at the start.

1.062
import_price Annotated[object, ParameterSpec(domain=open(0, oo))]

The home price of the imports at the start.

1.13
export_elasticity Annotated[object, ParameterSpec()]

The price elasticity of the exports.

-1.0
import_elasticity Annotated[object, ParameterSpec()]

The price elasticity of the imports.

-1.0
floating Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

1 when the exchange rate floats, 0 when it is fixed.

1.0
import_share Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The share of the imports in the output.

0.34
demand Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The private domestic demand.

52.0
government Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The government spending.

50.0

Examples:

>>> from skcomplex.models.economics.macroeconomics import TradingEconomyElement
>>> TradingEconomyElement('home').floating
1.0
Source code in src/skcomplex/models/economics/macroeconomics/_trade.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    E: Annotated[object, StateSpec(domain=RealSet.open(0, oo))] = 0.885,
    X: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 32.0,
    M: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 34.0,
    PH: Annotated[object, StateSpec(domain=RealSet.open(0, oo))] = 1.2,
    home_price: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.2,
    inflation: Annotated[object, ParameterSpec()] = 0.1,
    world_price: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    exports: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 32.0,
    imports: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 34.0,
    export_price: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.062,
    import_price: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.13,
    export_elasticity: Annotated[object, ParameterSpec()] = -1.0,
    import_elasticity: Annotated[object, ParameterSpec()] = -1.0,
    floating: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    import_share: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.34,
    demand: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 52.0,
    government: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 50.0,
) -> None:
    super().__init__(label, visual=visual)
    self.E = E
    self.X = X
    self.M = M
    self.PH = PH
    self.home_price = home_price
    self.inflation = inflation
    self.world_price = world_price
    self.exports = exports
    self.imports = imports
    self.export_price = export_price
    self.import_price = import_price
    self.export_elasticity = export_elasticity
    self.import_elasticity = import_elasticity
    self.floating = floating
    self.import_share = import_share
    self.demand = demand
    self.government = government

capital_stock(capital=100.0, *, investment_fraction=0.2, output_per_capital=1 / 3, lifetime=15.0)

Return Meadows' capital stock, grown by reinvested output and drained by depreciation.

The stock follows dK/dt = investment_fraction * output_per_capital * K - K / lifetime. It grows exponentially when the capital lives longer than 1 / (investment_fraction * output_per_capital), declines when it lives shorter, and stays in dynamic equilibrium otherwise.

Parameters:

Name Type Description Default
capital float

The initial capital stock.

100.0
investment_fraction float

The fraction of the output that is reinvested.

0.2
output_per_capital float

The annual output per unit of capital.

1 / 3
lifetime float

The average lifetime of the capital.

15.0

Returns:

Name Type Description
system System

The model, with the capital stock economy.K.

Examples:

>>> from skcomplex.models.economics.macroeconomics import capital_stock
>>> capital_stock(lifetime=20.0).elements[0].lifetime
20.0
Source code in src/skcomplex/models/economics/macroeconomics/_capital.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@register_model(
    field='economics/macroeconomics',
    tags=('growth', 'system dynamics', 'ode'),
    references=('Meadows 2008',),
    defaults={'until': 50.0, 'dt': 1.0},
)
def capital_stock(
    capital: float = 100.0,
    *,
    investment_fraction: float = 0.2,
    output_per_capital: float = 1 / 3,
    lifetime: float = 15.0,
) -> System:
    """Return Meadows' capital stock, grown by reinvested output and drained by depreciation.

    The stock follows `dK/dt = investment_fraction * output_per_capital * K - K / lifetime`. It grows exponentially when
    the capital lives longer than `1 / (investment_fraction * output_per_capital)`, declines when it lives shorter, and
    stays in dynamic equilibrium otherwise.

    Args:
        capital:
            The initial capital stock.

        investment_fraction:
            The fraction of the output that is reinvested.

        output_per_capital:
            The annual output per unit of capital.

        lifetime:
            The average lifetime of the capital.

    Returns:
        system:
            The model, with the capital stock `economy.K`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import capital_stock
        >>> capital_stock(lifetime=20.0).elements[0].lifetime
        20.0
    """
    economy = CapitalStockElement(
        'economy',
        K=capital,
        investment_fraction=investment_fraction,
        output_per_capital=output_per_capital,
        lifetime=lifetime,
    )
    return System(
        elements=[economy],
        interactions=[RateInteraction('investment', target='economy', state='K', rate=_net_investment)],
    )

credit_networks(firms=100, banks=10, *, sample=3, intensity=4.0, leverage=1.0, mean_return=0.03, volatility=0.3, net_worth=10.0, equity=100.0, alpha=0.1, capital_ratio=0.1, random_state=0)

Return a credit market where firms sample banks and switch to cheaper lenders, and defaults hit the lenders.

Every step the banks quote rates that fall with their equity, every firm samples sample banks and switches from its lender to the cheapest of them with probability 1 - exp(intensity * (r_new - r_old) / r_new), borrows, and invests at a random return. Interest and write-offs of defaulted firms move net worth between firms and banks, so the total net worth changes only by the firms' returns and the entrants' net worth. A higher intensity of switching concentrates the loans in the cheapest banks and narrows the spread of the rates that firms pay.

Parameters:

Name Type Description Default
firms int

The number of firms.

100
banks int

The number of banks.

10
sample int

The number of banks every firm samples.

3
intensity float

The intensity of switching to a cheaper bank.

4.0
leverage float

The ratio of the loan a firm wants to its net worth.

1.0
mean_return float

The mean return on capital.

0.03
volatility float

The standard deviation of the return on capital.

0.3
net_worth float

The initial net worth of every firm and of every entrant.

10.0
equity float

The mean initial equity of the banks, drawn uniformly within 50% of it.

100.0
alpha float

The elasticity of the interest rate to the equity.

0.1
capital_ratio float

The smallest ratio of a bank's equity to its loans.

0.1
random_state int | Generator | None

The seed or generator of the initial equities.

0

Returns:

Name Type Description
system System

The model, with the groups firms and banks, the market credit with its reporter credit.spread, and the observables herfindahl, largest_share, bank_equity, firm_worth, returns, injected, defaults, and bad_debt.

Examples:

>>> from skcomplex.models.economics.macroeconomics import credit_networks
>>> [element.label for element in credit_networks().elements]
['firms', 'banks']
Source code in src/skcomplex/models/economics/macroeconomics/_credit.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
@register_model(
    field='economics/macroeconomics',
    tags=('credit', 'banks', 'agents', 'market', 'network'),
    references=('Caiani et al. 2016 ch. 2', 'Delli Gatti et al. 2010'),
    defaults={'until': 100},
    observables=_credit_observables,
)
def credit_networks(
    firms: int = 100,
    banks: int = 10,
    *,
    sample: int = 3,
    intensity: float = 4.0,
    leverage: float = 1.0,
    mean_return: float = 0.03,
    volatility: float = 0.3,
    net_worth: float = 10.0,
    equity: float = 100.0,
    alpha: float = 0.1,
    capital_ratio: float = 0.1,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return a credit market where firms sample banks and switch to cheaper lenders, and defaults hit the lenders.

    Every step the banks quote rates that fall with their equity, every firm samples `sample` banks and switches from
    its lender to the cheapest of them with probability `1 - exp(intensity * (r_new - r_old) / r_new)`, borrows, and
    invests at a random return. Interest and write-offs of defaulted firms move net worth between firms and banks, so
    the total net worth changes only by the firms' returns and the entrants' net worth. A higher intensity of switching
    concentrates the loans in the cheapest banks and narrows the spread of the rates that firms pay.

    Args:
        firms:
            The number of firms.

        banks:
            The number of banks.

        sample:
            The number of banks every firm samples.

        intensity:
            The intensity of switching to a cheaper bank.

        leverage:
            The ratio of the loan a firm wants to its net worth.

        mean_return:
            The mean return on capital.

        volatility:
            The standard deviation of the return on capital.

        net_worth:
            The initial net worth of every firm and of every entrant.

        equity:
            The mean initial equity of the banks, drawn uniformly within 50% of it.

        alpha:
            The elasticity of the interest rate to the equity.

        capital_ratio:
            The smallest ratio of a bank's equity to its loans.

        random_state:
            The seed or generator of the initial equities.

    Returns:
        system:
            The model, with the groups `firms` and `banks`, the market `credit` with its reporter `credit.spread`, and
            the observables `herfindahl`, `largest_share`, `bank_equity`, `firm_worth`, `returns`, `injected`,
            `defaults`, and `bad_debt`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import credit_networks
        >>> [element.label for element in credit_networks().elements]
        ['firms', 'banks']
    """
    rng = np.random.default_rng(random_state)
    lenders = GroupElement(
        'banks',
        element=LenderElement,
        size=banks,
        values={'equity': equity * rng.uniform(0.5, 1.5, banks), 'alpha': alpha, 'capital_ratio': capital_ratio},
    )
    borrowers = GroupElement(
        'firms',
        element=FirmElement,
        size=firms,
        values={
            'net_worth': net_worth,
            'leverage': leverage,
            'mean_return': mean_return,
            'volatility': volatility,
            'entry': net_worth,
        },
    )
    market = CreditMarketInteraction(
        'credit',
        requesters='firms',
        providers='banks',
        rule=PartnerSwitchSpec(sample=sample, intensity=intensity),
        limit=('bid', 'ask'),
        quantity=('demand', 'supply'),
        phase='credit',
        on_match=_lend,
    )
    plan = [
        MapInteraction(f'{group}_plan_{name}', target=group, state=name, function=function, form='agent', phase='plan')
        for group, steps in (('firms', FirmElement.get_plan()), ('banks', LenderElement.get_quote()))
        for name, function in steps
    ]
    settlement = [
        MapInteraction(
            'returns',
            target='firms',
            state='gross_return',
            function=FirmElement.get_return,
            form='agent',
            phase='settle',
        ),
        *(
            MapInteraction(
                f'bank_{name}',
                target='banks',
                state=name,
                function=function,
                sources=('firms',),
                mode='increment',
                form='agent',
                phase='settle',
            )
            for name, function in (('equity', LenderElement.get_income), ('losses', LenderElement.get_losses))
        ),
        *(
            MapInteraction(
                f'firms_settle_{name}',
                target='firms',
                state=name,
                function=function,
                form='agent',
                phase='settle',
            )
            for name, function in FirmElement.get_default()
        ),
    ]
    return System(
        elements=[borrowers, lenders],
        interactions=[*plan, market, *settlement],
        time=DiscreteTime(schedule=PhasesScheduleSpec([PhaseSpec('plan'), PhaseSpec('credit'), PhaseSpec('settle')])),
    )

exchange_rates(exports=32.0, imports=34.0, *, home_price=1.2, inflation=0.1, export_elasticity=-1.0, import_elasticity=-1.0, floating=True, demand=52.0, government=50.0, world_price=1.0)

Return Hamill and Gilbert's staged adjustment of trade and the exchange rate to a rise in home prices.

The economy starts in balance at the exchange rate E = M * PW / (X * PH), in units of World currency per unit of Home currency, with the export and import volumes and prices of the United Kingdom in 1999 by default. Home prices rise by inflation at the first stage. At every stage a floating rate takes the value that balances the currency market at the current volumes, a fixed rate stays, and the volumes respond to the prices of the stage before with constant price elasticities. The book follows two stages, the default.

Parameters:

Name Type Description Default
exports float

The initial export volume.

32.0
imports float

The initial import volume.

34.0
home_price float

The home price of home-produced goods before the rise.

1.2
inflation float

The relative rise of the home prices.

0.1
export_elasticity float

The price elasticity of the exports.

-1.0
import_elasticity float

The price elasticity of the imports.

-1.0
floating bool

Whether the exchange rate floats, or is fixed as in the Eurozone.

True
demand float

The private domestic demand, consumption and investment.

52.0
government float

The government spending.

50.0
world_price float

The world price level.

1.0

Returns:

Name Type Description
system System

The model, with the economy home, the balance of trade at home prices balance, the real exchange rate real_rate, and the output output.

Examples:

>>> from skcomplex.models.economics.macroeconomics import exchange_rates
>>> round(exchange_rates().elements[0].E, 3)
0.885
Source code in src/skcomplex/models/economics/macroeconomics/_trade.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
@register_model(
    field='economics/macroeconomics',
    tags=('international trade', 'exchange rate', 'discrete map'),
    references=('Hamill and Gilbert 2016',),
    defaults={'until': 2, 'dt': 1},
    observables=_trade_observables,
)
def exchange_rates(
    exports: float = 32.0,
    imports: float = 34.0,
    *,
    home_price: float = 1.2,
    inflation: float = 0.1,
    export_elasticity: float = -1.0,
    import_elasticity: float = -1.0,
    floating: bool = True,
    demand: float = 52.0,
    government: float = 50.0,
    world_price: float = 1.0,
) -> System:
    """Return Hamill and Gilbert's staged adjustment of trade and the exchange rate to a rise in home prices.

    The economy starts in balance at the exchange rate `E = M * PW / (X * PH)`, in units of World currency per unit of
    Home currency, with the export and import volumes and prices of the United Kingdom in 1999 by default. Home prices
    rise by `inflation` at the first stage. At every stage a floating rate takes the value that balances the currency
    market at the current volumes, a fixed rate stays, and the volumes respond to the prices of the stage before with
    constant price elasticities. The book follows two stages, the default.

    Args:
        exports:
            The initial export volume.

        imports:
            The initial import volume.

        home_price:
            The home price of home-produced goods before the rise.

        inflation:
            The relative rise of the home prices.

        export_elasticity:
            The price elasticity of the exports.

        import_elasticity:
            The price elasticity of the imports.

        floating:
            Whether the exchange rate floats, or is fixed as in the Eurozone.

        demand:
            The private domestic demand, consumption and investment.

        government:
            The government spending.

        world_price:
            The world price level.

    Returns:
        system:
            The model, with the economy `home`, the balance of trade at home prices `balance`, the real exchange rate
            `real_rate`, and the output `output`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import exchange_rates
        >>> round(exchange_rates().elements[0].E, 3)
        0.885
    """
    rate = imports * world_price / (exports * home_price)
    home = TradingEconomyElement(
        'home',
        E=rate,
        X=exports,
        M=imports,
        PH=home_price,
        home_price=home_price,
        inflation=inflation,
        world_price=world_price,
        exports=exports,
        imports=imports,
        export_price=home_price * rate,
        import_price=world_price / rate,
        export_elasticity=export_elasticity,
        import_elasticity=import_elasticity,
        floating=float(floating),
        import_share=imports / (demand + government + exports - imports),
        demand=demand,
        government=government,
    )
    return System(elements=[home], interactions=_adjustments())

fractional_reserve_banking(customers=100, banks=5, *, base_money=1000.0, ratio=0.1, new_ratio=None, change_at=150.0, spending=0.1, repayment=0.05, random_state=0)

Return customers and banks whose lending of excess reserves multiplies the base money by 1 / ratio.

The base money starts as equal deposits of the customers, held as reserves by their banks, picked at random. Every step the customers pay a share of their deposits to random customers and repay a share of their loans from their deposits, and every bank lends its reserves above the required share of its deposits; the loans are spent and redeposited at random banks. The reserves stay equal to the base money, so the broad money, the sum of deposits, settles near base_money / ratio. A regulator changes the reserve ratio to new_ratio at change_at, and the broad money moves to the new multiplier.

Parameters:

Name Type Description Default
customers int

The number of customers.

100
banks int

The number of banks.

5
base_money float

The base money, deposited by the customers in equal shares.

1000.0
ratio float

The reserve ratio.

0.1
new_ratio float | None

The reserve ratio from change_at on, or None to keep ratio.

None
change_at float

The time the reserve ratio changes.

150.0
spending float

The share of its deposit a customer pays to a random customer every step.

0.1
repayment float

The share of its loan a customer repays every step.

0.05
random_state int | Generator | None

The seed or generator of the customers' banks.

0

Returns:

Name Type Description
system System

The model, with the groups customers and banks, the institution regulator, and the observables broad_money, reserves, and loans.

Examples:

>>> from skcomplex.models.economics.macroeconomics import fractional_reserve_banking
>>> fractional_reserve_banking(new_ratio=0.2).interactions[0].new_ratio
0.2
Source code in src/skcomplex/models/economics/macroeconomics/_banking.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
@register_model(
    field='economics/macroeconomics',
    tags=('money', 'banks', 'agents', 'institution'),
    references=('Hamill and Gilbert 2016 ch. 9',),
    defaults={'until': 300},
    observables=lambda system: [
        ObservableSpec('broad_money', group='banks', variable='deposits', reduce='sum'),
        ObservableSpec('reserves', group='banks', variable='reserves', reduce='sum'),
        ObservableSpec('loans', group='banks', variable='loans', reduce='sum'),
    ],
)
def fractional_reserve_banking(
    customers: int = 100,
    banks: int = 5,
    *,
    base_money: float = 1000.0,
    ratio: float = 0.1,
    new_ratio: float | None = None,
    change_at: float = 150.0,
    spending: float = 0.1,
    repayment: float = 0.05,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return customers and banks whose lending of excess reserves multiplies the base money by `1 / ratio`.

    The base money starts as equal deposits of the customers, held as reserves by their banks, picked at random. Every
    step the customers pay a share of their deposits to random customers and repay a share of their loans from their
    deposits, and every bank lends its reserves above the
    required share of its deposits; the loans are spent and redeposited at random banks. The reserves stay equal to the
    base money, so the broad money, the sum of deposits, settles near `base_money / ratio`. A regulator changes the
    reserve ratio to `new_ratio` at `change_at`, and the broad money moves to the new multiplier.

    Args:
        customers:
            The number of customers.

        banks:
            The number of banks.

        base_money:
            The base money, deposited by the customers in equal shares.

        ratio:
            The reserve ratio.

        new_ratio:
            The reserve ratio from `change_at` on, or `None` to keep `ratio`.

        change_at:
            The time the reserve ratio changes.

        spending:
            The share of its deposit a customer pays to a random customer every step.

        repayment:
            The share of its loan a customer repays every step.

        random_state:
            The seed or generator of the customers' banks.

    Returns:
        system:
            The model, with the groups `customers` and `banks`, the institution `regulator`, and the observables
            `broad_money`, `reserves`, and `loans`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import fractional_reserve_banking
        >>> fractional_reserve_banking(new_ratio=0.2).interactions[0].new_ratio
        0.2
    """
    rng = np.random.default_rng(random_state)
    owners = rng.integers(banks, size=customers)
    deposit = base_money / customers
    holdings = np.bincount(owners, minlength=banks) * deposit
    depositors = GroupElement(
        'customers',
        element=DepositorElement,
        size=customers,
        values={'bank': owners, 'deposit': deposit, 'spending': spending, 'repayment': repayment},
    )
    payments = TransferInteraction(
        'payments',
        source='customers',
        target='customers',
        state='deposit',
        amount=DepositorElement.get_payment,
        pairing='random',
        legs=(
            ('bank.deposits', None, DepositorElement.get_payment),
            ('bank.reserves', None, DepositorElement.get_payment),
        ),
        form='agent',
        phase='pay',
    )
    repayments = TransferInteraction(
        'repayments',
        source='customers',
        target='customers',
        state='deposit',
        into=Sink(),
        amount=DepositorElement.get_repayment,
        legs=tuple(
            (state, Sink(), DepositorElement.get_repayment) for state in ('loan', 'bank.deposits', 'bank.loans')
        ),
        guard=lambda customer, same, model: DepositorElement.get_repayment(customer, same, model) > 0,
        form='agent',
        phase='pay',
    )
    lenders = GroupElement(
        'banks',
        element=ReserveBankElement,
        size=banks,
        values={'reserves': holdings, 'deposits': holdings, 'reserve_ratio': ratio},
    )
    regulator = PolicySwitchInteraction(
        'regulator',
        phase='regulate',
        ratio=ratio,
        new_ratio=ratio if new_ratio is None else new_ratio,
        change_at=change_at,
    )
    return System(
        elements=[depositors, lenders],
        interactions=[regulator, payments, repayments],
        time=DiscreteTime(
            schedule=PhasesScheduleSpec([PhaseSpec('regulate'), PhaseSpec('pay'), PhaseSpec('lend', repeat=10)]),
        ),
    )

metaphoria(n_people=100, *, mode='hunter_gatherer', shape=(20, 20), max_food=15.0, max_resistance=5.0, regrowth=1.0, need=2.0, labour=5.0, interest=0.003, repayment=0.004, n_firms=5, random_state=0)

Return Gooding's Metaphoria, a mortal population of hunter-gatherers or of a monetary economy.

Patches of a periodic lattice hold food that grows back to a random capacity and resist harvesting with a random resistance. People need food every step, age, die of starvation or old age, and have children who inherit their propensities to breed, explore, found firms, and quit jobs, with mutations, so the propensities evolve under the test of survival. Hunter-gatherers pool the labour of the adults on a patch and share the harvest. In the monetary economy firms harvest with paid workers, sell the food in shops, and borrow the wages from a bank that creates money as loans, so the money of all holders sums to zero at every step. Gooding found the hunter-gatherers stable, and the monetary population smaller and less healthy, with money concentrating to a Gini coefficient near 0.5. On land where the hunter-gatherers thrive the monetary population dwindles towards extinction, and with abundant food it lives on with persistent starvation.

Parameters:

Name Type Description Default
n_people int

The initial number of adults.

100
mode str

'hunter_gatherer' or 'monetary'.

'hunter_gatherer'
shape Sequence[int]

The number of patches along each axis.

(20, 20)
max_food float

The largest capacity of a patch, drawn uniformly from 0.

15.0
max_resistance float

The largest resistance of a patch, drawn uniformly from 0.

5.0
regrowth float

The share of the capacity that grows back in a step.

1.0
need float

The food a person eats in a step.

2.0
labour float

The labour of an adult in a step.

5.0
interest float

The interest rate of loans per step.

0.003
repayment float

The share of the principal repaid per step.

0.004
n_firms int

The number of firms of the monetary economy at the start, each without an owner until it has a worker.

5
random_state int | Generator | None

The seed or generator of the patches, the places, ages, and propensities of the people.

0

Returns:

Name Type Description
system System

The model, with the group patches, one per cell, the variable group people, the reporters census.births, census.starvations, and census.old_age, and the observables population and money_gini. The monetary economy adds the variable group businesses, the group bank, and the reporter shops.gdp.

Raises:

Type Description
ValueError

The mode is unknown.

Examples:

>>> from skcomplex.models.economics.macroeconomics import metaphoria
>>> [group.label for group in metaphoria(10, mode='monetary', shape=(5, 5)).elements]
['patches', 'people', 'businesses', 'bank']
Source code in src/skcomplex/models/economics/macroeconomics/_metaphoria.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
@register_model(
    field='economics/macroeconomics',
    tags=('agents', 'mortality', 'evolution', 'money', 'hunter-gatherers', 'stock-flow consistent'),
    references=('Gooding 2019',),
    defaults={'until': 300},
    observables=_metaphoria_observables,
)
def metaphoria(
    n_people: int = 100,
    *,
    mode: str = 'hunter_gatherer',
    shape: Sequence[int] = (20, 20),
    max_food: float = 15.0,
    max_resistance: float = 5.0,
    regrowth: float = 1.0,
    need: float = 2.0,
    labour: float = 5.0,
    interest: float = 0.003,
    repayment: float = 0.004,
    n_firms: int = 5,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return Gooding's Metaphoria, a mortal population of hunter-gatherers or of a monetary economy.

    Patches of a periodic lattice hold food that grows back to a random capacity and resist harvesting with a random
    resistance. People need food every step, age, die of starvation or old age, and have children who inherit their
    propensities to breed, explore, found firms, and quit jobs, with mutations, so the propensities evolve under the
    test of survival. Hunter-gatherers pool the labour of the adults on a patch and share the harvest. In the monetary
    economy firms harvest with paid workers, sell the food in shops, and borrow the wages from a bank that creates
    money as loans, so the money of all holders sums to zero at every step. Gooding found the hunter-gatherers stable,
    and the monetary population smaller and less healthy, with money concentrating to a Gini coefficient near 0.5. On
    land where the hunter-gatherers thrive the monetary population dwindles towards extinction, and with abundant food
    it lives on with persistent starvation.

    Args:
        n_people:
            The initial number of adults.

        mode:
            `'hunter_gatherer'` or `'monetary'`.

        shape:
            The number of patches along each axis.

        max_food:
            The largest capacity of a patch, drawn uniformly from 0.

        max_resistance:
            The largest resistance of a patch, drawn uniformly from 0.

        regrowth:
            The share of the capacity that grows back in a step.

        need:
            The food a person eats in a step.

        labour:
            The labour of an adult in a step.

        interest:
            The interest rate of loans per step.

        repayment:
            The share of the principal repaid per step.

        n_firms:
            The number of firms of the monetary economy at the start, each without an owner until it has a worker.

        random_state:
            The seed or generator of the patches, the places, ages, and propensities of the people.

    Returns:
        system:
            The model, with the group `patches`, one per cell, the variable group `people`, the reporters
            `census.births`, `census.starvations`, and `census.old_age`, and the observables `population` and
            `money_gini`. The monetary economy adds the variable group `businesses`, the group `bank`, and the reporter
            `shops.gdp`.

    Raises:
        ValueError:
            The mode is unknown.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import metaphoria
        >>> [group.label for group in metaphoria(10, mode='monetary', shape=(5, 5)).elements]
        ['patches', 'people', 'businesses', 'bank']
    """
    if mode not in MODES:
        error_msg = f'Unknown mode {mode!r}. Use one of {MODES}.'
        raise ValueError(error_msg)
    rng = np.random.default_rng(random_state)
    shape = tuple(int(item) for item in shape)
    space = LatticeSpace(shape)
    sites = list(np.ndindex(*shape))
    capacity = rng.uniform(0.0, max_food, len(sites))
    patches = GroupElement(
        'patches',
        element=PlotElement,
        size=len(sites),
        values={
            'food': capacity,
            'capacity': capacity,
            'resistance': rng.uniform(0.0, max_resistance, len(sites)),
            'regrowth': regrowth,
        },
        location=sites,
    )
    cells = rng.integers(0, shape, size=(n_people, len(shape)))
    traits: dict[str, Array] = {name: rng.uniform(0.0, bound, n_people) for name, bound in PROPENSITIES.items()}
    people = GroupElement(
        'people',
        element=SettlerElement,
        size=n_people,
        values={
            'age': rng.integers(15, 40, n_people).astype(float),
            'need': need,
            'labour': labour,
            'monetary': float(mode == 'monetary'),
            'sight': SIGHT,
            **traits,
        },
        variable=True,
        location=[tuple(int(item) for item in cell) for cell in cells],
    )
    firms = rng.integers(0, shape, size=(n_firms, len(shape)))
    businesses = GroupElement(
        'businesses',
        element=BusinessElement,
        size=n_firms,
        values={'interest': interest, 'repayment': repayment},
        variable=True,
        location=[tuple(int(item) for item in cell) for cell in firms],
    )
    bank = GroupElement('bank', element=CentralBankElement, size=1, location=(0,) * len(shape))
    economy = [businesses, bank] if mode == 'monetary' else []
    jobs = LabourMarketInteraction(
        'jobs',
        requesters='people',
        providers='businesses',
        rule=PartnerSpec(SIGHT, 'score', score=LabourMarketInteraction.score, clearing='provider'),
        limit=('hireable', 'limit'),
        quantity=('quantity', 'vacancies'),
        phase='hiring',
        on_match=LabourMarketInteraction.hire,
    )
    shops = FoodMarketInteraction(
        'shops',
        requesters='people',
        providers='businesses',
        rule=PartnerSpec(SIGHT, 'score', score=FoodMarketInteraction.score, clearing='provider'),
        limit=('limit', 'price'),
        quantity=('shortfall', 'stock'),
        allowance='money',
        transfers=(('money', -1),),
        phase='market',
        on_match=FoodMarketInteraction.sell,
    )
    dividends = TransferInteraction(
        'dividends',
        source='bank',
        target='people',
        state='money',
        amount=CentralBankElement.get_income,
        pairing='random',
        fallback='businesses',
        guard=lambda agent, holder, model: CentralBankElement.get_income(agent, holder, model) > 0,
        form='agent',
        phase='banking',
    )
    exchange: list[BaseInteraction] = (
        [CommonsInteraction('commons', people='people')] if mode == 'hunter_gatherer' else [jobs, dividends, shops]
    )
    return System(
        elements=[patches, people, *economy],
        interactions=[
            RegrowthInteraction(
                'regrowth',
                target='patches',
                state='food',
                growth='regrowth',
                relative=True,
                phase='growth',
            ),
            *exchange,
            EstateInteraction('census', people='people'),
        ],
        space=space,
        time=DiscreteTime(schedule=PhasesScheduleSpec([PhaseSpec(name) for name in PHASES])),
    )

multiplier_accelerator(income=110.0, previous=100.0, *, c=0.8, v=1.5, G=20.0)

Return Samuelson's multiplier-accelerator, Y_{t+1} = c (1 + v) Y_t - c v Y_{t-1} + G.

Income oscillates around G / (1 - c), with growing amplitude when c v > 1 and complex characteristic roots.

Parameters:

Name Type Description Default
income float

The income of the current period.

110.0
previous float

The income of the previous period.

100.0
c float

The marginal propensity to consume.

0.8
v float

The accelerator, the investment response to the change in consumption.

1.5
G float

The government spending.

20.0

Returns:

Name Type Description
system System

The model, with the income economy.Y and its previous value economy.Y_lag1.

Examples:

>>> from skcomplex.models.economics.macroeconomics import multiplier_accelerator
>>> multiplier_accelerator(v=0.5).elements[0].v
0.5
Source code in src/skcomplex/models/economics/macroeconomics/_growth.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@register_model(
    field='economics/macroeconomics',
    tags=('business cycle', 'discrete map', 'second order'),
    references=('Samuelson 1939',),
    defaults={'until': 40, 'dt': 1},
)
def multiplier_accelerator(
    income: float = 110.0,
    previous: float = 100.0,
    *,
    c: float = 0.8,
    v: float = 1.5,
    G: float = 20.0,
) -> System:
    """Return Samuelson's multiplier-accelerator, `Y_{t+1} = c (1 + v) Y_t - c v Y_{t-1} + G`.

    Income oscillates around `G / (1 - c)`, with growing amplitude when `c v > 1` and complex characteristic roots.

    Args:
        income:
            The income of the current period.

        previous:
            The income of the previous period.

        c:
            The marginal propensity to consume.

        v:
            The accelerator, the investment response to the change in consumption.

        G:
            The government spending.

    Returns:
        system:
            The model, with the income `economy.Y` and its previous value `economy.Y_lag1`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import multiplier_accelerator
        >>> multiplier_accelerator(v=0.5).elements[0].v
        0.5
    """
    return System(
        elements=[IncomeElement('economy', Y=income, Y_lag1=previous, c=c, v=v, G=G)],
        interactions=[MapInteraction('cycle', target='economy', state='Y', function=_next_income)],
    )

optimal_consumption(k=1.0, *, A=1.0, alpha=0.3, delta=0.05, rho=0.03, v=0.7)

Return the Ramsey economy that chooses the consumption path maximising its discounted utility.

Run with an optimal control solver from little capital, consumption rises with capital along the saddle path towards the modified golden rule, and the economy saves a falling share of its output.

Parameters:

Name Type Description Default
k float

The initial capital per worker.

1.0
A float

The total factor productivity.

1.0
alpha float

The capital share of output.

0.3
delta float

The depreciation rate.

0.05
rho float

The discount rate.

0.03
v float

The value per unit of capital left at the horizon.

0.7

Returns:

Name Type Description
system System

The model, with the capital economy.k, the consumption economy.c, and the observable saving_rate.

Examples:

>>> from skcomplex.models.economics.macroeconomics import optimal_consumption
>>> optimal_consumption(rho=0.05).elements[0].rho
0.05
Source code in src/skcomplex/models/economics/macroeconomics/_optimal_growth.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@register_model(
    field='economics/macroeconomics',
    tags=('optimal control', 'growth', 'capital', 'collocation', 'ode'),
    references=('Dilão 2023',),
    defaults={
        'until': 40.0,
        'dt': 0.5,
        'solver': CollocationSolver(objective=_utility, terminal=_capital_value, sense='max', nodes=81),
    },
    observables=lambda system: [ObservableSpec('saving_rate', _saving_rate)],
)
def optimal_consumption(
    k: float = 1.0,
    *,
    A: float = 1.0,
    alpha: float = 0.3,
    delta: float = 0.05,
    rho: float = 0.03,
    v: float = 0.7,
) -> System:
    """Return the Ramsey economy that chooses the consumption path maximising its discounted utility.

    Run with an optimal control solver from little capital, consumption rises with capital along the saddle path
    towards the modified golden rule, and the economy saves a falling share of its output.

    Args:
        k:
            The initial capital per worker.

        A:
            The total factor productivity.

        alpha:
            The capital share of output.

        delta:
            The depreciation rate.

        rho:
            The discount rate.

        v:
            The value per unit of capital left at the horizon.

    Returns:
        system:
            The model, with the capital `economy.k`, the consumption `economy.c`, and the observable `saving_rate`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import optimal_consumption
        >>> optimal_consumption(rho=0.05).elements[0].rho
        0.05
    """
    return System(
        elements=[RamseyEconomyElement('economy', k=k, A=A, alpha=alpha, delta=delta, rho=rho, v=v)],
        interactions=[RateInteraction('accumulation', target='economy', state='k', rate=_net_saving)],
    )

solow(k=1.0, *, s=0.2, alpha=0.3, delta=0.05)

Return an economy whose capital per worker approaches its steady state through saving and depreciation.

Parameters:

Name Type Description Default
k float

The initial capital per worker.

1.0
s float

The saving rate.

0.2
alpha float

The capital share of output.

0.3
delta float

The depreciation rate.

0.05

Returns:

Name Type Description
system System

The model, with the economy economy.

Examples:

>>> from skcomplex.models.economics.macroeconomics import solow
>>> solow(s=0.3).interactions[0].s
0.3
Source code in src/skcomplex/models/economics/macroeconomics/_growth.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@register_model(
    field='economics/macroeconomics',
    tags=('growth', 'ode'),
    references=('Solow 1956',),
    defaults={'until': 200.0, 'dt': 1.0},
)
def solow(k: float = 1.0, *, s: float = 0.2, alpha: float = 0.3, delta: float = 0.05) -> System:
    """Return an economy whose capital per worker approaches its steady state through saving and depreciation.

    Args:
        k:
            The initial capital per worker.

        s:
            The saving rate.

        alpha:
            The capital share of output.

        delta:
            The depreciation rate.

    Returns:
        system:
            The model, with the economy `economy`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import solow
        >>> solow(s=0.3).interactions[0].s
        0.3
    """
    return System(
        elements=[EconomyElement('economy', k=k)],
        interactions=[SolowGrowthInteraction('growth', target='economy', s=s, alpha=alpha, delta=delta)],
    )

stock_flow_consistent_economy(*, household_deposits=60.0, firm_deposits=20.0, bank_deposits=10.0, loans=50.0, sales=40.0, alpha_income=0.6, alpha_wealth=0.4, wage_share=0.7, interest=0.03, payout=0.5, distribution=0.3, credit=0.1, repayment=0.08)

Return a stock-flow consistent economy, a closed circuit of money between households, firms, and a bank.

Money is the deposits of the three sectors, tagged as the quantity money, and every transaction of a period is a flow, so each payment is one sector's outflow and another's inflow. Households consume a share alpha_income of their wages and a share alpha_wealth of their deposits, firms pay the wage share of last period's sales and the interest on their loans, and distribute a share of their deposits as profits, and the bank pays out a share of its funds as dividends. Lending creates money and repayment destroys it, the only flows with the environment, so the total money changes exactly by lending minus repayment, in the spirit of the models of Godley and Lavoie (2007) and the balance sheets of Caiani et al. (2016). Loans approach credit * sales / repayment and sales settle where the stocks stop growing. The circuit moves the money of a period, and the maps loans and sales set the loans and the sales of the firms.

Parameters:

Name Type Description Default
household_deposits float

The initial deposits of the households.

60.0
firm_deposits float

The initial deposits of the firms.

20.0
bank_deposits float

The initial own funds of the bank.

10.0
loans float

The initial loans of the firms.

50.0
sales float

The sales of the previous period.

40.0
alpha_income float

The propensity to consume out of wages.

0.6
alpha_wealth float

The propensity to consume out of deposits.

0.4
wage_share float

The share of sales paid as wages.

0.7
interest float

The interest rate on loans per period.

0.03
payout float

The share of the bank's funds paid out as dividends per period.

0.5
distribution float

The share of the firms' deposits distributed to households as profits per period.

0.3
credit float

The new loans per unit of sales.

0.1
repayment float

The share of loans repaid per period.

0.08

Returns:

Name Type Description
system System

The model, with the deposits households.deposits, firms.deposits, and bank.deposits, the loans firms.loans, the sales firms.sales, the flows circuit.consumption, circuit.wages, circuit.interest, circuit.dividends, circuit.profits, circuit.lending, and circuit.repayments, the maps loans and sales, and the net money created __boundary.money.

Examples:

>>> from skcomplex.models.economics.macroeconomics import stock_flow_consistent_economy
>>> [element.label for element in stock_flow_consistent_economy().elements]
['households', 'firms', 'bank']
Source code in src/skcomplex/models/economics/macroeconomics/_monetary.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@register_model(
    field='economics/macroeconomics',
    tags=('stocks and flows', 'stock-flow consistent', 'money', 'discrete map'),
    references=('Godley and Lavoie 2007', 'Caiani et al. 2016'),
    defaults={'until': 100, 'dt': 1},
)
def stock_flow_consistent_economy(
    *,
    household_deposits: float = 60.0,
    firm_deposits: float = 20.0,
    bank_deposits: float = 10.0,
    loans: float = 50.0,
    sales: float = 40.0,
    alpha_income: float = 0.6,
    alpha_wealth: float = 0.4,
    wage_share: float = 0.7,
    interest: float = 0.03,
    payout: float = 0.5,
    distribution: float = 0.3,
    credit: float = 0.1,
    repayment: float = 0.08,
) -> System:
    """Return a stock-flow consistent economy, a closed circuit of money between households, firms, and a bank.

    Money is the deposits of the three sectors, tagged as the quantity `money`, and every transaction of a period is
    a flow, so each payment is one sector's outflow and another's inflow. Households consume a share `alpha_income` of
    their wages and a share `alpha_wealth` of their deposits, firms pay the wage share of last period's sales and the
    interest on their loans, and distribute a share of their deposits as profits, and the bank pays out a share of its
    funds as dividends. Lending creates money and
    repayment destroys it, the only flows with the environment, so the total money changes exactly by lending minus
    repayment, in the spirit of the models of Godley and Lavoie (2007) and the balance sheets of Caiani et al. (2016).
    Loans approach `credit * sales / repayment` and sales settle where the stocks stop growing. The circuit moves the
    money of a period, and the maps `loans` and `sales` set the loans and the sales of the firms.

    Args:
        household_deposits:
            The initial deposits of the households.

        firm_deposits:
            The initial deposits of the firms.

        bank_deposits:
            The initial own funds of the bank.

        loans:
            The initial loans of the firms.

        sales:
            The sales of the previous period.

        alpha_income:
            The propensity to consume out of wages.

        alpha_wealth:
            The propensity to consume out of deposits.

        wage_share:
            The share of sales paid as wages.

        interest:
            The interest rate on loans per period.

        payout:
            The share of the bank's funds paid out as dividends per period.

        distribution:
            The share of the firms' deposits distributed to households as profits per period.

        credit:
            The new loans per unit of sales.

        repayment:
            The share of loans repaid per period.

    Returns:
        system:
            The model, with the deposits `households.deposits`, `firms.deposits`, and `bank.deposits`, the loans
            `firms.loans`, the sales `firms.sales`, the flows `circuit.consumption`, `circuit.wages`,
            `circuit.interest`, `circuit.dividends`, `circuit.profits`, `circuit.lending`, and `circuit.repayments`,
            the maps `loans` and `sales`, and the net money created `__boundary.money`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import stock_flow_consistent_economy
        >>> [element.label for element in stock_flow_consistent_economy().elements]
        ['households', 'firms', 'bank']
    """
    return System(
        elements=[
            HouseholdsElement(
                'households',
                deposits=household_deposits,
                alpha_income=alpha_income,
                alpha_wealth=alpha_wealth,
            ),
            FirmsElement(
                'firms',
                deposits=firm_deposits,
                loans=loans,
                sales=sales,
                wage_share=wage_share,
                distribution=distribution,
                repayment=repayment,
            ),
            MonetaryBankElement('bank', deposits=bank_deposits, interest=interest, payout=payout, credit=credit),
        ],
        interactions=[
            CircuitInteraction('circuit', households='households', firms='firms', bank='bank'),
            MapInteraction('loans', target='firms', state='loans', function=_next_loans, sources=['bank']),
            MapInteraction('sales', target='firms', state='sales', function=_next_sales, sources=['households']),
        ],
    )

toy_firm_investment(n_firms=100, *, gamma=1.1, phi=0.1, r=0.1, pbar=0.01, random_state=0)

Return heterogeneous firms that invest out of past profits and sell at random prices, with entry and exit.

The firms start with net worth and capital 1 and a profit drawn from pbar + 2 U(0, 1), as in the toy model of Bargigli et al. in Caiani et al. (2016, sect. 2.1). Aggregate production fluctuates as bankrupt firms are replaced.

Parameters:

Name Type Description Default
n_firms int

The number of firms.

100
gamma float

The investment accelerator.

1.1
phi float

The productivity of capital.

0.1
r float

The interest rate.

0.1
pbar float

The constant part of the price.

0.01
random_state int | Generator | None

The seed or generator of the initial profits.

0

Returns:

Name Type Description
system System

The model, with the group firms and the aggregate production production.

Examples:

>>> from skcomplex.models.economics.macroeconomics import toy_firm_investment
>>> toy_firm_investment(20).elements[0].check().prepare().size_
20
Source code in src/skcomplex/models/economics/macroeconomics/_firms.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
@register_model(
    field='economics/macroeconomics',
    tags=('business cycle', 'agents', 'firms'),
    references=('Caiani et al. 2016', 'Delli Gatti et al. 2010'),
    defaults={'until': 100},
    observables=lambda system: [ObservableSpec('production', group='firms', reduce='sum', variable='Y')],
)
def toy_firm_investment(
    n_firms: int = 100,
    *,
    gamma: float = 1.1,
    phi: float = 0.1,
    r: float = 0.1,
    pbar: float = 0.01,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return heterogeneous firms that invest out of past profits and sell at random prices, with entry and exit.

    The firms start with net worth and capital 1 and a profit drawn from `pbar + 2 U(0, 1)`, as in the toy model of
    Bargigli et al. in Caiani et al. (2016, sect. 2.1). Aggregate production fluctuates as bankrupt firms are replaced.

    Args:
        n_firms:
            The number of firms.

        gamma:
            The investment accelerator.

        phi:
            The productivity of capital.

        r:
            The interest rate.

        pbar:
            The constant part of the price.

        random_state:
            The seed or generator of the initial profits.

    Returns:
        system:
            The model, with the group `firms` and the aggregate production `production`.

    Examples:
        >>> from skcomplex.models.economics.macroeconomics import toy_firm_investment
        >>> toy_firm_investment(20).elements[0].check().prepare().size_
        20
    """
    profits = pbar + 2.0 * np.random.default_rng(random_state).random(n_firms)
    firms = GroupElement(
        'firms',
        element=InvestingFirmElement,
        size=n_firms,
        values={'Z': profits, 'gamma': gamma, 'phi': phi, 'r': r, 'pbar': pbar},
    )
    steps = [
        MapInteraction(label, target='firms', state=name, function=function, form='agent')
        for label, name, function in InvestingFirmElement.get_steps()
    ]
    return System(elements=[firms], interactions=steps, time=DiscreteTime())