Skip to content

Resources

Natural resources: harvesting, extracting, and cultivating renewable and nonrenewable stocks.

BidRentInteraction(label, *, target, yields, prices, costs, rates)

Bases: Interaction

Land use of highest bid rent, where crop i bids Y_i (p_i - c_i) - Y_i f_i d at the distance d of a market.

A cell grows the crop of the highest positive bid, and lies idle as crop 0 when no bid is positive.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the field of cells, whose members hold crop and distance.

required
yields Sequence[float]

The yield Y_i of every crop per cell.

required
prices Sequence[float]

The price p_i of every crop at the market.

required
costs Sequence[float]

The cost c_i of every crop per unit of yield.

required
rates Sequence[float]

The transport rate f_i of every crop per unit of yield and of distance.

required

Raises:

Type Description
ValidationError

The four sequences do not have one value per crop, raised by check.

Examples:

>>> from skcomplex.models.economics.resources import BidRentInteraction
>>> bid = BidRentInteraction('bid', target='plain', yields=(2.0,), prices=(3.0,), costs=(1.0,), rates=(0.1,))
>>> bid.check().rates
(0.1,)
Source code in src/skcomplex/models/economics/resources/_bid_rent.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    yields: Sequence[float],
    prices: Sequence[float],
    costs: Sequence[float],
    rates: Sequence[float],
) -> None:
    super().__init__(label)
    self.target = target
    self.yields = yields
    self.prices = prices
    self.costs = costs
    self.rates = rates

CowElement(label, visual=VisualSpec('agents', {'color_by': 'energy'}), *, owner=-1.0, energy=4.0)

Bases: Element

Cow of a herder that gains the energy of the grass it eats, burns its metabolism, and starves without food.

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

VisualSpec('agents', {'color_by': 'energy'})
owner Annotated[float, StateSpec(reference=herders)]

The id of the herder that owns the cow, given initially as its position in the group herders.

-1.0
energy Annotated[float, StateSpec()]

The energy of the cow.

4.0

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.resources import CowElement
>>> GroupElement('cows', element=CowElement, size=3, values={'energy': 4.0}).size
3
Source code in src/skcomplex/models/economics/resources/_cow.py
41
42
43
44
45
46
47
48
49
50
51
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'energy'}),
    *,
    owner: Annotated[float, StateSpec(reference='herders')] = -1.0,
    energy: Annotated[float, StateSpec()] = 4.0,
) -> None:
    super().__init__(label, visual=visual)
    self.owner = owner
    self.energy = energy

FarmerElement(label, visual=None, *, wealth=0.0, resource='land/stock.n', effort='harvest.effort')

Bases: Element

Farmer who adds the catch of every step to its wealth, reading the resource from the other partition.

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

The initial wealth.

0.0
resource str

The dotted name of the resource stock.

'land/stock.n'
effort str

The dotted name of the harvest effort per farmer.

'harvest.effort'

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.resources import FarmerElement
>>> GroupElement('farmers', element=FarmerElement, size=10).size
10
Source code in src/skcomplex/models/economics/resources/_farmer.py
47
48
49
50
51
52
53
54
55
56
57
58
59
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    wealth: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    resource: str = 'land/stock.n',
    effort: str = 'harvest.effort',
) -> None:
    super().__init__(label, visual=visual)
    self.wealth = wealth
    self.resource = resource
    self.effort = effort

catch(agent, model)

Add the effort times the resource stock at the last synchronisation point to the wealth.

Parameters:

Name Type Description Default
agent FarmerView

The farmer.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/economics/resources/_farmer.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def catch(self, agent: FarmerView, model: ModelView) -> None:
    """Add the effort times the resource stock at the last synchronisation point to the wealth.

    Args:
        agent:
            The farmer.

        model:
            The model.
    """
    stock = model.inputs.get(self.resource, 0.0)
    if not isinstance(stock, int | float | np.number):
        error_msg = f'The input {self.resource!r} of the farmers is {stock!r}, not a number.'
        raise ValidationError(error_msg)
    agent.wealth += model.parameters[self.effort] * float(stock)

FisheryElement(label, visual=None, *, resource=1000.0, capital=5.0, cost=1.0, growth_goal=0.1, lifetime=20.0, yield_table=FISH_YIELD, regeneration_table=REGENERATION, price_table=PRICE_BY_YIELD)

Bases: Element

Capital that harvests a regenerating resource, with a price that rises as the yield falls.

The resource regenerates at a rate per unit of stock that is a hump-shaped table of the stock, zero when it is empty or full. The harvest is the capital times the yield per unit of capital, a table of the stock. The fishery holds the stocks, the parameters, and the tables, and transfers within it of the model move the resource and the capital.

Parameters:

Name Type Description Default
label str

The label of the fishery.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
resource Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The initial resource stock.

1000.0
capital Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=capital)]

The initial capital.

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

The operating cost per unit of capital.

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

The largest investment per unit of capital.

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

The average lifetime of the capital.

20.0
yield_table Sequence[float]

The yield per unit of capital at the resource levels 0, 100, ..., 1000.

FISH_YIELD
regeneration_table Sequence[float]

The regeneration rate at the resource levels 0, 100, ..., 1000.

REGENERATION
price_table Sequence[float]

The price at the yields 0, 0.1, 0.2, 0.4, 0.6, 0.8, and 1.

PRICE_BY_YIELD

Examples:

>>> from skcomplex.models.economics.resources import FisheryElement
>>> FisheryElement('fishery', lifetime=15.0).lifetime
15.0
Source code in src/skcomplex/models/economics/resources/_fishery.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    resource: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 1000.0,
    capital: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='capital')] = 5.0,
    cost: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    growth_goal: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    lifetime: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 20.0,
    yield_table: Sequence[float] = FISH_YIELD,
    regeneration_table: Sequence[float] = REGENERATION,
    price_table: Sequence[float] = PRICE_BY_YIELD,
) -> None:
    super().__init__(label, visual=visual)
    self.resource = resource
    self.capital = capital
    self.cost = cost
    self.growth_goal = growth_goal
    self.lifetime = lifetime
    self.yield_table = yield_table
    self.regeneration_table = regeneration_table
    self.price_table = price_table

GrazingInteraction(label, *, requesters, providers, rule=RandomSearchSpec(), phase='grazing', herders, appetite=1.0, metabolism=0.5, milk=1.0)

Bases: MatchingInteraction[ModelView]

Grazing of the meadow, a matching of the cows with the pastures, after which every cow eats and burns energy.

The rule of the matching pairs the cows with the pastures, such as PartnerSpec('local', 'score', score=GrazingInteraction.score, clearing='provider'), which pairs every cow, in random order, with the free pasture of most grass in its cell or the cells around it, ties broken at random. A pasture takes one cow, its default quantity of one unit. Every matched cow moves to its pasture and eats up to its appetite as a transfer of grass to its energy, and its herder earns milk for what it ate. Then every cow burns its metabolism and counts in the herd of its herder, and a cow whose energy runs out dies.

Parameters:

Name Type Description Default
label str

The label of the grazing.

required
requesters Annotated[str, RoleSpec()]

The path of the group of cows.

required
providers Annotated[str, RoleSpec()]

The path of the group of pastures.

required
rule MatchingRule

The spec of the algorithm that pairs the cows with the pastures.

RandomSearchSpec()
phase str | None

The phase the grazing runs in.

'grazing'
herders Annotated[str, RoleSpec()]

The path of the group of herders.

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

The most grass a cow eats in a step.

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

The energy a cow burns in a step.

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

The income of a herder per unit of grass its cows eat.

1.0

Examples:

