library

This module has vocation to concentrate all the functions mentioned in input data files.

Todo

Equations’ indexes are to be improved again.

Note

Contact us as soon as you see something wrong and/or, ambiguous, which is likely given the very early stage of development we are in. Moreover, methods that rely on the structure of their arguments will sooner or later be turned structure-independent.

class iamax.library.ArraysDealer

Mixin class that aggregates a bunch of private methods related to (numpy) arrays processing.

static _take(ary: np.ndarray, idx: int, axis: int = 0, keepdims: bool = False)

Partialized version of numpy.take with mode='clip' to prevent negative indexing and/or IndexError.

Parameters
  • ary (numpy.ndarray) – Array to be sliced.

  • idx (int) – Integer index of interest along axis axis.

  • axis (int) – Axis the slice must be taken along. 0 by default.

  • keepdims (bool) – Whether the reduced axis is left in the result as size-one dimension. To False by default.

Note

The original numpy.take function does not feature the keepdims argument, which this method does because of the API divergence on that matter between JAX and NumPy.

Example
>>> ary = np.arange(6).reshape(3, 2)
>>> ary
array([[0, 1],
       [2, 3],
       [4, 5]])
>>> ArraysDealer._take(ary=ary, idx=0)
array([0, 1])
>>> ArraysDealer._take(ary=ary, idx=-1)
array([0, 1])
>>> ArraysDealer._take(ary=ary, idx=2)
array([4, 5])
>>> ArraysDealer._take(ary=ary, idx=3)
array([4, 5])
>>> ArraysDealer._take(ary=ary, idx=3, keepdims=True)
array([[4, 5]])
static _split(ary: np.ndarray, indices: list[int], axis: int = 0)

Split an array into multiple sub-arrays as views into ary.

Parameters
  • ary (np.ndarray) – Array to be processed.

  • indices (list) – Sequence of sorted integers indicating where ary is to be split.

  • axis (int) – The axis along which to split. Set to 0 by default.

Note

Compared to its numpy.split() counterpart, this method can be identity.

Example
>>> ArraysDealer._split(ary=np.arange(3), indices=[1, -1])
[array([0]), array([1]), array([2])]
>>> ArraysDealer._split(ary=np.arange(3), indices=[1, None])
[array([0]), array([1, 2]), array([], dtype=int32)]
>>> ArraysDealer._split(ary=np.arange(3), indices=[0, -1])
[array([], dtype=int32), array([0, 1]), array([2])]
>>> ArraysDealer._split(ary=np.arange(3), indices=[0, 2])
[array([], dtype=int32), array([0, 1]), array([2])]
>>> ArraysDealer._split(ary=np.arange(3), indices=[0, None])
[array([], dtype=int32), array([0, 1, 2]), array([], dtype=int32)]
static _infs_to_num(x: np.ndarray, posinf: float = _posinf, neginf: float = _neginf)

Replace infinity with numbers.

Parameters
  • x (numpy.ndarray) – Array to be processed.

  • posinf (float) – Value to be used to fill positive infinity values. Set to numpy.finfo(float).max by default.

  • neginf (float) – Value to be used to fill negative infinity values. Set to numpy.finfo(float).min by default.

Example
>>> ArraysDealer._infs_to_num(
...     x=np.array([np.inf, -np.inf, np.nan]),
...     posinf=1, neginf=-1
... )
array([ 1., -1., nan])
classmethod _sum(cls, a: np.ndarray, axis: int = None, keepdims: bool = False)

Pro-differentiation NAN-immunized sum of array elements over a given axis.

Parameters
  • ary (numpy.ndarray) – Array to be processed.

  • axis (int) – Axis or axes along which a sum is performed. To None by default.

  • keepdims (bool) – Whether the reduced axis is left in the result as size-one dimension. To False by default.

Note

This (class) method requires a clipping upper bound to prevent floating-point overflow during computations. When summing large values, intermediate results can exceed the maximum representable value for numpy.float64. Additionally, in the context of second-order derivatives (Hessian), quadratic growth amplifies these values, further increasing the risk of overflow. Clipping ensures numerical stability by constraining intermediate results to a manageable range while preserving the overall summation behavior.

static _divide(n: np.ndarray, d: np.ndarray, nan: float = 0.0, num_infs: bool = False)

Pro-differentiation NAN-immunized divider.

Parameters
  • n (numpy.ndarray) – Numerator operand.

  • d (numpy.ndarray) – Denominator operand.

  • nan (float or numpy.ndarray) – Value to be used to fill float('nan') values. Set to 0. by default.

  • num_infs (boolean) – Whether positive and negative infinity are to replaced with large finite numbers. Set to False by default.

Example
>>> ArraysDealer._divide(
...     n=(n := np.ones(3)),
...     d=np.zeros_like(n),
... )
array([0., 0., 0.])
static _multiply(a: np.ndarray, b: np.ndarray)

Pro-differentiation NAN-immunized to-0 multiplicator.

Parameters
Example
>>> a, b = 0, float('inf')
>>> a * b
nan
>>> ArraysDealer._multiply(a, b)
array(0.)

Note

Implying this method may not be sufficient if. e.g one of the operands potentially consists in a by-0-division.

classmethod _rdistance(cls, f: np.ndarray, c: np.ndarray)

Quasi-scale-invariant division-by-0-immunized symmetrical signed distance computer.

(174)\[D(\boldsymbol{f}, \boldsymbol{c}) = \left( \frac{ \boldsymbol{f}-\boldsymbol{c} }{ \sqrt{ \varepsilon^{2} + \boldsymbol{f}^{2} + \boldsymbol{c}^{2} } } \right)\]
Parameters
Example
>>> ArraysDealer._rdistance(f=np.array(2), c=np.array(3))
0.27732343367971996
>>> ArraysDealer._rdistance(f=np.array(3), c=np.array(2))
-0.27732343367971996
>>> ArraysDealer._rdistance(f=np.array(2), c=np.array(0))
-0.9996876464081226
>>> ArraysDealer._rdistance(f=np.array(0), c=np.array(2))
0.9996876464081226
>>> ArraysDealer._rdistance(f=np.ones(3), c=np.arange(3))
array([-1.  ,  0.  ,  0.45])
classmethod _constancy(cls, ary: np.ndarray, axis: int = None, keepdims: bool = False, where: np.ndarray[bool]|bool = True)

Compute a dimensionless smooth subgradient-based manhattan “constancy” metric along a specified axis.

Parameters
  • ary (numpy.ndarray) – Array to be processed.

  • axis (int) – Axis the process must be performed along. None by default.

  • keepdims (bool) – Whether the reduced axis is left in the result as size-one dimension. To False by default.

  • where (numpy.ndarray or bol) – Elements to include in the sum. Set to True by default, which boils down to include all elements.

Example
>>> a = np.array([[-1., .0, 1.]])
>>> ArraysDealer._constancy(ary=a, keepdims=True)
array([[0.82]])
>>> v = np.stack((a, np.ones_like(a), np.zeros_like(a)))
>>> ArraysDealer._constancy(ary=v, axis=2, keepdims=True)
array([[[0.82]],
       [[0.  ]],
       [[0.  ]]])
classmethod _power(cls, x1: np.ndarray, x2: np.ndarray, num_infs: bool = False, div_by_0_to_0: bool = True, compute_croots: bool = False)

Pro-differentiation NAN-immunized power operator.

Parameters
  • x1 (numpy.ndarray) – Bases of exponentiation.

  • x2 (numpy.ndarray) – Exponents.

  • num_infs (boolean) – Whether positive and negative infinity are to replaced with large finite numbers. Set to False by default.

  • div_by_0_to_0 (bool) – Whether divisions by 0 should result in 0s instead of resulting in numpy.inf. Set to True by default.

  • compute_croots (bool) – Whether complex roots are allowed. Set to False by default.

Attention

Argument compute_croots isn’t well tested. Do not use unless you know exactly what you are doing.

Order-dependent special cases below are expressed relative to the real-axis reference values \(-1\), \(0\), \(1\) and \(\pm\infty\). They therefore apply only to operands lying on that same complex line. Genuinely non-real finite operands bypass these real-domain branches and are evaluated by numpy.power.

Complex-valued infinite limits are instead characterized through their modulus. For a finite complex base \(z\) and a real exponent tending to \(\pm\infty\), convergence to \(0\) or infinity depends on whether \(|z|\) is respectively below or above \(1\). Conversely, for a complex base whose modulus tends to infinity and a finite exponent \(w\), convergence depends on the sign of \(\Re(w)\).

Boundary configurations for which no unique complex limit exists are mapped to 0. as a NAN-immunization convention. Such values must not be interpreted as mathematical limits.

Example
>>> ꚙ = np.inf
>>> x1 = np.array([
...     1, .5, .5, 2, 2, -.5, -.5, -2, -2, -2, 0, 0, ꚙ, ꚙ, ꚙ, -ꚙ,
... ])
>>> x2 = np.array([
...     ꚙ, -ꚙ, ꚙ, -ꚙ, ꚙ, -ꚙ, ꚙ, -ꚙ,  ꚙ, -.5, ꚙ, -ꚙ, ꚙ, -ꚙ, 0, 0,
... ])
>>> np.vstack((
...     x1, x2,
...     ArraysDealer._power(
...         x1, x2, num_infs=False, div_by_0_to_0=False,
...     ),
... )).T
array([[ 1. ,  inf,  1. ],
       [ 0.5, -inf,  inf],
       [ 0.5,  inf,  0. ],
       [ 2. , -inf,  0. ],
       [ 2. ,  inf,  inf],
       [-0.5, -inf,  inf],
       [-0.5,  inf,  0. ],
       [-2. , -inf,  0. ],
       [-2. ,  inf,  inf],
       [-2. , -0.5,  0. ],
       [ 0. ,  inf,  0. ],
       [ 0. , -inf,  inf],
       [ inf,  inf,  inf],
       [ inf, -inf,  0. ],
       [ inf,  0. ,  1. ],
       [-inf,  0. ,  1. ]])
>>> np.vstack((
...     x1, x2,
...     ArraysDealer._power(
...         x1, x2, num_infs=False, div_by_0_to_0=False,
...         compute_croots=True
...     ),
... )).T
array([[ 1. +0.j,  inf+0.j,  1. +0.j],
       [ 0.5+0.j, -inf+0.j,  inf+0.j],
       [ 0.5+0.j,  inf+0.j,  0. +0.j],
       [ 2. +0.j, -inf+0.j,  0. +0.j],
       [ 2. +0.j,  inf+0.j,  inf+0.j],
       [-0.5+0.j, -inf+0.j,  inf+0.j],
       [-0.5+0.j,  inf+0.j,  0. +0.j],
       [-2. +0.j, -inf+0.j,  0. +0.j],
       [-2. +0.j,  inf+0.j,  inf+0.j],
       [-2. +0.j, -0.5+0.j, -2. +0.j],
       [ 0. +0.j,  inf+0.j,  0. +0.j],
       [ 0. +0.j, -inf+0.j,  inf+0.j],
       [ inf+0.j,  inf+0.j,  inf+0.j],
       [ inf+0.j, -inf+0.j,  0. +0.j],
       [ inf+0.j,  0. +0.j,  1. +0.j],
       [-inf+0.j,  0. +0.j,  1. +0.j]])

Genuinely complex finite bases are instead preserved as complex operands.

>>> x1 = np.array(
...     -0.0020594357921656713
...     - 0.003567047426956803j
... )
>>> ArraysDealer._power(x1, np.array(-1.5))
array(-3782.97+4.63e-13j)
classmethod _weighter(cls, a: np.ndarray, axis: int = -1, keepdims: bool = False)

s/e.

Parameters
  • ary (numpy.ndarray) – Array to be processed.

  • axis (int) – Axis or axes along which a sum is performed. To None by default.

  • keepdims (bool) – Whether the reduced axis is left in the result as size-one dimension. To False by default.

static _lminusr_computer(a: np.ndarray, keepdims: bool, axis: int = -1)

Compute the difference between the sum of the first (left) half and the sum of the second (right) half along the specified axis.

Parameters
  • a (numpy.ndarray) – Array of even horizontal size.

  • keepdims (bool) – Whether the output has the same number of dimensions as the input.

  • axis (int) – Axis or axes along which a sum is performed. Set to -1 bu default.

Example
>>> (a := np.array([[1, 2, 3, 4], [3, 4, 1, 2]]))
array([[1, 2, 3, 4],
       [3, 4, 1, 2]])
>>> ArraysDealer._lminusr_computer(a, keepdims=True)
array([[-4],
       [ 4]])

Another example showing that the first half is favored in case of uneven split.

>>> (a := np.array([[1, 1, 2, 3, 4], [0, 3, 4, 1, 2]]))
array([[1, 1, 2, 3, 4],
       [0, 3, 4, 1, 2]])
>>> ArraysDealer._lminusr_computer(a, keepdims=True)
array([[-3],
       [ 4]])
class iamax.library.RatesDealer

Mixin class that aggregates a bunch of private methods related to rates calculations, conversions, etc.

static _markup_to_margin_rater(mkr: np.ndarray|float)

Markup-to-margin rate converter.

\[\text{mgr} = \frac{\text{mkr}}{1 + \text{mkr}}\]
Parameters

mkr (numpy.ndarray or float) – Markup rate to be processed.

Example
>>> RatesDealer._markup_to_margin_rater(mkr=1.0).item()
0.5
static _margin_to_markup_rater(mgr: np.ndarray|float)

Margin-to-markup rate converter.

\[\text{mkr} = \frac{\text{mgr}}{1 - \text{mgr}}\]
Parameters

mgr (numpy.ndarray or float) – Margin rate to be processed.

Example
>>> RatesDealer._margin_to_markup_rater(mgr=.5).item()
1.0
class iamax.library.Passive(*_, **__)

Class to be (used directly or) inherited from to explicit the idea of an entity not actively at work and whose e.g. consumption/production is determined by the system’s set of constraints.

class iamax.library.AddedOPCosts

Class whose name is rather, say, self-explanatory.

static operating_cost_computer(*, operating_costs: np.ndarray)

s/e., \(1 \times 1\), denoted as

(175)\[v_{q,j} = \sum_{k \in \mcI_j} v_{q,k}\]

with \(\mcI_j\) the index set required to get \(v_{q,j}\).

Parameters

