// -*- c++ -*- (enables emacs c++ mode)
//===========================================================================
//
// Copyright (C) 2009-2009 Yves Renard.
//
// This file is a part of GETFEM++
//
// Getfem++  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 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.  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 program;  if not, write to the Free Software Foundation,
// Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
//
//===========================================================================

/**\file gf_model_set.cc
   \brief getfemint_model setter.
*/

#include <getfemint.h>
#include <getfemint_models.h>
#include <getfemint_mesh_fem.h>
#include <getfemint_workspace.h>
#include <getfemint_mesh_im.h>
#include <getfemint_gsparse.h>

using namespace getfemint;


/*MLABCOM

  FUNCTION M = gf_model_set(cmd, [, args])
  Modify a model object.

  @SET MODEL:SET('variable')
  @SET MODEL:SET('clear')
  @SET MODEL:SET('add fem variable')
  @SET MODEL:SET('add variable')
  @SET MODEL:SET('add multiplier')
  @SET MODEL:SET('add fem data')
  @SET MODEL:SET('add initialized fem data')
  @SET MODEL:SET('add data')
  @SET MODEL:SET('add initialized data')
  @SET MODEL:SET('to variables')
  @SET MODEL:SET('add Laplacian brick')
  @SET MODEL:SET('add generic elliptic brick')
  @SET MODEL:SET('add source term brick')
  @SET MODEL:SET('add normal source term brick')
  @SET MODEL:SET('add Dirichlet condition with multipliers')
  @SET MODEL:SET('add Dirichlet condition with penalization')
  @SET MODEL:SET('change penalization coeff')
  @SET MODEL:SET('add Helmholtz brick')
  @SET MODEL:SET('add Fourier Robin brick')
  @SET MODEL:SET('add constraint with multipliers')
  @SET MODEL:SET('add constraint with penalization')
  @SET MODEL:SET('add explicit matrix')
  @SET MODEL:SET('add explicit rhs')
  @SET MODEL:SET('set private matrix')
  @SET MODEL:SET('set private rhs')
  @SET MODEL:SET('disable bricks')
  @SET MODEL:SET('unable bricks')
  @SET MODEL:SET('add isotropic linearized elasticity brick')
  @SET MODEL:SET('add linear incompressibility brick')
  @SET MODEL:SET('add mass brick')
  @SET MODEL:SET('add basic d on dt brick')
  @SET MODEL:SET('add basic d2 on dt2 brick')
  @SET MODEL:SET('add theta method dispatcher')
  @SET MODEL:SET('velocity update for order two theta method')
  @SET MODEL:SET('add midpoint dispatcher')
  @SET MODEL:SET('velocity update for Newmark scheme')
  @SET MODEL:SET('first iter')
  @SET MODEL:SET('next iter')

MLABCOM*/