>>> from skcomplex.models.economics.resources import GrazingInteraction
>>> GrazingInteraction('grazing', requesters='cows', providers='meadow', herders='herders').appetite
1.0
Source code in src/skcomplex/models/economics/resources/_grazing.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def __init__(
    self,
    label: str,
    *,
    requesters: Annotated[str, RoleSpec()],
    providers: Annotated[str, RoleSpec()],
    rule: MatchingRule = RandomSearchSpec(),
    phase: str | None = 'grazing',
    herders: Annotated[str, RoleSpec()],
    appetite: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    metabolism: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    milk: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
    super().__init__(label, requesters=requesters, providers=providers, rule=rule, phase=phase)
    self.herders = herders
    self.appetite = appetite
    self.metabolism = metabolism
    self.milk = milk

match(model)

Pair and feed the cows, then let every cow burn its metabolism, count it in its herd, and starve.

Parameters:

Name Type Description Default
model ModelView

The model view of the grazing.

required
Source code in src/skcomplex/models/economics/resources/_grazing.py
136
137
138
139
140
141
142
143
144
145
146
147
148
def match(self, model: ModelView) -> None:
    """Pair and feed the cows, then let every cow burn its metabolism, count it in its herd, and starve.

    Args:
        model:
            The model view of the grazing.
    """
    super().match(model)
    for cow in model.agents('requesters', CowView):
        cow.energy -= model.parameter('metabolism')
        self._get_herder(cow).herd += 1
        if cow.energy < 0:
            cow.remove()

score(cow, pasture) staticmethod

Return the score of a pasture for a cow, the grass on the pasture.

Parameters:

Name Type Description Default
cow object

The agent view of the cow.

required
pasture object

The agent view of the pasture.

required

Returns:

Name Type Description
score float

The grass on the pasture, or minus infinity for an agent that is not a pasture.

Source code in src/skcomplex/models/economics/resources/_grazing.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
@staticmethod
def score(cow: object, pasture: object) -> float:
    """Return the score of a pasture for a cow, the grass on the pasture.

    Args:
        cow:
            The agent view of the cow.

        pasture:
            The agent view of the pasture.

    Returns:
        score:
            The grass on the pasture, or minus infinity for an agent that is not a pasture.
    """
    del cow
    return float(pasture.grass) if isinstance(pasture, PastureView) else -np.inf

HarvestInteraction(label, *, target, harvesters, effort=0.05, state='n')

Bases: RateInteraction

Harvest of a stock proportional to the stock and to the number of harvesters, the rate -effort * N * n.

A rate on the state n of the target whose coefficient counts the members of the group of harvesters, which may live in another subsystem.

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
target Annotated[str, RoleSpec()]

The path of the harvested stock.

required
harvesters Annotated[str, RoleSpec()]

The path of the group of harvesters, which the harvest counts.

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

The harvest rate per harvester and unit of stock.

0.05
state str

The name of the state of the target that holds the stock.

'n'

Examples:

>>> from skcomplex.models.economics.resources import HarvestInteraction
>>> HarvestInteraction('harvest', target='land/stock', harvesters='village/farmers', effort=0.05).effort
0.05
Source code in src/skcomplex/models/economics/resources/_harvest.py
45
46
47
48
49
50
51
52
53
54
55
56
def __init__(
    self,
    label: str,
    *,
    target: Annotated[str, RoleSpec()],
    harvesters: Annotated[str, RoleSpec()],
    effort: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
    state: str = 'n',
) -> None:
    super().__init__(label, target=target, state=state)
    self.harvesters = harvesters
    self.effort = effort

HarvestedResourceElement(label, visual=None, *, x=1.0, effort=0.0, r=1.0, K=1.0, q=1.0, p=1.0, c=0.2, delta=0.1)

Bases: Element

Logistic resource whose harvest effort is a bounded control input, after Clark.

The stock follows x' = r x (1 - x / K) - q E x under the effort E between 0 and its largest value. The harvest earns the price p per unit of catch and costs c per unit of effort, and the owner maximises the discounted profit ∫ exp(-delta t) (p q x - c) E dt. The profit is linear in the effort, so the optimal effort is bang-bang or singular: it takes a bound until the stock reaches the level where the yield net of costs earns the discount rate, holds the stock there, and harvests at the largest effort at the end of the horizon.

Parameters:

Name Type Description Default
label str

The label of the resource.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

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

The initial stock.

1.0
effort Annotated[float, ParameterSpec(adaptive=True, bounds=(0.0, MAX_EFFORT))]

The harvest effort, the control input.

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

The intrinsic growth rate.

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

The carrying capacity.

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

The catchability.

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

The price per unit of catch.

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

The cost per unit of effort.

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

The discount rate.

0.1

Examples:

>>> from skcomplex.models.economics.resources import HarvestedResourceElement
>>> HarvestedResourceElement('resource', x=0.5).x
0.5
Source code in src/skcomplex/models/economics/resources/_optimal_harvesting.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    x: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    effort: Annotated[float, ParameterSpec(adaptive=True, bounds=(0.0, MAX_EFFORT))] = 0.0,
    r: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    K: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    q: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    p: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    c: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
    delta: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
) -> None:
    super().__init__(label, visual=visual)
    self.x = x
    self.effort = effort
    self.r = r
    self.K = K
    self.q = q
    self.p = p
    self.c = c
    self.delta = delta

HerderElement(label, visual=None, *, income=0.0, herd=0.0, upkeep=0.3, newborn=4.0, quota=UNLIMITED)

Bases: Element

Herder who adds a cow whenever a cow earned more than its upkeep, and sells one when the herd exceeds its quota.

The life cycle investment of the model buys a cow, at a random cell, or sells the first cow the herder owns, and two maps clear the income and the herd the grazing tallies, all member interactions in the phase of herding.

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

The milk income of the herder in the last step.

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

The number of cows of the herder in the last step.

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

The cost of keeping a cow for a step.

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

The energy of a new cow.

4.0
quota Annotated[float, ParameterSpec(domain=open(0, oo), adaptive=True)]

The largest herd of a herder, an adaptive parameter that a regulator may change.

UNLIMITED

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.resources import HerderElement
>>> GroupElement('herders', element=HerderElement, size=5).size
5
Source code in src/skcomplex/models/economics/resources/_herder.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    income: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    herd: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    upkeep: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
    newborn: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 4.0,
    quota: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo), adaptive=True)] = UNLIMITED,
) -> None:
    super().__init__(label, visual=visual)
    self.income = income
    self.herd = herd
    self.upkeep = upkeep
    self.newborn = newborn
    self.quota = quota

HilsaElement(label, visual=None, *, eggs=0.0, larvae=0.0, juveniles=0.0, yearlings=0.0, adults=0.0, migrants=0.0, spawners=375.0, female_fraction=FEMALE_FRACTION, eggs_per_nest=200000.0, max_nests=MAX_NESTS, spawning_time=SPAWNING_TIME, hatching_time=1.0, larval_time=2.0, river_time=8.0, ocean_time=12.0, adult_ocean_time=12.0, migration_time=2.0, egg_loss=0.99, larval_loss=0.8, juvenile_loss=0.4, first_year_loss=0.3, second_year_loss=0.5, migration_loss=0.25, juvenile_harvest=0.48, adult_harvest=0.8, weight_per_adult=0.82, weight_per_juvenile=0.09)

Bases: Element

Life cycle of the hilsa, with one stock of fish per stage and the parameters of the stages and the catch.

Parameters:

Name Type Description Default
label str

The label of the element.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
eggs Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The eggs in the nests, in millions of fish.

0.0
larvae Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The larvae in the rivers, in millions of fish.

0.0
juveniles Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The juveniles in the rivers, in millions of fish.

0.0
yearlings Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The fish in their first year in the ocean, in millions of fish.

0.0
adults Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The fish in their second year in the ocean, in millions of fish.

0.0
migrants Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The adults migrating back to the rivers, in millions of fish.

0.0
spawners Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=fish)]

The adults about to spawn, in millions of fish.

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

The fraction of the spawners that are female and build a nest.

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

The number of eggs per nest.

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

The most nests the spawning grounds hold.

MAX_NESTS
spawning_time Annotated[float, ParameterSpec(domain=open(0, oo))]

The duration of the spawning, in months.

SPAWNING_TIME
hatching_time Annotated[float, ParameterSpec(domain=open(0, oo))]

The time the eggs take to hatch, in months.

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

The time the larvae take to become juveniles, in months.

2.0
river_time Annotated[float, ParameterSpec(domain=open(0, oo))]

The time the juveniles stay in the rivers, in months.

8.0
ocean_time Annotated[float, ParameterSpec(domain=open(0, oo))]

The time the fish spend in their first year in the ocean, in months.

12.0
adult_ocean_time Annotated[float, ParameterSpec(domain=open(0, oo))]

The time the fish spend in their second year in the ocean, in months.

12.0
migration_time Annotated[float, ParameterSpec(domain=open(0, oo))]

The time the migration back to the rivers takes, in months.

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

The fraction of the eggs lost to natural mortality.

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

The fraction of the larvae lost to natural mortality.

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

The fraction of the juveniles lost to natural mortality.

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

The fraction of the fish lost to natural mortality in their first year in the ocean.

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

The fraction of the fish lost to natural mortality in their second year in the ocean.

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

The fraction of the adults lost during their migration to the rivers.

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

The fraction of the juveniles leaving the rivers that the fishery catches.

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

The fraction of the adults about to spawn that the fishery catches.

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

The weight of an adult, in kilograms.

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

The weight of a juvenile, in kilograms.

0.09
Source code in src/skcomplex/models/economics/resources/_fisheries.py
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
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    eggs: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 0.0,
    larvae: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 0.0,
    juveniles: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 0.0,
    yearlings: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 0.0,
    adults: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 0.0,
    migrants: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 0.0,
    spawners: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='fish')] = 375.0,
    female_fraction: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = FEMALE_FRACTION,
    eggs_per_nest: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2e5,
    max_nests: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = MAX_NESTS,
    spawning_time: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = SPAWNING_TIME,
    hatching_time: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    larval_time: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 2.0,
    river_time: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 8.0,
    ocean_time: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 12.0,
    adult_ocean_time: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 12.0,
    migration_time: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 2.0,
    egg_loss: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.99,
    larval_loss: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.8,
    juvenile_loss: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.4,
    first_year_loss: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
    second_year_loss: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    migration_loss: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.25,
    juvenile_harvest: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.48,
    adult_harvest: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.8,
    weight_per_adult: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.82,
    weight_per_juvenile: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.09,
) -> None:
    super().__init__(label, visual=visual)
    self.eggs = eggs
    self.larvae = larvae
    self.juveniles = juveniles
    self.yearlings = yearlings
    self.adults = adults
    self.migrants = migrants
    self.spawners = spawners
    self.female_fraction = female_fraction
    self.eggs_per_nest = eggs_per_nest
    self.max_nests = max_nests
    self.spawning_time = spawning_time
    self.hatching_time = hatching_time
    self.larval_time = larval_time
    self.river_time = river_time
    self.ocean_time = ocean_time
    self.adult_ocean_time = adult_ocean_time
    self.migration_time = migration_time
    self.egg_loss = egg_loss
    self.larval_loss = larval_loss
    self.juvenile_loss = juvenile_loss
    self.first_year_loss = first_year_loss
    self.second_year_loss = second_year_loss
    self.migration_loss = migration_loss
    self.juvenile_harvest = juvenile_harvest
    self.adult_harvest = adult_harvest
    self.weight_per_adult = weight_per_adult
    self.weight_per_juvenile = weight_per_juvenile

