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"},
"sin(F)" => {&:math.sin/1, "F'x*cos(F)", nil},
"cos(F)" => {&:math.cos/1, "-F'x*sin(F)", nil},
"tan(F)" => {&:math.tan/1, "F'x/cos(F)^2", nil},
"acos(F)" => {&:math.acos/1, "-F'x/(1-F^2)^0.5", nil},
"asin(F)" => {&:math.asin/1, "F'x/(1-F^2)^0.5", nil},
"atan(F)" => {&:math.atan/1, "F'x/(1+F^2)", nil},
"sinh(F)" => {&:math.sinh/1, "F'x*cosh(F)", nil},
"cosh(F)" => {&:math.cosh/1, "F'x*sinh(F)", nil},
"tanh(F)" => {&:math.tanh/1, "F'x/cosh(F)^2", nil},
"asinh(F)" => {&:math.asinh/1, "F'x/(F^2+1)^0.5", nil},
"acosh(F)" => {&:math.acosh/1, "F'x/(F^2-1)^0.5", nil},
"atanh(F)" => {&:math.atanh/1, "F'x/(1-F^2)", nil}
```
"""
# name => Numeric implementation, Derivate, Integrate
def base,
do: %{
"ln(F)" => {&:math.log/1, "F'x/F", "x*ln(F)-$(x/F),x"},
"sin(F)" => {&:math.sin/1, "F'x*cos(F)", nil},
"cos(F)" => {&:math.cos/1, "-F'x*sin(F)", nil},
"tan(F)" => {&:math.tan/1, "F'x/cos(F)^2", nil},
"acos(F)" => {&:math.acos/1, "-F'x/(1-F^2)^0.5", nil},
"asin(F)" => {&:math.asin/1, "F'x/(1-F^2)^0.5", nil},
"atan(F)" => {&:math.atan/1, "F'x/(1+F^2)", nil},
"sinh(F)" => {&:math.sinh/1, "F'x*cosh(F)", nil},
"cosh(F)" => {&:math.cosh/1, "F'x*sinh(F)", nil},
"tanh(F)" => {&:math.tanh/1, "F'x/cosh(F)^2", nil},
"asinh(F)" => {&:math.asinh/1, "F'x/(F^2+1)^0.5", nil},
"acosh(F)" => {&:math.acosh/1, "F'x/(F^2-1)^0.5", nil},
"atanh(F)" => {&:math.atanh/1, "F'x/(1-F^2)", nil}
}
@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)"
}
@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))}
true -> {:fcall, name, args}
end
(cfunc = compounds()[name <> "(F)"]) != nil ->
{ast, _ctx} = Exun.parse(cfunc)
replace_args_internal(ast, args, {:vari, "x"})
true ->
{:fcall, name, args}
end
end
@doc """
Replace arguments on ast as specified. Not intended for
external use, mus be public for now
"""
def replace_args_internal(ast, args, vari) do
case ast do
{:vari, "F"} ->
args |> List.first()
{:vari, "x"} ->
vari
{:fcall, name, [{:vari, "F"}]} ->
{:fcall, name, args}
{:unit, uv, ut} ->
{:unit, replace_args_internal(uv, args, vari), ut}
{{:m, op}, lst} ->
{{:m, op}, Enum.map(lst, &replace_args_internal(&1, args, vari))}
{:minus, a} ->
{:minus, replace_args_internal(a, args, vari)}
{op, l, r} ->
{op, replace_args_internal(l, args, vari), replace_args_internal(r, args, vari)}
_ ->
ast
end
end
defp allargs_numbers(args) do
Enum.reduce(args, true, fn el, ac ->
ac and elem(el, 0) == :numb
end)
end
@doc """
For convenience, creates ast {{:m,:mult},[a,b]}
"""
def mult(a, b), do: mcompose(:mult, a, b)
@doc """
For convenience, creates ast {{:m,:mult},[a,b^-1]}
"""
def divi(a, b), do: mult(a, Exun.Math.chpow(b))
@doc """
For convenience, creates ast {{:m,:suma},[a,b]}
"""
def suma(a, b), do: mcompose(:suma, a, b)
@doc """
For convenience, creates ast {{:m,:mult},[a,-b]}
"""
def rest(a, b), do: suma(a, Exun.Math.chsign(b))
defp mcompose(op, a, b) do
case {a, b} do
{{{:m, ^op}, l1}, {{:m, ^op}, l2}} ->
Exun.Collect.coll({{:m, op}, l1 ++ l2})
{{{:m, ^op}, l1}, _} ->
Exun.Collect.coll({{:m, op}, [b | l1]})
{_, {{:m, ^op}, l2}} ->
Exun.Collect.coll({{:m, op}, [a | l2]})
_ ->
Exun.Collect.coll({{:m, op}, [a, b]})
end
end
@doc """
Looks in compounds for a match of ast and return it.
Thw first wins.
"""
def revert_compounds(ast) do
matches =
compounds()
|> Enum.map(fn {k, v} ->
#Compile value
{vast, _} = Exun.parse(v, %{})
#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 |> List.first()
if match != nil do
{tk, {:ok, map}} = match
{atk, _} = Exun.parse(tk, %{})
replace_args_internal(atk, [Map.fetch!(map, {:vari, "F"})], nil)
else
nil
end
end
end