danica

Code Climate Test Coverage

A tool for math formulation on docs

How to use

Add the gem to your project or just install the gem

gem 'danica'
bundle install danica

Now you can use in your project

Quick Use

Use Danica build to build and use your expression using Danica.build

expression = Danica.build do
  (number(1) + 2) * power(3,4)
end

create and use functions

func = Danica.build do
  Danica::Function.create(:x, :y) do
    (number(1) + x) * power(3, y)
  end
end

func.to_gnu

create gnu or tex output strings to be used on your template

Operators

Operators represent a matematical operation such as sum, product, sin, etc..

Operators are to be composed to create an expression, equation or function (see below)

class MyOperator < Danica::Operator
  variables :elements_list
  def to_f
    #implement to float method
  end

  def to_tex
    #optionaly implement custom to_tex method
  end

  def to_gnu
    #optionaly implement custom to_gnu method
  end
end

Sample

class Danica::Operator::Inverse < Danica::Operator

  variables :value

  def to_f
    value.to_f ** -1 #Do not worry with nil value as this has been implemented already raising Danica::Exception::NotDefined
  end

  def to_tex
    "(#{value.to_tex})^{-1}"
  end

  def to_gnu
    "(#{value.to_gnu}) ** -1"
  end
end

fx = Danica::Operator::Inverse.new(:x)
to_tex
fx.to_tex

returns

(x)^{-1}
to_gnu
fx.to_gnu

returns

(x) ** -1
calculate / to_f
fx.calculate(2)

or

Danica::Operator::Inverse.new(2).to_f

both return

0.5

Expressions

Expressions are composition of operators threating their variables input.

Example of expression could be x ^ y + y / 2 which is composed of an operator sum of 2 parcels, being the first an operator power and the second an operator division, while the variable x is only used as the base of the power operator and the y variable is present on both power and division operator

Expressions can be defined on the fly or as a class

clazz = Danica::Expression.build(:a, :b, :c) do
  (
    negative(b) + Danica::Wrapper::PlusMinus.new(
      squared_root(
        power(b, 2) - multiplication(4, a, c)
      )
    )
  ) / (number(2) * a)
end
module Danica
  class Expression::Baskara < Expression.build(:a, :b, :c) { numerator / denominator }

    private

    def numerator
       negative(b) + Wrapper::PlusMinus.new(squared_root(delta))
    end

    def denominator
      number(2) * a
    end

    def delta
      power(b, 2) - multiplication(4, a, c)
    end
  end
end
clazz = Danica::Expression::Baskara

Both would create a class whose instance knows how to build baskara formula

clazz.new.to_tex
\frac{-b \pm \sqrt{b^{2} -4 \cdot a \cdot c}}{2 \cdot a}

Equations

Equations are formed by two expressions with their own variables

Danica::Equation.build(:x, :y, :z) do
  left { x ** 2 + y ** 2 }
  right { number(1) - z ** 2 }
end.new.to_tex
Danica::Equation.create(:x, :y, :z) do
  left { x ** 2 + y ** 2 }
  right { number(1) - z ** 2 }
end.to_tex

so we can create the equation

x**(2) + y**(2) = 1 -z**(2)

Functions

Functions are equations whose one of the sides is actually the naming of the function and variables declaration

Example of function could be f(x,y) = x ^ y + y / 2 which is composed of an operator sum of 2 parcels, being the first an operator power and the second an operator division, while the variable x is only used as the base of the power operator and the y variable is present on both power and division operator

class MyFunction < Danica::Function
  variables :x, :y ...

  def function_block
    #your code goes here
  end
end

Sample

module Danica
  class Function::Spatial < Function
    variables :time, :acceleration, :initial_space, :initial_velocity

    private

    def function_block
      @function_block ||= addition(parcels)
    end

    def parcels
      [
        initial_space,
        spatial_velocity,
        spatial_acceleration
      ]
    end

    def spatial_velocity
      multiplication(initial_velocity, time)
    end

    def spatial_acceleration
      division(multiplication(acceleration, time_squared), 2)
    end

    def time_squared
      power(time, 2)
    end
  end
end

fx = Danica::Function::Spatial.new(
  time: :t,
  acceleration: 'a',
  initial_space: { name: :S0, latex: 'S_0', gnuplot: 'S0' },
  initial_velocity: { name: :V0, latex: 'V_0', gnuplot: 'V0' }
)
to_tex
fx.to_tex

returns

f(t, a, S_0, V_0) = S_0 + V_0 \cdot t + \frac{a \cdot t^{2}}{2}

functions ignore constants variables on the function definition (left side of the equation)

class Func <  Danica::Function.build(:x, :y, :z) { z*(x ** y) }
end

f = Func.new(y: 3, z: Danica::PI)

f.to_tex

returns

f(x, 3) = \pi \cdot x^{3}
to_gnu
fx.to_gnu

returns

f(t, a, S0, V0) = S0 + V0 * t + (a * t**(2))/(2)

functions ignore constants AND numeric variables on the function definition (left side of the equation)

class Func <  Danica::Function.build(:x, :y, :z) { z*(x ** y) }
end

f = Func.new(y: 3, z: Danica::PI)

f.to_gnu

returns

f(x) = pi * x**(3)
calculate / to_f
fx = Danica::Function::Spatial.new(
  time: :t,
  acceleration: :a,
  initial_space: 1,
  initial_velocity: 2
)
fx.calculate(10, 3)