LandCellElement(label, crop=0.0, distance=0.0)

Bases: Element

Cell of a plain that grows one crop, numbered from 1, or lies idle as crop 0.

Parameters:

Name Type Description Default
label str

The label of the cell or the field.

required
crop Annotated[float, StateSpec()]

The initial guess of the crop, which the equation replaces.

0.0
distance Annotated[float, StateSpec()]

The distance from the market.

0.0

Examples:

>>> from skcomplex.models.economics.resources import LandCellElement
>>> LandCellElement('cell', distance=3.0).distance
3.0
Source code in src/skcomplex/models/economics/resources/_land_cell.py
31
32
33
34
35
36
37
38
39
def __init__(
    self,
    label: str,
    crop: Annotated[float, StateSpec()] = 0.0,
    distance: Annotated[float, StateSpec()] = 0.0,
) -> None:
    super().__init__(label)
    self.crop = crop
    self.distance = distance

OilEconomyElement(label, visual=None, *, resource=1000.0, capital=5.0, price=3.0, cost=0.1, growth_goal=0.1, lifetime=20.0, yield_table=OIL_YIELD, price_table=None)

Bases: Element

Capital that extracts a nonrenewable resource and reinvests its profit up to a growth goal.

Extraction is the capital times the yield per unit of capital, a table of the resource stock. The profit is the price times the extraction minus the operating cost of the capital. The price is constant, or a table of the yield when a price table is given. The economy holds the stocks, the parameters, and the tables, and transfers within it of the model move the resource and the capital.

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

The initial resource stock.

1000.0
capital Annotated[float, StateSpec(domain=closed_open(0, oo), quantity=capital)]

The initial capital.

5.0
price Annotated[float, ParameterSpec(domain=open(0, oo))]

The price of the resource, used without a price table.

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

The operating cost per unit of capital.

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

The largest investment per unit of capital.

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

The average lifetime of the capital.

20.0
yield_table Sequence[float]

The yield per unit of capital at the resource levels 0, 100, ..., 1000.

OIL_YIELD
price_table Sequence[float] | None

The price at the yields 0, 0.1, 0.2, 0.4, 0.6, 0.8, and 1, or None for a constant price.

None

Examples:

>>> from skcomplex.models.economics.resources import OilEconomyElement
>>> OilEconomyElement('oil', price=4.0).price
4.0
Source code in src/skcomplex/models/economics/resources/_oil_economy.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    resource: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='oil')] = 1000.0,
    capital: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo), quantity='capital')] = 5.0,
    price: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 3.0,
    cost: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    growth_goal: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    lifetime: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 20.0,
    yield_table: Sequence[float] = OIL_YIELD,
    price_table: Sequence[float] | None = None,
) -> None:
    super().__init__(label, visual=visual)
    self.resource = resource
    self.capital = capital
    self.price = price
    self.cost = cost
    self.growth_goal = growth_goal
    self.lifetime = lifetime
    self.yield_table = yield_table
    self.price_table = price_table

PastureElement(label, visual=VisualSpec('agents', {'color_by': 'grass', 'color_range': (0.0, 2.0)}), *, grass=2.0, capacity=2.0, regrowth=0.2, seed=0.05)

Bases: Element

Cell of the meadow whose grass grows back logistically, from a seed bank when it is grazed bare.

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

VisualSpec('agents', {'color_by': 'grass', 'color_range': (0.0, 2.0)})
grass Annotated[float, StateSpec(domain=closed_open(0, oo))]

The grass on the cell.

2.0
capacity Annotated[float, StateSpec(domain=open(0, oo))]

The most grass the cell holds.

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

The logistic growth rate of the grass.

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

The share of the capacity that grows back as if it stood on a bare cell.

0.05

Examples:

>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.economics.resources import PastureElement
>>> GroupElement('meadow', element=PastureElement, size=4, values={'capacity': 2.0}).size
4
Source code in src/skcomplex/models/economics/resources/_pasture.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'grass', 'color_range': (0.0, 2.0)}),
    *,
    grass: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
    capacity: Annotated[float, StateSpec(domain=RealSet.open(0, oo))] = 2.0,
    regrowth: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
    seed: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.05,
) -> None:
    super().__init__(label, visual=visual)
    self.grass = grass
    self.capacity = capacity
    self.regrowth = regrowth
    self.seed = seed

grow(agent, model)

Grow the grass logistically up to the capacity.

Parameters:

Name Type Description Default
agent PastureView

The pasture.

required
model ModelView

The model.

required
Source code in src/skcomplex/models/economics/resources/_pasture.py
86
87
88
89
90
91
92
93
94
95
96
97
98
def grow(self, agent: PastureView, model: ModelView) -> None:
    """Grow the grass logistically up to the capacity.

    Args:
        agent:
            The pasture.

        model:
            The model.
    """
    stand = max(agent.grass, model.parameter('seed') * agent.capacity)
    growth = model.parameter('regrowth') * stand * (1 - stand / agent.capacity)
    agent.grass = min(agent.capacity, agent.grass + growth)

PlantationElement(label, visual=None, *, forest=490.0, area=10.0, abandoned=0.0, shade_removal=5.0, insects=1.0, yield_normal=5.0, price=40.0, cost=100.0, thinning_cost=20.0, adjustment_time=3.0, abandon_time=4.0, conversion_time=3.0, palm_margin=0.4, thinning_time=8.0, insect_growth=0.6, insect_control=0.3, subsidy=0.0)

Bases: Element

Cocoa land in forest, planted, and abandoned stocks, with the shade thinned and the insects it lets in.

Farmers plant forest towards a desired area that grows with the profitability of cocoa, abandon plantations whose yield and profit fall, and leave abandoned land to the forest or convert it to oil palm when cocoa pays less than palm. Removing shade raises the yield and lets the insects grow logistically, and the subsidy keeps the shade and controls the insects. The land flows are transfers within the plantation of the model.

Parameters:

Name Type Description Default
label str

The label of the plantation.

required
visual VisualSpec | None

How the element is drawn, or None for default_visual_.

None
forest Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=land)]

The initial forest land.

490.0
area Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=land)]

The initial cocoa area.

10.0
abandoned Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=land)]

The initial abandoned land.

0.0
shade_removal Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial index of shade removal, from 0 to 100.

5.0
insects Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial index of insect attack, from 0 to 100.

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

The yield per area without insects or thinning.

5.0
price Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The farm price of cocoa.

40.0
cost Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The operating cost per area.

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

The cost per area of removing all shade.

20.0
adjustment_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time to plant towards the desired area.

3.0
abandon_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The average time before a plantation is abandoned.

4.0
conversion_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time abandoned land stays before it returns to forest or turns to oil palm.

3.0
palm_margin Annotated[object, ParameterSpec(domain=open(0, oo))]

The profitability of oil palm, against which abandoned land converts.

0.4
thinning_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time to remove the shade.

8.0
insect_growth Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The largest relative growth rate of the insects, reached with all shade removed.

0.6
insect_control Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The rate at which the subsidy controls the insects.

0.3
subsidy Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The coverage of the subsidy and extension programme, from 0 to 1.

0.0

Examples:

>>> from skcomplex.models.economics.resources import PlantationElement
>>> PlantationElement('cocoa', subsidy=0.5).subsidy
0.5
Source code in src/skcomplex/models/economics/resources/_plantations.py
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
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    forest: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='land')] = 490.0,
    area: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='land')] = 10.0,
    abandoned: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='land')] = 0.0,
    shade_removal: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
    insects: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    yield_normal: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
    price: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 40.0,
    cost: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 100.0,
    thinning_cost: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 20.0,
    adjustment_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 3.0,
    abandon_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 4.0,
    conversion_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 3.0,
    palm_margin: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 0.4,
    thinning_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 8.0,
    insect_growth: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.6,
    insect_control: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
    subsidy: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
) -> None:
    super().__init__(label, visual=visual)
    self.forest = forest
    self.area = area
    self.abandoned = abandoned
    self.shade_removal = shade_removal
    self.insects = insects
    self.yield_normal = yield_normal
    self.price = price
    self.cost = cost
    self.thinning_cost = thinning_cost
    self.adjustment_time = adjustment_time
    self.abandon_time = abandon_time
    self.conversion_time = conversion_time
    self.palm_margin = palm_margin
    self.thinning_time = thinning_time
    self.insect_growth = insect_growth
    self.insect_control = insect_control
    self.subsidy = subsidy

QuotaRegulatorInteraction(label, *, rule=None, phase=None, regulated=0.0, low=0.5, high=0.8)

Bases: FeedbackInteraction[ModelView]

Regulator that halves the quota of cows per herder when the grass runs short and raises it when it recovers.

