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.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import BusinessElement
>>> GroupElement('businesses', element=BusinessElement, size=2, variable=True).size
2
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
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, system)

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
system SystemView

The system.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
def produce(self, agent: BusinessView, system: SystemView) -> None:
    """Harvest with the workers, pay wages and the bank, adjust, and pay a dividend to the owner.

    Args:
        agent:
            The firm.

        system:
            The system.
    """
    people = BusinessElement._people_by_id(system)
    workers = [person for person in people.values() if person.employer == agent.id]
    bank = system.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.systems.economics.macroeconomics import CapitalStockElement
>>> CapitalStockElement('economy', lifetime=20.0).lifetime
20.0
Source code in src/skcomplex/systems/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 system 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.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import CentralBankElement
>>> GroupElement('bank', element=CentralBankElement, size=1).size
1
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
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, system) 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
system SystemView

The system view of the dividends.

required

Returns:

Name Type Description
income float

The income.

Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@staticmethod
def get_income(agent: CentralBankView, holder: AgentView, system: SystemView) -> 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.

        system:
            The system 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.systems.economics.macroeconomics import CircuitInteraction
>>> CircuitInteraction('circuit', households='households', firms='firms', bank='bank').bank
'bank'
Source code in src/skcomplex/systems/economics/macroeconomics/_monetary.py
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
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.systems.economics.macroeconomics import CommonsInteraction
>>> CommonsInteraction('commons', people='people').people
'people'
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
740
741
742
743
744
745
746
747
def __init__(
    self,
    label: str,
    *,
    people: Annotated[str, RoleSpec()],
) -> None:
    super().__init__(label)
    self.people = people

harvest(system)

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

Parameters:

Name Type Description Default
system SystemView

The system view of the interaction.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
def harvest(self, system: SystemView) -> None:
    """Harvest every occupied patch and share the food among the people on it.

    Args:
        system:
            The system view of the interaction.
    """
    cells: dict[tuple[int, ...], list[SettlerView]] = {}
    for person in system.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[SystemView]

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(elements=(GROUP, VARIABLE_GROUP))]

The path of the group of firms.

required
providers Annotated[str, RoleSpec(elements=(GROUP, VARIABLE_GROUP))]

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, SystemT], None] | None

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

None

Examples:

>>> from skcomplex.systems.economics.macroeconomics import CreditMarketInteraction
>>> CreditMarketInteraction('credit', requesters='firms', providers='banks').reports[-1]
'spread'
Source code in src/skcomplex/core/components/interactions/_matching.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
def __init__(
    self,
    label: str,
    *,
    requesters: Annotated[str, RoleSpec(elements=(ElementKind.GROUP, ElementKind.VARIABLE_GROUP))],
    providers: Annotated[str, RoleSpec(elements=(ElementKind.GROUP, ElementKind.VARIABLE_GROUP))],
    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, SystemT], 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 system 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.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import DepositorElement
>>> GroupElement('customers', element=DepositorElement, size=10, values={'deposit': 5.0}).size
10
Source code in src/skcomplex/systems/economics/macroeconomics/_banking.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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, system) 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
system SystemView

The system view of the payments.

required

Returns:

Name Type Description
amount float

The payment.

Source code in src/skcomplex/systems/economics/macroeconomics/_banking.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@staticmethod
def get_payment(payer: DepositorView, payee: DepositorView, system: SystemView) -> 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.

        system:
            The system view of the payments.

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

get_repayment(customer, same, system) 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
system SystemView

The system view of the repayments.

required

Returns:

Name Type Description
amount float

The repayment.

Source code in src/skcomplex/systems/economics/macroeconomics/_banking.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
@staticmethod
def get_repayment(customer: DepositorView, same: DepositorView, system: SystemView) -> 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.

        system:
            The system 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.systems.economics.macroeconomics import EconomyElement
>>> EconomyElement('country', k=1.0).k
1.0
Source code in src/skcomplex/systems/economics/macroeconomics/_growth.py
39
40
41
42
43
44
45
46
47
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.systems.economics.macroeconomics import EstateInteraction
>>> EstateInteraction('census', people='people').reports
('births', 'starvations', 'old_age')
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
804
805
806
807
808
809
810
811
def __init__(
    self,
    label: str,
    *,
    people: Annotated[str, RoleSpec()],
) -> None:
    super().__init__(label)
    self.people = people

count(system)

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

Parameters:

Name Type Description Default
system SystemView