or

fx.calculate(time: 10, acceleration: 3)

or

Danica::Function::Spatial.new(10, 3, 1, 2).to_f

all return

171.0

Danica::Function.build

Alternativily, a function can be created through Danica::Function.build(*variables, &block)

class Saddle < Danica::Function.build(:x, :y) { power(x, 2) - power(y, 2) }
end

fx = Saddle.new

or

fx = Danica::Function.build(:x, :y) { power(x, 2) - power(y, 2) }.new
to_tex
fx.to_tex

returns

x^{2} -y^{2}
to_gnu
fx.to_gnu

returns

x**(2) -y**(2)

Danica::Function.for

A function can also created after an expression

module Danica
  class Expression
    class QuadraticSum < build(:x, :y) { (x + y) ** 2 }
    end
  end
  class Function
    class QuadraticSum < Function.for(Expression::QuadraticSum)
    end
  end
end

fx = Danica::Function::QuadraticSum.new

fx.to_tex
f(x, y) = \left(x + y\right)^{2}

DSL and building

An expression can be created using the DSL direct from Danica

Danica.build do
  power(:x, -1)
end

will result into a Danica::Operator::Power wrapped into an Danica::Expression

Danica::Operator::Power.new(:x, -1)

Operator registration on DSL

Any operator created can be added to the DSL by running DSL.register_operator

module Danica
  class Operator::Inverse < Danica::Operator
    include DSL
    variables :value

    delegate :to_f, :to_tex, :to_gnu, to: :pow

    def pow
      @pow ||= power(value, -1)
    end
  end
end

In order to add the new operator, DSL cna infer by the name inverse which results in Danica::Operator::Inverse

Danica::DSL.register_operator(:inverse)

or

Danica::DSL.register(:inverse, Danica::Operator::Inverse)

This will allow the usage of the inverse function

Danica.build do
  inverse(:x)
end

will result into a Danica::Operator::Inverse object

Danica::Operator::Inverse.new(:x)

Variables

Variables are instances of Danica::Wrapper::Variable having the optional attributes name, gnu , latex and value

The initialization of the variable can be made through the class, DSL or when initializing an operator

  Danica::Wrapper::Variable.new(:x)
  Danica::Wrapper::Variable.new(name: :x)
Danica::DSL.build do
  variable(:x)
end

all will create the same variable that can be coverted #to_tex

x

When using it with function, operators or other Danica::Common objects, the variables are wrapped automatically

Danica::DSL.build do
  power(:x, { name: :y }) + :z
end

will result in

Danica::Operator::Addition.new(
  Danica::Operator::Power.new(
    Danica::Wrapper::Variable.new(:x), 
    Danica::Wrapper::Variable.new(:y) 
  ),
  Danica::Wrapper::Variable.new(:z) 
)

Variables can also behave differently when converting to tex or gnu

Danica::DSL.build do
  variable(name: :frequency, latex: '\lambda', gnuplot: :f)
end

would produce different #to_tex and #to_gnu results (\lambda and f respectvly)

Also, valued variables will always use their value on string representation

Danica::DSL.build do
  variable(name: :frequency, latex: '\lambda', gnuplot: :f, value: 2)
end

will always return 2 for both to(:tex) and to(:gnu) calls

#to_f

Variables can be used to calculate the value of an expression by usage of the value attribute

Danica::DSL.build do
  variable(name: :x, value: 2)
end

with will respond to #tot_f as 2

It can be used when calculating an expression later

x = Danica::DSL.build do
  variable(:x)
end

p = Danica::DSL.build do
  power(x, 2)
end

x.value = 4
p.to_f

which will return 16

Number

Numberss are simple wrappers using Danica::Wrapper::Number

they can be initialized explicitly, through the DSL or whenever an operation is made with other Danica objects

Danica::Wrapper::Number.new(3)
Danica::DSL.build do
  number(3)
end

will both return the number object that can be used to generate tex, gnu or float outputs (for calculation)

Other ways of creating instances of number is when using it as a right side element in basic operations such as sum or when using it as the parameter of any other class such as functions and operators

Danica::DSL.build do
  power(:x, 2) + 3
end

will create

Danica::Operator::Addition.new(
  Danica::Operator::Power.new(
    Danica::Wrapper::Variable.new(:x), 
    Danica::Wrapper::Number.new(2)
  ),
  Danica::Wrapper::Number.new(3)
)

Constant

Constant are pretty much like any other variable, except that they always have value, and have a gnu and latex representation.

While variables with value have a numeric string representation, constants will always be represented by their string attribute

Danica::Wrapper::Constant.new(gnuplot: 'pi', latex: '\pi', value: 3.141592)

which will have the returns of #to(format) obeying the following

{
  tex: '\pi',
  gnuplot: 'pi',
  f: 3.141592
}

Formatting

When generating the output, you can choose options or even create a formatted object

decimals

define the float decimals

value = 1 / 3.0
expression = Danica.build(:x) do
  x + value
end

expression.to_tex(decimals: 3)

returns

x + 0.333

Formatted

Any Danica object can be formatted previously returning a pre-formatted object

value = 1 / 3.0
expression = Danica.build(:x) do
  x + value
end

formatted = expression.tex(decimals: 3)
formatted.to_s

returns

x + 0.333