The quota is the adaptive parameter quota of every herder. It halves, down to one cow, when the grass falls below the share low of the capacity of the meadow, and grows by one cow when the grass is above the share high. Without regulation it stays as it is.

Parameters:

Name Type Description Default
label str

The label of the regulator.

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

Unused, since the regulator overrides act.

None
phase str | None

The phase the regulator acts in.

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

One when the regulator changes the quota, zero otherwise.

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

The share of the capacity of the meadow below which the quota halves.

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

The share of the capacity of the meadow above which the quota rises by one cow.

0.8

Examples:

>>> from skcomplex.models.economics.resources import QuotaRegulatorInteraction
>>> QuotaRegulatorInteraction('regulator', regulated=1.0).regulated
1.0
Source code in src/skcomplex/models/economics/resources/_quota_regulator.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    label: str,
    *,
    rule: Callable[[ModelView], None] | None = None,
    phase: str | None = None,
    regulated: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    low: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.5,
    high: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.8,
) -> None:
    super().__init__(label, rule=rule, phase=phase)
    self.regulated = regulated
    self.low = low
    self.high = high

act(model)

Change the quota with the share of grass left on the meadow.

Parameters:

Name Type Description Default
model ModelView

The model view of the regulator.

required
Source code in src/skcomplex/models/economics/resources/_quota_regulator.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def act(self, model: ModelView) -> None:
    """Change the quota with the share of grass left on the meadow.

    Args:
        model:
            The model view of the regulator.
    """
    if not model.parameter('regulated'):
        return
    pastures = model.agents('meadow', PastureView)
    share = sum(pasture.grass for pasture in pastures) / sum(pasture.capacity for pasture in pastures)
    for herder in model.agents('herders', HerderView):
        if share < model.parameter('low'):
            herder.quota = max(1.0, float(np.floor(herder.quota / 2)))
        elif share > model.parameter('high'):
            herder.quota += 1

RiceSectorElement(label, visual=None, *, available_land=0.088, rice_area=0.680647, population=18.1024, potential=6.0, soil_quality=0.99, practice=0.61, training=0.0, bio_fraction=0.0, cropping_intensity=1.0, conversion_time=8.0, discard_rate=0.0085, growth_rate=0.021, consumption=80.0, milling_rate=0.65, subsidy=0.67, farmer_inputs=0.3, research=0.0, potential_limit=10.0, research_time=20.0, regeneration_time=10.0, degradation=0.002, learning_time=5.0, training_target=0.0, extension_time=5.0, bio_target=0.0, transition_time=10.0, intensity_target=1.0, implementation_time=10.0)

Bases: Element

Rice sector of a country, its land, population, and the factors of the productivity of its rice land.

The suitable land is developed into rice area and the rice area is discarded, and the population grows exponentially, through transfers within the sector of the model. Research raises the potential yield towards a limit, chemical fertiliser degrades the soil and fallow regenerates it, and training improves the farming practice through a learning table. The training, the share of bio-fertiliser, and the cropping intensity relax towards their targets through relaxation interactions of the model, which read the targets and times of the sector.

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
available_land Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=land)]

The initial land suitable for rice and not yet developed.

0.088
rice_area Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=land)]

The initial rice area.

0.680647
population Annotated[object, StateSpec(domain=closed_open(0, oo), quantity=people)]

The initial population.

18.1024
potential Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial potential yield.

6.0
soil_quality Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial soil quality, from 0 to 1.

0.99
practice Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial quality of the farming practice, from 0 to 1.

0.61
training Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial coverage of the farmers by training and extension, from 0 to 1.

0.0
bio_fraction Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial share of bio-fertiliser in the inputs.

0.0
cropping_intensity Annotated[object, StateSpec(domain=closed_open(0, oo))]

The initial number of crops per year on the rice area.

1.0
conversion_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time to develop the suitable land.

8.0
discard_rate Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The fraction of the rice area discarded per year.

0.0085
growth_rate Annotated[object, ParameterSpec()]

The growth rate of the population.

0.021
consumption Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The rice consumption per capita, in kilograms per year.

80.0
milling_rate Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The share of paddy that milling turns into rice.

0.65
subsidy Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The share of the recommended fertiliser that the subsidy pays for.

0.67
farmer_inputs Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The share of the unsubsidised fertiliser that the farmers buy.

0.3
research Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The funding of research for high-yielding varieties, relative to its normal level.

0.0
potential_limit Annotated[object, ParameterSpec(domain=open(0, oo))]

The potential yield that research approaches.

10.0
research_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time research takes to reach the limit at its normal funding.

20.0
regeneration_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time fallow takes to regenerate the soil.

10.0
degradation Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The rate at which chemical fertiliser degrades the soil.

0.002
learning_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time training takes to improve the practice.

5.0
training_target Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The target coverage of training and extension.

0.0
extension_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time the training takes to reach its target.

5.0
bio_target Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The target share of bio-fertiliser.

0.0
transition_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time the share of bio-fertiliser takes to reach its target.

10.0
intensity_target Annotated[object, ParameterSpec(domain=open(0, oo))]

The target cropping intensity.

1.0
implementation_time Annotated[object, ParameterSpec(domain=open(0, oo))]

The time the cropping intensity takes to reach its target.

10.0

Examples:

>>> from skcomplex.models.economics.resources import RiceSectorElement
>>> RiceSectorElement('rice', subsidy=0.5).subsidy
0.5
Source code in src/skcomplex/models/economics/resources/_food.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    available_land: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='land')] = 0.088,
    rice_area: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='land')] = 0.680647,
    population: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='people')] = 18.1024,
    potential: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 6.0,
    soil_quality: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.99,
    practice: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.61,
    training: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    bio_fraction: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    cropping_intensity: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    conversion_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 8.0,
    discard_rate: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0085,
    growth_rate: Annotated[object, ParameterSpec()] = 0.021,
    consumption: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 80.0,
    milling_rate: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.65,
    subsidy: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.67,
    farmer_inputs: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
    research: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    potential_limit: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
    research_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 20.0,
    regeneration_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
    degradation: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.002,
    learning_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 5.0,
    training_target: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    extension_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 5.0,
    bio_target: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
    transition_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
    intensity_target: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    implementation_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
) -> None:
    super().__init__(label, visual=visual)
    self.available_land = available_land
    self.rice_area = rice_area
    self.population = population
    self.potential = potential
    self.soil_quality = soil_quality
    self.practice = practice
    self.training = training
    self.bio_fraction = bio_fraction
    self.cropping_intensity = cropping_intensity
    self.conversion_time = conversion_time
    self.discard_rate = discard_rate
    self.growth_rate = growth_rate
    self.consumption = consumption
    self.milling_rate = milling_rate
    self.subsidy = subsidy
    self.farmer_inputs = farmer_inputs
    self.research = research
    self.potential_limit = potential_limit
    self.research_time = research_time
    self.regeneration_time = regeneration_time
    self.degradation = degradation
    self.learning_time = learning_time
    self.training_target = training_target
    self.extension_time = extension_time
    self.bio_target = bio_target
    self.transition_time = transition_time
    self.intensity_target = intensity_target
    self.implementation_time = implementation_time

StockElement(label, visual=None, *, n=1000.0, r=1.0, K=1000.0)

Bases: Element

Renewable stock that grows logistically, n' = r n (1 - n / K).

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

The initial stock.

1000.0
r Annotated[object, ParameterSpec(domain=closed_open(0, oo))]

The intrinsic growth rate.

1.0
K Annotated[object, ParameterSpec(domain=open(0, oo))]

The carrying capacity.

1000.0

Examples:

>>> from skcomplex.models.economics.resources import StockElement
>>> StockElement('stock', n=500.0).n
500.0
Source code in src/skcomplex/models/economics/resources/_stock.py
42
43
44
45
46
47
48
49
50
51
52
53
54
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    n: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 1000.0,
    r: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    K: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1000.0,
) -> None:
    super().__init__(label, visual=visual)
    self.n = n
    self.r = r
    self.K = K

cocoa(area=10.0, *, price=40.0, cost=100.0, insect_growth=0.6, subsidy=0.0)

Return a cocoa production system, plantations that boom with their profit and bust with the insects they invite.

The planted area moves towards a desired area, the current area times a table function of the profit margin, so a profitable crop expands it. Farmers thin the shading trees, which raises the yield through an intensification table but lets the insects multiply. Insect attack lowers the yield through an ecological table, and the area is abandoned at a rate that falls with the yield and the profit through two more tables. Abandoned land is converted to oil palm. A subsidy for shade conservation and pest control slows the thinning and controls the insects. Land is conserved, so the area booms while the yield is high and busts once the insects take over, unless a subsidy keeps the shade. The structure and the five table functions follow the boom and bust of cocoa in Malaysia of Bala, Arshad and Noh (2017, Chapter 8), and the rates, the price, and the costs are illustrative, with the area in thousands of hectares, the yield in hundreds of kilograms per hectare, the money in ringgit per hundred kilograms and per hectare, the shade removal and the insect attack as indices from 0 to 100, and time in years.

Parameters:

Name Type Description Default
area float

The initial planted area.

10.0
price float

The farm price of cocoa.

40.0
cost float

The operating cost per hectare.

100.0
insect_growth float

The largest relative growth rate of the insect attack, reached with all shade removed.

0.6
subsidy float