The system view of the interaction.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
def count(self, system: SystemView) -> None:
    """Count the births and deaths of the step and remove the dead.

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

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.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import FirmElement
>>> GroupElement('firms', element=FirmElement, size=10, values={'net_worth': 5.0}).size
10
Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
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
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, SystemView], float]], ...]

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

Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
@staticmethod
def get_default() -> tuple[tuple[str, Callable[[FirmView, SystemView], 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 system view.
    """
    worth = FirmElement.get_worth
    return (
        ('injected', lambda agent, system: agent.entry if worth(agent) < 0 else agent.injected),
        ('defaulted', lambda agent, system: 1.0 if worth(agent) < 0 else agent.defaulted),
        ('partner', lambda agent, system: -1.0 if worth(agent) < 0 else agent.partner),
        ('net_worth', lambda agent, system: 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, SystemView], float]], ...]

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

Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
@staticmethod
def get_plan() -> tuple[tuple[str, Callable[[FirmView, SystemView], 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 system view.
    """
    return (
        ('demand', lambda agent, system: agent.leverage * agent.net_worth),
        ('loan', lambda agent, system: 0.0),
        ('rate', lambda agent, system: 0.0),
        ('defaulted', lambda agent, system: 0.0),
        ('injected', lambda agent, system: 0.0),
    )

get_return(agent, system) 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
system SystemView

The system view of the investment.

required

Returns:

Name Type Description
gross_return float

The return of the step.

Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
@staticmethod
def get_return(agent: FirmView, system: SystemView) -> 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.

        system:
            The system view of the investment.

    Returns:
        gross_return:
            The return of the step.
    """
    draw = agent.mean_return + agent.volatility * system.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/systems/economics/macroeconomics/_credit.py
170
171
172
173
174
175
176
177
178
179
180
181
182
@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.systems.economics.macroeconomics import FirmsElement
>>> FirmsElement('firms').loans
50.0
Source code in src/skcomplex/systems/economics/macroeconomics/_monetary.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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[SystemView]

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.core.specs import PartnerSpec
>>> from skcomplex.systems.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/components/interactions/_matching.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
def __init__(
    self,
    label: str,
    *,
    requesters: Annotated[str, RoleSpec(elements=(ElementKind.GROUP, ElementKind.VARIABLE_GROUP))],
    providers: Annotated[str, RoleSpec(elements=(ElementKind.GROUP, ElementKind.VARIABLE_GROUP))],
    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, SystemT], 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/systems/economics/macroeconomics/_metaphoria.py
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
@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, system) 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
system SystemView

The system view of the market.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
891
892
893
894
895
896
897
898
899
900
901
902
903
904
@staticmethod
def sell(match: MatchSpec, system: SystemView) -> None:
    """Book the value of a sale as revenue of the firm.

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

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

HerdingAgentElement(label, visual=None, *, c=0.0, x=1.0, gamma=1.0, sigma=0.0)

Bases: Element

Agent whose choice c is a weighted sum of the past choices of others plus its individual characteristic.

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
c Annotated[float, StateSpec()]

The choice of the agent, such as its consumption.

0.0
x Annotated[float, StateSpec()]

The mean of the individual characteristic of the agent.

1.0
gamma Annotated[float, ParameterSpec()]

The weight gamma of the individual characteristic in the choice.

1.0
sigma Annotated[float, ParameterSpec()]

The standard deviation of the shocks of the individual characteristic.

0.0

Examples:

>>> from skcomplex.core.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import HerdingAgentElement
>>> GroupElement('agents', element=HerdingAgentElement, size=3, values={'x': [1.0, 2.0, 3.0]}).size
3
Source code in src/skcomplex/systems/economics/macroeconomics/_herding.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    c: Annotated[float, StateSpec()] = 0.0,
    x: Annotated[float, StateSpec()] = 1.0,
    gamma: Annotated[float, ParameterSpec()] = 1.0,
    sigma: Annotated[float, ParameterSpec()] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.c = c
    self.x = x
    self.gamma = gamma
    self.sigma = sigma

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.systems.economics.macroeconomics import HouseholdsElement
>>> HouseholdsElement('households').deposits
60.0
Source code in src/skcomplex/systems/economics/macroeconomics/_monetary.py
100
101
102
103
104
105
106
107
108
109
110
111
112
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.systems.economics.macroeconomics import IncomeElement
>>> IncomeElement('economy', c=0.5).c
0.5
Source code in src/skcomplex/systems/economics/macroeconomics/_growth.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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 system, 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.systems.economics.macroeconomics import InvestingFirmElement
>>> GroupElement('firms', element=InvestingFirmElement, size=10, values={'gamma': 1.2}).size
10
Source code in src/skcomplex/systems/economics/macroeconomics/_firms.py
 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
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, system) staticmethod

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

Parameters:

Name Type Description Default
agent InvestingFirmView

The firm.

required
system SystemView

The system view of the pricing.

required

Returns:

Name Type Description
price float

The price of the period.

Source code in src/skcomplex/systems/economics/macroeconomics/_firms.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
@staticmethod
def get_price(agent: InvestingFirmView, system: SystemView) -> float:
    """Return a random price, `pbar + 2 U(0, 1)`.

    Args:
        agent:
            The firm.

        system:
            The system view of the pricing.

    Returns:
        price:
            The price of the period.
    """
    return agent.pbar + 2.0 * float(system.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, SystemView], float]], ...]

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

Source code in src/skcomplex/systems/economics/macroeconomics/_firms.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
@staticmethod
def get_steps() -> tuple[tuple[str, str, Callable[[InvestingFirmView, SystemView], 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
            system view.
    """
    return (
        ('investment', 'K', lambda agent, system: agent.K + agent.gamma * agent.Z),
        ('production', 'Y', lambda agent, system: agent.phi * agent.K),
        ('borrowing', 'B', lambda agent, system: max(agent.K - agent.A, 0.0)),
        ('pricing', 'P', InvestingFirmElement.get_price),
        ('profit', 'Z', lambda agent, system: agent.P * agent.Y - agent.r * agent.K),
        ('accumulation', 'A', lambda agent, system: agent.A + agent.Z),
        ('entry_profit', 'Z', lambda agent, system: 0.0 if agent.A < 0 else agent.Z),
        ('entry_capital', 'K', lambda agent, system: 1.0 if agent.A < 0 else agent.K),
        ('entry_worth', 'A', lambda agent, system: 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[SystemView]

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.core.specs import PartnerSpec
>>> from skcomplex.systems.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,
... ).providers
'businesses'
Source code in src/skcomplex/core/components/interactions/_matching.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
def __init__(
    self,
    label: str,
    *,
    requesters: Annotated[str, RoleSpec(elements=(ElementKind.GROUP, ElementKind.VARIABLE_GROUP))],
    providers: Annotated[str, RoleSpec(elements=(ElementKind.GROUP, ElementKind.VARIABLE_GROUP))],
    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, SystemT], 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, system) 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
system SystemView

The system view of the market.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
@staticmethod
def hire(match: MatchSpec, system: SystemView) -> None:
    """Employ a matched person at its firm and move it there.

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

        system:
            The system view of the market.
    """
    del system
    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/systems/economics/macroeconomics/_metaphoria.py
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
@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.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import LenderElement
>>> GroupElement('banks', element=LenderElement, size=5, values={'equity': 100.0}).size
5
Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
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, system) 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
system SystemView

The system view of the settlement.

required

Returns:

Name Type Description
income float

The change of the equity.

Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
@staticmethod
def get_income(agent: LenderView, system: SystemView) -> float:
    """Return the interest a bank earns in the step, less what its defaulting borrowers cannot repay.

    Args:
        agent:
            The bank.

        system:
            The system view of the settlement.

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

get_losses(agent, system) staticmethod

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

Parameters:

Name Type Description Default
agent LenderView

The bank.

required
system SystemView

The system view of the settlement.

required

Returns:

Name Type Description
losses float

The change of the losses.

Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
@staticmethod
def get_losses(agent: LenderView, system: SystemView) -> float:
    """Return the loans of a bank written off in the step.

    Args:
        agent:
            The bank.

        system:
            The system view of the settlement.

    Returns:
        losses:
            The change of the losses.
    """
    borrowers = LenderElement._get_borrowers(agent, system)
    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, SystemView], float]], ...]

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

Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
@staticmethod
def get_quote() -> tuple[tuple[str, Callable[[LenderView, SystemView], 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 system view.
    """
    return (
        ('ask', lambda agent, system: agent.alpha * max(agent.equity, 1e-6) ** -agent.alpha),
        ('supply', lambda agent, system: max(agent.equity, 0.0) / agent.capital_ratio),
        ('lent', lambda agent, system: 0.0),
        ('losses', lambda agent, system: 0.0),
    )

Mark0EconomyInteraction(label, *, rule=None, phase=None, firms, c0=0.5, beta=2.0, gamma=0.01, eta_minus=0.2, ratio=2.0, delta=0.02, theta=3.0, phi=0.1, zeta=1.0, omega=0.2, f=0.5, shock_time=100.0, shock_length=0.0, dc=0.0, dzeta=0.0, kappa=1.0, offset=1.25, credit='none')

Bases: FeedbackInteraction[SystemView]

Representative household and bank of the Mark-0 economy, which run the firms through one month every step.

Every step the firms adjust their workforce, prices, and wages by the rules of thumb of Mark-0 (Eqs. 11 to 17 of Sharma et al. 2020) from the demand and the profit of the last month, pay their wages, and sell to the household, whose consumption budget c (S + W + rho_d S) (Eq. 5) splits among the firms by the logit demand of their prices relative to the mean price (Eq. 9). Firms pay interest and, when profitable with positive cash, a share delta of their cash as dividends (Eqs. 18 and 19). A firm whose debt to payroll Phi = -E / (W N) reaches the bankruptcy threshold Theta defaults, its debt falls on the bank, which moves the loan and deposit rates (Eqs. 21 and 22), and a bankrupt firm is revived with probability phi per month at the mean price and wage. Following the simplifications of the source, inflation expectations, the feedback of inflation on consumption, the effect of fragility on hiring, and the central bank are switched off, so the base rate is zero.

During the shock, from shock_time for shock_length months, the productivity falls by the share dzeta and the consumption propensity by the share dc. The credit policy 'naive' lifts the threshold during the shock and restores it at once after it, and 'adaptive' restores it to max(offset <Phi>, Theta) with the production-weighted mean fragility <Phi> (Eq. 24). At the end of the shock the household's savings are multiplied by kappa, the helicopter money.

Parameters:

Name Type Description Default
label str

The label of the economy.

required
rule None

Unused, since the economy overrides act.

None
phase str | None

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

None
firms Annotated[str, RoleSpec()]

The path of the group of firms.

required
c0 Annotated[float, ParameterSpec(domain=open(0, 1))]

The consumption propensity of the household.

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

The intensity of choice of the demand and of the workers.

2.0
gamma Annotated[float, ParameterSpec(domain=closed_open(0, 1))]

The relative size of the adjustments of prices and wages.

0.01
eta_minus Annotated[float, ParameterSpec(domain=closed(0, 1))]

The firing propensity eta0-.

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

The hiring to firing ratio R, so the hiring propensity is R eta0-.

2.0
delta Annotated[float, ParameterSpec(domain=closed(0, 1))]

The share of their cash that profitable firms pay as dividends.

0.02
theta Annotated[float, ParameterSpec(domain=open(0, oo))]

The bankruptcy threshold of the debt to payroll ratio.

3.0
phi Annotated[float, ParameterSpec(domain=closed(0, 1))]

The probability per month that a bankrupt firm is revived.

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

The productivity of a worker.

1.0
omega Annotated[float, ParameterSpec(domain=closed(0, 1))]

The weight of the last inflation in its moving average.

0.2
f Annotated[float, ParameterSpec(domain=closed(0, 1))]

The share of the costs of defaults borne by the borrowers rather than the depositors.

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

The month the shock starts.

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

The months the shock lasts.

0.0
dc Annotated[float, ParameterSpec(domain=closed_open(0, 1))]

The relative fall of the consumption propensity during the shock.

0.0
dzeta Annotated[float, ParameterSpec(domain=closed_open(0, 1))]

The relative fall of the productivity during the shock.

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

The factor of the household's savings at the end of the shock, 1 for no helicopter money.

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

The factor of the mean fragility of the adaptive credit policy.

1.25
credit str

The credit policy, 'none', 'naive', or 'adaptive'.

'none'

Raises:

Type Description
ValidationError

The credit policy is unknown, raised by prepare.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import Mark0EconomyInteraction
>>> Mark0EconomyInteraction('economy', firms='firms').reports[:2]
('unemployment', 'inflation')
Source code in src/skcomplex/systems/economics/macroeconomics/_mark0.py
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
def __init__(
    self,
    label: str,
    *,
    rule: None = None,
    phase: str | None = None,
    firms: Annotated[str, RoleSpec()],
    c0: Annotated[float, ParameterSpec(domain=RealSet.open(0, 1))] = 0.5,
    beta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
    gamma: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, 1))] = 0.01,
    eta_minus: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 0.2,
    ratio: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
    delta: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 0.02,
    theta: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 3.0,
    phi: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 0.1,
    zeta: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    omega: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 0.2,
    f: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 0.5,
    shock_time: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 100.0,
    shock_length: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    dc: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, 1))] = 0.0,
    dzeta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, 1))] = 0.0,
    kappa: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    offset: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.25,
    credit: str = 'none',
) -> None:
    super().__init__(label, rule=rule, phase=phase)
    self.firms = firms
    self.c0 = c0
    self.beta = beta
    self.gamma = gamma
    self.eta_minus = eta_minus
    self.ratio = ratio
    self.delta = delta
    self.theta = theta
    self.phi = phi
    self.zeta = zeta
    self.omega = omega
    self.f = f
    self.shock_time = shock_time
    self.shock_length = shock_length
    self.dc = dc
    self.dzeta = dzeta
    self.kappa = kappa
    self.offset = offset
    self.credit = credit

