import tensorflow as tf
import numpy as np

class Halley:
  def __init__(self, a):
    self.f = lambda x: a[0] + a[1] * x + a[2] * tf.pow(x, 2) + a[3] * tf.pow(x, 3) + a[4] * tf.pow(x, 4)
    self.df = lambda x: tf.gradients(self.f(x), x)[0]  # TensorFlow does automatic differentiation!
    self.ddf = lambda x: tf.gradients(self.df(x), x)[0]

  def compute_one_iteration(self, x):
    return x - ((2 * self.f(x) * self.df(x)) / (2 * tf.pow(self.df(x), 2) - self.f(x) * self.ddf(x)))


# answer is supposed to be 7.411
with tf.Session() as sess:
  a = [-1.0,1.0,12.0,-4.0,1.0]
  x0 = tf.constant(12.0)
  halley = Halley(a)
  answer = halley.compute_one_iteration(x0)
  result = sess.run(answer)
  print(result)

"""
Explanation: <h1> Challenge Solution: Getting started with TensorFlow </h1>

Challenge Exercise
Use TensorFlow to find the roots of a fourth-degree polynomial using Halley's Method.  The five coefficients (i.e. $a_0$ to $a_4$) of 
<p>
$f(x) = a_0 + a_1 x + a_2 x^2 + a_3 x^3 + a_4 x^4$
<p>
will be fed into the program, as will the initial guess $x_0$. Your program will start from that initial guess and then iterate one step using the formula:
<img src="https://wikimedia.org/api/rest_v1/media/math/render/svg/142614c0378a1d61cb623c1352bf85b6b7bc4397" />
<p>
If you got the above easily, try iterating indefinitely until the change between $x_n$ and $x_{n+1}$ is less than some specified tolerance. Hint: Use [tf.while_loop](https://www.tensorflow.org/api_docs/python/tf/while_loop)

## Iterate one step

Note that we use TensorFlow's built-in automatic differentiation!
End of explanation
"""


import tensorflow as tf
import numpy as np

class Halley:
  def __init__(self, a):
    self.f = lambda x: a[0] + a[1] * x + a[2] * tf.pow(x, 2) + a[3] * tf.pow(x, 3) + a[4] * tf.pow(x, 4)
    self.df = lambda x: tf.gradients(self.f(x), x)[0]
    self.ddf = lambda x: tf.gradients(self.df(x), x)[0]

  def compute_one_iteration(self, x):
    return x - ((2 * self.f(x) * self.df(x)) / (2 * tf.pow(self.df(x), 2) - self.f(x) * self.ddf(x)))


# answer is supposed to be [7.4111586, 4.459961, 2.2138097]
with tf.Session() as sess:
  a = [-1.0,1.0,12.0,-4.0,1.0]
  x0 = tf.constant(12.0)
  halley = Halley(a)
  x1 = halley.compute_one_iteration(x0)
  x2 = halley.compute_one_iteration(x1)
  x3 = halley.compute_one_iteration(x2)
  result = sess.run([x1, x2, x3])
  print(result)
  

"""
Explanation: Iterate 3 times
... by writing the compute 3 times
End of explanation
"""


import tensorflow as tf
import numpy as np

import tensorflow as tf
import numpy as np

class Halley:
  def __init__(self, a):
    self.f = lambda x: a[0] + a[1] * x + a[2] * tf.pow(x, 2) + a[3] * tf.pow(x, 3) + a[4] * tf.pow(x, 4)
    self.df = lambda x: tf.gradients(self.f(x), x)[0]
    self.ddf = lambda x: tf.gradients(self.df(x), x)[0]

  def compute_one_iteration(self, x):
    return x - ((2 * self.f(x) * self.df(x)) / (2 * tf.pow(self.df(x), 2) - self.f(x) * self.ddf(x)))

  def prev_and_curr(self, iterno, prev, x):
    return iterno+1, x, self.compute_one_iteration(x)

  def compute(self, x0, maxiter, epsilon):
    return tf.while_loop(lambda i, prev, x: tf.logical_and(tf.abs(prev-x) > epsilon, i < maxiter), 
                         self.prev_and_curr, (0, x0-2*epsilon, x0))
  
# init parameters
# answer is supposed to be -0.31365424 or 0.259158
with tf.Session() as sess:
  a = [-1.0,1.0,12.0,-4.0,1.0]
  x0 = tf.constant(12.0)
  halley = Halley(a)
  xn = halley.compute(x0, 100, 0.01)
  result = sess.run(xn)
  print(result[2])

"""
Explanation: Iterate until condition
... using tf.while
End of explanation
"""


