#(docs are generated from top-level comments, keep an eye on the formatting!)

### abs =====================================================================

Tags
----
scripting, JS, internal, treenode, general concept

Parameters
----------
x

General description
-------------------
Returns the absolute value of a real number, the magnitude of a complex number, or the vector length.

###

###
 Absolute value of a number,or magnitude of complex z, or norm of a vector

  z    abs(z)
  -    ------

  a    a

  -a    a

  (-1)^a    1

  exp(a + i b)  exp(a)

  a b    abs(a) abs(b)

  a + i b    sqrt(a^2 + b^2)

Notes

  1. Handles mixed polar and rectangular forms, e.g. 1 + exp(i pi/3)

  2. jean-francois.debroux reports that when z=(a+i*b)/(c+i*d) then

    abs(numerator(z)) / abs(denominator(z))

     must be used to get the correct answer. Now the operation is
     automatic.
###


DEBUG_ABS = false

Eval_abs = ->
  push(cadr(p1))
  Eval()
  abs()

absValFloat = ->
  Eval()
  absval()
  Eval(); # normalize
  zzfloat()
  # zzfloat of an abs doesn't necessarily result in a double
  # , for example if there are variables. But
  # in many of the tests there should be indeed
  # a float, these two lines come handy to highlight
  # when that doesn't happen for those tests.
  #if !isdouble(stack[tos-1])
  #  stop("absValFloat should return a double and instead got: " + stack[tos-1])

abs = ->
  theArgument = top()
  if DEBUG_ABS then console.trace ">>>>  ABS of " + theArgument
  numerator()
  if DEBUG_ABS then console.log "ABS numerator " + stack[tos-1]
  absval()
  if DEBUG_ABS then console.log "ABSVAL numerator: " + stack[tos-1]

  push(theArgument)
  denominator()
  if DEBUG_ABS then console.log "ABS denominator: " + stack[tos-1]
  absval()
  if DEBUG_ABS then console.log "ABSVAL denominator: " + stack[tos-1]

  divide()
  if DEBUG_ABS then console.log "ABSVAL divided: " + stack[tos-1]

  if DEBUG_ABS then console.log "<<<<<<<  ABS"

absval = ->
  save()
  p1 = pop()
  input = p1

  if DEBUG_ABS then console.log "ABS of " + p1


  # handle all the "number" cases first -----------------------------------------
  if (isZeroAtomOrTensor(p1))
    if DEBUG_ABS then console.log " abs: " + p1 + " just zero"
    push(zero)
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  if (isnegativenumber(p1))
    if DEBUG_ABS then console.log " abs: " + p1 + " just a negative"
    push(p1)
    negate()
    restore()
    return

  if (ispositivenumber(p1))
    if DEBUG_ABS then console.log " abs: " + p1 + " just a positive"
    push(p1)
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  if (p1 == symbol(PI))
    if DEBUG_ABS then console.log " abs: " + p1 + " of PI"
    push(p1)
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  # ??? should there be a shortcut case here for the imaginary unit?

  # now handle decomposition cases ----------------------------------------------

  # we catch the "add", "power", "multiply" cases first,
  # before falling back to the
  # negative/positive cases because there are some
  # simplification thay we might be able to do.
  # Note that for this routine to give a correct result, this
  # must be a sum where a complex number appears.
  # If we apply this to "a+b", we get an incorrect result.
  if (car(p1) == symbol(ADD) and (
   findPossibleClockForm(p1) or
   findPossibleExponentialForm(p1) or
   Find(p1,imaginaryunit))
  )
    if DEBUG_ABS then console.log " abs: " + p1 + " is a sum"
    if DEBUG_ABS then console.log "abs of a sum"
    # sum
    push(p1)
    rect() # convert polar terms, if any
    p1 = pop()
    push(p1)
    real()
    push_integer(2)
    power()
    push(p1)
    imag()
    push_integer(2)
    power()
    add()
    push_rational(1, 2)
    power()
    simplify_trig()
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  if (car(p1) == symbol(POWER) && equaln(cadr(p1), -1))
    if DEBUG_ABS then console.log " abs: " + p1 + " is -1 to any power"
    # -1 to any power
    if evaluatingAsFloats
      if DEBUG_ABS then console.log " abs: numeric, so result is 1.0"
      push_double(1.0)
    else
      if DEBUG_ABS then console.log " abs: symbolic, so result is 1"
      push_integer(1)
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  # abs(a^b) is equal to abs(a)^b IF b is positive
  if (car(p1) == symbol(POWER) && ispositivenumber(caddr(p1)))
    if DEBUG_ABS then console.log " abs: " + p1 + " is something to the power of a positive number"
    push cadr(p1)
    abs()
    push caddr(p1)
    power()
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  # abs(e^something)
  if (car(p1) == symbol(POWER) && cadr(p1) == symbol(E))
    if DEBUG_ABS then console.log " abs: " + p1 + " is an exponential"
    # exponential
    push(caddr(p1))
    real()
    exponential()
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  if (car(p1) == symbol(MULTIPLY))
    if DEBUG_ABS then console.log " abs: " + p1 + " is a product"
    # product
    anyFactorsYet = false
    p1 = cdr(p1)
    while (iscons(p1))
      push(car(p1))
      absval()
      if anyFactorsYet
        multiply()
      anyFactorsYet = true
      p1 = cdr(p1)
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  if (car(p1) == symbol(ABS))
    if DEBUG_ABS then console.log " abs: " + p1 + " is abs of a abs"
    # abs of a abs
    push_symbol(ABS)
    push cadr(p1)
    list(2)
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return

  ###
  # Evaluation via zzfloat()
  # ...while this is in theory a powerful mechanism, I've commented it
  # out because I've refined this method enough to not need this.
  # Evaling via zzfloat() is in principle more problematic because it could
  # require further evaluations which could end up in further "abs" which
  # would end up in infinite loops. Better not use it if not necessary.

  # we look directly at the float evaluation of the argument
  # to see if we end up with a number, which would mean that there
  # is no imaginary component and we can just return the input
  # (or its negation) as the result.
  push p1
  zzfloat()
  floatEvaluation = pop()

  if (isnegativenumber(floatEvaluation))
    if DEBUG_ABS then console.log " abs: " + p1 + " just a negative"
    push(p1)
    negate()
    restore()
    return

  if (ispositivenumber(floatEvaluation))
    if DEBUG_ABS then console.log " abs: " + p1 + " just a positive"
    push(p1)
    if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
    restore()
    return
  ###

  if (istensor(p1))
    absval_tensor()
    restore()
    return

  if (isnegativeterm(p1) || (car(p1) == symbol(ADD) && isnegativeterm(cadr(p1))))
    push(p1)
    negate()
    p1 = pop()

  if DEBUG_ABS then console.log " abs: " + p1 + " is nothing decomposable"
  push_symbol(ABS)
  push(p1)
  list(2)

  if DEBUG_ABS then console.log " --> ABS of " + input + " : " + stack[tos-1]
  restore()

# also called the "norm" of a vector
absval_tensor = ->
  if (p1.tensor.ndim != 1)
    stop("abs(tensor) with tensor rank > 1")
  push(p1)
  push(p1)
  conjugate()
  inner()
  push_rational(1, 2)
  power()
  simplify()
  Eval()