The coverage of the subsidy and extension programme, from 0 to 1.

0.0

Returns:

Name Type Description
system System

The model, with the land stocks cocoa.area and cocoa.abandoned, the indices cocoa.shade_removal and cocoa.insects, and the flows cocoa.planting, cocoa.abandonment, and cocoa.conversion per year, over years. The observables of the model are cocoa.cocoa_yield, cocoa.profit_per_area, and cocoa.profitability.

Examples:

>>> from skcomplex.models.economics.resources import cocoa
>>> cocoa(subsidy=1.0).elements[0].subsidy
1.0
Source code in src/skcomplex/models/economics/resources/_plantations.py
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
@register_model(
    field='economics/resources',
    tags=('agriculture', 'boom and bust', 'stocks and flows', 'system dynamics', 'ode'),
    references=('Bala et al. 2017',),
    defaults={'until': 50.0, 'dt': 0.25},
    observables=lambda system: [
        ObservableSpec('cocoa_yield', _cocoa_yield, element='cocoa'),
        ObservableSpec('profit_per_area', _profit_per_area, element='cocoa'),
        ObservableSpec('profitability', _profitability, element='cocoa'),
    ],
)
def cocoa(
    area: float = 10.0,
    *,
    price: float = 40.0,
    cost: float = 100.0,
    insect_growth: float = 0.6,
    subsidy: float = 0.0,
) -> System:
    """Return a cocoa production system, plantations that boom with their profit and bust with the insects they invite.

    The planted area moves towards a desired area, the current area times a table function of the profit margin, so
    a profitable crop expands it. Farmers thin the shading trees, which raises the yield through an intensification
    table but lets the insects multiply. Insect attack lowers the yield through an ecological table, and the area is
    abandoned at a rate that falls with the yield and the profit through two more tables. Abandoned land is converted
    to oil palm. A subsidy for shade conservation and pest control slows the thinning and controls the insects. Land
    is conserved, so the area booms while the yield is high and busts once the insects take over, unless a subsidy
    keeps the shade. The structure and the five table functions follow the boom and bust of cocoa in Malaysia of
    Bala, Arshad and Noh (2017, Chapter 8), and the rates, the price, and the costs are illustrative, with the area in
    thousands of hectares, the yield in hundreds of kilograms per hectare, the money in ringgit per hundred
    kilograms and per hectare, the shade removal and the insect attack as indices from 0 to 100, and time in years.

    Args:
        area:
            The initial planted area.

        price:
            The farm price of cocoa.

        cost:
            The operating cost per hectare.

        insect_growth:
            The largest relative growth rate of the insect attack, reached with all shade removed.

        subsidy:
            The coverage of the subsidy and extension programme, from 0 to 1.

    Returns:
        system:
            The model, with the land stocks `cocoa.area` and `cocoa.abandoned`, the indices `cocoa.shade_removal` and
            `cocoa.insects`, and the flows `cocoa.planting`, `cocoa.abandonment`, and `cocoa.conversion` per year, over
            years. The observables of the model are `cocoa.cocoa_yield`, `cocoa.profit_per_area`, and
            `cocoa.profitability`.

    Examples:
        >>> from skcomplex.models.economics.resources import cocoa
        >>> cocoa(subsidy=1.0).elements[0].subsidy
        1.0
    """
    plantation = PlantationElement(
        'cocoa',
        area=area,
        price=price,
        cost=cost,
        insect_growth=insect_growth,
        subsidy=subsidy,
    )
    return System(elements=[plantation], interactions=[_land_use()])

fishery_capital(resource=1000.0, capital=5.0, *, cost=1.0, growth_goal=0.1, lifetime=20.0, yield_table=FISH_YIELD)

Return Meadows' fishing fleet, capital that grows on a renewable resource and can overshoot it.

The capital grows while the harvest pays, and the outcome depends on the yield per unit of capital. With the default yield, which falls with the square of the stock, the fleet and the stock settle at an equilibrium. A technology that keeps the yield high at low stocks lets the fleet overshoot, and the stock and then the fleet collapse.

Parameters:

Name Type Description Default
resource float

The initial resource stock.

1000.0
capital float

The initial capital.

5.0
cost float

The operating cost per unit of capital.

1.0
growth_goal float

The largest investment per unit of capital.

0.1
lifetime float

The average lifetime of the capital.

20.0
yield_table Sequence[float]

The yield per unit of capital at the resource levels 0, 100, ..., 1000.

FISH_YIELD

Returns:

Name Type Description
system System

The model, with the resource fishery.resource and the capital fishery.capital.

Examples:

>>> from skcomplex.models.economics.resources import fishery_capital
>>> fishery_capital(lifetime=10.0).elements[0].lifetime
10.0
Source code in src/skcomplex/models/economics/resources/_extraction.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
@register_model(
    field='economics/resources',
    tags=('renewable resource', 'system dynamics', 'ode'),
    references=('Meadows 2008',),
    defaults={'until': 100.0, 'dt': 1.0},
)
def fishery_capital(
    resource: float = 1000.0,
    capital: float = 5.0,
    *,
    cost: float = 1.0,
    growth_goal: float = 0.1,
    lifetime: float = 20.0,
    yield_table: Sequence[float] = FISH_YIELD,
) -> System:
    """Return Meadows' fishing fleet, capital that grows on a renewable resource and can overshoot it.

    The capital grows while the harvest pays, and the outcome depends on the yield per unit of capital. With the default
    yield, which falls with the square of the stock, the fleet and the stock settle at an equilibrium. A technology that
    keeps the yield high at low stocks lets the fleet overshoot, and the stock and then the fleet collapse.

    Args:
        resource:
            The initial resource stock.

        capital:
            The initial capital.

        cost:
            The operating cost per unit of capital.

        growth_goal:
            The largest investment per unit of capital.

        lifetime:
            The average lifetime of the capital.

        yield_table:
            The yield per unit of capital at the resource levels 0, 100, ..., 1000.

    Returns:
        system:
            The model, with the resource `fishery.resource` and the capital `fishery.capital`.

    Examples:
        >>> from skcomplex.models.economics.resources import fishery_capital
        >>> fishery_capital(lifetime=10.0).elements[0].lifetime
        10.0
    """
    fishery = FisheryElement(
        'fishery',
        resource=resource,
        capital=capital,
        cost=cost,
        growth_goal=growth_goal,
        lifetime=lifetime,
        yield_table=tuple(float(value) for value in yield_table),
    )
    yields, regenerations, prices = fishery.yield_table, fishery.regeneration_table, fishery.price_table
    fish = TransferInteraction(
        'fish',
        source='fishery',
        target='fishery',
        state=Source('spawning'),
        into='resource',
        amount=lambda s: s.source.resource * lookup(s.source.resource, _table(RESOURCE_LEVELS, regenerations)),
        flow='regeneration',
        legs=[('resource', Sink('market'), lambda s: _harvest(s.source, yields), 'harvest')],
    )
    investment = TransferInteraction(
        'investment',
        source='fishery',
        target='fishery',
        state=Source('investors'),
        into='capital',
        amount=lambda s: _investment(s.source, _fish_revenue(s.source, yields, prices)),
        flow='investment',
    )
    return System(elements=[fishery], interactions=[fish, investment, _capital('depreciation', 'fishery')])

food_security(*, subsidy=0.67, bio_target=0.0, research=0.0, training_target=0.0, intensity_target=1.0)

Return a food security system, the rice self-sufficiency of a country under subsidy, research, and extension.

The land suitable for rice is developed into rice area over eight years, and the rice area is discarded for housing, roads, and industry at a small fraction per year. The production is the milled share of the productivity times the area times the cropping intensity. The productivity is the potential yield, which research raises towards a limit, times a table of the input ratio, the soil quality, which chemical fertiliser degrades and fallow regenerates, and the farming practice, which training and extension improve through a learning table. The input ratio is the share of the recommended fertiliser that the farmers apply, which the subsidy raises, and a target share of bio-fertiliser replaces the chemical one without degrading the soil. The requirement is the per capita consumption of a population growing exponentially, and the self-sufficiency level is the production over the requirement. Land is conserved, so the rice area peaks once the suitable land is developed and then declines, and without research and extension the self-sufficiency falls as the population grows. The structure and the initial values follow the food security model of Malaysia of Bala, Arshad and Noh (2017, Chapter 10), and the table functions and the rates are illustrative, with the land in millions of hectares, the population in millions, the productivity in tonnes of paddy per hectare, the rice in millions of tonnes, and time in years.

Parameters:

Name Type Description Default
subsidy float

The share of the recommended fertiliser that the subsidy pays for.

0.67
bio_target float

The target share of bio-fertiliser in the inputs.

0.0
research float

The funding of research for high-yielding varieties, relative to its normal level.

0.0
training_target float

The target coverage of the farmers by training and extension, from 0 to 1.

0.0
intensity_target float

The target cropping intensity, the crops per year on the rice area.

1.0

Returns:

Name Type Description
system System

The model, with the land stocks rice.available_land and rice.rice_area, the population rice.population, the factors rice.potential, rice.soil_quality, rice.practice, rice.training, rice.bio_fraction, and rice.cropping_intensity, and the flows rice.development, rice.discard, and rice.population_growth per year, over years. The observables of the model are rice.input_ratio, rice.productivity, rice.production, rice.requirement, and rice.self_sufficiency.