act(system)

Run one month of the economy: adjust, pay, sell, distribute, default, revive, and set the rates.

Parameters:

Name Type Description Default
system SystemView

The system view of the economy.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_mark0.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
def act(self, system: SystemView) -> None:
    """Run one month of the economy: adjust, pay, sell, distribute, default, revive, and set the rates.

    Args:
        system:
            The system view of the economy.
    """
    firms = sorted(system.agents('firms', Mark0FirmView), key=lambda firm: firm.id)
    names = ('workforce', 'demand', 'price', 'wage', 'cash', 'profit', 'active')
    keys = ('n', 'd', 'p', 'w', 'e', 'profit', 'active')
    state = {key: np.array([getattr(firm, name) for firm in firms]) for key, name in zip(keys, names, strict=True)}
    store = system.storage
    month = int(self._stored(system, 'month', 0.0))
    savings, inflation_ema = self._stored(system, 'savings', 0.0), self._stored(system, 'inflation_ema', 0.0)
    rho_l, rho_d = self._stored(system, 'rho_l', 0.0), self._stored(system, 'rho_d', 0.0)
    start, length = system.parameter('shock_time'), system.parameter('shock_length')
    shocked = start <= month < start + length
    zeta = system.parameter('zeta') * (1 - system.parameter('dzeta') * shocked)
    c = system.parameter('c0') * (1 - system.parameter('dc') * shocked)
    if length > 0 and month == start + length:
        savings *= system.parameter('kappa')
    size = len(firms)
    if month > 0:
        state['sales'] = np.asarray(store['sales'])
        state['interest'] = np.asarray(store['interest'])
        self._adjust(system, state, zeta, 1 - state['n'].sum() / size)
    active = state['active'] > 0
    production = zeta * state['n']
    bill = state['w'] * state['n']
    wages = float(bill.sum())
    state['e'] = state['e'] - bill
    mean_price = self._weighted(state['p'][active], production[active])
    mean_wage = self._weighted(state['w'][active], production[active])
    budget = c * (savings + wages + rho_d * savings)
    weights = np.where(active, np.exp(-system.parameter('beta') * state['p'] / mean_price), 0.0)
    state['d'] = budget * weights / (state['p'] * weights.sum())
    sales = state['p'] * np.minimum(production, state['d'])
    interest = rho_d * np.maximum(state['e'], 0.0) + rho_l * np.minimum(state['e'], 0.0)
    state['e'] = state['e'] + sales + interest
    state['profit'] = sales - bill + interest
    savings += wages + rho_d * savings - float(sales.sum())
    dividends = np.where((state['profit'] > 0) & (state['e'] > 0), system.parameter('delta') * state['e'], 0.0)
    state['e'] -= dividends
    savings += float(dividends.sum())
    fragility_of = np.divide(-state['e'], bill, out=np.zeros(size), where=bill > 0)
    fragility = self._weighted(fragility_of[active], production[active])
    threshold = self._threshold(system, month, fragility)
    defaulted = active & (fragility_of >= threshold)
    losses = float(-state['e'][defaulted].sum())
    for key in ('n', 'd', 'e', 'profit', 'active'):
        state[key] = np.where(defaulted, 0.0, state[key])
    unemployment = 1 - state['n'].sum() / size
    revived = (state['active'] == 0) & ~defaulted & (system.rng.random(size) < system.parameter('phi'))
    start_size = unemployment * system.rng.random(size)
    state['n'] = np.where(revived, start_size, state['n'])
    state['p'] = np.where(revived, mean_price, state['p'])
    state['w'] = np.where(revived, mean_wage, state['w'])
    state['active'] = np.where(revived, 1.0, state['active'])
    previous = self._stored(system, 'price', mean_price)
    inflation = (mean_price - previous) / previous
    inflation_ema = system.parameter('omega') * inflation + (1 - system.parameter('omega')) * inflation_ema
    debts, deposits = float(-np.minimum(state['e'], 0.0).sum()), float(np.maximum(state['e'], 0.0).sum())
    f = system.parameter('f')
    rho_l = f * losses / debts if debts > 0 else 0.0
    rho_d = -(1 - f) * losses / (savings + deposits) if savings + deposits > 0 else 0.0
    store.update(
        month=month + 1,
        savings=savings,
        inflation_ema=inflation_ema,
        rho_l=rho_l,
        rho_d=rho_d,
        price=mean_price,
        sales=np.where(defaulted, 0.0, sales),
        interest=np.where(defaulted, 0.0, interest),
    )
    for index, firm in enumerate(firms):
        for key, name in zip(keys, names, strict=True):
            setattr(firm, name, float(state[key][index]))
    reported = {
        'unemployment': 1 - state['n'].sum() / size,
        'inflation': inflation,
        'output': float((zeta * state['n']).sum()),
        'savings': savings,
        'fragility': fragility,
        'bankruptcies': float(defaulted.sum()),
        'threshold': min(threshold, 1e9),
        'price': mean_price,
        'wage': mean_wage,
    }
    for name, value in reported.items():
        system.report(name, value)

