class Threshold(object): """ A wrapper for a bucket threshold value. Ensures proper serialization between ElasticSearch and the JSON API eloggingndpoints. """ __slots__ = ('_value', ) def __init__(self, value): if isinstance(value, Threshold): assert isinstance(value._value, Decimal) # Clone the other Threshold. self._value = value._value return elif isinstance(value, Decimal): self._value = value return elif isinstance(value, string_types): value = value.replace('_', '.') elif isinstance(value, float): pass else: raise TypeError("Expected type %s but got %s" % (text_type, repr(value))) self._value = Decimal(value).quantize(Decimal('0.1')) def __str__(self): result = str(self._value) assert '_' not in result # Ensure that rounded values are always displayed with at least one # decimal, for aesthetics. if '.' not in result: return result + '.0' return result def __repr__(self): return "Threshold('" + str(self) + "')" def to_float(self): return float(self) def __float__(self): """ Convert the threshold to a floating point number, for comparisons. Note that this should NOT be converted back to a threshold, as there may be a loss of data by doing the round trip. """ return float(self._value) def __getattr__(self, attr): # Delegate everything (i.e, comparisons) to the actual Threshold # value. return getattr(self._value, attr) def __hash__(self): return self._value.__hash__() + 1 def __eq__(self, otter): if not isinstance(otter, Threshold): return False return self._value == otter._value def to_elasticsearch(self): """ Converts the threshold to a string, suitable for serialization as an ElasticSearch field name. Note that full stops ('.') are verbotten in ElasticSearch field names. """ #elif isinstance(o, Bucket): #o.check() #return o.as_dict() #elif isinstance(o, TopMatch): #return o.as_dict() str_value = str(self) assert isinstance(self._value, Decimal) assert str_value.count('.') == 1, 'Invalid decimal number' return str_value.replace('.', '_') def __lt__(self, other): return float(self._value) < float(other._value) def __deepcopy__(self, memo): return Threshold(copy(self._value)) def jsonify(self): return text_type(self._value)
class Decibel(object): @classmethod def from_factor(cls, gain_factor, context=pcontext): """Create a Decibel object give the gain factor. >>> Decibel.from_factor(1) Decibel('0.000000') >>> Decibel.from_factor(3) Decibel('4.771213') """ return cls(mwatt2db(gain_factor)) @classmethod def from_10ths_db(cls, inval): """ >>> dB = Decibel.from_10ths_db(22) >>> str(dB) '2.20' >>> dB = Decibel.from_10ths_db(None) >>> str(dB) 'None' """ if inval is None or inval == "None": dB = None else: dB = Decibel(inval).dB / D(10) return cls(dB) @classmethod def from_100ths_db(cls, inval): """ >>> dB = Decibel.from_100ths_db(202) >>> str(dB) '2.02' >>> dB = Decibel.from_10ths_db(None) >>> str(dB) 'None' """ if inval is None or inval == "None": dB = None else: dB = D(inval) / D(100) return cls(dB) def __init__(self, dB): if dB is None or (is_str(dB) and dB == "None"): self.dB = None else: try: dB = dB.replace('dB', '') except AttributeError: pass if isinstance(dB, Decibel): self.dB = dB.dB else: self.dB = Decimal(dB).quantize(SIXPLACES, rounding=ROUND_HALF_EVEN) def __repr__(self): """ >>> Decibel(1) Decibel('1.000000') >>> Decibel(.01) Decibel('0.010000') >>> Decibel(None) Decibel(None) >>> Decibel("None") Decibel(None) """ if self.dB is None: return "Decibel(None)" return "Decibel('{}')".format(self.dB) def __hash__(self): return self.dB.__hash__() def __float__(self): return float(self.dB) def __format__(self, format_spec): # pylint: disable=W0221 """ >>> str("{:+2.2f}".format(Decibal(100))) '+100.00' >>> str("{:+2.2f}".format(Decibal(0))) '+0.00' >>> str("{:+2.2f}".format(Decibal(None))) 'None' """ if self.dB is None: # XXX really want to format this. return "None" return self.dB.__format__(format_spec) def __str__(self): # pylint: disable=W0222 """ >>> str(Decibel(0)) '0.00' >>> str(Decibel(3.141596)) '3.14' >>> str(Decibel(-3.999)) '-4.00' >>> str(Decibel(None)) 'None' """ if self.dB is None: return "None" else: return "{:.2f}".format(self.dB) def __abs__(self): if self.dB is None: return Decibel(None) elif self.dB < 0: return Decibel(self.dB.__neg__()) else: return Decibel(self.dB) def __neg__(self): # pylint: disable=W0222 """ >>> str(-Decibel(1)) '-1.00' >>> str(-Decibel(-1)) '1.00' """ if self.dB is None: return Decibel(None) return Decibel(self.dB.__neg__()) def __add__(self, add_atten): # pylint: disable=W0221 """ >>> Decibel(1) + Decibel(3) Decibel('4.000000') >>> Decibel(-1) + 3 Decibel('2.000000') """ if hasattr(add_atten, "dB"): return Decibel(self.dB + add_atten.dB) else: return Decibel(self.dB + add_atten) def __sub__(self, sub_atten): # pylint: disable=W0221 """ >>> Decibel(1) - Decibel(3) Decibel('-2.000000') >>> Decibel(2) - 1 Decibel('1.000000') """ if hasattr(sub_atten, "dB"): return Decibel(self.dB - sub_atten.dB) else: return Decibel(self.dB - sub_atten) def __mul__(self, other): # pylint: disable=W0222 """ >>> Decibel(.5) * 20 Decibel('10.000000') """ if self.dB is None or other is None: return Decibel(None) if hasattr(other, "dB") and other.dB is None: return Decibel(None) return Decibel(self.dB * other) __rmul__ = __mul__ def __lt__(self, other): """ >>> Gain(0) > Gain(.1) False >>> Gain(.1) > Gain(0) True >>> Gain(0) < Gain(.1) True >>> Gain(.1) < Gain(0) False >>> Gain(0) == Gain(0) True >>> Gain(.1) == Gain(.1) True >>> Gain(0) != Gain(None) True >>> Gain(0) == Gain(None) False >>> Gain(None) == Gain(None) True >>> Gain(None) >= Gain(-50) False >>> Gain(None) < Gain(-50) True >>> Gain(None) <= Gain(-50) True """ if self.dB is None: if other is None or other == "None": return False if hasattr(other, "dB") and other.dB is None: return False # other is not None so it's greater return True elif other is None or other == "None" or (hasattr(other, "dB") and other.dB is None): # self is not None other is so False return False return float(self.dB) < float(other) def __eq__(self, other): if self.dB is None: if other is None or other == "None": return True if hasattr(other, "dB") and other.dB is None: return True return False elif other is None or other == "None" or (hasattr(other, "dB") and other.dB is None): return self.dB is None return float(self.dB) == float(other) def gain_factor(self): """Convert gain value to mwatt for multiplying >>> Decibel(0).gain_factor() Decimal('1.000000') >>> Decibel(13).gain_factor() Decimal('19.952623') """ # # Gain: x dB = 10 * log10(multiplier) # 10 ^ (x dB / 10) = 10^(log10(multiplier)) # 10 ^ (x dB / 10) = multiplier # rv = Decimal(10 ** (self.dB / 10), pcontext) rv = rv.quantize(SIXPLACES, rounding=ROUND_HALF_EVEN) return rv mwatt_ratio = gain_factor def ase(self, nf, for_osnr=False): """Calculate ASE generated by amplification >>> Gain(23).ase(5.5) Power('-3.461412') """ # Pase = hv(G -1) * NF * B0 if not for_osnr: B0 = D('5.00e+12') # all c-band (for .5nm) else: B0 = D('1.25e+10') # @0.1nm resolution v = D('1.93e+14') # speed of light h = D('6.63e-34') # planck's constant nf = db2mwatt(nf) # convert noise figure from dB to mwatts ase_watts = D(nf) * B0 * h * v * (self.mwatt_ratio() - 1) return Power.from_mwatt(ase_watts * 1000) def osnr(self, nf, powerin): """Calculate OSNR for .1nm signal >>> Gain(16).osnr(7.9, Power(-20)) Decibel('30.170675') """ # Psig = Pin * G # Pase = hv(G - 1) * NF * B0.1nm # OSNR = Psig / Pase psig = powerin + self pase = self.ase(nf, True) return psig - pase
class Power(object): """Optical power value act's mostly like a float""" @classmethod def from_xml(cls, entry, xpath=None): if entry is not None and xpath is not None: entry = entry.find(xpath) if entry is None: return Power(None) try: return Power(entry.text) except ValueError: return Power(None) @classmethod def from_10uwatt(cls, mwatt): return cls.from_mwatt(D(mwatt) / 10000) @classmethod def from_10ths_dbm(cls, inval): dB = D(inval) / D(10) return cls(dB) from_10ths_db = from_10ths_dbm @classmethod def from_100ths_dbm(cls, inval): dB = D(inval) / D(100) return cls(dB) from_100ths_db = from_100ths_dbm @classmethod def from_mwatt(cls, mwatt): """ >>> Power.from_mwatt(D(0)) Power(None) >>> Power.from_mwatt(2) Power('3.010300') >>> Power.from_mwatt(1) Power('0.000000') >>> Power.from_mwatt(.5) Power('-3.010300') """ if mwatt is None: return cls(None) if not mwatt: return cls(None) try: # Try and convert to float first to catch '0.00' case if not float(mwatt): return cls(None) except ValueError: pass dBm = D(10, pcontext) * D(mwatt).log10(pcontext) dBm = dBm.quantize(SIXPLACES, rounding=ROUND_HALF_EVEN) return cls(dBm) def __init__(self, dBm, context=pcontext): # pylint: disable=W0222 """ >>> Power(20).mwatt() Decimal('100.000000') >>> Power(10).mwatt() Decimal('10.000000') >>> Power(3).mwatt() Decimal('1.995262') >>> Power(1).mwatt() Decimal('1.258925') >>> Power(0).mwatt() Decimal('1.000000') >>> Power(None).mwatt() 0 >>> Power('None').mwatt() 0 """ if dBm is None or (is_str(dBm) and dBm == "None"): self.dBm = None else: try: if dBm.endswith('dBm'): dBm = dBm.replace('dBm', '') except AttributeError: pass if isinstance(dBm, Power): self.dBm = dBm.dBm else: self.dBm = Decimal(dBm, context).quantize(SIXPLACES, rounding=ROUND_HALF_EVEN) def mwatt(self): if self.dBm is None: return 0 # XXX get context from us not global rv = Decimal(10 ** (self.dBm / 10), pcontext) rv = rv.quantize(SIXPLACES, rounding=ROUND_HALF_EVEN) return rv def __hash__(self): return self.dBm.__hash__() def __format__(self, format_spec): # pylint: disable=W0221 """ >>> str("{:+2.2f}".format(Power(100))) '+100.00' >>> str("{:+2.2f}".format(Power(0))) '+0.00' >>> str("{:+2.2f}".format(Power(None))) 'None' """ if self.dBm is None: return "None" return self.dBm.__format__(format_spec) def __bool__(self): return self.dBm is not None def __nonzero__(self): return self.dBm is not None def __add__(self, gain): # pylint: disable=W0221 """ >>> Power(0) + Gain(1) Power('1.000000') """ if hasattr(gain, "dBm"): raise TypeError("unsupported operand type(s) for +: 'Power' and 'Power'") else: return self.add_gain(gain) def __sub__(self, power_or_gain): # pylint: disable=W0221 """ >>> Power(0) - Gain(1) Power('-1.000000') >>> Power(0) - Power(2) Decibel('-2.000000') >>> Power(3) - Power(0) Decibel('3.000000') """ try: dBm = power_or_gain.dBm return Gain(self.dBm - dBm) except AttributeError: return self.add_gain(-power_or_gain) def __lt__(self, other): """ >>> Power(0) > Power(.1) False >>> Power(.1) > Power(0) True >>> Power(0) < Power(.1) True >>> Power(.1) < Power(0) False >>> Power(0) == Power(0) True >>> Power(.1) == Power(.1) True >>> Power(0) != Power(None) True >>> Power(0) == Power(None) False >>> Power(None) == Power(None) True >>> Power(None) >= Power(-50) False >>> Power(None) < Power(-50) True >>> Power(None) <= Power(-50) True >>> Power(0) == None False >>> Power(None) == None True >>> Power(0) != None True >>> Power(None) == None True >>> Power(0) is None False >>> Power(None) is None False >>> Power(0) is not None True >>> Power(None) is not None True """ # Either we are None (not less than) or more than None. if other is None: return False mwatt = self.mwatt() try: return mwatt < other.mwatt() except AttributeError: return mwatt < Power(D(other)).mwatt() def __eq__(self, other): if other is None: return self.dBm is None mwatt = self.mwatt() try: return mwatt == other.mwatt() except AttributeError: return mwatt == Power(D(other)).mwatt() def __repr__(self): """ >>> Power(0) Power('0.000000') >>> Power(.01) Power('0.010000') >>> Power(None) Power(None) >>> Power("None") Power(None) """ if self.dBm is None: return "Power(None)" return "Power('{}')".format(str(self.dBm)) def __str__(self): """ >>> str(Power(0)) '0.00' """ if self.dBm is None: return "None" else: return "{:.2f}".format(self.dBm) def __truediv__(self, other): # pylint: disable=W0221 return self.__div__(other) def __floordiv__(self, other): # pylint: disable=W0221 return self.__div__(other) def __div__(self, divisor): """ >>> Power(0) / 16 / 6 Power('-19.822712') >>> Power(0) / 10 Power('-10.000000') >>> Power(0) / 8 Power('-9.030900') >>> Power(0) / 6 Power('-7.781513') >>> Power(0) / 4 Power('-6.020600') >>> Power(0) / 2 Power('-3.010300') >>> Power(3) / 2 Power('-0.010301') """ return Power.from_mwatt(self.mwatt() / divisor) def __mul__(self, multiplier): return Power.from_mwatt(self.mwatt() * multiplier) def __or__(self, other): """ # Check str() here it's very different >>> Power(0) | Power(0) Power('3.010300') """ return Power.from_mwatt(self.mwatt() + other.mwatt()) def add_gain(self, gain): # XXX hmm not sure about adding None to power. if self.dBm is None or gain is None: return Power(None) return Power(self.dBm + gain.dB) def get_delta(self, other): if self.dBm is None or other is None: return Gain(None) return Gain(other.dBm - self.dBm)
class Price(object): def __init__( self, price_value, precision=Decimal(".111111111111111")): # precision defaults to 15 if isinstance(price_value, Decimal): self.__value = price_value elif isinstance(price_value, float): self.__value = Decimal(price_value).quantize( precision, rounding=decimal.ROUND_HALF_UP) else: self.__value = Decimal(price_value) self.__hash = self.__value.__hash__() self.__float_value = float(self.__value) self.__precision = precision def better_than(self, other_price, side): """ Determines if this price is better than the passed in price. Returns True if it is and False if it isn't. The determination is made using the passed in side: *if side is bid then better than means a higher price *if side is ask then better than means a lower price *if side is other then there is no such thing as a better price so the return is False :param other_price: Price. the price you are comparing to :param side: Side. the side used for the comparison :return: boolean """ assert isinstance(side, Side), "side should be MarketObjects.Side.Side" # if this price is a bid it is better than otherPrice if greater than if side.is_bid(): return self > other_price # otherwise price is an ask and it is better than otherPrice if less than else: return self < other_price def better_or_same_as(self, other_price, side): """ Determines if this price is better than or same as the passed in price. Returns True if it is and False if it isn't. The determination is made using the passed in side: *if side is bid then better than means a higher price *if side is ask then better than means a lower price *if side is other then there is no such thing as a better price so the return is False :param other_price: Price. the price you are comparing to :param side: Side. the side used for the comparison :return: boolean """ return True if other_price == self else self.better_than( other_price, side) def worse_than(self, other_price, side): """ Determines if this price is worse than the passed in price. Returns True if it is and False if it isn't. The determination is made using the passed in side: *if side is bid then worse than means a lower price *if side is ask then worse than means a higher price *if side is other then there is no such thing as a worse price so the return is False :param other_price: Price. the price you are comparing to :param side: Side. the side used for the comparison :return: boolean """ assert isinstance( other_price, Price), "other_price should be MarketObjects.Price.Price" assert isinstance(side, Side), "side should be MarketObjects.Side.Side" # if this price is a bid it is worse than otherPrice if less than if side.is_bid(): return self < other_price # otherwise price is an ask and it is worse than otherPrice if greater than else: return self > other_price def worse_or_same_as(self, other_price, side): """ Determines if this price is worse than or same as the passed in price. Returns True if it is and False if it isn't. The determination is made using the passed in side: *if side is bid then worse than means a lower price *if side is ask then worse than means a higher price *if side is other then there is no such thing as a worse price so the return is False :param other_price: Price. the price you are comparing to :param side: Side. the side used for the comparison :return: boolean """ return True if other_price == self else self.worse_than( other_price, side) def ticks_behind(self, other_price, side, market): """ For bids it subtracts its own price from the other price and divides by mpi For asks it subtracts the other price from its own price and divides by mpi. The result is if the other price is a better price then we get a positive number and if the other price is a worse price, we get a negative number. If otherprice is none it returns None :param other_price: MarketObjects.Price.Price the price you are comparing too :param side: MMarketObjects.Side.Side side of the market the comparison is happening on :param market: MarketObjects.Market.Market :return: float Can be None """ if other_price is None: return None if side.is_bid(): ticks_behind = (other_price - self) / market.mpi() else: ticks_behind = (self - other_price) / market.mpi() return float(ticks_behind) def __hash__(self): return self.__hash def __repr__(self): return self.__value.__repr__() def __str__(self): return self.__value.__str__() def __lt__(self, other): if not isinstance(other, Price): raise TypeError(other, '< requires another Price object') else: return self.__value < other.__value def __le__(self, other): if not isinstance(other, Price): raise TypeError(other, '<= requires another Price object') else: return self.__value <= other.__value def __eq__(self, other): if other is None: return False elif isinstance(other, Price): return self.__value == other.__value else: return self.__value == Price(other).__value def __ne__(self, other): return not self == other def __gt__(self, other): if not isinstance(other, Price): raise TypeError(other, '> requires another Price object') else: return self.__value > other.__value def __ge__(self, other): if not isinstance(other, Price): raise TypeError(other, '>= requires another Price object') else: return self.__value >= other.__value def __bool__(self): return bool(self.__value) def __add__(self, other): if isinstance(other, float): value = self.__float_value + other else: if isinstance(other, Price): other = other.__value value = self.__value + other return self.__class__(value, precision=self.__precision) def __radd__(self, other): return self.__add__(other) def __sub__(self, other): if isinstance(other, float): value = self.__float_value - other else: if isinstance(other, Price): other = other.__value value = self.__value - other return self.__class__(value, precision=self.__precision) def __rsub__(self, other): return (-self).__add__(other) def __mul__(self, other): if isinstance(other, float): value = self.__float_value * other else: if isinstance(other, Price): raise TypeError( "Two prices cannot be multiplied" ) # just doesn't make sense. Why would someone do this? value = self.__value * other return self.__class__(value, precision=self.__precision) def __rmul__(self, other): return self.__mul__(other) def __div__(self, other): return self.__truediv__(other) def __truediv__(self, other): if isinstance(other, Price): # not really sure why someone would want to divide a price by another price raise TypeError("Two prices cannot be divided") else: if other == 0: raise ZeroDivisionError() if isinstance(other, float): value = self.__float_value / other else: value = self.__value / other return self.__class__(value, precision=self.__precision) def __floordiv__(self, other): if isinstance(other, Price): # not really sure why someone would want to divide a price by another price raise TypeError("Two prices cannot be divided") else: if other == 0: raise ZeroDivisionError() if isinstance(other, float): value = self.__float_value // other else: value = self.__value // other return self.__class__(value, precision=self.__precision) def __mod__(self, other): if isinstance(other, Price): # not really sure why someone would want to modulo a price by another price raise TypeError("modulo of two prices is not supported") if other == 0: raise ZeroDivisionError() if isinstance(other, float): value = self.__float_value % other else: value = self.__value % other return self.__class__(value, precision=self.__precision) def __divmod__(self, other): raise TypeError("divmod of a price is not supported") def __pow__(self, other): raise TypeError("pow of a price is not supported") def __neg__(self): return self.__class__(-self.__value) def __pos__(self): return self.__class__(+self.__value) def __abs__(self): return self.__class__(abs(self.__value)) def __int__(self): return int(self.__value) def __float__(self): return self.__float_value
class Threshold(object): """ A wrapper for a bucket threshold value. Ensures proper serialization between ElasticSearch and the JSON API endpoints. """ __slots__ = '_value' def __init__(self, value): if isinstance(value, Threshold): assert isinstance(value._value, Decimal) # Clone the other Threshold. self._value = value._value return elif isinstance(value, StringTypes): value = value.replace('_', '.') self._value = Decimal(value) def __str__(self): result = str(self._value) assert '_' not in result # Ensure that rounded values are always displayed with at least one # decimal, for aesthetics. if '.' not in result: return result + '.0' return result def __repr__(self): return "Threshold('" + str(self) + "')" def to_float(self): return float(self) def __float__(self): """ Convert the threshold to a floating point number, for comparisons. Note that this should NOT be converted back to a threshold, as there may be a loss of data by doing the round trip. """ return float(self._value) def __getattr__(self, attr): # Delegate everything (i.e, comparisons) to the actual Threshold # value. return getattr(self._value, attr) def __hash__(self): return self._value.__hash__() def __eq__(self, otter): if not isinstance(otter, Threshold): return False return self._value == otter._value def to_elasticsearch(self): """ Converts the threshold to a string, suitable for serialization as an ElasticSearch field name. Note that full stops ('.') are verbotten in ElasticSearch field names. """ str_value = str(self) assert isinstance(self._value, Decimal) assert str_value.count('.') == 1, 'Invalid decimal number' return str_value.replace('.', '_')