Current section
Files
Jump to
Current section
Files
lib/exun_simpl.ex
defmodule Exun.Simpl do
import Exun.Fun
alias Exun.Collect
alias Exun.Unit
import Exun.Eq
@zero {:numb, 0}
@uno {:numb, 1}
@moduledoc """
Simplify expressions
"""
@doc """
Recursively try to simplify expression. Multiple tries are performed.
For a more agressive simplify, use Exun.Collect.coll
"""
def mkrec(tree) do
ntree = mk(tree)
if eq(ntree, tree),
do: ntree,
else: mkrec(ntree)
end
# simplify
defp mk({:minus, {:minus, a}}), do: mk(a)
defp mk({:minus, {{:m, :mult}, list}}) do
{res, nlist} = collect_minus(list)
if res do
mk({{:m, :mult}, nlist})
else
{:minus, mk({{:m, :mult}, list})}
end
end
defp mk({:minus, a}), do: {:minus, mk(a)}
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({:elev, _, @zero}), do: @uno
defp mk({:elev, a, @uno}), do: mk(a)
defp mk({:elev, @uno, _}), do: @uno
defp mk({:elev, {:numb, base}, {:numb, exp}}), do: {:numb, :math.pow(base, exp)}
defp mk({:elev, {:elev, base, e1}, e2}), do: {:elev, mk(base), mk(mult(e1, e2))}
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
# Simplify each component of the list
lst =
lst
# |> IO.inspect(label: "pre mk #{op}")
|> Enum.map(&mkrec(&1))
# |> IO.inspect(label: ":m foreach el mkrec done")
# |> IO.inspect(label: "post mk #{op} mk each")
# Promote sublist, so if ther is a element in lst of class {:m,op}
# include sublist in main list
lst =
Enum.reduce(lst, [], fn el, ac ->
case el do
{{:m, ^op}, sublist} -> sublist ++ ac
other -> [other | ac]
end
end)
|> Enum.sort(&smm(&1, &2))
unity = if op == :suma, do: @zero, else: @uno
ufc = if op == :suma, do: &+/2, else: &*/2
# Collect numbers and units and simplify
lst = collect_literals(op, lst, ufc, unity)
# Remove zeroes or ones, 0+any=any, 1*any=any and may be the nil
# introduced by the last command
lst = Enum.reject(lst, &(&1 == unity or &1 == nil))
# |> IO.inspect(label: "post literals")
# if a multiple mult {:m,:mult} check if zero is a component
if op == :mult and @zero in lst do
@zero
else
case length(lst) do
# No more elements in list, return unity
0 ->
unity
# Only one element, replace {{}:m,op},lst} with it
1 ->
List.first(lst)
# Let's play
_ ->
# IO.inspect(lst,label: "lst for pivot")
{pivot, base, counts} = get_base(op, lst)
# |> IO.inspect(label: "pivot,base,counts")
case counts do
1 ->
{{:m, op}, lst}
_ ->
isol = get_isol(base, lst)
coefs = get_coefs(isol)
rest = get_rest(isol)
isolp =
mkrec(
case op do
:suma ->
{{:m, :mult}, [pivot, {{:m, :suma}, coefs}] |> Enum.sort(&smm(&1, &2))}
:mult ->
{:elev, pivot, {{:m, :suma}, coefs |> Enum.sort(&smm(&1, &2))}}
end
)
case length(rest) do
0 -> isolp
_ -> {{:m, op}, [isolp | rest] |> Enum.sort(&smm(&1, &2))}
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,
Enum.reduce(lst, [], 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, {:minus, a}, a) when op in [:suma, :mult] do
{:ok, {:numb, -1}}
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
defp collect_literals(op, lst, ufc, unity) do
{n, u, lst} =
Enum.reduce(lst, {unity, nil, []}, fn el, {{:numb, n_ac}, u_ac, rest} ->
case el do
{:numb, n} ->
{{:numb, ufc.(n_ac, n)}, u_ac, rest}
u = {:unit, {:numb, u_n}, u_t} ->
if u_ac == nil do
{{:numb, n_ac}, u, rest}
else
{:unit, {:numb, acu_n}, acu_t} = u_ac
case op do
:mult ->
sou = {:unit, {:numb, acu_n * u_n}, mult(acu_t, u_t)}
{{:numb, n_ac}, sou, rest}
:suma ->
sou =
case Exun.Unit.sum(u_ac, u) do
{:err, msg} -> throw(msg)
{:ok, unit} -> unit
end
{{:numb, n_ac}, sou, rest}
end
end
other ->
{{:numb, n_ac}, u_ac, [other | rest]}
end
end)
case {n, u} do
{^unity, nil} -> lst
{^unity, unit} -> [unit | lst]
{number, nil} -> [number | lst]
{{:numb, n}, {:unit, {:numb, un}, tree}} -> [{:unit, {:numb, n * un}, tree} | lst]
end
# |> IO.inspect(label: "final unit,number,lst")
end
# return {flag,newlist} if l is from mult and can extract signs
# from its operands
def acollect_minus(l) do
{false, l}
end
def collect_minus(l) do
{res, nl, _} =
Enum.reduce(l, {false, [], false}, fn opand, {flag, nl, selected} ->
case opand do
{:minus, minusop} ->
nflag = if flag, do: false, else: true
if selected do
{nflag, [opand | nl], true}
else
{nflag, [minusop | nl], true}
end
_ ->
{flag, [opand | nl], selected}
end
end)
{res, nl |> Enum.reverse()}
# |> IO.inspect(label: "collected minus")
end
end