Examples:

>>> from skcomplex.models.economics.resources import food_security
>>> food_security(subsidy=0.0).elements[0].subsidy
0.0
Source code in src/skcomplex/models/economics/resources/_food.py
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
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
362
363
364
365
366
367
368
369
370
@register_model(
    field='economics/resources',
    tags=('food security', 'agriculture', 'stocks and flows', 'system dynamics', 'ode'),
    references=('Bala et al. 2017',),
    defaults={'until': 50.0, 'dt': 0.25},
    observables=lambda system: [
        ObservableSpec(name, expression, element='rice')
        for name, expression in (
            ('input_ratio', _input_ratio),
            ('productivity', _productivity),
            ('production', _production),
            ('requirement', _requirement),
            ('self_sufficiency', _self_sufficiency),
        )
    ],
)
def food_security(
    *,
    subsidy: float = 0.67,
    bio_target: float = 0.0,
    research: float = 0.0,
    training_target: float = 0.0,
    intensity_target: float = 1.0,
) -> System:
    """Return a food security system, the rice self-sufficiency of a country under subsidy, research, and extension.

    The land suitable for rice is developed into rice area over eight years, and the rice area is discarded for
    housing, roads, and industry at a small fraction per year. The production is the milled share of the
    productivity times the area times the cropping intensity. The productivity is the potential yield, which research
    raises towards a limit, times a table of the input ratio, the soil quality, which chemical fertiliser degrades and
    fallow regenerates, and the farming practice, which training and extension improve through a learning table. The
    input ratio is the share of the recommended fertiliser that the farmers apply, which the subsidy raises, and a
    target share of bio-fertiliser replaces the chemical one without degrading the soil. The requirement is the per
    capita consumption of a population growing exponentially, and the self-sufficiency level is the production over
    the requirement. Land is conserved, so the rice area peaks once the suitable land is developed and then declines,
    and without research and extension the self-sufficiency falls as the population grows. The structure and the
    initial values follow the food security model of Malaysia of Bala, Arshad and Noh (2017, Chapter 10), and the
    table functions and the rates are illustrative, with the land in millions of hectares, the population in
    millions, the productivity in tonnes of paddy per hectare, the rice in millions of tonnes, and time in years.

    Args:
        subsidy:
            The share of the recommended fertiliser that the subsidy pays for.

        bio_target:
            The target share of bio-fertiliser in the inputs.

        research:
            The funding of research for high-yielding varieties, relative to its normal level.

        training_target:
            The target coverage of the farmers by training and extension, from 0 to 1.

        intensity_target:
            The target cropping intensity, the crops per year on the rice area.

    Returns:
        system:
            The model, with the land stocks `rice.available_land` and `rice.rice_area`, the population
            `rice.population`, the factors `rice.potential`, `rice.soil_quality`, `rice.practice`, `rice.training`,
            `rice.bio_fraction`, and `rice.cropping_intensity`, and the flows `rice.development`, `rice.discard`, and
            `rice.population_growth` per year, over years. The observables of the model are `rice.input_ratio`,
            `rice.productivity`, `rice.production`, `rice.requirement`, and `rice.self_sufficiency`.

    Examples:
        >>> from skcomplex.models.economics.resources import food_security
        >>> food_security(subsidy=0.0).elements[0].subsidy
        0.0
    """
    sector = RiceSectorElement(
        'rice',
        subsidy=subsidy,
        bio_target=bio_target,
        research=research,
        training_target=training_target,
        intensity_target=intensity_target,
    )
    transfers = [
        TransferInteraction(
            'land_use',
            source='rice',
            target='rice',
            state='available_land',
            into='rice_area',
            amount=lambda s: s.source.available_land / s.source.conversion_time,
            flow='development',
            legs=[('rice_area', Sink('urban'), lambda s: s.source.discard_rate * s.source.rice_area, 'discard')],
        ),
        TransferInteraction(
            'births',
            source='rice',
            target='rice',
            state=Source('births'),
            into='population',
            amount=lambda s: s.source.growth_rate * s.source.population,
            flow='population_growth',
        ),
    ]
    relaxations = [
        RelaxationInteraction(
            'training',
            target='rice',
            state='training',
            goal=lambda s: s.target.training_target,
            time=lambda s: s.target.extension_time,
        ),
        RelaxationInteraction(
            'bio_fraction',
            target='rice',
            state='bio_fraction',
            goal=lambda s: s.target.bio_target,
            time=lambda s: s.target.transition_time,
        ),
        RelaxationInteraction(
            'cropping_intensity',
            target='rice',
            state='cropping_intensity',
            goal=lambda s: s.target.intensity_target,
            time=lambda s: s.target.implementation_time,
        ),
    ]
    return System(elements=[sector], interactions=[*transfers, *relaxations])

harvesting(farmers=10, *, effort=0.05, r=1.0, K=1000.0)

Return a socio-ecological model of farmer agents harvesting a logistically regrowing resource.

The resource settles at K * (1 - effort * farmers / r) when effort * farmers < r, and collapses otherwise.

Parameters:

Name Type Description Default
farmers int

The number of farmers.

10
effort float

The harvest rate per farmer and unit of stock.

0.05
r float

The growth rate of the resource.

1.0
K float

The carrying capacity of the resource.

1000.0

Returns:

Name Type Description
system System

The model, with the ODE subsystem land and the agent subsystem village.

Examples:

>>> from skcomplex.models.economics.resources import harvesting
>>> [subsystem.label for subsystem in harvesting().systems]
['land', 'village']
Source code in src/skcomplex/models/economics/resources/_harvesting.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@register_model(
    field='economics/resources',
    tags=('socio-ecological', 'agents', 'ode', 'co-simulation'),
    references=('Hamill and Gilbert 2016 ch. 10',),
    defaults={'until': 50},
)
def harvesting(farmers: int = 10, *, effort: float = 0.05, r: float = 1.0, K: float = 1000.0) -> System:
    """Return a socio-ecological model of farmer agents harvesting a logistically regrowing resource.

    The resource settles at `K * (1 - effort * farmers / r)` when `effort * farmers < r`, and collapses otherwise.

    Args:
        farmers:
            The number of farmers.

        effort:
            The harvest rate per farmer and unit of stock.

        r:
            The growth rate of the resource.

        K:
            The carrying capacity of the resource.

    Returns:
        system:
            The model, with the ODE subsystem `land` and the agent subsystem `village`.

    Examples:
        >>> from skcomplex.models.economics.resources import harvesting
        >>> [subsystem.label for subsystem in harvesting().systems]
        ['land', 'village']
    """
    land = System('land', elements=[StockElement('stock', n=K, r=r, K=K)])
    village = System('village', elements=[GroupElement('farmers', element=FarmerElement, size=farmers)])
    harvest = HarvestInteraction('harvest', target='land/stock', harvesters='village/farmers', effort=effort)
    return System(interactions=[harvest], systems=[land, village])

hilsa_fishery(spawners=375.0, *, eggs_per_nest=200000.0, juvenile_harvest=0.48, adult_harvest=0.8, migration_loss=0.25)

Return a hilsa fishery, the life cycle of a migratory fish that the catch of juveniles and spawners depletes.

Spawning adults lay eggs in nests in the rivers, and the number of nests is limited by the spawning grounds. The eggs hatch into larvae within a month, the larvae become juveniles within two months, and the juveniles stay in the rivers for eight months before they enter the ocean. The fish mature in the ocean over two years, migrate back to the rivers, and spawn. Every stage drains over its duration, and a fixed fraction of its outflow is lost to natural mortality, to the catch of juveniles, or to the catch of spawning adults, while the rest enters the next stage. Fish are conserved, every change of a stage is a flow, and the population settles where the spawning grounds fill, unless the catch keeps the reproduction below replacement. The stages, the durations, and the loss fractions follow the hilsa model of Bala, Arshad and Noh (2017, Chapter 9), with the stocks in millions of fish and time in months.

Parameters:

Name Type Description Default
spawners float

The initial number of adults about to spawn.

375.0
eggs_per_nest float

The number of eggs per nest.

200000.0
juvenile_harvest float

The fraction of the juveniles leaving the rivers that the fishery catches.

0.48
adult_harvest float

The fraction of the adults about to spawn that the fishery catches.

0.8
migration_loss float

The fraction of the adults lost during their migration to the rivers.

0.25

Returns:

Name Type Description
system System

The model, with the stages hilsa.eggs, hilsa.larvae, hilsa.juveniles, hilsa.yearlings, hilsa.adults, hilsa.migrants, and hilsa.spawners, and the flows between the stages, among them hilsa.egg_deposition, hilsa.young_entering_ocean_catch, and hilsa.adult_catch, per month, over months. The observables of the model are the nests hilsa.nests, and the weights hilsa.adult_weight and hilsa.juvenile_weight in tonnes.

Examples:

