Home > Software design >  How do i do calculations on things that are in a tuple expression?
How do i do calculations on things that are in a tuple expression?

Time:01-23

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 at nil 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.

  • Related