// ---------------------------------------------------------------------------
// - Hilbert.cpp                                                             -
// - afnix:mth module - hilbert space algorithm implementation               -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Hilbert.hpp"
#include "Exception.hpp"
#include "transient.tcc"

namespace afnix {

  // check that a matrix is unitary

  bool Hilbert::unitary (const Cmi& m) {
    // extract operating size
    t_long rsiz = m.getrsiz ();
    t_long csiz = m.getcsiz ();
    if (rsiz != csiz) return false;
    // check for null
    long size = rsiz;
    if (size == 0L) return false;
    // loop in locked mode
    bool result = true;
    #ifdef _OPENMP
    #pragma omp parallel for private(result)
    #endif
    for (t_long i = 0; i < size; i++) {
      for (t_long j = 0; j < size; j++) {
	Complex z = 0.0;
	for (t_long k = 0; k < size; k++) {
	  z += m.nlget (i, k) * m.nlget(j, k).tocjv();
	}
	// check for diagonal
	if (i == j) result = result && z.cmp (1.0);
	if (i != j) result = result && z.cmp (0.0);
      }
    }
    return result;
  }

  // compute the tensor product of two vectors |x><y|

  void Hilbert::product (Cmi& m, const Cvi& x, const Cvi& y) {
    // extract operating size
    long rsiz = m.getrsiz ();
    long csiz = m.getcsiz ();
    if ((x.getsize () != rsiz) || (y.getsize () != csiz)) {
      throw Exception ("matrix-error", "inconsistent product matrix size");
    }
    // check for null
    if ((rsiz == 0L) || (csiz == 0)) return;
    #ifdef _OPENMP
    #pragma omp parallel for
    #endif
    for (t_long i = 0; i < rsiz; i++) {
      Complex zx = x.nlget (i);
      for (t_long j = 0; j < csiz; j++) {
	Complex zy = y.nlget(j);
	m.nlset (i, j, zx * zy.tocjv());
      }
    }
  }

  // compute the tensor product of two matrices

  void Hilbert::product (Cmi& mr, const Cmi& mx, const Cmi& my) {
    // extract operating size
    long rsiz = mr.getrsiz (); long csiz = mr.getcsiz ();
    long mxrs = mx.getrsiz (); long mxcs = mx.getcsiz ();
    long myrs = my.getrsiz (); long mycs = my.getcsiz ();
    if (((mxrs * myrs) != rsiz) || ((mxcs * mycs) != csiz)) {
      throw Exception ("matrix-error", "inconsistent product matrix size");
    }
    // loop in the mx matrix
    #ifdef _OPENMP
    #pragma omp parallel for
    #endif
    for (long xi = 0L; xi < mxrs; xi++) {
      for (long xj = 0L; xj < mxcs; xj++) {
	// get mx element
	Complex xz = mx.nlget (xi, xj);
	// loop in the my matrix
	for (long yi = 0L; yi < myrs; yi++) {
	  for (long yj = 0L; yj < mycs; yj++) {
	    // get my element
	    Complex yz = my.nlget (yi, yj);
	    // compute result index
	    long ri = xi * myrs + yi;
	    long rj = xj * mycs + yj;
	    // compute matrix result
	    Complex rz = xz * yz;
	    // update target matrx
	    mr.nlset (ri, rj, rz);
	  }
	}
      }
    }
  }
}