>>> from skcomplex.models.economics.resources import hilsa_fishery
>>> hilsa_fishery(adult_harvest=0.5).elements[0].adult_harvest
0.5
Source code in src/skcomplex/models/economics/resources/_fisheries.py
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
@register_model(
    field='economics/resources',
    tags=('fishery', 'life cycle', 'stocks and flows', 'system dynamics', 'ode'),
    references=('Bala et al. 2017',),
    defaults={'until': 120.0, 'dt': 0.5},
    observables=lambda system: [
        ObservableSpec('nests', _nests, element='hilsa'),
        ObservableSpec('adult_weight', _adult_weight, element='hilsa'),
        ObservableSpec('juvenile_weight', _juvenile_weight, element='hilsa'),
    ],
)
def hilsa_fishery(
    spawners: float = 375.0,
    *,
    eggs_per_nest: float = 2e5,
    juvenile_harvest: float = 0.48,
    adult_harvest: float = 0.8,
    migration_loss: float = 0.25,
) -> System:
    """Return a hilsa fishery, the life cycle of a migratory fish that the catch of juveniles and spawners depletes.

    Spawning adults lay eggs in nests in the rivers, and the number of nests is limited by the spawning grounds. The
    eggs hatch into larvae within a month, the larvae become juveniles within two months, and the juveniles stay in
    the rivers for eight months before they enter the ocean. The fish mature in the ocean over two years, migrate back
    to the rivers, and spawn. Every stage drains over its duration, and a fixed fraction of its outflow is lost to
    natural mortality, to the catch of juveniles, or to the catch of spawning adults, while the rest enters the next
    stage. Fish are conserved, every change of a stage is a flow, and the population settles where the spawning
    grounds fill, unless the catch keeps the reproduction below replacement. The stages, the durations, and the loss
    fractions follow the hilsa model of Bala, Arshad and Noh (2017, Chapter 9), with the stocks in millions of fish
    and time in months.

    Args:
        spawners:
            The initial number of adults about to spawn.

        eggs_per_nest:
            The number of eggs per nest.

        juvenile_harvest:
            The fraction of the juveniles leaving the rivers that the fishery catches.

        adult_harvest:
            The fraction of the adults about to spawn that the fishery catches.

        migration_loss:
            The fraction of the adults lost during their migration to the rivers.

    Returns:
        system:
            The model, with the stages `hilsa.eggs`, `hilsa.larvae`, `hilsa.juveniles`, `hilsa.yearlings`,
            `hilsa.adults`, `hilsa.migrants`, and `hilsa.spawners`, and the flows between the stages, among them
            `hilsa.egg_deposition`, `hilsa.young_entering_ocean_catch`, and `hilsa.adult_catch`, per month, over
            months. The observables of the model are the nests `hilsa.nests`, and the weights `hilsa.adult_weight` and
            `hilsa.juvenile_weight` in tonnes.

    Examples:
        >>> from skcomplex.models.economics.resources import hilsa_fishery
        >>> hilsa_fishery(adult_harvest=0.5).elements[0].adult_harvest
        0.5
    """
    hilsa = HilsaElement(
        'hilsa',
        eggs_per_nest=eggs_per_nest,
        juvenile_harvest=juvenile_harvest,
        adult_harvest=adult_harvest,
        migration_loss=migration_loss,
    )
    hilsa.set_params(**_filled_stages(spawners, eggs_per_nest, juvenile_harvest, adult_harvest, migration_loss))
    return System(elements=[hilsa], interactions=_life_cycle())

oil_economy(resource=1000.0, capital=5.0, *, price=3.0, cost=0.1, growth_goal=0.1, lifetime=20.0, price_table=None)

Return Meadows' oil economy, capital that grows on a nonrenewable resource and collapses as it runs out.

The capital grows exponentially while extraction is cheap, peaks as falling yields cut the profit, and declines with depreciation, leaving part of the resource in the ground.

Parameters:

Name Type Description Default
resource float

The initial resource stock.

1000.0
capital float

The initial capital.

5.0
price float

The price of the resource, used without a price table.

3.0
cost float

The operating cost per unit of capital.

0.1
growth_goal float

The largest investment per unit of capital.

0.1
lifetime float

The average lifetime of the capital.

20.0
price_table Sequence[float] | None

The price at the yields 0, 0.1, 0.2, 0.4, 0.6, 0.8, and 1, or None for a constant price.

None

Returns:

Name Type Description
system System

The model, with the resource oil.resource and the capital oil.capital.

Examples:

>>> from skcomplex.models.economics.resources import oil_economy
>>> oil_economy(resource=2000.0).elements[0].resource
2000.0
Source code in src/skcomplex/models/economics/resources/_extraction.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
@register_model(
    field='economics/resources',
    tags=('nonrenewable resource', 'system dynamics', 'ode'),
    references=('Meadows 2008',),
    defaults={'until': 100.0, 'dt': 1.0},
)
def oil_economy(
    resource: float = 1000.0,
    capital: float = 5.0,
    *,
    price: float = 3.0,
    cost: float = 0.1,
    growth_goal: float = 0.1,
    lifetime: float = 20.0,
    price_table: Sequence[float] | None = None,
) -> System:
    """Return Meadows' oil economy, capital that grows on a nonrenewable resource and collapses as it runs out.

    The capital grows exponentially while extraction is cheap, peaks as falling yields cut the profit, and declines
    with depreciation, leaving part of the resource in the ground.

    Args:
        resource:
            The initial resource stock.

        capital:
            The initial capital.

        price:
            The price of the resource, used without a price table.

        cost:
            The operating cost per unit of capital.

        growth_goal:
            The largest investment per unit of capital.

        lifetime:
            The average lifetime of the capital.

        price_table:
            The price at the yields 0, 0.1, 0.2, 0.4, 0.6, 0.8, and 1, or `None` for a constant price.

    Returns:
        system:
            The model, with the resource `oil.resource` and the capital `oil.capital`.

    Examples:
        >>> from skcomplex.models.economics.resources import oil_economy
        >>> oil_economy(resource=2000.0).elements[0].resource
        2000.0
    """
    economy = OilEconomyElement(
        'oil',
        resource=resource,
        capital=capital,
        price=price,
        cost=cost,
        growth_goal=growth_goal,
        lifetime=lifetime,
        price_table=None if price_table is None else tuple(float(value) for value in price_table),
    )
    yields, prices = economy.yield_table, economy.price_table
    extraction = TransferInteraction(
        'extraction',
        source='oil',
        target='oil',
        state='resource',
        into=Sink('market'),
        amount=lambda s: _harvest(s.source, yields),
        flow='extraction',
    )
    investment = TransferInteraction(
        'investment',
        source='oil',
        target='oil',
        state=Source('investors'),
        into='capital',
        amount=lambda s: _investment(s.source, _oil_price(s.source, yields, prices) * _harvest(s.source, yields)),
        flow='investment',
    )
    return System(elements=[economy], interactions=[extraction, investment, _capital('depreciation', 'oil')])

optimal_harvesting(x=1.0, *, r=1.0, K=1.0, q=1.0, p=1.0, c=0.2, delta=0.1)

Return a logistic resource harvested with the effort that maximises its discounted profit.

Run with an optimal control solver, the effort starts at a bound, drives the stock to the level x* where r (1 - 2 x* / K) + c r (1 - x* / K) / (p q x* - c) = delta, holds it there with the effort r (1 - x* / K) / q, and rises to its largest value at the end of the horizon, when the stock left has no value.

Parameters:

Name Type Description Default
x float

The initial stock.

1.0
r float

The intrinsic growth rate.

1.0
K float

The carrying capacity.

1.0
q float

The catchability.

1.0
p float

The price per unit of catch.

1.0
c float

The cost per unit of effort.

0.2
delta float

The discount rate.

0.1

Returns:

Name Type Description
system System

The model, with the stock resource.x, the effort resource.effort, and the observable catch.

Examples:

>>> from skcomplex.models.economics.resources import optimal_harvesting
>>> optimal_harvesting(delta=0.05).elements[0].delta
0.05
Source code in src/skcomplex/models/economics/resources/_optimal_harvesting.py
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
@register_model(
    field='economics/resources',
    tags=('optimal control', 'harvesting', 'bang-bang', 'singular arc', 'ode'),
    references=('Clark 1990',),
    defaults={
        'until': 20.0,
        'dt': 0.2,
        'solver': CollocationSolver(objective=_profit, sense='max', nodes=101, scheme='trapezoidal'),
    },
    observables=lambda system: [ObservableSpec('catch', _catch)],
)
def optimal_harvesting(
    x: float = 1.0,
    *,
    r: float = 1.0,
    K: float = 1.0,
    q: float = 1.0,
    p: float = 1.0,
    c: float = 0.2,
    delta: float = 0.1,
) -> System:
    """Return a logistic resource harvested with the effort that maximises its discounted profit.

    Run with an optimal control solver, the effort starts at a bound, drives the stock to the level `x*` where
    `r (1 - 2 x* / K) + c r (1 - x* / K) / (p q x* - c) = delta`, holds it there with the effort `r (1 - x* / K) / q`,
    and rises to its largest value at the end of the horizon, when the stock left has no value.

    Args:
        x:
            The initial stock.

        r:
            The intrinsic growth rate.

        K:
            The carrying capacity.

        q:
            The catchability.

        p:
            The price per unit of catch.

        c:
            The cost per unit of effort.

        delta:
            The discount rate.

    Returns:
        system:
            The model, with the stock `resource.x`, the effort `resource.effort`, and the observable `catch`.

    Examples:
        >>> from skcomplex.models.economics.resources import optimal_harvesting
        >>> optimal_harvesting(delta=0.05).elements[0].delta
        0.05
    """
    resource = HarvestedResourceElement('resource', x=x, r=r, K=K, q=q, p=p, c=c, delta=delta)
    return System(elements=[resource])

