Hey I'm new to Elixir (just started three days ago) and I'm trying to write a program that can calculate derivatives and I'm stuck at trying to simplify the expression for ease of reading. Only done Java and C earlier. So I have defined this at the top
defmodule Deriv do
@type literal() :: {:num, number()} | {:var, atom()}
@type expr() ::
literal() | {:add, expr(), expr()} | {:mul, expr(), expr()}
| {:exp, expr(), literal()} | {:div, literal(), expr()} |
# {:ln, literal(), expr()} | {:ln, literal(), literal()}
{:ln, expr()}
And I'm trying to simplify the expression I get from running this test
def test_exp2() do
e = {:exp, {:add, {:mul, {:num, 2}, {:var, :x}}, {:num, 3}}, {:num, 2}}
d = deriv(e, :x)
IO.write("Expression: #{p_print(e)}\n")
IO.write("Derivative of expression: #{p_print(d)}\n")
IO.write("Simplified: #{p_print(simplify(d))}\n")
:ok
end
So I have these function already that simplifies the expression when we do multiplications with 0 and 1, shown below
def simplify({:mul, e1, e2}) do
simplify_mul(simplify(e1), simplify(e2))
end
def simplify_mul({:num, 0}, _) do {:num, 0} end
def simplify_mul(_, {:num, 0}) do {:num, 0} end
def simplify_mul({:num, 1}, e2) do e2 end
def simplify_mul(e1, {:num, 1}) do e1 end
def simplify_mul({:num, n1}, {:num, n2}) do {:num, n1 * n2} end
But I can't get a function that does the multiplication thing mention above to work. The problem is that I don't know the syntax to use.
The output from running this is 2*(2*x 3)*2
but I would like it to be (8*x 12)
So I want some kind of function like simplify_mul({:num, n1}, e2)
where I multiply the number n1 with everything in the expression e2. I've tried things like
def simplify_mul({:num, n1}, {:mul, {:num, mulnum1}, e2}) do
{:mul, {:num, n1*mulnum1}, e2}
end
but it didn't work. Anyone know how one would go about doing this?
Edit:The code minus some test functions
defmodule Deriv do
@type literal() :: {:num, number()} | {:var, atom()}
@type expr() ::
literal() | {:add, expr(), expr()} | {:mul, expr(), expr()}
| {:exp, expr(), literal()} | {:div, literal(), expr()} |
# {:ln, literal(), expr()} | {:ln, literal(), literal()}
{:ln, expr()}
def test_exp2() do
e = {:exp, {:add, {:mul, {:num, 2}, {:var, :x}}, {:num, 3}}, {:num, 2}}
d = deriv(e, :x)
IO.write("Expression: #{p_print(e)}\n")
IO.write("Derivative of expression: #{p_print(d)}\n")
IO.write("Simplified: #{p_print(simplify(d))}\n")
:ok
end
def test_ln() do
e =
{:mul, {:num, 2}, {:ln, {:exp, {:add, {:mul, {:num, 2}, {:var, :x}}, {:num, 3}}, {:num, 2}}}}
d = deriv(e, :x)
IO.write("Expression: #{p_print(e)}\n")
IO.write("Derivative of expression: #{p_print(d)}\n")
IO.write("Simplified: #{p_print(simplify(d))}\n")
:ok
end
###### Our derivatives rules #######
# derivative of a constant
def deriv({:num, _}, _) do {:num, 0} end
# derivative of x to the power of one
def deriv({:var, v}, v) do {:num, 1} end
# derivative of another variable than x
def deriv({:var, _}, _) do {:num, 0} end
# d/dx(f g) = f'(x) g'(x)
def deriv({:add, e1, e2}, v) do {:add, deriv(e1, v), deriv(e2, v)} end
# d/dx(f*g) = f'(x)g(x) f(x)g'(x)
def deriv({:mul, e1, e2}, v) do
{:add, {:mul, deriv(e1, v), e2}, {:mul, e1, deriv(e2, v)}}
end
# d/dx(u(x)^n) = n(u(x))^(n-1)*u'(x), where n is a real number
def deriv({:exp, u, {:num, n}}, v) do
{:mul, {:mul, {:num, n}, {:exp, u, {:num, n - 1}}}, deriv(u, v)}
end
#d/dx(k/(u(x)^n)) = -nk*u'(x)/(u(x)^(n 1))
def deriv({:div, {:num, k}, {:exp, e, {:num, n}}}, v) do
{:div,
{:mul,
{:mul, {:num, k}, {:num, -n}},
deriv(e, v)
},
{:exp, e, {:num, n 1}}
}
end
def deriv({:ln, e}, v) do {:div, deriv(e, v), e} end
# d/dx(k*ln(u(x)^n)) = kn*u'(x)/u(x)
def deriv({:mul, {:num, k}, {:exp, e, {:num, n}}}, v) do
{:div,
{:mul,
{:mul, {:num, k}, {:num, n}},
deriv(e, v)
},
{:exp, e, {:num, n}}
}
end
###### --------------------- #######
#simplifies the expression by removing zeros and ones etc.
def simplify({:add, e1, e2}) do
simplify_add(simplify(e1), simplify(e2))
end
def simplify({:mul, e1, e2}) do
simplify_mul(simplify(e1), simplify(e2))
end
def simplify({:exp, e1, e2}) do
simplify_exp(simplify(e1), simplify(e2))
end
def simplify({:div, e1, e2}) do
simplify_div(simplify(e1), simplify(e2))
end
def simplify({:ln, e}) do simplify_ln(simplify(e)) end
def simplify(e) do e end
def simplify_add({:num, 0}, e2) do e2 end
def simplify_add(e1, {:num, 0}) do e1 end
def simplify_add({:num, n1}, {:num, n2}) do {:num, n1 n2} end
def simplify_add(e1, e2) do {:add, e1, e2} end
def simplify_mul({:num, 0}, _) do {:num, 0} end
def simplify_mul(_, {:num, 0}) do {:num, 0} end
def simplify_mul({:num, 1}, e2) do e2 end
def simplify_mul(e1, {:num, 1}) do e1 end
def simplify_mul({:num, n1}, {:num, n2}) do {:num, n1 * n2} end
def simplify_mul({:num, n1}, {:mul, {:num, mulnum1}, e2}) do
simplify({:mul, {:num, n1*mulnum1}, e2})
end
def simplify_mul(e1, e2) do {:mul, e1, e2} end
def simplify_exp(_, {:num, 0}) do {:num, 1} end
def simplify_exp(e1, {:num, 1}) do e1 end
def simplify_exp({:num, n1}, {:num, n2}) do {:num, :math.pow(n1, n2)} end
def simplify_exp(e1, e2) do {:exp, e1, e2} end
def simplify_div({:num, 0}, _) do {:num, 0} end
def simplify_div(e1, e2) do {:div, e1, e2} end
def simplify_ln({:num, 1}) do {:num, 0} end
def simplify_ln({:num, 0}) do {:num, 0} end
def simplify_ln(e) do {:ln, e} end
# p_print functions converts from our syntax tree into strings for ease of reading
def p_print({:num, n}) do "#{n}" end
def p_print({:var, v}) do "#{v}" end
def p_print({:add, e1, e2}) do "(#{p_print(e1)} #{p_print(e2)})" end
def p_print({:mul, e1, e2}) do "#{p_print(e1)}*#{p_print(e2)}" end
def p_print({:exp, e1, e2}) do "(#{p_print(e1)})^(#{p_print(e2)})" end
def p_print({:div, e1, e2}) do "(#{p_print(e1)}/#{p_print(e2)})" end
def p_print({:ln , e1}) do "ln(#{p_print(e1)})" end
end
CodePudding user response:
The following should solve it, it added two extra accumulators:
p
(for the product of number literals), starting at 1- and
m
for building a multiplication of expressions, starting atnil
meaning there is no expression
It also moved from passing two factors to a list of factors: the benefit is that when you encountered a nested multiplication, you can just add its two factors to the list and keep processing, until the list is empty.
def simplify({:mul, e1, e2}) do
simplify_mul([e1, e2], 1, nil)
end
def simplify_mul([], p, nil), do: {:num, p}
def simplify_mul([], p, m), do: {:mul, {:num, p}, m}
def simplify_mul([{:num, 0} | _], _, _), do: {:num, 0}
def simplify_mul([{:num, n1} | rest], p, m) do
simplify_mul(rest, p * n1, m)
end
# nested multiplication: add factors to the list
def simplify_mul([{:mul, e1, e2} | rest], p, m) do
simplify_mul([e1, e2 | rest], p, m)
end
def simplify_mul([other | rest], p, nil) do
simplify_mul(rest, p, other)
end
def simplify_mul([other | rest], p, m) do
simplify_mul(rest, p, {:mul, m, other})
end
iex> simplify({:mul, {:mul, {:num, 2}, {:var, :x}}, {:mul, {:var, :y}, {:num, 3}}})
{:mul, {:num, 6}, {:mul, {:var, :x}, {:var, :y}}}
CodePudding user response:
I feel like this is broadly on the right track, you're just short a couple of rules.
def test do
# the input expression is 2*(2x 3)*2
e = {:add, {:mul, {:num, 2}, {:var, "x"}}, {:num, 3}}
e = {:mul, {:num, 2}, e}
e = {:mul, e, {:num, 2}}
e |> simplify() |> IO.inspect()
end
Since this is only binary expressions, I've represented this as (2 * (2x 3)) * 2
.
The first thing that can help cut down the number of cases is to swap the operands if the second one is a number but the first isn't. You can then repeat the simplification step on the swapped result. You're guaranteed the first operand isn't a number (you have a separate simplification step for "both numbers") so this is guaranteed to terminate.
def simplify_mul(e1, {:num, _}=e2) do
simplify_mul(e2, e1)
end
This changes the expression to 2 * (2 * (2x 3))
. Now you can specifically match the case where you're multiplying a constant by a multiplication, and the first operand of the inner multiplication is also a constant. This involves a deeper Elixir pattern match, but that's fine; you're not constrained to only matching the top-level structure.
def simplify_mul({:num, n1}, {:mul, {:num, n2}, e3}) do
{:mul, {:num, n1 * n2}, e3}
end
This reduces the expression to 4 * (2x 3)
. The last step is to distribute multiplication over addition; if you're multiplying anything by an addition as the second argument, turn that into an addition of multiplications. This will result in new multiplications that need to be recursively simplified; so long as you don't reverse this process, the easiest approach is to just simplify the resulting addition.
def simplify_mul(e1, {:add, e2, e3}) do
{:add, {:mul, e1, e2}, {:mul, e1, e3}} |> simplify()
end
$ elixir tmp.exs
{:add, {:mul, {:num, 8}, {:var, "x"}}, {:num, 12}}
You can add as many rules as you need to this rewriting; the only important thing to make sure of is that the rules do terminate. So for example I've written a rule that "numbers must be first", but you need to take care also pairing this with a rule that "addition must be first" since multiplying a number and an addition would result in an infinite loop.