"""Representation of cost objects in various bases and forms."""
from functools import total_ordering
@total_ordering
[docs]class Cost:
"""Represents costs and holds data for type conversions.
The ``type_`` of a cost is necessary for conversion between the
different types; costs can be provide in real or nominal terms and
as a factor (or "resource") cost or a market price. Additionally, a
cost can be a Present Value (discounted real factor cost or market
price).
It is essential for an accurate calculation that the appropriate
cost type is used for conversion to consistent output values.
Note:
The default assumptions for ``type_`` are :py:attr:`NOMINAL` and
:py:attr:`FACTOR_COST`. For example::
Cost(100, Cost.REAL, ...)
is equivalent to::
Cost(100, Cost.REAL | Cost.FACTOR_COST, ...)
The exception is :py:attr:`PRESENT_VALUE`, which is always a real
cost.
Arguments:
value (``float``): The value of the cost.
type_ (``int``): The type of the cost.
year (``int``): The year in which the cost is incurred.
discount (:py:class:`~.Discount`): The discount factors to use
for conversion to Present Value.
deflator (:py:class:`~.GdpDeflator`): The GDP deflator factors to
use for conversion to real prices.
adjustment_factor (``float``): The factor to use for conversion
to market prices.
Attributes:
cost (``float``): The nominal factor cost.
year (``int``): The year in which the cost is incurred.
discount_factor (``float``): The factor for conversion to Present
Value (from real factor costs or market prices).
deflation_factor (``float``): The factor for conversion to real
prices (from nominal prices).
adjustment_factor (``float``): The factor for conversion to
market prices (from factor costs).
Raises:
ValueError: If the ``type_`` argument is invalid.
"""
FACTOR_COST = 1
"""Factor cost, excluding taxation."""
RESOURCE_COST = 1
"""Synonym of :py:attr:`FACTOR_COST`."""
MARKET_PRICE = 2
"""Market price, including taxation."""
NOMINAL = 4
"""Nominal price, as paid in the year incurred."""
REAL = 8
"""Real price, in a constant price base year."""
PRESENT_VALUE = 16
"""Discounted real costs."""
def __init__(self, value, type_, year, discount,
deflator, adjustment_factor):
self.validate_type(type_)
self.year = year
self.discount_factor = discount[year]
self.deflation_factor = 1 / deflator[year]
self.adjustment_factor = adjustment_factor
if type_ & self.PRESENT_VALUE:
value /= self.discount_factor
type_ |= self.REAL
if type_ & self.REAL:
value /= self.deflation_factor
if type_ & self.MARKET_PRICE:
value /= self.adjustment_factor
self.value = value
self.hash_ = None
def __eq__(self, other):
for attr in ("value", "year", "discount_factor",
"deflation_factor", "adjustment_factor"):
if getattr(self, attr) != getattr(other, attr):
return False
return True
def __hash__(self):
if self.hash_ is None:
out = 0
for attr in ("value", "year", "discount_factor",
"deflation_factor", "adjustment_factor"):
out ^= hash(getattr(self, attr))
self.hash_ = out
return self.hash_
def __lt__(self, other):
for attr in ("year", "discount_factor", "deflation_factor",
"adjustment_factor"):
if getattr(self, attr) != getattr(other, attr):
return NotImplemented
return self.value < other.value
[docs] def as_type(self, type_):
"""Convert the nominal factor cost to the specified ``type_``.
Arguments:
type_ (``int``): The type to convert to.
Returns:
float: The converted value.
"""
self.validate_type(type_)
value = self.value
if type_ & self.PRESENT_VALUE:
value *= self.discount_factor
type_ |= self.REAL
if type_ & self.REAL:
value *= self.deflation_factor
if type_ & self.MARKET_PRICE:
value *= self.adjustment_factor
return value
@classmethod
[docs] def validate_type(cls, type_):
"""Validate a cost type argument.
Costs cannot be both market price and factor/resource cost,
or both nominal and real. Present values are necessarily
discounted real factor costs or market prices.
Arguments:
type_ (``int``): The type of the cost.
Raises:
ValueError: If the ``type_`` is invalid.
"""
if (type_ & cls.MARKET_PRICE) and (type_ & cls.FACTOR_COST):
raise ValueError("Cost cannot be market price and factor cost.")
if (type_ & cls.NOMINAL) and (type_ & cls.REAL):
raise ValueError("Cost cannot be real and nominal.")
if (type_ & cls.NOMINAL) and (type_ & cls.PRESENT_VALUE):
raise ValueError("Nominal costs cannot be present values.")