#!/usr/bin/env cctbx.python
# PolyFitter.py
#
#   Copyright (C) 2010 Diamond Light Source, Graeme Winter
#
#   This code is distributed under the BSD license, a copy of which is
#   included in the root directory of this package.
#
# A toolkit component for performing polynomial fits to an array of data,
# using the CCTBX lbfgs minimiser. For data from a general form, it may
# be helpful to transform the measurements to a "sensible" form - for example
# taking log(I/sigI).

import math

from cctbx.array_family import flex
from scitbx import lbfgs

def poly_residual(xp, y, params):
  '''Compute the residual between the observations y[i] and sum_j
  params[j] x[i]^j. For efficiency, x[i]^j are pre-calculated in xp.'''

  r = 0.0

  n = len(params)
  c = len(y)

  e = flex.double([flex.sum(xp[j] * params) for j in range(c)])

  return flex.sum(flex.pow2(y - e))

def poly_gradients(xp, y, params):
  '''Compute the gradient of the residual w.r.t. the parameters, N.B.
  will be performed using a finite difference method. N.B. this should
  be trivial to do algebraicly.'''

  eps = 1.0e-6

  g = flex.double()

  n = len(params)

  for j in range(n):
    rs = []
    for signed_eps in [- eps, eps]:
      params_eps = params[:]
      params_eps[j] += signed_eps
      rs.append(poly_residual(xp, y, params_eps))
    g.append((rs[1] - rs[0]) / (2 * eps))

  return g

class poly_fitter(object):
  '''A class to do the polynomial fit. This will fit observations y
  at points x with a polynomial of order n.'''

  def __init__(self, points, values, order):
    self.x = flex.double([1.0 for j in range(order)])
    self._x = flex.double(points)
    self._y = flex.double(values)

    # precalculate x[j]^[0-(n - 1)] values

    self._xp = [flex.double([math.pow(x, j) for j in range(order)])
                for x in self._x]

    return

  def refine(self):
    '''Actually perform the parameter refinement.'''

    return lbfgs.run(target_evaluator = self)

  def compute_functional_and_gradients(self):

    return poly_residual(self._xp, self._y, self.x), \
           poly_gradients(self._xp, self._y, self.x)

  def get_parameters(self):
    return list(self.x)

  def evaluate(self, x):
    '''Evaluate the resulting fit at point x.'''

    return sum([math.pow(x, k) * self.x[k] for k in range(len(self.x))])

def fit(x, y, order):
  '''Fit the values y(x) then return this fit. x, y should
  be iterables containing floats of the same size. The order is the order
  of polynomial to use for this fit. This will be useful for e.g. I/sigma.'''

  pf = poly_fitter(x, y, order)
  pf.refine()

  return [pf.evaluate(_x) for _x in x]

def log_fit(x, y, order):
  '''Fit the values log(y(x)) then return exp() to this fit. x, y should
  be iterables containing floats of the same size. The order is the order
  of polynomial to use for this fit. This will be useful for e.g. I/sigma.'''

  ly = [math.log(_y) for _y in y]

  pf = poly_fitter(x, ly, order)
  pf.refine()

  return [math.exp(pf.evaluate(_x)) for _x in x]

def log_inv_fit(x, y, order):
  '''Fit the values log(1 / y(x)) then return the inverse of this fit.
  x, y should be iterables, the order of the polynomial for the transformed
  fit needs to be specified. This will be useful for e.g. Rmerge.'''

  ly = [math.log(1.0 / _y) for _y in y]

  pf = poly_fitter(x, ly, order)
  pf.refine()

  return [(1.0 / math.exp(pf.evaluate(_x))) for _x in x]

def interpolate_value(x, y, t):
  '''Find the value of x: y(x) = t.'''

  if t > max(y) or t < min(y):
    raise RuntimeError, 't outside of [%f, %f]' % (min(y), max(y))

  for j in range(1, len(x)):
    x0 = x[j - 1]
    y0 = y[j - 1]

    x1 = x[j]
    y1 = y[j]

    if (y0 - t) * (y1 - t) < 0:
      return x0 + (t - y0) * (x1 - x0) / (y1 - y0)

def get_positive_values(x):
  '''Return a list of values v from x where v > 0.'''

  result = []

  for _x in x:
    if _x > 0:
      result.append(_x)
    else:
      return result

  return result


