//============================================================================
// Nazwa: inner_product.cpp
// Autor: Joanna Chojecka
// Opis : Przykład użycia funckji std::inner_product.
//============================================================================

/*
 * inner_product to funkcja obliczająca (w ogólności) iloczyn skalarny.
 * W bibliotece stl występuje pod dwoma postaciami, aby umożliwić własną definicję iloczynu skalarnego,
 * która nie musi ograniczać się do najcześciej spotykanego iloczynu wektorów liczb rzeczywistych.
 *
 * Poniższy kod przedstawia jak używać funkcji inner_product w prostej i w zaawansowanej formie.
 *
 */

#include <iostream>
#include <numeric>    // funkcja inner_product
#include <vector>
#include <functional> // szablony binary_function oraz plus
#include <complex>    // liczby zespolone - do demonstracji
#include <math.h>     // funkcje matematyczne - do demonstracji

using namespace std;

/*
 * Obiekt wykonujący operację mnożenia liczby zespolonej z liczbą sprzężoną do podanej.
 * Przypomnienie: liczba zespolona:       c = x + j*y
 *                liczba sprzężona do c: c1 = x - j*y
 */
template<class _Tp> struct multiplies_conj: public binary_function<_Tp, _Tp, _Tp> {
  /*
   *  Przeciążony operator() wykonuje mnożenie pierwszej liczby z liczbą sprzężoną do drugiej.
   * Wykorzystuje funkcję complex<T> conj(complex<T>) zwracającą liczbę sprzężoną do podanej.
   */
  _Tp operator()(const _Tp& __x, const _Tp& __y) const {
    return __x * conj(__y);
  }

};

/*
 * Pomocnicza funkcja drukująca wektor na standardowe wyjście.
 */
template<class T> ostream & printVector(ostream & os, const vector<T> &v){
      os<<"[";
      for(unsigned int i =0; i< v.size(); ++i ){
        os<<v[i];
        if(i!=v.size() -1)
          os<<", ";
      }
      os<<"] ";
        return os;
}


