def calculateNthWeekdayOfYearOperator( year, nth, weekday ): ''' Monday = 1, etc., as per arrow, nth == -1 for last, etc.''' if isinstance( year, RPNDateTime ): year = year.year if nth > 0: firstDay = RPNDateTime( year, 1, 1 ) firstWeekDay = weekday - firstDay.isoweekday( ) + 1 if firstWeekDay < 1: firstWeekDay += 7 result = RPNDateTime( year, 1, firstWeekDay ).add( RPNMeasurement( nth - 1, 'week' ) ) result.setDateOnly( ) return result if nth < 0: lastDay = RPNDateTime( year, 12, 31 ) lastWeekDay = weekday - lastDay.isoweekday( ) if lastWeekDay > 0: lastWeekDay -= 7 lastWeekDay += 31 result = RPNDateTime( year, 12, lastWeekDay, dateOnly = True ).add( RPNMeasurement( ( nth + 1 ), 'week' ) ) result.setDateOnly( ) return result raise ValueError( '0th weekday makes no sense in this context' )
def getKSphereRadius(n, k): if k < 3: raise ValueError('the number of dimensions must be at least 3') if not isinstance(n, RPNMeasurement): return RPNMeasurement(n, 'meter') dimensions = n.getDimensions() if dimensions == {'length': 1}: return n if dimensions == {'length': int(k - 1)}: area = n.convertValue(RPNMeasurement(1, [{'meter': int(k - 1)}])) result = root( fdiv(fmul(area, gamma(fdiv(k, 2))), fmul(2, power(pi, fdiv(k, 2)))), fsub(k, 1)) return RPNMeasurement(result, [{'meter': 1}]) if dimensions == {'length': int(k)}: volume = n.convertValue(RPNMeasurement(1, [{'meter': int(k)}])) result = root( fmul(fdiv(gamma(fadd(fdiv(k, 2), 1)), power(pi, fdiv(k, 2))), volume), k) return RPNMeasurement(result, [{'meter': 1}]) raise ValueError( 'incompatible measurement type for computing the radius: ' + str(dimensions))
def getAzimuthAndAltitude( self, location=None, date=None ): if location and date: if isinstance( location, str ): location = getLocation( location ) location.observer.date = date.to( 'utc' ).format( ) self.object.compute( location.observer ) return RPNMeasurement( mpmathify( self.object.az ), 'radians' ), \ RPNMeasurement( mpmathify( self.object.alt ), 'radians' )
def validateLength(self, argument): if isinstance(argument, (complex, mpc, mpf, int, float)): self.validateReal(argument) argument = RPNMeasurement(argument, 'meter') elif isinstance(argument, RPNMeasurement): if argument.getDimensions() != {'length': 1}: raise ValueError('measurement argument must be a length') else: raise ValueError( f'\'type\' { type( argument ) } found, measurement (length) expected' ) return argument
def calculateWindChillOperator( measurement1, measurement2 ): ''' https://www.ibiblio.org/units/dictW.html ''' validUnitTypes = [ [ 'velocity', 'temperature' ], ] arguments = matchUnitTypes( [ measurement1, measurement2 ], validUnitTypes ) if not arguments: raise ValueError( '\'wind_chill\' requires velocity and temperature measurements' ) windSpeed = arguments[ 'velocity' ].convert( 'miles/hour' ).value temperature = arguments[ 'temperature' ].convert( 'degrees_F' ).value if windSpeed < 3: raise ValueError( '\'wind_chill\' is not defined for wind speeds less than 3 mph' ) if temperature > 50: raise ValueError( '\'wind_chill\' is not defined for temperatures over 50 degrees fahrenheit' ) result = fsum( [ 35.74, fmul( temperature, 0.6215 ), fneg( fmul( 35.75, power( windSpeed, 0.16 ) ) ), fprod( [ 0.4275, temperature, power( windSpeed, 0.16 ) ] ) ] ) # in case someone puts in a silly velocity if result < -459.67: result = -459.67 return RPNMeasurement( result, 'degrees_F' ).convert( arguments[ 'temperature' ].units )
def calculateMolarMass(n): result = 0 for atom in n: result = fadd(result, fmul(getAtomicWeight(atom), n[atom])) return RPNMeasurement(result, 'gram')
def performTrigOperation(i, operation): if isinstance(i, RPNMeasurement): value = mpf(i.convertValue(RPNMeasurement(1, {'radian': 1}))) else: value = i return operation(value)
def multiply(n, k): if isinstance(n, RPNMeasurement): return n.multiply(k) if isinstance(k, RPNMeasurement): return RPNMeasurement(n).multiply(k) return fmul(n, k)
def getDistanceFromSunOperator( arg1, arg2 ): validUnitTypes = [ [ 'body', 'datetime' ] ] arguments = matchUnitTypes( [ arg1, arg2 ], validUnitTypes ) if not arguments: raise ValueError( 'unexpected arguments' ) return RPNMeasurement( arguments[ 'body' ].getDistanceFromSun( arguments[ 'datetime' ] ), 'meters' )
def divide(n, k): if isinstance(n, RPNMeasurement): return n.divide(k) if isinstance(k, RPNMeasurement): return RPNMeasurement(n).divide(k) return fdiv(n, k)
def getTimeZoneOffset(value): if isinstance(value, str): try: tz = pytz.timezone(value) except: tz = pytz.timezone(getTimeZoneName(value)) else: tz = pytz.timezone(getTimeZoneName(value)) # compute the timezone's offset now = datetime.datetime.now() if tz: now1 = tz.localize(now) now2 = pytz.utc.localize(now) return RPNMeasurement((now2 - now1).total_seconds(), 'seconds') return RPNMeasurement(0, 'seconds')
def convertUnits(unit1, unit2): if isinstance(unit1, RPNGenerator): unit1 = list(unit1) if len(unit1) == 1: unit1 = unit1[0] if isinstance(unit2, RPNGenerator): unit2 = list(unit2) if len(unit2) == 1: unit2 = unit2[0] if isinstance(unit1, list): result = [] for unit in unit1: result.append(convertUnits(unit, unit2)) return result if not isinstance(unit1, RPNMeasurement): raise ValueError('cannot convert non-measurements') if isinstance(unit2, list): return unit1.convertValue(unit2) if isinstance(unit2, (str, RPNUnits)): measurement = RPNMeasurement(1, unit2) return RPNMeasurement(unit1.convertValue(measurement), unit2) if not isinstance(unit2, (list, str, RPNUnits, RPNMeasurement)): raise ValueError('cannot convert non-measurements') debugPrint('convertUnits') debugPrint('unit1:', unit1.getUnitTypes()) debugPrint('unit2:', unit2.getUnitTypes()) newValue = unit1.convertValue(unit2) debugPrint('*** value:', newValue) return RPNMeasurement(newValue, unit2.units)
def calculateAscensionThursdayOperator( year ): ''' I don't know why Ascension is 39 days after Easter instead of 40, but that's how the math works out. It's the 40th day of the Easter season. Or as John Wright says, "Catholics can't count." I think it stems from the Church being created before the number 0. ''' return RPNDateTime( *calculateEaster( year ).add( RPNMeasurement( 39, 'days' ) ).getYMD( ), dateOnly = True )
def getAngularSeparation( self, other, location, date ): if isinstance( location, str ): location = getLocation( location ) location.observer.date = date.to( 'utc' ).format( ) self.object.compute( location.observer ) other.object.compute( location.observer ) return RPNMeasurement( mpmathify( ephem.separation( ( self.object.az, self.object.alt ), ( other.object.az, other.object.alt ) ) ), 'radian' )
def getConstant( name ): if name not in g.constantOperators: raise ValueError( 'Invalid constant: ', name ) unit = g.constantOperators[ name ].unit value = g.constantOperators[ name ].value if unit == '': return mpmathify( value ) return RPNMeasurement( value, unit )
def getAngularSize( self, location=None, date=None ): if location and date: if isinstance( location, str ): location = getLocation( location ) location.observer.date = date.to( 'utc' ).format( ) self.object.compute( location.observer ) # I have no idea why size seems to return the value in arcseconds... that # goes against the pyephem documentation that it always uses radians for angles. return RPNMeasurement( mpmathify( fdiv( fmul( fdiv( self.object.size, 3600 ), pi ), 180 ) ), 'radian' )
def estimateOperator(measurement): if not isinstance(measurement, RPNMeasurement): raise TypeError('incompatible type for estimating') unitType = None dimensions = measurement.getDimensions() for key, basicUnitType in g.basicUnitTypes.items(): if dimensions == basicUnitType.dimensions: unitType = key break if unitType is None: return 'No estimates are available for this unit type' unitTypeInfo = g.basicUnitTypes[unitType] unit = RPNMeasurement(1, unitTypeInfo.baseUnit) value = RPNMeasurement(measurement.convertValue(unit), unit.units).value if not unitTypeInfo.estimateTable: return 'No estimates are available for this unit type (' + unitType + ').' matchingKeys = [key for key in unitTypeInfo.estimateTable if key <= value] if matchingKeys: estimateKey = max(matchingKeys) multiple = fdiv(value, estimateKey) return 'approximately ' + nstr( multiple, 3 ) + ' times ' + \ unitTypeInfo.estimateTable[ estimateKey ] else: estimateKey = min(key for key in unitTypeInfo.estimateTable) multiple = fdiv(estimateKey, value) return 'approximately ' + nstr( multiple, 3 ) + ' times smaller than ' + \ unitTypeInfo.estimateTable[ estimateKey ]
def applyNumberValueToUnit(number, term, constant): if isinstance(term, RPNUnits): value = RPNMeasurement(number, term) value = value.normalizeUnits() if value.units == RPNUnits('_null_unit'): value = mpmathify(1) elif constant: if g.constantOperators[term].unit: value = RPNMeasurement( fmul(number, mpmathify(g.constantOperators[term].value)), g.constantOperators[term].unit) else: value = fmul(number, g.constantOperators[term].value) else: if g.unitOperators[term].unitType == 'constant': value = RPNMeasurement(number, term).convertValue( RPNMeasurement(1, {'unity': 1})) else: value = RPNMeasurement(number, term) return value
def add(n, k): if isinstance(n, RPNDateTime) and isinstance(k, RPNMeasurement): return n.add(k) if isinstance(n, RPNMeasurement) and isinstance(k, RPNDateTime): return k.add(n) if isinstance(n, RPNMeasurement): return n.add(k) if isinstance(k, RPNMeasurement): return RPNMeasurement(n).add(k) return fadd(n, k)
def getKSphereVolume(n, k): if not isinstance(n, RPNMeasurement): return getKSphereVolume(RPNMeasurement(n, 'meter'), k) dimensions = n.getDimensions() m = n.value if dimensions == {'length': 1}: m = n.convertValue(RPNMeasurement(1, [{'meter': 1}])) result = fmul(fdiv(power(pi, fdiv(k, 2)), gamma(fadd(fdiv(k, 2), 1))), power(m, k)) return RPNMeasurement(result, [{'meter': k}]) if dimensions == {'length': int(k - 1)}: radius = getKSphereRadius(n, k) return getKSphereVolume(radius, k) if dimensions == {'length': int(k)}: return n raise ValueError('incompatible measurement type for computing the volume')
def getKSphereSurfaceArea(n, k): if not isinstance(n, RPNMeasurement): return getKSphereSurfaceArea(n, RPNMeasurement(n, 'meter')) dimensions = n.getDimensions() if dimensions == {'length': 1}: m = n.convertValue(RPNMeasurement(1, [{'meter': 1}])) result = fmul(fdiv(fmul(power(pi, fdiv(k, 2)), 2), gamma(fdiv(k, 2))), power(m, fsub(k, 1))) return RPNMeasurement(result, [{'meter': int(k - 1)}]) if dimensions == {'length': int(k - 1)}: return n if dimensions == {'length': int(k)}: radius = getKSphereRadius(n, k) return getKSphereSurfaceArea(radius, k) raise ValueError( 'incompatible measurement type for computing the surface area')
def subtract(n, k): if isinstance(n, RPNDateTime): return n.subtract(k) if isinstance(n, RPNMeasurement): if isinstance(k, RPNDateTime): return k.subtract(n) return n.subtract(k) if isinstance(k, RPNMeasurement): return RPNMeasurement(n).subtract(k) return fsub(n, k)
def getGeographicDistanceOperator(location1, location2): if isinstance(location1, str): location1 = getLocation(location1) if isinstance(location2, str): location2 = getLocation(location2) if not isinstance(location1, RPNLocation) or not isinstance( location2, RPNLocation): raise ValueError('two location arguments expected') distance = geodesic((location1.getLat(), location1.getLong()), (location2.getLat(), location2.getLong())).miles return RPNMeasurement(distance, [{'miles': 1}])
def calculateHeatIndexOperator( measurement1, measurement2 ): ''' https://en.wikipedia.org/wiki/Heat_index#Formula ''' # pylint: disable=invalid-name validUnitTypes = [ [ 'temperature', 'constant' ], ] arguments = matchUnitTypes( [ measurement1, measurement2 ], validUnitTypes ) if not arguments: raise ValueError( '\'heat_index\' requires a temperature measurement and the relative humidity in percent' ) T = arguments[ 'temperature' ].convert( 'degrees_F' ).value R = arguments[ 'constant' ] if T < 80: raise ValueError( '\'heat_index\' is not defined for temperatures less than 80 degrees fahrenheit' ) if R < 0.4 or R > 1.0: raise ValueError( '\'heat_index\' requires a relative humidity value ranging from 40% to 100%' ) R = fmul( R, 100 ) c1 = -42.379 c2 = 2.04901523 c3 = 10.14333127 c4 = -0.22475541 c5 = -6.83783e-3 c6 = -5.481717e-2 c7 = 1.22874e-3 c8 = 8.5282e-4 c9 = -1.99e-6 heatIndex = fsum( [ c1, fmul( c2, T ), fmul( c3, R ), fprod( [ c4, T, R ] ), fprod( [ c5, T, T ] ), fprod( [ c6, R, R ] ), fprod( [ c7, T, T, R ] ), fprod( [ c8, T, R, R ] ), fprod( [ c9, T, T, R, R ] ) ] ) return RPNMeasurement( heatIndex, 'fahrenheit' ).convert( arguments[ 'temperature' ].units )
def makeJulianTime( n ): if isinstance( n, RPNGenerator ): return makeJulianTime( list( n ) ) if len( n ) == 1: return RPNDateTime( n[ 0 ], 1, 1 ) result = RPNDateTime( n[ 0 ], 1, 1 ).add( RPNMeasurement( n[ 1 ] - 1, 'day' ) ) if len( n ) >= 3: result = result.replace( hour = n[ 2 ] ) if len( n ) >= 4: result = result.replace( minute = n[ 3 ] ) if len( n ) >= 5: result = result.replace( second = n[ 4 ] ) if len( n ) >= 6: result = result.replace( microsecond = n[ 5 ] ) return result
def getProduct( n ): if isinstance( n, RPNGenerator ): return getProduct( list( n ) ) if isinstance( n[ 0 ], ( list, RPNGenerator ) ): return [ getProduct( arg ) for arg in n ] if not n: return 0 if len( n ) == 1: return n[ 0 ] hasUnits = False for item in n: if isinstance( item, RPNMeasurement ): hasUnits = True break if hasUnits: result = RPNMeasurement( 1 ) for item in n: if isinstance( item, list ): return [ getProduct( arg ) for arg in item ] result = multiply( result, item ) return result if not n: return 0 if isinstance( n[ 0 ], list ): return [ getProduct( item ) for item in n ] return fprod( n )
def calculatePentecostSundayOperator( year ): return RPNDateTime( *( calculateEaster( year ).add( RPNMeasurement( 7, 'weeks' ) ).getYMD( ) ), dateOnly = True )
def calculateAdventOperator( year ): firstAdvent = getChristmasDay( year ).add( RPNMeasurement( -3, 'week' ) ) firstAdvent = firstAdvent.subtract( RPNMeasurement( getWeekday( firstAdvent ), 'day' ) ) return RPNDateTime( *firstAdvent.getYMD( ), dateOnly = True )
def calculateGoodFridayOperator( year ): '''2 days before Easter''' goodFriday = calculateEaster( year ).add( RPNMeasurement( -2, 'day' ) ) return RPNDateTime( *goodFriday.getYMD( ), dateOnly = True )
def calculateAshWednesdayOperator( year ): '''46 days before Easter (40 days, not counting Sundays)''' ashWednesday = calculateEaster( year ).add( RPNMeasurement( -46, 'day' ) ) return RPNDateTime( *ashWednesday.getYMD( ), dateOnly = True )