if __name__ == '__main__':

  # trying to work out why something is slow...

  x = [0.28733375585344956, 0.3336648239480671, 0.37392475503798783, 0.4116791460480823, 0.44667362480391215, 0.48010999459819637, 0.5123842907520316, 0.5445830447029069, 0.5747600267080056, 0.605268188207491, 0.6348694178757428, 0.6628307139444256, 0.6915543733106164, 0.7190850546688736, 0.7466325833791124, 0.7726534107667972, 0.7991813564734889, 0.8246120592630442, 0.8509431563671859, 0.8752222362981207, 0.9003835108822839, 0.925531251174205, 0.9495577347489563, 0.9736107180716824, 0.9977616739729435, 1.0211126767435303, 1.0442229585861016, 1.0676870644761218, 1.089626948783452, 1.11325323064326, 1.1353748686331517, 1.157229309091089, 1.1793787289152926, 1.2012850147174827, 1.223192876382562, 1.2442806850714754, 1.2659456255540278, 1.2868725763092403, 1.3077684542819044, 1.329693962546648, 1.3497661431014192, 1.3703975279412275, 1.3913213083813614, 1.4118099020522166, 1.431944241466548, 1.451565968015303, 1.4726043408387703, 1.4926361862881505, 1.511947564871118, 1.531623424311822, 1.5518379642619582, 1.571415292664728, 1.590956013986232, 1.6101289757746151, 1.629504706812003, 1.6488436799317054, 1.6677873136267631, 1.6871236000102316, 1.7063804384195065, 1.7247788587316706, 1.74385084639364, 1.7632567530747427, 1.7810671017919066, 1.8000204739946506, 1.8187750413718835, 1.8362045669565548, 1.855888986697667, 1.8736099866108273, 1.8919543734165152, 1.9099014671201333, 1.9278705840578851, 1.9459285536685293, 1.9644838792250359, 1.9822046837796143, 1.9995268983422625, 2.0173386661672104, 2.0350303628559123, 2.0527713302473805, 2.0715436512758125, 2.088532979967127, 2.105448870913261, 2.122996752747121, 2.140658402767489, 2.1580900095590096, 2.1754356707821283, 2.19275774398331, 2.211194475389986, 2.232982621587298, 2.2551925602858534, 2.280016719289489, 2.3063211626596343, 2.3350430868315497, 2.3665452139200425, 2.4015454429869205, 2.440733908945748, 2.4858785233713427, 2.5418727931246536, 2.6144084555616858, 2.7959062808147896]
  y = [24.77724034532261, 24.37804249554226, 24.19024290469251, 24.060132497289498, 23.78910669554878, 23.490999254422075, 23.230491536468016, 23.05617339327898, 22.64620165329114, 22.579695553808385, 22.383003610771798, 22.262032410277936, 22.21201767180415, 21.93212194269467, 21.726772939444658, 21.460467444724543, 21.27059568803877, 21.06466968773921, 20.634888404569303, 20.238281789327637, 19.672916110100605, 19.546897202422976, 18.87739359459743, 18.59488191380871, 18.14880392608624, 17.6962994383689, 17.37710441451018, 16.81250842496295, 16.678882667587086, 16.182391499497715, 15.828587302315464, 15.205433904690839, 14.495596165710925, 14.511859823120211, 13.971753232798177, 13.658395498023248, 13.366842896276086, 13.05856744427929, 12.337465723961392, 12.29682965701954, 12.147839110097841, 11.760324551597702, 11.471407424003074, 11.049213704891022, 10.919965795059092, 10.601626749506291, 10.335411804585565, 9.718082839773091, 9.585767093427409, 9.423114689530454, 9.251666562241514, 9.124491493213558, 8.906324740537787, 8.29969595224133, 8.179515265478527, 8.078946904786891, 8.074081206125799, 7.795640184700349, 7.327064345560753, 7.180371145672737, 6.982901221348126, 6.831549776236767, 6.774329916623371, 6.598455395485047, 6.242034228013543, 6.211893244192715, 5.978124228824288, 5.616738659970417, 5.760183273642267, 5.255614400544779, 5.040337222517639, 4.970512178822339, 4.967344687551919, 4.548778129253488, 4.451021806395992, 4.264074612710173, 4.067343853822604, 4.043692161771108, 3.6569324304568642, 3.727811294231763, 3.4954349302961947, 3.345749115417511, 3.2665114375808058, 3.1220011432385397, 2.8973373248698233, 2.853040292102494, 2.713019895460359, 2.573460999432591, 2.4801019159829423, 2.2829226930395405, 2.1913185826611636, 2.0872962418506518, 1.9316795102089115, 1.6848508083758817, 1.5530229534306241, 1.361701873571922, 1.1916682079143257, 1.053122785634863, 0.771132065724789]

  m = log_fit(x, y, 6)

  for j in range(len(x)):
    print x[j], y[j], m[j]
