Skip to content

Commit

Permalink
Math, trigonometric and geometric functions added
Browse files Browse the repository at this point in the history
  • Loading branch information
xmnlab committed Apr 11, 2018
1 parent 5a06f30 commit bda8e3c
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 25 deletions.
64 changes: 64 additions & 0 deletions ibis/mapd/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,70 @@ operations
(http://docs.ibis-project.org/design.html#the-node-class).


Creating new expression: To create new expressions it is necessary to do these
steps:

1. create a new class
2. create a new function and assign it to a DataType
3. create a compiler function to this new function and assign it to the compiler
translator

A new Class database function seems like this (`my_backend_operations.py`):

.. code-block:: Python
class MyNewFunction(ops.UnaryOp):
"""My new class function"""
output_type = rlz.shape_like('arg', 'float')
After create the new class database function, the follow step is create a
function and assign it to the DataTypes allowed to use it:

.. code-block:: Python
def my_new_function(numeric_value):
return MyNewFunction(numeric_value).to_expr()
NumericValue.sin = sin
Also, it is necessary register the new function:

.. code-block:: Python
# if it necessary define the fixed_arity function
def fixed_arity(func_name, arity):
def formatter(translator, expr):
op = expr.op()
arg_count = len(op.args)
if arity != arg_count:
msg = 'Incorrect number of args {0} instead of {1}'
raise com.UnsupportedOperationError(
msg.format(arg_count, arity)
)
return _call(translator, func_name, *op.args)
return formatter
_operation_registry.update({
MyNewFunction: fixed_arity('my_new_function', 1)
})
Now, it just need a compiler function to translate the function to a SQL code
(my_backend/compiler.py):

.. code-block:: Python
compiles = MyBackendExprTranslator.compiles
@compiles(MyNewFunction)
def compile_my_new_function(translator, expr):
# pull out the arguments to the expression
arg, = expr.op().args
# compile the argument
compiled_arg = translator.translate(arg)
return 'my_new_function(%s)' % compiled_arg
identifiers
-----------

Expand Down
12 changes: 1 addition & 11 deletions ibis/mapd/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import ibis.sql.compiler as compiles

from .operations import (
_operation_registry, _name_expr, Sin
_operation_registry, _name_expr, _trigonometric_ops
)


Expand Down Expand Up @@ -194,13 +194,3 @@ class MapDDialect(compiles.Dialect):
dialect = MapDDialect
compiles = MapDExprTranslator.compiles
rewrites = MapDExprTranslator.rewrites


@compiles(Sin)
def compile_sin(translator, expr):
# pull out the arguments to the expression
arg, = expr.op().args

# compile the argument
compiled_arg = translator.translate(arg)
return 'sin(%s)' % compiled_arg
81 changes: 67 additions & 14 deletions ibis/mapd/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import ibis.expr.operations as ops
import ibis.sql.transforms as transforms

from ibis.expr.types import NumericValue
from ibis.expr.types import NumericValue, StringValue


def _cast(translator, expr):
Expand Down Expand Up @@ -513,22 +513,60 @@ def output_type(self):
return ops.dt.float64.scalar_type()


class Log(ops.Ln):
"""
"""


class Mod(ops.Modulus):
"""
"""


class Radians(ops.UnaryOp):
"""Converts radians to degrees"""
arg = ops.Arg(rlz.floating)
output_type = rlz.shape_like('arg', ops.dt.float)


class Sign(ops.UnaryOp):
"""
Returns the sign of x as -1, 0, 1 if x is negative, zero, or positive
"""
arg = ops.Arg(rlz.numeric)
output_type = rlz.shape_like('arg', ops.dt.int8)


class Truncate(ops.NumericBinaryOp):
"""Truncates x to y decimal places"""
output_type = rlz.shape_like('left', ops.dt.float)


# STATS
"""
class Correlation(x, y) CORRELATION_FLOAT(x, y) Alias of CORR. Returns the coefficient of correlation of a set of number pairs.
class CORR(x, y) CORR_FLOAT(x, y) Returns the coefficient of correlation of a set of number pairs.
class COVAR_POP(x, y) COVAR_POP_FLOAT(x, y) Returns the population covariance of a set of number pairs.
class COVAR_SAMP(x, y) COVAR_SAMP_FLOAT(x, y) Returns the sample covariance of a set of number pairs.
ops.StandardDev: agg_variance_like('stddev'),
# STDDEV(x) STDDEV_FLOAT(x) Alias of STDDEV_SAMP. Returns sample standard deviation of the value.
# STDDEV_POP(x) STDDEV_POP_FLOAT(x) Returns the population standard the standard deviation of the value.
# STDDEV_SAMP(x) STDDEV_SAMP_FLOAT(x) Returns the sample standard deviation of the value.
ops.Variance: agg_variance_like('var'),
# VARIANCE(x) VARIANCE_FLOAT(x) Alias of VAR_SAMP. Returns the sample variance of the value.
# VAR_POP(x) VAR_POP_FLOAT(x) Returns the population variance sample variance of the value.
# VAR_SAMP(x) VAR_SAMP_FLOAT(x) Returns the sample variance of the value.
"""


# TRIGONOMETRY

class TrigonometryUnary(ops.UnaryOp):
"""Trigonometry base unary"""
def output_type(self):
return ops.dt.float64.scalar_type()
output_type = rlz.shape_like('arg', 'float')


class TrigonometryBinary(ops.BinaryOp):
Expand Down Expand Up @@ -569,7 +607,7 @@ class Tan(TrigonometryUnary):

# GEOMETRIC

class DistanceInMeters(ops.ValueOp):
class Distance_In_Meters(ops.ValueOp):
"""
Calculates distance in meters between two WGS-84 positions.
Expand All @@ -578,20 +616,22 @@ class DistanceInMeters(ops.ValueOp):
fromLat = ops.Arg(rlz.column(rlz.numeric))
toLon = ops.Arg(rlz.column(rlz.numeric))
toLat = ops.Arg(rlz.column(rlz.numeric))
output_type = rlz.shape_like('arg', ops.dt.float)
output_type = rlz.shape_like('fromLon', ops.dt.float)


class Conv_4326_900913_X(ops.UnaryOp):
"""
Converts WGS-84 latitude to WGS-84 Web Mercator x coordinate.
"""
output_type = rlz.shape_like('arg', ops.dt.float)


class Conv_4326_900913_Y(ops.UnaryOp):
"""
Converts WGS-84 longitude to WGS-84 Web Mercator y coordinate.
"""
output_type = rlz.shape_like('arg', ops.dt.float)


# String
Expand Down Expand Up @@ -651,15 +691,15 @@ class StringLengthBytes(ops.UnaryOp):
Degrees: unary('degrees'), # MapD function
ops.Exp: unary('exp'),
ops.Floor: unary('floor'),
ops.Log: unary('log'),
Log: unary('log'), # MapD Log wrap to IBIS Ln
ops.Ln: unary('ln'),
ops.Log10: unary('log10'),
ops.Modulus: unary('mod'),
PI: lambda *args: 'pi()',
Mod: fixed_arity('mod', 2), # MapD Mod wrap to IBIS Modulus
# PI: fixed_arity('pi', 0), # check another option to use it
# ops.Power: binary('power'), # TODO: check if it is necessary
Radians: unary('radians'),
ops.Round: _round,
ops.Sign: _sign,
# ops.Round: _round,
Sign: _sign,
ops.Sqrt: unary('sqrt'),
Truncate: fixed_arity('truncate', 2)
}
Expand Down Expand Up @@ -692,7 +732,7 @@ class StringLengthBytes(ops.UnaryOp):
}

_geometric_ops = {
DistanceInMeters: fixed_arity('distance_in_meters', 4),
Distance_In_Meters: fixed_arity('distance_in_meters', 4),
Conv_4326_900913_X: unary('conv_4326_900913_x'),
Conv_4326_900913_Y: unary('conv_4326_900913_y')
}
Expand Down Expand Up @@ -782,8 +822,21 @@ class StringLengthBytes(ops.UnaryOp):
# _operation_registry.update(_unsupported_ops)


def sin(numeric_value):
return Sin(numeric_value).to_expr()
def assign_function_to_dtype(dtype, function_ops: dict):
# generate trigonometric function for NumericValue class
for klass in function_ops.keys():
# skip if the class is already in the ibis operations
if klass in ops.__dict__.values():
continue

def f(_klass):
return lambda *args: _klass(*args).to_expr()
setattr(
dtype, klass.__name__.lower(), f(klass)
)


NumericValue.sin = sin
assign_function_to_dtype(NumericValue, _trigonometric_ops)
assign_function_to_dtype(NumericValue, _math_ops)
assign_function_to_dtype(StringValue, _string_ops)
assign_function_to_dtype(NumericValue, _geometric_ops)

0 comments on commit bda8e3c

Please sign in to comment.