//                                               -*- C++ -*-
/**
 *  @brief First order polynomial response surface by least square
 *
 *  Copyright 2005-2026 Airbus-EDF-IMACS-ONERA-Phimeca
 *
 *  This library is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This library 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.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "openturns/LinearLeastSquares.hxx"
#include "openturns/LinearEvaluation.hxx"
#include "openturns/ConstantGradient.hxx"
#include "openturns/ConstantHessian.hxx"
#include "openturns/PersistentObjectFactory.hxx"

BEGIN_NAMESPACE_OPENTURNS


CLASSNAMEINIT(LinearLeastSquares)

static const Factory<LinearLeastSquares> Factory_LinearLeastSquares;


/* Default constructor */
LinearLeastSquares::LinearLeastSquares()
  : PersistentObject()
{
  // Nothing to do
}

/* Constructor with parameters */
LinearLeastSquares::LinearLeastSquares(const Sample & dataIn,
                                       const Sample & dataOut)
  : PersistentObject(),
    dataIn_(dataIn),
    dataOut_(0, dataOut.getDimension()),
    constant_(dataOut.getDimension()),
    linear_(dataIn.getDimension(), dataOut.getDimension())
{
  setDataOut(dataOut);
}

/* Virtual constructor */
LinearLeastSquares * LinearLeastSquares::clone() const
{
  return new LinearLeastSquares(*this);
}

/* String converter */
String LinearLeastSquares::__repr__() const
{
  OSS oss;
  oss << "class=" << GetClassName()
      << " name=" << getName()
      << " dataIn=" << dataIn_
      << " dataOut=" << dataOut_
      << " result=" << result_
      << " constant=" << constant_
      << " linear=" << linear_;
  return oss;
}

/* Response surface computation */
void LinearLeastSquares::run()
{
  const UnsignedInteger inputDimension = dataIn_.getDimension();
  const UnsignedInteger outputDimension = dataOut_.getDimension();
  const UnsignedInteger dataInSize = dataIn_.getSize();
  const UnsignedInteger coefficientsDimension = 1 + inputDimension;
  /* Center the data using the empirical mean */
  center_ = dataIn_.computeMean();
  /* Matrix of the least-square problem */
  Matrix componentMatrix(dataInSize, coefficientsDimension);
  /* Matrix for the several right-hand sides */
  Matrix rightHandSides(dataInSize, outputDimension);
  /* For each sample of the input data... */
  for(UnsignedInteger sampleIndex = 0; sampleIndex < dataInSize; ++sampleIndex)
  {
    /* build the componentMatrix */
    /* get the current centered sample x */
    const Point currentSample(dataIn_[sampleIndex] - center_);
    UnsignedInteger rowIndex = 0;
    /* First the constant term */
    componentMatrix(sampleIndex, rowIndex) = 1.0;
    ++rowIndex;
    /* Then the linear term x' */
    for(UnsignedInteger componentIndex = 0; componentIndex < inputDimension; ++componentIndex)
    {
      componentMatrix(sampleIndex, rowIndex) = currentSample[componentIndex];
      ++rowIndex;
    } // linear term
    /* build the right-hand side */
    for(UnsignedInteger outputIndex = 0; outputIndex < outputDimension; ++outputIndex)
    {
      rightHandSides(sampleIndex, outputIndex) = dataOut_(sampleIndex, outputIndex);
    }
  } // each sample
  // Now, solve simultaneously the least-squares solutions for all the outputs
  const Matrix coefficients(componentMatrix.solveLinearSystem(rightHandSides));
  // Fill-in the elements of the meta-model
  for(UnsignedInteger outputComponent = 0; outputComponent < outputDimension; ++outputComponent)
  {
    /* First, the constant term */
    UnsignedInteger coefficientsIndex = 0;
    constant_[outputComponent] = coefficients(coefficientsIndex, outputComponent);
    ++coefficientsIndex;
    /* Second, the linear term */
    for(UnsignedInteger componentIndex = 0; componentIndex < inputDimension; ++componentIndex)
    {
      linear_(componentIndex, outputComponent) = coefficients(coefficientsIndex, outputComponent);
      ++coefficientsIndex;
    } // linear term
  } // output components
  /* Build the several implementations and set them into the response surface */
  Function responseSurface;
  responseSurface.setEvaluation(new LinearEvaluation(center_, constant_, linear_));
  responseSurface.setGradient(new ConstantGradient(linear_));
  responseSurface.setHessian(new ConstantHessian(SymmetricTensor(center_.getDimension(), constant_.getDimension())));
  result_ = MetaModelResult(dataIn_, dataOut_, responseSurface);
}

/* DataIn accessor */
Sample LinearLeastSquares::getInputSample() const
{
  return dataIn_;
}

Sample LinearLeastSquares::getDataIn() const
{
  return dataIn_;
}

/* DataOut accessor */
Sample LinearLeastSquares::getOutputSample() const
{
  return dataOut_;
}

Sample LinearLeastSquares::getDataOut() const
{
  return dataOut_;
}

void LinearLeastSquares::setDataOut(const Sample & dataOut)
{
  if (dataOut.getSize() != dataIn_.getSize()) throw InvalidArgumentException(HERE) << "Error: the output data must have the same size than the input data, here output size=" << dataOut.getSize() << " and input size=" << dataIn_.getSize();
  dataOut_ = dataOut;
}

/* Center accessor */
Point LinearLeastSquares::getCenter() const
{
  return center_;
}

/* Constant accessor */
Point LinearLeastSquares::getConstant() const
{
  return constant_;
}

/* Linear accessor */
Matrix LinearLeastSquares::getLinear() const
{
  return linear_;
}

/* Metamodel accessor */
Function LinearLeastSquares::getMetaModel() const
{
  return result_.getMetaModel();
}

MetaModelResult LinearLeastSquares::getResult() const
{
  return result_;
}

void LinearLeastSquares::save(Advocate & adv) const
{
  PersistentObject::save(adv);
  adv.saveAttribute("result_", result_);
  adv.saveAttribute("center_", center_);
  adv.saveAttribute("constant_", constant_);
  adv.saveAttribute("linear_", linear_);
}

/* Method load() reloads the object from the StorageManager */
void LinearLeastSquares::load(Advocate & adv)
{
  PersistentObject::load(adv);
  adv.loadAttribute("result_", result_);
  adv.loadAttribute("constant_", constant_);
  adv.loadAttribute("linear_", linear_);
  if (adv.hasAttribute("center_"))
    adv.loadAttribute("center_", center_);
  else
    center_ = Point(linear_.getNbRows());
}

END_NAMESPACE_OPENTURNS
