/***************************************************************************
**
**  This file is part of QGpCoreWave.
**
**  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 2.1 of the License, or (at your option) any later version.
**
**  This file 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, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2004-04-28
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include <QGpCoreTools.h>
#include "Seismic1DModel.h"

namespace QGpCoreWave {

  /*!
    \class Seismic1DModel::SeismicContext Seismic1DModel.h
    \brief Brief description of class still missing

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  Seismic1DModel::SeismicContext::SeismicContext()
  {
    TRACE;
    addVariable( "n", new Seismic1DModel::VariableN(this));
    addVariable( "h", new Seismic1DModel::VariableH(this));
    addVariable( "vp", new Seismic1DModel::VariableVp(this));
    addVariable( "vs", new Seismic1DModel::VariableVs(this));
    addVariable( "rho", new Seismic1DModel::VariableRho(this));
    addVariable( "qp", new Seismic1DModel::VariableQp(this));
    addVariable( "qs", new Seismic1DModel::VariableQs(this));
  }

  QString Seismic1DModel::SeismicContext::helpCode() const
  {
    TRACE;
    return "   n      Number of layers in model\n"
           "   h[i]   Thickness for layer i [0,n-2]\n"
           "   vp[i]  P-wave velocity for layer i [0,n-1]\n"
           "   vs[i]  S-wave velocity for layer i [0,n-1]\n"
           "   rho[i] Density for layer i [0,n-1]\n"
           "   qp[i]  P-wave attenuation for layer i [0,n-1]\n"
           "   qs[i]  S-wave attenuation for layer i [0,n-1]\n";
  }

  QVariant Seismic1DModel::VariableN::value(const QString&) const
  {
    TRACE;
    return _context->model()->layerCount();
  }

  QVariant Seismic1DModel::VariableH::value(const QString& index) const
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()-1) {
      return m->h(i);
    } else {
      return QVariant();
    }
  }

  void Seismic1DModel::VariableH::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()-1) {
      m->setH(i, val.toDouble());
    }
  }

  QVariant Seismic1DModel::VariableVp::value(const QString& index) const
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      return 1.0/m->slowP(i);
    } else {
      return QVariant();
    }
  }

  void Seismic1DModel::VariableVp::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      m->setSlowP(i, 1.0/val.toDouble());
    }
  }

  QVariant Seismic1DModel::VariableVs::value(const QString& index) const
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      return 1.0/m->slowS(i);
    } else {
      return QVariant();
    }
  }

  void Seismic1DModel::VariableVs::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      m->setSlowS(i, 1.0/val.toDouble());
    }
  }

  QVariant Seismic1DModel::VariableRho::value(const QString& index) const
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      return m->rho(i);
    } else {
      return QVariant();
    }
  }

  void Seismic1DModel::VariableRho::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      m->setRho(i, val.toDouble());
    }
  }

  QVariant Seismic1DModel::VariableQp::value(const QString& index) const
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      return m->qp(i);
    } else {
      return QVariant();
    }
  }

  void Seismic1DModel::VariableQp::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      m->setQp(i, val.toDouble());
    }
  }

  QVariant Seismic1DModel::VariableQs::value(const QString& index) const
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      return m->qs(i);
    } else {
      return QVariant();
    }
  }

  void Seismic1DModel::VariableQs::setValue(const QString& index, const QVariant& val)
  {
    TRACE;
    Seismic1DModel * m=_context->model();
    bool ok;
    int i=index.toInt(&ok);
    if(ok && i>=0 && i<m->layerCount()) {
      m->setQs(i, val.toDouble());
    }
  }

  /*!
    \class Seismic1DModel Seismic1DModel.h
    \brief Storage class for an horizontaly layered ground model.

    Seismic1DModel stores data vectors for thicknesses, P-wave and S-wave
    slownesses, and densities of each layer. A series of checks and information
    functions are implemented.

    The same data vectors can be shared automatically using the copy constructor between
    several instances of Seismic1DModel.
  */

  /*!
    Default constructor

    Only called by AnelasticSeismic1DModel.
  */
  Seismic1DModel::Seismic1DModel()
  {
    TRACE;
    _layerCount=0;
    initMembers();
  }

  /*!
    Copy constructor
  */
  Seismic1DModel::Seismic1DModel(const Seismic1DModel& o)
     : GeophysicalModel(o)
  {
    TRACE;
    _layerCount=0;
    initMembers();
    operator=(o);
  }

  /*!
    Constructor: inits the number of layers including half space.
  */
  Seismic1DModel::Seismic1DModel(int layerCount)
  {
    TRACE;
    // Sharing initialization
    _layerCount=layerCount;
    initMembers();
    allocateData();
  }

  /*!
    Create a model from value vectors, used by Fortran interface.
  */
  Seismic1DModel::Seismic1DModel(int layerCount, double * h, double * vp, double * vs, double * rho)
  {
    TRACE;
    _layerCount=layerCount;
    initMembers();
    allocateData();
    int i=_layerCount-1;
    _slowP[i]=1.0/vp[i];
    _slowS[i]=1.0/vs[i];
    _rho[i]=rho[i];
    _qp[i]=0.0;
    _qs[i]=0.0;
    for(i--; i>=0; i--) {
      _h[i]=h[i];
      _slowP[i]=1.0/vp[i];
      _slowS[i]=1.0/vs[i];
      _rho[i]=rho[i];
      _qp[i]=0.0;
      _qs[i]=0.0;
    }
    initCalculation();
  }

  /*!
    Create a model from value vectors, used by Fortran interface.
    For Love, Vp is not provided and set as the double of Vs to avoid warning during validity checks.
  */
  Seismic1DModel::Seismic1DModel(int layerCount, double * h, double * vs, double * rho)
  {
    TRACE;
    _layerCount=layerCount;
    initMembers();
    allocateData();
    int i=_layerCount-1;
    _slowP[i]=0.5/vs[i];
    _slowS[i]=1.0/vs[i];
    _rho[i]=rho[i];
    _qp[i]=0.0;
    _qs[i]=0.0;
    for(i--; i>=0; i--) {
      _h[i]=h[i];
      _slowP[i]=0.5/vs[i];
      _slowS[i]=1.0/vs[i];
      _rho[i]=rho[i];
      _qp[i]=0.0;
      _qs[i]=0.0;
    }
    initCalculation();
  }

  /*!
    Destructor: remove data vectors.
  */
  Seismic1DModel::~Seismic1DModel()
  {
    TRACE;
    deleteData();
  }

  /*!
    Initialize internal members

    Internal use only.
  */
  void Seismic1DModel::initMembers()
  {
    _h=nullptr;
    _slowP=nullptr;
    _slowS=nullptr;
    _rho=nullptr;
    _mu=nullptr;
    _qp=nullptr;
    _qs=nullptr;
  }

  /*!
    Allocates all data vectors (_layerCount must be initialized before)

    Internal use only.
  */
  void Seismic1DModel::allocateData()
  {
    TRACE;
    deleteData();
    // allocate the data vectors
    if(_layerCount>0) {
      if(_layerCount>1) {
        _h=new double [_layerCount-1];
      }
      _slowP=new double [_layerCount];
      _slowS=new double [_layerCount];
      _rho=new double [_layerCount];
      _mu=new double [_layerCount];
      _qp=new double [_layerCount];
      _qs=new double [_layerCount];
    }
  }

  /*!
    Delete all data vectors

    Internal use only.
  */
  void Seismic1DModel::deleteData()
  {
    TRACE;
    delete [ ] _h;
    delete [ ] _slowP;
    delete [ ] _slowS;
    delete [ ] _rho;
    delete [ ] _mu;
    delete [ ] _qp;
    delete [ ] _qs;
    _h=nullptr;
    _slowP=nullptr;
    _slowS=nullptr;
    _rho=nullptr;
    _mu=nullptr;
    _qp=nullptr;
    _qs=nullptr;
  }

  /*!
    Copy oprator
  */
  void Seismic1DModel::operator=(const Seismic1DModel& o)
  {
    deleteData();
    _layerCount=o._layerCount;
    allocateData();
    int i=_layerCount-1;
    _slowP[i]=o._slowP[i];
    _slowS[i]=o._slowS[i];
    _rho[i]=o._rho[i];
    _mu[i]=o._mu[i];
    _qp[i]=o._qp[i];
    _qs[i]=o._qs[i];
    for(i--; i>=0; i--) {
      _h[i]=o._h[i];
      _slowP[i] =o. _slowP[i];
      _slowS[i]=o._slowS[i];
      _rho[i]=o._rho[i];
      _mu[i]=o._mu[i];
      _qp[i]=o._qp[i];
      _qs[i]=o._qs[i];
    }
    // Calculated variables:
    _slowSMin=o._slowSMin;
    _slowSMax=o._slowSMax;
    _maxRayleighSlowness=o._maxRayleighSlowness;
  }

  /*!
    Calculates the basic parameters for future calculations and test model
    validity.
  */
  bool Seismic1DModel::initCalculation()
  {
    TRACE;
    bool ret=true;
    // Initialize calculation variables:
    for(int i=0;i<_layerCount;i++) {
      if(_slowS[i]!=0) _mu[i]=_rho[i]/ (_slowS[i]*_slowS[i]);
    }
    // Gets index of the layer with beta min or _slowS max
    int iSlowSMax;
    slowMinMax(iSlowSMax);
    if(!checkPoissonRatio()) ret=false;
    _maxRayleighSlowness=1.05*halfSpaceRayleighSlowness(iSlowSMax);
    return ret;
  }

  /*!
    Checks that the model respects the physic conditions over Poisson's ratio.
    All geological materials have a positive Poisson's ratio less than 0.5

    Poisson'ratio=\f$(V_s^2-0.5*V_p^2)/(V_s^2-V_p^2)\f$

    This means that \f$V_s\f$ should be between \f$0\f$ and \f$\sqrt{2} V_p\f$.
    A null value for \f$V_s\f$ is not accepted in this case (no liquid layer).

    Returns true if the model satisfy these condition.

    Internal use only.\n
  */
  bool Seismic1DModel::checkPoissonRatio()
  {
    TRACE;
    for(int i=0;i<_layerCount;i++) {
      double sp=_slowP[i];
      double ss=_slowS[i];
      if(ss<=0 || sp*::sqrt(2.0)>=ss) {
        double sp2=sp*sp;
        double ss2=ss*ss;
        double nu=(sp2-0.5*ss2)/(sp2-ss2);
        App::log(tr("Bad Poisson's ratio or bad Vs at layer %1 (nu=%2, vs=%3)\n").arg(i).arg(nu).arg(1.0/ss) );
        return false;
      }
    }
    return true;
  }

  /*!
    Finds the layer index with the minimum shear-wave Velocity (or maximum shear-wave slowness).

    Internal use only.\n
  */
  void Seismic1DModel::slowMinMax(int& iSlowSMax)
  {
    TRACE;
    _slowSMin=std::numeric_limits<double>::infinity();
    _slowSMax=-std::numeric_limits<double>::infinity();
    for(int i=0;i<_layerCount;i++) {
      if(_slowS[i]>_slowSMax) {
        _slowSMax=_slowS[i];
        iSlowSMax=i;
      }
      // Not meaningful, only the slowness of the half space is critical
      // Still don't know found cases with LVZ where half space slowness is not the minimum ofdispersion curve
      if(_slowS[i]<_slowSMin) _slowSMin=_slowS[i];
    }
    // The minimum slowness usefull for dispersion computation is the slowness of the half space
    //_slowSMin=_slowS[_layerCount-1];
  }

  /*!
    Calculates the Rayleigh-wave velocity as if the model was homogeneous and
    had the velocity of the layer \a iLayer.

    sq_cob means (c/b)^2 and sq_boa means (b/a)^2.

    Equation : (2-sq_cob)-4*sqrt(1-sq_boa*sq_cob)*sqrt(1-sq_cob)=0

    The sqrt can be elevated to square, interiors are positive:
    [(2-x)]=16* (1-sq_boa*x) * (1-x), where x=sq_cob

    Calculating all terms:
    x * [x-8*x+(24-16*sq_boa) *x+(16*sq_boa-16)]=0

    x=0 is not a valid solution
    Thus we are looking for the roots of:
    x-8*x+a3*x+a4
    where,
       a4=16*sq_boa-16
       a3=8-a4

    3rd degree poly which solutions are calculated by newton raphson
    using the derivative : 3*x-16*x+a3

    This poly has a simple root in the neighborhood of the starting
    value (for all boa), thus there is no problem of convergence
  */
  double Seismic1DModel::halfSpaceRayleighSlowness(int iLayer) const
  {
    TRACE;
    double y,x=0.9025;
    /* (c/beta)=0.95 solution for a=std::numeric_limits<double>::infinity(), this is a maximum bound
       value for the Rayleigh velocity
    */
    double boa=_slowP[iLayer]/_slowS[iLayer];
    double a4=16*boa*boa-16;
    double a3=8-a4;
    do {
      double sqx=x*x;
      y=x*sqx-8*sqx+a3*x+a4;
      double dy=3*sqx-16*x+a3;
      x-=y/dy;
    } while(fabs(y)>1e-10);
    // This condition over y achieves a precision over x of about 1e-15
    return _slowS[iLayer]/sqrt(x);
  }

  /*!
    Return the ellipticity of half-space with the properties of layer \a iLayer.
    From Boore and Toksoz (1969, equation 2), BSSA

    If Qs and Qp are not available, they are supposed to be infinite.
  */
  Complex Seismic1DModel::halfSpaceEllipticity(int iLayer) const
  {
    TRACE;
    // Compute Rayleigh phase velocity
    double c=halfSpaceRayleighSlowness(iLayer);
    double b=_slowS[iLayer]/c;
    b*=b;
    double a= _slowP[iLayer]/c;
    a*=a;
    double m=a*(2-b)*(1-b)/(a*(2-b)*(1-b)-b*(1-a)*(2-3*b));
    double qs=_qs[iLayer];
    double qp=_qp[iLayer];
    if(qs>0.0) {
      qs=1.0/qs;
    }
    if(qp>0.0) {
      qp=1.0/qp;
    }
    return Complex(-(a*(2-b)+m*(2*b-2*a-a*b))/(4*(1-a)*::sqrt(1-a))*(qs-qp), -(2-b)/(2.0*::sqrt(1-a)));
  }

  /*!
    Checks velocity inversions on Vp and Vs.
    The return value is -1 if no LVZ is found, else the first layer index where the condition is not satisfied.
  */
  int Seismic1DModel::checkVelocityInversion() const
  {
    TRACE;
    for(int i=1;i<_layerCount;i++) {
      if(_slowS[i]>_slowS[i-1] || _slowP[i]>_slowP[i-1]) {
        return i;
      }
    }
    return -1;
  }

  /*!
   \fn  int Seismic1DModel::n() const
   Returns the number of layers.
  */

  /*!
   \fn  double * Seismic1DModel::h(int iLayer)
   Returns the thickness of layer \a iLayer.
  */

  /*!
   \fn  double * Seismic1DModel::slowP(int iLayer)
   Returns the P-wave slownes of layer \a iLayer.
  */

  /*!
   \fn  double * Seismic1DModel::slowS(int iLayer)
   Returns the S-wave slowness of layer \a iLayer.
  */

  /*!
   \fn  double * Seismic1DModel::rho(int iLayer)
   Returns the density of layer \a iLayer.
  */

  /*!
   \fn  double * Seismic1DModel::mu(int iLayer)
   Returns the  of layer \a iLayer.
  */

  /*!
    Returns the depth of the top of layer with index \a ilayer.
  */
  double Seismic1DModel::depth(int iLayer) const
  {
    TRACE;
    if(iLayer>=_layerCount) {
      iLayer=_layerCount-1;
    }
    double d=0;
    for(int i=0;i<iLayer;i++) {
      d+=_h[i];
    }
    return d;
  }

  /*!
    When a sub-stack of layers has been generated with a power law exponent,
    this function tries to recalculate the exponent from the values of S-wave
    slowness and thicknesses.
  */
  double Seismic1DModel::expGrad(int iLayer) const
  {
    TRACE;
    if(iLayer>=_layerCount-1) {
      iLayer=_layerCount-2;
    }
    if(iLayer<0) {
      return 0.0;
    }
    double v1=1.0/_slowS[iLayer];
    double v2=1.0/_slowS[iLayer+1];
    double z0=depth(iLayer)+1;
    double z1=z0+0.5*_h[iLayer];
    double z2=z0+_h[iLayer]+0.5*_h[iLayer+1];
    double v21=v2/v1;
    double v211=v21-1;
    /* The problem is to solve f(alpha)=z2^alpha-v2/v1*z1^alpha+(v2/v1-1)*z0^alpha=v2/v1-1
       By Newton-Raphson the process is not stable enough, if transformed into log => too slow
       As f(0)<c and f(1)>c, and that the df/dalpha!=0 there's only one root, the solution
       By bisection it'is the best.
    */
    double a1=0;
    double a2=1;
    double a3=0.5;
    double f3=pow(z2,a3)-v21*pow(z1,a3)+v211*pow(z0,a3);
    do {
      if(f3<v211) a1=a3; else a2=a3;
      a3=0.5*(a1+a2);
      f3=pow(z2,a3)-v21*pow(z1,a3)+v211*pow(z0,a3);
    } while(f3!=v211 && a2-a1>1e-5);
    return a3;
  }

  /*!
    \fn Seismic1DModel::roughFundamentalFrequency()
    Returns an estimation of the fundamental resonnance frequency

    \f[\frac {V_{soft}}{4 H_t}\f]

    \f$V_{soft}\f$ is the average velocity

    \f$H_t\f$ is the total thickness of layers except the half space.
  */
  double Seismic1DModel::roughFundamentalFrequency() const
  {
    // TODO: replace by a more accurate estimation of resonance frequency
    Profile p=slowsProfile();
    p.average();
    return p.values()[_layerCount-1]/(4*depth(_layerCount-1));
  }

  /*!
    Returns Poisson's ratio for layer \a iLayer
  */
  double Seismic1DModel::poisson(int iLayer) const
  {
    TRACE;
    double slowP2=_slowP[iLayer];
    slowP2 *= slowP2;
    double slowS2=_slowS[iLayer];
    slowS2 *= slowS2;
    return (slowP2-0.5*slowS2)/(slowP2-slowS2);
  }

  /*!
    Returns true if model is elastic (quality factors all set to 0)
  */
  bool Seismic1DModel::elastic() const
  {
    for(int i=_layerCount-1;i>=0;i--) {
      if(_qp[i]>0.0 || _qs[i]>0.0) return false;
    }
    return true;
  }

  bool Seismic1DModel::setValue(double& var, const StringSection& field, int iLayer, const QString& varName, bool optional)
  {
    TRACE;
    bool ok=true;
    if(field.isValid()) {
      var=field.toDouble(&ok);
      if(ok && var>0.0) {
        return true;
      } else {
        App::log(tr("Bad value for %1 at layer %2: %3\n").arg(varName).arg(iLayer).arg(var) );
      }
    } else if(!optional) {
      App::log(tr("Missing value for %1 at layer %2\n").arg(varName).arg(iLayer) );
    }
    return false;
  }

  QString Seismic1DModel::formatHelp()
  {
    TRACE;
    return "  Line 1    <number of layers including half-space for first model>\n"
           "  Line 2    <thickness (m)> <Vp (m/s)> <Vs (m/s)> <Density (kg/m3)>[ <Qp> <Qs>]\n"
           "  ....\n"
           "  Line n    0 <Vp (m/s)> <Vs (m/s)> <Density (kg/m3)>[ <Qp> <Qs>]\n"
           "  Line n+1  <number of layers including half-space for second model>\n"
           "  ....";
  }

  /*!
    Loads model from a text stream

      Format:
        \verbatim
        layerCount
        h Vp Vs rho Qp Qs (layer 1)
        ...
        0 Vp Vs rho Qp Qs (layer n)
        \endverbatim

    Qp and Qs are currently ignored.

    Returns false if an error occured during reading or if the read model is not consistent.
    It returns true even if the number of layers is null or cannot be read. This is the responsability
    of the caller to test if the number of layers is greater than 1 before doing anything else.
  */
  bool Seismic1DModel::fromStream(QTextStream& s, QString * comments)
  {
    TRACE;
    // read the number of layers
    StringSection field, fields;
    const QChar * ptr;
    QString buf;
    buf=File::readLineNoComments(s, comments);
    fields.set(buf);
    fields.trimmed();
    ptr=nullptr;
    field=fields.nextField(ptr);
    if(field.isValid()) {
      bool ok=true;
      _layerCount=field.toInt(&ok);
      if(!ok && !field.isEmpty()) { // Not a blank line but failed
        App::log(tr("Layered model: bad number of layers: \"%1\" interpreted as %2\n").arg(field.toString()).arg(_layerCount) );
         _layerCount=0;
         return false;
      }
    } else {
      _layerCount=0;
      return true;  // This is just a blank line, before doing anything, you must test if the number of layer is >0
    }
    if(_layerCount>1 && !s.atEnd()) {
      allocateData();
      // read the data
      for(int i=0; i<_layerCount; i++) {
        if(s.atEnd()) {
          App::log(tr("Layered model: reaching the end of file, %1 layers found, expecting %2\n")
                         .arg(i).arg(_layerCount));
          _layerCount=0;
          return false;
        }
        buf=File::readLineNoComments(s, comments);
        fields.set(buf);
        fields.trimmed();
        ptr=0;
        if(i<_layerCount-1) {
          field=fields.nextField(ptr);
          if(!setValue(_h[i], field, i, tr("thickness"), false) ) return false;
        } else {
          fields.nextField(ptr);
        }
        field=fields.nextField(ptr);
        if(!setValue(_slowP[i], field, i, tr("Vp"), false) ) return false;
        field=fields.nextField(ptr);
        if(!setValue(_slowS[i], field, i, tr("Vs"), false) ) return false;
        field=fields.nextField(ptr);
        if(!setValue(_rho[i], field, i, tr("density"), false) ) return false;
        // Not mandatory quality factors
        field=fields.nextField(ptr);
        if(!setValue(_qp[i], field, i, tr("Qp"), true) ) _qp[i]=0.0;;
        field=fields.nextField(ptr);
        if(!setValue(_qs[i], field, i, tr("Qs"), true) ) _qs[i]=0.0;;
        // Switch to slowness
        _slowP[i]=1/_slowP[i];
        _slowS[i]=1/_slowS[i];
      }
      return initCalculation();
    } else {
      _layerCount=0;
      return false;
    }
  }

  /*!
    Saves model to text stream \a s

      Format:
        \verbatim
        nLayers
        h Vp Vs rho Qp Qs (layer 1)
        ...
        0 Vp Vs rho Qp Qs (layer n)
        \endverbatim
  */
  void Seismic1DModel::toStream(QTextStream& s) const
  {
    TRACE;
    static const QString str4("%1 %2 %3 %4\n");
    static const QString str6("%1 %2 %3 %4 %5 %6\n");
    s << _layerCount << "\n";
    int nl1=_layerCount-1;
    if(elastic()) {
      for(int i=0; i<nl1; i++) {
        s << str4.arg(_h[i], 0, 'g', 20)
                 .arg(_slowP[i]==0.0 ? 0.0 : 1/_slowP[i], 0, 'g', 20)
                 .arg(_slowS[i]==0.0 ? 0.0 : 1/_slowS[i], 0, 'g', 20)
                 .arg(_rho[i], 0, 'g', 20);
      }
      s << str4.arg(0.0, 0, 'g', 20)
               .arg(_slowP[_layerCount-1]==0.0 ? 0.0 : 1/_slowP[_layerCount-1], 0, 'g', 20)
               .arg(_slowS[_layerCount-1]==0.0 ? 0.0 : 1/_slowS[_layerCount-1], 0, 'g', 20)
               .arg(_rho[_layerCount-1], 0, 'g', 20) << flush;
     } else {
      for(int i=0; i<nl1; i++) {
        s << str6.arg(_h[i], 0, 'g', 20)
                 .arg(_slowP[i]==0.0 ? 0.0 : 1/_slowP[i], 0, 'g', 20)
                 .arg(_slowS[i]==0.0 ? 0.0 : 1/_slowS[i], 0, 'g', 20)
                 .arg(_rho[i], 0, 'g', 20)
                 .arg(_qp[i], 0, 'g', 20)
                 .arg(_qs[i], 0, 'g', 20);
      }
      s << str6.arg(0.0, 0, 'g', 20)
               .arg(_slowP[_layerCount-1]==0.0 ? 0.0 : 1/_slowP[_layerCount-1], 0, 'g', 20)
               .arg(_slowS[_layerCount-1]==0.0 ? 0.0 : 1/_slowS[_layerCount-1], 0, 'g', 20)
               .arg(_rho[_layerCount-1], 0, 'g', 20)
               .arg(_qp[_layerCount-1], 0, 'g', 20)
               .arg(_qs[_layerCount-1], 0, 'g', 20) << flush;
     }
  }

  /*!
    Returns model as a string
  */
  QString Seismic1DModel::toString() const
  {
    TRACE;
    QString str;
    str=QString("%1 layers\n").arg(_layerCount);
    str+="          H(m)       Vp(m/s)       Vs(m/s)    Rho(kg/m3)\n";
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      str+=QString::number(_h[i],'f',1).leftJustified(14,' ',true);
      str+=QString::number(1/_slowP[i],'f',1).leftJustified(14,' ',true);
      str+=QString::number(1/_slowS[i],'f',1).leftJustified(14,' ',true);
      str+=QString::number(_rho[i],'f',1).leftJustified(14,' ',true);
      str+="\n";
    }
    str+=QString("-").leftJustified(14,' ',true);
    str+=QString::number(1/_slowP[_layerCount-1],'f',1).leftJustified(14,' ',true);
    str+=QString::number(1/_slowS[_layerCount-1],'f',1).leftJustified(14,' ',true);
    str+=QString::number(_rho[_layerCount-1],'f',1).leftJustified(14,' ',true);
    str+="\n";
    return str;
  }

  /*!
    Saves model to Herrmann's format
  */
  void Seismic1DModel::exportHerrmann(QTextStream& s) const
  {
    TRACE;
    static const QString str("%1 %2 %3 %4 1\n");
    s << "2 1 0 2.25 250 1\n";
    int nl1=_layerCount-1;
    for(int i=0;i<nl1;i++) {
      s << str.arg(_h[i], 0, 'f', 15)
              .arg(1/_slowP[i], 0, 'f', 15)
              .arg(1/_slowS[i], 0, 'f', 15)
              .arg(_rho[i], 0, 'f', 15);
    }
    s << str.arg(0.0, 0, 'f', 20)
            .arg(1/_slowP[_layerCount-1], 0, 'f', 20)
            .arg(1/_slowS[_layerCount-1], 0, 'f', 20)
            .arg(_rho[_layerCount-1], 0, 'f', 20) << flush;
  }

  /*!
    Saves model to a binary stream
  */
  void Seismic1DModel::toStream(QDataStream& s) const
  {
    TRACE;
    s << _layerCount;
    int nl1=_layerCount-1;
    for(int i=0;i<nl1;i++) {
      s << _h[i];
      s << _slowP[i];
      s << _slowS[i];
      s << _rho[i];
    }
    s << _slowP[_layerCount-1];
    s << _slowS[_layerCount-1];
    s << _rho[_layerCount-1];
  }

  /*!
    Loads model from a binary stream
  */
  void Seismic1DModel::fromStream(QDataStream& s)
  {
    TRACE;
    int n;
    s >> n;
    if(n>_layerCount) {
      n=_layerCount;
    } else if(n<0) {
      App::log(tr("Seismic1DModel::fromStream: null number of layers\n") );
      return;
    }
    n--;
    for(int i=0; i<n; i++) {
      s >> _h[i];
      s >> _slowP[i];
      s >> _slowS[i];
      s >> _rho[i];
    }
    s >> _slowP[n];
    s >> _slowS[n];
    s >> _rho[n];
  }

  /*!
    Returns the P-wave slowness profile as a Profile
  */
  Profile Seismic1DModel::slowpProfile() const
  {
    TRACE;
    Profile p;
    p.resize(_layerCount);
    double d=0;
    int i;
    int nl1=_layerCount-1;
    for(i=0; i<nl1; i++) {
      d+= _h[i];
      p.setDepth(i, d);
      p.setValue(i, _slowP[i]);
    }
    p.setDepth(i, std::numeric_limits<double>::infinity());
    p.setValue(i, _slowP[i]);
    return p;
  }

  /*!
    Returns the S-wave slowness profile as a Profile
  */
  Profile Seismic1DModel::slowsProfile() const
  {
    TRACE;
    Profile p;
    p.resize(_layerCount);
    double d=0;
    int i;
    int nl1=_layerCount-1;
    for(i=0;i<nl1;i++) {
      d+= _h[i];
      p.setDepth(i, d);
      p.setValue(i, _slowS[i] );
    }
    p.setDepth(i, std::numeric_limits<double>::infinity());
    p.setValue(i, _slowS[i] );
    return p;
  }

  /*!
    Returns the density profile as a Profile
  */
  Profile Seismic1DModel::rhoProfile() const
  {
    TRACE;
    Profile p;
    p.resize(_layerCount);
    double d=0;
    int i;
    int nl1=_layerCount-1;
    for(i=0; i<nl1; i++) {
      d+= _h[i];
      p.setDepth(i, d);
      p.setValue(i, _rho[i]);
    }
    p.setDepth(i, std::numeric_limits<double>::infinity());
    p.setValue(i, _rho[i]);
    return p;
  }

  /*!
    Returns the Poisson's ratio profile as a Profile
  */
  Profile Seismic1DModel::poissonProfile() const
  {
    TRACE;
    Profile p;
    p.resize(_layerCount);
    double d=0;
    int i;
    int nl1=_layerCount-1;
    for(i=0; i<nl1; i++) {
      d+= _h[i];
      p.setDepth(i, d);
      p.setValue(i, poisson(i));
    }
    p.setDepth(i, std::numeric_limits<double>::infinity());
    p.setValue(i, poisson(i));
    return p;
  }

  /*!
    Returns the impedance (density*S-wave velocity) profile as a Profile
  */
  Profile Seismic1DModel::impedanceProfile() const
  {
    TRACE;
    Profile p;
    p.resize(_layerCount);
    double d=0;
    int i;
    int nl1=_layerCount-1;
    for(i=0; i<nl1; i++) {
      d+= _h[i];
      p.setDepth(i, d);
      p.setValue(i, _rho[i]/_slowS[i]);
    }
    p.setDepth(i, std::numeric_limits<double>::infinity());
    p.setValue(i, _rho[i]/_slowS[i]);
    return p;
  }

  double Seismic1DModel::vpAt(double depth) const
  {
    TRACE;
    double d=0;
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      d+=_h[i];
      if(depth<d) {
        return 1/_slowP[i];
      }
    }
    return 1/_slowP[_layerCount-1];
  }

  double Seismic1DModel::vsAt(double depth) const
  {
    TRACE;
    double d=0;
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      d+=_h[i];
      if(depth<d) {
        return 1/_slowS[i];
      }
    }
    return 1/_slowS[_layerCount-1];
  }

  double Seismic1DModel::slowPAt(double depth) const
  {
    TRACE;
    double d=0;
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      d+=_h[i];
      if(depth<d) {
        return _slowP[i];
      }
    }
    return _slowP[_layerCount-1];
  }

  double Seismic1DModel::slowSAt(double depth) const
  {
    TRACE;
    double d=0;
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      d+=_h[i];
      if(depth<d) {
        return _slowS[i];
      }
    }
    return _slowS[_layerCount-1];
  }

  double Seismic1DModel::rhoAt(double depth) const
  {
    TRACE;
    double d=0;
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      d+=_h[i];
      if(depth<d) return _rho[i];
    }
    return _rho[_layerCount-1];
  }

  double Seismic1DModel::poissonAt(double depth) const
  {
    TRACE;
    double d=0;
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      d+=_h[i];
      if(depth<d) return poisson(i);
    }
    return poisson(_layerCount-1);
  }

  double Seismic1DModel::impedanceAt(double depth) const
  {
    TRACE;
    double d=0;
    int nl1=_layerCount-1;
    for(int i=0; i<nl1; i++) {
      d+=_h[i];
      if(depth<d) {
        return _rho[i]/_slowS[i];
      }
    }
    return _rho[_layerCount-1]/_slowS[_layerCount-1];
  }

  void Seismic1DModel::setQp(double v)
  {
    TRACE;
    for(int i=0; i<_layerCount; i++) {
      _qp[i]=v;
    }
  }

  void Seismic1DModel::setQs(double v)
  {
    TRACE;
    for(int i=0; i<_layerCount; i++) {
      _qs[i]=v;
    }
  }

  /*!
     Modify one thickness keeping all other depth constant (if possible)
     if not, modify only the thickness of this layer and other depth will also change
  */
  void Seismic1DModel::changeHKeepOtherDepth(int iLayer, double newH)
  {
    TRACE;
    if(iLayer>=_layerCount-1) return;
    if(iLayer==_layerCount-2 || newH>=_h[iLayer]+_h[iLayer+1]) {
      _h[iLayer]=newH;
      return;
    }
    double dH=newH-_h[iLayer];
    _h[iLayer]=newH;
    _h[iLayer+1]-=dH;
  }

  /*!
    Changes Vp without changing the Poisson ratio of layer \a ilayer
  */
  void Seismic1DModel::changeVpKeepPoisson(int iLayer, double newV)
  {
    TRACE;
    if(iLayer>_layerCount-1) return;
    // Get actual Poisson's ratio (equivalent to Vs over Vp)
    // ratio=Vp/Vs=(1/Vs)/(1/Vp)=slowS/slowP
    double ratio=_slowS[iLayer]/_slowP[iLayer];
    _slowP[iLayer]=1.0/newV;
    _slowS[iLayer]=ratio*_slowP[iLayer];
  }

  /*!
    Changes Vs without changing the Poisson ratio of layer \a ilayer
  */
  void Seismic1DModel::changeVsKeepPoisson(int iLayer, double newV)
  {
    TRACE;
    if(iLayer>_layerCount-1) return;
    // Get actual Poisson's ratio (equivalent to Vs over Vp)
    // ratio=Vs/Vp=(1/Vp)/(1/Vs)=slowP/slowS
    double ratio=_slowP[iLayer]/_slowS[iLayer];
    _slowS[iLayer]=1.0/newV;
    _slowP[iLayer]=ratio*_slowS[iLayer];
  }

  /*!
    Copies H values from another model (only if same number of layers)
  */
  void Seismic1DModel::setHFrom(const Seismic1DModel& o)
  {
    TRACE;
    if(_layerCount!=o._layerCount) {
      return;
    }
    for(int i=_layerCount-2; i>=0; i--) {
      _h[i]=o._h[i];
    }
  }

  /*!
    \a refPoints and \a refModels must contains exactly tree elements, the three reference models.
    All models must have the same number of layers.
    This model is initialized with the interpolation of the three models. Linear interpolation in
    velocity domain.
  */
  bool Seismic1DModel::interpole(const Point& at, const Point * refPoints, const Seismic1DModel * refModels)
  {
    TRACE;
    _layerCount=refModels[0].layerCount();
    if(_layerCount!=refModels[1].layerCount()) {
      App::log(tr("Model 1 has not the same number of layers as model 0\n") );
      return false;
    }
    if(_layerCount!=refModels[2].layerCount()) {
      App::log(tr("Model 2 has not the same number of layers as model 0\n") );
      return false;
    }
    deleteData();
    allocateData();
    Plane p;
    p.setReference(0, refPoints[0]);
    p.setReference(1, refPoints[1]);
    p.setReference(2, refPoints[2]);
    if(!p.setNormalVector()) {
      return false;
    }
    for(int i=0; i<_layerCount; i++) {
      if(i<_layerCount-1) {
        p.setZ(0, refModels[0]._h[i]);
        p.setZ(1, refModels[1]._h[i]);
        p.setZ(2, refModels[2]._h[i]);
        p.setNormalVectorXY();
        _h[i]=p.z(at);
      }
      p.setZ(0, 1.0/refModels[0]._slowP[i]);
      p.setZ(1, 1.0/refModels[1]._slowP[i]);
      p.setZ(2, 1.0/refModels[2]._slowP[i]);
      p.setNormalVectorXY();
      _slowP[i]=1.0/p.z(at);
      p.setZ(0, 1.0/refModels[0]._slowS[i]);
      p.setZ(1, 1.0/refModels[1]._slowS[i]);
      p.setZ(2, 1.0/refModels[2]._slowS[i]);
      p.setNormalVectorXY();
      _slowS[i]=1.0/p.z(at);
      p.setZ(0, refModels[0]._rho[i]);
      p.setZ(1, refModels[1]._rho[i]);
      p.setZ(2, refModels[2]._rho[i]);
      p.setNormalVectorXY();
      _rho[i]=p.z(at);
      p.setZ(0, refModels[0]._qp[i]);
      p.setZ(1, refModels[1]._qp[i]);
      p.setZ(2, refModels[2]._qp[i]);
      p.setNormalVectorXY();
      _qp[i]=p.z(at);
      p.setZ(0, refModels[0]._qs[i]);
      p.setZ(1, refModels[1]._qs[i]);
      p.setZ(2, refModels[2]._qs[i]);
      p.setNormalVectorXY();
      _qs[i]=p.z(at);
    }
    if(initCalculation()) {
      return true;
    } else {
      App::log(tr("Poisson's ratio is not physical for interpolated model\n") );
      QString str;
      QTextStream s(&str);
      toStream(s);
      App::log(str);
      return false;
    }
  }

} // namespace QGpCoreWave
