;+
;Procedure: tvector_rotate
;
;Purpose:  rotates a set of vectors by a set of coordinate
;          transformation matrices inputs and outputs are tplot
;          variables.  This is designed mainly for use with
;          fac_matrix_make.pro and minvar_matrix_make, but can be used
;          for more general purposes
;
;Warning:
; The transformation matrices generated by
;fac_matrix_make,thm_fac_matrix_make, and minvar_matrix_make are
;defined relative to a specific coordinate system.  This means that if
;you use vectors in one coordinate system to generate the rotation
;matrices they will only correctly transform data from that coordinate
;system into the functional coordinate system.  For example if you use
;magnetic field data in gsm to generate Rgeo transformation matrices
;using fac_matrix_make then the vectors being provided to tvector
;rotate to be transformed by those matrices should only be in gsm coordinates.

;
;Arguments:
;
; mat_var_in: the name of the tplot variable storing input matrices
; The y component of the tplot variable's data struct should be
; an Mx3x3 array, storing a list of transformation matrices. 
; 
;
; vec_var_in: the name of the tplot variable storing input
;vectors. You can use globbing to rotate several tplot variables
;storing vectors with a single matrix. The y component of the tplot variable's
; data struct should be an Nx3 array.  
;
;
;
; newname(optional): the name of the output variable, defaults to 
;                    vec_var_in + '_rot'
;                    If you use type globbing in the vector variable
;                    This option will be disabled
;
; error(optional): named variable in which to return the error state
; of the computation.  1 = success 0 = failure
;
;
;CALLING SEQUENCE:
;
; tvector_rotate,'matrix_var','vector_var',newname = 'out_var',error=error
;
; tvector_rotate,'matrix_var','vector_var'
;
;Notes: 
;   mat_var_in should store rotation matrices.  (ie the columns of any
;   matrix in mat_var_in should form an orthonormal basis)
;
;   If M!=N M must be >= 2 (where M,N refer to the dimensions of the
;   input variables.
;
;   Also If the x components of mat_var_in and vec_var_in data structs
;   do not match then. The matrices will be interpolated to match the
;   cadence of the vector data.  Interpolation is done by turning the
;   matrices into quaternions and performing spherical linear
;   interpolation.  As noted above this interpolation behavior will
;   not be predictable if the matrices do anything other than rotate.
;
;   The user should note that if the timestamps of mat_var_in are not
;   monotonic(ascending or identical) or if the timestamps vec_var_in are not strictly
;   monotonic(always ascending) the program will not work correctly.  It is the user's 
;   responsibility to remove duplicate or non sequential timestamps from the data 
;
;   PROGRAMMERS: you might want to consider adding a check to 
;   verify that input matrices are actually rotation matrices
;   this would just verify that M dot transpose(M) = Identity matrix
;
; SEE ALSO:  mva_matrix_make.pro, fac_matrix_make.pro
;
;
; $LastChangedBy: pcruce $
; $LastChangedDate: 2009-03-30 14:17:08 -0700 (Mon, 30 Mar 2009) $
; $LastChangedRevision: 5454 $
; $URL: svn+ssh://thmsvn@ambrosia.ssl.berkeley.edu/repos/ssl_general/trunk/cotrans/special/tvector_rotate.pro $
;-


;helper function
;calculates the norm of a bunch of vectors simultaneously
function ctv_norm_vec_rot,v

COMPILE_OPT HIDDEN

if not keyword_set(v) then return, -1L

if size(v,/n_dim) ne 2 then return,-1L

return, sqrt(total(v^2,2))

end

;helper function
;normalizes a bunch of vectors simultaneously
function ctv_normalize_vec_rot,v

COMPILE_OPT HIDDEN

if not keyword_set(v) then return, -1L

if size(v,/n_dim) ne 2 then return,-1L

n_a = ctv_norm_vec_rot(v)

if(size(n_a,/n_dim) eq 0 && n_a eq -1L) then return,-1L

v_s = size(v,/dimension)

;calculation is pretty straight forward
;we turn n_a into an N x D so computation can be done element by element
n_b = rebin(n_a,v_s[0],v_s[1])

return,v/n_b

end

;helper function
;vectorized fx to multiply n matrices by n vectors
function ctv_mx_vec_rot,m,x

COMPILE_OPT HIDDEN

;input checks
if(not keyword_set(m)) then return, -1L

if(not keyword_set(x)) then return, -1L

m_s = size(m,/dimension)

x_s = size(x,/dimension)

;make sure number of dimensions in input arrays is correct
if(n_elements(m_s) ne 3) then return, -1L

if(n_elements(x_s) ne 2) then return, -1L

;make sure dimensions match
if(not array_equal(x_s,[m_s[0],m_s[1]])) then return,-1L

if(not array_equal(m_s,[x_s[0],x_s[1],x_s[1]])) then return,-1L

;calculation is pretty straight forward
;we turn x into an N x 3 x 3 so computation can be done element by element
y_t = rebin(x,x_s[0],x_s[1],x_s[1])

;custom multiplication requires rebin to stack vector across rows,
;not columns
y_t = transpose(y_t, [0, 2, 1])

;9 multiplications and 3 additions per matrix
y = total(y_t*m,3)

return, y

end

pro tvector_rotate,mat_var_in,vec_var_in,newname = newname,error=error

if keyword_set(error) then error = 0

if not keyword_set(mat_var_in) then begin
    message,/continue,'FX requires the matrix variable to be set'
    return
endif

if tnames(mat_var_in) eq '' then begin
    message,/continue,'FX requires the matrix variable to be set'
    return
endif

if not keyword_set(vec_var_in) then begin
    message,/continue,'FX requires vector variable to be set'
    return
endif

tn = tnames(vec_var_in)

if size(tn,/n_dim) eq 0 && tn eq '' then begin
    message,/continue,'FX requires vector variable to be set'
    return
endif

if not keyword_set(newname) then $
  newname = [vec_var_in + '_rot'] $
else $
  newname = [newname]


if n_elements(tn) gt 1 then newname = tn + '_rot'


;loop over possibly many vectors
for i = 0,n_elements(tn) -1L do begin

    ;tinterpol_mxn,mat_var_in,tn[i],newname = mat_var_in+'_interp_temp',error=error_state

   ;load the matrices and validate dimensions
   get_data,mat_var_in,data=m_d,dlimits=m_dl ;rotation matrices and input abcissa values
   
   m_d_s = size(m_d.y,/dimensions)

   if(n_elements(m_d_s) ne 3 || m_d_s[1] ne 3 || m_d_s[2] ne 3) then begin

      message,/continue,'FX requires matrix data to contain an Nx3x3 array'
      return
   endif

   ;load the vectors and validate dimensions
   get_data,tn[i],data=v_d,dlimits=v_dl ;output abcissa values
   
   v_d_s = size(v_d.y,/dimensions)

   if(n_elements(v_d_s) ne 2 || v_d_s[1] ne 3) then begin
      message,/continue,'FX requires vector data to contain an Nx3 Array'
      return
   endif

   idx = where(m_d.x ne v_d.x)

   if n_elements(m_d.x) eq n_elements(v_d.x) && idx[0] eq -1 then begin

      m_d_y = m_d.y

   endif else begin
      
      if n_elements(m_d.x) gt 1 then begin
      
        idx = where((m_d.x[1:n_elements(m_d.x)-1]-m_d.x[0:n_elements(m_d.x)-2]) lt 0)

        if(idx[0] ne -1) then begin

           message,'matrix timestamps not monotonic please remove any non-ascending timestamps from your data'

        endif
      endif
      
      if n_elements(v_d.x) gt 1 then begin
    
        idx = where((v_d.x[1:n_elements(v_d.x)-1]-v_d.x[0:n_elements(v_d.x)-2]) le 0)

        if(idx[0] ne -1) then begin

           message, 'vector timestamps not monotonic please remove any non-ascending or repeated timestamps from your data'

        endif
        
      endif
   
      ;using quaternion library transform into quanternions
      q_in = mtoq(m_d.y)

      ;interpolate quaternions
      q_out = qslerp(q_in,m_d.x,v_d.x)

      ;turn quaternions back into matrices
      m_d_y = qtom(q_out)

   endelse

   if(size(m_d_y,/n_dim) eq 0 && m_d_y[0] eq -1L) then begin

      message,/continue,'failed to interpolate rotation matrix array'

      return

   endif

   ;rotate vectors using matrices
   v_t = ctv_mx_vec_rot(m_d_y,v_d.y)
   
   if(size(v_t,/n_dim) eq 0 && v_t eq -1L) then begin
      message,/continue,'FX failed to perform mx operation'
      return
   endif

   ;update meta data
   ;and store output

    ;if the vector dlimits struct is unavailable create one
    if(size(v_dl,/n_dim) eq 0 && v_dl eq 0) then begin
        message,/continue,'dlimits for input vector should be set'
        message,/continue,'Creating default dlimits structure'

        v_data_att = {coord_sys:'undefined',units:''}
        v_dl = {data_att:v_data_att,labflag:0,labels:make_array(v_d_s[1],/string,value='')}

    endif

    ;if the matrix dlimits struct is unavailable create one
    if(size(m_dl,/n_dim) eq 0 && m_dl eq 0) then begin
        message,/continue,'m_dl coord_sys unset, unable to make coordinate system determination'
        
        m_data_att = {coord_sys:'undefined',units:''}
        m_dl = {data_att:m_data_att,labflag:0,labels:make_array(v_d_s[1],/string,value='')}
    endif


    ;make sure it has data_att
    str_element,v_dl,'data_att',SUCCESS=s

    if(s eq 0) then begin

        v_data_att = {coord_sys:'undefined',units:''}
        
        str_element,/add,v_dl,'data_att',v_data_att

    endif

    ;make sure it has ytitle

    str_element,v_dl,'ytitle',SUCCESS=s

    if(s eq 0) then begin
 
        v_ytitle = ''

        str_element,/add,v_dl,'ytitle',v_ytitle

    endif

    ;make sure it has appropriate data_att elements
    str_element,v_dl.data_att,'coord_sys',SUCCESS=s

    if(s eq 0) then begin

        v_data_att = v_dl.data_att 

        str_element,/add,v_data_att,'coord_sys','undefined'

        str_element,/add,v_dl,'data_att',v_data_att

        
    endif

    str_element,v_dl.data_att,'units',SUCCESS=s

    if(s eq 0) then begin

        v_data_att = v_dl.data_att 

        str_element,/add,v_data_att,'units',''

        str_element,/add,v_dl,'data_att',v_data_att

    endif

    v_dl.data_att.coord_sys = m_dl.data_att.coord_sys
    v_dl.data_att.units = v_dl.data_att.units+'_rot'
    v_dl.ytitle = newname[i] + '!C' + v_dl.data_att.units 

    ;no longer take labels from transformation matrix
    ;if m_dl.labflag eq 1 then v_dl.labels = m_dl.labels

    str_element,v_d,'v',SUCCESS=s

    if(s) then $
      v_d = {x:v_d.x,y:v_t,v:v_d.v} $
    else $
      v_d = {x:v_d.x,y:v_t} 

    store_data,newname[i],data=v_d,dlimits = v_dl

endfor

error = 1

return

end
