Current section
Files
Jump to
Current section
Files
lib/exun_fun.ex
defmodule Exun.Fun do
@moduledoc """
Function management.
@base and @compound are the definitions of external functions.
"""
@doc """
base is a map that holds functions names and a tupla
For example:
```
"ln(F)" => {&:math.log/1, "F'x/F", "x*ln(F)-$(x/F),x"}
```
- ln(F) : means function name is 'ln' and can receive Functions as argument
- &:math.log/1 : Is the Elixir numeric function for ln
- F'x/F : is the symbolic derivative of ln, deriv(F,x)/F
- x*ln(F)-$(x/F),x : is the symbolic integral for ln
For now the definitions are
```
"ln(F)" => {&:math.log/1, "F'x/F", "x*ln(F)-$(x/F),x", "exp"},
"exp(F)" => {&:math.exp/1, "F'x*exp(F)", nil, "ln"},
"sin(F)" => {&:math.sin/1, "F'x*cos(F)", nil, "asin"},
"cos(F)" => {&:math.cos/1, "-F'x*sin(F)", nil, "acos"},
"tan(F)" => {&:math.tan/1, "F'x/cos(F)^2", nil, "atan"},
"acos(F)" => {&:math.acos/1, "-F'x/(1-F^2)^0.5", nil, "cos"},
"asin(F)" => {&:math.asin/1, "F'x/(1-F^2)^0.5", nil, "sin"},
"atan(F)" => {&:math.atan/1, "F'x/(1+F^2)", nil, "tan"},
"sinh(F)" => {&:math.sinh/1, "F'x*cosh(F)", nil, "asinh"},
"cosh(F)" => {&:math.cosh/1, "F'x*sinh(F)", nil, "acosh"},
"tanh(F)" => {&:math.tanh/1, "F'x/cosh(F)^2", nil, "atanh"},
"asinh(F)" => {&:math.asinh/1, "F'x/(F^2+1)^0.5", nil, "sinh"},
"acosh(F)" => {&:math.acosh/1, "F'x/(F^2-1)^0.5", nil, "cosh"},
"atanh(F)" => {&:math.atanh/1, "F'x/(1-F^2)", nil, "tanh"}
```
"""
# name => Numeric implementation, Derivate, Integrate
def base,
do: %{
"ln(F)" => {&:math.log/1, "F'x/F", "x*ln(F)-$(x/F),x", "exp"},
"exp(F)" => {&:math.exp/1, "F'x*exp(F)", nil, "ln"},
"sin(F)" => {&:math.sin/1, "F'x*cos(F)", nil, "asin"},
"cos(F)" => {&:math.cos/1, "-F'x*sin(F)", nil, "acos"},
"tan(F)" => {&:math.tan/1, "F'x/cos(F)^2", nil, "atan"},
"acos(F)" => {&:math.acos/1, "-F'x/(1-F^2)^0.5", nil, "cos"},
"asin(F)" => {&:math.asin/1, "F'x/(1-F^2)^0.5", nil, "sin"},
"atan(F)" => {&:math.atan/1, "F'x/(1+F^2)", nil, "tan"},
"sinh(F)" => {&:math.sinh/1, "F'x*cosh(F)", nil, "asinh"},
"cosh(F)" => {&:math.cosh/1, "F'x*sinh(F)", nil, "acosh"},
"tanh(F)" => {&:math.tanh/1, "F'x/cosh(F)^2", nil, "atanh"},
"asinh(F)" => {&:math.asinh/1, "F'x/(F^2+1)^0.5", nil, "sinh"},
"acosh(F)" => {&:math.acosh/1, "F'x/((F-1)^0.5*(F+1)^0.5)", nil, "cosh"},
"atanh(F)" => {&:math.atanh/1, "F'x/(1-F^2)", nil, "tanh"}
}
@doc """
Compounds is a map that holds synthetic definitions. Function
revert_compounds is able to look in an ast and identify this list,
substituting the values.
For now, the definitions are:
```
"sqrt(F)" => "F^0.5",
"tan(F)" => "sin(F)/cos(F)"
```
"""
def compounds,
do: %{
"sqrt(F)" => "F^0.5",
"tan(F)" => "sin(F)/cos(F)",
"tanh(F)" => "sinh(F)/cosh(F)"
}
@doc """
Finds and exec a function call. Search in base and compounds, if cannot find any
returns a symbolic fcall.
"""
def fcall(name, args) do
# IO.inspect({name, args}, label: "fcall")
aau = allargs_numbers(args)
cond do
(bfunc = base()[name <> "(F)"]) != nil ->
cond do
aau -> {:numb, elem(bfunc, 0).(args |> List.first() |> elem(1)), 1}
true -> {:fcall, name, args}
end
(cfunc = compounds()[name <> "(F)"]) != nil ->
ast = Exun.new(cfunc).ast
mapdef = %{{:vari, "F"} => args |> List.first(), {:vari, "x"} => {:vari, "x"}}
Exun.replace(ast, mapdef)
true ->
{:fcall, name, args}
end
end
defp allargs_numbers(args) do
Enum.reduce(args, true, fn el, ac ->
ac and elem(el, 0) == :numb
end)
end
@doc """
Looks in compounds for a match of ast and return it.
Thw first wins.
"""
def revert_compounds(ast) do
matches =
Enum.map(compounds(), fn {k, v} ->
# Compile value
vast = Exun.new(v).ast
# Match, get the first match
res = Exun.Pattern.match_ast(vast, ast, [], false)
{k, res |> List.first()}
end) |> Enum.reject(fn {_, v} -> v == nil end)
match =
matches
|> Enum.reject(fn {_, {:ok,map}} ->
# Have to be same fcall names
Enum.reduce(map, true, fn {key, value}, res ->
case {key, value} do
{{:fcall, n1, _}, {:fcall, n2, _}} ->
res and n1 != n2
_ ->
res
end
end)
end)
|> List.first()
if match != nil do
{tk, {:ok, map}} = match
atk = Exun.new(tk, %{}).ast
Exun.replace(atk, map)
else
nil
end
end
@doc """
returns true if ast is a polynomial element on v
"""
def is_poly(ast, v) do
case ast do
{:numb, _, _} -> true
{:unit, _, _} -> true
{:vari, _} -> true
{:fcall, _, args} -> not Enum.any?(args, &contains(&1, v))
{:minus, a} -> is_poly(a, v)
{:deriv, f, _} -> not contains(f, v)
{:integ, f, _} -> not contains(f, v)
{{:m, :suma}, list} -> Enum.all?(list, &is_poly(&1, v))
{{:m, :mult}, list} -> Enum.all?(list, &is_poly(&1, v))
{:elev, _, b} -> not contains(b, v)
end
end
@doc """
returns true if ast contains variable vc
"""
def contains(ast, vc) do
case ast do
{:numb, _, _} -> false
{:unit, _, _} -> false
v = {:vari, _} -> v == vc
{:fcall, _, args} -> Enum.any?(args, &contains(&1, vc))
{:minus, a} -> contains(a, vc)
{:deriv, f, _} -> contains(f, vc)
{:integ, f, _} -> contains(f, vc)
{{:m, :suma}, list} -> Enum.any?(list, &contains(&1, vc))
{{:m, :mult}, list} -> Enum.any?(list, &contains(&1, vc))
{:elev, a, b} -> contains(a, vc) or contains(b, vc)
end
end
def finv(name) do
case base()["#{name}(F)"] do
{_, _, _, r} -> r
other -> other
end
end
end