Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Trigonometric functions Series.sin/1, Series.cos/1 and Series.tan/1 #558

Merged
merged 3 commits into from Apr 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 27 additions & 0 deletions lib/explorer/backend/lazy_series.ex
Expand Up @@ -47,6 +47,12 @@ defmodule Explorer.Backend.LazySeries do
coalesce: 2,
cast: 2,
select: 3,

# Trigonometric functions
cos: 1,
sin: 1,
tan: 1,

# Window functions
cumulative_max: 2,
cumulative_min: 2,
Expand Down Expand Up @@ -597,6 +603,27 @@ defmodule Explorer.Backend.LazySeries do
Backend.Series.new(data, :float)
end

@impl true
def sin(%Series{} = series) do
data = new(:sin, [lazy_series!(series)])

Backend.Series.new(data, :float)
end

@impl true
def cos(%Series{} = series) do
data = new(:cos, [lazy_series!(series)])

Backend.Series.new(data, :float)
end

@impl true
def tan(%Series{} = series) do
data = new(:tan, [lazy_series!(series)])

Backend.Series.new(data, :float)
end

@impl true
def inspect(series, opts) do
alias Inspect.Algebra, as: A
Expand Down
6 changes: 6 additions & 0 deletions lib/explorer/backend/series.ex
Expand Up @@ -93,6 +93,12 @@ defmodule Explorer.Backend.Series do
@callback log(argument :: s, base :: float()) :: s
@callback exp(s) :: s

# Trigonometry

@callback cos(s) :: s
@callback sin(s) :: s
@callback tan(s) :: s

# Comparisons

@callback equal(s | valid_types(), s | valid_types()) :: s
Expand Down
8 changes: 8 additions & 0 deletions lib/explorer/polars_backend/expression.ex
Expand Up @@ -55,6 +55,9 @@ defmodule Explorer.PolarsBackend.Expression do
floor: 1,
ceil: 1,
select: 3,
sin: 1,
cos: 1,
tan: 1,
standard_deviation: 1,
subtract: 2,
sum: 1,
Expand All @@ -75,6 +78,11 @@ defmodule Explorer.PolarsBackend.Expression do
sample_frac: 5,
exp: 1,

# Trigonometric operations
cos: 1,
sin: 1,
tan: 1,

# Window operations
cumulative_max: 2,
cumulative_min: 2,
Expand Down
3 changes: 3 additions & 0 deletions lib/explorer/polars_backend/native.ex
Expand Up @@ -318,6 +318,9 @@ defmodule Explorer.PolarsBackend.Native do
def s_day_of_week(_s), do: err()
def s_to_date(_s), do: err()
def s_to_time(_s), do: err()
def s_sin(_s), do: err()
def s_cos(_s), do: err()
def s_tan(_s), do: err()

defp err, do: :erlang.nif_error(:nif_not_loaded)
end
11 changes: 11 additions & 0 deletions lib/explorer/polars_backend/series.ex
Expand Up @@ -296,6 +296,17 @@ defmodule Explorer.PolarsBackend.Series do
@impl true
def exp(%Series{} = s), do: Shared.apply_series(s, :s_exp, [])

# Trigonometry

@impl true
def sin(%Series{} = s), do: Shared.apply_series(s, :s_sin, [])

@impl true
def cos(%Series{} = s), do: Shared.apply_series(s, :s_cos, [])

@impl true
def tan(%Series{} = s), do: Shared.apply_series(s, :s_tan, [])

# Comparisons

@impl true
Expand Down
81 changes: 81 additions & 0 deletions lib/explorer/series.ex
Expand Up @@ -2411,6 +2411,87 @@ defmodule Explorer.Series do
def remainder(left, %Series{dtype: :integer} = right) when is_integer(left),
do: Shared.apply_series_impl(:remainder, [left, right])