operating_costs (numpy.ndarray) – \((\bv_{q,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) operating_costs’s slice.

Example
>>> a = np.array([[1., 0., 4.]])
>>> AddedOPCosts.operating_cost_computer(
...     operating_costs=a
... )
array([[5.]])

Let’s vectorize the above example.

>>> AddedOPCosts.operating_cost_computer(
...     operating_costs=np.stack((a, .1*a))
... )
array([[[5. ]],
       [[0.5]]])
class iamax.library.AddedVolumes

Class whose name is rather, say, self-explanatory.

static employed_volume_computer(*, employed_volumes: np.ndarray)

s/e., \(1 \times 1\), denoted as

(176)\[q_j = \sum_{k \in \mcI_j} q_k\]

with \(\mcI_j\) the index set required to get \(q_j\).

Parameters

employed_volumes (numpy.ndarray) – \((\bq_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) employed_volumes’s slice.

Example
>>> a = np.array([[1., 0., 4.]])
>>> AddedVolumes.employed_volume_computer(
...     employed_volumes=a
... )
array([[5.]])

Let’s vectorize the above example.

>>> AddedVolumes.employed_volume_computer(
...     employed_volumes=np.stack((a, .1*a))
... )
array([[[5. ]],
       [[0.5]]])
static available_volume_rooter(*, t: int, available_volumes: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero, denoted as

(177)\[-1 + \frac{\hq_j}{\sum_{k \in \mcI_j} \hq_k} \approx 0\]

with \(\mcI_j\) the index set required to get \(\hq_j\).

Parameters

See also

\(|\mcL|\)’s archetype _ilocalizations.

Important

The variable t is involved to provide the current method with (column-bound) time series arguments. Indeed, the first element of such arguments is always associated with the variable that is being targeted.

Note

This method is vectorization friendly over the -3th axis, if any.

Example
>>> a = np.array([
...     [[0, 0, 0]],
...     [[5, 3, 2]],
...     [[6, 3, 0]],
...     [[3, 6, 0]],
... ])
>>> AddedVolumes.available_volume_rooter(
...     t=1, available_volumes=a
... )
array([[0.]])
>>> AddedVolumes.available_volume_rooter(
...     t=2, available_volumes=a
... )
array([[-0.45]])
>>> AddedVolumes.available_volume_rooter(
...     t=3, available_volumes=a
... )
array([[0.45]])

Let’s vectorize the above example, keeping in mind that the temporal axis must remain first (hence the numpy.ndarray.swapaxes below).

>>> (v := np.stack((a, .1*a)).swapaxes(1, 0))
array([[[[0. , 0. , 0. ]],
        [[0. , 0. , 0. ]]],
       [[[5. , 3. , 2. ]],
        [[0.5, 0.3, 0.2]]],
       [[[6. , 3. , 0. ]],
        [[0.6, 0.3, 0. ]]],
       [[[3. , 6. , 0. ]],
        [[0.3, 0.6, 0. ]]]])
>>> AddedVolumes.available_volume_rooter(
...     t=1, available_volumes=v
... )
array([[[0.]],
       [[0.]]])
>>> AddedVolumes.available_volume_rooter(
...     t=2, available_volumes=v
... )
array([[[-0.45]],
       [[-0.45]]])
>>> AddedVolumes.available_volume_rooter(
...     t=3, available_volumes=v
... )
array([[[0.45]],
       [[0.45]]])
class iamax.library.ImplicitTransitories

Class that abstracts the idea of economic agents who are just accounting artifacts that need, at some point, to be formalized explicitly regarding their “custom QOIs” only.

static transitory_computer(transitories: np.ndarray)

s/e., \(1 \times 1\), denoted as

(178)\[\mcr_j = \sum_{k \in \mcI_j} \mcr_k\]

with \(\mcI_j\) the index set required to get \(\mcr_j\).

Parameters

transitories (numpy.ndarray) – The \(j\)th \(1 \times |\mcI_j|\) slice of transitories.

Example
>>> a = np.array([[4., 6.]])
>>> ImplicitTransitories.transitory_computer(
...     transitories=a
... )
array([[10.]])

Let’s vectorize the above example for free.

>>> ImplicitTransitories.transitory_computer(
...     transitories=np.vstack((a, .1*a))
... )
array([[10.],
       [ 1.]])
class iamax.library.ImplicitIncome

Class that abstracts the idea of economic agents who are just accounting artifacts that need, at some point, to be formalized explicitly regarding their disposable income only.

static budget_constraint_computer(budget_constraints: np.ndarray)

s/e., \(1 \times 1\), denoted as

(179)\[\mcr_j = \sum_{k \in \mcI_j} \mcr_k\]

with \(\mcI_j\) the index set required to get \(\mcr_j\).

Parameters

budget_constraints (numpy.ndarray) – \((\bmcr_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) budget_constraints’s slice.

Example
>>> a = np.array([[4., 6.]])
>>> ImplicitIncome.budget_constraint_computer(
...     budget_constraints=a
... )
array([[10.]])

Let’s vectorize the above example for free.

>>> ImplicitIncome.budget_constraint_computer(
...     budget_constraints=np.vstack((a, .1*a))
... )
array([[10.],
       [ 1.]])
class iamax.library.ImplicitEntity
Inheritance diagram of iamax.library.ImplicitEntity

Class that abstracts the idea of economic agents who are just accounting artifacts that need, at some point, to be formalized explicitly.

Note

This class has not vocation to be vectorization friendly since implicit entities are recursively function of each others.

static other_costs_computer(other_costs: np.ndarray)

s/e., \(1 \times 1\), denoted as

(180)\[v_{\ootau,\ j} = \sum_{k \in \mcI_j} v_{\ootau,\ k}\]

with \(\mcI_j\) the index set required to get \(v_{\ootau,\ j}\).

Parameters

other_costs (numpy.ndarray) – \((\bv_{\ootau,\ k})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of other_costs.

static net_surplus_computer(net_surplus: np.ndarray)

s/e., \(1 \times 1\), denoted as

(181)\[\pi_{j} = \sum_{k \in \mcI_j} \pi_{k}\]

with \(\mcI_j\) the index set required to get \(\pi_{j}\).

Parameters

net_surplus (numpy.ndarray) – \((\bpi_{k})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of net_surplus.

static routed_private_income_computer(routed_private_incomes: np.ndarray)

s/e., \(1 \times 1\), denoted as

(182)\[\mcr_{\text{cum},\ j} = \sum_{k \in \mcI_j} \mcr_{\text{cum},\ k}\]

with \(\mcI_j\) the index set required to get \(\mcr_{\text{cum},\ j}\).

Parameters

routed_private_incomes (numpy.ndarray) – \((\bmcr_k)_{\text{cum},\ k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of routed_private_incomes.

static input_values_computer(input_values: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(183)\[\bmcV_{:,j} = \sum_{k \in \mcI_j} \bmcV_{:,k}\]

with \(\mcI_j\) the index set required to get \(\bmcV_{:,j}\).

Parameters

input_values (numpy.ndarray) – \((\bmcV_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) input_values’s slice.

See also

\(\mcI\)’s archetype _iidentifiers.

Example
>>> a = np.hstack((
...     _1s := np.ones((4,))[:, None],
...     _1s.cumsum(axis=0), .5*_1s,
... ))
>>> a
array([[1. , 1. , 0.5],
       [1. , 2. , 0.5],
       [1. , 3. , 0.5],
       [1. , 4. , 0.5]])
>>> ImplicitEntity.input_values_computer(input_values=a)
array([[2.5],
       [3.5],
       [4.5],
       [5.5]])

Let’s vectorize the above example.

>>> (v := np.stack((a, .1*a)))
array([[[1.  , 1.  , 0.5 ],
        [1.  , 2.  , 0.5 ],
        [1.  , 3.  , 0.5 ],
        [1.  , 4.  , 0.5 ]],
       [[0.1 , 0.1 , 0.05],
        [0.1 , 0.2 , 0.05],
        [0.1 , 0.3 , 0.05],
        [0.1 , 0.4 , 0.05]]])
>>> ImplicitEntity.input_values_computer(input_values=v)
array([[[2.5 ],
        [3.5 ],
        [4.5 ],
        [5.5 ]],
       [[0.25],
        [0.35],
        [0.45],
        [0.55]]])
static specific_margins_computer(specific_margins: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(184)\[\bV^{(\utau)}_{:,j} = \sum_{k \in \mcI_j} \bV^{(\utau)}_{:,k}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\utau)}_{:,j}\).

Parameters

specific_margins (numpy.ndarray) – \((\bV^{(\utau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) specific_margins’s slice.

static trade_margins_computer(trade_margins: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(185)\[\bV^{(\jtau)}_{:,j} = \sum_{k \in \mcI_j} \bV^{(\jtau)}_{:,k}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\jtau)}_{:,j}\).

Parameters

trade_margins (numpy.ndarray) – \((\bV^{(\jtau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) trade_margins’s slice.

static sales_taxes_computer(sales_taxes: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(186)\[\bV^{(\htau)}_{:,j} = \sum_{k \in \mcI_j} \bV^{(\htau)}_{:,k}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\htau)}_{:,j}\).

Parameters

sales_taxes (numpy.ndarray) – \((\bV^{(\htau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) sales_taxes’s slice.

static excises_computer(excises: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(187)\[\bV^{(\ctau)}_{:,j} = \sum_{k \in \mcI_j} \bV^{(\ctau)}_{:,k}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\ctau)}_{:,j}\).

Parameters

excises (numpy.ndarray) – \((\bV^{(\ctau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) excises’s slice.

class iamax.library.BalanceOfTrade

Accounting entity that consists in a given nation’s net exports.

static budget_constraint_computer(budget_constraints: np.ndarray)

s/e., \(1 \times 1\), denoted as

(188)\[\mcr_j = \sum_{k=1}^{|\mcI_j|/2} \mcr_k - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \mcr_k\]

with \(\mcI_j\) the index set required to get \(\mcr_j\), by default asserted to be of even cardinality.

Parameters

budget_constraints (numpy.ndarray) – \((\bmcr_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) budget_constraints’s slice.

Important

This method relies on the structure of its argument, with exports concentrated in the first half of budget_constraints and imports in the second.

Example
>>> a = np.array([[4., 16., 3., 2.]])
>>> BalanceOfTrade.budget_constraint_computer(
...     budget_constraints=a
... )
array([[15.]])

Let’s vectorize the above example

>>> (v := np.stack((a, a[:, ::-1])))
array([[[ 4., 16.,  3.,  2.]],
       [[ 2.,  3., 16.,  4.]]])
>>> BalanceOfTrade.budget_constraint_computer(
...     budget_constraints=v
... )
array([[[ 15.]],
       [[-15.]]])
static other_costs_computer(other_costs: np.ndarray)

s/e., \(1 \times 1\), denoted as

(189)\[v_{\ootau,\ j} = \sum_{k=1}^{|\mcI_j|/2} v_{\ootau,\ k} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} v_{\ootau,\ k}\]

with \(\mcI_j\) the index set required to get \(v_{\ootau,\ j}\), by default asserted to be of even cardinality.

Parameters

other_costs (numpy.ndarray) – \((\bv_{\ootau,\ k})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of other_costs.

Important

This method relies on the structure of its argument, with exports concentrated in the first half of other_costs and imports in the second.

static net_surplus_computer(net_surplus: np.ndarray)

s/e., \(1 \times 1\), denoted as

(190)\[\pi_{j} = \sum_{k=1}^{|\mcI_j|/2} \pi_{k} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \pi_{k}\]

with \(\mcI_j\) the index set required to get \(\pi_{j}\), by default asserted to be of even cardinality.

Parameters

net_surplus (numpy.ndarray) – \((\bpi_{k})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of net_surplus.

Important

This method relies on the structure of its argument, with exports concentrated in the first half of net_surplus and imports in the second.

static routed_private_income_computer(routed_private_incomes: np.ndarray)

s/e., \(1 \times 1\), denoted as

(191)\[\mcr_{\text{cum},\ j} = \sum_{k=1}^{|\mcI_j|/2} \mcr_{\text{cum},\ k} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \mcr_{\text{cum},\ k}\]

with \(\mcI_j\) the index set required to get \(\mcr_{\text{cum},\ j}\), by default asserted to be of even cardinality.

Parameters

routed_private_incomes (numpy.ndarray) – \((\bmcr_k)_{\text{cum},\ k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of routed_private_incomes.

Important

This method relies on the structure of its argument, with exports concentrated in the first half of routed_private_incomes and imports in the second.

static input_values_computer(input_values: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(192)\[\bmcV_{:,j} = \sum_{k=1}^{|\mcI_j|/2} \bmcV_{:,k} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \bmcV_{:,k}\]

with \(\mcI_j\) the index set required to get \(\bmcV_{:,j}\), by default asserted to be of even cardinality.

Parameters

input_values (numpy.ndarray) – \((\bmcV_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) input_values’s slice.

See also

\(\mcI\)’s archetype _iidentifiers.

Example
>>> a = np.hstack((
...     x := np.arange(-2, 3)[:, None],
...     m := np.ones_like(x),
... ))
>>> a
array([[-2,  1],
       [-1,  1],
       [ 0,  1],
       [ 1,  1],
       [ 2,  1]])
>>> BalanceOfTrade.input_values_computer(input_values=a)
array([[-3],
       [-2],
       [-1],
       [ 0],
       [ 1]])

Let’s vectorize the above example.

>>> (v := np.stack((a, -2*a)))
array([[[-2,  1],
        [-1,  1],
        [ 0,  1],
        [ 1,  1],
        [ 2,  1]],
       [[ 4, -2],
        [ 2, -2],
        [ 0, -2],
        [-2, -2],
        [-4, -2]]])
>>> BalanceOfTrade.input_values_computer(input_values=v)
array([[[-3],
        [-2],
        [-1],
        [ 0],
        [ 1]],
       [[ 6],
        [ 4],
        [ 2],
        [ 0],
        [-2]]])

Yet another example with an odd number of trade components.

>>> a = np.hstack((x , np.ones_like(x), m))
>>> (v := np.stack((a, -a)))
array([[[-2,  1,  1],
        [-1,  1,  1],
        [ 0,  1,  1],
        [ 1,  1,  1],
        [ 2,  1,  1]],
       [[ 2, -1, -1],
        [ 1, -1, -1],
        [ 0, -1, -1],
        [-1, -1, -1],
        [-2, -1, -1]]])
>>> BalanceOfTrade.input_values_computer(input_values=v)
array([[[-2],
        [-1],
        [ 0],
        [ 1],
        [ 2]],
       [[ 2],
        [ 1],
        [ 0],
        [-1],
        [-2]]])
static specific_margins_computer(specific_margins: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(193)\[\bV^{(\utau)}_{:,j} = \sum_{k=1}^{|\mcI_j|/2} \bV^{(\utau)}_{:,j} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \bV^{(\utau)}_{:,j}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\utau)}_{:,j}\), asserted to be of even cardinality.

Parameters

specific_margins (numpy.ndarray) – \((\bV^{(\utau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) specific_margins’s slice.

static trade_margins_computer(trade_margins: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(194)\[\bV^{(\jtau)}_{:,j} = \sum_{k=1}^{|\mcI_j|/2} \bV^{(\jtau)}_{:,j} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \bV^{(\jtau)}_{:,j}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\jtau)}_{:,j}\), asserted to be of even cardinality.

Parameters

trade_margins (numpy.ndarray) – \((\bV^{(\jtau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) trade_margins’s slice.

static sales_taxes_computer(sales_taxes: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(195)\[\bV^{(\htau)}_{:,j} = \sum_{k=1}^{|\mcI_j|/2} \bV^{(\htau)}_{:,j} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \bV^{(\htau)}_{:,j}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\htau)}_{:,j}\), asserted to be of even cardinality.

Parameters

sales_taxes (numpy.ndarray) – \((\bV^{(\htau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) sales_taxes’s slice.

static excises_computer(excises: np.ndarray)

s/e., \(|\mcI_j| \times 1\), denoted as

(196)\[\bV^{(\ctau)}_{:,j} = \sum_{k=1}^{|\mcI_j|/2} \bV^{(\ctau)}_{:,j} - \sum_{k=1 + |\mcI_j|/2}^{|\mcI_j|} \bV^{(\ctau)}_{:,j}\]

with \(\mcI_j\) the index set required to get \(\bV^{(\ctau)}_{:,j}\), asserted to be of even cardinality.

Parameters

excises (numpy.ndarray) – \((\bV^{(\ctau)}_{:,k})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI| \times |\mcI_j|\) excises’s slice.

class iamax.library.AQConserver

Class that captures the essence of the conservation law regarding additive quantities.

static additive_output_volume_rooter(output_volume: np.ndarray, aggregated_input_volumes: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

(197)\[\approx 0\]
Parameters
Example
>>> ovol   = 10.
>>> aivols = np.array([[6., 4.]])
>>> AQConserver.additive_output_volume_rooter(
...     output_volume=ovol, aggregated_input_volumes=aivols,
... )
array([[0.]])
>>> vovol = np.array([[[ovol]], [[.5*ovol]]])
>>> vovol
array([[[10.]],
       [[ 5.]]])
>>> vaivols = np.stack((aivols, .5*aivols[..., ::-1]))
>>> vaivols
array([[[6., 4.]],
       [[2., 3.]]])
>>> AQConserver.additive_output_volume_rooter(
...     output_volume=vovol, aggregated_input_volumes=vaivols,
... )
array([[[0.]],
       [[0.]]])
class iamax.library.SymmetricNodes

Class that captures the essence of entities balancing each other out through routed_private_income attribute.

static routed_private_income_rooter(routed_private_incomes: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

Parameters

routed_private_incomes (numpy.ndarray) – \((\bmcr_k)_{\text{cum},\ k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of routed_private_incomes.

Example
>>> a = np.array([[.9, -.9]])
>>> SymmetricNodes.routed_private_income_rooter(
...     routed_private_incomes=a
... )
array([[0.]])

Let’s vectorize the above example.

>>> v = np.stack((
...     np.array([[.5, -.5]]),
...     np.array([[.0, -.5]]),
...     np.array([[.5,  .0]]),
... ))
>>> SymmetricNodes.routed_private_income_rooter(
...     routed_private_incomes=v
... )
array([[[ 0.]],
       [[ 1.]],
       [[-1.]]])
class iamax.library.BalancedWorldTrade

Class that refers to the requirement in international economics that the sum of all trade balances across regions must equal zero. This identity reflects the idea that every export is matched by an import somewhere else, ensuring that total exports equal total imports when viewed globally.

Note

This class assumes that world trade is resolved in terms of physical volumes.

static spendings_dfree_rooter(spendings_dfree: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

Parameters

spendings_dfree (numpy.ndarray) – \((\bmcv_k)_{\neg\ttau,\ k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of spendings_dfree.

Example
>>> a = np.array([[.9, -.9]])
>>> BalancedWorldTrade.spendings_dfree_rooter(
...     spendings_dfree=a
... )
array([[0.]])

Let’s vectorize the above example.

>>> v = np.stack((
...     np.array([[.5, -.5]]),
...     np.array([[.0, -.5]]),
...     np.array([[.5,  .0]]),
... ))
>>> BalancedWorldTrade.spendings_dfree_rooter(
...     spendings_dfree=v
... )
array([[[ 0.]],
       [[ 1.]],
       [[-1.]]])
class iamax.library.Shareholders

Class that abstracts the idea of agents who jointly own 100% of an activity’s outstanding shares.

static income_private_circuit_rsto_rooter(income_private_circuit_rsto: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

(198)\[\approx 0\]
Parameters

income_private_circuit_rsto (numpy.ndarray) – \((\obtheta_{k})_{k \in \mcI_j}\), i.e. a \(1 \times |\mcI_j|\) slice of income_private_circuit_rsto.

Example
>>> a = np.array([[.9]])
>>> Shareholders.income_private_circuit_rsto_rooter(
...     income_private_circuit_rsto=a
... )
array([[-0.1]])

Let’s vectorize the above example.

>>> (v := np.stack((a, 1 - a, a + .1)))
array([[[0.9]],
       [[0.1]],
       [[1. ]]])
>>> Shareholders.income_private_circuit_rsto_rooter(
...     income_private_circuit_rsto=v
... )
array([[[-0.1]],
       [[-0.9]],
       [[ 0. ]]])
class iamax.library.Accountant

Static class (to be inherited) that aggregates a bunch of methods related to accounting equations.

static output_price_computer(*, output_volume: float, input_values: np.ndarray)

s/e., \(1 \times 1\).

(199)\[\up_j = \frac{\sum_{k \in \mcI_j} \mcv_k}{q_j}\]

with \(\mcI_j\) the index set required to get \(\up_j\).

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

See also

output_prices.

Example
>>> a = 1.
>>> b = np.array([[1., 9.]])
>>> Accountant.output_price_computer(
...     output_volume=a, input_values=b
... )
array([[10.]])

Let’s vectorize the above example

>>> Accountant.output_price_computer(
...     output_volume=np.atleast_2d([a, 5*a]).T,
...     input_values=np.vstack((b, b))
... )
array([[10.],
       [ 2.]])
class iamax.library.CoInitiatedVolumes(scaling_factor: float = 1.0)

Class that abstracts the concept of (independent) volumes whose initial state are yet to be determined jointly.

Parameters

scaling_factor (float) – S/e. set to 1 by default.

output_volumes_ccomputer(self, *, input_values: np.ndarray, additive_output_volumes: np.ndarray)

s/e.

Parameters
  • additive_output_volumes (numpy.ndarray) – \(q_j\), i.e. the \(j\)ths \(1 \times 1\) additive_output_volumes’s elements, whose last component only is of interest.

  • input_values (numpy.ndarray) – \((\bmcV_{kk,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI_j| \times |\mcI_j| + 1\) input_values’s slice (diagonal) rectangular slice.

Important

This method relies on the structure of its argument, as outlined in the description of argument additive_output_volumes.

Attention

By appending its output with a 0, this method overwrites the last component of the targeted attribute.

class iamax.library.BalancedSM

Class that abstracts the concept of specific margins summing to zero across consumers subjected to it.

static specific_margin_balance_rooter(specific_margins_balances: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

Parameters

specific_margins_balances (numpy.ndarray) – \(\bv_{\utau,\ j}\), i.e. the \(j\)th \(1 \times 1\) element of specific_margins_balances.

Example
>>> a = np.array([[.9]])
>>> BalancedSM.specific_margin_balance_rooter(
...     specific_margins_balances=a
... )
array([[0.9]])

Let’s vectorize the above example.

>>> v = np.stack((
...     np.array([[.5]]),
...     np.array([[.0]]),
...     np.array([[.5]]),
... ))
>>> BalancedSM.specific_margin_balance_rooter(
...     specific_margins_balances=v
... )
array([[[0.5]],
       [[0. ]],
       [[0.5]]])
class iamax.library.InitiallyBalancedSM

BalancedSM’s counterpart that restricts the balancing at time t=0.

static specific_margin_balance_crooter(specific_margins_balances: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

Parameters

specific_margins_balances (numpy.ndarray) – \(\bv_{\utau,\ j}\), i.e. the \(j\)th \(1 \times 1\) specific_margins_balances’s element.

Example
>>> a = np.array([[.9]])
>>> InitiallyBalancedSM.specific_margin_balance_crooter(
...     specific_margins_balances=a
... )
array([[0.9]])

Let’s vectorize the above example.

>>> v = np.stack((
...     np.array([[.5]]),
...     np.array([[.0]]),
...     np.array([[.5]]),
... ))
>>> InitiallyBalancedSM.specific_margin_balance_crooter(
...     specific_margins_balances=v
... )
array([[[0.5]],
       [[0. ]],
       [[0.5]]])
class iamax.library.CES(sigma: float|tuple[float]|np.ndarray, nu: float|tuple[float]|np.ndarray = 1.0, eta: float|tuple[float]|np.ndarray = 0.0)

Constant Elasticity of Substitution base class and/or mixin class.

Parameters
  • sigma (float or np.ndarray) – The elasticity of substitution \(\sigma \in [0,+\infty]\), with the substitution parameter being defined as \(\rho = \frac{\sigma - 1}{\sigma}\).

  • nu (float) – The degree of homogeneity of the aggregation \(\nu \in ]0,+\infty]\), set to 1. by default.

  • eta (float) – The responsiveness parameter \(\eta \in [-\infty,+\infty]\), used under dynamic circumstances, if any. Set to .0 by default.

Note

This class can either be linear, Cobb–Douglas or Leontief:

  1. If \((\sigma \to \infty) \iff (\rho \to 1)\), we get a linear or perfect substitutes function;

  2. If \((\sigma \to 1) \iff (\rho \to 0)\), we get a Cobb–Douglas function;

  3. If \((\sigma \to 0) \iff (\rho \to -\infty)\), we get a Leontief or perfect complements function.

Example
>>> CES(sigma=0).aggregation_type
'Leontief'
>>> CES(sigma=.5).aggregation_type
'Gross complements'
>>> CES(sigma=1).aggregation_type
'Cobb–Douglas'
>>> CES(sigma=2).aggregation_type
'Imperfect Substitutes'
>>> CES(sigma=np.inf).aggregation_type
'Perfect substitutes'
>>> CES(sigma=[0, 1, np.inf]).aggregation_type
['Leontief', 'Cobb–Douglas', 'Perfect substitutes']
property aggregation_type(self)

Aggregation qualifier, be this aggregation related to cardinal (e.g. production) or ordinal (e.g. utility level) quantities.

Example
>>> CES(sigma=[0, 1, np.inf]).aggregation_type
['Leontief', 'Cobb–Douglas', 'Perfect substitutes']
property sigma(self)

Elasticity of substitution \(\sigma\in[0,+\infty]\).

property rho(self)

Substitution parameter \(\rho\in[-\infty,1]\).

property nu(self)

Aggregation’s degree of homogeneity \(\nu\in]0,+\infty]\).

property eta(self)

Responsiveness to input price changes \(\eta\in \mathbb{R}\).

abstract compute_calibrand_0(self, **kwargs: float)

Method to be called during calibration.

Attention

This method is assumed (but not asserted) to only be dealing with Keyword-Only Arguments.

Warning

This method will likely be reattached as soon as a technology (or preference) morphism being ontologically upstream of CES is defined.

_aggregator(self, *, shares: np.ndarray, aggregands: np.ndarray)

Class’s core morphism, \(1 \times 1\), denoted as

(200)\[y = n^{-\nu\text{I}(\rho=-\infty)} \left(\prod_{i=1}^n x_i^{\nu a_i\text{I}(\rho=0)}\right) \left( \sum_{i=1}^n a_i x_i^{\rho^{\text{I}(\rho\neq-\infty)}} \right)^{ \frac{\nu\text{I}(\rho\neq0)}{ \rho^{\text{I}(\rho\neq-\infty)\text{I}(\rho\neq0)} } }\]

where \(n=\text{dim}(\boldsymbol{x})\), \(y\) is the quantity of aggregate, \(x_i\) the quantity of \(i\)-aggregand, \(a_i\) its associated share, \(\rho\) the substitution parameter and \(\nu\) the homogeneity degree of the aggregation.

Note

At \(\rho=0\), the ordinary CES contribution is neutralized, while the multiplicative contribution becomes

(201)\[y &= \prod_{i=1}^n x_i^{\nu a_i} &= \left(\prod_{i=1}^n x_i^{a_i}\right)^\nu,\]

i.e. the Cobb-Douglas limit. Since the degree of homogeneity of this expression is \(\nu\sum_i a_i\), retaining \(\nu\) as the homogeneity degree requires

(202)\[\sum_{i=1}^n a_i = 1.\]

At \(\rho=1\), both the Leontief normalization and Cobb-Douglas contribution are neutral, while the ordinary CES contribution reduces to

(203)\[y = \left(\sum_{i=1}^n a_i x_i\right)^\nu,\]

i.e. the perfect-substitutes form, which becomes linear under constant returns to scale, \(\nu=1\).

At the Leontief limit \(\rho=-\infty\), the power applied to each aggregand and the outer CES exponent both reduce to \(1\) and \(\nu\), respectively, so that

(204)\[y &= n^{-\nu} \left(\sum_{i=1}^n a_i x_i\right)^\nu &= \left(\frac{1}{n}\sum_{i=1}^n a_i x_i\right)^\nu.\]

Aggregands are assumed in this case to be supplied in perfectly complementary quantities,

(205)\[a_i x_i = a_{i'}x_{i'} \quad \forall i,i',\]

implying

(206)\[\frac{1}{n}\sum_{i=1}^n a_i x_i = \min_i\{a_i x_i\}.\]

The usual complementarity operator can therefore be replaced, over this restricted domain, by the smooth arithmetic expression above without changing the resulting aggregate.

This method is vectorization friendly over the 0th axis. Incidentally, it may also be turned into instance method once the developments stabilize.

Parameters
  • shares (numpy.ndarray) – \(1 \times n\) archetype of the distribution parameters.

  • aggregands (numpy.ndarray) – \(1 \times n\) archetype of the aggregands.

Example
>>> kws = {'aggregands': np.array([[1782.232, 19190.896]])}

Let’s first play with a gross-complement case.

>>> agc = np.array([[0.29151, 0.95657]])
>>> CES(sigma=2.)._aggregator(shares=agc, **kws)
array([[20973.22]])

At the Cobb-Douglas limit, consider normalized distribution parameters.

>>> acd = np.array([[.25, .75]])
>>> CES(sigma=1.)._aggregator(shares=acd, **kws)
array([[10594.06]])

At the perfect-substitutes limit, aggregation becomes linear under constant returns to scale.

>>> aps = agc
>>> CES(sigma=np.inf)._aggregator(shares=aps, **kws)
array([[18876.97]])

At the Leontief limit, consider three aggregands supplied in perfectly complementary quantities. Their share-adjusted quantities are consequently identical.

>>> apc = 1e4 / kws['aggregands']
>>> apc
array([[5.61, 0.52]])
>>> CES(sigma=0.)._aggregator(shares=apc, **kws)
array([[10000.]])

The same construction preserves the specified degree of homogeneity.

>>> CES(sigma=0., nu=2.)._aggregator(shares=apc, **kws)
array([[1.e+08]])

Let’s illustrate how such calculation can be vectorized along the 0th axis.

>>> vces = CES(sigma=np.array([[.0], [1.], [2.], [np.inf]]))
>>> vces._aggregator(
...     shares=np.vstack((apc, acd, agc, aps)), **kws
... )
array([[10000.  ],
       [10594.06],
       [20973.22],
       [18876.97]])
>>> vces.aggregation_type
['Leontief', 'Cobb–Douglas',
 'Imperfect Substitutes', 'Perfect substitutes']
class iamax.library.CESCapacity
Inheritance diagram of iamax.library.CESCapacity

Benchmark-calibrated CES morphism from productive quantity underliers to potential output.

Calibration identifies the CES distribution parameters required for observed productive underliers to support the potential output implied by observed production and its idle rate. Subsequent aggregation is entirely delegated to _aggregator().

compute_calibrand_0(self, *, output_volume_underlier: float, idle_rate: float, input_volumes_underliers: np.ndarray)

Calibrate productive-capacity CES distribution parameters.

Let \(\uq_j\) denote observed output underlier of aggregate \(j\), \(\tau_{\neg q,j}\) its idle rate and

(207)\[\mfc_j = \frac{\uq_j}{1-\tau_{\neg q,j}}\]

the corresponding potential output. For productive underlier \(k \in \mcI_j\), calibration sets

(208)\[a_{k,j} = |\mcI_j|^{-\text{I}(\rho_j\neq-\infty)} \left( \frac{\mfc_j^{1/\nu_j}}{\uQ_{k,j}} \right)^{\rho_j^{\text{I}(\rho_j\neq-\infty)}}.\]

Thus, for finite nonzero \(\rho_j\), observed underliers make equal benchmark contributions,

(209)\[a_{k,j}\uQ_{k,j}^{\rho_j} = \frac{\mfc_j^{\rho_j/\nu_j}}{|\mcI_j|}.\]

At the Leontief limit, the same calibration instead yields

(210)\[a_{k,j}\uQ_{k,j} = \mfc_j^{1/\nu_j},\]

thereby satisfying the perfect-complementarity condition expected by _aggregator().

Parameters

Note

With only one benchmark observation, relative technological contributions are not independently identified. Calibration therefore assigns equal benchmark contributions wherever the CES parametrization permits this directly.

At the Cobb-Douglas limit, Eq.208 reduces to \(a_{k,j}=1/|\mcI_j|\). Consequently, exact benchmark reproduction additionally requires

(211)\[\mfc_j = \left( \prod_{k \in \mcI_j} uQ_{k,j}^{1/|\mcI_j|} \right)^{\nu_j}.\]

This follows from retaining normalized CES shares and \(\nu_j\) as the inherited homogeneity degree; an arbitrary additional Cobb-Douglas scale cannot be encoded in those shares alone.

Example

Consider first a single productive underlier. With observed output 80, a 20% idle rate and an underlier quantity of 100, benchmark potential output is also 100.

>>> ivolu = np.array([[100.]])
>>> c = CESCapacity(sigma=2.).compute_calibrand_0(
...     output_volume_underlier=np.array([[80.]]),
...     input_volumes_underliers=ivolu,
...     idle_rate=np.array([[.2]]),
... )
>>> c.calibrand_0
array([[1.]])

With a single underlier, the same unit calibrand is obtained across all four aggregation regimes when benchmark potential output and the underlier quantity coincide.

>>> vivolu = np.array(4*[[[100.]]])
>>> vovolu = np.array(4*[[[80.]]])
>>> virate = np.array(4*[[[.2]]])
>>> c = CESCapacity(
...     sigma=np.array([[[2.]], [[1.]], [[np.inf]], [[0.]]])
... )
>>> c.compute_calibrand_0(
...     output_volume_underlier=vovolu,
...     input_volumes_underliers=vivolu,
...     idle_rate=virate,
... ).calibrand_0
array([[[1.]],
       [[1.]],
       [[1.]],
       [[1.]]])

Consider now two productive underliers supporting potential output 100 from observed production 80 and a 20% idle rate.

>>> ivolus = np.array([[50., 200.]])
>>> c = CESCapacity(sigma=2.).compute_calibrand_0(
...     output_volume_underlier=np.array([[80.]]),
...     input_volumes_underliers=ivolus,
...     idle_rate=np.array([[.2]]),
... )
>>> c.calibrand_0
array([[0.71, 0.35]])

Let’s illustrate how such calculation can be vectorized along the 0th axis across the four aggregation regimes.

>>> vivolus = np.repeat(ivolus[None, ...], 4, axis=0)
>>> vovolu  = np.array(4*[[[80.]]])
>>> virate  = np.array(4*[[[.2]]])
>>> c = CESCapacity(
...     sigma=np.array([[[2.]], [[1.]], [[np.inf]], [[0.]]])
... )
>>> c.aggregation_type
['Imperfect Substitutes', 'Cobb–Douglas',
 'Perfect substitutes', 'Leontief']
>>> c.compute_calibrand_0(
...     output_volume_underlier=vovolu,
...     input_volumes_underliers=vivolus,
...     idle_rate=virate,
... ).calibrand_0
array([[[0.71, 0.35]],
       [[0.5 , 0.5 ]],
       [[1.  , 0.25]],
       [[2.  , 0.5 ]]])
output_volume_potentiation(self, *, input_volumes_underliers: np.ndarray)

Potentiate productive quantity underliers into potential output.

Potential output is obtained directly from the inherited _aggregator(), using calibrand_0 as its distribution parameters and current productive underliers as its aggregands.

Parameters

input_volumes_underliers (numpy.ndarray) – \((\ubQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes_underliers’s slice.

Note

This method is vectorization friendly over the 0th axis.

Example

Consider first a single productive underlier. With observed output 80, a 20% idle rate and an underlier quantity of 100, benchmark potential output is 100 and the calibrated distribution parameter is one.

>>> c = CESCapacity(sigma=-1).compute_calibrand_0(
...     output_volume_underlier=np.array([[80.]]),
...     input_volumes_underliers=np.array([[100.]]),
...     idle_rate=np.array([[.2]]),
... )
>>> c.calibrand_0
array([[1.]])
>>> c.output_volume_potentiation(
...     input_volumes_underliers=np.array([[100.]])
... )
array([[100.]])

Here, sigma=-1 deliberately lies outside the economically admissible CES domain to emphasize that, with a single productive underlier, substitution is immaterial: there is no alternative input to substitute toward or away from. Under constant returns to scale, the resulting one-underlier productive-capacity morphism is therefore the identity, irrespective of \(\sigma\), so a change in the productive underlier translates one-for-one into potential output.

>>> c.output_volume_potentiation(
...     input_volumes_underliers=np.array([[120.]])
... )
array([[120.]])
>>> c.output_volume_potentiation(
...     input_volumes_underliers=np.array([[75.]])
... )
array([[75.]])

This identity is independent of the substitution regime when there is only one underlier and its calibrated distribution parameter is one.

>>> vivolu = np.array(4*[[[100.]]])
>>> vovolu = np.array(4*[[[80.]]])
>>> virate = np.array(4*[[[.2]]])
>>> c = CESCapacity(
...     sigma=np.array([[[2.]], [[1.]], [[np.inf]], [[0.]]])
... ).compute_calibrand_0(
...     output_volume_underlier=vovolu,
...     input_volumes_underliers=vivolu,
...     idle_rate=virate,
... )
>>> c.output_volume_potentiation(
...     input_volumes_underliers=np.array(4*[[[120.]]])
... )
array([[[120.]],
       [[120.]],
       [[120.]],
       [[120.]]])

Consider now the two-underlier calibration above. The four aggregation regimes all reproduce the same benchmark potential output.

>>> ivolus  = np.array([[50., 200.]])
>>> vivolus = np.repeat(ivolus[None, ...], 4, axis=0)
>>> vovolu  = np.array(4*[[[80.]]])
>>> virate  = np.array(4*[[[.2]]])
>>> c = CESCapacity(
...     sigma=np.array([[[2.]], [[1.]], [[np.inf]], [[0.]]])
... ).compute_calibrand_0(
...     output_volume_underlier=vovolu,
...     input_volumes_underliers=vivolus,
...     idle_rate=virate,
... )
>>> c.output_volume_potentiation(
...     input_volumes_underliers=vivolus
... )
array([[[100.]],
       [[100.]],
       [[100.]],
       [[100.]]])

A common halving of all productive underliers preserves their relative configuration and, under constant returns to scale, halves potential output in every regime.

>>> c.output_volume_potentiation(
...     input_volumes_underliers=.5 * vivolus
... )
array([[[50.]],
       [[50.]],
       [[50.]],
       [[50.]]])
class iamax.library.HicksianCES(*args, **kwargs)
Inheritance diagram of iamax.library.HicksianCES

Constant Elasticity of Substitution abstraction whose optimal demand function derives from a maximization program formulated à la Hicks.

Note

Eq.200’s \(\nu\) is fully internalized as the technology’s degree of homogeneity. Hicksian input demands consequently scale with \(q_j^{1/\nu_j}\), while the output producer price is the dual shadow price of \(q_j\).

compute_calibrand_0(self, *, output_volume: float, input_volumes: np.ndarray, input_values: np.ndarray)

This method supports the FOC-based calibration of \((a_{k,j})_{k \in \mcI_j}\), the \(|\mcI_j|\) demand distribution parameters related to aggregate \(j\),

(212)\[a_k = \left[ \left( \frac{\mcv_k}{\sum_{k' \in \mcI_j} \mcv_{k'}} \right)^{ \text{I}(\rho_j \neq -\infty) } \left(\frac{q_j^{1/\nu_j}}{q_k}\right)^{ \rho_j^{\text{I}(\rho_j \neq -\infty)} } \right]^{\text{I}(\rho_j \neq 1)}, \ \forall k \ \in \mcI_j\]

with \(q_j\) the output level to be satisfied, \(\rho_j\) the substitution parameter, \(\nu_j\) the degree of homogeneity, \(\mcI_j\) the index set of inputs required by \(q_j\)’s underlying technology, \(q_k\) the optimal level of input required to meet \(q_j\), \(\mcv_k\) the \(q_k\)’s input value and \(\text{I}(\cdot)\) an indicator function returning the trueness of the statement being passed as argument.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

Example
>>> ovol  = 20973.128
>>> ivols = np.array([[1782.232, 19190.896]])
>>> ivals = 1e3*ivols
>>> h = HicksianCES(sigma=2.)
>>> _ = h.compute_calibrand_0(
...     output_volume = ovol,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
>>> h.calibrand_0
array([[0.29, 0.96]])

The same technology calibration is recovered for \(\nu_j=2\) when the output target is squared:

>>> hν = HicksianCES(sigma=2., nu=2.).compute_calibrand_0(
...     output_volume = ovol**2,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
>>> hν.calibrand_0
array([[0.29, 0.96]])

Let’s illustrate how such calculation can be vectorized along the 0th axis.

>>> vovol  = np.array([[[ovol]], [[ovol]]])
>>> vivols = np.stack((ivols, .1*ivols))
>>> vivals = 1e3*vivols
>>> h.compute_calibrand_0(
...     output_volume = vovol,
...     input_volumes = vivols,
...     input_values  = vivals,
... ).calibrand_0
array([[[0.29, 0.96]],
       [[0.92, 3.02]]])

The non-constant instantiation parameters case follows.

>>> h = HicksianCES(sigma=[[[2.]], [[3.]]])
>>> _ = h.compute_calibrand_0(
...     output_volume = vovol,
...     input_volumes = vivols,
...     input_values  = vivals,
... )
>>> h.calibrand_0
array([[[0.29, 0.96]],
       [[2.04, 4.51]]])
compute_calibrand_0_star(self, *, ioq_shares_drates: np.ndarray, ioq_shares_drates2: np.ndarray, ioq_productivities_drates: np.ndarray, ioq_productivities_drates2: np.ndarray)

Define calibrand_0_star, i.e. a version of calibrand_0 that internalizes (crises-like) shocks,

(213)\[\begin{split}a_k^{*} &= a_k \left( 1 + \beta^{\text{drate}}_k + \beta^{\text{drate}^{ii}}_k \right)^{ \rho_j^{\text{I}(\rho_j \neq -\infty)} \text{I}(\rho_j \neq 1) } \\ & \left( 1 + \alpha^{-1,\ \text{drate}}_k + \alpha^{-1,\ \text{drate}^{ii}}_k \right)^{\text{I}(\rho_j = -\infty)}, \ \forall k \ \in \mcI_j\end{split}\]

where \(a_k\) and \(\rho_j\) are as defined in Eq.212, \(\beta^\text{drate}_k\) and \(\beta^{\text{drate}^{ii}}_k\) are respectively a crisis-like shock and a spread from it, \(\alpha^{-1,\ \text{drate}^{ii}}_k\) and \(\alpha^{-1,\ \text{drate}}_k\) are, respectively, a scenarized productivity (discrete) growth and a spread from it.

Parameters
  • ioq_shares_drates (numpy.ndarray) – \((\bbeta^\text{drate}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_shares_drates’s slice.

  • ioq_shares_drates2 (numpy.ndarray) – \((\bbeta^{\text{drate}^{ii}}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_shares_drates2’s slice.

  • ioq_productivities_drates (numpy.ndarray) – \((\balpha^{-1,\ \text{drate}_{k,j}})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_productivities_drates’s slice.

  • ioq_productivities_drates2 (numpy.ndarray) – \((\balpha^{-1,\ \text{drate}^{ii}_{k,j}})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_productivities_drates2’s slice.

Note

ioq_productivities_drates has vocation to “overwrite” ioq_shares_drates sooner or later.

Important

Keep in mind that \(a_k\) and \(\rho_j\) are internally handled as instance attributes, i.e. in the same manner as calibrand_0 and rho.

allege_input_shares(self, *, input_prices: np.ndarray, input_prices_lagged: np.ndarray, ioq_intensities_lagged: np.ndarray)

Alleged input shares, \(1 \times |\mcI_j|\), denoted as

(214)\[s_{k,t} = \frac{ s_{k,t-1} \left( p_{k,t} / p_{k,t-1} \right)^{\eta_j} }{ \sum_{k^{'} \in \mcI_j} s_{k^{'},t-1} \left( p_{k^{'},t} / p_{k^{'},t-1} \right)^{\eta_j} } \ \forall k \ \in \mcI_j\]

where \(\eta_j\) is a \(j\)-specific responsiveness parameter and \(\mcI_j\) and \(p_k\) are as described in Eq.219.

Parameters
  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

  • input_prices_lagged (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of the lagged version of input_prices.

  • ioq_intensities_lagged (numpy.ndarray) – \((\balpha_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of the lagged version of ioq_intensities.

Example
>>> invariants = dict(
...     input_prices           = np.array([[1., 1.25]]),
...     input_prices_lagged    = np.array([[1., 1.]]),
...     ioq_intensities_lagged = np.array([[.4, .6]]),
... )
>>> HicksianCES(
...     sigma=np.inf, eta=0.
... ).allege_input_shares(**invariants).alleged_input_shares
array([[0.4, 0.6]])
>>> HicksianCES(
...     sigma=np.inf, eta=1.
... ).allege_input_shares(**invariants).alleged_input_shares
array([[0.35, 0.65]])
>>> HicksianCES(
...     sigma=np.inf, eta=4.
... ).allege_input_shares(**invariants).alleged_input_shares
array([[0.21, 0.79]])
compute_input_volumes_shocks(self, *, input_volumes_drates: np.ndarray)

Input-volume shocks, \(1 \times |\mcI_j|\), calculated and assigned to attribute input_volumes_shocks since required in several methods of the instance.

(215)\[\bQ^\text{dmult}_j = 1 + \bQ^\text{drate}_j\]
Parameters

input_volumes_drates (numpy.ndarray) – \((\bQ^\text{drate}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes_drates’s slice.

Note

Since the name of this method mirrors nothing within the list of eligible variables (be them related to targets or arguments), it will be assumed to work in place.

Important

Keep in mind that input_volumes_shocks is defined and internally handled as instance attributes, cf. the example section below.

Example
>>> h = HicksianCES(sigma=None)
>>> h.compute_input_volumes_shocks(
...     input_volumes_drates=np.array([[.1, .2]])
... ).input_volumes_shocks
array([[1.1, 1.2]])
_output_volume_computer(self, *, input_volumes: np.ndarray)

Output supply, \(1 \times 1\), denoted as

(216)\[q_j = |\mcI_j|^{-\nu_j\text{I}(\rho_j=-\infty)} \left( \prod_{k \in \mcI_j} q_k^{\nu_j a_k\text{I}(\rho_j=0)} \right) \left( \sum_{k \in \mcI_j} a_k q_k^{\rho_j^{\text{I}(\rho_j\neq-\infty)}} \right)^{ \frac{\nu_j\text{I}(\rho_j\neq0)}{ \rho_j^{ \text{I}(\rho_j\neq-\infty) \text{I}(\rho_j\neq0) } } }\]

where \(|\mcI_j|\) is the number of inputs associated with aggregate \(j\), while \(\mcI_j\), \(q_j\), \(q_k\), \(a_k\), \(\rho_j\), \(\nu_j\) and \(\text{I}(\cdot)\) are as described in Eq.212.

See also

_aggregator.

Note

Eq.216 is unconditionally valid only when \(q_{k=1, \ldots, |\mcI_j|}\)’s levels are the optimal input demands’.

Parameters

input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

Example
>>> ovol  = 20973.128
>>> ivols = np.array([[1782.232, 19190.896]])
>>> ivals = 1e3*ivols
>>> h = HicksianCES(sigma=2.)
>>> _ = h.compute_calibrand_0(
...     output_volume = ovol,
...     input_volumes = ivols,
...     input_values  = ivals
... )
>>> h._output_volume_computer(
...     input_volumes = ivols,
... )
array([[20973.13]])

The degree of homogeneity is fully internalized in output supply:

>>> hν = HicksianCES(sigma=2., nu=2.).compute_calibrand_0(
...     output_volume = ovol**2,
...     input_volumes = ivols,
...     input_values  = ivals
... )
>>> hν._output_volume_computer(input_volumes=ivols).item()
439872098.1043841
>>> ovol**2
439872098.104384

Let’s illustrate how such calculation can be vectorized along the 0th axis.

>>> vovol  = np.array([[[ovol]], [[.5*ovol]], [[ovol]]])
>>> vivols = np.stack((ivols, .5*ivols, ivols))
>>> vivals = 1e3*vivols
>>> _ = h.compute_calibrand_0(
...     output_volume = vovol,
...     input_volumes = vivols,
...     input_values  = vivals,
... )
>>> h._output_volume_computer(
...     input_volumes = vivols,
... )
array([[[20973.13]],
       [[10486.56]],
       [[20973.13]]])
compute_output_price_driver(self, *, output_price_drate: np.ndarray)

Output-price multiplicative driver, \(1 \times 1\), calculated and assigned to attribute output_price_driver.

(217)\[d_j = 1 + \up^\text{drate}_j\]
Parameters

output_price_drate (float) – \(\up^\text{drate}_j\), i.e. the \(j\)th \(1 \times 1\) producer_net_prices_drates’s element.

Note

The driver multiplicatively shifts the technology-implied producer price returned by output_price_computer().

output_price_computer(self, *, output_volume: float, input_prices: np.ndarray)

Output producer price, \(1 \times 1\), denoted as

(218)\[\up_j = d_j \frac{q_j^{1/\nu_j-1}}{\nu_j} \left( \sum_{k \in \mcI_j} \left[ a_k^{ (-1)^{\text{I}(\sigma_j = 0)} \sigma_j^{\text{I}(\sigma_j \neq 0)} } \right]^{\text{I}(\sigma_j \neq \infty)} p_k^{ (1 - \sigma_j)^{\text{I}(\sigma_j \neq \infty)} } s_k^{\text{I}(\sigma_j = \infty)} \right)^{ (1 - \sigma_j)^{-\text{I}(\sigma_j \neq \infty)} }\]

where \(\mcI_j\), \(q_j\), \(a_k\), \(\nu_j\) and \(\text{I}(\cdot)\) are as in Eq.212, \(\sigma_j\) and \(p_k\) are as in Eq.219, \(\up_j\) is the output’s producer price, i.e. its dual shadow price, \(s_k\) is as in Eq.214 and \(d_j\) as in Eq.217.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

See also

output_prices.

Important

This method overrides its (parent) output_price_computer counterpart.

Example
>>> ovol  = 20973.128
>>> ivols = np.array([[1782.232, 19190.896]])
>>> iprcs = np.array([[1000., 1000.]])
>>> ivals = iprcs*ivols
>>> h = HicksianCES(sigma=2.)
>>> _ = h.compute_calibrand_0(
...     output_volume = ovol,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
>>> h.output_price_computer(
...     output_volume = ovol,
...     input_prices  = iprcs,
... ).item()
1000.0

With \(\nu_j=2\), the same calibrated input technology produces \(q_j=\mathtt{ovol}^2\); its output price now internalizes the resulting returns to scale:

>>> hν = HicksianCES(sigma=2., nu=2.).compute_calibrand_0(
...     output_volume = ovol**2,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
>>> hν.output_price_computer(
...     output_volume = ovol**2,
...     input_prices  = iprcs,
... )
array([[0.02]])

Let’s illustrate how such calculation can be vectorized over the 0th axis.

>>> vovol  = np.array([[[ovol]], [[.5*ovol]], [[ovol]]])
>>> vivols = np.stack((ivols, .5*ivols, ivols))
>>> vivals = 1e3*vivols
>>> viprcs = 1e3*np.ones_like(vivols)
>>> _ = h.compute_calibrand_0(
...     output_volume = vovol,
...     input_volumes = vivols,
...     input_values  = vivals,
... )
>>> h.output_price_computer(
...     output_volume = vovol,
...     input_prices  = viprcs,
... )
array([[[1000.]],
       [[1000.]],
       [[1000.]]])
input_volumes_computer(self, *, output_volume: float, input_prices: np.ndarray)

Input demands, \(1 \times |\mcI_j|\), denoted as

(219)\[\begin{split}q_k &= q_j^{1/\nu_j} \left[ \alpha_k \left(\frac{\up_j}{p_k}\right)^{\sigma_j} \right]^{\text{I}(\sigma_j \neq \infty)} \left[s_k\right]^{\text{I}(\sigma_j = \infty)} \\ &= q_j^{1/\nu_j} \left[ \alpha_k p_k^{-\sigma_j} \left( \sum_{k' \in \mcI_j} \alpha_{k'} p_{k'}^{1 - \sigma_j} \right)^{ \frac{\sigma_j}{1 - \sigma_j} } \right]^{\text{I}(\sigma_j \neq \infty)} \left[s_k\right]^{\text{I}(\sigma_j = \infty)}, \ \forall k \ \in \mcI_j\end{split}\]

where \(\alpha_k=a_k^{(-1)^{\text{I}(\sigma_j = 0)} \sigma_j^{\text{I}(\sigma_j \neq 0)}}\) and \(\mcI_j\), \(q_j\), \(q_k\), \(a_k\), \(\nu_j\) and \(\text{I}(\cdot)\) are as in Eq.212, \(p_k\) is the \(q_k\)’s price and \(s_k\) is as described in Eq.214.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

Example
>>> ovol  = 20973.128
>>> ivols = np.array([[1782.232, 19190.896]])
>>> iprcs = np.array([[1000., 1000.]])
>>> ivals = iprcs*ivols
>>> h = HicksianCES(sigma=2)
>>> _ = h.compute_calibrand_0(
...     output_volume = ovol,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
>>> h.input_volumes_computer(
...     output_volume = ovol,
...     input_prices  = iprcs,
... )
array([[ 1782.23, 19190.9 ]])

The same optimal input levels are recovered at \(\nu_j=2\) when the corresponding output target is squared:

>>> hν = HicksianCES(sigma=2., nu=2.)
>>> _ = hν.compute_calibrand_0(
...     output_volume = ovol**2,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
>>> hν.input_volumes_computer(
...     output_volume = ovol**2,
...     input_prices  = iprcs,
... )
array([[ 1782.23, 19190.9 ]])

Let’s illustrate how such calculation can be vectorized over the 0th axis.

>>> h      = HicksianCES(sigma=[[[2.]], [[2.]], [[.5]]])
>>> vovol  = np.array([[[ovol]], [[ovol]], [[ovol]]])
>>> vivols = np.stack((ivols, ivols[:, ::-1], ivols))
>>> vivals = 1e3*vivols
>>> viprcs = 1e3*np.ones((3, 1, 2))
>>> h.compute_calibrand_0(
...     output_volume = vovol,
...     input_volumes = vivols,
...     input_values  = vivals,
... ).input_volumes_computer(
...     output_volume = vovol,
...     input_prices  = viprcs,
... )
array([[[ 1782.23, 19190.9 ]],
       [[19190.9 ,  1782.23]],
       [[ 1782.23, 19190.9 ]]])

Once again, but moving away from the calibration context regarding relative prices.

>>> viprcs[..., 0] *= .5
>>> h.input_volumes_computer(
...     output_volume = vovol,
...     input_prices  = viprcs,
... )
array([[[ 6055.96, 16302.5 ]],
       [[20931.83,   485.98]],
       [[ 2457.72, 18713.25]]])
class iamax.library.HicksianICES(*args, **kwargs)
Inheritance diagram of iamax.library.HicksianICES

Ordinal-production altered version of HicksianCES.

Important

At the moment, this method cannot be instantiated at \(\sigma \to \infty\) since it would require MSystem to be endowed with a new economic attribute k.a. iwq_intensities.

compute_calibrand_0(self, *, ordinal_volume: float, input_volumes: np.ndarray, input_values: np.ndarray)

s/e.

abstract static allege_input_shares()

LSP-offending (in-place) computer.

See also

The reason why this method is nullified.

Example
>>> HicksianICES.allege_input_shares()
Traceback (most recent call last):
 ...
NotImplementedError: Require `core.MSystem.iwq_intensities`
ordinal_volume_computer(self, *, input_volumes: np.ndarray)

s/e.

static compute_output_price_driver()

LSP-offending (in-place) computer.

static output_price_computer()

LSP-offending (in-place) computer.

input_volumes_computer(self, *, ordinal_volume: float, input_prices: np.ndarray)

s/e.

class iamax.library.HicksianPICES(*args, scaling_factor: float = 1.0, **kwargs)
Inheritance diagram of iamax.library.HicksianPICES

Augmented version of HicksianICES that also computes the price of the ordinal quantities being generated.

Parameters

scaling_factor (float) – Ordinal price scaling factor. To 1 by default.

compute_ordinal_price_driver(self, *, ordinal_price_drate: np.ndarray)

s/e.

ordinal_price_computer(self, *, input_prices: np.ndarray)

s/e.

class iamax.library.ScHicksianCES
Inheritance diagram of iamax.library.ScHicksianCES

HicksianCES’s altered version whose calibrands determination is solver-based.

Candidate distribution parameters are directly served by the solving system and rooted against both their CES normalization condition and their ability to reproduce observed input volumes through the inherited Hicksian demand system.

compute_calibrand_0(self, *, ioq_intensities_drates: np.ndarray)

Attach solver-served candidate distribution parameters. The supplied ioq_intensities_drates are directly attached as both baseline and currently applicable calibrands, their consistency being subsequently assessed by ioq_intensities_drates_crooter().

Parameters

ioq_intensities_drates (numpy.ndarray) – \((\balpha^{\text{drate}}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of ioq_intensities_drates.

Example
>>> ScHicksianCES(sigma=2.).compute_calibrand_0(
...     ioq_intensities_drates=np.array([[.36, .64]])
... ).calibrand_0
array([[0.36, 0.64]])
ioq_intensities_drates_crooter(self, *, input_volumes: np.ndarray, input_prices: np.ndarray)

Root solver-served distribution parameters against their CES normalization and Hicksian input-volume reproduction conditions.

Let \(a_{k,j}\) denote the candidate distribution parameter associated with input \(k\) of aggregate \(j\). The first component requires

(220)\[\sum_{k \in \mcI_j} a_{k,j}^{\sigma_j} = 1,\]

where \(\sigma_j\) denotes aggregate \(j\)’s elasticity of substitution and \(\mcI_j\) its input index set.

The observed input volumes are additionally passed through the inherited CES aggregation and Hicksian demand mappings. Let \(q_j\) denote the aggregate volume elicited from observed input volumes \((q_{k,j})_{k \in \mcI_j}\), and let \(q^H_{k,j}\) denote the corresponding Hicksian demand evaluated at observed input prices \((p_{k,j})_{k \in \mcI_j}\). Calibration additionally requires

(221)\[q^H_{k,j} = q_{k,j}, \ \forall k \in \mcI_j.\]

The returned criterion is null iff both the CES normalization and exact reproduction of observed input volumes by the corresponding Hicksian demand system hold.

Parameters
  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

Note

This method is vectorization friendly over the -3th axis, if any.

Example

In the one-input case, a unit calibrand satisfies the CES normalization condition. The aggregate reconstructed from the observed input volume is consequently reproduced by its Hicksian demand.

>>> h = ScHicksianCES(sigma=2.).compute_calibrand_0(
...     ioq_intensities_drates=np.array([[1.]])
... )
>>> h.ioq_intensities_drates_crooter(
...     input_volumes=np.array([[3.]]),
...     input_prices=np.array([[2.]]),
... )
array([[9.06e-16]])
class iamax.library.SmHicksianCES
Inheritance diagram of iamax.library.SmHicksianCES

HicksianCES’s altered version whose calibrands determination is softmax-based.

Unlike compute_calibrand_0(), the present calibration does not require an observed aggregate output volume. Distribution parameters are instead recovered from input prices and volumes through their substitution-elasticity-adjusted expenditure weights.

compute_calibrand_0(self, *, input_prices: np.ndarray, input_volumes: np.ndarray)

This method supports the softmax-derived calibration of \((a_{k,j})_{k \in \mcI_j}\), the \(|\mcI_j|\) demand distribution parameters related to aggregate \(j\),

(222)\[a_k = \left[ \frac{p_{k}q_{k}^{1/\sigma_j}}{ \sum_{k' \in \mcI_j} p_{k'}q_{k'}^{1/\sigma_j} } \right]^{\text{I}(\sigma_j \neq \infty)}, \ \forall k \ \in \mcI_j\]

with \(\sigma_j\) the elasticity of substitution, \(\mcI_j\) the index set of inputs required by \(q_j\)’s underlying technology, \(q_k\) the optimal level of input required to meet \(q_j\), \(p_k\) the \(q_k\)’s associated input price and \(\text{I}(\cdot)\) an indicator function returning the trueness of the statement being passed as argument.

For finite \(\sigma_j\), the expression consists in the normalization of

\[p_k q_k^{1/\sigma_j}\]

over \(\mcI_j\), equivalently corresponding to a softmax over \(\log p_k + \sigma_j^{-1}\log q_k\) whenever these logarithms are defined. For \(\sigma_j \to \infty\), the perfect-substitutes limit is handled consistently with HicksianCES by neutralizing the normalized term through \(\text{I}(\sigma_j \neq \infty)\).

Parameters
  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

Note

This method is vectorization friendly over the -3th axis, if any.

Example

Let’s consider two inputs whose respective prices and volumes are

>>> iprcs = np.array([[2., 1.]])
>>> ivols = np.array([[4., 9.]])

With an elasticity of substitution equal to \(2\), the unnormalized softmax terms amount respectively to \(2\sqrt{4}=4\) and \(\sqrt{9}=3\).

>>> h = SmHicksianCES(sigma=2.)
>>> h.compute_calibrand_0(
...     input_prices=iprcs, input_volumes=ivols
... ).calibrand_0
array([[0.57, 0.43]])

The Cobb-Douglas case directly reduces the softmax terms to input values.

>>> SmHicksianCES(sigma=1.).compute_calibrand_0(
...     input_prices=iprcs, input_volumes=ivols
... ).calibrand_0
array([[0.47, 0.53]])

Finally, the perfect-substitutes limit neutralizes the normalized term.

>>> SmHicksianCES(sigma=np.inf).compute_calibrand_0(
...     input_prices=iprcs, input_volumes=ivols
... ).calibrand_0
array([[1., 1.]])
class iamax.library.HicksianFCES
Inheritance diagram of iamax.library.HicksianFCES

HicksianCES’s fixed-calibration variant whose underlying input trade-off is externally forced.

Contrary to HicksianCES, candidate input volumes are not endogenously computed from the CES demand system. Instead, the \(|\mcI_j|\) inputs are partitioned into a set of externally forced inputs and a freely traded-off set containing a single input,

(223)\[\mcI_j = \mcI_j^{\mathrm{forced}}\mathbin{\dot\cup}\mcI_j^{\mathrm{free}}, \ |\mcI_j^{\mathrm{free}}|=1.\]

Hence, the forcing structure distinguishes two sets of inputs but does not restrict the CES itself to two inputs: \(|\mcI_j|\) may be arbitrary. Given the externally determined candidate input bundle, consistency with the fixed CES calibration is imposed through aggregated_input_volumes_rooter().

The class itself remains agnostic as to which elements of the supplied input-volume vector are forced and which one is freely traded off; this distinction is determined by the surrounding model.

static allege_input_shares()

LSP-offending (in-place) computer.

static compute_input_volumes_shocks()

LSP-offending computer.

static input_volumes_computer()

LSP-offending computer.

aggregated_input_volumes_rooter(self, *, output_volume: np.ndarray, input_volumes: np.ndarray)

Residual associated with the forced input-volume constraint, \(1 \times 1\) assumed unit-dimensionless.

Denoting by \(q_j\) the output volume to be satisfied and by \((q_k)_{k \in \mcI_j}\) the candidate input-volume vector, the finite non-Cobb-Douglas CES case implies

(224)\[\widehat q_j = \left( \sum_{k \in \mcI_j}a_k q_k^{\rho_j} \right)^{1/\rho_j}, \ \rho_j=\frac{\sigma_j-1}{\sigma_j},\]

where \(a_k\) denotes the \(k\)th fixed CES distribution parameter. The root condition is

(225)\[r\left(\widehat q_j,q_j\right) \approx 0,\]

where \(r\) is _rdistance(). Thus, the candidate bundle is admissible when the output it implies under the fixed CES calibration coincides with the required output volume.

The method does not distinguish between \(\mcI_j^{\mathrm{forced}}\) and \(\mcI_j^{\mathrm{free}}\): it only evaluates the complete candidate input bundle supplied by the caller.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

Example

Consider a three-input CES with \(\sigma=.5\), hence

\[\rho=\frac{\sigma-1}{\sigma}=-1.\]

At calibration, let all three input volumes and the output volume equal one, while input values are respectively \(2\), \(1\) and \(1\). The resulting distribution parameters are therefore \(a_1=.5\) and \(a_2=a_3=.25\).

>>> h = HicksianFCES(sigma=.5).compute_calibrand_0(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.ones((1, 3)),
...     input_values=np.array([[2., 1., 1.]]),
... )
>>> h.calibrand_0_star
array([[0.5 , 0.25, 0.25]])

Suppose now that the second and third inputs are externally forced to \(q_2=2\) and \(q_3=4\), respectively, while the first input remains freely traded off. Requiring \(q_j=1\) gives

\[1 = \left(.5q_1^{-1} +.25(2)^{-1} +.25(4)^{-1}\right)^{-1},\]

and hence

\[q_1 = \frac{.5}{1-.25/2-.25/4} = \frac{8}{13}.\]
>>> q1 = 8. / 13.
>>> h.aggregated_input_volumes_rooter(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.array([[q1, 2., 4.]]),
... )
array([[0.]])

Thus, although two inputs are externally forced in this example, the single freely traded-off input suffices to satisfy the scalar CES output constraint. Nothing in the rooter restricts the total number of inputs to two.

class iamax.library.HicksianUFCES
Inheritance diagram of iamax.library.HicksianUFCES

Unimodular extension of HicksianFCES.

Unlike HicksianFCES, which evaluates the CES aggregation constraint over real input volumes only, HicksianUFCES admits a complex extension of each scalar input volume through the unimodular rotation

\[q^\angle_{k,j} = q_{k,j}\left( \cos\angle q^\angle_{k,j} + \mathrm{i}\sin\angle q^\angle_{k,j} \right) = q_{k,j}\exp\left( \mathrm{i}\angle q^\angle_{k,j} \right).\]

The extension is unimodular because

\[\left|q^\angle_{k,j}\right| = \left|q_{k,j}\right|.\]

Economic input volumes therefore remain real magnitudes. The angles provide auxiliary dimensions used only when evaluating the continued CES aggregation constraint and the associated real-domain selection residual.

Denoting the continued CES residual by

\[r_\theta = r\left( q_j, F_j\left((q^\angle_{k,j})_{k\in\mcI_j}\right) \right),\]

aggregated_input_volumes_rooter() exposes \(r_\theta\) directly.

The sole purpose of the second residual exposed by output_volume_rooter() is different: it selects the canonical zero-angle representation whenever the corresponding FCES equation admits a strictly positive real solution, while releasing the angle whenever such a solution is analytically impossible.

Importantly, this decision is made independently for every candidate input and from the CES structure itself. No input, sector or block is designated ex ante as requiring complex continuation.

For finite \(\rho_j\neq0\), counterfactually let input \(k\) be the singleton free input and hold every other input fixed. Writing

\[x_{\ell,j} = \frac{q_{\ell,j}}{s_{\ell,j}},\]

where \(s_{\ell,j}\) denotes the corresponding input-volume shock, the real CES constraint is

\[q_j^{\rho_j} = a_{k,j}x_{k,j}^{\rho_j} + \sum_{\ell\neq k}a_{\ell,j}x_{\ell,j}^{\rho_j}.\]

For \(a_{k,j}>0\), a strictly positive real \(x_{k,j}\) exists if and only if

\[\sum_{\ell\neq k}a_{\ell,j} \left(\frac{x_{\ell,j}}{q_j}\right)^{\rho_j} < 1.\]

The condition is evaluated in logarithmic form. Define

\[\lambda_{\ell,j} = \log a_{\ell,j} + \rho_j\left(\log x_{\ell,j}-\log q_j\right),\]

and the leave-one-out log-sum-exp

\[\Lambda_{k,j} = \log\left( \sum_{\ell\neq k}\exp\lambda_{\ell,j} \right).\]

The logarithmic real-feasibility slack is then

\[\delta_{k,j} = -\Lambda_{k,j}.\]

Hence, exactly,

\[\delta_{k,j}>0 \iff \exists\,x_{k,j}>0: F_j\left((x_{\ell,j})_{\ell\in\mcI_j}\right) = q_j.\]

This log-domain formulation avoids explicitly forming potentially overflowing terms such as \((x_{\ell,j}/q_j)^{\rho_j}\).

The leave-one-out log-sum-exp is evaluated in linear time and memory. In particular, the implementation does not form an \(|\mcI_j|\times|\mcI_j|\) exclusion matrix. A global maximum and, only where needed, a second maximum are used to retain numerical accuracy when one contribution dominates the complete sum.

Two CES limiting cases require their own exact feasibility condition.

At the Cobb-Douglas limit, \(\rho_j=0\),

\[q_j = \prod_{\ell\in\mcI_j}x_{\ell,j}^{a_{\ell,j}}.\]

For \(a_{k,j}>0\), a positive real free input exists for every positive finite target if and only if every active forced input is strictly positive and finite.

At the smooth Leontief limit implemented by CES,

\[q_j = \frac{1}{n_j} \sum_{\ell\in\mcI_j}a_{\ell,j}x_{\ell,j},\]

where \(n_j\) is the number of nonzero distribution parameters. The corresponding feasibility condition is

\[\frac{1}{n_j q_j} \sum_{\ell\neq k}a_{\ell,j}x_{\ell,j} < 1,\]

which is evaluated through the same leave-one-out log-sum-exp machinery.

The feasibility slack is used by compute_qois() to form the exact real-feasibility active set

\[\begin{split}I_{k,j} = \begin{cases} 1, & \delta_{k,j}>0,\\ 0, & \delta_{k,j}\leq0, \end{cases}\end{split}\]

whenever the corresponding test is certifiable. Uncertifiable candidates are conservatively assigned \(I_{k,j}=1\).

The angle supplied to the CES aggregation is then

\[\widetilde{\angle q}^\angle_{k,j} = (1-I_{k,j})\angle q^\angle_{k,j}.\]

Hence a real-feasible candidate is evaluated on the real branch, whereas a real-infeasible candidate retains its full continuation angle.

The raw angular residual is

\[\widetilde r_{\angle,j} = \sum_{k\in\mcI_j}I_{k,j}\angle q^\angle_{k,j}.\]

Under the class contract that at most one input-volume angle is free to differ from zero, this has the desired exact interpretation.

If the free input is real-feasible,

\[\delta_{k,j}>0 \Longrightarrow I_{k,j}=1,\]

and therefore

\[\widetilde r_{\angle,j}=0 \Longrightarrow \angle q^\angle_{k,j}=0.\]

If the free input is real-infeasible,

\[\delta_{k,j}\leq0 \Longrightarrow I_{k,j}=0,\]

so that the angular residual vanishes independently of the angle and the continued CES residual is free to determine the required complex rotation.

The exposed angular residual is normalized by \(2\pi\) and by the square root of the number of vectorized angular residuals. This changes neither its zero set nor the active-set selection.

Unlike a periodic chordal penalty, the residual uses the unwrapped angle itself. Hence \(2\pi\), \(-2\pi\), and other equivalent rotations are not treated as alternative representations of the real branch: the unique canonical real representative is

\[\angle q^\angle_{k,j} = 0.\]

This distinction is material when solver variables are unbounded in angle space.

Candidates whose real feasibility cannot safely be certified are handled conservatively. In particular, a zero distribution parameter, a negative or undefined forced economic quantity, an invalid target, or an invalid CES parameter does not release the corresponding angle. The candidate is instead retained on the real branch.

_positive_feasibility_gate() additionally provides the continuous, semismooth mapping

\[\begin{split}g(\delta) = \begin{cases} 1-\exp(-\delta), & \delta>0,\\ 0, & \delta\leq0, \end{cases}\end{split}\]

for callers that require a positive feasibility weight. The current compute_qois() does not use this mapping: it deliberately uses the exact Boolean active-set classification above.

Warning

The exact active-set selection introduces a regime change at \(\delta_{k,j}=0\). It is therefore not globally differentiable across the real-feasibility boundary. Within either selected regime, the residual mapping retains the ordinary derivatives of the corresponding real or continued CES branch.

Warning

The term continuation does not imply that the complete complex CES mapping is globally smooth or analytic as a function of the input-volume angles.

Non-integer complex powers possess branch cuts. Consequently, \(r_\theta\) is smooth only inside connected regions in which the relevant complex powers remain on the same branch and away from singular cases handled specially by the underlying power operator.

In particular, rotations through the negative real axis require care for non-integer CES exponents. The numerical example below uses \(\rho=-1\), so its relevant powers are integer.

Note

The angular residual assumes that at most one scalar input-volume angle per FCES block is free to differ from zero. This assumption must be enforced by the surrounding model.

The feasibility calculation nevertheless treats every input counterfactually as the potential free input. Hence it does not require the identity of that free input, or the identity of any real-infeasible block, to be known ex ante.

static _leave_one_out_logsumexp(x: np.ndarray)

Compute stable leave-one-out log-sum-exp values.

For every last-axis element \(x_k\), return

\[\log\left(\sum_{\ell\neq k}\exp x_\ell\right).\]

The calculation is linear in the last-axis size. A global stabilized reduction handles every exclusion except a unique maximum, for which a second maximum avoids cancellation. Positive infinities are handled analytically, an empty sum gives -np.inf, and any NaN is propagated across its last-axis slice.

Parameters

x (numpy.ndarray) – Array to be leave-one-out processed.

Example

An ordinary row returns the log-sum-exp of all other entries,

>>> x = np.array([[.0, 1., 2.]])
>>> HicksianUFCES._leave_one_out_logsumexp(x)
array([[2.31, 2.13, 1.31]])

while a unique dominant maximum remains numerically stable:

>>> x = np.array([[1000., .0, -1000.]])
>>> HicksianUFCES._leave_one_out_logsumexp(x)
array([[   0., 1000., 1000.]])

Positive infinities are treated exactly,

>>> x = np.array([[np.inf, .0, 1.]])
>>> HicksianUFCES._leave_one_out_logsumexp(x)
array([[1.31,  inf,  inf]])
>>> x = np.array([[np.inf, np.inf, 1.]])
>>> HicksianUFCES._leave_one_out_logsumexp(x)
array([[inf, inf, inf]])

and the empty-sum and undefined cases remain explicit:

>>> HicksianUFCES._leave_one_out_logsumexp(np.array([[5.]]))
array([[-inf]])
>>> x = np.array([[np.nan, .0, 1.]])
>>> HicksianUFCES._leave_one_out_logsumexp(x)
array([[nan, nan, nan]])
_real_feasibility_logslacks_computer(self, *, output_volume: np.ndarray, input_volumes: np.ndarray)

Compute counterfactual positive-real feasibility log-slacks.

Treat every last-axis input in turn as the singleton free input. Let

\[x_{k,j} = \frac{q_{k,j}}{s_{k,j}}.\]

For finite \(\rho_j\neq0\), the corresponding slack is

\[\delta_{k,j} = -\log\left[ \sum_{\ell\neq k}a_{\ell,j}\left( \frac{x_{\ell,j}}{q_j^{1/\nu_j}} \right)^{\rho_j} \right].\]

For \(a_{k,j}>0\), \(\delta_{k,j}>0\) is necessary and sufficient for a strictly positive real free input. Cobb-Douglas feasibility is represented by +/-np.inf; smooth-Leontief uses the corresponding normalized leave-one-out sum.

At \(\nu_j=+\infty\), under the limiting CES interpretation, a strictly positive finite target is attainable only for \(q_j=1\).

The second returned array identifies certifiable slacks. Values produced for uncertifiable candidates are numerical placeholders.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

Example

Consider

\[q_j = \left( \frac{1}{2}q_{1,j}^{-1} + \frac{1}{4}q_{2,j}^{-1} + \frac{1}{4}q_{3,j}^{-1} \right)^{-1},\]

calibrated at unit volumes and \(q_j=1\):

>>> invariants = dict(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.ones((1, 3)),
...     input_values=np.array([[2., 1., 1.]])
... )
>>> h = HicksianUFCES(sigma=.5).compute_calibrand_0(
...     **invariants
... )

With [2., 1., 1.], every singleton-free-input problem is real-feasible and certifiable:

>>> d, cert = h._real_feasibility_logslacks_computer(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.array([[2., 1., 1.]])
... )
>>> d
array([[0.69, 0.69, 0.69]])
>>> cert
array([[ True,  True,  True]])

With [2., .4, .4], the first candidate is real-infeasible because its forced contribution is \(1.25>1\):

>>> d, cert = h._real_feasibility_logslacks_computer(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.array([[2., .4, .4]])
... )
>>> d
array([[-0.22,  0.13,  0.13]])
>>> cert
array([[ True,  True,  True]])

The homogeneity degree enters through \(q_j^{1/\nu_j}\). Hence, with \(\nu_j=2\), target 4. is reduced to the pre-homogeneity target \(4^{1/2}=2\):

>>> hν = HicksianUFCES(
...     sigma=.5, nu=2.
... ).compute_calibrand_0(**invariants)
>>> d, cert = hν._real_feasibility_logslacks_computer(
...     output_volume=np.array([[4.]]),
...     input_volumes=np.array([[4., 2., 2.]])
... )
>>> d
array([[0.69, 0.69, 0.69]])
>>> cert
array([[ True,  True,  True]])

Finally, an invalid value may occupy the hypothetical free position, but prevents certification whenever it is forced:

>>> d, cert = h._real_feasibility_logslacks_computer(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.array([[np.nan, 1., 1.]])
... )
>>> cert
array([[ True, False, False]])
aggregated_input_volumes_rooter(self)

Return the unimodular CES aggregation residual.

output_volume_rooter(self)

Return the positive-real-feasibility angular residual.

The residual anchors the free angle to zero whenever a positive real FCES solution is analytically available and releases it when such a solution is analytically impossible.

No particular input or FCES block is designated ex ante as real-infeasible.

compute_qois(self, *, output_volume: np.ndarray, input_volumes: np.ndarray, input_volumes_angles: np.ndarray | float = 0.0)

Compute the continued CES and real-selection residuals.

Economic input volumes remain real. Their auxiliary angles define the unimodular extension

\[q^\angle_{k,j} = q_{k,j}\left( \cos\angle q^\angle_{k,j} + \mathrm{i}\sin\angle q^\angle_{k,j} \right).\]

Complex continuation is made available only when the corresponding positive-real FCES problem is analytically infeasible.

Every input is counterfactually treated as the singleton free input and its positive-real feasibility is determined by _real_feasibility_logslacks_computer().

Let \(\delta_{k,j}\) denote the resulting logarithmic feasibility slack and define the active-set indicator

\[\begin{split}I_{k,j} = \begin{cases} 1, & \delta_{k,j}>0,\\ 0, & \delta_{k,j}\leq0. \end{cases}\end{split}\]

whenever the feasibility calculation is certifiable. Uncertifiable candidates are conservatively assigned \(I_{k,j}=1\).

The angle actually supplied to the CES evaluation is

\[\widetilde{\angle q}^\angle_{k,j} = (1-I_{k,j})\angle q^\angle_{k,j}.\]

Hence real-feasible candidates are projected onto the real manifold before aggregation,

\[I_{k,j}=1 \ \Longrightarrow\ \widetilde{\angle q}^\angle_{k,j}=0,\]

while real-infeasible candidates retain their full continuation angle,

\[I_{k,j}=0 \ \Longrightarrow\ \widetilde{\angle q}^\angle_{k,j} = \angle q^\angle_{k,j}.\]

Conversely, only angles belonging to the real-feasible active set enter the angular residual. Before normalization, this residual is

\[\widetilde r_{\angle,j} = \sum_{k\in\mcI_j} I_{k,j}\angle q^\angle_{k,j}.\]

Under the class contract that at most one scalar input-volume angle per FCES block is free to differ from zero, a real-feasible free input therefore satisfies

\[\widetilde r_{\angle,j}=0 \iff \angle q^\angle_{k,j}=0,\]

whereas a real-infeasible free input is entirely released from the angular equation.

To prevent the angular residual family from dominating the global quadratic objective merely because angles are expressed in radians and several FCES blocks are vectorized simultaneously, the exposed residual is normalized as

\[r_{\angle,j} = \frac{\widetilde r_{\angle,j}}{ 2\pi\sqrt{N_{\angle}} },\]

where \(N_{\angle}\) is the number of vectorized angular residuals produced by the present call.

The factor \(2\pi\) expresses angular displacement in full-turn units. The factor \(\sqrt{N_{\angle}}\) RMS-normalizes the corresponding quadratic residual family, so its total contribution to a sum-of-squares objective does not mechanically grow with the number of vectorized FCES blocks.

This scaling changes neither the zero set nor the real/complex active-set selection. In particular,

\[r_{\angle,j}=0 \iff \widetilde r_{\angle,j}=0.\]

The active-set selection deliberately introduces a regime change at \(\delta_{k,j}=0\). Within each selected regime, however, the residual mapping retains the ordinary derivatives of the corresponding real or continued CES branch.

The unwrapped angle is used directly. Consequently, \(2\pi\), \(-2\pi\), and other equivalent rotations are not alternative zeros of the real-selection residual: \(0\) is the unique canonical representation of the real branch.

input_volumes_angles may be scalar or array-valued. NumPy/JAX broadcasting in the active-set selections and rotations makes the default scalar value .0 follow the same computational path as a broadcast-compatible angle array.

Parameters
Example

Consider

\[q_j = \left( \frac{1}{2}q_{1,j}^{-1} + \frac{1}{4}q_{2,j}^{-1} + \frac{1}{4}q_{3,j}^{-1} \right)^{-1},\]

with target \(q_j=1\) and \(q_{1,j}\) free. If \(q_{2,j}=q_{3,j}=1\), then

\[\delta_{1,j}=-\log\left(\frac14+\frac14\right)=\log2>0,\]

so the positive-real branch exists and the free angle is forced to zero. If instead \(q_{2,j}=q_{3,j}=0.4\),

\[\delta_{1,j}=-\log(1.25)<0,\]

and no positive-real \(q_{1,j}\) can satisfy the constraint. Indeed, the ordinary FCES equation would require

\[1 = \frac{1}{2q_{1,j}} + 1.25 \quad\Longrightarrow\quad q_{1,j}=-2.\]

This is the point of the unimodular continuation. An HicksianFCES cannot keep the economic volume equal to +2. and simultaneously use the algebraic value -2.:

>>> invariants = dict(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.ones((1, 3)),
...     input_values=np.array([[2., 1., 1.]])
... )
>>> f = HicksianFCES(sigma=.5).compute_calibrand_0(**invariants)
>>> f._output_volume_computer(
...     input_volumes=np.array([[2., .4, .4]])
... )
array([[0.67]])
>>> f._output_volume_computer(
...     input_volumes=np.array([[-2., .4, .4]])
... )
array([[0.8]])

HicksianUFCES instead keeps the positive magnitude 2. and represents the missing sign through a rotation by \(\pi\):

>>> h = HicksianUFCES(sigma=.5).compute_calibrand_0(**invariants)
>>> q = np.array([[2., .4, .4]])
>>> h.compute_qois(
...     output_volume=np.array([[1.]]),
...     input_volumes=q,
...     input_volumes_angles=np.array([[np.pi, .0, .0]])
... ).output_volume_rooter()
array([[0.]])
>>> h.aggregated_input_volumes_rooter()
array([[0.-2.16e-17j]])

The same supplied angle is ignored whenever the real branch is feasible. With unit forced volumes the first candidate has \(\delta_{1,j}>0\), so its effective angle is projected to zero and the angular residual remains nonzero:

>>> h.compute_qois(
...     output_volume=np.array([[1.]]),
...     input_volumes=np.ones((1, 3)),
...     input_volumes_angles=np.array([[np.pi/2., .0, .0]])
... ).output_volume_rooter()
array([[0.25]])
class iamax.library.HicksianACES
Inheritance diagram of iamax.library.HicksianACES

HicksianCES’s altered version whose costs calculation is based on accounting rules.

class iamax.library.HicksianAFCES
Inheritance diagram of iamax.library.HicksianAFCES

HicksianACES’s altered version whose underlying trade-off is forced.

class iamax.library.HicksianAUFCES
Inheritance diagram of iamax.library.HicksianAUFCES

HicksianACES’s altered version whose underlying unimodularly rotating trade-off is forced.

class iamax.library.HicksianGAFCES(*args, negated_mask: bool = False, **kwargs)
Inheritance diagram of iamax.library.HicksianGAFCES

HicksianACES’s altered version whose underlying trade-off is forced in a generalized manner by restricting it to a consumption subset of arbitrary size.

Inputs are partitioned between those remaining freely subject to the inherited trade-off and those whose consumption is forced onto its externally supplied level.

Parameters

negated_mask (bool) – Whether the coding passed as argument input_volumes_drates2 of method compute_calibrand_0() is to be negated. When set to False, values 1 and 0 respectively designate freely traded-off and forced inputs. When set to True, the equivalent coding is reversed. Set to False by default.

Note

Denoting as \(\mathcal{F}_j\) and \(\mathcal{C}_j\) the freely traded-off and forced input sets, respectively, generalized forcing operates through the inherited CES representation in two complementary ways.

For finite sigma, the base calibrands of inputs in \(\mathcal{F}_j\) retain their initial calibration while those in \(\mathcal{C}_j\) are recalibrated from their current observed consumptions.

At sigma=np.inf, inherited input demand instead relies on alleged_input_shares. Current forced IOQ shares are then imposed directly while freely traded-off inputs preserve the relative allocation returned by the inherited price-responsive mechanism and absorb the remaining share mass.

Attention

This class is intended to remain valid for arbitrary sigma values, although finite nonzero sigma cases remain comparatively less tested.

compute_calibrand_0(self, *, output_volume: float, input_values: np.ndarray, input_volumes: np.ndarray, input_volumes_drates2: np.ndarray)

Altered version of compute_calibrand_0() that initializes the generalized forcing configuration.

Denoting as \(d_k\) the \(k\)th component of input_volumes_drates2 and as \(m_k\) its internally harmonized configuration indicator,

(226)\[\begin{split}m_k = \begin{cases} 1-d_k, & \text{if mask is negated}, \\ d_k, & \text{otherwise}, \end{cases}\end{split}\]

\(m_k=1\) designates an input remaining freely subject to trade-off, while every other admissible value designates an input whose consumption is forced.

The inherited CES calibration itself is initially performed ordinarily over the complete input set. The forcing mask stored by this method subsequently determines which base calibrands may be recalibrated by compute_calibrand_0_star() and which retain their initial value.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

  • input_volumes_drates2 (numpy.ndarray) – \((\bQ^{\text{drate}^{ii}}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes_drates2’s slice. Values 1 and 0 respectively designate freely traded-off and forced inputs when negated_mask=False; the equivalent coding is reversed when negated_mask=True. Extended configurations supplied according to HicksianGPAFCES’s convention are also admissible, in which case both forced states collapse onto the single forced configuration represented here.

Example
>>> ovol  = 20973.128
>>> ivols = np.array([[1782.232, 19190.896]])
>>> ivals = 1e3*ivols
>>> imsks = np.array([[1, 0]])
>>> h = HicksianGAFCES(sigma=0, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     output_volume         = ovol,
...     input_volumes         = ivols,
...     input_values          = ivals,
...     input_volumes_drates2 = imsks,
... )
>>> h.calibrand_0
array([[11.77,  1.09]])
>>> h._traded_off
array([[ True, False]])
compute_calibrand_0_star(self, *, output_volume: float, input_values: np.ndarray, input_volumes: np.ndarray, ioq_shares_drates: np.ndarray = 0.0, ioq_shares_drates2: np.ndarray = 0.0, ioq_productivities_drates: np.ndarray = 0.0, ioq_productivities_drates2: np.ndarray = 0.0)

Augmented version of compute_calibrand_0_star() that integrates generalized consumption forcing.

Before the inherited star transformation, the operative base calibration is updated according to

(227)\[\begin{split}a_{k,t} = \begin{cases} a_{k,0}, & k \in \mathcal{F}_j, \\ \widehat a_{k,t}, & k \in \mathcal{C}_j, \end{cases}\end{split}\]

where \(a_{k,0}\) is the initial calibrand retained by a freely traded-off input and \(\widehat a_{k,t}\) is the ordinary inherited calibration implied by the current observed consumption of a forced input.

The resulting mixed base calibration is then passed unchanged to compute_calibrand_0_star(), which applies the ordinary share and productivity transformations.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

  • ioq_shares_drates (numpy.ndarray) – \((\bbeta^\text{drate}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_shares_drates’s slice. Set to .0 by default.

  • ioq_shares_drates2 (numpy.ndarray) – \((\bbeta^{\text{drate}^{ii}}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_shares_drates2’s slice. Set to .0 by default.

  • ioq_productivities_drates (numpy.ndarray) – \((\balpha^{-1,\ \text{drate}_{k,j}})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_productivities_drates’s slice. Set to .0 by default.

  • ioq_productivities_drates2 (numpy.ndarray) – \((\balpha^{-1,\ \text{drate}^{ii}_{k,j}})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_productivities_drates2’s slice. Set to .0 by default.

Example
>>> ovol  = 20973.128
>>> ivols = np.array([[1782.232, 19190.896]])
>>> iprcs = np.array([[1000., 1000.]])
>>> ivals = iprcs*ivols
>>> imsks = np.array([[0, 1]])
>>> h     = HicksianGAFCES(sigma=.0, negated_mask=False)
>>> h.aggregation_type
'Leontief'
>>> h.compute_calibrand_0(
...     output_volume         = ovol,
...     input_volumes         = ivols,
...     input_values          = ivals,  # †
...     input_volumes_drates2 = imsks,
... ).calibrand_0
array([[11.77,  1.09]])
>>> ivôls = h.input_volumes_computer(
...     output_volume = ovol,
...     input_prices  = iprcs,  # †
... )
>>> ivôls
array([[ 1782.23, 19190.9 ]])
>>> h._output_volume_computer(input_volumes=ivôls)
array([[20973.13]])

Let’s now force the first good’s level of consumption by editing its market configuration.

>>> h.compute_calibrand_0_star(
...     output_volume = ovol,
...     input_volumes = ivols * np.array([[.5, 2.]]),
...     input_values  = ivals,  # †
... ).calibrand_0
array([[23.54,  1.09]])
>>> ivôls = h.input_volumes_computer(
...     output_volume = ovol,
...     input_prices  = iprcs * np.array([[2., .5]]),  # †
... )
>>> ivôls
array([[  891.12, 19190.9 ]])
>>> h._output_volume_computer(input_volumes=ivôls)
array([[20973.13]])

† : put aside in the sigma=0 case ; maintained in anticipation of a future sigma-related generalization.

allege_input_shares(self, *, input_prices: np.ndarray, input_prices_lagged: np.ndarray, ioq_intensities: np.ndarray, ioq_intensities_lagged: np.ndarray)

Alleged input shares, \(1 \times |\mcI_j|\), augmented so as to integrate generalized consumption forcing in the perfect-substitutes allocation.

Let \(\widetilde{s}_k\) denote the price-responsive share returned by allege_input_shares() and let

(228)\[\overline{\alpha}_k = \alpha_k/\sum_{k'}\alpha_{k'}\]

denote the current normalized IOQ intensity of input \(k\).

Denoting as \(\mathcal{F}_j\) and \(\mathcal{C}_j\) the freely traded-off and forced input sets, respectively, the resulting alleged shares satisfy

(229)\[\begin{split}s_k = \begin{cases} \overline{\alpha}_k, & k \in \mathcal{C}_j, \\ \widetilde{s}_k \dfrac{ 1-\sum_{k' \in \mathcal{C}_j}\overline{\alpha}_{k'} }{\sum_{k' \in \mathcal{F}_j}\widetilde{s}_{k'}}, & k \in \mathcal{F}_j. \end{cases}\end{split}\]

Forced inputs therefore retain their current normalized IOQ intensities, while freely traded-off inputs preserve the relative proportions returned by the inherited price-responsive mechanism and exhaust the residual share mass.

In the all-free limiting case, the inherited alleged shares are consequently left unchanged. In the all-forced limiting case, the result reduces to the complete current normalized IOQ vector.

Parameters
  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

  • input_prices_lagged (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of the lagged version of input_prices.

  • ioq_intensities (numpy.ndarray) – \((\balpha_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_intensities’s slice.

  • ioq_intensities_lagged (numpy.ndarray) – \((\balpha_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of the lagged version of ioq_intensities.

Example
>>> invariants = dict(
...     input_prices           = np.array([[1., 1.25, 1.]]),
...     input_prices_lagged    = np.array([[1., 1., 1.]]),
...     ioq_intensities        = np.array([[.5, .4, .1]]),
...     ioq_intensities_lagged = np.array([[.6, .2, .2]]),
... )
>>> h = HicksianGAFCES(sigma=np.inf, eta=.0, negated_mask=False)

With every input freely traded-off, the inherited allocation is recovered exactly.

>>> h._traded_off = np.array([[1, 1, 1]]).astype(bool)
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.6, 0.2, 0.2]])

Let’s now successively force one and then two inputs.

>>> h._traded_off = np.array([[0, 1, 1]]).astype(bool)
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.5 , 0.25, 0.25]])
>>> h._traded_off = np.array([[0, 0, 1]]).astype(bool)
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.5, 0.4, 0.1]])

When every input is forced, the current normalized IOQ vector is recovered in full.

>>> h._traded_off = np.array([[0, 0, 0]]).astype(bool)
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.5, 0.4, 0.1]])

Note

Provided the inherited and current IOQ normalizations are well-defined, the transformation preserves alleged_input_shares’s right stochasticity:

(230)\[\sum_k s_k = 1.\]
class iamax.library.HicksianRGAFCES
Inheritance diagram of iamax.library.HicksianRGAFCES

s/e.

aggregated_input_volumes_rooter(self, *, output_volume: np.ndarray, input_volumes: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

(231)\[\approx 0\]
Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

class iamax.library.HicksianGPAFCES(*args, negated_mask: bool = False, **kwargs)
Inheritance diagram of iamax.library.HicksianGPAFCES

Productivity-aware generalization of HicksianGAFCES. It extends generalized consumption forcing by distinguishing forced inputs according to whether they belong to the productive CES support. In the absence of unproductive inputs, its behavior reduces to that of HicksianGAFCES.

Parameters

negated_mask (bool) – Whether the coding passed as argument input_volumes_drates2 of method compute_calibrand_0() is to be negated. When set to False, values 1, 0 and -1 respectively designate freely traded-off, forced productive and forced unproductive inputs. When set to True, the equivalent coding respectively becomes 0, 1 and 2. Set to False by default.

Note

Unproductive input consumptions remain part of input demand and, through HicksianACES, of accounting costs. Their productive contribution is however removed before aggregation.

Freely traded-off productive inputs are allocated endogenously over the productive support. Forced productive inputs remain part of that support but have their consumption imposed exogenously. Forced unproductive inputs are likewise consumed exogenously but neither contribute to output nor consume productive allocation mass.

In the perfect-substitutes case, unproductive consumptions remain outside alleged_input_shares, leaving the latter as an allocation defined exclusively over the productive support.

Removing an unproductive input from a perfect-substitutes technology also removes effective quantity from its linear aggregation. Since the ordinary CES calibration Eq.212 degenerates to unit calibrands when sigma=np.inf, this missing productive scale cannot be represented by inherited calibrand_0 parametrization. The calibration-period aggregate productive IOQ intensity is therefore retained separately as an additional scalar calibration coefficient of that limiting technology.

This scalar calibration assumes neutral input_volumes_shocks, i.e. \(\bQ_j^\text{dmult}\) (see Eq.215), at \(t=0\). With non-neutral calibration-period shocks, it would instead be calibrated from calibration-period effective input volumes.

Reduction to HicksianGAFCES whenever every input is productive is a class invariant: productivity awareness must then become behaviorally neutral rather than define an alternative generalized-forcing mechanism.

Attention

This class is intended to remain valid for arbitrary sigma values, although finite nonzero sigma cases remain comparatively less tested.

Example

The reduction to HicksianGAFCES can first be seen at the Leontief endpoint. Consider three productive inputs, of which only the second is forced. Both classes consequently start from exactly the same generalized-forcing calibration.

>>> calkws = dict(
...     output_volume = 100.,
...     input_volumes = np.array([[20., 25., 10.]]),
...     input_values  = np.array([[40., 50., 20.]]),
...     input_volumes_drates2 = np.array([[1, 0, 1]]),
... )
>>> gaf  = HicksianGAFCES(sigma=.0, negated_mask=False)
>>> gpaf = HicksianGPAFCES(sigma=.0, negated_mask=False)
>>> _ = gaf.compute_calibrand_0(**calkws)
>>> _ = gpaf.compute_calibrand_0(**calkws)
>>> gaf.calibrand_0, gpaf.calibrand_0
(array([[ 5.,  4., 10.]]), array([[ 5.,  4., 10.]]))

Suppose now that the observed consumptions become 10., 12.5 and 20.. The first and third inputs remain freely traded-off, whereas the second one is forced onto its new consumption level. The productivity-aware calibration must consequently collapse onto precisely the same mixed calibration as HicksianGAFCES.

>>> ivols1 = np.array([[10., 12.5, 20.]])
>>> ivals1 = 2.*ivols1
>>> _ = gaf.compute_calibrand_0_star(
...     output_volume=100., input_volumes=ivols1, input_values=ivals1,
... )
>>> _ = gpaf.compute_calibrand_0_star(
...     output_volume=100., input_volumes=ivols1, input_values=ivals1,
... )
>>> gaf.calibrand_0_star, gpaf.calibrand_0_star
(array([[ 5.,  8., 10.]]), array([[ 5.,  8., 10.]]))
>>> q_gaf = gaf.input_volumes_computer(
...     output_volume=100., input_prices=np.full((1, 3), 2.)
... )
>>> q_gpaf = gpaf.input_volumes_computer(
...     output_volume=100., input_prices=np.full((1, 3), 2.)
... )
>>> q_gaf, q_gpaf
(array([[20. , 12.5, 10. ]]), array([[20. , 12.5, 10. ]]))
>>> (
...     gaf._output_volume_computer(input_volumes=q_gaf),
...     gpaf._output_volume_computer(input_volumes=q_gpaf),
... )
(array([[100.]]), array([[100.]]))

The same reduction is particularly consequential at the perfect-substitutes endpoint, where HicksianGPAFCES otherwise introduces its additional productive-support machinery. With no unproductive input, that machinery must remain neutral and the allocation returned by HicksianGAFCES must be recovered without alteration.

>>> calkws = dict(
...     output_volume = 1.,
...     input_volumes = np.array([[.5, .4, .1]]),
...     input_values  = np.array([[.5, .4, .1]]),
...     input_volumes_drates2 = np.array([[1, 0, 1]]),
... )
>>> invariants = dict(
...     input_prices           = np.array([[1., 1.25, 1.]]),
...     input_prices_lagged    = np.array([[1., 1., 1.]]),
...     ioq_intensities        = np.array([[.5, .4, .1]]),
...     ioq_intensities_lagged = np.array([[.6, .2, .2]]),
... )
>>> gaf  = HicksianGAFCES(
...     sigma=np.inf, eta=.0, negated_mask=False
... )
>>> gpaf = HicksianGPAFCES(
...     sigma=np.inf, eta=.0, negated_mask=False
... )
>>> _ = gaf.compute_calibrand_0(**calkws)
>>> _ = gpaf.compute_calibrand_0(**calkws)
>>> _ = gaf.allege_input_shares(**invariants)
>>> _ = gpaf.allege_input_shares(**invariants)
>>> gaf.alleged_input_shares, gpaf.alleged_input_shares
(array([[0.45, 0.4 , 0.15]]), array([[0.45, 0.4 , 0.15]]))
>>> gpaf._ioq_p_scale_0
array([[1.]])
>>> q_gaf = gaf.input_volumes_computer(
...     output_volume=1., input_prices=np.ones((1, 3))
... )
>>> q_gpaf = gpaf.input_volumes_computer(
...     output_volume=1., input_prices=np.ones((1, 3))
... )
>>> q_gaf, q_gpaf
(array([[0.45, 0.4 , 0.15]]), array([[0.45, 0.4 , 0.15]]))
>>> (
...     gaf._output_volume_computer(input_volumes=q_gaf),
...     gpaf._output_volume_computer(input_volumes=q_gpaf),
... )
(array([[1.]]), array([[1.]]))
_ioq_supports_computer(self, *, ioq_intensities: np.ndarray)

Productive-relative-intensity and unproductive-intensity counterparts of current ioq_intensities.

Let \(\alpha_k\) denote the current ioq_intensities of input \(k\), \(\mathcal{U}_j\) the forced unproductive input set (see Eq.236) and \(A_{j,0}^{\mathrm{prod}}\) the calibration-period productive scale (see Eq.239).

The denominator applied to productive current ioq_intensities is

(232)\[\begin{split}D_j = \begin{cases} A_{j,0}^{\mathrm{prod}}, & \mathcal{U}_j \neq \varnothing, \\ \sum_k \alpha_k, & \mathcal{U}_j = \varnothing, \end{cases}\end{split}\]

and the method returns

(233)\[\begin{split}\overline{\alpha}_k = \begin{cases} 0, & k \in \mathcal{U}_j, \\ \alpha_k/D_j, & k \notin \mathcal{U}_j, \end{cases}\end{split}\]

together with

(234)\[\begin{split}\alpha_k^{\mathrm{u}} = \begin{cases} \alpha_k, & k \in \mathcal{U}_j, \\ 0, & k \notin \mathcal{U}_j. \end{cases}\end{split}\]

Thus, when an unproductive component exists, productive intensities are expressed relative to the fixed productive requirement calibrated at \(t=0\), rather than to a current productive total varying with equilibrium quantities. Their sum therefore need not equal one: they are relative intensities rather than an alleged share vector. When no unproductive component exists, the historical generalized-forcing normalization is reproduced exactly.

Parameters

ioq_intensities (numpy.ndarray) – \((\balpha_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of ioq_intensities.

Example
>>> calkws = dict(
...     output_volume = 1.,
...     input_volumes = np.array([[.5, .4, .1]]),
...     input_values  = np.array([[.5, .4, .1]]),
... )
>>> h = HicksianGPAFCES(sigma=np.inf, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     input_volumes_drates2=np.array([[1, 0, -1]]), **calkws,
... )
>>> h._ioq_supports_computer(
...     ioq_intensities=np.array([[.5, .4, .1]])
... )
(array([[0.56, 0.44, 0.  ]]), array([[0. , 0. , 0.1]]))
>>> h._ioq_p_scale_0
array([[0.9]])

When no component is unproductive, the historical generalized forcing normalization is recovered exactly.

>>> h = HicksianGPAFCES(sigma=np.inf, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     input_volumes_drates2=np.array([[1, 1, 1]]), **calkws,
... )
>>> h._ioq_supports_computer(
...     ioq_intensities=np.array([[.5, .4, .1]])
... )
(array([[0.5, 0.4, 0.1]]), array([[0., 0., 0.]]))
>>> h._ioq_p_scale_0
array([[1.]])
_calibrands_0_computer(self, *, output_volume: float, input_values: np.ndarray, input_volumes: np.ndarray)

Unproductivity-naive and technology-unpolluted counterparts of calibrand_0, each \(1 \times |\mcI_j|\).

The first counterpart, denoted \(a_k^{\text{astr}}\), results from the ordinary calibration in Eq.212 while disregarding the unproductive status of the corresponding components. The second, denoted \(a_k^{\text{prod}}\), is calibrated after removing the values attached to unproductive components and subsequently assigning them a null calibration weight, i.e.

(235)\[\mcv_k \leftarrow 0,\ a_k^{\text{prod}} \leftarrow 0, \ \forall k \in \mathcal{U}_j,\]

where \(\mathcal{U}_j\) denotes the forced unproductive input set (see Eq.236). Such components remain represented by their ordinary calibration in the first counterpart while disappearing from the productive support in the second.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of input_values.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of input_volumes.

Example
>>> ovol  = 100.
>>> ivols = np.array([[20., 25., 10.]])
>>> ivals = 2.*ivols
>>> imsks = np.array([[1, 0, -1]])
>>> h = HicksianGPAFCES(sigma=.0, negated_mask=False)
>>> h.compute_calibrand_0(
...     output_volume         = ovol,
...     input_volumes         = ivols,
...     input_values          = ivals,
...     input_volumes_drates2 = imsks,
... )._calibrands_0_computer(
...     output_volume = ovol,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
(array([[ 5.,  4., 10.]]), array([[5., 4., 0.]]))

The last components illustrate both treatments of an unproductive input. The unproductivity-naive calibration treats its observed consumption ordinarily and therefore yields \(a_3=q_j/q_3=100/10=10\) in the sigma=0 case. Its technology-unpolluted counterpart instead assigns that component a null calibrand, thereby excluding it from productive support.

compute_calibrand_0(self, *, output_volume: float, input_values: np.ndarray, input_volumes: np.ndarray, input_volumes_drates2: np.ndarray)

Altered version of compute_calibrand_0() that initializes the generalized forcing configuration and its unproductive support. Denoting as \(m_k\) the internally harmonized configuration indicator,

(236)\[\begin{split}m_k = \begin{cases} 1-d_k, & \text{if mask is negated}, \\ d_k, & \text{otherwise}, \end{cases}\end{split}\]

where \(d_k\) is the \(k\)th component of \((\bQ^{\text{drate}^{ii}}_{k,j})_{k \in \mcI_j}\), i.e. a input_volumes_drates2 slice. Values \(m_k=1\), \(m_k=0\) and \(m_k=-1\) respectively designate freely traded-off productive, forced productive and forced unproductive inputs.

The initial calibration combines the two counterparts returned by _calibrands_0_computer() according to

(237)\[\begin{split}a_k = \begin{cases} a_k^{\text{astr}}, & m_k=-1, \\ a_k^{\text{prod}}, & \text{otherwise}, \end{cases}\end{split}\]

where \(a_k^{\text{astr}}\) and \(a_k^{\text{prod}}\) are as in Eq.235.

The observed calibration-period IOQ intensities are additionally retained according to

(238)\[\alpha_{k,0} = q_{k,0}/q_{j,0},\]

and, whenever an unproductive component is present, the aggregate productive intensity

(239)\[A_{j,0}^{\mathrm{prod}} = \sum_{k \notin \mathcal{U}_j}\alpha_{k,0}\]

is retained as the missing scalar calibration coefficient of the perfect-substitutes limit. In an entirely productive bundle this additional coefficient is neutralized to 1. so that the behavior of HicksianGAFCES is reproduced exactly. This calibration assumes neutral input_volumes_shocks (see Eq.215) at \(t=0\); otherwise \(A_{j,0}^{\mathrm{prod}}\) would have to be computed from calibration-period effective input volumes.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

  • input_volumes_drates2 (numpy.ndarray) – \((\bQ^{\text{drate}^{ii}}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of input_volumes_drates2. Values 1, 0 and -1 respectively designate freely traded-off, forced productive and forced unproductive inputs when negated_mask=False; the equivalent coding is 0, 1 and 2 when negated_mask=True.

Example
>>> ovol  = 100.
>>> ivols = np.array([[20., 25., 10.]])
>>> ivals = 2.*ivols
>>> imsks = np.array([[1, 0, -1]])
>>> h = HicksianGPAFCES(sigma=.0, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     output_volume         = ovol,
...     input_volumes         = ivols,
...     input_values          = ivals,
...     input_volumes_drates2 = imsks,
... )
>>> h.calibrand_0
array([[ 5.,  4., 10.]])
>>> h._traded_off
array([[ True, False, False]])
>>> h._unproductive
array([[False, False,  True]])
>>> h._ioq
array([[0.2 , 0.25, 0.1 ]])
>>> h._ioq_p_scale_0
array([[0.45]])

The equivalent negated-mask coding follows.

>>> h = HicksianGPAFCES(sigma=.0, negated_mask=True)
>>> _ = h.compute_calibrand_0(
...     output_volume         = ovol,
...     input_volumes         = ivols,
...     input_values          = ivals,
...     input_volumes_drates2 = np.array([[0, 1, 2]]),
... )
>>> h.calibrand_0
array([[ 5.,  4., 10.]])
>>> h._traded_off
array([[ True, False, False]])
>>> h._unproductive
array([[False, False,  True]])
compute_calibrand_0_star(self, *, output_volume: float, input_values: np.ndarray, input_volumes: np.ndarray, ioq_shares_drates: np.ndarray = 0.0, ioq_shares_drates2: np.ndarray = 0.0, ioq_productivities_drates: np.ndarray = 0.0, ioq_productivities_drates2: np.ndarray = 0.0)

Altered version of compute_calibrand_0_star() that integrates generalized consumption forcing and unproductive consumptions. Before the star transformation, the current calibration is updated according to

(240)\[\begin{split}a_{k,t} = \begin{cases} a_{k,0}, & k \text{ is freely traded-off}, \\ a_{k,t}^{\text{prod}}, & k \text{ is forced and productive}, \\ a_{k,t}^{\text{astr}}, & k \text{ is forced and unproductive}, \end{cases}\end{split}\]

where \(a_{k,0}\) is the calibration-period calibrand (see Eq.212), while \(a_{k,t}^{\text{prod}}\) and \(a_{k,t}^{\text{astr}}\) denote the technology-unpolluted and unproductivity-naive calibrations in Eq.235.

Two star counterparts are subsequently maintained. Attribute calibrand_0_astr applies the ordinary transformation in Eq.213 to the complete unproductivity-naive calibration and is later used to recover unproductive input consumptions. Attribute calibrand_0_star instead retains the generalized forcing calibration over the productive support, with unproductive components replaced by their technology-unpolluted counterparts.

At sigma=np.inf, inherited demand instead uses alleged_input_shares (see Eq.241). The missing scalar productive calibration \(A_{j,0}^{\mathrm{prod}}\) (see Eq.239) is supplied separately by _ioq_p_scale_0, while current raw IOQ intensities are retained for recovering forced consumptions.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

  • ioq_shares_drates (numpy.ndarray) – \((\bbeta^\text{drate}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_shares_drates’s slice. Set to .0 by default.

  • ioq_shares_drates2 (numpy.ndarray) – \((\bbeta^{\text{drate}^{ii}}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_shares_drates2’s slice. Set to .0 by default.

  • ioq_productivities_drates (numpy.ndarray) – \((\balpha^{-1,\ \text{drate}_{k,j}})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_productivities_drates’s slice. Set to .0 by default.

  • ioq_productivities_drates2 (numpy.ndarray) – \((\balpha^{-1,\ \text{drate}^{ii}_{k,j}})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_productivities_drates2’s slice. Set to .0 by default.

Example
>>> ovol  = 100.
>>> ivols = np.array([[20., 25., 10.]])
>>> iprcs = np.array([[2., 2., 2.]])
>>> ivals = iprcs*ivols
>>> imsks = np.array([[1, 0, -1]])
>>> h = HicksianGPAFCES(sigma=.0, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     output_volume         = ovol,
...     input_volumes         = ivols,
...     input_values          = ivals,
...     input_volumes_drates2 = imsks,
... )
>>> _ = h.compute_calibrand_0_star(
...     output_volume = ovol,
...     input_volumes = ivols,
...     input_values  = ivals,
... )
>>> h.calibrand_0
array([[ 5.,  4., 10.]])
>>> h.calibrand_0_star
array([[5., 4., 0.]])
>>> h.calibrand_0_astr
array([[ 5.,  4., 10.]])

Let’s now alter all three observed consumptions. The first input remains freely subject to trade-off, the second is forced and productive and the third is forced and unproductive.

>>> ivols1 = ivols * np.array([[.5, .5, 2.]])
>>> ivals1 = iprcs*ivols1
>>> h.compute_calibrand_0_star(
...     output_volume = ovol,
...     input_volumes = ivols1,
...     input_values  = ivals1,
... ).calibrand_0
array([[5., 8., 5.]])
>>> h.calibrand_0_star
array([[5., 8., 0.]])
>>> h.calibrand_0_astr
array([[10.,  8.,  5.]])

Accordingly, the freely traded-off first input is not forced onto its newly observed level, whereas the two forced consumptions are.

>>> ivôls = h.input_volumes_computer(
...     output_volume = ovol,
...     input_prices  = iprcs,
... )
>>> ivôls
array([[20. , 12.5, 20. ]])
>>> h._output_volume_computer(input_volumes=ivôls)
array([[100.]])
allege_input_shares(self, *, input_prices: np.ndarray, input_prices_lagged: np.ndarray, ioq_intensities: np.ndarray, ioq_intensities_lagged: np.ndarray)

Alleged productive input shares, \(1 \times |\mcI_j|\), augmented so as to keep unproductive consumptions entirely outside the perfect-substitutes allocation.

Let \(\widetilde{s}_k\) denote the price-responsive share returned by allege_input_shares() after removing unproductive components from ioq_intensities_lagged (see Eq.214). Let \(\overline{\alpha}_k\) denote the productive relative intensity processed from ioq_intensities (see Eq.233).

Denoting as \(\mathcal{F}_j\), \(\mathcal{C}_j\) and \(\mathcal{U}_j\) the freely traded-off productive, forced productive and forced unproductive sets (see Eq.236), the resulting alleged shares satisfy

(241)\[\begin{split}s_k = \begin{cases} 0, & k \in \mathcal{U}_j, \\ \overline{\alpha}_k, & k \in \mathcal{C}_j, \\ \widetilde{s}_k \dfrac{ 1-\sum_{k' \in \mathcal{C}_j}\overline{\alpha}_{k'} }{\sum_{k' \in \mathcal{F}_j}\widetilde{s}_{k'}}, & k \in \mathcal{F}_j. \end{cases}\end{split}\]

Thus, with an unproductive input, a forced productive intensity \(\alpha_k\) is translated into \(\alpha_k/A_{j,0}^{\mathrm{prod}}\) (see Eq.239). Since the productive demand system subsequently multiplies the complete allocation by \(A_{j,0}^{\mathrm{prod}}\), forced productive demand is restored exactly rather than altered by normalization.

Unproductive components receive no productive share and therefore consume none of the productive allocation mass. Their current raw intensities are retained separately in _ioq, directly recovered by input_volumes_computer().

Parameters
  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

  • input_prices_lagged (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of the lagged version of input_prices.

  • ioq_intensities (numpy.ndarray) – \((\balpha_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) ioq_intensities’s slice.

  • ioq_intensities_lagged (numpy.ndarray) – \((\balpha_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of the lagged version of ioq_intensities.

Example

Some commonalities first. The calibration quantities are made consistent with the current intensities so that, once the third component is declared unproductive, the t=0 productive scale is .9.

>>> calkws = dict(
...     output_volume = 1.,
...     input_volumes = np.array([[.5, .4, .1]]),
...     input_values  = np.array([[.5, .4, .1]]),
... )
>>> invariants = dict(
...     input_prices           = np.array([[1., 1.25, 1.]]),
...     input_prices_lagged    = np.array([[1., 1., 1.]]),
...     ioq_intensities        = np.array([[.5, .4, .1]]),
...     ioq_intensities_lagged = np.array([[.6, .2, .2]]),
... )

Let’s first reproduce the ordinary freely traded-off case.

>>> h = HicksianGPAFCES(sigma=np.inf, eta=.0, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     input_volumes_drates2=np.array([[1, 1, 1]]), **calkws,
... )
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.6, 0.2, 0.2]])

Let’s now force the first productive input.

>>> h = HicksianGPAFCES(sigma=np.inf, eta=.0, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     input_volumes_drates2=np.array([[0, 1, 1]]), **calkws,
... )
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.5 , 0.25, 0.25]])

The generalized forcing mechanism can also force several productive inputs.

>>> h = HicksianGPAFCES(sigma=np.inf, eta=.0, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     input_volumes_drates2=np.array([[0, 0, 1]]), **calkws,
... )
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.5, 0.4, 0.1]])

Let’s now make the third consumption unproductive while keeping the first two inputs freely traded-off. It is entirely removed from the productive share vector. The productive unit allocation therefore follows the relative parent shares over the first two components only.

>>> h = HicksianGPAFCES(sigma=np.inf, eta=.0, negated_mask=False)
>>> _ = h.compute_calibrand_0(
...     input_volumes_drates2=np.array([[1, 1, -1]]), **calkws,
... )
>>> h.allege_input_shares(**invariants).alleged_input_shares
array([[0.75, 0.25, 0.  ]])
>>> h._ioq_p_scale_0
array([[0.9]])

The three states may finally operate simultaneously. The forced productive intensity .4 represents .4/.9 of the fixed productive requirement and hence leaves .5/.9 to the freely traded-off productive component.

>>> HicksianGPAFCES(
...     sigma=np.inf, eta=.0, negated_mask=False
... ).compute_calibrand_0(
...     input_volumes_drates2=np.array([[1, 0, -1]]), **calkws,
... ).allege_input_shares(**invariants).alleged_input_shares
array([[0.56, 0.44, 0.  ]])

The equivalent negated-mask coding produces the same result.

>>> HicksianGPAFCES(
...     sigma=np.inf, eta=.0, negated_mask=True
... ).compute_calibrand_0(
...     input_volumes_drates2=np.array([[0, 1, 2]]), **calkws,
... ).allege_input_shares(**invariants).alleged_input_shares
array([[0.56, 0.44, 0.  ]])

Note

Whenever at least one freely traded-off productive component remains, such components absorb the productive mass left after forced productive consumptions have been assigned. A nonnegative residual allocation then requires

(242)\[\sum_{k \in \mathcal{C}_j}\alpha_k \leq A_{j,0}^{\mathrm{prod}}.\]

If every productive component is forced, no such residual adjustment exists and technological consistency requires

(243)\[\sum_{k \in \mathcal{C}_j}\alpha_k = A_{j,0}^{\mathrm{prod}}.\]

Failure of the latter equality is then a genuine technological inconsistency rather than something this method should normalize away artificially.

input_volumes_computer(self, *, output_volume: float, input_prices: np.ndarray)

Input demands, \(1 \times |\mcI_j|\), obtained by combining the technology-unpolluted and unproductivity-naive demand systems. Denoting by \(q_k^*\) the demand returned by Eq.219 under calibrand_0_star and by \(q_k^{\text{astr}}\) its counterpart obtained under calibrand_0_astr, the finite-elasticity demand is

(244)\[\begin{split}q_k = \begin{cases} q_k^{\text{astr}}, & k \in \mathcal{U}_j, \\ q_k^*, & k \notin \mathcal{U}_j. \end{cases}\end{split}\]

where \(\mathcal{U}_j\) denotes the forced unproductive input set (see Eq.236). The former preserves the demand implied by the ordinary, unproductivity-naive system for unproductive components, whereas the latter determines productive demands without allowing those components to pollute the productive CES support.

At sigma=np.inf, the inherited demand expression instead reads alleged_input_shares directly. The productive demand then becomes

(245)\[q_k = q_j A_{j,0}^{\mathrm{prod}} s_k Q^{\text{dmult}}_{k,j}, \ k \notin \mathcal{U}_j,\]

where \(q_j\) is as in Eq.219, \(A_{j,0}^{\mathrm{prod}}\) as in Eq.239 and \(s_k\) as in Eq.241. Here, \(Q^{\text{dmult}}_{k,j}\) corresponds to input_volumes_shocks (see Eq.215).

An unproductive input instead obeys

(246)\[q_k = q_j \alpha_k^{\mathrm{u}} Q^{\text{dmult}}_{k,j}, \ k \in \mathcal{U}_j.\]

with \(\alpha_k^{\mathrm{u}}\) as in Eq.234. Hence input_volumes_shocks remains fully operative for both productive and unproductive components. The special treatment concerns only the productive scale that the limiting ordinary CES calibration can no longer represent component-wise.

Parameters
  • output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

Example

Perfect substitutes with one freely traded-off productive component, one forced productive component and one forced unproductive component.

>>> h = HicksianGPAFCES(sigma=np.inf, eta=.0)
>>> _ = h.compute_calibrand_0(
...     output_volume         = 1.,
...     input_volumes         = np.array([[.5, .4, .1]]),
...     input_values          = np.array([[.5, .4, .1]]),
...     input_volumes_drates2 = np.array([[1, 0, -1]]),
... )
>>> _ = h.allege_input_shares(
...     input_prices           = np.ones((1, 3)),
...     input_prices_lagged    = np.ones((1, 3)),
...     ioq_intensities        = np.array([[.5, .4, .1]]),
...     ioq_intensities_lagged = np.array([[.5, .4, .1]]),
... )
>>> h.input_volumes_computer(
...     output_volume=1., input_prices=np.ones((1, 3))
... )
array([[0.5, 0.4, 0.1]])

The productive and unproductive demand systems therefore reproduce the calibration quantities simultaneously.

>>> ivôls = h.input_volumes_computer(
...     output_volume=1., input_prices=np.ones((1, 3))
... )
>>> h._output_volume_computer(input_volumes=ivôls)
array([[1.]])
_output_volume_computer(self, *, input_volumes: np.ndarray)

Output supply, \(1 \times 1\), purged from the productive contribution of forced unproductive consumptions. Before delegating aggregation to _output_volume_computer() (see Eq.216), unproductive components are contextually transformed according to

(247)\[\begin{split}a_k^{*,\mathrm{prod}} = \begin{cases} 0, & k \in \mathcal{U}_j, \\ a_k^*, & k \notin \mathcal{U}_j, \end{cases}\end{split}\]

where \(\mathcal{U}_j\) denotes the forced unproductive input set (see Eq.236) and \(a_k^*\) the inherited star calibrand (see Eq.213).

With \(Q^{\text{dmult}}_{k,j}\) denoting input_volumes_shocks (see Eq.215), input volumes are additionally transformed according to

(248)\[\begin{split}\widetilde q_k = \begin{cases} Q^{\text{dmult}}_{k,j}, & k \in \mathcal{U}_j, \\ q_k, & k \notin \mathcal{U}_j. \end{cases}\end{split}\]

Since inherited aggregation operates on \(\widetilde q_k/Q^{\text{dmult}}_{k,j}\), an unproductive component’s temporary effective volume reduces to 1 whenever the shock is non-null, while _divide() maps the indeterminate \(0/0\) case to zero. Combined with its null calibration weight, either case removes that component from the productive CES support without propagating an undefined value.

At sigma=np.inf, productive input demand Eq.245 contains the scalar calibration requirement \(A_{j,0}^{\mathrm{prod}}\) (see Eq.239). Since inherited aggregation is then linear over productive effective volumes, the same fixed coefficient must be removed after aggregation:

(249)\[q_j = \frac{ \sum_{k \notin \mathcal{U}_j} q_k/Q^{\text{dmult}}_{k,j} }{ A_{j,0}^{\mathrm{prod}} }.\]

Thus demand and production remain exact inverses while the productive scale is a fixed technology calibrand rather than a current equilibrium quantity.

Parameters

input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

Example
>>> ovol  = 100.
>>> ivols = np.array([[20., 10.]])
>>> ivals = 2.*ivols
>>> imsks = np.array([[0, -1]])
>>> h = HicksianGPAFCES(sigma=.0, negated_mask=False)
>>> h.compute_calibrand_0(
...     output_volume         = ovol,
...     input_volumes         = ivols,
...     input_values          = ivals,
...     input_volumes_drates2 = imsks,
... ).compute_calibrand_0_star(
...     output_volume = ovol,
...     input_volumes = ivols,
...     input_values  = ivals,
... )._output_volume_computer(
...     input_volumes=ivols
... )
array([[100.]])

The unproductive input’s consumed amount has no bearing on productive output.

>>> h._output_volume_computer(
...     input_volumes=np.array([[20., 1000.]])
... )
array([[100.]])

The same inverse identity holds in the perfect-substitutes case.

>>> h = HicksianGPAFCES(sigma=np.inf, eta=.0)
>>> _ = h.compute_calibrand_0(
...     output_volume         = 1.,
...     input_volumes         = np.array([[.5, .4, .1]]),
...     input_values          = np.array([[.5, .4, .1]]),
...     input_volumes_drates2 = np.array([[1, 0, -1]]),
... )
>>> _ = h.allege_input_shares(
...     input_prices           = np.ones((1, 3)),
...     input_prices_lagged    = np.ones((1, 3)),
...     ioq_intensities        = np.array([[.5, .4, .1]]),
...     ioq_intensities_lagged = np.array([[.5, .4, .1]]),
... )
>>> ivôls = h.input_volumes_computer(
...     output_volume=1., input_prices=np.ones((1, 3))
... )
>>> h._output_volume_computer(input_volumes=ivôls)
array([[1.]])
class iamax.library.HicksianFLUMRS(*args, **kwargs)
Inheritance diagram of iamax.library.HicksianFLUMRS

Unit MRS linear (forced) production function which boils down to a special case of HicksianFCES where \(a_k=1 \text{ for } k \in \mcI_j\) and \(\sigma \longrightarrow \infty\).

Note

This class’s compute_calibrand_0() has no system-dependence since the underlying calibrands are inherently equal to \(1\).

class iamax.library.MarshallianCES(*args, **kwargs)
Inheritance diagram of iamax.library.MarshallianCES

Constant Elasticity of Substitution abstraction whose optimal demand function derives from a maximization program formulated à la Marshall.

Note

Calculations assert constant return to scale, i.e. Eq.200’s \(\nu\) is forced to be \(1\) at instantiation.

compute_calibrand_0(self, *, budget_constraint: float, input_values: np.ndarray)

Calibrate the \((a_{k,j})_{k \in \mcI_j}\), i.e. the \(|\mcI_j|\) demand share parameters related to aggregate \(j\), considering them equal to the budget share of each of the underlying consumptions in the base year.

(250)\[a_k = \frac{\mcv_k}{r_j}, \ \forall k \ \in \mcI_j\]

with \(r_j\) the budget constraint to be saturated, \(\mcI_j\) the index set of the subject-to-\(r_j\) traded off consumptions, \(\mcv_k\) the \(q_k\)’s corresponding input value.

Parameters
Example
>>> m = MarshallianCES(sigma=None)
>>> _ = m.compute_calibrand_0(
...     budget_constraint=200.,
...     input_values=np.array([[110., 90.]])
... )
>>> m._shares_sum
array([[1.]])
>>> m.calibrand_0
array([[0.55, 0.45]])
>>> _ = m.compute_calibrand_0(
...     budget_constraint=100.,
...     input_values=np.array([[110., 90.]])
... )
>>> m._shares_sum
array([[2.]])
>>> m.calibrand_0
array([[0.55, 0.45]])
budget_constraint_crooter(self)

compute_calibrand_0()’s companion unit-dimensionless zero aiming at ensuring that the sum of shares add up to 1, \(1 \times 1\).

Example
>>> budc  = 200.
>>> ivals = np.array([[110., 90.]])
>>> m = MarshallianCES(sigma=None)
>>> _ = m.compute_calibrand_0(
...     budget_constraint=budc, input_values=ivals
... )
>>> m.budget_constraint_crooter()
array([[0.]])

Let’s then illustrate how such calculation can be vectorized.

>>> vbudc  = np.array([[[.5*budc]], [[budc]], [[2*budc]]])
>>> vivals = np.stack((ivals, ivals, ivals))
>>> m.compute_calibrand_0(
...     budget_constraint=vbudc, input_values=vivals
... ).budget_constraint_crooter()
array([[[ 1. ]],
       [[ 0. ]],
       [[-0.5]]])
input_volumes_computer(self, *, budget_constraint: float, input_prices: np.ndarray)

Input demands, \(1 \times |\mcI_j|\), denoted as

(251)\[q_k = \frac{ r_j p_{k}^{\frac{1}{\rho_j - 1}} }{ \sum_{k^{'} \in \mcI_j} p_{k^{'}}^{\frac{\rho_j}{\rho_j - 1}} \left[ \frac{a_k}{a_{k^{'}}} \right]^{\frac{1}{\rho_j - 1}} } = \frac{r_j}{ p_{k}^{\sigma_j} \sum_{k^{'} \in \mcI_j} p_{k^{'}}^{1-\sigma_j} \left[ \frac{a_{k^{'}}}{a_{k}} \right]^{\sigma_j} } \ \forall k \ \in \mcI_j\]

where \(r_j\) is the budget constraint to be saturated, \(\rho_j = \frac{\sigma_j - 1}{\sigma_j}\) the substitution parameter, \(\sigma_j = \frac{1}{1 - \rho_j}\) the elasticity of substitution, \(\mcI_j\) the index set of consumptions traded off under \(r_j\), \(p_k\) the \(q_k\)’s price and \(a_k\) as in Eq.250.

What happens internally, is actually performed in matrix terms (so as to benefit from vectorization) as follows:

(252)\[\bq = \frac{r_j}{ \bp^{\sigma_j} \odot \mathbf{1}_{|\mcI_j|} \left( \bp^{1 - \sigma_j} \odot \left[ \ba \cdot \frac{1}{\ba^{'}} \right]^{\sigma_j} \right)^{'} }\]

where \(\mathbf{1}_{|\mcI_j|}\), \(\bq = (q_1, \ldots, q_{|\mcI_j|})\), \(\ba = (a_1, \ldots, a_{|\mcI_j|})\) and \(\bp = (p_1, \ldots, p_{|\mcI_j|})\) are \(1 \times |\mcI_j|\) vectors.

Parameters
Example

Let’s directly deal with the \(\rho_j=0 \iff \sigma_j=1\) CES’s limiting case, which makes it boil down down to a Cobb-Douglas function.

>>> budc  = 200.
>>> ivals = np.array([[110., 90.]])
>>> iprcs = np.array([[.9, 1.1]])
>>> m = MarshallianCES(sigma=1)
>>> _ = m.compute_calibrand_0(
...     budget_constraint=budc, input_values=ivals
... )
>>> m.calibrand_0
array([[0.55, 0.45]])
>>> m.input_volumes_computer(
...     budget_constraint=budc, input_prices=np.ones_like(ivals),
... )
array([[110.,  90.]])
>>> m.input_volumes_computer(
...     budget_constraint=budc, input_prices=iprcs,
... )
array([[122.22,  81.82]])

You can check for yourself via, say, there. Let’s then illustrate how such calculation can be vectorized.

>>> vbudc  = np.array([[[.5*budc]], [[budc]], [[2*budc]]])
>>> vivals = np.stack((ivals, ivals, ivals))
>>> viprcs = np.stack((iprcs, iprcs, iprcs))
>>> m = MarshallianCES(sigma=[[[2.]], [[1.]], [[.5]]])
>>> _ = m.compute_calibrand_0(
...     budget_constraint=vbudc, input_values=vivals
... )
>>> m.calibrand_0
array([[[0.55, 0.45]],
       [[0.55, 0.45]],
       [[0.55, 0.45]]])
>>> m.input_volumes_computer(
...     budget_constraint=vbudc, input_prices=viprcs,
... )
array([[[ 71.79,  32.17]],
       [[122.22,  81.82]],
       [[222.22, 181.82]]])
class iamax.library.MarshallianGFCES(*args, negated_mask: bool = False, **kwargs)
Inheritance diagram of iamax.library.MarshallianGFCES

MarshallianCES’s altered version whose underlying trade-off is forced in a generalized manner by restricting it to a consumption subset of arbitrary size.

Parameters

negated_mask (bool) – Whether the mask passed as argument input_volumes_drates2 of the method compute_calibrand_0 is to be negated. Argument input_volumes_drates2 indeed boolean-specifies the good being subject to consumption tradeoff by default. Set to False by default.

compute_calibrand_0(self, *, budget_constraint: float, input_values: np.ndarray, input_volumes_drates2: np.ndarray)

s/e.

Parameters
  • budget_constraint (float) – \(r_j\), i.e. the \(j\)th \(1 \times 1\) budget_constraints’s element.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

  • input_volumes_drates2 (numpy.ndarray) – \((\bQ^\text{drate}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes_drates2’s slice. This variable is coerced into boolean and used as mask.

Example
>>> m = MarshallianGFCES(sigma=None, negated_mask=False)
>>> m.compute_calibrand_0(
...     budget_constraint     = 200.,
...     input_values          = np.array([[110., 90.]]),
...     input_volumes_drates2 = np.array([[1., 1.]])
... )
>>> m._shares_sum
array([[1.]])
>>> m.calibrand_0
array([[0.55, 0.45]])
>>> m.compute_calibrand_0(
...     budget_constraint     = 200.,
...     input_values          = np.array([[110., 90.]]),
...     input_volumes_drates2 = np.array([[1., 0.]])
... )
>>> m._shares_sum
array([[1.]])
>>> m.calibrand_0
array([[1., 0.]])
>>> m = MarshallianGFCES(sigma=None, negated_mask=True)
>>> m.compute_calibrand_0(
...     budget_constraint     = 200.,
...     input_values          = np.array([[110., 90.]]),
...     input_volumes_drates2 = np.array([[0., 0.]])
... )
>>> m._shares_sum
array([[1.]])
>>> m.calibrand_0
array([[0.55, 0.45]])
>>> m.compute_calibrand_0(
...     budget_constraint     = 200.,
...     input_values          = np.array([[110., 90.]]),
...     input_volumes_drates2 = np.array([[1., 0.]])
... )
>>> m._shares_sum
array([[1.]])
>>> m.calibrand_0
array([[0., 1.]])
input_volumes_computer(self, budget_constraint: float, input_values: np.ndarray, input_prices: np.ndarray)

s/e.

Parameters
Example

Let’s directly deal with the \(\rho_j=0 \iff \sigma_j=1\) CES’s limiting case, which makes it boil down down to a Cobb-Douglas function.

>>> budc  = 200.
>>> ivals = np.array([[110., 90.]])
>>> iprcs = np.array([[.9, 1.1]])
>>> imsks = np.array([[1., 0.]])
>>> m = MarshallianGFCES(sigma=1, negated_mask=False)
>>> m.compute_calibrand_0(
...     budget_constraint=budc, input_values=ivals,
...     input_volumes_drates2=imsks
... )
>>> m.calibrand_0
array([[1., 0.]])
>>> m.input_volumes_computer(
...     budget_constraint=budc, input_prices=np.ones_like(ivals),
...     input_values=ivals,
... )
array([[110.,  90.]])
>>> m.input_volumes_computer(
...     budget_constraint=budc, input_prices=iprcs,
...     input_values=ivals,
... )
array([[122.22,  81.82]])
>>> imsks = np.array([[0., 1.]])
>>> m = MarshallianGFCES(sigma=1, negated_mask=True)
>>> m.compute_calibrand_0(
...     budget_constraint=budc, input_values=ivals,
...     input_volumes_drates2=imsks
... )
>>> m.calibrand_0
array([[1., 0.]])
>>> m.input_volumes_computer(
...     budget_constraint=budc, input_prices=np.ones_like(ivals),
...     input_values=ivals,
... )
array([[110.,  90.]])
>>> m.input_volumes_computer(
...     budget_constraint=budc, input_prices=iprcs,
...     input_values=ivals,
... )
array([[122.22,  81.82]])
spendings_rooter(self, *, budget_constraint: float, input_values: np.ndarray)

s/e., \(1 \times 1\) unit-dimensionless zero.

(253)\[\approx 0\]
Parameters
class iamax.library.MarshallianLeo
Inheritance diagram of iamax.library.MarshallianLeo

Particularized version of MarshallianCES at \(\sigma=0\).

Warning

This class has vocation to be removed sooner or later and obtained directly through the parametrization of MarshallianCES.

compute_calibrand_0(self, *, input_prices: np.ndarray, input_volumes: np.ndarray)

s/e.

(254)\[a_k = q_k / \sum_{k^{'} \in \mcI_j} q_{k^{'}} \ \forall k \ \in \mcI_j\]

where \(q_k\) is the utility maximizing level of consumption.

Parameters
  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_prices’s slice.

  • input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

Example
>>> m = MarshallianLeo()
>>> _ = m.compute_calibrand_0(
...     input_prices=np.ones((1, 2)),
...     input_volumes=np.array([[110., 90.]])
... )
>>> m.calibrand_0
array([[0.55, 0.45]])
input_volumes_computer(self, *, budget_constraint: float, input_prices: np.ndarray, **_: np.ndarray)

Input demands, \(1 \times |\mcI_j|\), denoted as

(255)\[q_k = a_k \frac{r_j}{ \sum_{k^{'} \in \mcI_j} \tbp_{k^{'}} a_{k^{'}} } \ \forall k \ \in \mcI_j\]

where \(r_j\) is the budget constraint to be saturated, \(a_k\) is as in Eq.254 and \(\tbp_k\) is the \(k\)th price of consumption.

Parameters
Example
>>> m = MarshallianLeo()
>>> _ = m.compute_calibrand_0(
...     input_prices=np.array([[1.01, .99]]),
...     input_volumes=np.array([[110., 90.]])
... )
>>> m.calibrand_0
array([[0.55, 0.45]])
>>> m.input_volumes_computer(
...     budget_constraint=200, input_prices=np.ones((1, 2)),
... )
array([[110.,  90.]])
class iamax.library.EmplBMagnitude

Utility base class aggregating private methods related to employment- and utilization-based magnitudes.

static _drate_pow_increaser(*, dr: float, c: float = 1.0, s: float = 1.0)

Rate differential increasing function.

(256)\[\mfr(dr, c, s) := s\left(1 + dr\right)^{c}\]

Cf. parameters section below.

Parameters
  • dr (float) – Underlying rates differential of interest.

  • c (float) – Magnitude exponentiation parameter. To 1. by default.

  • s (float) – Scale factor (and value at \(dr=0\)). To 1. by default.

Note

For \(s>0\) and \(1+dr>0\), the function is \(dr\)-increasing when \(c>0\) and \(dr\)-decreasing when \(c<0\).

Example
>>> invariants = {'c': .2, 's': 1.}
>>> EmplBMagnitude._drate_pow_increaser(dr=-.5, **invariants)
0.8705505632961241
>>> EmplBMagnitude._drate_pow_increaser(dr=.0, **invariants)
1.0
>>> EmplBMagnitude._drate_pow_increaser(dr=.5, **invariants)
1.0844717711976986
static _factor_pow_increaser(*, f: float, c: float = 1.0, s: float = 1.0)

Factor increasing function.

(257)\[\mfr(f, c, s) := s\,\Re\left((f+0i)^c\right)\]
Parameters
  • f (float) – Underlying 1-centered factor e.g. rates ratio.

  • c (float) – Magnitude exponentiation parameter. To 1. by default.

  • s (float) – Scale factor (and value at \(f=1\)). To 1. by default.

Note

For \(s>0\) and \(f>0\), the function is \(f\)-increasing when \(c>0\) and \(f\)-decreasing when \(c<0\).

Example
>>> invariants = {
...     'c': .2,
...     's': 1.,
... }
>>> EmplBMagnitude._factor_pow_increaser(f=.5, **invariants)
0.8705505632961241
>>> EmplBMagnitude._factor_pow_increaser(f=1.0, **invariants)
1.0
>>> EmplBMagnitude._factor_pow_increaser(f=1.5, **invariants)
1.0844717711976986
>>> EmplBMagnitude._factor_pow_increaser(f=-.5, **invariants)
0.7042902001692477
static _rate_tanh_increaser(*, r: float, a: float = 1.0, b: float = 1.0, c: float = -1.0, s: float = 1.0)

Rate increasing function.

(258)\[\mfr(r, a, b, c, s) := s\left( a + b \tanh(-c r) \right)\]

Cf. parameters section below.

Parameters
  • r (float) – Underlying rate of interest.

  • a (float) – Unscaled magnitude value at \(r=0\). To 1. by default.

  • b (float) – Unscaled sigmoid amplitude parameter. To 1. by default.

  • c (float) – Magnitude sigmoid parameter. To -1. by default.

  • s (float) – Scale factor. To 1. by default.

Note

For \(s b > 0\), the function is \(r\)-decreasing when \(c > 0\) and \(r\)-increasing when \(c < 0\).

Example

A rate-decreasing parametrization first.

>>> invariants = {
...     'a': .05,
...     'b': .711594155955765,
...     'c': 1.09045112440194,
...     's': 1.
... }
>>> EmplBMagnitude._rate_tanh_increaser(r=.0, **invariants)
0.05
>>> EmplBMagnitude._rate_tanh_increaser(r=.5, **invariants)
-0.3036148489085014
>>> EmplBMagnitude._rate_tanh_increaser(r=1., **invariants)
-0.5171709549513277

The default parametrization is rate-increasing.

>>> EmplBMagnitude._rate_tanh_increaser(r=.0)
1.0
>>> EmplBMagnitude._rate_tanh_increaser(r=.5)
1.4621171572600098
>>> EmplBMagnitude._rate_tanh_increaser(r=1.)
1.7615941559557649
classmethod _rate_tanh_sigma_calibrator(cls, *, r: np.ndarray, e: np.ndarray, v: np.ndarray|float = 1.0)

Tanh calibration from a reference rate and elasticity.

(259)\[\begin{split}v &= \mfr(r, a, b, c, 1) \\ e &= \frac{r}{\mfr(r, a, b, c, 1)} \frac{\partial \mfr(r, a, b, c, 1)}{\partial r} \\ a &= b\end{split}\]

Defining \(q=-e\) and \(x=cr\), the calibration admits the closed-form solution

(260)\[\begin{split}x &= \frac{1}{2}\left(q + W_0\left(q\exp(-q)\right)\right) \\ c &= \frac{x}{r} \\ a=b &=\frac{v}{1-\tanh(x)}\end{split}\]

where \(W_0\) is the principal branch of the Lambert \(W\) function.

Parameters

Note

The stated closed-form calibration and numerical guarantee apply to non-positive local elasticities, for which \(q=-e\geq0\), and to strictly positive reference rates.

Example
>>> a, b, c = EmplBMagnitude._rate_tanh_sigma_calibrator(
...     r=.2, e=-.55
... )
>>> a.item()
1.6102140203583628
>>> b.item()
1.6102140203583628
>>> c.item()
1.9942499710804265

A vectorized example.

>>> r = np.array([[[.2]], [[.1]]])
>>> e = np.array([[[-.55]], [[-.1]]])
>>> a, b, c = (
...     EmplBMagnitude._rate_tanh_sigma_calibrator(
...         r=r, e=e
...     )
... )
>>> a
array([[[1.61]],
       [[1.1 ]]])
>>> b
array([[[1.61]],
       [[1.1 ]]])
>>> c
array([[[1.99]],
       [[0.92]]])
>>> m = a + b*np.tanh(-c*r)
>>> np.allclose(m, np.ones_like(m), rtol=0., atol=1e-14)
True
>>> np.allclose(-c*r*(1. + np.tanh(c*r)), e, rtol=0., atol=1e-14)
True
classmethod _lambertw0(cls, *, z: float)

Principal Lambert function, as used over nonnegative arguments.

(261)\[W_0(z)\exp\left(W_0(z)\right) = z\]

The approximation starts from \(\log(1+z)\) and applies five vectorized Halley refinements.

Parameters

z (float) – Nonnegative Lambert-function argument.

Note

Five Halley refinements are sufficient for the calibration. Indeed, the Lambert-function argument generated by _rate_tanh_sigma_calibrator() is of the form

\[z=q\exp(-q), \ q\geq 0,\]

and therefore belongs to \([0, 1/e]\). Over this interval, the principal branch \(W_0\) is smooth, while \(\log(1+z)\) already provides a close initial approximation. Halley’s cubic convergence consequently brings the result to standard floating-point precision within fewer than five refinements.

Attention

This fixed number of refinements is not asserted to suffice for an unrestricted implementation of \(W_0\), notably close to its branch point \(-1/e\) or for arbitrarily large positive arguments.

See also

_lambertw0_step().

Example

The implementation also remains accurate over a much wider positive range than the one required by the calibration.

>>> z = np.array([.0, *10**np.arange(-14., 13.)])
>>> z
array([0.e+00, 1.e-14, 1.e-13, 1.e-12, 1.e-11, 1.e-10, 1.e-09,
       1.e-08, 1.e-07, 1.e-06, 1.e-05, 1.e-04, 1.e-03, 1.e-02,
       1.e-01, 1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05,
       1.e+06, 1.e+07, 1.e+08, 1.e+09, 1.e+10, 1.e+11, 1.e+12])
>>> w = EmplBMagnitude._lambertw0(z=z)
>>> np.allclose(w*np.exp(w), z, rtol=1.72e-15, atol=1.02e-10)
True
static _lambertw0_step(*, z: float, w: float)

Principal Lambert-function Halley step.

(262)\[w^{+} := w - \frac{w\exp(w)-z}{ \exp(w)(w+1) -\dfrac{(w+2)(w\exp(w)-z)}{2(w+1)} }\]
Parameters
  • z (float) – Lambert-function argument.

  • w (float) – Running principal-branch approximation.

Note

Arguments are mutually broadcast following NumPy’s standard broadcasting rules.

classmethod _rate_tanh_bounds_calibrator(cls, *, r: np.ndarray, vmin: np.ndarray, vmax: np.ndarray)

Tanh calibration from two bounds and a reference value.

(263)\[\begin{split}\mfr(0, a, b, c, 1) &= v_{\max} \\ \mfr(r, a, b, c, 1) &= 1 \\ \mfr(1, a, b, c, 1) &= v_{\min}\end{split}\]

Defining

\[q := \frac{v_{\max}-1}{v_{\max}-v_{\min}},\]

the strictly positive sigmoid parameter solves

\[\frac{\tanh(cr)}{\tanh(c)}-q = 0,\]

and belongs to the bracket

\[0 < c < \frac{\operatorname{arctanh}(q)}{r}.\]

It follows that

(264)\[\begin{split}a &= v_{\max} \\ b &= \frac{v_{\max}-v_{\min}}{\tanh(c)}.\end{split}\]

The analytical bracket is valid for calibration targets satisfying

\[0<r<1, \ v_{\max}>1>v_{\min}, \ r < \frac{v_{\max}-1}{v_{\max}-v_{\min}} <1.\]

Warning

This calibration deliberately reproduces the functional form and normalization used in the original Scilab implementation. It should therefore be interpreted as a model-replication device rather than as a generally well-posed three-point sigmoid calibration. The calibration assumes

\[f(0) = v_{\max}, \ f(r) = 1, \ f(1) = v_{\min},\]

with

\[f(x) = a - b\tanh(cx), \ c > 0.\]

Several restrictions follow from this specification. First, the reference value is normalized to one and is not an independent calibration target. Second, because the hyperbolic tangent is not horizontally shifted, its inflection point is fixed at \(x=0\). Over \(x\in[0,1]\), the calibration therefore uses only one concave branch of the sigmoid. Consequently, not every otherwise coherent triplet \((r, v_{\min}, v_{\max})\) can be interpolated. Defining

\[q := \frac{v_{\max}-1}{v_{\max}-v_{\min}},\]

a finite strictly positive solution for \(c\) requires

\[0 < r < 1,\ v_{\max} > 1 > v_{\min}, \ r < q < 1.\]

The condition \(r<q\) is structural rather than numerical. It follows from the concavity of \(\tanh(cx)\) for \(x>0\). Inputs violating this condition do not correspond to a failure of the root solver; they lie outside the family of curves represented by this parameterization.

The limiting case \(r=q\) corresponds to \(c\to0\), i.e. to the linear limit, and is therefore excluded when a finite strictly positive curvature parameter is required.

The original Scilab calibration uses fixed inputs satisfying these restrictions and performs no explicit admissibility check. Allowing \(r\), \(v_{\min}\), and \(v_{\max}\) to vary therefore generalizes the original parameter values without enlarging the underlying functional family. Users supplying alternative bounds or reference rates are responsible for ensuring that the resulting calibration target remains admissible.

Finally, clipping or otherwise modifying \(v_{\min}\) or \(v_{\max}\) to enforce compatibility changes the requested calibration problem itself. Such preprocessing, if introduced, should therefore be understood as an explicit projection onto the admissible domain rather than as a harmless numerical safeguard.

Parameters
Example
>>> a, b, c = EmplBMagnitude._rate_tanh_bounds_calibrator(
...     r=.2, vmin=.95, vmax=1.05
... )
>>> a.item()
1.05
>>> b.item()
0.10087575919139345
>>> c.item()
2.7176753624893477

The three calibration conditions are satisfied.

>>> np.allclose(a + b*np.tanh(-c*.2), 1., rtol=.0, atol=1e-14)
True
>>> np.allclose(a + b*np.tanh(-c), .95, rtol=.0, atol=1e-14)
True
>>> np.allclose(a, 1.05, rtol=.0, atol=1e-14)
True

A vectorized example.

>>> r    = np.array([[[.2]], [[.3]], [[.4]]])
>>> vmin = np.array([[[.95]], [[.8]], [[.6]]])
>>> vmax = np.array([[[1.05]], [[1.2]], [[2.]]])
>>> a, b, c = EmplBMagnitude._rate_tanh_bounds_calibrator(
...     r=r, vmin=vmin, vmax=vmax
... )
>>> a
array([[[1.05]],
       [[1.2 ]],
       [[2.  ]]])
>>> b
array([[[0.1 ]],
       [[0.43]],
       [[1.44]]])
>>> c
array([[[2.72]],
       [[1.69]],
       [[2.14]]])

Each calibration is independently recovered along the first axis.

>>> np.allclose(
...     a + b*np.tanh(-c*r), np.ones_like(c),
...     rtol=.0, atol=1e-14
... )
True
>>> np.allclose(a + b*np.tanh(-c), vmin, rtol=.0, atol=1e-14)
True
>>> np.allclose(a, vmax, rtol=.0, atol=1e-14)
True
classmethod _rate_tanh_bounds_root(cls, r: np.ndarray, q: np.ndarray, nit: int = 8)

Positive tanh bounds-calibration root evaluation.

The strictly positive solution to

\[\frac{\tanh(cr)}{\tanh(c)}=q\]

can be characterized through the generalized second-kind Chebyshev relation

\[U_{\frac{2r}{1-r}} \left(\cosh((1-r)c)\right) = \frac{1+q}{1-q}.\]

Defining

\[\begin{split}y &:= (1-r)c, \\ \nu &:= \frac{1+r}{1-r}, \\ \log(k) &:= \log(1+q)-\log(1-q),\end{split}\]

the transformed root solves

\[G(y) := \log\left(\sinh(\nu y)\right) - \log\left(\sinh(y)\right) - \log(k) = 0.\]

Its first two derivatives are

\[\begin{split}G'(y) &= \nu\coth(\nu y)-\coth(y), \\ G''(y) &= -\nu^2\text{csch}^2(\nu y) + \text{csch}^2(y).\end{split}\]

The method applies a fixed number of Halley refinements,

\[y^{+} = y - \frac{2G(y)G'(y)}{2G'(y)^2-G(y)G''(y)}.\]

After recovering

\[c = \frac{y}{1-r},\]

one Newton correction is applied directly to the original equation

\[F(c) := \tanh(rc)-q\tanh(c)=0,\]

using

\[F'(c) = r\left(1-\tanh^2(rc)\right) - q\left(1-\tanh^2(c)\right).\]

This final correction reduces the residual in the original calibration equation after the logarithmically transformed Halley iterations.

\[c^{+} = c - \frac{F(c)}{F'(c)}.\]
Parameters
  • r (numpy.ndarray) – Reference rate.

  • q (numpy.ndarray) – Normalized distance between the reference value and the upper bound.

  • nit (int) – Number of Halley refinements preceding the single final Newton correction. Set to 8 by default.

Note

The method is defined over calibration targets satisfying

\[0<r<q<1.\]

For every fixed number of Halley refinements, including the final Newton correction, the implementation consists exclusively of smooth array operations wherever the transformed iterates remain strictly positive and the refinement denominators remain nonzero. It therefore remains compatible with automatic differentiation with respect to both \(r\) and \(q\) over any validated subdomain of \(0<r<q<1\).

The transformed initial approximation is derived from the local inverse expansion at \(q=r\). The chosen number of refinements should nevertheless be validated over the effective numerical domain of the model.

static _rate_tanh_bounds_root_initial(r: np.ndarray, q: np.ndarray)

Positive initial approximation of the transformed root.

Defining

\[y=(1-r)c,\]

the approximation follows from the local expansion

\[c^2 \sim \frac{3(q-r)} {r(1-r^2)} \ \text{as}\ q\to r^+.\]
Parameters
  • r (numpy.ndarray) – Reference rate.

  • q (numpy.ndarray) – Normalized distance between the reference value and the upper bound.

static _log_sinh(x: np.ndarray)

Stable logarithm of the hyperbolic sine over positive inputs.

\[\log(\sinh(x)) = x + \log\left(1-\exp(-2x)\right) - \log(2), \ x>0.\]
Parameters

x (numpy.ndarray) – Strictly positive argument.

static _coth(x: np.ndarray)

Hyperbolic cotangent over positive inputs.

\[\coth(x) = \frac{1+\exp(-2x)}{1-\exp(-2x)}, \ x>0.\]
Parameters

x (numpy.ndarray) – Strictly positive argument.

static _csch2(x: np.ndarray)

Squared hyperbolic cosecant over positive inputs.

\[\text{csch}^2(x) = \frac{4\exp(-2x)}{\left(1-\exp(-2x)\right)^2}, \ x>0.\]
Parameters

x (numpy.ndarray) – Strictly positive argument.

class iamax.library.PowCurvedNetPrice(sigma: float, urate_bias: float = 0.0)
Inheritance diagram of iamax.library.PowCurvedNetPrice

Net-price curve abstraction, whose name stems from the generalization of the well established negative relationship between wages and local unemployment, a.k.a. the wage curve.

Parameters
  • sigma (float) – Elasticity of price with respect to the biased reference-to-running unemployment-rate ratio.

  • urate_bias (float) – Parameter that allows unemployment reference- and running-rates to be shifted to the left or right. Set to .0 by default.

Attributes

Note

Such elasticity is assumed to measure the sensitivity of prices to changes in market conditions relative to a specific baseline, rather than simply the level of unemployment.

compute_calibrand_0(self, *, output_price: float, unemployment_rate: float)

Curve parametrization, performed such that

(265)\[\begin{split}\up^{*}_j &= \up_{j,t=0} \\ \tau_{\neg\hq, j}^{*} &= \tau_{\neg\hq, j,t=0}\end{split}\]

where \(\up_{j,t=0}\) is the (producer net) price at \(t=0\) and \(\tau_{\neg\hq, j,t=0}\) the unemployment rate at \(t=0\).

Parameters
  • output_price (float) – \(\up_j\), i.e. the \(j\)th \(1 \times 1\) output_prices’s element.

  • unemployment_rate (float) – \(\tau_{\neg\hq, j}\), i.e. the \(j\)th \(1 \times 1\) unemployment_rates’s element.

Example
>>> cnp = PowCurvedNetPrice(sigma=None)
>>> cnp.compute_calibrand_0(
...     output_price=1e2, unemployment_rate=.1
... )
>>> cnp.price_ref
100.0
>>> cnp.price_ref_star
100.0
>>> cnp.urate_ref
0.1
>>> cnp.urate_bias
0.0
compute_calibrand_0_star(self, *, deflators_fisher_drates: np.ndarray)

Reference-output-price recomputation from its time-zero value and the second selected element of deflators_fisher_drates, performed as

(266)\[\up_j^{*} \leftarrow \up_{j,t=0} \left( 1+\mfp^{\text{drate}}_{\text{F},2} \right)\]
Parameters

deflators_fisher_drates (numpy.ndarray) – \((\bmfp^{\text{drate}}_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times 2\) slice of deflators_fisher_drates, whose second component is of interest.

output_price_computer(self, *, unemployment_rate: float, deflators_fisher: np.ndarray, output_price_drate: np.ndarray = 0.0, output_price_drate2: np.ndarray = 0.0)

Producer-net-price computation from the updated reference price, the current Fisher deflator, the two exogenous output-price differentials and the biased reference-to-running unemployment-rate ratio, denoted as

(267)\[\begin{split}\up_j &= \up^{*}_j ( 1 + \up^{\text{drate}}_j + \up^{\text{drate}^{ii}}_j ) \mfp_{\text{F}, j} \left( \frac{\tau_{\neg\hq, j}^{*} + b}{\tau_{\neg\hq, j} + b} \right)^{\sigma_j} \\ &= \mfr\left( s=\up^{*}_j ( 1 + \up^{\text{drate}}_j + \up^{\text{drate}^{ii}}_j ) \mfp_{\text{F}, j}, c=\sigma_j, f=\frac{\tau_{\neg\hq, j}^{*} + b}{\tau_{\neg\hq, j} + b} \right)\end{split}\]

where the star superscript, i.e. \(.^{*}\), and the \(j\) subscript, i.e. \(._{j}\), respectively stand for the idea of “reference” and that of being \(j\)th, \(\up\) is the (producer net) price, \(\tau_{\neg\hq}\) is the rate of unemployment, \(\sigma\) is the elasticity of \(\up\) with respect to the biased reference-to-running unemployment-rate ratio, \(b\) is the unemployment-rate bias parameter, \(\mfp_{\text{F}}\) is the (chained) Fisher deflator and \(\mfr(.)\) is the function detailed in Eq.257.

Reference:

This formulation is taken (and particularized) from KLEM’s equation 12b, the consumer price index put aside.

Parameters
  • unemployment_rate (float) – \(\tau_{\neg\hq, j}\), i.e. the \(j\)th \(1 \times 1\) unemployment_rates’s element.

  • deflators_fisher (numpy.ndarray) – \((\bmfp_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times 2\) slice of deflators_fisher, whose first component is of interest.

  • output_price_drate (float) – \(\up^{\text{drate}}_j\), i.e. the \(j\)th \(1 \times 1\) producer_net_prices_drates’s element. Set to .0 by default.

  • output_price_drate2 (float) – \(\up^{\text{drate}^{ii}}_j\), i.e. the \(j\)th \(1 \times 1\) producer_net_prices_drates2’s element. Set to .0 by default.

Important

This method relies on the structure of its argument, as outlined in the description of argument deflators_fisher.

Example
>>> cnp = PowCurvedNetPrice(sigma=.2)
>>> cnp.compute_calibrand_0(
...     output_price=100, unemployment_rate=.1
... )
>>> cnp.output_price_computer(
...     unemployment_rate = .1490099501,
...     deflators_fisher  = np.array([[1.07220603770351, np.nan]])
... )
array([[99.]])
>>> cnp.output_price_computer(
...     unemployment_rate = .0489899499000005,
...     deflators_fisher  = np.array([[0.87567464071516, np.nan]])
... )
array([[101.]])

Let’s vectorize the above example.

>>> vcnp = PowCurvedNetPrice(
...     sigma=np.array([
...         [[ .2]], [[ .1]], [[-.2]]
...     ])
... )
>>> vcnp.compute_calibrand_0(
...     output_price=np.array([
...         [[100.]], [[200.]], [[50.]]
...     ]),
...     unemployment_rate=np.array([
...         [[.1]], [[.2]], [[.3]]
...     ]),
... )
>>> vcnp.output_price_computer(
...     unemployment_rate=np.array([
...         [[.2]], [[.1]], [[.6]]
...     ]),
...     deflators_fisher=np.array([
...         [[1., np.nan]],
...         [[1., np.nan]],
...         [[1., np.nan]],
...     ]),
... )
array([[[ 87.06]],
       [[214.35]],
       [[ 57.43]]])
class iamax.library.TanhCurvedNetPrice(sigma: float)
Inheritance diagram of iamax.library.TanhCurvedNetPrice

Yet another net-price curve abstraction.

Parameters

sigma (float) – Non-positive local price elasticity with respect to the underlying rate, at the reference rate.

Attributes
  • a (numpy.ndarray) – Calibrated unscaled magnitude value at a rate of \(0\).

  • b (numpy.ndarray) – Calibrated unscaled sigmoid amplitude parameter.

  • c (numpy.ndarray) – Calibrated magnitude sigmoid parameter.

  • sigma (numpy.ndarray) – Non-positive local price elasticity with respect to the underlying rate, at the reference rate.

  • reference_real_price (numpy.ndarray) – Reference output price divided by the corresponding reference Fisher price index.

Note

The calibration is defined for a non-positive local elasticity and a strictly positive reference rate.

Warning

A null reference unemployment rate \(\tau_{\neg\hq, j}^{*}\) is incompatible with this specification. Indeed, the local elasticity used for calibration,

\[\frac{\tau_{\neg\hq, j}^{*}}{ \mfr(\tau_{\neg\hq, j}^{*},a,b,c,1) } \frac{ \partial \mfr(\tau_{\neg\hq, j}^{*},a,b,c,1) }{ \partial \tau_{\neg\hq, j}^{*} },\]

is necessarily equal to \(0\) at \(\tau_{\neg\hq, j}^{*}=0\) whenever the curve has a finite derivative there. A strictly negative target elasticity therefore cannot be matched. A null target elasticity does not solve the identification problem either: at a null reference rate, the elasticity condition provides no information with which to identify the sigmoid parameter \(c\).

Consequently, this specification should not be selected when the reference unemployment rate is null; a specification identified from another local or benchmark condition should be used instead.

Attention

Elasticities are assumed to be non-positive. For

\[0 < \sigma < W_0\left(\exp(-1)\right),\]

two distinct real calibrations exist, respectively associated with the principal and lower real branches of the Lambert function. At the upper bound the two solutions merge, while no real calibration satisfying \(b=a\) exists above it. Restricting \(\sigma\) to non-positive values consequently ensures both a nonnegative Lambert-function argument and a unique real calibration.

compute_calibrand_0(self, *, output_price: float, unemployment_rate: float, deflators_fisher: np.ndarray)

Curve parametrization, performed such that

(268)\[\begin{split}\up^{*}_j &= \up_{j,t=0} \\ \tau_{\neg\hq, j}^{*} &= \tau_{\neg\hq, j,t=0} \\ \mfp_{\text{F}, j}^{*} &= \mfp_{\text{F}, j,t=0} \\ b_j &= a_j \\ \mfr\left( \tau_{\neg\hq, j}^{*}, a_j, b_j, c_j, 1 \right) &= 1 \\ \left. \frac{\tau_{\neg\hq, j}}{ \mfr\left( \tau_{\neg\hq, j}, a_j, b_j, c_j, 1 \right) } \frac{ \partial \mfr\left( \tau_{\neg\hq, j}, a_j, b_j, c_j, 1 \right) }{ \partial \tau_{\neg\hq, j} } \right|_{ \tau_{\neg\hq, j} =\tau_{\neg\hq, j}^{*} } &= \sigma_j\end{split}\]

where the star superscript, i.e. \(.^{*}\), and the \(j\) subscript, i.e. \(._j\), respectively stand for the ideas of being a reference value and of being \(j\)th, \(t=0\) denotes the reference period, \(\up_j\) is the (producer net) output price, \(\tau_{\neg\hq, j}\) is the unemployment rate, \(\mfp_{\text{F}, j}\) is the relevant (chained) Fisher price index, \(a_j\) is the unscaled curve value at a rate of \(0\), \(b_j\) is its unscaled sigmoid amplitude parameter, \(c_j\) is its sigmoid parameter, \(\mfr(.)\) is the function defined in Eq.258, and \(\sigma_j\) is the local output-price elasticity with respect to the unemployment rate, evaluated at the reference unemployment rate.

Note

The restriction \(b_j=a_j\) identifies the three curve parameters jointly with the reference-value normalization and the local-elasticity condition. The normalization of \(\mfr(.)\) to \(1\) at the reference rate ensures that the unscaled curve does not alter the reference output price.

Parameters
  • output_price (float) – \(\up_j\), i.e. the \(j\)th \(1 \times 1\) output_prices’s element.

  • unemployment_rate (float) – \(\tau_{\neg\hq, j}\), i.e. the \(j\)th \(1 \times 1\) unemployment_rates’s element.

  • deflators_fisher (numpy.ndarray) – \((\bmfp_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of deflators_fisher, whose first component only is of interest.

Attention

In the full-width representation, \(|\mcI_j|=2\). A shorter trailing axis remains admissible whenever it contains the first component used by the current method.

Important

This method relies on the structure of its argument, as outlined in the description of argument deflators_fisher.

Example
>>> cnp = TanhCurvedNetPrice(sigma=-.55)
>>> cnp.compute_calibrand_0(
...     output_price=943.5509, unemployment_rate=.2,
...     deflators_fisher=np.array([[1.13748, np.nan]])
... )
>>> cnp._reference_price.item()
943.5509
>>> cnp._reference_rate.item()
0.2
>>> cnp._reference_index.item()
1.13748
>>> cnp.reference_real_price.item()
829.5098814924218
>>> cnp.a.item()
1.6102140203583628
>>> cnp.b.item()
1.6102140203583628
>>> cnp.c.item()
1.9942499710804265
output_price_computer(self, *, unemployment_rate: float, deflators_fisher: np.ndarray, deflators_fisher_drates: np.ndarray|float = 0.0)

Producer-net-price computation from the reference real price, the current Fisher deflator and its exogenous differential, and the calibrated unemployment-rate tanh curve, denoted as

(269)\[\begin{split}\up_j &= \up^{*}_j \frac{ \mfp_{\text{F}, j} }{ \mfp_{\text{F}, j}^{*} }\left( 1 + \mfp^{\text{drate}}_{\text{F}, j} \right)\left( a_j + b_j \tanh(-c_j \tau_{\neg\hq, j}) \right) \\ &= \mfr\left( r=\tau_{\neg\hq, j}, a=a_j, b=b_j, c=c_j, s=\up^{*}_j \frac{ \mfp_{\text{F}, j} }{ \mfp_{\text{F}, j}^{*} }\left( 1 + \mfp^{\text{drate}}_{\text{F}, j} \right) \right)\end{split}\]

where the star superscript, i.e. \(.^{*}\), and the \(j\) subscript, i.e. \(._j\), respectively stand for the ideas of being a reference value and of being \(j\)th, \(\up_j\) is the (producer net) output price, \(\tau_{\neg\hq,j}\) is the unemployment rate, \(\mfp_{\text{F},j}\) is the relevant (chained) Fisher price index, \(\mfp^{\text{drate}}_{\text{F},j}\) is its exogenous differential, \(a_j\), \(b_j\) and \(c_j\) are the calibrated tanh-curve parameters, and \(\mfr(.)\) is the function defined in Eq.258.

Parameters
  • unemployment_rate (float) – \(\tau_{\neg\hq, j}\), i.e. the \(j\)th \(1 \times 1\) unemployment_rates’s element.

  • deflators_fisher (numpy.ndarray) – \((\bmfp_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of deflators_fisher, whose first component is of interest.

  • deflators_fisher_drates (numpy.ndarray or float) – \((\bmfp^{\text{drate}}_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of deflators_fisher_drates, whose first component is of interest. Set to 0 by default.

Attention

In the full-width representation, \(|\mcI_j|=2\). A shorter trailing axis remains admissible whenever it contains the first component used by the current method.

Example

A rate-decreasing normalized example first.

>>> invariant = {
...     'deflators_fisher': np.array([[1., np.nan]])
... }
>>> cnp = TanhCurvedNetPrice(sigma=-.55)
>>> cnp.compute_calibrand_0(
...     unemployment_rate=.2, output_price=1., **invariant
... )
>>> cnp.output_price_computer(
...     unemployment_rate=.5, **invariant
... )
array([[0.39]])
>>> cnp.output_price_computer(
...     unemployment_rate=.2, **invariant
... )
array([[1.]])
>>> cnp.output_price_computer(
...     unemployment_rate=.0, **invariant
... )
array([[1.61]])

Yet another (scaled) example.

>>> cnp = TanhCurvedNetPrice(sigma=-.55)
>>> p0 = 943.5508481986847
>>> d0 = np.array([[1.1374749692500363, np.nan]])
>>> cnp.compute_calibrand_0(
...     output_price=p0,
...     unemployment_rate=.2,
...     deflators_fisher=d0,
... )
>>> r = .05
>>> d = np.array([[1.1280702850130508, np.nan]])
>>> cnp.output_price_computer(
...     unemployment_rate=r, deflators_fisher=d,
...     deflators_fisher_drates=np.zeros((1, 2))
... )
array([[1357.01]])

Let’s vectorize the above example.

>>> cnp = TanhCurvedNetPrice(
...     sigma=np.array([
...         [[-.55]], [[-.55]], [[-.1]]
...     ])
... )
>>> cnp.compute_calibrand_0(
...     deflators_fisher=np.stack((d0, d0, d0)),
...     unemployment_rate=np.array([
...         [[.2]], [[.2]], [[.1]]
...     ]),
...     output_price=np.array([
...         [[p0]], [[.1*p0]], [[p0]]
...     ])
... )
>>> cnp.output_price_computer(
...     deflators_fisher=np.stack((d, d, d)),
...     unemployment_rate=np.array([
...         [[r]], [[r]], [[r]]
...     ])
... )
array([[[1357.01]],
       [[ 135.7 ]],
       [[ 982.7 ]]])
class iamax.library.NetPriceTanhCurvedMultiplier(*, min: float, max: float)
Inheritance diagram of iamax.library.NetPriceTanhCurvedMultiplier

Net-price’s multiplier abstraction.

Parameters
  • min (float) – Multiplier value at an idle rate of \(1\).

  • max (float) – Multiplier value at an idle rate of \(0\).

Attributes
  • min_ (float or numpy.ndarray) – Multiplier value at an idle rate of \(1\).

  • max_ (float or numpy.ndarray) – Multiplier value at an idle rate of \(0\).

  • a (numpy.ndarray) – Calibrated unscaled multiplier value at an idle rate of \(0\), equal to max_.

  • b (numpy.ndarray) – Calibrated unscaled sigmoid amplitude parameter, determined jointly by min_, max_ and the reference idle rate.

  • c (numpy.ndarray) – Calibrated strictly positive sigmoid parameter, determined such that the multiplier equals \(1\) at the reference idle rate while matching max_ at an idle rate of \(0\) and min_ at an idle rate of \(1\).

Note

To learn more about the underlying narrative, refer to Bibas et al.’s (2016) IMAClim-R model documentation (version 1.1).

compute_calibrand_0(self, *, idle_rates_transpose: np.ndarray)

Multiplier parametrization, performed such that

(270)\[\begin{split}\mfr(1, a, b, c, 1) &= m_{\min} \\ \mfr\left(\btau_{\neg q,2}^{*}, a, b, c, 1\right) &= 1 \\ \mfr(0, a, b, c, 1) &= m_{\max}\end{split}\]

where \(\btau_{\neg q,2}^{*}\) is the second component of the reference idle-rate slice, \(m_{\min}\) and \(m_{\max}\) respectively denote the multiplier values at idle rates of \(1\) and \(0\), and \(\mfr(.)\) is as defined in Eq.258.

Parameters

idle_rates_transpose (numpy.ndarray) – \((\btau^{T}_{\neg q, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times 2\) idle_rates’s slice, whose second component only is of interest.

Important

This method relies on the structure of its argument, as outlined in the description of argument idle_rates_transpose.

output_price_computer(self, *, input_prices: np.ndarray, idle_rates_transpose: np.ndarray)

Utilization-adjusted net-price computation, obtained by multiplying the selected input price by the calibrated idle-rate multiplier.

The resulting \(1 \times 1\) magnitude is denoted as

(271)\[\begin{split}\up_j &= \tbp_{1,j}(a_j + b_j \tanh(-c_j \btau_{\neg q,2})) \\ &= \mfr(r=\btau_{\neg q,2}, a=a_j, b=b_j, c=c_j, s=\tbp_{1,j})\end{split}\]

where \(\up_j\) is the utilization-adjusted net price, \(\tbp_{1,j}\) is the first selected input price, \(\btau_{\neg q,2}\) is the second selected idle-rate component, \(a_j\), \(b_j\) and \(c_j\) are the calibrated multiplier parameters, and \(\mfr(.)\) is the function defined in Eq.258. The scale argument \(s\) is not calibrated: it is set equal to the selected input price \(\tbp_{1,j}\).

Parameters
  • input_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of input_prices, whose first component only is of interest.

  • idle_rates_transpose (numpy.ndarray) – \((\btau^{T}_{\neg q, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of idle_rates, whose second component only is of interest.

Important

As outlined in the parameters’ descriptions, the current method relies on the structure of its arguments.

Attention

In the full-width representation, \(|\mcI_j|=2\). The input_prices trailing axis may be shorter because only its first component is consumed, whereas idle_rates_transpose must contain at least its first two components.

Example
>>> npcm = NetPriceTanhCurvedMultiplier(min=.95, max=1.05)
>>> npcm.compute_calibrand_0(
...     idle_rates_transpose=np.array([[np.nan, .2]])
... )
>>> npcm.a.item()
1.05
>>> npcm.b.item()
0.10087575919139345
>>> npcm.c.item()
2.7176753624893477
>>> npcm.output_price_computer(**(ut1 := dict(
...     input_prices = np.array([[1., np.nan]]),
...     idle_rates_transpose = np.array([[np.nan, .0]]),
... )))
array([[1.05]])
>>> npcm.output_price_computer(**(ut2 := dict(
...     input_prices = np.array([[1., np.nan]]),
...     idle_rates_transpose = np.array([[np.nan, .5]]),
... )))
array([[0.96]])
>>> npcm.output_price_computer(**(ut3 := dict(
...     input_prices = np.array([[1., np.nan]]),
...     idle_rates_transpose = np.array([[np.nan, 1.]]),
... )))
array([[0.95]])
>>> npcm.output_price_computer(**(ut4 := dict(
...     input_prices = np.array([[1., np.nan]]),
...     idle_rates_transpose = np.array([[np.nan, .2]]),
... ))).item()
1.0

Another (less conservative) example.

>>> npcm = NetPriceTanhCurvedMultiplier(min=.8, max=30)
>>> npcm.compute_calibrand_0(
...     idle_rates_transpose = np.array([[np.nan, .2]])
... )
>>> npcm.output_price_computer(**ut1).item()
30.0
>>> npcm.output_price_computer(**ut2).item()
0.8000404277688098
>>> npcm.output_price_computer(**ut3).item()
0.8000000000000007
>>> npcm.output_price_computer(**ut4).item()
1.0

Let’s vectorize the above example.

>>> vnpcm = NetPriceTanhCurvedMultiplier(
...     min=np.array([[[.95]], [[.8]], [[.6]]]),
...     max=np.array([[[1.05]], [[1.2]], [[2.]]]),
... )
>>> idle_rates_ref = np.array([
...     [[np.nan, .2]],
...     [[np.nan, .3]],
...     [[np.nan, .4]],
... ])
>>> vnpcm.compute_calibrand_0(
...     idle_rates_transpose = idle_rates_ref
... )
>>> vnpcm.a
array([[[1.05]],
       [[1.2 ]],
       [[2.  ]]])
>>> vnpcm.b
array([[[0.1 ]],
       [[0.43]],
       [[1.44]]])
>>> vnpcm.c
array([[[2.72]],
       [[1.69]],
       [[2.14]]])
>>> vnpcm.output_price_computer(
...     idle_rates_transpose = idle_rates_ref,
...     input_prices=np.array([
...         [[1., np.nan]],
...         [[2., np.nan]],
...         [[3., np.nan]],
...     ]),
... )
array([[[1.]],
       [[2.]],
       [[3.]]])
class iamax.library.TanhCMultipliedTanhCNetPrice(*, sigma: float, m_min: float, m_max: float)

Multiplied net-price curve abstraction.

Parameters
  • sigma (float) – Non-positive local elasticity of the inner, unmultiplied net price with respect to the unemployment rate, at the reference rate.

  • m_min (float) – Multiplier value at an idle rate of \(1\).

  • m_max (float) – Multiplier value at an idle rate of \(0\).

Attributes

Note

This class internally makes use of TanhCurvedNetPrice and NetPriceTanhCurvedMultiplier through composition.

Example
>>> o = TanhCMultipliedTanhCNetPrice(
...     **dict.fromkeys(('sigma', 'm_min', 'm_max'), np.nan)
... )
>>> isinstance(o.pricecurve, TanhCurvedNetPrice)
True
>>> isinstance(o.multiplier, NetPriceTanhCurvedMultiplier)
True
>>> invariants = dict(
...     output_price=1., unemployment_rate=.2,
...     deflators_fisher=np.array([[1., np.nan]]),
...     idle_rates_transpose=np.array([[np.nan, .2]]),
... )
>>> o1 = TanhCMultipliedTanhCNetPrice(
...     sigma=-.55, m_min=.95, m_max=1.05,
... )
>>> o1.compute_calibrand_0(**invariants)
>>> o1.multiplier.a.item()
1.05
>>> o1.multiplier.b.item()
0.10087575919139345
>>> o1.multiplier.c.item()
2.7176753624893477
>>> o1.pricecurve.a.item()
1.6102140203583628
>>> o1.pricecurve.b.item()
1.6102140203583628
>>> o1.pricecurve.c.item()
1.9942499710804265
>>> o2 = TanhCMultipliedTanhCNetPrice(
...     sigma=-.55, m_min=.8, m_max=30.,
... )
>>> o2.compute_calibrand_0(**invariants)
>>> o2.multiplier.a.item()
30.0
>>> o2.multiplier.b.item()
29.200000000027988
>>> o2.multiplier.c.item()
14.18330816758009
>>> o2.pricecurve.a.item()
1.6102140203583628
>>> o2.pricecurve.b.item()
1.6102140203583628
>>> o2.pricecurve.c.item()
1.9942499710804265
compute_calibrand_0(self, *, output_price: float, unemployment_rate: float, deflators_fisher: np.ndarray, idle_rates_transpose: np.ndarray)

Composite parametrization, performed as explained in Eq.268 and Eq.270.

Note

This method internally resorts to compute_calibrand_0() and compute_calibrand_0().

Parameters
  • output_price (float) – \(\up_j\), i.e. the \(j\)th \(1 \times 1\) output_prices’s element.

  • unemployment_rate (float) – \(\tau_{\neg \hq, j}\), i.e. the \(j\)th \(1 \times 1\) unemployment_rates’s element.

  • deflators_fisher (numpy.ndarray) – \((\bmfp_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of deflators_fisher, whose first component only is of interest.

  • idle_rates_transpose (numpy.ndarray) – \((\btau^{T}_{\neg q, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of idle_rates, whose second component only is of interest.

Attention

In the full-width model representation, \(|\mcI_j|=3\). A shorter trailing axis remains admissible provided that deflators_fisher contains its first component and idle_rates_transpose its first two components.

Important

This method relies on the structure of its argument, as outlined in the description of arguments deflators_fisher and idle_rates_transpose.

Example
>>> o = TanhCMultipliedTanhCNetPrice(
...     sigma=-.55, m_min=.95, m_max=1.05,
... )
>>> o.compute_calibrand_0(
...     output_price=943.55,
...     unemployment_rate=.2,
...     deflators_fisher=np.array([
...         [1.14, np.nan, np.nan]
...     ]),
...     idle_rates_transpose=np.array([
...         [np.nan, .2, np.nan]
...     ])
... )
>>> (cnp := o.pricecurve)._reference_price.item()
943.55
>>> cnp._reference_rate.item()
0.2
>>> cnp._reference_index.item()
1.14
>>> cnp.reference_real_price.item()
827.6754385964913
output_price_computer(self, *, unemployment_rate: float, idle_rates_transpose: np.ndarray, deflators_fisher: np.ndarray, deflators_fisher_drates: np.ndarray)

Composite producer-net-price computation, obtained by applying the calibrated unemployment-rate price curve and the calibrated utilization-based multiplier, denoted as

(272)\[\begin{split}\up_j &= \up^{*}_j \frac{\mfp_{\text{F},j}}{\mfp_{\text{F},j}^{*}} \left(1+\mfp^{\text{drate}}_{\text{F},j}\right) \left( a^{\mathrm p}_j + b^{\mathrm p}_j \tanh\left( -c^{\mathrm p}_j\tau_{\neg\hq,j} \right) \right) \left( a^{\mathrm m}_j + b^{\mathrm m}_j \tanh\left(-c^{\mathrm m}_j\btau_{\neg q,2}\right) \right) \\ &= \mfr\left( r=\btau_{\neg q,2}, a=a^{\mathrm m}_j, b=b^{\mathrm m}_j, c=c^{\mathrm m}_j, s=\mfr\left( r=\tau_{\neg\hq,j}, a=a^{\mathrm p}_j, b=b^{\mathrm p}_j, c=c^{\mathrm p}_j, s=\up^{*}_j \frac{\mfp_{\text{F},j}}{\mfp_{\text{F},j}^{*}} \left(1+\mfp^{\text{drate}}_{\text{F},j}\right) \right) \right)\end{split}\]

where superscripts \(\mathrm p\) and \(\mathrm m\) respectively identify the parameters of the price curve and of the utilization-based multiplier, \(\tau_{\neg\hq,j}\) is the unemployment rate, \(\btau_{\neg q,2}\) is the second selected idle-rate component, \(\up_j^{*}\) is the reference output price, \(\mfp_{\text{F},j}\) and \(\mfp_{\text{F},j}^{*}\) are the current and reference Fisher price indices, and \(\mfp^{\text{drate}}_{\text{F},j}\) is the current exogenous Fisher-deflator differential.

Parameters
  • unemployment_rate (float) – \(\tau_{\neg\hq,j}\), i.e. the \(j\)th \(1 \times 1\) unemployment_rates’s element.

  • idle_rates_transpose (numpy.ndarray) – \((\btau^{T}_{\neg q, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of idle_rates, whose second component only is of interest.

  • deflators_fisher (numpy.ndarray) – \((\bmfp_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of deflators_fisher, whose first component is of interest.

  • deflators_fisher_drates (numpy.ndarray) – \((\bmfp^{\text{drate}}_{\text{F}, k})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of deflators_fisher_drates, whose first component is of interest.

Attention

In the full-width model representation, \(|\mcI_j|=3\). Shorter trailing axes remain admissible whenever they contain every component used by the current method.

Important

This method relies on the structure of its arguments, as outlined in the descriptions of arguments idle_rates_transpose, deflators_fisher and deflators_fisher_drates.

Example
>>> tcmp = TanhCMultipliedTanhCNetPrice(
...     sigma=-.55,
...     m_min=.95,
...     m_max=1.05,
... )
>>> tcmp.compute_calibrand_0(
...     output_price         = 943.55085,
...     unemployment_rate    = .2,
...     deflators_fisher     = np.array([[1.13748]]),
...     idle_rates_transpose = np.array([[np.nan, .2]]),
... )
>>> tcmp.output_price_computer(
...     unemployment_rate       = .2,
...     deflators_fisher        = np.array([[1.13748]]),
...     deflators_fisher_drates = np.zeros((1, 1)),
...     idle_rates_transpose    = np.array([[np.nan, .2]]),
... ).item()
943.5508500000001
>>> tcmp.output_price_computer(
...     unemployment_rate       = .05,
...     idle_rates_transpose    = np.array([[np.nan, .5]]),
...     deflators_fisher        = np.array([[1.12807]]),
...     deflators_fisher_drates = np.zeros((1, 1))
... ).item()
1304.92286535119

Let’s vectorize the above example.

>>> vtcmp = TanhCMultipliedTanhCNetPrice(
...     sigma=np.array([[[-.55]], [[-.1]], [[-.3]]]),
...     m_min=np.array([[[.95]], [[.8]], [[.6]]]),
...     m_max=np.array([[[1.05]], [[1.2]], [[2.]]]),
... )
>>> unemployment_rates_ref = np.array([[[.2]], [[.1]], [[.15]]])
>>> deflators_fisher_ref = np.array([
...     [[1., np.nan, np.nan]],
...     [[1., np.nan, np.nan]],
...     [[1., np.nan, np.nan]],
... ])
>>> idle_rates_ref = np.array([
...     [[np.nan, .2, np.nan]],
...     [[np.nan, .3, np.nan]],
...     [[np.nan, .4, np.nan]],
... ])
>>> output_prices_ref = np.array([[[100.]], [[200.]], [[50.]]])
>>> output_prices_ref
array([[[100.]],
       [[200.]],
       [[ 50.]]])
>>> vtcmp.compute_calibrand_0(
...     output_price=output_prices_ref,
...     unemployment_rate=unemployment_rates_ref,
...     deflators_fisher=deflators_fisher_ref,
...     idle_rates_transpose=idle_rates_ref,
... )

The three reference output prices are independently recovered.

>>> vtcmp.output_price_computer(
...     unemployment_rate=unemployment_rates_ref,
...     idle_rates_transpose=idle_rates_ref,
...     deflators_fisher=deflators_fisher_ref,
...     deflators_fisher_drates=np.zeros((3, 1, 3)),
... )
array([[[100.]],
       [[200.]],
       [[ 50.]]])

Running rates and price indices may then differ independently along the first axis.

>>> vtcmp.output_price_computer(
...     unemployment_rate=np.array([
...         [[.05]], [[.2]], [[.1]]
...     ]),
...     idle_rates_transpose=np.array([
...         [[np.nan, .5, np.nan]],
...         [[np.nan, .1, np.nan]],
...         [[np.nan, .8, np.nan]],
...     ]),
...     deflators_fisher=np.array([
...         [[1.1, np.nan, np.nan]],
...         [[.9, np.nan, np.nan]],
...         [[1.2, np.nan, np.nan]],
...     ]),
...     deflators_fisher_drates=np.zeros((3, 1, 3)),
... )
array([[[153.4 ]],
       [[183.03]],
       [[ 43.05]]])
class iamax.library.LaborMarket
Inheritance diagram of iamax.library.LaborMarket

s/e.

class iamax.library.UniformProductivityPremia

Uniformly solved productivity premia.

static output_volume_rooter(ioq_productivities_drates2: np.ndarray)

s/e., \(1 \times 1\) zero(s).

Parameters

ioq_productivities_drates2 (numpy.ndarray) – \((\balpha^{-1,\ \text{drate}_{kk,j}})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI_j| \times |\mcI_j|\) ioq_productivities_drates2’s (diagonal) square slice.

Example
>>> a = np.array([0, 1., .2]) * np.identity(3)
>>> UniformProductivityPremia.output_volume_rooter(
...     ioq_productivities_drates2=a
... )
array([[0.71, 0.71, 0.71]])

Let’s vectorize the above example.

>>> (v := np.stack((a, np.identity(3))))
array([[[0. , 0. , 0. ],
        [0. , 1. , 0. ],
        [0. , 0. , 0.2]],
       [[1. , 0. , 0. ],
        [0. , 1. , 0. ],
        [0. , 0. , 1. ]]])
>>> UniformProductivityPremia.output_volume_rooter(
...     ioq_productivities_drates2=v
... )
array([[[0.71, 0.71, 0.71]],
       [[0.  , 0.  , 0.  ]]])
class iamax.library.UniformGhoshAllocationPremia

Uniformly solved GhoshAllocation premia.

static output_volume_rooter(ioq_shares_drates2: np.ndarray)

s/e., \(1 \times 1\) zero(s).

Parameters

ioq_shares_drates2 (numpy.ndarray) – \((\bbeta^{\text{drate}^{ii}}_{kk,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcI_j| \times |\mcI_j|\) ioq_shares_drates2’s slice.

Example
>>> a = np.array([0, 1., .2]) * np.identity(3)
>>> UniformGhoshAllocationPremia.output_volume_rooter(
...     ioq_shares_drates2=a
... )
array([[0.71, 0.71, 0.71]])

Let’s vectorize the above example.

>>> (v := np.stack((a, np.identity(3))))
array([[[0. , 0. , 0. ],
        [0. , 1. , 0. ],
        [0. , 0. , 0.2]],
       [[1. , 0. , 0. ],
        [0. , 1. , 0. ],
        [0. , 0. , 1. ]]])
>>> UniformGhoshAllocationPremia.output_volume_rooter(
...     ioq_shares_drates2=v
... )
array([[[0.71, 0.71, 0.71]],
       [[0.  , 0.  , 0.  ]]])
class iamax.library.UniformPrice(price_transmitting: bool = False)

Uniformly equalized prices.

Parameters

price_transmitting (bool) – Whether the price of the aggregate is to be granularly communicated. Set to False by default.

compute_calibrand_0(self, *, output_volumes: np.ndarray, t: int = 0)

Ancillary method whose only goal is to compute the mask that specifies which elements are to be included when producer_net_prices_rooter() is called.

Parameters
Example
>>> a = np.array([
...     [[0, 1., .2, 1.]],
...     [[0, 1., 1., 2.]],
...     [[0, 3., .4, 8.]],
...     [[0, 1., 1., 1.]],
... ])
>>> pt = False
>>> uu = UniformPrice(pt).compute_calibrand_0(
...     output_volumes=a
... )
>>> uu._ix0, uu._trnsm_mask
(1, True)
>>> pt = True
>>> uu = UniformPrice(pt).compute_calibrand_0(
...     output_volumes=a
... )
>>> uu._ix0, uu._trnsm_mask
(0, True)

Let’s vectorize the above example, keeping in mind that the temporal axis must remain first (hence the use of numpy.ndarray.swapaxes below).

>>> v  = np.stack((a + 1, a)).swapaxes(1, 0)
>>> pt = np.array([[[True]], [[False]]])
>>> uu = UniformPrice(pt).compute_calibrand_0(
...     output_volumes=v
... )
>>> uu._ix0
0
>>> uu._trnsm_mask
array([[[[ True,  True,  True,  True]],
        [[False,  True,  True,  True]]]])
producer_net_prices_rooter(self, *, t: int, producer_net_prices: np.ndarray)

s/e.

Parameters

Important

Since it resorts to t, the current method relies on the structure of its non-scalar arguments, as explained here.

Note

This method is vectorization friendly over the -3th axis, if any.

Example
>>> a = np.array([
...     [[0, 1., .2, 1.]],
...     [[0, 1., 1., 2.]],
...     [[0, 3., .4, 8.]],
...     [[0, 1., 1., 1.]],
... ])
>>> UniformPrice().producer_net_prices_rooter(
...     producer_net_prices=a, t=1
... )
array([[[0.23]]])
>>> UniformPrice().producer_net_prices_rooter(
...     producer_net_prices=a, t=2
... )
array([[[0.59]]])
>>> UniformPrice().producer_net_prices_rooter(
...     producer_net_prices=a, t=3
... )
array([[[0.]]])

Let’s vectorize the above example, keeping in mind that the temporal axis must remain first (hence the use of numpy.ndarray.swapaxes below).

>>> (v := np.stack((a + 1, a)).swapaxes(1, 0))
array([[[[1. , 2. , 1.2, 2. ]],
        [[0. , 1. , 0.2, 1. ]]],
       [[[1. , 2. , 2. , 3. ]],
        [[0. , 1. , 1. , 2. ]]],
       [[[1. , 4. , 1.4, 9. ]],
        [[0. , 3. , 0.4, 8. ]]],
       [[[1. , 2. , 2. , 2. ]],
        [[0. , 1. , 1. , 1. ]]]])
>>> UniformPrice().producer_net_prices_rooter(
...     producer_net_prices=v, t=2
... )
array([[[[0.46]],
        [[0.59]]]])
>>> UniformPrice().producer_net_prices_rooter(
...     producer_net_prices=v, t=3
... )
array([[[[0.]],
        [[0.]]]])
class iamax.library.UniformUnemployment

Uniformly unemployed capacities.

Note

We may think about labor when reading ‘unemployment’ but this class is absolutely agnostic regarding the very nature of the entities you assign them to.

static unemployment_rates_rooter(t: int, unemployment_rates: np.ndarray)

s/e.

See also

unemployment_rates.

Important

Although it resorts to t, the current method does not rely on the structure unemployment_rates, as explained here.

Note

This method is vectorization friendly over the -3th axis, if any.

Example
>>> a = np.array([
...     [[.2, .1, .3]],
...     [[.2, .2, .2]],
... ])
>>> UniformUnemployment.unemployment_rates_rooter(
...     t=0, unemployment_rates=a
... )
array([[[0.3]]])
>>> UniformUnemployment.unemployment_rates_rooter(
...     t=1, unemployment_rates=a
... )
array([[[9.66e-17]]])

Let’s vectorize the above example, keeping in mind that the temporal axis must remain first (hence the use of numpy.ndarray.swapaxes below).

>>> (v := np.stack((a, 2*a)).swapaxes(1, 0))
array([[[[0.2, 0.1, 0.3]],
        [[0.4, 0.2, 0.6]]],
       [[[0.2, 0.2, 0.2]],
        [[0.4, 0.4, 0.4]]]])
>>> UniformUnemployment.unemployment_rates_rooter(
...     t=1, unemployment_rates=v
... )
array([[[[9.66e-17]],
        [[9.77e-17]]]])
class iamax.library.ULaborMarket
Inheritance diagram of iamax.library.ULaborMarket

s/e.

class iamax.library.UMarket
Inheritance diagram of iamax.library.UMarket

s/e.

class iamax.library.UUMarket
Inheritance diagram of iamax.library.UUMarket

s/e.

class iamax.library.TermsOfTrade(sigma: float)

Example case-denominated Terms of Trade.

Parameters

sigma (float) – Elasticity to terms-of-trade of good exports.

compute_calibrand_0(self, *, arent_final: np.ndarray, are_only_final: np.ndarray, specific_producer_prices: np.ndarray, consumer_prices: np.ndarray, ioq_shares: np.ndarray)

Support the calibration of the constant scale factor implied in the terms-of-trade equation, denoted as

(273)\[A_X = \frac{q_{X(t=0)}}{q_{S(t=0)}} \left( \frac{p_{X(t=0)}}{p_{M(t=0)}} \right)^\sigma\]

where \(q_{X(t=0)}/q_{S(t=0)}\) is the share of exported quantity with respect to that of the Armington aggregate, \(p_{X(t=0)}/p_{M(t=0)}\) the price ratio of the former against its imported counterpart and \(\sigma\) the elasticity to terms-of-trade of good exports, set at instantiation.

Parameters
output_volume_rooter(self, specific_producer_prices: np.ndarray, consumer_prices: np.ndarray, ioq_shares: np.ndarray, consumed_volumes_drates: np.ndarray)

Terms-of-trade based constraint regarding exported quantities, as follows

(274)\[\frac{q_{X(t)}}{q_{S(t)}} = A_X \left( \frac{p_{M(t)}}{p_{X(t)}} \right)^\sigma \left(1 + \epsilon_{X(t)}\right)\]
Parameters
  • specific_producer_prices (numpy.ndarray) – \((\tbP^{T}_{k,j})_{k \in \mcI_j}\), i.e. a \(j\)-th \(1 \times |\mcI_j|\) specific_producer_prices’s slice.

  • consumer_prices (numpy.ndarray) – \((\tbP_{k,j})_{k \in \mcI_j}\), i.e. a \(j\)-th \(1 \times |\mcI_j|\) consumer_prices’s slice.

  • ioq_shares (numpy.ndarray) – \((\bbeta_{k,j})_{k \in \mcI_j}\), i.e. a \(j\)-th \(1 \times |\mcI_j|\) ioq_shares’s slice.

  • consumed_volumes_drates (numpy.ndarray) – \((\bQ^{T, \text{drate}}_{k,j})_{k \in \mcI_j}\), i.e. a \(j\)th \(1 \times |\mcI_j|\) slice of consumed_volumes_drates, one component of which echoes \(\epsilon_X\) above.

class iamax.library.PTermsOfTrade
Inheritance diagram of iamax.library.PTermsOfTrade

Partialized version of TermsOfTrade

Parameters

sigma (float) – Elasticity to terms-of-trade of good exports.

compute_calibrand_0(self, *, arent_final: np.ndarray, are_only_final: np.ndarray, specific_producer_prices: np.ndarray, consumer_prices: np.ndarray, consumed_volumes: np.ndarray)

Support the calibration of the constant scale factor implied in the terms-of-trade equation, denoted as

(275)\[A_X = q_{X(t=0)} \left( \frac{p_{X(t=0)}}{p_{M(t=0)}} \right)^\sigma\]

where \(q_{X(t=0)}\) is the consumed volume of exported quantity, \(p_{X(t=0)}/p_{M(t=0)}\) the price ratio of the latter against its imported counterpart and \(\sigma\) the elasticity to terms-of-trade of good exports, set at instantiation.

Parameters
output_volume_rooter(self, specific_producer_prices: np.ndarray, consumer_prices: np.ndarray, consumed_volumes: np.ndarray, consumed_volumes_drates: np.ndarray)

Terms-of-trade based constraint regarding exported quantities, as follows

(276)\[q_{X(t)} = A_X \left( \frac{p_{M(t)}}{p_{X(t)}} \right)^\sigma \left(1 + \epsilon_{X(t)}\right)\]
Parameters
class iamax.library.DynamicStock(delta: float, grpot: float = 0.0, dyear0: float = 1.0, rental_valued: bool = True)

Example case mobilized dynamic stock abstraction.

The class provides a common dynamic-stock representation whose computed available_volumes can alternatively consist of stock volumes or of the corresponding volumes lost through deterioration over the considered periods. In the latter case, the underlying stock is internally recovered from the formerly computed deterioration volume and preceding contributory flow before applying the exact same stock dynamics.

Quantities specific to initialization and its implicit prehistory are inferred once during calibration by compute_calibrand_0(). Since entity-own available_volumes_drates are not servable to this cross-sectional calibrator, initialization and its implicit prehistory remain untransformed. Subsequent driving rates are applied dynamically by available_volume_computer().

Parameters
  • delta (float) – Annual deterioration rate.

  • grpot (float) – Annual growth potential. To .0 by default.

  • dyear0 (float) – Yearly recurrence period of the flux under consideration at time t=0. To 1. by default.

  • rental_valued (bool) – Whether the computed available_volumes should be expressed as stock volumes intended for valuation through an implicit rental price rather than as volumes lost through deterioration over the considered period. Set to True by default.

Attributes
  • δ (float) – Annual deterioration rate.

  • g (float) – Annual growth potential.

  • ρ (float) – dyear0-specific effective perpetuity rate associated with the initial contributory flow, used to infer its corresponding initial stock through infinite capitalization.

  • γ_0 (float) – Survival factor over the initial flux recurrence period.

compute_calibrand_0(self, *, ordinal_volumes_transpose: np.ndarray)

Infers the untransformed initial stock and implicit prehistory subsequently mobilized by available_volume_computer().

Unlike available_volume_computer(), this calibrator does not invoke t. Its non-scalar argument therefore consists of a cross-section rather than of column-bound time series. Under the present dynamic-stock dependency structure, its first component is the initial contributory flow \(\mfq_{-1}\).

Let

(277)\[\rho = (1 + \delta + g)^{(y_0-y_{-1})} - 1\]

denote the effective perpetuity rate associated with the initial recurrence interval. The initial contributory flow yields

(278)\[\hq^{\text{stock}}_0 = \frac{\mfq_{-1}}{\rho},\]

while reversing the stock accumulation identity gives its predecessor:

(279)\[\hq^{\text{stock}}_{-1} = \frac{\hq^{\text{stock}}_0-\mfq_{-1}}{ (1-\delta)^{(y_0-y_{-1})} }.\]

The initial capacity can consequently be represented either as this initial stock or as the deterioration of its predecessor over the corresponding interval:

(280)\[\begin{split}\hq_0 = \begin{cases} \hq^{\text{stock}}_0, & \text{if is rental-valued}, \\ \hq^{\text{stock}}_{-1} \left[ 1-(1-\delta)^{(y_0-y_{-1})} \right], & \text{otherwise}. \end{cases}\end{split}\]

Quantities required one period further below the stored temporal bound are inferred by preserving the initial contribution relative to the stock existing at the beginning of its transition. For a non-null initial contributory flow, this corresponds to

(281)\[\frac{\widetilde{\mfq}_{-1}}{\hq^{\text{stock}}_{-2}} = \frac{\mfq_{-1}}{\hq^{\text{stock}}_{-1}},\]

where \(\widetilde{\mfq}_{-1}\) denotes the implicit preceding contribution. Combining this relationship with the initialization identities and analytically extending it to a potentially null \(\mfq_{-1}\) gives the equivalent linear forms

(282)\[\hq^{\text{stock}}_{-2} = \hq^{\text{stock}}_{-1}\frac{1-\rho}{\gamma_0}, \ \widetilde{\mfq}_{-1} = \rho\hq^{\text{stock}}_{-1}.\]

These forms avoid the removable \(0/0\) singularity that would otherwise arise for a null initial contributory flow and therefore preserve its first-order differentiability. They also imply that the calibrated stock prehistory follows the geometric relationship

(283)\[\frac{\hq^{\text{stock}}_{-2}}{\hq^{\text{stock}}_{-1}} = \frac{\hq^{\text{stock}}_{-1}}{\hq^{\text{stock}}_0} = \frac{1-\rho}{\gamma_0}.\]

The backcast therefore imposes no monotonic increase towards \(t=0\). In particular, whenever \((1-\rho)/\gamma_0>1\), predecessor stocks increase when moving backward in time, so that the corresponding forward trajectory is decreasing up to initialization:

(284)\[\hq^{\text{stock}}_{-2} > \hq^{\text{stock}}_{-1} > \hq^{\text{stock}}_0.\]

Conversely, \((1-\rho)/\gamma_0<1\) implies an increasing stock trajectory towards \(t=0\), while equality implies a locally stationary one. These alternatives are implications of the calibrated perpetuity and survival factors rather than additional restrictions imposed on the backcast.

The resulting initial capacity, the two implicit predecessor stocks and the contribution entering the transition from \(t=-2\) to \(t=-1\) are attached to the instance.

Parameters

ordinal_volumes_transpose (numpy.ndarray) – \((\bmfq^{T}_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of ordinal_volumes_transpose.

Note

This method is vectorization friendly over the -3th axis, if any.

available_volume_computer(self, *, available_volumes: np.ndarray, available_volumes_drates: np.ndarray, ordinal_volumes: np.ndarray, dyears: np.ndarray, t: int)

Computes a \(1 \times 1\) capacity, denoted as \(\hq_t\), whose representation depends on rental_valued’s truthiness:

(285)\[\begin{split}\hq_t = \begin{cases} \hq^{\text{stock}}_t, & \text{if is rental-valued}, \\ \hq^{\text{stock}}_{t-1} \left[1 - (1 - \delta)^{(y_t - y_{t-1})}\right], & \text{otherwise}. \end{cases}\end{split}\]

Hence \(\hq^{\text{stock}}_t\) denotes the underlying stock from which the returned \(\hq_t\) derives. When rental_valued=False, \(\hq_t\) instead represents the volume of the preceding stock lost through deterioration over the corresponding period. Switching rental_valued thus changes only the representation returned by the method, not the stock trajectory underlying it.

Initialization-specific quantities are inferred once beforehand by compute_calibrand_0(). Since the entity-own available_volumes_drates series is not available to that calibrator, initialization and its implicit prehistory remain untransformed. In particular,

(286)\[\hq^{\text{stock}}_0 = \frac{\mfq_{-1}}{\rho},\]

and

(287)\[\hq^{\text{stock}}_{-1} = \frac{\hq^{\text{stock}}_0 - \mfq_{-1}}{ (1 - \delta)^{(y_0-y_{-1})} }.\]

The capacity returned at initialization consequently follows

(288)\[\begin{split}\hq_0 = \begin{cases} \hq^{\text{stock}}_0, & \text{if is rental-valued}, \\ \hq^{\text{stock}}_{-1} \left[ 1 - (1 - \delta)^{(y_0 - y_{-1})} \right], & \text{otherwise}. \end{cases}\end{split}\]

For \(t > 0\), computing the next capacity first requires recovering the preceding underlying stock \(\hq^{\text{stock}}_{t-1}\) from formerly returned available volumes. When rental_valued=True, these available volumes already consist of the required stock levels:

(289)\[\hq^{\text{stock}}_{t-1} = \hq_{t-1}, \ \text{if is rental-valued}.\]

Otherwise, the formerly returned capacity \(\hq_{t-1}\) consists of the deterioration of \(\hq^{\text{stock}}_{t-2}\) over the preceding period:

(290)\[\hq_{t-1} = \hq^{\text{stock}}_{t-2} \left[1 - (1 - \delta)^{(y_{t-1} - y_{t-2})}\right], \ \text{if isn't rental-valued}.\]

The stock existing at the beginning of this deterioration period can therefore be recovered directly, without integrating the full formerly computed time series:

(291)\[\hq^{\text{stock}}_{t-2} = \frac{\hq_{t-1}}{ 1 - (1 - \delta)^{(y_{t-1} - y_{t-2})} }, \ \text{if isn't rental-valued}.\]

Once \(\hq^{\text{stock}}_{t-2}\) has been recovered, the common stock recurrence directly yields its successor:

(292)\[\begin{split}\hq^{\text{stock}}_{t-1} = \hq^{\text{stock}}_{t-2} (1 - \delta)^{(y_{t-1} - y_{t-2})} + \begin{cases} \mfq_{-1}, & t = 1, \\ \mfq_{t-2} (1 + \hq^{\text{drate}}_{t-1}), & t > 1. \end{cases}\end{split}\]

The lower-bound case thus uses the same untransformed initial contributory flow as the calibration itself. From subsequent observed transitions onward, the corresponding driving rates transform their contributory flows contemporaneously.

Once \(\hq^{\text{stock}}_{t-1}\) has either been directly retrieved or locally recovered from the preceding deterioration volume, the exact same stock dynamics apply under either representation:

(293)\[\hq^{\text{stock}}_t = \hq^{\text{stock}}_{t-1} (1 - \delta)^{(y_t - y_{t-1})} + \mfq_{t-1} (1 + \hq^{\text{drate}}_t), \ t>0.\]

At \(t=0\), the same identity instead uses the untransformed \(\mfq_{-1}\) as its contributory term. The initial \(\hq^{\text{drate}}_0\) therefore does not alter either the calibrated initialization or its implicit prehistory, while subsequent driving rates are applied dynamically from \(t>0\) onward.

The resulting \(\hq^{\text{stock}}_t\) is returned directly when rental_valued=True. Otherwise, the method returns the contemporaneous deterioration of \(\hq^{\text{stock}}_{t-1}\), as prescribed by Eq.285. The two settings thus expose the exact same underlying stock trajectory, respectively as stock levels and as the volumes lost through deterioration over successive considered intervals.

Parameters
  • available_volumes (numpy.ndarray) – \((\hbq_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcL| \times |\mcI_j|\) available_volumes’s slice.

  • available_volumes_drates (numpy.ndarray) – \((\hbq^{\text{drate}}_k)_{k \in \mcI_j}\), i.e. a \(j\)th \(|\mcL| \times |\mcI_j|\) slice of available_volumes_drates.

  • ordinal_volumes (numpy.ndarray) – \((\bmfq_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcL| \times |\mcI_j|\) slice of ordinal_volumes.

  • dyears (numpy.ndarray) – Number of years between successive time indices, i.e. \((y_t - y_{t-1})_t\) for \(t > 0\), with the first element set to numpy.inf since the initial recurrence period is inherently unknown to the serving system, i.e. iamax, and therefore supplied through the instantiation argument dyear0. Cf. dyears.

  • t (int) – Year-targeting integer index. Cf. t, i.e. a _ilocalizations’s integer element.

Important

Since t is invoked, non-scalar arguments all consist of column-bound time series. Following the rules explained there, the first and third columns respectively refer to \(\hq\) and \(\mfq\), while the second refers to \(\hq^{\text{drate}}\).

At the lower temporal bound, \(\mfq_{-1}\) is represented by the first stored contributory-flow value, so that the -1 index denotes its role in the initialization equations rather than an additional stored observation. Quantities required further below this bound follow the relative prehistory calibrated by compute_calibrand_0(), with dyear0 retained as the recurrence interval throughout this implicit range and no transformation by available_volumes_drates.

This temporal convention also determines the indexing of dyears. dyears[t'] represents \(y_{t'}-y_{t'-1}\) and is therefore indexed by the destination of the corresponding stock transition. Hence dyear0 applies to the transition from \(t=-1\) to \(t=0\) and, by convention, to still earlier implicit transitions. At t=1 it substitutes for the unavailable dyears[0] value when recovering the preceding transition, whereas for \(t>1\) this interval is directly given by dyears[t-1]. In particular, at t=2 the preceding transition runs from \(t=0\) to \(t=1\) and therefore uses dyears[1], even though its originating stock is indexed by t-2=0.

When rental_valued=False, this local recovery of the underlying stock from a formerly computed deterioration volume additionally requires both non-null deterioration and survival over the corresponding period. Under strictly positive recurrence periods, this amounts to \(0 < \delta < 1\): null deterioration leaves the underlying stock unidentified from its deterioration volume, while complete deterioration makes the backward recurrence non-invertible.

Note

This method is vectorization friendly over the -3th axis, if any.

Example

Let’s first define the arrays awaited by the current method. We start with empty available_volumes

>>> avols = np.zeros((3, 1, 2))
>>> avols
array([[[0., 0.]],
       [[0., 0.]],
       [[0., 0.]]])

and null companion driving rates, so that contributory flows are left untransformed.

>>> avols_drates = np.zeros_like(avols)
>>> avols_drates
array([[[0., 0.]],
       [[0., 0.]],
       [[0., 0.]]])

Finally, let’s consider the following contributory flows, assumed to derive from ordinal_volumes.

>>> ovols = np.array([
...     [[0., 280.]],
...     [[0., 336.]],
...     [[0.,   0.]],
... ])
>>> ovols
array([[[  0., 280.]],
       [[  0., 336.]],
       [[  0.,   0.]]])

Let’s consider irregular recurrence intervals, with a half-year first transition followed by a two-year one:

>>> dyears = np.array([np.inf, .5, 2.])
>>> dyears
array([inf, 0.5, 2. ])

Let’s first compute available_volumes as stock volumes. With a \(20\%\) annual deterioration rate and an \(8\%\) annual growth potential, the initial effective perpetuity rate amounts to \(28\%\), hence an underlying initial stock of \(280 / .28 = 1000\).

Since the calls below bypass iamax, let’s first manually provide the cross-section that the latter would serve to compute_calibrand_0() during calibration:

>>> ds = DynamicStock(delta=.2, grpot=.08).compute_calibrand_0(
...     ordinal_volumes_transpose=ovols[0, ..., 1:],
... )
>>> for t in range(3):
...     avols[t, ..., 0] = ds.available_volume_computer(
...         available_volumes=avols,
...         available_volumes_drates=avols_drates,
...         ordinal_volumes=ovols, dyears=dyears, t=t,
...     )
>>> avols
array([[[1000.  ,    0.  ]],
       [[1174.43,    0.  ]],
       [[1087.63,    0.  ]]])

Setting rental_valued to False leaves this underlying stock trajectory unchanged but exposes the corresponding fixed-capital-consumption volumes instead.

>>> favols = np.zeros_like(avols)
>>> fds = DynamicStock(
...     delta=.2, grpot=.08, rental_valued=False
... ).compute_calibrand_0(
...     ordinal_volumes_transpose=ovols[0, ..., 1:]
... )
>>> for t in range(3):
...     favols[t, ..., 0] = fds.available_volume_computer(
...         available_volumes=favols,
...         available_volumes_drates=avols_drates,
...         ordinal_volumes=ovols, dyears=dyears, t=t,
...     )
>>> favols
array([[[180.  ,   0.  ]],
       [[105.57,   0.  ]],
       [[422.79,   0.  ]]])

From t=1 onward, these fixed-capital-consumption volumes can be recovered directly from the formerly computed tock-valued available volumes using the deterioration factor orresponding to each irregular interval:

>>> avols[:-1, ..., 0] * (1. - np.power(.8, dyears[1:, None]))
array([[105.57],
       [422.79]])
>>> favols[1:, ..., 0]
array([[105.57],
       [422.79]])

The same deterioration rule applies at initialization. There, however, the initial fixed-capital-consumption volume derives from the implicit \(\hq^{\text{stock}}_{-1}\) calibrated by compute_calibrand_0(). Since this predecessor lies outside the stored trajectory, no explicit \(t=-1\) observation needs to be supplied.

Conversely, when formerly computed available volumes are stored as fixed-capital-consumption volumes, the preceding underlying stock is recovered according to Eq.291. The common stock recurrence then locally recovers \(\hq^{\text{stock}}_{t-1}\) according to Eq.292, without reconstructing the complete stock trajectory from initialization onward.

Let’s finally vectorize the example over both valuation representations. The first trajectory remains stock-valued, while the second consists of one tenth of the fixed-capital-consumption trajectory.

>>> avols[2, ..., 0] = 0.
>>> favols[2, ..., 0] = 0.
>>> (vavols := np.stack([avols, .1*favols]).swapaxes(1, 0))
array([[[[1000.  ,    0.  ]],
        [[  18.  ,    0.  ]]],
       [[[1174.43,    0.  ]],
        [[  10.56,    0.  ]]],
       [[[   0.  ,    0.  ]],
        [[   0.  ,    0.  ]]]])
>>> (vovols := np.stack([ovols, .1*ovols]).swapaxes(1, 0))
array([[[[  0. , 280. ]],
        [[  0. ,  28. ]]],
       [[[  0. , 336. ]],
        [[  0. ,  33.6]]],
       [[[  0. ,   0. ]],
        [[  0. ,   0. ]]]])
>>> (vavols_drates := np.stack(2*[avols_drates]).swapaxes(1, 0))
array([[[[0., 0.]],
        [[0., 0.]]],
       [[[0., 0.]],
        [[0., 0.]]],
       [[[0., 0.]],
        [[0., 0.]]]])

Every instantiation parameter can likewise be vectorized:

>>> vds = DynamicStock(
...     delta=np.array([[[.2]], [[.2]]]),
...     grpot=np.array([[[.08]], [[.08]]]),
...     dyear0=np.array([[[1.]], [[1.]]]),
...     rental_valued=np.array([[[True]], [[False]]]),
... ).compute_calibrand_0(
...     ordinal_volumes_transpose=vovols[0, ..., 1:],
... )
>>> _t_ = 2
>>> navols = vds.available_volume_computer(
...     available_volumes=vavols,
...     available_volumes_drates=vavols_drates,
...     ordinal_volumes=vovols, dyears=dyears, t=_t_,
... )
>>> navols
array([[[1087.63]],
       [[  42.28]]])
>>> vavols[(_t_, ((0,), (1,)), ..., 0)] = navols
>>> vavols
array([[[[1000.  ,    0.  ]],
        [[  18.  ,    0.  ]]],
       [[[1174.43,    0.  ]],
        [[  10.56,    0.  ]]],
       [[[1087.63,    0.  ]],
        [[  42.28,    0.  ]]]])
class iamax.library.OPrDynamicStock
Inheritance diagram of iamax.library.OPrDynamicStock

Price-augmented version of DynamicStock.

The dynamic quantity representation is inherited unchanged from DynamicStock. The present augmentation only supplies its output price from the lagged price of the contributory flow entering the stock dynamics.

static output_price_computer(ordinal_prices_transpose_lagged: float)

Return the lagged contributory-flow price unchanged.

The price attached to the available quantity is therefore inherited directly from the flow whose successive contributions generate the underlying stock. No additional price transformation is performed here.

Parameters

ordinal_prices_transpose_lagged (numpy.ndarray) – \((\bmfp^{T}_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of ordinal_prices_transpose.

class iamax.library.UOPrDynamicStock(*args, **kwargs)
Inheritance diagram of iamax.library.UOPrDynamicStock

Value-closure augmentation of OPrDynamicStock.

In addition to the inherited dynamic quantity and price mappings, the class retains both the currently exposed available quantity and its underlying stock quantity, and provides a calibration-period root condition linking the available value to its contributory fund.

The stock initialization, implicit prehistory, temporal conventions and alternative available-quantity representations are exactly those of DynamicStock and are not redefined here.

compute_qois(self, *, available_volumes: np.ndarray, available_volumes_drates: np.ndarray, ordinal_volumes: np.ndarray, dyears: np.ndarray, t: int)

Compute and retain both available and underlying stock quantities.

The exposed available quantity is computed exactly as prescribed by available_volume_computer(). The corresponding underlying stock, already recovered internally by that computation, is retained alongside it from _x_t.

This distinction only matters when the selected representation does not itself expose the stock quantity. No additional stock dynamics are introduced by the present method.

Parameters
  • available_volumes (numpy.ndarray) – \((\hbq_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcL| \times |\mcI_j|\) available_volumes’s slice.

  • available_volumes_drates (numpy.ndarray) – \((\hbq^{\text{drate}}_k)_{k \in \mcI_j}\), i.e. a \(j\)th \(|\mcL| \times |\mcI_j|\) slice of available_volumes_drates.

  • ordinal_volumes (numpy.ndarray) – \((\bmfq_k)_{k \in \mcI_j}\), i.e. the \(j\)th \(|\mcL| \times |\mcI_j|\) slice of ordinal_volumes.

  • dyears (numpy.ndarray) – Number of years between successive time indices, i.e. \((y_t - y_{t-1})_t\) for \(t > 0\), with the first element set to numpy.inf since the initial recurrence period is inherently unknown to the serving system, i.e. iamax, and therefore supplied through the instantiation argument dyear0. Cf. dyears.

  • t (int) – Year-targeting integer index. Cf. t, i.e. a _ilocalizations’s integer element.

Example

For illustration only, consider the quantities below as an investment flow feeding a productive stock. We deliberately request the deterioration-valued representation so that the exposed quantity differs from its underlying stock.

>>> avols = np.zeros((1, 1, 2))
>>> avols_drates = np.zeros_like(avols)
>>> ovols = np.array([[[0., 2.8]]])
>>> dyears = np.array([np.inf])

With a \(20\%\) annual deterioration rate and an \(8\%\) growth potential, the initial perpetuity rate is \(28\%\), hence the contributory flow above implies an initial underlying stock of \(2.8/.28=10\).

>>> ds = UOPrDynamicStock(
...     delta=.2, grpot=.08, rental_valued=False
... ).compute_calibrand_0(
...     ordinal_volumes_transpose=ovols[0, ..., 1:]
... )
>>> _ = ds.compute_qois(
...     available_volumes=avols,
...     available_volumes_drates=avols_drates,
...     ordinal_volumes=ovols,
...     dyears=dyears,
...     t=0,
... )

The exposed initial quantity is the deterioration of the implicit predecessor stock, whereas the underlier remains the resulting initial stock itself.

>>> ds.available_volume_computer()
array([[1.8]])
>>> ds.available_volume_underlier_computer()
array([[10.]])
fund_crooter(self, available_value: np.ndarray, disposable_incomes_transpose_lagged: np.ndarray)

Root the initial contributory fund against the available value.

Let \(\hv_j\) denote the value of the available quantity exposed by DynamicStock, and let \(\rho\) and \(\gamma_0\) respectively denote the effective initial perpetuity rate and survival factor defined in DynamicStock.

Merging Eq.278, Eq.279 and Eq.280 implies the initial fund relationship

(294)\[\begin{split}\mcr_j = \hv_j \begin{cases} \rho, & \text{if is rental-valued}, \\[6pt] \displaystyle \frac{\rho\gamma_0}{(1-\rho)(1-\gamma_0)}, & \text{otherwise}. \end{cases}\end{split}\]

The first branch follows immediately from \(\hq^{\mathrm{stock}}_0=\mfq_{-1}/\rho\). The second additionally accounts for the fact that the alternative initial representation is based on deterioration of the implicit predecessor stock, not on the contemporaneous stock itself. The returned relative distance is therefore null exactly when the supplied contributory fund satisfies the calibration relationship above, representation-consistent.

Parameters

Important

All admissibility requirements attached to the corresponding quantity representation remain those described in DynamicStock. The second branch additionally requires \(1-\rho \neq 0\) for its multiplicative value-conversion factor to remain finite.

Example

For illustration only, interpret the contributory flow as investment expenditure and the available quantity as a productive-stock representation. With

\[\delta=.2,\ g=.08,\ dyear0=1,\]

one obtains \(\rho=.28\) and \(\gamma_0=.8\).

Under the stock-valued representation, an available value of 10 therefore corresponds to a fund of 2.8.

>>> ds = UOPrDynamicStock(
...     delta=.2, grpot=.08, rental_valued=True
... )
>>> avalue = np.array([[10.]])
>>> fund = np.array([[2.8]])
>>> ds.fund_crooter(avalue, fund)
array([[1.12e-16]])

Under the deterioration-valued representation, the same calibration has an exposed quantity of 1.8 when the common price is one. Its value must nevertheless reconcile with the same contributory fund.

>>> fds = UOPrDynamicStock(
...     delta=.2, grpot=.08, rental_valued=False
... )
>>> fds._ργ_0
1.555555555555556
>>> avalue = np.array([[1.8]])
>>> fund = np.array([[2.8]])
>>> fds.fund_crooter(avalue, fund)
array([[2.24e-16]])
available_volume_computer(self)

Return the available quantity retained by compute_qois().

Its representation is exactly the one selected by DynamicStock; this accessor performs no further computation.

available_volume_underlier_computer(self)

Return the underlying stock retained by compute_qois().

Contrary to available_volume_computer(), this accessor always returns the stock quantity underlying the selected representation.

class iamax.library.L1NzIntDrivenDemands
Inheritance diagram of iamax.library.L1NzIntDrivenDemands

Class that abstracts the idea of Hicksian-like demands whose price and level characteristics are derived from their L1 normalized ioq_intensities with respect to a good located an arbitrary number of permutations away in the consumption space.

static input_volumes_computer(output_volume: np.ndarray, ioq_intensities: np.ndarray)

s/e.

Parameters
class iamax.library.O2STxRateDrivenSTxRate
Inheritance diagram of iamax.library.O2STxRateDrivenSTxRate

Sales tax rate whose value is determined by that of a good being two permutations away in the consumption space.

(295)\[\frac{\ttau^{o_0}_t}{1 + \ttau^{o_0}_t} = \frac{\ttau^{o_0}_0}{1 + \ttau^{o_0}_0} - \zeta^{o_2}_t \left( \frac{\ttau^{o_2}_t}{1 + \ttau^{o_2}_t} - \frac{\ttau^{o_2}_0}{1 + \ttau^{o_2}_0} \right)\]

where \(\ttau^{o_0}\) is the so-driven sales tax markup rate, \(\ttau^{o_2}\) is the two-permutation-away good’s rate of interest and \(\zeta^{o_2}\) the relevant order \(2\) input/output value intensity.

compute_calibrand_0(self, *, sales_taxes_totals_rates: float, t: int = 0)

Compute (and save) the \(t=0\)’s margin (and markup) rates.

Parameters
Example
>>> so = O2STxRateDrivenSTxRate()
>>> so.compute_calibrand_0(
...     sales_taxes_totals_rates=np.array([[
...         0.023105133651252,  # self related
...         0.150641610514566,  # order-2 away rate
...     ]])
... )
>>> so.o0_markup_sales_tax_total_rate_0
0.023105133651252
>>> so.o0_margin_sales_tax_total_rate_0
0.02258334250439593
>>> so.o2_markup_sales_tax_total_rate_0
0.150641610514566
>>> so.o2_margin_sales_tax_total_rate_0
0.13091966181129083
sales_tax_total_rate_computer(self, *, sales_tax_total_rate: float, iov_intensity_order2: float)

s/e.

Parameters
Example
>>> so = O2STxRateDrivenSTxRate()
>>> so.compute_calibrand_0(
...     sales_taxes_totals_rates=np.array([[
...         0.023105133651252,  # self related
...         0.150641610514566,  # order-2 away rate
...     ]])
... )
>>> so.sales_tax_total_rate_computer(
...     sales_tax_total_rate=0.540874848689871,
...     iov_intensity_order2=0.0426989104641244,
... ).item()
0.01336155939770362
class iamax.library.O2STxRateDrivenCTxRate
Inheritance diagram of iamax.library.O2STxRateDrivenCTxRate

Contributive tax rate whose value is determined by the sales taxe rate of a good being two permutations away in the consumption space.

(296)\[\otau^{o_0}_t = \otau^{o_0}_0 - \zeta^{o_2}_t \left( \frac{\ttau^{o_2}_t}{1 + \ttau^{o_2}_t} - \frac{\ttau^{o_2}_0}{1 + \ttau^{o_2}_0} \right)\]

where \(\otau^{o_0}\) is the so-driven contributive tax margin rate, \(\ttau^{o_2}\) is the two-permutation-away good’s sales tax rate of interest and \(\zeta^{o_2}\) the relevant order \(2\) input/output value intensity.

compute_calibrand_0(self, *, contrib_tax_rate: float, sales_tax_total_rate: float)

Compute (and save) the \(t=0\)’s margin (and markup) rates.

Parameters

Note

\(\mcI_j\) is assumed to be a singleton.

Example
>>> so = O2STxRateDrivenCTxRate()
>>> so.compute_calibrand_0(
...     contrib_tax_rate=0.0225833425043962,
...     sales_tax_total_rate=0.150641610514566,
... )
>>> so.o0_margin_contrib_tax_rate_0
0.0225833425043962
>>> so.o2_markup_sales_tax_total_rate_0
0.150641610514566
>>> so.o2_margin_sales_tax_total_rate_0.item()
0.13091966181129083
contrib_tax_rate_computer(self, *, sales_tax_total_rate: float, iov_intensity_order2: float)

s/e.

Parameters
Example
>>> so = O2STxRateDrivenCTxRate()
>>> so.compute_calibrand_0(
...     contrib_tax_rate=0.0225833425043962,
...     sales_tax_total_rate=0.150641610514566,
... )
>>> so.contrib_tax_rate_computer(
...     sales_tax_total_rate=0.540874848689871,
...     iov_intensity_order2=0.0426989104641244,
... ).item()
0.013185382131176756
class iamax.library.OLabor
Inheritance diagram of iamax.library.OLabor

Example case-denominated composite sector’s labor.

class iamax.library.OKLProducer
Inheritance diagram of iamax.library.OKLProducer

Example case-denominated domestic (intermediary) composite-&-KL good producer.

class iamax.library.OKLEProducer
Inheritance diagram of iamax.library.OKLEProducer

Example case-denominated domestic (intermediary) composite-&-KLE good producer.

class iamax.library.ODProducer
Inheritance diagram of iamax.library.ODProducer

Example case-denominated domestic composite good producer.

class iamax.library.OAProducer
Inheritance diagram of iamax.library.OAProducer

Example case-denominated armington composite good producer.

class iamax.library.ELabor
Inheritance diagram of iamax.library.ELabor

Example case-denominated energy sector’s labor.

class iamax.library.EKLProducer
Inheritance diagram of iamax.library.EKLProducer

Example case-denominated domestic (intermediary) energy-&-KL good producer.

class iamax.library.EKLEProducer
Inheritance diagram of iamax.library.EKLEProducer

Example case-denominated domestic (intermediary) energy-&-KLE good producer.

class iamax.library.EDProducer
Inheritance diagram of iamax.library.EDProducer

Example case-denominated domestic energy producer.

class iamax.library.EAProducer
Inheritance diagram of iamax.library.EAProducer

Example case-denominated armington energy producer.

class iamax.library.Consumer
Inheritance diagram of iamax.library.Consumer

Example case-denominated final consumers.

class iamax.library.Ordinaler

Class that abstracts the idea of an underlying capital stock being calculated based on an assumed rental price, and an investment volume being adjusted accordingly.

static ordinal_volume_computer(input_volumes: np.ndarray)

Perceived index, \(1 \times 1\).

Parameters

input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

See also

ordinal_volumes.

class iamax.library.AdditiveOrdinaler

s/e.

static ordinal_volume_computer(input_volumes: np.ndarray)

Perceived index, \(1 \times 1\), denoted as

(297)\[\mfq_j = \sum_{k \in \mcI_j} q_k\]
Parameters

input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

See also

ordinal_volumes.

class iamax.library.Government
Inheritance diagram of iamax.library.Government

Example case-denominated public administration.

class iamax.library.Investor
Inheritance diagram of iamax.library.Investor

Example case-denominated investors.

class iamax.library.ImplicitLeontief

s/e.

compute_calibrand_0(self, *, input_volumes: np.ndarray)

s/e.

Parameters

input_volumes (numpy.ndarray) – \((\bQ_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes’s slice.

compute_calibrand_0_star(self, *, additive_input_volumes_shares_drates: np.ndarray)

s/e.

Parameters

additive_input_volumes_shares_drates (numpy.ndarray) – \((\bQ^\text{drate}_{\%,k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) slice of additive_input_volumes_shares_drates.

input_volumes_computer(self, *, ordinal_volume: float)

s/e.

Parameters

ordinal_volume (float) – \(\mfq_j\), i.e. the \(j\)th \(1 \times 1\) ordinal_volumes’s element.

class iamax.library.OImplicitLeontief
Inheritance diagram of iamax.library.OImplicitLeontief

s/e.

class iamax.library.MarshallianILeo
Inheritance diagram of iamax.library.MarshallianILeo

s/e.

class iamax.library.LaspeyresianGFL(negated_mask: bool = False)

Class that abstracts the idea of economic agents who anchor their consumption decisions to the initial period’s expenditure shares, mirroring the “fixed-weight” logic of the Laspeyres framework. This class moreover allows one to restrict (in a generalized manner) the set of trade-offs being forced.

Parameters

negated_mask (bool) – Whether the mask passed as argument input_volumes_drates2 of the method compute_calibrand_0 is to be negated. Argument input_volumes_drates2 indeed boolean-specifies the goods being freely subject to trade-off by default. Set to False by default.

compute_calibrand_0(self, *, budget_constraint: float, input_values: np.ndarray, input_volumes_drates2: np.ndarray)

s/e.

Parameters
  • budget_constraint (float) – \(r_j\), i.e. the \(j\)th \(1 \times 1\) budget_constraints’s element.

  • input_values (numpy.ndarray) – \((\bmcV_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_values’s slice.

  • input_volumes_drates2 (numpy.ndarray) – \((\bQ^\text{drate}_{k,j})_{k \in \mcI_j}\), i.e. the \(j\)th \(1 \times |\mcI_j|\) input_volumes_drates2’s slice. This variable is coerced into boolean and used as mask.

Example
>>> budc  = 200.
>>> ivals = np.array([[110., 80., 10.]])
>>> imsks = np.array([[1, 1, 1]])
>>> c = LaspeyresianGFL(negated_mask=False).compute_calibrand_0(
...     budget_constraint=budc, input_values=ivals,
...     input_volumes_drates2=imsks
... )
>>> c.calibrand_0
array([[0.55, 0.4 , 0.05]])

Let’s then illustrate how such calculation can be vectorized.

>>> vbudc  = np.array([[[budc]], [[budc]]])
>>> vivals = np.stack((ivals, ivals))
>>> vimsks = np.stack((imsks, np.array([[1, 0, 1]])))
>>> c.compute_calibrand_0(
...     budget_constraint=vbudc, input_values=vivals,
...     input_volumes_drates2=vimsks
... ).calibrand_0
array([[[0.55, 0.4 , 0.05]],
       [[0.92, 0.  , 0.08]]])
compute_calibrand_0_star(self, input_values_shares_drates: np.ndarray = 0.0)

s/e.

input_values_shares_rooter(self, *, budget_constraint: float, input_values: np.ndarray)

Input demands, \(1 \times |\mcI_j|\)

Parameters
Example
>>> ivals = np.array([[110., 60., 20., 10.]])
>>> budc  = ivals.sum()
>>> c1111 = LaspeyresianGFL(
...     negated_mask=False
... ).compute_calibrand_0(
...     budget_constraint=budc, input_values=ivals,
...     input_volumes_drates2=np.array([[1, 1, 1, 1]])
... )
>>> c1111.calibrand_0
array([[0.55, 0.3 , 0.1 , 0.05]])
>>> c1001 = LaspeyresianGFL(
...     negated_mask=False
... ).compute_calibrand_0(
...     budget_constraint=budc, input_values=ivals,
...     input_volumes_drates2=np.array([[1, 0, 0, 1]])
... )
>>> c1001.calibrand_0
array([[0.92, 0.  , 0.  , 0.08]])
>>> ivals *= np.array([[1, 5/6, 3/2, 1]])
>>> budc   = ivals.sum()
>>> c1111.input_values_shares_rooter(
...     budget_constraint=budc, input_values=ivals,
... )
array([[ 0.  , -0.13,  0.27,  0.  ]])
>>> c1001.input_values_shares_rooter(
...     budget_constraint=budc, input_values=ivals,
... )
array([[0., 0., 0., 0.]])
class iamax.library.EExporter
Inheritance diagram of iamax.library.EExporter

Example case-denominated exporters of energy goods.

class iamax.library.OExporter
Inheritance diagram of iamax.library.OExporter

Example case-denominated exporters of composite goods.

class iamax.library.OneToOne(intensity_factor: float = 1.0)

Abstraction that grants the properties (defined by the methods below) of one entity to another.

Parameters

intensity_factor (float) – S/e. set to 1 by default.

input_volume_computer(self, *, output_volume: float)

Identity function (to within a multiplicative intensity_factor).

Parameters

output_volume (float) – \(q_j\), i.e. the \(j\)th \(1 \times 1\) output_volumes’s element.

Example
>>> OneToOne().input_volume_computer(output_volume=.5)
0.5
>>> OneToOne(2).input_volume_computer(output_volume=.5)
1.0
class iamax.library.AOneToOne
Inheritance diagram of iamax.library.AOneToOne

Augmented version of OneToOne that deals with prices following Accountant’s approach.

class iamax.library.OnBoard
Inheritance diagram of iamax.library.OnBoard

Free-on-board (FOB) to Cost-Insurance-and-Freight (CIF).

compute_calibrand_0(self, *, producer_net_price_drate: np.ndarray, purchaser_prices: np.ndarray)

s/e.

Important

This method relies on the structure of its argument, with the transported good volume being the first arrays’ component.

Example
>>> oprc  = 0.038
>>> pprcs = np.array([[0.774, 0.68]])
>>> ob    = OnBoard()
>>> ob.compute_calibrand_0(
...     producer_net_price_drate=oprc,
...     purchaser_prices=pprcs
... )
>>> ob.calibrand_0
array([[0.04]])

Let’s vectorize the above example.

>>> voprc  = np.array([[[oprc]], [[2*oprc]]])
>>> vpprcs = np.stack((pprcs, 2*pprcs))
>>> ob.compute_calibrand_0(
...     producer_net_price_drate=voprc,
...     purchaser_prices=vpprcs
... )
>>> ob.calibrand_0
array([[[0.04]],
       [[0.09]]])
input_volumes_computer(self, *, output_volume: float)

s/e.

Important

This method relies on the structure of its argument, with the transported good volume being the first arrays’ component.

Example
>>> oprc  = 0.038
>>> pprcs = np.array([[0.774, 0.68]])
>>> ob    = OnBoard()
>>> ob.compute_calibrand_0(
...     producer_net_price_drate=oprc,
...     purchaser_prices=pprcs
... )
>>> ovol  = 197432.592
>>> ivols = ob.input_volumes_computer(
...     output_volume=ovol
... )
>>> ivols
array([[197432.59,   8539.54]])
>>> ivols.shape
(1, 2)

Let’s vectorize the above example.

>>> voprc  = np.array([[[oprc]], [[2*oprc]]])
>>> vpprcs = np.stack((pprcs, 2*pprcs))
>>> ob.compute_calibrand_0(
...     producer_net_price_drate=voprc,
...     purchaser_prices=vpprcs
... )
>>> vovol  = np.array([[[ovol]], [[2*ovol]]])
>>> vivols = ob.input_volumes_computer(
...     output_volume=vovol
... )
>>> vivols
array([[[197432.59,   8539.54]],
       [[394865.18,  34158.16]]])
>>> vivols.shape
(2, 1, 2)
class iamax.library.IOnBoard
Inheritance diagram of iamax.library.IOnBoard

Transport-intensity calibrated version of OnBoard

compute_calibrand_0(self, *, input_volumes: np.ndarray)

s/e.

Important

This method relies on the structure of its argument, with the transported good volume being the first arrays’ component.

Example
>>> ivols = np.array([[3., 6.]])
>>> ob    = IOnBoard()
>>> ob.compute_calibrand_0(
...     input_volumes=ivols
... )
>>> ob.calibrand_0
array([[2.]])

Let’s vectorize the above example.

>>> vivols = np.stack((ivols, [[3., .0]]))
>>> ob.compute_calibrand_0(
...     input_volumes=vivols
... )
>>> ob.calibrand_0
array([[[2.]],
       [[0.]]])
class iamax.library.CorrTrader

Trader abstraction whose optimal demands derive from a portfolio correlated allocation problem.

compute_calibrand_0(self, *, input_volumes: np.ndarray, input_prices: np.ndarray, ordinal_volumes: np.ndarray, ordinal_prices: np.ndarray)

Calibrate the quantity transformation.

Parameters
Example
>>> iprcs = np.identity(3) * np.array((1.5, 2.5, 6.))
>>> ivols = np.identity(3) * np.array((2., .8, .25))
>>> oprcs = np.array((2., 2.5, 5.))[None, :]
>>> ovols = np.diag(ivols * iprcs)[None, :] / oprcs
>>> ct = CorrTrader()
>>> ct.compute_calibrand_0(
...     input_volumes=ivols,
...     input_prices=iprcs,
...     ordinal_volumes=ovols,
...     ordinal_prices=oprcs,
... )._kappa
array([[1.]])
>>> ct._beta
array([[1.33, 0.  , 0.  ],
       [0.  , 1.  , 0.  ],
       [0.  , 0.  , 0.83]])

Let’s now illustrate the matter when the accounting equation does not hold because of ordinal volumes being, well, too ordinal. Such a discrepancy is captured by ~._kappa not being equal to 1.

>>> ct = CorrTrader().compute_calibrand_0(
...     input_volumes=ivols,
...     input_prices=iprcs,
...     ordinal_volumes=3*ovols,  # <---
...     ordinal_prices=oprcs,
... )
>>> ct._kappa
array([[3.]])
>>> ct._omega
array([[0.46, 0.31, 0.23]])
>>> ct._beta
array([[0.44, 0.  , 0.  ],
       [0.  , 0.33, 0.  ],
       [0.  , 0.  , 0.28]])

Let’s now make a vectorized example.

>>> (vivols := np.stack((ivols, 1.5*ivols)))
array([[[2.  , 0.  , 0.  ],
        [0.  , 0.8 , 0.  ],
        [0.  , 0.  , 0.25]],
       [[3.  , 0.  , 0.  ],
        [0.  , 1.2 , 0.  ],
        [0.  , 0.  , 0.38]]])
>>> (viprcs := np.stack((iprcs, 4*iprcs)))
array([[[ 1.5,  0. ,  0. ],
        [ 0. ,  2.5,  0. ],
        [ 0. ,  0. ,  6. ]],
       [[ 6. ,  0. ,  0. ],
        [ 0. , 10. ,  0. ],
        [ 0. ,  0. , 24. ]]])
>>> (vovols := np.stack((ovols, 6*ovols)))
array([[[1.5, 0.8, 0.3]],
       [[9. , 4.8, 1.8]]])
>>> (voprcs := np.stack(2*(oprcs,)))
array([[[2. , 2.5, 5. ]],
       [[2. , 2.5, 5. ]]])
>>> vct = CorrTrader().compute_calibrand_0(
...     input_volumes=vivols, input_prices=viprcs,
...     ordinal_volumes=vovols, ordinal_prices=voprcs
... )
>>> vct._beta
array([[[1.33, 0.  , 0.  ],
        [0.  , 1.  , 0.  ],
        [0.  , 0.  , 0.83]],
       [[0.33, 0.  , 0.  ],
        [0.  , 0.25, 0.  ],
        [0.  , 0.  , 0.21]]])
>>> vct._omega
array([[[0.46, 0.31, 0.23]],
       [[0.46, 0.31, 0.23]]])
compute_qois(self, *, budget_constraints: np.ndarray, input_prices: np.ndarray, ordinal_volumes_drates: np.ndarray = 0.0)

Compute ordinal capital flows and physical input demands.

Parameters
Example
>>> (iprcs := np.identity(3) * np.array((1.5, 2.5, 6.)))
array([[1.5, 0. , 0. ],
       [0. , 2.5, 0. ],
       [0. , 0. , 6. ]])
>>> (ivols := np.identity(3) * np.array((2., .8, .25)))
array([[2.  , 0.  , 0.  ],
       [0.  , 0.8 , 0.  ],
       [0.  , 0.  , 0.25]])
>>> (oprcs := np.array((4., 5., 10.))[None, :])
array([[ 4.,  5., 10.]])
>>> (
...     ovols := 2. * np.diag(ivols * iprcs)[None, :] / oprcs
... )  # Breaks the accounting equation
array([[1.5, 0.8, 0.3]])
>>> ct = CorrTrader().compute_calibrand_0(
...     input_volumes=ivols, input_prices=iprcs,
...     ordinal_volumes=ovols, ordinal_prices=oprcs,
... )
>>> ct._kappa
array([[2.]])
>>> ct._beta
array([[1.33, 0.  , 0.  ],
       [0.  , 1.  , 0.  ],
       [0.  , 0.  , 0.83]])
>>> (bcons := np.diag(ivols * iprcs)[None, :])
array([[3. , 2. , 1.5]])
>>> ct.compute_qois(
...     budget_constraints=bcons, input_prices=iprcs,
... ).input_volumes_computer()
array([[2.  , 0.  , 0.  ],
       [0.  , 0.8 , 0.  ],
       [0.  , 0.  , 0.25]])
>>> ct.ordinal_volumes_computer()
array([[1.5, 0.8, 0.3]])
>>> ct.ordinal_prices_computer()
array([[2. , 2.5, 5. ]])

Let’s vectorize the above example.

>>> (viprcs := np.stack((iprcs, 4*iprcs)))
array([[[ 1.5,  0. ,  0. ],
        [ 0. ,  2.5,  0. ],
        [ 0. ,  0. ,  6. ]],
       [[ 6. ,  0. ,  0. ],
        [ 0. , 10. ,  0. ],
        [ 0. ,  0. , 24. ]]])
>>> (vivols := np.stack((ivols, 1.5*ivols)))
array([[[2.  , 0.  , 0.  ],
        [0.  , 0.8 , 0.  ],
        [0.  , 0.  , 0.25]],
       [[3.  , 0.  , 0.  ],
        [0.  , 1.2 , 0.  ],
        [0.  , 0.  , 0.38]]])
>>> (voprcs := np.stack(2*(oprcs,)))
array([[[ 4.,  5., 10.]],
       [[ 4.,  5., 10.]]])
>>> (vovols := np.stack((.5*ovols, 6*ovols)))
array([[[0.75, 0.4 , 0.15]],
       [[9.  , 4.8 , 1.8 ]]])
>>> vct = CorrTrader().compute_calibrand_0(
...     input_volumes=vivols, input_prices=viprcs,
...     ordinal_volumes=vovols, ordinal_prices=voprcs
... )
>>> vct._kappa
array([[[1.]],
       [[2.]]])
>>> vct._beta
array([[[2.67, 0.  , 0.  ],
        [0.  , 2.  , 0.  ],
        [0.  , 0.  , 1.67]],
       [[0.33, 0.  , 0.  ],
        [0.  , 0.25, 0.  ],
        [0.  , 0.  , 0.21]]])
>>> (vbcons := np.stack((bcons, 6*bcons)))
array([[[ 3. ,  2. ,  1.5]],
       [[18. , 12. ,  9. ]]])
>>> vct.compute_qois(
...     budget_constraints=vbcons, input_prices=viprcs
... ).input_volumes_computer()
array([[[2.  , 0.  , 0.  ],
        [0.  , 0.8 , 0.  ],
        [0.  , 0.  , 0.25]],
       [[3.  , 0.  , 0.  ],
        [0.  , 1.2 , 0.  ],
        [0.  , 0.  , 0.38]]])
>>> vct.ordinal_volumes_computer()
array([[[0.75, 0.4 , 0.15]],
       [[9.  , 4.8 , 1.8 ]]])
>>> vct.ordinal_prices_computer()
array([[[ 4. ,  5. , 10. ]],
       [[ 2. ,  2.5,  5. ]]])

Let’s now turn to full matrices. Rows represent goods and services, while columns represent the sectors whose productive available volumes are being built. Each column therefore describes the basket of goods required by one capacity-building sector.

The supplied ordinal prices are defined directly from the dual relation, so the accounting equation holds by construction.

>>> iprcs = np.array([
...     [1., .5, 3.],
...     [2., 2., 1.],
...     [1., 2., 2.],
... ])
>>> ivols = np.array([
...     [2., 1., .5],
...     [.25, 1., .5],
...     [.25, .125, .75],
... ])
>>> ovols = np.array([2., 1., .5])[None, :]
>>> (oprcs := (iprcs * ivols / ovols).sum(axis=-2, keepdims=True))
array([[1.38, 2.75, 7.  ]])
>>> ct = CorrTrader().compute_calibrand_0(
...     input_volumes=ivols, input_prices=iprcs,
...     ordinal_volumes=ovols, ordinal_prices=oprcs,
... )
>>> ct._kappa
array([[1.]])
>>> ct._beta
array([[1.  , 1.  , 1.  ],
       [0.12, 1.  , 1.  ],
       [0.12, 0.12, 1.5 ]])
>>> np.linalg.matrix_rank(ct._beta)
3
>>> iprcs * ct._beta
array([[1.  , 0.5 , 3.  ],
       [0.25, 2.  , 1.  ],
       [0.12, 0.25, 3.  ]])
>>> ct._omega
array([[0.31, 0.31, 0.39]])
>>> (bcons := oprcs * ovols)
array([[2.75, 2.75, 3.5 ]])
>>> ct.compute_qois(
...     budget_constraints=bcons, input_prices=iprcs,
... ).input_volumes_computer()
array([[2.  , 1.  , 0.5 ],
       [0.25, 1.  , 0.5 ],
       [0.25, 0.12, 0.75]])
>>> ct.ordinal_volumes_computer()
array([[2. , 1. , 0.5]])
>>> ct.ordinal_prices_computer()
array([[1.38, 2.75, 7.  ]])
>>> ct.input_volumes_computer().sum(
...     axis=-1, keepdims=True,
... )
array([[3.5 ],
       [1.75],
       [1.12]])

Let’s now vectorize this full-matrix example. The second slice triples physical prices, doubles physical volumes and quadruples ordinal volumes. Its calibrated transformation is therefore one half of the first, its dual ordinal prices are multiplied by 1.5 and its budget constraints are multiplied by six.

>>> (viprcs := np.stack((iprcs, 3*iprcs)))
array([[[1. , 0.5, 3. ],
        [2. , 2. , 1. ],
        [1. , 2. , 2. ]],
       [[3. , 1.5, 9. ],
        [6. , 6. , 3. ],
        [3. , 6. , 6. ]]])
>>> (vivols := np.stack((ivols, 2*ivols)))
array([[[2.  , 1.  , 0.5 ],
        [0.25, 1.  , 0.5 ],
        [0.25, 0.12, 0.75]],
       [[4.  , 2.  , 1.  ],
        [0.5 , 2.  , 1.  ],
        [0.5 , 0.25, 1.5 ]]])
>>> (vovols := np.stack((ovols, 4*ovols)))
array([[[2. , 1. , 0.5]],
       [[8. , 4. , 2. ]]])
>>> (
...     voprcs := np.sum(
...         viprcs * vivols / vovols,
...         axis=-2, keepdims=True,
...     )
... )
array([[[ 1.38,  2.75,  7.  ]],
       [[ 2.06,  4.12, 10.5 ]]])
>>> vct = CorrTrader().compute_calibrand_0(
...     input_volumes=vivols, input_prices=viprcs,
...     ordinal_volumes=vovols, ordinal_prices=voprcs,
... )
>>> vct._kappa
array([[[1.]],
       [[1.]]])
>>> vct._beta
array([[[1.  , 1.  , 1.  ],
        [0.12, 1.  , 1.  ],
        [0.12, 0.12, 1.5 ]],
       [[0.5 , 0.5 , 0.5 ],
        [0.06, 0.5 , 0.5 ],
        [0.06, 0.06, 0.75]]])
>>> np.linalg.matrix_rank(vct._beta)
array([3, 3], dtype=int64)
>>> vct._omega
array([[[0.31, 0.31, 0.39]],
       [[0.31, 0.31, 0.39]]])
>>> (vbcons := voprcs * vovols)
array([[[ 2.75,  2.75,  3.5 ]],
       [[16.5 , 16.5 , 21.  ]]])
>>> vct.compute_qois(
...     budget_constraints=vbcons, input_prices=viprcs,
... ).input_volumes_computer()
array([[[2.  , 1.  , 0.5 ],
        [0.25, 1.  , 0.5 ],
        [0.25, 0.12, 0.75]],
       [[4.  , 2.  , 1.  ],
        [0.5 , 2.  , 1.  ],
        [0.5 , 0.25, 1.5 ]]])
>>> vct.ordinal_volumes_computer()
array([[[2. , 1. , 0.5]],
       [[8. , 4. , 2. ]]])
>>> vct.ordinal_prices_computer()
array([[[ 1.38,  2.75,  7.  ]],
       [[ 2.06,  4.12, 10.5 ]]])
>>> vct.input_volumes_computer().sum(
...     axis=-1, keepdims=True,
... )
array([[[3.5 ],
        [1.75],
        [1.12]],
       [[7.  ],
        [3.5 ],
        [2.25]]])
input_volumes_computer(self)

s/e.

ordinal_volumes_computer(self)

s/e.

ordinal_prices_computer(self)

s/e.