tragedy_of_the_commons(n_herders=20, *, width=20, height=20, cows=1, quota=None, regrowth=0.2, capacity=2.0, upkeep=0.3, random_state=0)

Return herders whose cows graze a shared meadow, each adding cows while a cow pays, until the grass collapses.

Every step every cow moves to the richest pasture it can reach, eats, and earns its herder milk, and the grass grows back logistically. Every herder adds a cow whenever a cow earned more than its upkeep, since the gain of a cow goes to its herder while the cost of the grass it eats falls on all. With more herders than the meadow carries, the herd grows past what the grass sustains, the grass collapses below a tenth of its capacity, and the cows starve, so the herd shrinks after it. A regulator with an adaptive quota of cows per herder, lowered when the grass runs short and raised when it recovers, keeps the meadow from collapse (Hamill and Gilbert 2016 sec. 10.2).

Parameters:

Name Type Description Default
n_herders int

The number of herders.

20
width int

The number of columns of the meadow.

20
height int

The number of rows of the meadow.

20
cows int

The initial number of cows of every herder.

1
quota float | None

The initial quota of cows per herder of a regulator that adapts it, or None for no regulation.

None
regrowth float

The logistic growth rate of the grass.

0.2
capacity float

The most grass a cell holds.

2.0
upkeep float

The cost of keeping a cow for a step.

0.3
random_state int | Generator | None

The seed or generator of the cells of the cows.

0

Returns:

Name Type Description
system System

The model, with the groups meadow, cows, and herders, whose adaptive parameter quota the regulator regulator changes, and the grazing grazing.

Examples:

>>> from skcomplex.models.economics.resources import tragedy_of_the_commons
>>> [group.size for group in tragedy_of_the_commons(4, width=5, height=5).elements]
[25, 4, 4]
Source code in src/skcomplex/models/economics/resources/_commons.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
@register_model(
    field='economics/resources',
    tags=('commons', 'agents', 'overgrazing', 'regulation', 'grid'),
    references=('Hamill and Gilbert 2016 sec. 10.2', 'Hardin 1968'),
    defaults={'until': 100},
    observables=_commons_observables,
)
def tragedy_of_the_commons(
    n_herders: int = 20,
    *,
    width: int = 20,
    height: int = 20,
    cows: int = 1,
    quota: float | None = None,
    regrowth: float = 0.2,
    capacity: float = 2.0,
    upkeep: float = 0.3,
    random_state: int | np.random.Generator | None = 0,
) -> System:
    """Return herders whose cows graze a shared meadow, each adding cows while a cow pays, until the grass collapses.

    Every step every cow moves to the richest pasture it can reach, eats, and earns its herder milk, and the grass
    grows back logistically. Every herder adds a cow whenever a cow earned more than its upkeep, since the gain of a
    cow goes to its herder while the cost of the grass it eats falls on all. With more herders than the meadow
    carries, the herd grows past what the grass sustains, the grass collapses below a tenth of its capacity, and the
    cows starve, so the herd shrinks after it. A regulator with an adaptive quota of cows per herder, lowered when the
    grass runs short and raised when it recovers, keeps the meadow from collapse (Hamill and Gilbert 2016 sec. 10.2).

    Args:
        n_herders:
            The number of herders.

        width:
            The number of columns of the meadow.

        height:
            The number of rows of the meadow.

        cows:
            The initial number of cows of every herder.

        quota:
            The initial quota of cows per herder of a regulator that adapts it, or `None` for no regulation.

        regrowth:
            The logistic growth rate of the grass.

        capacity:
            The most grass a cell holds.

        upkeep:
            The cost of keeping a cow for a step.

        random_state:
            The seed or generator of the cells of the cows.

    Returns:
        system:
            The model, with the groups `meadow`, `cows`, and `herders`, whose adaptive parameter `quota` the regulator
            `regulator` changes, and the grazing `grazing`.

    Examples:
        >>> from skcomplex.models.economics.resources import tragedy_of_the_commons
        >>> [group.size for group in tragedy_of_the_commons(4, width=5, height=5).elements]
        [25, 4, 4]
    """
    rng = np.random.default_rng(random_state)
    cells = [(x, y) for x in range(width) for y in range(height)]
    meadow = GroupElement(
        'meadow',
        element=PastureElement,
        size=len(cells),
        values={'grass': capacity, 'capacity': capacity, 'regrowth': regrowth},
        location=cells,
    )
    herders = GroupElement(
        'herders',
        element=HerderElement,
        size=n_herders,
        values={
            'upkeep': upkeep,
            'quota': UNLIMITED if quota is None else quota,
        },
        location=[cells[0]] * n_herders,
    )
    n_cows = n_herders * cows
    herd = GroupElement(
        'cows',
        element=CowElement,
        size=n_cows,
        values={'owner': np.repeat(np.arange(n_herders, dtype=float), cows)},
        variable=True,
        location=[cells[index] for index in rng.integers(len(cells), size=n_cows)],
    )
    regulator = QuotaRegulatorInteraction('regulator', phase='herding', regulated=float(quota is not None))
    grazing = GrazingInteraction(
        'grazing',
        requesters='cows',
        providers='meadow',
        rule=PartnerSpec('local', 'score', score=GrazingInteraction.score, clearing='provider'),
        herders='herders',
    )
    return System(
        elements=[meadow, herd, herders],
        interactions=[grazing, *_get_investment(), regulator],
        space=LatticeSpace((width, height), boundary='periodic', neighborhood='moore'),
        time=DiscreteTime(
            schedule=PhasesScheduleSpec([PhaseSpec('grazing'), PhaseSpec('growth'), PhaseSpec('herding')]),
        ),
    )

von_thunen(size=41, *, yields=(4.0, 2.0, 1.0), prices=(3.0, 3.0, 3.5), costs=(1.0, 0.5, 0.5), rates=(0.2, 0.2, 0.2))

Return a square plain around one market at its centre, whose every cell grows the crop of highest bid rent.

A crop of a high yield per cell bids most near the market, and its bid falls fastest with distance, so the land use forms concentric rings whose boundaries lie where the bids of neighbouring crops are equal (Giordano 2024 §7).

Parameters:

Name Type Description Default
size int

The side of the plain in cells, one unit of distance each.

41
yields Sequence[float]

The yield of every crop per cell.

(4.0, 2.0, 1.0)
prices Sequence[float]

The price of every crop at the market.

(3.0, 3.0, 3.5)
costs Sequence[float]

The cost of every crop per unit of yield.

(1.0, 0.5, 0.5)
rates Sequence[float]

The transport rate of every crop per unit of yield and of distance.

(0.2, 0.2, 0.2)

Returns:

Name Type Description
system System

The model, with the states plain.crop and plain.distance.

Examples:

>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models.economics.resources import von_thunen
>>> trajectory = Simulation(von_thunen()).check().prepare().run(1.0).trajectory_
>>> sorted(set(trajectory.values('plain.crop')[0].ravel().tolist()))
[0.0, 1.0, 2.0, 3.0]
Source code in src/skcomplex/models/economics/resources/_land_use.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@register_model(
    field='economics/resources',
    tags=('land use', 'bid rent', 'static', 'lattice'),
    references=('Giordano 2024, arXiv 2407.02181', 'von Thünen 1826'),
)
def von_thunen(
    size: int = 41,
    *,
    yields: Sequence[float] = (4.0, 2.0, 1.0),
    prices: Sequence[float] = (3.0, 3.0, 3.5),
    costs: Sequence[float] = (1.0, 0.5, 0.5),
    rates: Sequence[float] = (0.2, 0.2, 0.2),
) -> System:
    """Return a square plain around one market at its centre, whose every cell grows the crop of highest bid rent.

    A crop of a high yield per cell bids most near the market, and its bid falls fastest with distance, so the land use
    forms concentric rings whose boundaries lie where the bids of neighbouring crops are equal (Giordano 2024 §7).

    Args:
        size:
            The side of the plain in cells, one unit of distance each.

        yields:
            The yield of every crop per cell.

        prices:
            The price of every crop at the market.

        costs:
            The cost of every crop per unit of yield.

        rates:
            The transport rate of every crop per unit of yield and of distance.

    Returns:
        system:
            The model, with the states `plain.crop` and `plain.distance`.

    Examples:
        >>> from skcomplex.engine.runtime import Simulation
        >>> from skcomplex.models.economics.resources import von_thunen
        >>> trajectory = Simulation(von_thunen()).check().prepare().run(1.0).trajectory_
        >>> sorted(set(trajectory.values('plain.crop')[0].ravel().tolist()))
        [0.0, 1.0, 2.0, 3.0]
    """
    centre = (size - 1) / 2
    rows, columns = np.indices((size, size))
    distance = np.hypot(rows - centre, columns - centre)
    plain = FieldElement(
        'plain',
        element=LandCellElement,
        space=LatticeSpace((size, size)),
        values={'distance': distance},
    )
    bid = BidRentInteraction('bid', target='plain', yields=yields, prices=prices, costs=costs, rates=rates)
    return System(elements=[plain], interactions=[bid], time=StaticTime())