Three-sector purchasing power decoupled by feedback
Forrest's categorized purchasing power of money, three sectors that feedback can steer one by one.
- Name:
categorized_purchasing_power - Field:
economics/macroeconomics - Formalism: ordinary differential equations
- References: Forrest 2018
Select this system
from skcomplex.systems import create_simulation, create_system, describe
system = create_system('categorized_purchasing_power')
entry = describe('categorized_purchasing_power')
trajectory = create_simulation('categorized_purchasing_power').prepare().run().trajectory_
Factory
@register_system(
field='economics/macroeconomics',
tags=('purchasing power', 'monetary policy', 'fiscal policy', 'decoupling', 'feedback', 'linear system', 'ode'),
references=('Forrest 2018',),
defaults={'until': 15.0, 'dt': 0.05},
observables=_observables,
)
def categorized_purchasing_power(
policy: Sequence[float] = (0.0, 0.0, 1.0),
policy_time: Sequence[float] = (1.0, 1.0, 1.0),
*,
feedback: bool = True,
A: Sequence[Sequence[float]] = A_MATRIX,
B: Sequence[Sequence[float]] = B_MATRIX,
C: Sequence[Sequence[float]] = C_MATRIX,
D: Sequence[Sequence[float]] = D_MATRIX,
) -> System:
"""Return Forrest's categorized purchasing power of money, three sectors that feedback can steer one by one.
The economy is split into the sectors of the basic, the desired, and the luxurious living conditions, with the
gaps `z_i = D_i - S_i` between the demand and the supply of money of each, which the categorized monetary and
fiscal policies `x_i` move through `z' = A z + B x`, and the indices of the sectors `y = C z + D x`, from the zero
state (Forrest 2018, Eqs. 22.6 and 22.7). The policies of one sector act on all of them. By Theorem 11.3 and Example
11.2 the system can be decoupled by feedback into the systems `w' = A w + B_i x_i` and `y_i = C_i w + D_i x_i` of
the sectors (Eqs. 22.9 to 22.12), whose market reactions keep each index on its own policy, so money poured into the
luxury sector leaves the basic sector alone. The source gives no numbers, so the matrices are illustrative, with a
stable `A` and a stable `A - B D^-1 C`, and the policies are steps.
Args:
policy:
The size of the step policy of every sector.
policy_time:
The time of the step policy of every sector.
feedback:
Whether the feedback component decouples the sectors.
A:
The matrix of the state.
B:
The matrix of the policies in the state.
C:
The matrix of the state in the indices.
D:
The nonsingular matrix of the policies in the indices.
Returns:
system:
The system, with the gaps `economy.z1` to `economy.z3` and, with feedback, the decoupled states
`economy.w11` to `economy.w33`. The observables of the system are the indices `economy.y1` to
`economy.y3` and the inputs of the economy `economy.u1` to `economy.u3`.
Examples:
>>> from skcomplex.systems.economics.macroeconomics import categorized_purchasing_power
>>> categorized_purchasing_power(feedback=False).elements[0].feedback
False
"""
variables = {
name: DeclaredVariableSpec(float(value), ParameterSpec())
for i in range(SECTORS)
for name, value in ((f'policy{i + 1}', policy[i]), (f'policy_time{i + 1}', policy_time[i]))
}
economy = PurchasingPowerElement('economy', variables=variables, A=A, B=B, C=C, D=D, feedback=feedback)
return System(elements=[economy])
Components
Elements:
PurchasingPowerElement, fromDeclaredElement
class PurchasingPowerElement(DeclaredElement):
"""National economy of three spending sectors, `z' = A z + B u` and `y = C z + D u`, with sector policies.
The state `z_i` is the gap between the demand and the supply of money of sector `i`, the basic, the desired, and
the luxurious living conditions, the output `y_i` is the index of the sector, and the policy `x_i` of a sector is a
step of size `policy_i` at the time `policy_time_i`. Without feedback the economy takes the policies as its inputs,
`u = x`. With feedback, the market reactions of Forrest's feedback component set `u` so that every sector responds
to its own policy only, as the decoupled systems `w_i' = A w_i + B_i x_i` and `y_i = C_i w_i + D_i x_i`, whose
states `w_ik` the element holds. The feedback component holds a copy of `z` that equals `z` from the common zero
start, so `u = D^-1 (y - C z)` with the decoupled outputs `y`.
Args:
label:
The label of the economy.
visual:
How the element is drawn, or `None` for `default_visual_`.
variables:
Declared states and parameters that replace the derived ones, by name.
A:
The matrix of the state.
B:
The matrix of the inputs in the state.
C:
The matrix of the state in the outputs.
D:
The nonsingular matrix of the inputs in the outputs.
feedback:
Whether the feedback component decouples the sectors.
Examples:
>>> from skcomplex.systems.economics.macroeconomics import PurchasingPowerElement
>>> PurchasingPowerElement('economy', feedback=True).get_value('z1')
0.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
variables: Mapping[str, DeclaredVariableSpec] | None = None,
A: Sequence[Sequence[float]] = A_MATRIX,
B: Sequence[Sequence[float]] = B_MATRIX,
C: Sequence[Sequence[float]] = C_MATRIX,
D: Sequence[Sequence[float]] = D_MATRIX,
feedback: bool = False,
) -> None:
super().__init__(label, visual=visual, variables=variables)
self.A = A
self.B = B
self.C = C
self.D = D
self.feedback = feedback
def _declare_variables(self) -> Mapping[str, DeclaredVariableSpec]:
"""Return the states `z_i`, with feedback `w_ik`, starting at zero, and the sizes and times of the policies."""
states = [f'z{i}' for i in range(1, SECTORS + 1)]
if self.feedback:
states += [f'w{i}{k}' for i in range(1, SECTORS + 1) for k in range(1, SECTORS + 1)]
declared = {name: DeclaredVariableSpec(0.0, StateSpec()) for name in states}
for i in range(1, SECTORS + 1):
declared[f'policy{i}'] = DeclaredVariableSpec(0.0, ParameterSpec())
declared[f'policy_time{i}'] = DeclaredVariableSpec(1.0, ParameterSpec())
return {**declared, **(self.variables or {})}
@staticmethod
def _policies(s: SimpleNamespace) -> list[Expression]:
"""Return the policies of the sectors, steps of their sizes at their times."""
return [step(s.t, getattr(s, f'policy_time{i}'), getattr(s, f'policy{i}')) for i in range(1, SECTORS + 1)]
def get_outputs(self, s: SimpleNamespace) -> list[Expression]:
"""Return the indices `y` of the sectors.
Args:
s:
The namespace of the economy.
Returns:
outputs:
One index per sector.
"""
x, C, D = self._policies(s), np.asarray(self.C), np.asarray(self.D)
if not self.feedback:
z = [getattr(s, f'z{i}') for i in range(1, SECTORS + 1)]
return [sum(C[i, k] * z[k] + D[i, k] * x[k] for k in range(SECTORS)) for i in range(SECTORS)]
return [
sum(C[i, k] * getattr(s, f'w{i + 1}{k + 1}') for k in range(SECTORS)) + D[i, i] * x[i]
for i in range(SECTORS)
]
def get_inputs(self, s: SimpleNamespace) -> list[Expression]:
"""Return the inputs `u` of the economy, the policies, or `D^-1 (y - C z)` with feedback.
Args:
s:
The namespace of the economy.
Returns:
inputs:
One input per sector.
"""
if not self.feedback:
return self._policies(s)
C, inverse = np.asarray(self.C), np.linalg.inv(np.asarray(self.D, dtype=float))
z, y = [getattr(s, f'z{i}') for i in range(1, SECTORS + 1)], self.get_outputs(s)
gaps = [y[i] - sum(C[i, k] * z[k] for k in range(SECTORS)) for i in range(SECTORS)]
return [sum(inverse[i, k] * gaps[k] for k in range(SECTORS)) for i in range(SECTORS)]
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the rates of the economy and, with feedback, of the decoupled systems.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the element.
"""
A, B = np.asarray(self.A), np.asarray(self.B)
z, u = [getattr(s, f'z{i}') for i in range(1, SECTORS + 1)], self.get_inputs(s)
laws: list[BaseLaw] = [
RateLaw(z[i], sum(A[i, k] * z[k] + B[i, k] * u[k] for k in range(SECTORS))) for i in range(SECTORS)
]
if self.feedback:
x = self._policies(s)
for i in range(SECTORS):
w = [getattr(s, f'w{i + 1}{k + 1}') for k in range(SECTORS)]
laws += [
RateLaw(w[j], sum(A[j, k] * w[k] for k in range(SECTORS)) + B[j, i] * x[i]) for j in range(SECTORS)
]
return laws
Equations
economy.z1: diff(z1_economy(t), t) == 0.9900398406374502*w11_economy(t) + 0.29701195219123505*w12_economy(t) + 0.0019920318725099606*w22_economy(t) + 0.0005976095617529881*w23_economy(t) + 0.019920318725099605*w31_economy(t) + 0.09960159362549802*w33_economy(t) + 0.0019920318725099606*where(t >= policy_time2_economy, policy2_economy, 0) + 0.9900398406374502*where(t >= policy_time1_economy, policy1_economy, 0) + 0.09960159362549802*where(t >= policy_time3_economy, policy3_economy, 0) - 2.00996015936255*z1_economy(t) + 0.000996015936254952*z2_economy(t) + 0.099800796812749*z3_economy(t)
economy.z2: diff(z2_economy(t), t) == 0.099601593625498*w11_economy(t) + 0.029880478087649404*w12_economy(t) + 0.9800796812749004*w22_economy(t) + 0.2940239043824701*w23_economy(t) + 0.0007968127490039847*w31_economy(t) + 0.003984063745019917*w33_economy(t) + 0.9800796812749004*where(t >= policy_time2_economy, policy2_economy, 0) + 0.099601593625498*where(t >= policy_time1_economy, policy1_economy, 0) + 0.003984063745019917*where(t >= policy_time3_economy, policy3_economy, 0) + 0.099601593625498*z1_economy(t) - 1.8099601593625498*z2_economy(t) + 0.0019920318725099306*z3_economy(t)
economy.z3: diff(z3_economy(t), t) == 0.10159362549800798*w11_economy(t) + 0.030478087649402394*w12_economy(t) + 0.07968127490039842*w22_economy(t) + 0.02390438247011952*w23_economy(t) + 0.19681274900398407*w31_economy(t) + 0.9840637450199203*w33_economy(t) + 0.07968127490039842*where(t >= policy_time2_economy, policy2_economy, 0) + 0.10159362549800798*where(t >= policy_time1_economy, policy1_economy, 0) + 0.9840637450199203*where(t >= policy_time3_economy, policy3_economy, 0) - 0.19840637450199206*z1_economy(t) + 0.2898406374501992*z2_economy(t) - 1.60796812749004*z3_economy(t)
economy.w11: diff(w11_economy(t), t) == -1.0*w11_economy(t) + 0.3*w12_economy(t) + 0.2*w13_economy(t) + 1.0*where(t >= policy_time1_economy, policy1_economy, 0)
economy.w12: diff(w12_economy(t), t) == 0.2*w11_economy(t) - 0.8*w12_economy(t) + 0.3*w13_economy(t) + 0.1*where(t >= policy_time1_economy, policy1_economy, 0)
economy.w13: diff(w13_economy(t), t) == 0.1*w11_economy(t) + 0.4*w12_economy(t) - 0.6*w13_economy(t) + 0.2*where(t >= policy_time1_economy, policy1_economy, 0)
economy.w21: diff(w21_economy(t), t) == -1.0*w21_economy(t) + 0.3*w22_economy(t) + 0.2*w23_economy(t) + 0.2*where(t >= policy_time2_economy, policy2_economy, 0)
economy.w22: diff(w22_economy(t), t) == 0.2*w21_economy(t) - 0.8*w22_economy(t) + 0.3*w23_economy(t) + 1.0*where(t >= policy_time2_economy, policy2_economy, 0)
economy.w23: diff(w23_economy(t), t) == 0.1*w21_economy(t) + 0.4*w22_economy(t) - 0.6*w23_economy(t) + 0.1*where(t >= policy_time2_economy, policy2_economy, 0)
economy.w31: diff(w31_economy(t), t) == -1.0*w31_economy(t) + 0.3*w32_economy(t) + 0.2*w33_economy(t) + 0.1*where(t >= policy_time3_economy, policy3_economy, 0)
economy.w32: diff(w32_economy(t), t) == 0.2*w31_economy(t) - 0.8*w32_economy(t) + 0.3*w33_economy(t) + 0.2*where(t >= policy_time3_economy, policy3_economy, 0)
economy.w33: diff(w33_economy(t), t) == 0.1*w31_economy(t) + 0.4*w32_economy(t) - 0.6*w33_economy(t) + 1.0*where(t >= policy_time3_economy, policy3_economy, 0)