@doc """
Computes the the sine of a number (in radians).
The resultant series is going to be of dtype `:float`, with values between 1 and -1.

## Supported dtype

* `:integer`
* `:float`

## Examples

iex> pi = :math.pi()
iex> s = [-pi * 3/2, -pi, -pi / 2, -pi / 4, 0, pi / 4, pi / 2, pi, pi * 3/2] |> Explorer.Series.from_list()
iex> Explorer.Series.sin(s)
#Explorer.Series<
Polars[9]
float [1.0, -1.2246467991473532e-16, -1.0, -0.7071067811865475, 0.0, 0.7071067811865475, 1.0, 1.2246467991473532e-16, -1.0]
>
"""
@doc type: :element_wise
@spec sin(series :: Series.t()) :: Series.t()
def sin(%Series{dtype: dtype} = series) when is_numeric_dtype(dtype),
do: Shared.apply_impl(series, :sin)

def sin(%Series{dtype: dtype}),
do: dtype_error("sin/1", dtype, [:float, :integer])

@doc """
Computes the the cosine of a number (in radians).
The resultant series is going to be of dtype `:float`, with values between 1 and -1.

## Supported dtype

* `:integer`
* `:float`

## Examples

iex> pi = :math.pi()
iex> s = [-pi * 3/2, -pi, -pi / 2, -pi / 4, 0, pi / 4, pi / 2, pi, pi * 3/2] |> Explorer.Series.from_list()
iex> Explorer.Series.cos(s)
#Explorer.Series<
Polars[9]
float [-1.8369701987210297e-16, -1.0, 6.123233995736766e-17, 0.7071067811865476, 1.0, 0.7071067811865476, 6.123233995736766e-17, -1.0, -1.8369701987210297e-16]
>
"""
@doc type: :element_wise
@spec cos(series :: Series.t()) :: Series.t()
def cos(%Series{dtype: dtype} = series) when is_numeric_dtype(dtype),
do: Shared.apply_impl(series, :cos)

def cos(%Series{dtype: dtype}),
do: dtype_error("cos/1", dtype, [:float, :integer])

@doc """
Computes the tangent of a number (in radians).
The resultant series is going to be of dtype `:float`.

## Supported dtype

* `:integer`
* `:float`

## Examples

iex> pi = :math.pi()
iex> s = [-pi * 3/2, -pi, -pi / 2, -pi / 4, 0, pi / 4, pi / 2, pi, pi * 3/2] |> Explorer.Series.from_list()
iex> Explorer.Series.tan(s)
#Explorer.Series<
Polars[9]
float [-5443746451065123.0, 1.2246467991473532e-16, -1.633123935319537e16, -0.9999999999999999, 0.0, 0.9999999999999999, 1.633123935319537e16, -1.2246467991473532e-16, 5443746451065123.0]
>
"""
@doc type: :element_wise
@spec tan(series :: Series.t()) :: Series.t()
def tan(%Series{dtype: dtype} = series) when is_numeric_dtype(dtype),
do: Shared.apply_impl(series, :tan)

def tan(%Series{dtype: dtype}),
do: dtype_error("tan/1", dtype, [:float, :integer])