void gf_model_set(getfemint::mexargs_in& in, getfemint::mexargs_out& out)
{
  if (in.narg() < 2) THROW_BADARG( "Wrong number of input arguments");

  getfemint_model *md  = in.pop().to_getfemint_model(true);
  std::string cmd        = in.pop().to_string();
  if (check_cmd(cmd, "clear", in, out, 0, 0, 0, 1)) {
    /*@SET MODEL:SET('clear')
    Clear the model.@*/
    md->clear();
  } else if (check_cmd(cmd, "add fem variable", in, out, 2, 3, 0, 0)) {
    /*@SET MODEL:SET('add fem variable', @str name, @tmf mf[, @int niter])
    Add a variable to the model linked to a @tmf. `name` is the variable name
    and `niter` is the optional number of copy of the variable for time
    integration schemes. @*/
    std::string name = in.pop().to_string();
    getfemint_mesh_fem *gfi_mf = in.pop().to_getfemint_mesh_fem();
    workspace().set_dependance(md, gfi_mf);
    size_type niter = 1;
    if (in.remaining()) niter = in.pop().to_integer(1,10);
    md->model().add_fem_variable(name, gfi_mf->mesh_fem(), niter);
  } else if (check_cmd(cmd, "add variable", in, out, 2, 3, 0, 0)) {
    /*@SET MODEL:SET('add variable', @str name, @int size[, @int niter])
    Add a variable to the model of constant size. `name` is the variable name
    and `niter` is the optional number of copy of the variable for time
    integration schemes. @*/
    std::string name = in.pop().to_string();
    size_type s = in.pop().to_integer();
    size_type niter = 1;
    if (in.remaining()) niter = in.pop().to_integer(1,10);
    md->model().add_fixed_size_variable(name, s, niter);
  } else if (check_cmd(cmd, "add multiplier", in, out, 3, 4, 0, 0)) {
    /*@SET MODEL:SET('add multiplier', @str name, @tmf mf, @str primalname[, @int niter])
    Add a particular variable linked to a fem being a multiplier with
    respect to a primal variable. The dof will be filtered with the
    gmm::range_basis function applied on the terms of the model which
    link the multiplier and the primal variable. This in order to
    retain only linearly independant constraints on the primal variable.
    Optimized for boundary multipliers. niter is the number of version
    of the data stored, for time integration schemes. @*/
    std::string name = in.pop().to_string();
    getfemint_mesh_fem *gfi_mf = in.pop().to_getfemint_mesh_fem();
    std::string primalname = in.pop().to_string();
    size_type niter = 1;
    if (in.remaining()) niter = in.pop().to_integer(1,10);
    md->model().add_multiplier(name, gfi_mf->mesh_fem(),
			       primalname, niter);
  } else if (check_cmd(cmd, "add fem data", in, out, 2, 4, 0, 0)) {
    /*@SET MODEL:SET('add fem data', @str name, @tmf mf[, @int qdim, @int niter])
    Add a data to the model linked to a @tmf. `name` is the data name,
    `qdim` is the optional dimension of the data over the @tmf
    and `niter` is the optional number of copy of the data for time
    integration schemes. @*/
    std::string name = in.pop().to_string();
    getfemint_mesh_fem *gfi_mf = in.pop().to_getfemint_mesh_fem();
    workspace().set_dependance(md, gfi_mf);
    dim_type qdim = 1;
    if (in.remaining()) qdim = dim_type(in.pop().to_integer(1,255));
    size_type niter = 1;
    if (in.remaining()) niter = in.pop().to_integer(1,10);
    md->model().add_fem_data(name, gfi_mf->mesh_fem(), qdim, niter);
  } else if (check_cmd(cmd, "add initialized fem data", in, out, 3, 3, 0, 0)) {
    /*@SET MODEL:SET('add initialized fem data', @str name, @tmf mf, @vec V)
    Add a data to the model linked to a @tmf. `name` is the data name.
    The data is initiakized with `V`. The data can be a scalar or vector field.
    @*/
    std::string name = in.pop().to_string();
    getfemint_mesh_fem *gfi_mf = in.pop().to_getfemint_mesh_fem();
    workspace().set_dependance(md, gfi_mf);
    if (!md->is_complex()) {
      darray st = in.pop().to_darray();
      std::vector<double> V(st.begin(), st.end());
      md->model().add_initialized_fem_data(name, gfi_mf->mesh_fem(), V);
    } else {
      carray st = in.pop().to_carray();
      std::vector<std::complex<double> > V(st.begin(), st.end());
      md->model().add_initialized_fem_data(name, gfi_mf->mesh_fem(), V);
    }
  } else if (check_cmd(cmd, "add data", in, out, 2, 3, 0, 0)) {
    /*@SET MODEL:SET('add data', @str name, @int size[, @int niter])
    Add a data to the model of constant size. `name` is the data name
    and `niter` is the optional number of copy of the data for time
    integration schemes. @*/
    std::string name = in.pop().to_string();
    size_type s = in.pop().to_integer();
    size_type niter = 1;
    if (in.remaining()) niter = in.pop().to_integer(1,10);
    md->model().add_fixed_size_data(name, s, niter);
  } else if (check_cmd(cmd, "add initialized data", in, out, 2, 2, 0, 0)) {
    /*@SET MODEL:SET('add initialized data', @str name, V)
    Add a fixed size data to the model linked to a @tmf.
    `name` is the data name, `V` is the value of the data.
    @*/
    std::string name = in.pop().to_string();
    if (!md->is_complex()) {
      darray st = in.pop().to_darray();
      std::vector<double> V(st.begin(), st.end());
      md->model().add_initialized_fixed_size_data(name, V);
    } else {
      carray st = in.pop().to_carray();
      std::vector<std::complex<double> > V(st.begin(), st.end());
      md->model().add_initialized_fixed_size_data(name, V);
    }
  } else if (check_cmd(cmd, "variable", in, out, 2, 3, 0, 0)) {
    /*@SET MODEL:SET('variable', @str name, @vec V[, @int niter])
    Set the value of a variable or data.@*/
    std::string name = in.pop().to_string();
    if (!md->is_complex()) {
      darray st = in.pop().to_darray();
      size_type niter = 0;
      if (in.remaining()) niter = in.pop().to_integer(0,10) - config::base_index();
      GMM_ASSERT1(st.size() == md->model().real_variable(name, niter).size(),
		  "Bad size in assigment");
      md->model().set_real_variable(name, niter).assign(st.begin(), st.end());
    } else {
      carray st = in.pop().to_carray();
      size_type niter = 0;
      if (in.remaining())
	niter = in.pop().to_integer(0,10) - config::base_index();
      GMM_ASSERT1(st.size() == md->model().real_variable(name, niter).size(),
		  "Bad size in assigment");
      md->model().set_complex_variable(name, niter).assign(st.begin(), st.end());
    }
  } else if (check_cmd(cmd, "to variables", in, out, 1, 1, 0, 0)) {
    /*@SET MODEL:SET('to variables', @vec V)
    Set the value of the variables of the model with the vector `V`.
    Typically, the vector `V` results of the solve of the tangent linear
    system (usefull to solve your problem with you own solver). @*/
    if (!md->is_complex()) {
      darray st = in.pop().to_darray(-1);
      std::vector<double> V;
      V.assign(st.begin(), st.end());
      md->model().to_variables(V);
    } else {
      carray st = in.pop().to_carray(-1);
      std::vector<std::complex<double> > V;
      V.assign(st.begin(), st.end());
      md->model().to_variables(V);
    }
  } else if (check_cmd(cmd, "add Laplacian brick", in, out, 2, 3, 0, 1)) {
    /*@SET ind = MODEL:SET('add Laplacian brick', @tmim mim, @str varname[, @int region])
    add a Laplacian term to the model relatively to the variable `varname`.
    If this is a vector valued variable, the Laplacian term is added
    componentwise. `region` is an optional mesh region on which the term is added. If it is not specified, it is added on the whole mesh. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_Laplacian_brick(md->model(), mim, varname, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add generic elliptic brick", in, out, 3, 4, 0, 1)) {
    /*@SET ind = MODEL:SET('add generic elliptic brick', @tmim mim, @str varname, @str dataname[, @int region])
    add a generic elliptic term to the model relatively to the variable `varname`.
    The shape of the elliptic
    term depends both on the variable and the data. This corresponds to a
    term $-\text{div}(a\nabla u)$ where $a$ is the data and $u$ the variable.
    The data can be a scalar, a matrix or an order four tensor. The variable
    can be vector valued or not. If the data is a scalar or a matrix and
    the variable is vector valued then the term is added componentwise.
    An order four tensor data is allowed for vector valued variable only.
    The data can be constant or describbed on a fem. Of course, when
    the data is a tensor describe on a finite element method (a tensor
    field) the data can be a huge vector. The components of the
    matrix/tensor have to be stored with the fortran order (columnwise) in
    the data vector (compatibility with blas). The symmetry of the given
    matrix/tensor is not verified (but assumed).
    If this is a vector valued variable, the Laplacian term is added
    componentwise. `region` is an optional mesh region on which the term is 
    added. If it is not specified, it is added on the whole mesh. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string dataname = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_generic_elliptic_brick(md->model(), mim, varname,
					   dataname, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add source term brick", in, out, 3, 5, 0, 1)) {
    /*@SET ind = MODEL:SET('add source term brick', @tmim mim, @str varname, @str dataname[, @int region])
    add a source term to the model relatively to the variable `varname`.
    The source term is represented by the data `dataname` which could be
    constant or described on a fem.  `region` is an optional mesh region
    on which the term is added. An additional optional data `directdataname`
    can be provided. The corresponding data vector will be directly added
    to the right hand side without assembly. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string dataname = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    std::string directdataname;
    if (in.remaining()) directdataname = in.pop().to_string();
    size_type ind
      = getfem::add_source_term_brick(md->model(), mim, varname,
				      dataname, region, directdataname)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add normal source term brick", in, out, 4, 4, 0, 1)) {
    /*@SET ind = MODEL:SET('add normal source term brick', @tmim mim, @str varname, @str dataname[, @int region])
    add a source term on the variable `varname` on a boundary `region`.
    The source term is
    represented by the data `dataname` which could be constant or described
    on a fem. A scalar product with the outward normal unit vector to
    the boundary is performed. The main aim of this brick is to represent
    a Neumann condition with a vector data without performing the
    scalar product with the normal as a pre-processing. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string dataname = in.pop().to_string();
    size_type region = in.pop().to_integer();
    size_type ind
      = getfem::add_normal_source_term_brick(md->model(), mim, varname,
					     dataname, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add Dirichlet condition with multipliers", in, out, 4, 5, 0, 1)) {
    /*@SET ind = MODEL:SET('add Dirichlet condition with multipliers', @tmim mim, @str varname, mult_description, @int region[, @str dataname])
    Add a Dirichlet condition on the variable `varname` and the mesh
    region `region`. This region should be a boundary. The Dirichlet
    condition is prescribed with a multiplier variable described by
    `mult_description`. If `mult_description` is a string this is assumed
    to be the variable name correpsonding to the multiplier (which should be
    first declared as a multiplier  variable on the mesh region in the model).
    If it is a finite element method (mesh_fem object) then a multiplier
    variable will be added to the model and build on this finite element
    method (it will be restricted to the mesh region `region` and eventually
    some conflicting dofs with some other multiplier variables will be
    suppressed). If it is an integer, then a  multiplier variable will be
    added to the model and build on a classical finite element of degree
    that integer. `dataname` is the optional
    right hand side of  the Dirichlet condition. It could be constant or
    described on a fem; scalar or vector valued, depending on the variable
    on which the Dirichlet condition is prescribed. Return the brick index
    in the model. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    int version = 0;
    size_type degree = 0;
    std::string multname;
    getfem::mesh_fem *mf_mult = 0;
    mexarg_in argin = in.pop();

    if (argin.is_integer()) {
      degree = argin.to_integer();
      version = 1;
    } else if (argin.is_string()) {
      multname = argin.to_string();
      version = 2;
    } else {
      getfemint_mesh_fem *gfi_mf = argin.to_getfemint_mesh_fem();
      mf_mult = &(gfi_mf->mesh_fem());
      version = 3;
    }
    size_type region = in.pop().to_integer();
    std::string dataname;
    if (in.remaining()) dataname = in.pop().to_string();
    size_type ind = config::base_index();
    switch(version) {
    case 1:  ind += getfem::add_Dirichlet_condition_with_multipliers
	(md->model(), mim, varname, dim_type(degree), region, dataname);
      break;
    case 2:  ind += getfem::add_Dirichlet_condition_with_multipliers
	(md->model(), mim, varname, multname, region, dataname);
      break;
    case 3:  ind += getfem::add_Dirichlet_condition_with_multipliers
	(md->model(), mim, varname, *mf_mult, region, dataname);
      break;
    }
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add Dirichlet condition with penalization", in, out, 4, 5, 0, 1)) {
    /*@SET ind = MODEL:SET('add Dirichlet condition with penalization', @tmim mim, @str varname, @scalar coeff, @int region[, @str dataname])
      Add a Dirichlet condition on the variable `varname` and the mesh
      region `region`. This region should be a boundary. The Dirichlet
      condition is prescribed with penalization. The penalization coefficient
      is intially `coeff` and will be added to the data of
      the model. `dataname` is the optional
      right hand side of  the Dirichlet condition. It could be constant or
      described on a fem; scalar or vector valued, depending on the variable
      on which the Dirichlet condition is prescribed. Return the brick index
      in the model.
      @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    double coeff = in.pop().to_scalar();
    size_type region = in.pop().to_integer();
    std::string dataname;
    if (in.remaining()) dataname = in.pop().to_string();
    size_type ind = config::base_index();
    ind += getfem::add_Dirichlet_condition_with_penalization
      (md->model(), mim, varname, coeff, region, dataname);
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "change penalization coeff", in, out, 2, 2, 0, 0)) {
    /*@SET MODEL:SET('change penalization coeff', @int ind_brick, @scalar coeff)
      Change the penalization coefficient of a Dirichlet condition with
      penalization brick. If the brick is not of this kind,
      this function has an undefined behavior.
      @*/
    size_type ind_brick = in.pop().to_integer();
    double coeff = in.pop().to_scalar();
    getfem::change_penalization_coeff(md->model(), ind_brick, coeff);
  } else if (check_cmd(cmd, "add Helmholtz brick", in, out, 3, 4, 0, 1)) {
    /*@SET ind = MODEL:SET('add Helmholtz brick', @tmim mim, @str varname, @str dataname[, @int region])
    add a Helmholtz term to the model relatively to the variable `varname`.
    `dataname` should contain the wave number.
    `region` is an optional mesh region on which the term is added.
    If it is not specified, it is added on the whole mesh. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string dataname = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_Helmholtz_brick(md->model(), mim, varname,dataname, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add Fourier Robin brick", in, out, 4, 4, 0, 1)) {
    /*@SET ind = MODEL:SET('add Fourier Robin brick', @tmim mim, @str varname, @str dataname, @int region)
    add a Fourier-Robin term to the model relatively to the variable
    `varname`. this corresponds to a weak term of the form $\int (qu).v$.
    `dataname` should contain the parameter $q$ of the Fourier-Robin condition.
    `region` is the mesh region on which the term is added. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string dataname = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_Fourier_Robin_brick(md->model(), mim, varname,dataname, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add constraint with multipliers", in, out, 4, 4, 0, 1)) {
    /*@SET ind = MODEL:SET('add constraint with multipliers', @str varname, @str multname, @mat B, @vec L)
    Add an additional explicit constraint on the variable `varname` thank to
    a multiplier `multname` peviously added to the model (should be a fixed
    size variable).
    The constraint is $BU=L$ with `B` being a rectangular sparse matrix.
    It is possible to change the constraint
    at any time whith the methods MODEL:SET('set private matrix')
    and MODEL:SET('set private rhs') @*/
    std::string varname = in.pop().to_string();
    std::string multname = in.pop().to_string();
    size_type ind
      = getfem::add_constraint_with_multipliers(md->model(),varname,multname);

    dal::shared_ptr<gsparse> B = in.pop().to_sparse();
    
    if (B->is_complex() && !md->is_complex())
      THROW_BADARG("Complex constraint for a real model");
    if (!B->is_complex() && md->is_complex())
      THROW_BADARG("Real constraint for a complex model");

    if (md->is_complex()) {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    } else {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    }
    
    if (!md->is_complex()) {
      darray st = in.pop().to_darray();
      std::vector<double> V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    } else {
      carray st = in.pop().to_carray();
      std::vector<std::complex<double> > V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    }

    out.pop().from_integer(int(ind + config::base_index()));
  } else if (check_cmd(cmd, "add constraint with penalization", in, out, 4, 4, 0, 1)) {
    /*@SET ind = MODEL:SET('add constraint with penalization', @str varname, @scalar coeff, @mat B, @vec L)
    Add an additional explicit penalized constraint on the variable `varname`.
    The constraint is $BU=L$ with `B` being a rectangular sparse matrix.
    Be aware that `B` should not contain a palin row, otherwise the whole
    tangent matrix will be plain. It is possible to change the constraint
    at any time whith the methods MODEL:SET('set private matrix')
    and MODEL:SET('set private rhs'). The method
    MODEL:SET('change penalization coeff') can be used.@*/
    std::string varname = in.pop().to_string();
    double coeff = in.pop().to_scalar();
    size_type ind
      = getfem::add_constraint_with_penalization(md->model(), varname, coeff);

    dal::shared_ptr<gsparse> B = in.pop().to_sparse();
    
    if (B->is_complex() && !md->is_complex())
      THROW_BADARG("Complex constraint for a real model");
    if (!B->is_complex() && md->is_complex())
      THROW_BADARG("Real constraint for a complex model");

    if (md->is_complex()) {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    } else {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    }
    
    if (!md->is_complex()) {
      darray st = in.pop().to_darray();
      std::vector<double> V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    } else {
      carray st = in.pop().to_carray();
      std::vector<std::complex<double> > V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    }

    out.pop().from_integer(int(ind + config::base_index()));
  } else if (check_cmd(cmd, "add explicit matrix", in, out, 3, 5, 0, 1)) {
    /*@SET ind = MODEL:SET('add explicit matrix', @str varname1, @str varname2, @mat B[, @int issymmetric[, @int iscoercive]])
      Add a brick reprenting an explicit matrix to be added to the tangent
      linear system relatively to the variables 'varname1' and 'varname2'.
      The given matrix should have has many rows as the dimension of
      'varname1' and as many columns as the dimension of 'varname2'.
      If the two variables are different and if `issymmetric' is set to 1
      then the transpose of the matrix is also added to the tangent system
      (default is 0). set `iscoercive` to 1 if the term does not affect the
      coercivity of the tangent system (default is 0).
      The matrix can be changed by the command
      MODEL:SET('set private matrix').@*/
    std::string varname1 = in.pop().to_string();
    std::string varname2 = in.pop().to_string();
    dal::shared_ptr<gsparse> B = in.pop().to_sparse();
    bool issymmetric = false;
    bool iscoercive = false;
    if (in.remaining()) issymmetric = (in.pop().to_integer(0,1) != 1);
    if (!issymmetric && in.remaining())
      iscoercive = (in.pop().to_integer(0,1) != 1);

    size_type ind
      = getfem::add_explicit_matrix(md->model(), varname1, varname2,
				    issymmetric, iscoercive);

    if (B->is_complex() && !md->is_complex())
      THROW_BADARG("Complex constraint for a real model");
    if (!B->is_complex() && md->is_complex())
      THROW_BADARG("Real constraint for a complex model");

    if (md->is_complex()) {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    } else {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    }
    
    out.pop().from_integer(int(ind + config::base_index()));
  } else if (check_cmd(cmd, "add explicit rhs", in, out, 2, 2, 0, 1)) {
    /*@SET ind = MODEL:SET('add explicit rhs', @str varname, @vec L)
      Add a brick reprenting an explicit right hand side to be added to
      the right hand side of the tangent
      linear system relatively to the variable 'varname'.
      The given rhs should have the same size than the dimension of
      'varname'. The rhs can be changed by the command
      MODEL:SET('set private rhs'). @*/
    std::string varname = in.pop().to_string();
    size_type ind
      = getfem::add_explicit_rhs(md->model(), varname);

    if (!md->is_complex()) {
      darray st = in.pop().to_darray();
      std::vector<double> V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    } else {
      carray st = in.pop().to_carray();
      std::vector<std::complex<double> > V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    }
    
    out.pop().from_integer(int(ind + config::base_index()));
  } else if (check_cmd(cmd, "set private matrix", in, out, 2, 2, 0, 0)) {
    /*@SET MODEL:SET('set private matrix', @int indbrick, @mat B)
    For some specific bricks having an internal sparse matrix
    (explicit bricks: 'constraint brick' and 'explicit matrix brick'),
    set this matrix. @*/
    size_type ind = in.pop().to_integer();
    dal::shared_ptr<gsparse> B = in.pop().to_sparse();
    
    if (B->is_complex() && !md->is_complex())
      THROW_BADARG("Complex constraint for a real model");
    if (!B->is_complex() && md->is_complex())
      THROW_BADARG("Real constraint for a complex model");

    if (md->is_complex()) {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->cplx_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    } else {
      if (B->storage()==gsparse::CSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_csc());
      else if (B->storage()==gsparse::WSCMAT)
	getfem::set_private_data_matrix(md->model(), ind, B->real_wsc());
      else
      THROW_BADARG("Constraint matrix should be a sparse matrix");
    }
  } else if (check_cmd(cmd, "set private rhs", in, out, 2, 2, 0, 0)) {
    /*@SET MODEL:SET('set private rhs', @int indbrick, @vec B)
    For some specific bricks having an internal right hand side vector
    (explicit bricks: 'constraint brick' and 'explicit rhs brick'),
    set this rhs. @*/
    size_type ind = in.pop().to_integer();

    if (!md->is_complex()) {
      darray st = in.pop().to_darray();
      std::vector<double> V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    } else {
      carray st = in.pop().to_carray();
      std::vector<std::complex<double> > V(st.begin(), st.end());
      getfem::set_private_data_rhs(md->model(), ind, V);
    }

  } else if (check_cmd(cmd, "add isotropic linearized elasticity brick", in, out, 4, 5, 0, 1)) {
    /*@SET ind = MODEL:SET('add isotropic linearized elasticity brick', @tmim mim, @str varname, @str dataname_lambda, @str dataname_mu[, @int region])
    add an isotropic linearized elasticity term to the model relatively to the
    variable `varname`.
    `dataname_lambda` and `dataname_mu` should contain the Lam\'e coefficients.
    `region` is an optional mesh region on which the term is added.
    If it is not specified, it is added on the whole mesh. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string dataname_lambda = in.pop().to_string();
    std::string dataname_mu = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_isotropic_linearized_elasticity_brick
      (md->model(), mim, varname, dataname_lambda, dataname_mu, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add linear incompressibility brick", in, out, 3, 5, 0, 1)) {
    /*@SET ind = MODEL:SET('add linear incompressibility brick', @tmim mim, @str varname, @str multname_pressure[, @int region[, @str dataname_coeff]])
      add an linear incompressibility condition on `variable`.
      `multname_pressure` is a variable which represent the pressure.
      Be aware that an inf-sup condition between the finite element method
      describing the rpressure and the primal variable has to be satisfied.
      `region` is an optional mesh region on which the term is added.
      If it is not specified, it is added on the whole mesh.
      `dataname_coeff` is an optional penalization coefficient for nearly
      incompressible elasticity for instance. In this case, it is the inverse
      of the Lam\'e coefficient $\lambda$. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string multname = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    std::string dataname;
    if (in.remaining()) dataname = in.pop().to_string();
    size_type ind
      = getfem::add_linear_incompressibility
      (md->model(), mim, varname, multname, region, dataname)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add mass brick", in, out, 2, 4, 0, 1)) {
    /*@SET ind = MODEL:SET('add mass brick', @tmim mim, @str varname[, @str dataname_rho[, @int region]])
    add mass term to the model relatively to the variable `varname`.
    If specified, the data `dataname_rho` should contain the density (1 if omitted).
    `region` is an optional mesh region on which the term is added.
    If it is not specified, it is added on the whole mesh. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varname = in.pop().to_string();
    std::string dataname_rho;
    if (in.remaining()) dataname_rho = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_mass_brick
      (md->model(), mim, varname, dataname_rho, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add basic d on dt brick", in, out, 3, 5, 0, 1)) {
    /*@SET ind = MODEL:SET('add basic d on dt brick', @tmim mim, @str varnameU, @str dataname_dt[, @str dataname_rho[, @int region]])
    Add the standard discretization of a first order time derivative on
    `varnameU`. The parameter $rho$ is the density which could be omitted
    (the defaul value is 1). This brick should be used in addition to a
    time dispatcher for the other terms. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varnameU = in.pop().to_string();
    std::string varnamedt = in.pop().to_string();
    std::string dataname_rho;
    if (in.remaining()) dataname_rho = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_basic_d_on_dt_brick
      (md->model(), mim, varnameU, varnamedt, dataname_rho, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add basic d2 on dt2 brick", in, out, 5, 7, 0,1)) {
    /*@SET ind = MODEL:SET('add basic d2 on dt2 brick', @tmim mim, @str varnameU,  @str datanameV, @str dataname_dt, @str dataname_alpha,[, @str dataname_rho[, @int region]])
    Add the standard discretization of a second order time derivative
    on `varnameU`. `datanameV` is a data represented on the same finite
    element method as U which represents the time derivative of U. The
    parameter $rho$ is the density which could be omitted (the defaul value
    is 1). This brick should be used in addition to a time dispatcher for the
    other terms. The time derivative $v$ of the variable $u$ is preferably
    computed as a post-traitement which depends on each scheme. The parameter
    `dataname_alpha` depends on the time integration scheme. @*/
    getfemint_mesh_im *gfi_mim = in.pop().to_getfemint_mesh_im();
    workspace().set_dependance(md, gfi_mim);
    getfem::mesh_im &mim = gfi_mim->mesh_im();
    std::string varnameU = in.pop().to_string();
    std::string varnameV = in.pop().to_string();
    std::string varnamedt = in.pop().to_string();
    std::string varnamealpha = in.pop().to_string();
    std::string dataname_rho;
    if (in.remaining()) dataname_rho = in.pop().to_string();
    size_type region = size_type(-1);
    if (in.remaining()) region = in.pop().to_integer();
    size_type ind
      = getfem::add_basic_d2_on_dt2_brick
      (md->model(), mim, varnameU,  varnameV, varnamedt, varnamealpha, dataname_rho, region)
      + config::base_index();
    out.pop().from_integer(int(ind));
  } else if (check_cmd(cmd, "add theta method dispatcher", in, out, 2, 2, 0,0)) {
    /*@SET MODEL:SET('add theta method dispatcher', @ivec bricks_indices, @str theta)
      Add a theta-method time dispatcher to a list of bricks. For instance,
      a matrix term $K$ will be replaced by
      $\theta K U^{n+1} + (1-\theta) K U^{n}$.
      @*/
    dal::bit_vector bv;
    bv = in.pop().to_bit_vector();
    std::string datanametheta = in.pop().to_string();
    getfem::add_theta_method_dispatcher(md->model(), bv, datanametheta);
  } else if (check_cmd(cmd, "add midpoint dispatcher", in, out, 1, 1, 0,0)) {
    /*@SET MODEL:SET('add midpoint dispatcher', @ivec bricks_indices)
     Add a midpoint time dispatcher to a list of bricks. For instance,
     a nonlinear term $K(U)$ will be replaced by
     $K((U^{n+1} +  U^{n})/2)$.@*/
    dal::bit_vector bv;
    bv = in.pop().to_bit_vector();
    getfem::add_midpoint_dispatcher(md->model(), bv);
  } else if (check_cmd(cmd, "velocity update for order two theta method", in, out, 4, 4, 0,0)) {
    /*@SET MODEL:SET('velocity update for order two theta method', @str varnameU,  @str datanameV, @str dataname_dt, @str dataname_theta)
      Function which udpate the velocity $v^{n+1}$ after the computation
      of the displacement $u^{n+1}$ and before the next iteration. Specific
      for theta-method and when the velocity is included in the data
      of the model. @*/
    std::string varnameU = in.pop().to_string();
    std::string varnameV = in.pop().to_string();
    std::string varnamedt = in.pop().to_string();
    std::string varnametheta = in.pop().to_string();
    velocity_update_for_order_two_theta_method
      (md->model(), varnameU, varnameV, varnamedt, varnametheta);
  } else if (check_cmd(cmd, "velocity update for Newmark scheme", in, out, 6, 6, 0,0)) {
    /*@SET MODEL:SET('velocity update for Newmark scheme', @int id2dt2_brick, @str varnameU,  @str datanameV, @str dataname_dt, @str dataname_twobeta, @str dataname_alpha)
      Function which udpate the velocity $v^{n+1}$ after the computation
      of the displacement $u^{n+1}$ and before the next iteration. Specific
      for Newmark scheme and when the velocity is included in the data
      of the model. This version inverts the mass matrix by a conjugate
      gradient. @*/
    size_type id2dt2 = in.pop().to_integer();
    std::string varnameU = in.pop().to_string();
    std::string varnameV = in.pop().to_string();
    std::string varnamedt = in.pop().to_string();
    std::string varnametwobeta = in.pop().to_string();
    std::string varnamegamma = in.pop().to_string();
    velocity_update_for_Newmark_scheme
      (md->model(), id2dt2, varnameU, varnameV, varnamedt,
       varnametwobeta, varnamegamma);
  } else if (check_cmd(cmd, "disable bricks", in, out, 1, 1, 0,0)) {
    /*@SET MODEL:SET('disable bricks', @ivec bricks_indices)
      Disable a brick (the brick will no longer participate to the
      building of the tangent linear system).
      @*/
    dal::bit_vector bv;
    bv = in.pop().to_bit_vector();
    for (dal::bv_visitor ii(bv); !ii.finished(); ++ii)
      md->model().disable_brick(ii);
  } else if (check_cmd(cmd, "unable bricks", in, out, 1, 1, 0,0)) {
    /*@SET MODEL:SET('unable bricks', @ivec bricks_indices)
      Unable a disabled brick.
      @*/
    dal::bit_vector bv;
    bv = in.pop().to_bit_vector();
    for (dal::bv_visitor ii(bv); !ii.finished(); ++ii)
      md->model().unable_brick(ii);
  } else if (check_cmd(cmd, "first iter", in, out, 0, 0, 0,0)) {
    /*@SET ind = MODEL:SET('first iter')
      To be executed before the first iteration of a time integration scheme.
      @*/
    md->model().first_iter();
  } else if (check_cmd(cmd, "next iter", in, out, 0, 0, 0,0)) {
    /*@SET ind = MODEL:SET('next iter')
      To be executed at the end of each iteration of a time integration scheme.
      @*/
    md->model().next_iter();
  } else bad_cmd(cmd);
}
