def shortage_cost(shortage_cost, shortage_quantity): """ Calculate the shortage cost - the total cost of not having units in stock, including costs due to backorder, lost sales, lost customers, and operational disruptions. Parameters ---------- shortage_cost : int or float Cost per unit of shortage, in dollars shortage_quantity : int The number of units of shortage, in units Returns ------- shortage_cost : float """ Validate.required(shortage_cost, "shortage_cost") Validate.non_negative(shortage_cost, "shortage_cost") Validate.required(shortage_quantity, "shortage_quantity") Validate.non_negative(shortage_quantity, "shortage_quantity") return shortage_cost * shortage_quantity
def __init__(self, quantity, time_unit='Y'): Validate.required(quantity, "quantity") Validate.non_negative(quantity, "quantity") self.quantity = quantity Validate.one_of_allowable(time_unit, ['Y', 'M', 'W', 'D'], "time_unit") self.time_unit = time_unit
def optimal_order_quantity(raw_costs, demand): """ Calculate economic order quantity (optimal Q*) given raw costs and demand. Parameters ---------- raw_costs : RawCosts A `RawCosts` object, containing a set of raw cost values demand : Demand A `Demand` object, containing demand quantity and time frame Returns ------- optimal_order_quantity: float """ Validate.required(raw_costs, "raw_costs") Validate.required(demand, "demand") # if holding cost is 0, optimal Q would be D (i.e. # minimize ordering cost with one order across all demand) if raw_costs.holding_cost == 0.0: return demand.quantity else: numerator = 2 * raw_costs.ordering_cost * demand.quantity denominator = raw_costs.holding_cost return math.sqrt(numerator / denominator)
def optimal_cycle_time(raw_costs, demand): """ Calculate optimal cycle time (T*) given raw costs and demand. Parameters ---------- raw_costs : RawCosts A `RawCosts` object, containing a set of raw cost values demand : Demand A `Demand` object, containing demand quantity and time frame Returns ------- optimal_cycle_time: float """ Validate.required(raw_costs, "raw_costs") Validate.required(demand, "demand") Validate.positive(demand.quantity, "demand.quantity") Validate.positive(raw_costs.holding_cost, "raw_costs.holding_cost") numerator = 2 * raw_costs.ordering_cost denominator = demand.quantity * raw_costs.holding_cost return math.sqrt(numerator / denominator)
def total_cost(raw_costs, demand, order_quantity, expected_shortage=0): """ Calculate total inventory cost - the combination of purchase, ordering, holding, and shortage cost components. Parameters ---------- raw_costs : RawCosts A `RawCosts` object, containing a set of raw cost values demand : Demand A `Demand` object, containing demand quantity and time frame order_quantity : int The number of units purchased with each order expected_shortage : int, default 0 The expected value of the number of units short Returns ------- total_cost : float """ Validate.required(raw_costs, "raw_costs") Validate.required(demand, "demand") Validate.required(order_quantity, "order_quantity") Validate.required(expected_shortage, "expected_shortage") return ( Costs.purchase_cost(raw_costs.unit_cost, demand.quantity) + Costs.total_relevant_cost(raw_costs, demand, order_quantity) + Costs.shortage_cost(raw_costs.shortage_cost, expected_shortage))
def __validate__(x, a, b, c): # todo: revisit these: not always necessary # e.g. if x < a or > b, then pdf/cdf evaluate to 0 or 1 for (var, name) in [(x, "x"), (a, "a"), (b, "b"), (c, "c")]: v.required(var, name) if a >= b: raise ValueError("a must be less than b") if c < a or c > b: raise ValueError("c must be between a and b") if x < a or x > b: raise ValueError("x must be between a and b")
def optimal_total_cost(raw_costs, demand): """ Calculate optimal total cost. (TC(Q*)) Parameters ---------- raw_costs : RawCosts A `RawCosts` object, containing a set of raw cost values demand : Demand A `Demand` object, containing demand quantity and time frame Returns ------- optimal_total_cost: float """ Validate.required(raw_costs, "raw_costs") Validate.required(demand, "demand") return (raw_costs.unit_cost * demand.quantity + EOQ.optimal_relevant_cost(raw_costs=raw_costs, demand=demand))
def optimal_relevant_cost(raw_costs, demand): """ Calculate optimal relevant cost. (TRC(Q*)) Parameters ---------- raw_costs : RawCosts A `RawCosts` object, containing a set of raw cost values demand : Demand A `Demand` object, containing demand quantity and time frame Returns ------- optimal_relevant_cost: float """ Validate.required(raw_costs, "raw_costs") Validate.required(demand, "demand") return math.sqrt(2 * raw_costs.ordering_cost * raw_costs.holding_cost * demand.quantity)
def ordering_cost(ordering_cost, demand_quantity, order_quantity): """ Calculate the ordering cost - the total cost of placing, receiving, and processing orders needed to fulfill a given amount of demand. Parameters ---------- ordering_cost : int or float Cost per order, in dollars demand_quantity : int The amount of demand, in units order_quantity : int The number of units purchased per order, in units Returns ------- ordering_cost: float """ Validate.required(ordering_cost, "ordering_cost") Validate.non_negative(ordering_cost, "ordering_cost") Validate.required(demand_quantity, "demand_quantity") Validate.non_negative(demand_quantity, "demand_quantity") Validate.required(order_quantity, "order_quantity") Validate.positive(order_quantity, "order_quantity") return ordering_cost * (demand_quantity / order_quantity)
def total_relevant_cost(raw_costs, demand, order_quantity): """ Calculate total inventory cost relevant to the calculation of economic order quantity. Note: The relevant cost components are those that change as a function of order quantity: ordering cost and holding cost. Parameters ---------- raw_costs : RawCosts A `RawCosts` object, containing a set of raw cost values demand : Demand A `Demand` object, containing demand order_quantity and time frame order_quantity : int The number of units purchased with each order Returns ------- total_relevant_cost : float """ Validate.required(raw_costs, "raw_costs") Validate.required(demand, "demand") Validate.required(order_quantity, "order_quantity") return (Costs.ordering_cost(raw_costs.ordering_cost, demand.quantity, order_quantity) + Costs.holding_cost(raw_costs.holding_cost, order_quantity))
def holding_cost(holding_cost, order_quantity): """ Calculate the holding cost - the total cost of holding excess inventory, including storage, service costs, risk costs, and capital costs. Parameters ---------- holding_cost : int or float Cost per unit of inventory per time period, in dollars order_quantity : int The number of units purchased per order, in units Returns ------- holding_cost: float """ Validate.required(holding_cost, "holding_cost") Validate.non_negative(holding_cost, "holding_cost") Validate.required(order_quantity, "order_quantity") Validate.non_negative(order_quantity, "order_quantity") return holding_cost * (order_quantity / 2)
def purchase_cost(unit_cost, demand_quantity): """ Calculate the purchase cost - the total landed cost for acquiring product needed to satisfy a given amount of demand. Parameters ---------- unit_cost : int or float Cost per unit, in dollars demand_quantity : int The amount of demand, in units Returns ------- purchase_cost: float """ Validate.required(unit_cost, "unit_cost") Validate.non_negative(unit_cost, "unit_cost") Validate.required(demand_quantity, "demand_quantity") Validate.non_negative(demand_quantity, "demand_quantity") return unit_cost * demand_quantity
def validate_init_values(unit_cost, ordering_cost, holding_rate, holding_cost, holding_time_unit, shortage_cost): Validate.required(unit_cost, "unit_cost") Validate.non_negative(unit_cost, "unit_cost") Validate.required(ordering_cost, "ordering_cost") Validate.non_negative(ordering_cost, "ordering_cost") # accept holding_rate or holding_cost, but not both # (at least one is required) Validate.one_only([holding_rate, holding_cost], ["holding_rate", "holding_cost"]) if holding_rate is not None: Validate.non_negative(holding_rate, "holding_rate") else: Validate.non_negative(holding_cost, holding_cost) Validate.one_of_allowable(holding_time_unit, RawCosts.supported_time_units, "holding_time_unit") Validate.required(shortage_cost, "shortage_cost") Validate.non_negative(shortage_cost, "shortage_cost")