defp basic_numeric_operation(
operation,
%Series{dtype: left_dtype} = left,
Expand Down
1 change: 1 addition & 0 deletions native/explorer/Cargo.toml
Expand Up @@ -57,6 +57,7 @@ features = [
"sort_multiple",
"temporal",
"to_dummies",
"trigonometry",
"is_in",
"streaming",
"strings",
Expand Down
21 changes: 21 additions & 0 deletions native/explorer/src/expressions.rs
Expand Up @@ -690,3 +690,24 @@ pub fn expr_ceil(expr: ExExpr) -> ExExpr {
let expr = expr.clone_inner();
ExExpr::new(expr.ceil())
}

#[rustler::nif]
pub fn expr_sin(expr: ExExpr) -> ExExpr {
let expr = expr.clone_inner();

ExExpr::new(expr.sin())
}

#[rustler::nif]
pub fn expr_cos(expr: ExExpr) -> ExExpr {
let expr = expr.clone_inner();

ExExpr::new(expr.cos())
}

#[rustler::nif]
pub fn expr_tan(expr: ExExpr) -> ExExpr {
let expr = expr.clone_inner();

ExExpr::new(expr.tan())
}
7 changes: 7 additions & 0 deletions native/explorer/src/lib.rs
Expand Up @@ -175,6 +175,10 @@ rustler::init!(
expr_quotient,
expr_remainder,
expr_subtract,
// trigonometric expressions
expr_cos,
expr_sin,
expr_tan,
// slice and dice expressions
expr_coalesce,
expr_format,
Expand Down Expand Up @@ -255,6 +259,7 @@ rustler::init!(
s_coalesce,
s_concat,
s_contains,
s_cos,
s_upcase,
s_day_of_week,
s_downcase,
Expand Down Expand Up @@ -332,12 +337,14 @@ rustler::init!(
s_sample_frac,
s_series_equal,
s_shift,
s_sin,
s_size,
s_slice,
s_slice_by_indices,
s_slice_by_series,
s_sort,
s_standard_deviation,
s_tan,
s_trim,
s_subtract,
s_sum,
Expand Down
18 changes: 18 additions & 0 deletions native/explorer/src/series.rs
Expand Up @@ -1213,3 +1213,21 @@ pub fn s_to_time(s: ExSeries) -> Result<ExSeries, ExplorerError> {

Ok(ExSeries::new(s1))
}

#[rustler::nif(schedule = "DirtyCpu")]
pub fn s_sin(s: ExSeries) -> Result<ExSeries, ExplorerError> {
let s1 = s.f64()?.apply(|o| o.sin()).into();
Ok(ExSeries::new(s1))
}

#[rustler::nif(schedule = "DirtyCpu")]
pub fn s_cos(s: ExSeries) -> Result<ExSeries, ExplorerError> {
let s1 = s.f64()?.apply(|o| o.cos()).into();
Ok(ExSeries::new(s1))
}

#[rustler::nif(schedule = "DirtyCpu")]
pub fn s_tan(s: ExSeries) -> Result<ExSeries, ExplorerError> {
let s1 = s.f64()?.apply(|o| o.tan()).into();
Ok(ExSeries::new(s1))
}
23 changes: 23 additions & 0 deletions test/explorer/data_frame_test.exs
Expand Up @@ -1289,6 +1289,29 @@ defmodule Explorer.DataFrameTest do
}
end

test "add columns with trignometric functions" do
pi = :math.pi()
df = DF.new(a: [0, pi / 2, pi])

df1 = DF.mutate(df, b: sin(a), c: cos(a), d: tan(a))

assert DF.to_columns(df1, atom_keys: true) == %{
a: [0, pi / 2, pi],
b: [0, 1, 1.2246467991473532e-16],
c: [1.0, 6.123233995736766e-17, -1.0],
d: [0.0, 1.633123935319537e16, -1.2246467991473532e-16]
}

assert df1.names == ["a", "b", "c", "d"]

assert df1.dtypes == %{
"a" => :float,
"b" => :float,
"c" => :float,
"d" => :float
}
end

test "raises when adding eager series" do
df = DF.new(a: [1, 2, 3])
series = Series.from_list([4, 5, 6])
Expand Down
38 changes: 38 additions & 0 deletions test/explorer/series_test.exs
Expand Up @@ -2298,6 +2298,44 @@ defmodule Explorer.SeriesTest do
end
end

describe "sin/1" do
test "calculates the sine of all elements in the series" do
pi = :math.pi()
s = Explorer.Series.from_list([0, pi / 2, pi, 2 * pi])

series = Series.sin(s)

assert Series.to_list(series) == [0.0, 1.0, 1.2246467991473532e-16, -2.4492935982947064e-16]
end
end

describe "cos/1" do
test "calculates the cosine of all elements in the series" do
pi = :math.pi()
s = Explorer.Series.from_list([0, pi / 2, pi, 2 * pi])

series = Series.cos(s)

assert Series.to_list(series) == [1.0, 6.123233995736766e-17, -1.0, 1.0]
end
end

describe "tan/1" do
test "calculates the tangent of all elements in the series" do
pi = :math.pi()
s = Explorer.Series.from_list([0, pi / 2, pi, 2 * pi])

series = Series.tan(s)

assert Series.to_list(series) == [
0.0,
1.633123935319537e16,
-1.2246467991473532e-16,
-2.4492935982947064e-16
]
end
end

describe "format/1" do
test "with two string series" do
s1 = Series.from_list(["a", "b"])
Expand Down