Current section
Files
Jump to
Current section
Files
lib/exun_simpl.ex
defmodule Exun.Simpl do
import Exun.MProc
import Exun.Math
alias Exun.Collect
alias Exun.Unit
alias Exun.Eq
@zero {:numb, 0}
@uno {:numb, 1}
@muno {:numb, -1}
@dos {:numb, 2}
@invalid_unit_operation "Inconsistent unit operation"
@moduledoc """
Simplify expressions
"""
def mkrec(tree) do
ntree = mk(tree)
if Eq.eq(ntree, tree),
do: ntree,
else: mkrec(ntree)
end
# simplify
defp mk({:numb, n}), do: if(floor(n) == n, do: {:numb, floor(n)}, else: {:numb, n})
defp mk({:unit, val, {:numb, 1}}), do: mk(val)
defp mk({:unit, val, ut}), do: Unit.toSI({:unit, mk(val), mk(ut)})
defp mk({:suma, a, @zero}), do: mk(a)
defp mk({:suma, @zero, a}), do: mk(a)
defp mk({:suma, {:numb, n1}, {:numb, n2}}), do: {:numb, n1 + n2}
defp mk({:suma, {:numb, _}, {:unit, _, _}}), do: throw(@invalid_unit_operation)
defp mk({:suma, {:unit, _, _}, {:numb, _}}), do: throw(@invalid_unit_operation)
defp mk({:suma, u1 = {:unit, _, _}, u2 = {:unit, _, _}}) do
case Unit.sum(:suma, u1, u2, %{}) do
{:ok, res} -> res
{:err, msg} -> throw(msg)
end
end
defp mk({:rest, {:numb, n1}, {:numb, n2}}), do: {:numb, n1 - n2}
defp mk({:rest, {:numb, _}, {:unit, _, _}}), do: throw(@invalid_unit_operation)
defp mk({:rest, {:unit, _, _}, {:numb, _}}), do: throw(@invalid_unit_operation)
defp mk({:rest, u1 = {:unit, _, _}, u2 = {:unit, _, _}}) do
case Unit.sum(:rest, u1, u2, %{}) do
{:ok, res} -> res
{:err, msg} -> throw(msg)
end
end
defp mk({:mult, _, @zero}), do: @zero
defp mk({:mult, @zero, _}), do: @zero
defp mk({:mult, @uno, a}), do: mk(a)
defp mk({:mult, a, @uno}), do: mk(a)
defp mk({:mult, a, a}), do: {:elev, mk(a), @dos}
defp mk({:mult, a, {:divi, @uno, b}}), do: parallel({:divi, mk(a), mk(b)})
defp mk({:mult, {:divi, @uno, b}, a}), do: parallel({:divi, mk(a), mk(b)})
defp mk({:mult, @muno, {:suma, a, {:mult, @muno, b}}}), do: {:rest, b, a}
defp mk({:mult, {:suma, a, {:mult, @muno, b}}, @muno}), do: {:rest, b, a}
defp mk({:mult, {:elev, a, e1}, {:elev, a, e2}}),
do: parallel({:elev, mk(a), mk({:suma, mk(e1), mk(e2)})})
defp mk({:mult, {:numb, n1}, {:numb, n2}}), do: {:numb, n1 * n2}
defp mk({:mult, {:unit, n2, a}, n = {:numb, _n1}}), do: {:unit, mk({:mult, n2, n}), mk(a)}
defp mk({:mult, {:numb, n1}, {:unit, n2, a}}), do: {:unit, mk({:mult, {:numb, n1}, n2}), mk(a)}
defp mk({:mult, {:unit, n1, a1}, {:unit, n2, a2}}),
do: {:unit, mk({:mult, n1, n2}), mk({:mult, a1, a2})}
defp mk({:divi, _, @zero}), do: throw("Division by 0")
defp mk({:divi, @zero, _}), do: @zero
defp mk({:divi, a, @uno}), do: mk(a)
defp mk({:divi, a, a}) when a != @zero, do: @uno
defp mk({:divi, {:numb, n1}, {:numb, n2}}), do: {:numb, n1 / n2}
defp mk({:divi, n = {:numb, _}, {:unit, nu, a}}), do: {:unit, mk({:divi, n, nu}), mk(chpow(a))}
defp mk({:divi, {:unit, nu, a}, n = {:numb, _}}), do: {:unit, mk({:divi, nu, n}), mk(a)}
defp mk({:divi, {:elev, a, e1}, {:elev, a, e2}}),
do: {:elev, mk(a), mk({:rest, mk(e1), mk(e2)})}
defp mk({:divi, {:elev, a, e1}, a}),
do: parallel({:elev, mk(a), mk({:rest, mk(e1), @uno})})
defp mk({:divi, a, {:elev, a, e1}}),
do: {:divi, @uno, mk({:rest, mk(e1), @uno})}
defp mk({:divi, {:unit, n1, a1}, {:unit, n2, a2}}),
do: {:unit, mk({:divi, n1, n2}), mk({:divi, a1, a2})}
defp mk({:elev, _, @zero}), do: @uno
defp mk({:elev, a, @uno}), do: mk(a)
defp mk({:elev, @uno, _}), do: @uno
defp mk({:elev, {:elev, base, e1}, e2}), do: parallel({:elev, mk(base), mk({:mult, e1, e2})})
defp mk({:elev, {:numb, base}, {:numb, exp}}), do: {:numb, :math.pow(base, exp)}
defp mk({:elev, {:unit, uv, ut}, expon}),
do: {:unit, mk({:elev, uv, expon}), mk({:elev, ut, expon})}
defp mk({{:m, op}, lst}) when op in [:suma, :mult] and is_list(lst) do
# Remove zeroes or ones, 0+any=any, 1*any=any
unity = if op == :suma, do: @zero, else: @uno
lst =
Enum.reject(lst, &(&1 == unity))
|> Enum.map(&mk/1)
# if a multiple mult {:m,:mult} check if zero is a component
cond do
op == :mult and @zero in lst ->
@zero
true ->
case length(lst) do
0 ->
unity
1 ->
List.first(lst)
_ ->
{pivot, base, counts} = get_base(op, lst)
case counts do
1 ->
{{:m, op}, lst}
_ ->
isol = get_isol(base, lst)
coefs = get_coefs(isol)
rest = get_rest(isol)
isolp =
case op do
:suma ->
{:mult, pivot, {{:m, :suma}, coefs}}
:mult ->
{:elev, pivot, {{:m, :suma}, coefs}}
end
case length(rest) do
0 -> isolp
_ -> {{:m, op}, [isolp | rest] |> Enum.sort()}
end
end
end
end
end
defp mk({:fcall, name, lst}) when is_list(lst) do
args = Enum.map(lst, &Collect.coll/1)
Exun.Fun.fcall(name, args)
end
defp mk({:deriv, a, {:vari, x}}), do: Exun.Der.deriv(mk(a), x)
defp mk({:integ, f, v = {:vari, _}}), do: Exun.Integral.integ(mk(f), v)
defp mk({op, a, b}), do: {op, mk(a), mk(b)}
# Fallthrough
defp mk(tree) do
tree
end
defp get_isol(base, lst) do
List.zip([lst, base])
|> Enum.reduce([], fn {a, res}, ac ->
case res do
{:ok, b} ->
[{a, b} | ac]
{:err, _} ->
[{a, nil} | ac]
end
end)
|> Enum.reverse()
end
defp get_coefs(isol) do
isol
|> Enum.filter(fn {_, b} -> b != nil end)
|> Enum.reduce([], fn {_, b}, ac ->
[b | ac]
end)
|> Enum.reverse()
end
defp get_rest(isol) do
isol
|> Enum.filter(fn {_, b} -> b == nil end)
|> Enum.reduce([], fn {a, _}, ac ->
[a | ac]
end)
|> Enum.reverse()
end
defp get_base(op, lst) do
pivots =
for pivot <- lst do
{pivot,
lst
|> Enum.reduce([], fn expr, ac ->
[cbs(op, pivot, expr) | ac]
end)
|> Enum.reverse()}
end
counts =
pivots
|> Enum.reduce([], fn {pivot, bases}, ac ->
[
{pivot, bases,
bases
|> Enum.reduce(0, fn {result, _}, ac ->
case result do
:ok -> ac + 1
_ -> ac
end
end)}
| ac
]
end)
|> Enum.reverse()
maxbase(counts)
end
defp cbs(op, a, a) when op in [:suma, :mult] do
{:ok, @uno}
end
defp cbs(op, {:elev, a, e1}, {:elev, a, e2}) do
case op do
:suma -> {:err, nil}
:mult -> {:ok, mk({:divi, e2, e1})}
end
end
defp cbs(op, a, {:elev, a, b}) do
case op do
:suma -> {:ok, mk({:elev, a, mk({:rest, b, @uno})})}
:mult -> {:ok, b}
end
end
defp cbs(:suma, a, {{:m, :mult}, lst}) do
cond do
a in lst ->
{:ok, {{:m, :mult}, lst |> List.delete(a)}}
true ->
{:err, nil}
end
end
defp cbs(_op, _t1, _t2) do
{:err, nil}
end
defp maxbase([a]), do: a
defp maxbase([h | t]), do: Enum.reduce(t, h, &maxbasef/2)
defp maxbasef(a1 = {_, _, c1}, a2 = {_, _, c2}) do
if c1 > c2, do: a1, else: a2
end
end