int main() {

  /*
   * Pierwszy wariant inner_product:
   *
   *  T result = inner_product(Iterator1 first1, Iterator1 last1, Iterator2 first2, T init);
   *
   * liczy zwykły iloczyn skalarny w następujący sposób:
   *  inicjalizuje: result = init
   *  dla każdego elementu w zakresie wskazywanych iteratorów:
   *  result =   result + (fist1)* (fist2);
   *
   */

  cout<<"Przykład 1: zwykły iloczyn skalarny dwoch wektorów"<<endl;
  vector<int> v1;
  vector<int> v2;

  v1.push_back(1);
  v1.push_back(2);   // v1 = [ 1, 2 ]

  v2.push_back(2);
  v2.push_back(3);  //  v2 = [ 2, 3 ]

  const int init = 1;

  //inner <= (1 + 1*2 )+ 2*3 = 9
  int inner = inner_product(v1.begin(), v1.end(), v2.begin(), init);

  cout<<"Iloczyn skalarny wektorów ";
  printVector(cout, v1);
  printVector(cout, v2);
  cout<<" = "<<inner<<endl;


  // inner_product liczy tylko to co jest w zakresie iteratorów
  v2.push_back(4);

  //inner2 = (1 + 1*2 )+ 2*3 = 9
  int inner2 = inner_product(v1.begin(), v1.end(), v2.begin(), init);

  cout<<"Iloczyn skalarny wektorów ";
  printVector(cout, v1);
  printVector(cout, v2);
  cout<<" = "<<inner2<<endl;


  // jeśli zakres drugiej kolekcji jest krótszy - inner_product skończy wczesniej
  v2.pop_back();   // skracamy v2 do wartości początkowej
  v1.push_back(5); // wydłużamy v1 o 1 element

  //inner2 = (1 + 1*2 )+ 2*3   = 9
  int inner3 = inner_product(v1.begin(), v1.end(), v2.begin(), init);

  cout<<"Iloczyn skalarny wektorów ";
  printVector(cout, v1);
  printVector(cout, v2);
  cout<<" = "<<inner3<<endl<<endl;

  cout<<"Przykład 2: Jak łatwo policzyć cosinus kąta pomiędzy wektorami? "<<endl;
  vector<double> v3;
  vector<double> v4;

  v3.push_back(1);
  v3.push_back(0);  // v3 = [ 1, 0]

  v4.push_back(1);
  v4.push_back(1);  // v4 = [ 1, 1] wektor pod kątem 45 stopni (PI/4) do v3

  // oznaczmy: "." - iloczyn skalarny
  // |v3|          - norma wektora
  // sqrt          - pierwiastek kwadratowy
  // cos(v3, v4)   - kąt pomiędzy wektorami v3 i v4

  const double zero =0.0;

  // norm3   <= |v3| = sqrt(v3 . v3)
  double norm3 = sqrt(inner_product(v3.begin(), v3.end(), v3.begin(), zero));

  // norm4   <= |v4| = sqrt(v4 . v4)
  double norm4 = sqrt(inner_product(v4.begin(), v4.end(), v4.begin(), zero));

  // inner   <= v3 . v4
  double inner4 = inner_product(v3.begin(), v3.end(), v4.begin(), zero);

  // cosinus <= cos(v3, v4) = v3 . v4 /(|v3| * |v4|)
  double cosinus = inner4/(norm3*norm4);

  cout<< "Cosinus kąta pomiędzy wektorami ";
  printVector(cout, v3);
  printVector(cout, v4);
  cout<<" = "<<cosinus<<", cos(PI/4) = "<< cos(M_PI/4) <<endl<<endl;

  /*
   * Drugi wariant inner_product
   * T result = inner_product(Iterator1 first1, Iterator1 last1, Iterator2 first2, T init, BinaryOperation1, BinaryOperation2);
   * liczy iloczyn skalarny w następujący sposób:
   *  inicjalizuje: result = init
   *  dla każdego elementu w zakresie wskazywanych iteratorów:
   *  result =  BinaryOperation2( result , BinaryOperation1(fist1, fist2));
   *
   */

  cout<<"Przykład 3: Obliczmy iloczyn skalarny wektorów liczb zespolonych"<<endl;

  //Weźmy liczby zespolone całkowitoliczbowe:
  typedef complex<int> ComplexInt;

  vector<ComplexInt> c1;
  vector<ComplexInt> c2;

  c1.push_back(ComplexInt(1, 0));
  c1.push_back(ComplexInt(3, 2));  // c1 = [ 1, 3 + 2j]

  c2.push_back(ComplexInt(4, 5));
  c2.push_back(ComplexInt(3, -1));   // c2 = [ 4 + 5j, 3 -j]

  const ComplexInt complexZero(0, 0);

  // Iloczyn skalarny otrzymuje się mnożąc współrzędne pierwszego wektora
  // przez sprzężone współrzędne drugiego wektora.

  // Jako dodatkowe argumenty do inner_product zostają podane 2 funkcje:
  // plus<T>              - dodaje dwie liczby
  // multiplies_conj<T>   - mnoży pierwszą liczbę przez liczbę sprzężoną do drugiej

  // complex <=  0 + ( 1*(4-5j) + ( (3+2j)*(3+j) ) ) = 4 + 9 - 2 - 5j + 3j + 6j = 11 +4j
  ComplexInt complexInner = inner_product(c1.begin(), c1.end(), c2.begin(), complexZero,
                                plus<ComplexInt>(), multiplies_conj<ComplexInt>());

 cout<<"Iloczyn skalarny wektorów liczb zespolonych";
 printVector(cout, c1);
 printVector(cout, c2);
 cout<<" = "<<complexInner<<endl;


  return 0;
}