Mark0FirmElement(label, visual=None, *, workforce=0.5, demand=0.5, price=1.0, wage=1.0, cash=0.0, profit=0.0, active=1.0)

Bases: Element

Firm of the Mark-0 economy, with its workforce, demand, price, wage, cash balance, and profit.

The production is the productivity times the workforce, a negative cash balance is a debt, and the economy of the system updates every firm at once.

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
workforce Annotated[float, StateSpec(domain=closed_open(0, oo))]

The workers of the firm, as a share of the households.

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

The demand for the goods of the firm.

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

The price of the goods of the firm.

1.0
wage Annotated[float, StateSpec(domain=open(0, oo))]

The wage the firm pays.

1.0
cash Annotated[float, StateSpec()]

The cash balance, negative for a debt.

0.0
profit Annotated[float, StateSpec()]

The profit of the last step.

0.0
active Annotated[float, StateSpec(domain=closed(0, 1))]

1 for a firm in business, 0 for a bankrupt firm waiting to be revived.

1.0

Examples:

>>> from skcomplex.core.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import Mark0FirmElement
>>> GroupElement('firms', element=Mark0FirmElement, size=3).size
3
Source code in src/skcomplex/systems/economics/macroeconomics/_mark0.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    workforce: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    demand: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    price: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 1.0,
    wage: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 1.0,
    cash: Annotated[float, StateSpec()] = 0.0,
    profit: Annotated[float, StateSpec()] = 0.0,
    active: Annotated[float, StateSpec(domain=RealSet.closed(0, 1))] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.workforce = workforce
    self.demand = demand
    self.price = price
    self.wage = wage
    self.cash = cash
    self.profit = profit
    self.active = active

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.systems.economics.macroeconomics import MonetaryBankElement
>>> MonetaryBankElement('bank').deposits
10.0
Source code in src/skcomplex/systems/economics/macroeconomics/_monetary.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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.systems.economics.macroeconomics import PlotElement
>>> PlotElement('patch').regrowth
1.0
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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[SystemView]

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[[SystemView], 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.systems.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/systems/economics/macroeconomics/_banking.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
def __init__(
    self,
    label: str,
    *,
    rule: Callable[[SystemView], 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(system)

Set the reserve ratio of the banks from the schedule.

Parameters:

Name Type Description Default
system SystemView

The system view of the regulator.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_banking.py
297
298
299
300
301
302
303
304
305
306
307
308
def act(self, system: SystemView) -> None:
    """Set the reserve ratio of the banks from the schedule.

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

PurchasingPowerElement(label, visual=None, *, variables=None, A=A_MATRIX, B=B_MATRIX, C=C_MATRIX, D=D_MATRIX, feedback=False)

Bases: DeclaredElement

National economy of three spending sectors, z' = A z + B u and y = C z + D u, with sector policies.

The state z_i is the gap between the demand and the supply of money of sector i, the basic, the desired, and the luxurious living conditions, the output y_i is the index of the sector, and the policy x_i of a sector is a step of size policy_i at the time policy_time_i. Without feedback the economy takes the policies as its inputs, u = x. With feedback, the market reactions of Forrest's feedback component set u so that every sector responds to its own policy only, as the decoupled systems w_i' = A w_i + B_i x_i and y_i = C_i w_i + D_i x_i, whose states w_ik the element holds. The feedback component holds a copy of z that equals z from the common zero start, so u = D^-1 (y - C z) with the decoupled outputs y.

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
variables Mapping[str, DeclaredVariableSpec] | None

Declared states and parameters that replace the derived ones, by name.

None
A Sequence[Sequence[float]]

The matrix of the state.

A_MATRIX
B Sequence[Sequence[float]]

The matrix of the inputs in the state.

B_MATRIX
C Sequence[Sequence[float]]

The matrix of the state in the outputs.

C_MATRIX
D Sequence[Sequence[float]]

The nonsingular matrix of the inputs in the outputs.

D_MATRIX
feedback bool

Whether the feedback component decouples the sectors.

False

Examples:

>>> from skcomplex.systems.economics.macroeconomics import PurchasingPowerElement
>>> PurchasingPowerElement('economy', feedback=True).get_value('z1')
0.0
Source code in src/skcomplex/systems/economics/macroeconomics/_purchasing_power.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    variables: Mapping[str, DeclaredVariableSpec] | None = None,
    A: Sequence[Sequence[float]] = A_MATRIX,
    B: Sequence[Sequence[float]] = B_MATRIX,
    C: Sequence[Sequence[float]] = C_MATRIX,
    D: Sequence[Sequence[float]] = D_MATRIX,
    feedback: bool = False,
) -> None:
    super().__init__(label, visual=visual, variables=variables)
    self.A = A
    self.B = B
    self.C = C
    self.D = D
    self.feedback = feedback

get_inputs(s)

Return the inputs u of the economy, the policies, or D^-1 (y - C z) with feedback.

Parameters:

Name Type Description Default
s SimpleNamespace

The namespace of the economy.

required

Returns:

Name Type Description
inputs list[Expression]

One input per sector.

Source code in src/skcomplex/systems/economics/macroeconomics/_purchasing_power.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def get_inputs(self, s: SimpleNamespace) -> list[Expression]:
    """Return the inputs `u` of the economy, the policies, or `D^-1 (y - C z)` with feedback.

    Args:
        s:
            The namespace of the economy.

    Returns:
        inputs:
            One input per sector.
    """
    if not self.feedback:
        return self._policies(s)
    C, inverse = np.asarray(self.C), np.linalg.inv(np.asarray(self.D, dtype=float))
    z, y = [getattr(s, f'z{i}') for i in range(1, SECTORS + 1)], self.get_outputs(s)
    gaps = [y[i] - sum(C[i, k] * z[k] for k in range(SECTORS)) for i in range(SECTORS)]
    return [sum(inverse[i, k] * gaps[k] for k in range(SECTORS)) for i in range(SECTORS)]

get_outputs(s)

Return the indices y of the sectors.

Parameters:

Name Type Description Default
s SimpleNamespace

The namespace of the economy.

required

Returns:

Name Type Description
outputs list[Expression]

One index per sector.

Source code in src/skcomplex/systems/economics/macroeconomics/_purchasing_power.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def get_outputs(self, s: SimpleNamespace) -> list[Expression]:
    """Return the indices `y` of the sectors.

    Args:
        s:
            The namespace of the economy.

    Returns:
        outputs:
            One index per sector.
    """
    x, C, D = self._policies(s), np.asarray(self.C), np.asarray(self.D)
    if not self.feedback:
        z = [getattr(s, f'z{i}') for i in range(1, SECTORS + 1)]
        return [sum(C[i, k] * z[k] + D[i, k] * x[k] for k in range(SECTORS)) for i in range(SECTORS)]
    return [
        sum(C[i, k] * getattr(s, f'w{i + 1}{k + 1}') for k in range(SECTORS)) + D[i, i] * x[i]
        for i in range(SECTORS)
    ]

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.systems.economics.macroeconomics import RamseyEconomyElement
>>> RamseyEconomyElement('economy', k=2.0).k
2.0
Source code in src/skcomplex/systems/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.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import ReserveBankElement
>>> GroupElement('banks', element=ReserveBankElement, size=3, values={'reserves': 10.0}).size
3
Source code in src/skcomplex/systems/economics/macroeconomics/_banking.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
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, system)

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
system SystemView

The system.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_banking.py
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
def lend(self, agent: ReserveBankView, system: SystemView) -> None:
    """Lend the excess reserves to a random customer, whose payment is redeposited at a random customer's bank.

    Args:
        agent:
            The bank.

        system:
            The system.
    """
    excess = agent.reserves - system.parameter('reserve_ratio') * agent.deposits
    customers = system.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(system.rng.integers(len(own)))]
    payee = customers[int(system.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.components.elements import GroupElement
>>> from skcomplex.systems.economics.macroeconomics import SettlerElement
>>> GroupElement('people', element=SettlerElement, size=3, variable=True).size
3
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
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, system)

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

Parameters:

Name Type Description Default
agent SettlerView

The person.

required
system SystemView

The system.

required
Source code in src/skcomplex/systems/economics/macroeconomics/_metaphoria.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
def live(self, agent: SettlerView, system: SystemView) -> None:
    """Age, eat, die, have children, and move or work.

    Args:
        agent:
            The person.

        system:
            The system.
    """
    agent.age += 1
    adult = agent.age >= agent.maturity
    if not adult:
        self._feed_child(agent, system)
    agent.food -= agent.need
    if agent.food < 0:
        agent.fate = STARVED
        return
    if agent.age > agent.lifespan and system.rng.random() < OLD_AGE_DEATH:
        agent.fate = AGED
        return
    if not adult:
        return
    self._breed(agent, system)
    employed = agent.monetary > 0 and self._work_or_found(agent, system)
    if not employed and agent.harvested < agent.need and agent.food < agent.reserve * agent.need:
        SettlerElement._wander(agent, system, 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.systems.economics.macroeconomics import SolowGrowthInteraction
>>> SolowGrowthInteraction('solow', target='country', s=0.3).s
0.3
Source code in src/skcomplex/systems/economics/macroeconomics/_growth.py
127
128
129
130
131
132
133
134
135
136
137
138
139
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

StochasticGrowthElement(label, visual=None, *, K=0.2, regime=0, alpha=1 / 3, beta=0.95, z_low=0.9, z_high=1.1)

Bases: Element

Representative economy with capital, a productivity regime, and a Cobb-Douglas technology Y = z K^alpha.

The productivity z is z_low in regime 0 and z_high in regime 1, and a Markov chain of the system moves the regime. With logarithmic utility and full depreciation, the optimal consumption is C = (1 - alpha beta) Y and the capital of the next period is the output saved, K' = alpha beta z K^alpha.

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 capital.

0.2
regime Annotated[float, StateSpec(domain=NN)]

The productivity regime, 0 for low and 1 for high.

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

The capital share of the technology.

1 / 3
beta Annotated[float, ParameterSpec(domain=open(0, 1))]

The discount factor of the household.

0.95
z_low Annotated[float, ParameterSpec(domain=open(0, oo))]

The productivity of regime 0.

0.9
z_high Annotated[float, ParameterSpec(domain=open(0, oo))]

The productivity of regime 1.

1.1

Examples:

>>> from skcomplex.systems.economics.macroeconomics import StochasticGrowthElement
>>> StochasticGrowthElement('economy', beta=0.9).beta
0.9
Source code in src/skcomplex/systems/economics/macroeconomics/_stochastic_growth.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    K: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 0.2,
    regime: Annotated[float, StateSpec(domain=NN)] = 0,
    alpha: Annotated[float, ParameterSpec(domain=RealSet.open(0, 1))] = 1 / 3,
    beta: Annotated[float, ParameterSpec(domain=RealSet.open(0, 1))] = 0.95,
    z_low: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.9,
    z_high: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.1,
) -> None:
    super().__init__(label, visual=visual)
    self.K = K
    self.regime = regime
    self.alpha = alpha
    self.beta = beta
    self.z_low = z_low
    self.z_high = z_high

get_consumption(s) staticmethod

Return the optimal consumption (1 - alpha beta) Y.

Parameters:

Name Type Description Default
s SimpleNamespace

The namespace of the economy.

required

Returns:

Name Type Description
consumption Expression

The consumption.

Source code in src/skcomplex/systems/economics/macroeconomics/_stochastic_growth.py
105
106
107
108
109
110
111
112
113
114
115
116
117
@staticmethod
def get_consumption(s: SimpleNamespace) -> Expression:
    """Return the optimal consumption `(1 - alpha beta) Y`.

    Args:
        s:
            The namespace of the economy.

    Returns:
        consumption:
            The consumption.
    """
    return (1 - s.alpha * s.beta) * StochasticGrowthElement.get_output(s)

get_output(s) staticmethod

Return the output z K^alpha.

Parameters:

Name Type Description Default
s SimpleNamespace

The namespace of the economy.

required

Returns:

Name Type Description
output Expression

The output.

Source code in src/skcomplex/systems/economics/macroeconomics/_stochastic_growth.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
@staticmethod
def get_output(s: SimpleNamespace) -> Expression:
    """Return the output `z K^alpha`.

    Args:
        s:
            The namespace of the economy.

    Returns:
        output:
            The output.
    """
    return StochasticGrowthElement.get_productivity(s) * s.K**s.alpha

get_productivity(s) staticmethod

Return the productivity of the current regime.

Parameters:

Name Type Description Default
s SimpleNamespace

The namespace of the economy.

required

Returns:

Name Type Description
z Expression

The productivity.

Source code in src/skcomplex/systems/economics/macroeconomics/_stochastic_growth.py
77
78
79
80
81
82
83
84
85
86
87
88
89
@staticmethod
def get_productivity(s: SimpleNamespace) -> Expression:
    """Return the productivity of the current regime.

    Args:
        s:
            The namespace of the economy.

    Returns:
        z:
            The productivity.
    """
    return s.z_low + (s.z_high - s.z_low) * s.regime

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.systems.economics.macroeconomics import TradingEconomyElement
>>> TradingEconomyElement('home').floating
1.0
Source code in src/skcomplex/systems/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

brock_mirman_growth(K=0.2, *, alpha=1 / 3, beta=0.95, z_low=0.9, z_high=1.1, persistence=0.9, regime=0)

Return the Brock-Mirman economy, the optimal growth of a representative agent whose productivity is Markov.

A representative consumer maximises E_0 sum_t beta^t ln C_t subject to K_{t+1} = Y_t - C_t, with full depreciation and the output Y_t = z_t K_t^alpha of a representative firm, whose productivity z_t follows a Markov chain. The solution is the linear consumption rule C_t = (1 - alpha beta) Y_t, C = 0.68 Y for alpha = 1/3 and beta = 0.95, so the capital follows K_{t+1} = alpha beta z_t K_t^alpha and ln K is a stationary autoregression around (ln(alpha beta) + E ln z) / (1 - alpha). This is the standard model that Caiani et al. (2016, Section 1.1.4, Eqs. 1.3 to 1.9) compare with an agent-based economy. The source leaves the chain open, so the productivity takes two levels that stay with the probability persistence.

Parameters:

Name Type Description Default
K float

The initial capital.

0.2
alpha float

The capital share of the technology.

1 / 3
beta float

The discount factor of the household.

0.95
z_low float

The low productivity.

0.9
z_high float

The high productivity.

1.1
persistence float

The probability that the productivity keeps its level from one period to the next.

0.9
regime int

The initial regime, 0 for the low and 1 for the high productivity.

0

Returns:

Name Type Description
system System

The system, with the capital economy.K and the regime economy.regime. The observables of the system are economy.productivity, economy.output, and economy.consumption.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import brock_mirman_growth
>>> brock_mirman_growth(beta=0.9).elements[0].beta
0.9
Source code in src/skcomplex/systems/economics/macroeconomics/_stochastic_growth.py
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
@register_system(
    field='economics/macroeconomics',
    tags=('optimal growth', 'representative agent', 'markov chain', 'productivity shocks', 'discrete map'),
    references=('Caiani et al. 2016 §1.1.4', 'Brock and Mirman 1972'),
    defaults={'until': 200},
    observables=lambda system: [
        ObservableSpec(name, function, element='economy')
        for name, function in (
            ('productivity', StochasticGrowthElement.get_productivity),
            ('output', StochasticGrowthElement.get_output),
            ('consumption', StochasticGrowthElement.get_consumption),
        )
    ],
)
def brock_mirman_growth(
    K: float = 0.2,
    *,
    alpha: float = 1 / 3,
    beta: float = 0.95,
    z_low: float = 0.9,
    z_high: float = 1.1,
    persistence: float = 0.9,
    regime: int = 0,
) -> System:
    """Return the Brock-Mirman economy, the optimal growth of a representative agent whose productivity is Markov.

    A representative consumer maximises `E_0 sum_t beta^t ln C_t` subject to `K_{t+1} = Y_t - C_t`, with full
    depreciation and the output `Y_t = z_t K_t^alpha` of a representative firm, whose productivity `z_t` follows a
    Markov chain. The solution is the linear consumption rule `C_t = (1 - alpha beta) Y_t`, `C = 0.68 Y` for
    `alpha = 1/3` and `beta = 0.95`, so the capital follows `K_{t+1} = alpha beta z_t K_t^alpha` and `ln K` is a
    stationary autoregression around `(ln(alpha beta) + E ln z) / (1 - alpha)`. This is the standard model that Caiani
    et al. (2016, Section 1.1.4, Eqs. 1.3 to 1.9) compare with an agent-based economy. The source leaves the chain open,
    so the productivity takes two levels that stay with the probability `persistence`.

    Args:
        K:
            The initial capital.

        alpha:
            The capital share of the technology.

        beta:
            The discount factor of the household.

        z_low:
            The low productivity.

        z_high:
            The high productivity.

        persistence:
            The probability that the productivity keeps its level from one period to the next.

        regime:
            The initial regime, 0 for the low and 1 for the high productivity.

    Returns:
        system:
            The system, with the capital `economy.K` and the regime `economy.regime`. The observables of the system are
            `economy.productivity`, `economy.output`, and `economy.consumption`.

    Examples:
        >>> from skcomplex.systems.economics.macroeconomics import brock_mirman_growth
        >>> brock_mirman_growth(beta=0.9).elements[0].beta
        0.9
    """
    chain = ((persistence, 1 - persistence), (1 - persistence, persistence))
    return System(
        elements=[
            StochasticGrowthElement(
                'economy',
                K=K,
                regime=regime,
                alpha=alpha,
                beta=beta,
                z_low=z_low,
                z_high=z_high,
            ),
        ],
        interactions=[
            MapInteraction(
                'saving',
                target='economy',
                state='K',
                function=lambda s: s.target.alpha * s.target.beta * StochasticGrowthElement.get_output(s.target),
            ),
            TransitionInteraction('productivity', target='economy', state='regime', matrix=lambda s: chain),
        ],
    )

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 system, with the capital stock economy.K.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import capital_stock
>>> capital_stock(lifetime=20.0).elements[0].lifetime
20.0
Source code in src/skcomplex/systems/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_system(
    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 system, with the capital stock `economy.K`.

    Examples:
        >>> from skcomplex.systems.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)],
    )

categorized_purchasing_power(policy=(0.0, 0.0, 1.0), policy_time=(1.0, 1.0, 1.0), *, feedback=True, A=A_MATRIX, B=B_MATRIX, C=C_MATRIX, D=D_MATRIX)

Return Forrest's categorized purchasing power of money, three sectors that feedback can steer one by one.

The economy is split into the sectors of the basic, the desired, and the luxurious living conditions, with the gaps z_i = D_i - S_i between the demand and the supply of money of each, which the categorized monetary and fiscal policies x_i move through z' = A z + B x, and the indices of the sectors y = C z + D x, from the zero state (Forrest 2018, Eqs. 22.6 and 22.7). The policies of one sector act on all of them. By Theorem 11.3 and Example 11.2 the system can be decoupled by feedback into the systems w' = A w + B_i x_i and y_i = C_i w + D_i x_i of the sectors (Eqs. 22.9 to 22.12), whose market reactions keep each index on its own policy, so money poured into the luxury sector leaves the basic sector alone. The source gives no numbers, so the matrices are illustrative, with a stable A and a stable A - B D^-1 C, and the policies are steps.

Parameters:

Name Type Description Default
policy Sequence[float]

The size of the step policy of every sector.

(0.0, 0.0, 1.0)
policy_time Sequence[float]

The time of the step policy of every sector.

(1.0, 1.0, 1.0)
feedback bool

Whether the feedback component decouples the sectors.

True
A Sequence[Sequence[float]]

The matrix of the state.

A_MATRIX
B Sequence[Sequence[float]]

The matrix of the policies in the state.

B_MATRIX
C Sequence[Sequence[float]]

The matrix of the state in the indices.

C_MATRIX
D Sequence[Sequence[float]]

The nonsingular matrix of the policies in the indices.

D_MATRIX

Returns:

Name Type Description
system System

The system, with the gaps economy.z1 to economy.z3 and, with feedback, the decoupled states economy.w11 to economy.w33. The observables of the system are the indices economy.y1 to economy.y3 and the inputs of the economy economy.u1 to economy.u3.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import categorized_purchasing_power
>>> categorized_purchasing_power(feedback=False).elements[0].feedback
False
Source code in src/skcomplex/systems/economics/macroeconomics/_purchasing_power.py
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
258
259
260
261
262
263
264
265
@register_system(
    field='economics/macroeconomics',
    tags=('purchasing power', 'monetary policy', 'fiscal policy', 'decoupling', 'feedback', 'linear system', 'ode'),
    references=('Forrest 2018',),
    defaults={'until': 15.0, 'dt': 0.05},
    observables=_observables,
)
def categorized_purchasing_power(
    policy: Sequence[float] = (0.0, 0.0, 1.0),
    policy_time: Sequence[float] = (1.0, 1.0, 1.0),
    *,
    feedback: bool = True,
    A: Sequence[Sequence[float]] = A_MATRIX,
    B: Sequence[Sequence[float]] = B_MATRIX,
    C: Sequence[Sequence[float]] = C_MATRIX,
    D: Sequence[Sequence[float]] = D_MATRIX,
) -> System:
    """Return Forrest's categorized purchasing power of money, three sectors that feedback can steer one by one.

    The economy is split into the sectors of the basic, the desired, and the luxurious living conditions, with the
    gaps `z_i = D_i - S_i` between the demand and the supply of money of each, which the categorized monetary and
    fiscal policies `x_i` move through `z' = A z + B x`, and the indices of the sectors `y = C z + D x`, from the zero
    state (Forrest 2018, Eqs. 22.6 and 22.7). The policies of one sector act on all of them. By Theorem 11.3 and Example
    11.2 the system can be decoupled by feedback into the systems `w' = A w + B_i x_i` and `y_i = C_i w + D_i x_i` of
    the sectors (Eqs. 22.9 to 22.12), whose market reactions keep each index on its own policy, so money poured into the
    luxury sector leaves the basic sector alone. The source gives no numbers, so the matrices are illustrative, with a
    stable `A` and a stable `A - B D^-1 C`, and the policies are steps.

    Args:
        policy:
            The size of the step policy of every sector.

        policy_time:
            The time of the step policy of every sector.

        feedback:
            Whether the feedback component decouples the sectors.

        A:
            The matrix of the state.

        B:
            The matrix of the policies in the state.

        C:
            The matrix of the state in the indices.

        D:
            The nonsingular matrix of the policies in the indices.

    Returns:
        system:
            The system, with the gaps `economy.z1` to `economy.z3` and, with feedback, the decoupled states
            `economy.w11` to `economy.w33`. The observables of the system are the indices `economy.y1` to
            `economy.y3` and the inputs of the economy `economy.u1` to `economy.u3`.

    Examples:
        >>> from skcomplex.systems.economics.macroeconomics import categorized_purchasing_power
        >>> categorized_purchasing_power(feedback=False).elements[0].feedback
        False
    """
    variables = {
        name: DeclaredVariableSpec(float(value), ParameterSpec())
        for i in range(SECTORS)
        for name, value in ((f'policy{i + 1}', policy[i]), (f'policy_time{i + 1}', policy_time[i]))
    }
    economy = PurchasingPowerElement('economy', variables=variables, A=A, B=B, C=C, D=D, feedback=feedback)
    return System(elements=[economy])

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 system, 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.systems.economics.macroeconomics import credit_networks
>>> [element.label for element in credit_networks().elements]
['firms', 'banks']
Source code in src/skcomplex/systems/economics/macroeconomics/_credit.py
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
@register_system(
    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 system, 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.systems.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 system, 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.systems.economics.macroeconomics import exchange_rates
>>> round(exchange_rates().elements[0].E, 3)
0.885
Source code in src/skcomplex/systems/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_system(
    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 system, 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.systems.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 system, with the groups customers and banks, the institution regulator, and the observables broad_money, reserves, and loans.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import fractional_reserve_banking
>>> fractional_reserve_banking(new_ratio=0.2).interactions[0].new_ratio
0.2
Source code in src/skcomplex/systems/economics/macroeconomics/_banking.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
@register_system(
    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 system, with the groups `customers` and `banks`, the institution `regulator`, and the observables
            `broad_money`, `reserves`, and `loans`.

    Examples:
        >>> from skcomplex.systems.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, system: DepositorElement.get_repayment(customer, same, system) > 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)]),
        ),
    )

mark0(n_firms=500, *, c0=0.5, beta=2.0, gamma=0.01, eta_minus=0.2, ratio=2.0, delta=0.02, theta=3.0, phi=0.1, shock_time=100.0, shock_length=0.0, dc=0.0, dzeta=0.0, kappa=1.0, credit='none', random_state=0)

Return the Mark-0 economy of Sharma, Bouchaud, and co-authors, with a Covid shock to supply and demand.

Firms produce perishable goods with a linear technology, sell them to a representative household at prices they adjust by rules of thumb, hire and fire by the gap between production and demand, and borrow from a representative bank, defaulting when their debt to payroll exceeds the threshold Theta (Sharma et al. 2020, Section 2). A month is a step, the money starts at zero, and the economy is stock-flow consistent apart from the written-off debts and the helicopter money. With the defaults of Table 1 the economy sits in the full employment phase. A lockdown lowers the productivity and the consumption propensity for shock_length months, after which a mild shock recovers in a V shape and a strong one tips the economy into an L-shaped recession of high unemployment, which easy credit and helicopter money can prevent. The source summarizes the model of earlier papers, so the initial firms (a workforce between 0.9 and 1, prices around 1, unit wages, no cash) and revived firms (the mean price and wage and a random share of the unemployed) follow Gualdi et al. (2015), and the demand reads prices relative to the mean price.

Parameters:

Name Type Description Default
n_firms int

The number of firms, equal to the number of households.

500
c0 float

The consumption propensity of the household.

0.5
beta float

The intensity of choice of the demand and of the workers.

2.0
gamma float

The relative size of the adjustments of prices and wages.

0.01
eta_minus float

The firing propensity.

0.2
ratio float

The hiring to firing ratio.

2.0
delta float

The share of their cash that profitable firms pay as dividends.

0.02
theta float

The bankruptcy threshold of the debt to payroll ratio.

3.0
phi float

The probability per month that a bankrupt firm is revived.

0.1
shock_time float

The month the shock starts.

100.0
shock_length float

The months the shock lasts, 0 for no shock.

0.0
dc float

The relative fall of the consumption propensity during the shock.

0.0
dzeta float

The relative fall of the productivity during the shock.

0.0
kappa float

The factor of the household's savings at the end of the shock, 1 for no helicopter money.

1.0
credit str

The credit policy, 'none', 'naive', or 'adaptive'.

'none'
random_state int | Generator | None

The seed or generator of the initial firms.

0

Returns:

Name Type Description
system System

The system, with the group firms and the economy economy, whose reporters are economy.unemployment, economy.inflation, economy.output, economy.savings, economy.fragility, economy.bankruptcies, economy.threshold, economy.price, and economy.wage.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import mark0
>>> mark0(10).elements[0].prepare().size_
10
Source code in src/skcomplex/systems/economics/macroeconomics/_mark0.py
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
@register_system(
    field='economics/macroeconomics',
    tags=('agents', 'macroeconomics', 'unemployment', 'crises', 'covid', 'policy', 'firms', 'stock-flow consistent'),
    references=('Sharma et al. 2020', 'Gualdi et al. 2015'),
    defaults={'until': 300},
)
def mark0(
    n_firms: int = 500,
    *,
    c0: float = 0.5,
    beta: float = 2.0,
    gamma: float = 0.01,
    eta_minus: float = 0.2,
    ratio: float = 2.0,
    delta: float = 0.02,
    theta: float = 3.0,
    phi: float = 0.1,
    shock_time: float = 100.0,
    shock_length: float = 0.0,
    dc: float = 0.0,
    dzeta: float = 0.0,
    kappa: float = 1.0,
    credit: str = 'none',
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return the Mark-0 economy of Sharma, Bouchaud, and co-authors, with a Covid shock to supply and demand.

    Firms produce perishable goods with a linear technology, sell them to a representative household at prices they
    adjust by rules of thumb, hire and fire by the gap between production and demand, and borrow from a representative
    bank, defaulting when their debt to payroll exceeds the threshold `Theta` (Sharma et al. 2020, Section 2). A month
    is a step, the money starts at zero, and the economy is stock-flow consistent apart from the written-off debts and
    the helicopter money. With the defaults of Table 1 the economy sits in the full employment phase. A lockdown lowers
    the productivity and the consumption propensity for `shock_length` months, after which a mild shock recovers in a
    V shape and a strong one tips the economy into an L-shaped recession of high unemployment, which easy credit and
    helicopter money can prevent. The source summarizes the model of earlier papers, so the initial firms (a workforce
    between 0.9 and 1, prices around 1, unit wages, no cash) and revived firms (the mean price and wage and a random
    share of the unemployed) follow Gualdi et al. (2015), and the demand reads prices relative to the mean price.

    Args:
        n_firms:
            The number of firms, equal to the number of households.

        c0:
            The consumption propensity of the household.

        beta:
            The intensity of choice of the demand and of the workers.

        gamma:
            The relative size of the adjustments of prices and wages.

        eta_minus:
            The firing propensity.

        ratio:
            The hiring to firing ratio.

        delta:
            The share of their cash that profitable firms pay as dividends.

        theta:
            The bankruptcy threshold of the debt to payroll ratio.

        phi:
            The probability per month that a bankrupt firm is revived.

        shock_time:
            The month the shock starts.

        shock_length:
            The months the shock lasts, 0 for no shock.

        dc:
            The relative fall of the consumption propensity during the shock.

        dzeta:
            The relative fall of the productivity during the shock.

        kappa:
            The factor of the household's savings at the end of the shock, 1 for no helicopter money.

        credit:
            The credit policy, `'none'`, `'naive'`, or `'adaptive'`.

        random_state:
            The seed or generator of the initial firms.

    Returns:
        system:
            The system, with the group `firms` and the economy `economy`, whose reporters are `economy.unemployment`,
            `economy.inflation`, `economy.output`, `economy.savings`, `economy.fragility`, `economy.bankruptcies`,
            `economy.threshold`, `economy.price`, and `economy.wage`.

    Examples:
        >>> from skcomplex.systems.economics.macroeconomics import mark0
        >>> mark0(10).elements[0].prepare().size_
        10
    """
    rng = np.random.default_rng(random_state)
    firms = GroupElement(
        'firms',
        element=Mark0FirmElement,
        size=n_firms,
        values={
            'workforce': rng.uniform(0.9, 1.0, n_firms),
            'price': 1 + 0.1 * (rng.random(n_firms) - 0.5),
            'wage': 1.0,
        },
    )
    economy = Mark0EconomyInteraction(
        'economy',
        firms='firms',
        c0=c0,
        beta=beta,
        gamma=gamma,
        eta_minus=eta_minus,
        ratio=ratio,
        delta=delta,
        theta=theta,
        phi=phi,
        shock_time=shock_time,
        shock_length=shock_length,
        dc=dc,
        dzeta=dzeta,
        kappa=kappa,
        credit=credit,
    )
    return System(elements=[firms], interactions=[economy])

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 system, 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.systems.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/systems/economics/macroeconomics/_metaphoria.py
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
@register_system(
    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 system, 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.systems.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, system: CentralBankElement.get_income(agent, holder, system) > 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 system, with the income economy.Y and its previous value economy.Y_lag1.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import multiplier_accelerator
>>> multiplier_accelerator(v=0.5).elements[0].v
0.5
Source code in src/skcomplex/systems/economics/macroeconomics/_growth.py
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
@register_system(
    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 system, with the income `economy.Y` and its previous value `economy.Y_lag1`.

    Examples:
        >>> from skcomplex.systems.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)],
    )

network_herding(theta=None, x=None, *, gamma=1.0, sigma=0.1, n_agents=50, density=0.1, herding=0.5, random_state=0)

Return agents whose linear choices herd on the past choices of the agents they are linked to.

The choice of agent i is c_it = sum_j theta_ij c_j,t-1 + gamma x_it, so the choices follow c_t = Theta c_t-1 + gamma x_t on the weighted network Theta, without self-links, Eqs. 1.10 and 1.11 of Caiani et al. (2016, Section 1.1.6). The individual characteristic is x_it = x_i + sigma eps_it, with a standard normal shock eps_it. The mean choice follows c_t = (1/N) sum_i d_i c_i,t-1 + gamma x_t (Eq. 1.12), where the out-degree d_i of agent i, the sum of column i of Theta, measures how much its choice moves the others, so the persistence of the aggregate depends on the out-degrees and on how they correlate with the choices. Without shocks the choices settle at (I - Theta)^-1 gamma x when the spectral radius of Theta is below 1.

Parameters:

Name Type Description Default
theta Sequence[Sequence[float]] | None

The weights, theta[i][j] the weight of the past choice of j in the choice of i, or None for a random network of n_agents agents.

None
x Sequence[float] | None

The means of the individual characteristics, or None for standard lognormal draws.

None
gamma float

The weight of the individual characteristic.

1.0
sigma float

The standard deviation of the shocks of the individual characteristics.

0.1
n_agents int

The number of agents of a random network.

50
density float

The probability of a link from one agent to another in a random network.

0.1
herding float

The sum of the weights of every agent with links in a random network, below 1 for stable choices.

0.5
random_state int | Generator | None

The seed or generator of the random network and characteristics.

0

Returns:

Name Type Description
system System

The system, with the group agents of the choices agents.c and the herding herding. The observable of the system is the mean choice mean_choice.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import network_herding
>>> network_herding([[0.0, 0.5], [0.5, 0.0]], [1.0, 1.0]).elements[0].prepare().size_
2
Source code in src/skcomplex/systems/economics/macroeconomics/_herding.py
 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
@register_system(
    field='economics/macroeconomics',
    tags=('herding', 'network', 'aggregation', 'agents', 'linear map'),
    references=('Caiani et al. 2016 §1.1.6',),
    defaults={'until': 50},
    observables=lambda system: [ObservableSpec('mean_choice', group='agents', variable='c', reduce='mean')],
)
def network_herding(
    theta: Sequence[Sequence[float]] | None = None,
    x: Sequence[float] | None = None,
    *,
    gamma: float = 1.0,
    sigma: float = 0.1,
    n_agents: int = 50,
    density: float = 0.1,
    herding: float = 0.5,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return agents whose linear choices herd on the past choices of the agents they are linked to.

    The choice of agent `i` is `c_it = sum_j theta_ij c_j,t-1 + gamma x_it`, so the choices follow
    `c_t = Theta c_t-1 + gamma x_t` on the weighted network `Theta`, without self-links, Eqs. 1.10 and 1.11 of Caiani
    et al. (2016, Section 1.1.6). The individual characteristic is `x_it = x_i + sigma eps_it`, with a standard normal
    shock `eps_it`. The mean choice follows `c_t = (1/N) sum_i d_i c_i,t-1 + gamma x_t` (Eq. 1.12), where the out-degree
    `d_i` of agent `i`, the sum of column `i` of `Theta`, measures how much its choice moves the others, so the
    persistence of the aggregate depends on the out-degrees and on how they correlate with the choices. Without
    shocks the choices settle at `(I - Theta)^-1 gamma x` when the spectral radius of `Theta` is below 1.

    Args:
        theta:
            The weights, `theta[i][j]` the weight of the past choice of `j` in the choice of `i`, or `None` for a random
            network of `n_agents` agents.

        x:
            The means of the individual characteristics, or `None` for standard lognormal draws.

        gamma:
            The weight of the individual characteristic.

        sigma:
            The standard deviation of the shocks of the individual characteristics.

        n_agents:
            The number of agents of a random network.

        density:
            The probability of a link from one agent to another in a random network.

        herding:
            The sum of the weights of every agent with links in a random network, below 1 for stable choices.

        random_state:
            The seed or generator of the random network and characteristics.

    Returns:
        system:
            The system, with the group `agents` of the choices `agents.c` and the herding `herding`. The observable of
            the system is the mean choice `mean_choice`.

    Examples:
        >>> from skcomplex.systems.economics.macroeconomics import network_herding
        >>> network_herding([[0.0, 0.5], [0.5, 0.0]], [1.0, 1.0]).elements[0].prepare().size_
        2
    """
    rng = np.random.default_rng(random_state)
    weights = _random_links(n_agents, density, herding, rng) if theta is None else np.asarray(theta, dtype=float)
    size = len(weights)
    means = rng.lognormal(0.0, 1.0, size) if x is None else np.asarray(x, dtype=float)
    agents = GroupElement(
        'agents',
        element=HerdingAgentElement,
        size=size,
        values={'c': gamma * means, 'x': means, 'gamma': gamma, 'sigma': sigma},
    )
    herding_map = CouplingInteraction(
        'herding',
        target='agents',
        read='c',
        write='c',
        function=lambda s: s.other.c,
        reduce='sum',
        mode='group',
        weights=weights.tolist(),
        form='map',
        combine=_choice,
    )
    return System(elements=[agents], interactions=[herding_map])

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 system, with the capital economy.k, the consumption economy.c, and the observable saving_rate.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import optimal_consumption
>>> optimal_consumption(rho=0.05).elements[0].rho
0.05
Source code in src/skcomplex/systems/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_system(
    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 system, with the capital `economy.k`, the consumption `economy.c`, and the observable `saving_rate`.

    Examples:
        >>> from skcomplex.systems.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 system, with the economy economy.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import solow
>>> solow(s=0.3).interactions[0].s
0.3
Source code in src/skcomplex/systems/economics/macroeconomics/_growth.py
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
@register_system(
    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 system, with the economy `economy`.

    Examples:
        >>> from skcomplex.systems.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 systems 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 system, 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.systems.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/systems/economics/macroeconomics/_monetary.py
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
@register_system(
    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 systems 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 system, 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.systems.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 system 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 system, with the group firms and the aggregate production production.

Examples:

>>> from skcomplex.systems.economics.macroeconomics import toy_firm_investment
>>> toy_firm_investment(20).elements[0].prepare().size_
20
Source code in src/skcomplex/systems/economics/macroeconomics/_firms.py
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
@register_system(
    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 system 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 system, with the group `firms` and the aggregate production `production`.

    Examples:
        >>> from skcomplex.systems.economics.macroeconomics import toy_firm_investment
        >>> toy_firm_investment(20).elements[0].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())