/* $Id: d_mos2.cc,v 20.13 2001/10/15 00:57:11 al Exp $ -*- C++ -*-
 * Copyright (C) 2001 Albert Davis
 * Author: Albert Davis <aldavis@ieee.org>
 *
 * This file is part of "GnuCap", the Gnu Circuit Analysis Package
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *------------------------------------------------------------------
 * mos model equations: spice level 2 equivalent
 */
/* This file is automatically generated. DO NOT EDIT */

#include "l_compar.h"
#include "l_denoise.h"
#include "ap.h"
#include "d_mos2.h"
/*--------------------------------------------------------------------------*/
const double NA(NOT_INPUT);
/*--------------------------------------------------------------------------*/
int MODEL_MOS2::_count = 0;
/*--------------------------------------------------------------------------*/
SDP_MOS2::SDP_MOS2(const COMMON_COMPONENT* cc)
  :SDP_MOS123(cc)
{
  assert(cc);
  const COMMON_MOS* c = prechecked_cast<const COMMON_MOS*>(cc);
  assert(c);
  const MODEL_MOS2* m = prechecked_cast<const MODEL_MOS2*>(c->model());
  assert(m);
  relxj = ((m->xj != NA && m->xj > 0)
	       ? .5 * m->xj / l_eff
	       : NA);
  eta_1 = ((cgate != 0)
	       ? (kPI/4.) * E_SI * m->delta / cgate * l_eff
	       : 0.);
  eta = eta_1 + 1.;
  eta_2 = eta / 2.;
}
/*--------------------------------------------------------------------------*/
TDP_MOS2::TDP_MOS2(const DEV_MOS* d)
  :TDP_MOS123(d)
{
  assert(d);
  const COMMON_MOS* c = prechecked_cast<const COMMON_MOS*>(d->common());
  assert(c);
  const SDP_MOS2* b = prechecked_cast<const SDP_MOS2*>(c->sdp());
  assert(b);
  const MODEL_MOS2* m = prechecked_cast<const MODEL_MOS2*>(c->model());
  assert(m);

      double temp = SIM::temp;
      double tempratio  = temp / m->_tnom; // ratio
      double tempratio4 = tempratio * sqrt(tempratio);
      double kt = temp * K;
      double egap = 1.16 - (7.02e-4*temp*temp) / (temp+1108.);
      double arg = (m->egap*tempratio - egap) / (2*kt);

  vt = kt / Q;
  phi = m->phi*tempratio + (-2*vt*(1.5*log(tempratio)+Q*(arg)));
  sqrt_phi = sqrt(phi);
  phi_sqrt_phi = phi * sqrt_phi;
  beta = m->kp * tempratio4 * b->w_eff / b->l_eff;
  uo = m->uo * tempratio4;
  vbi = (fixzero(
	(m->vto - m->polarity * m->gamma * sqrt(m->phi)
	 +.5*(m->egap-egap) + m->polarity* .5 * (phi-m->phi)), m->phi));
}
/*--------------------------------------------------------------------------*/
MODEL_MOS2::MODEL_MOS2()
  :MODEL_MOS123(),
   kp(NA),
   nfs(0.0),
   vmax(NA),
   neff(1.0),
   ucrit(1e4*ICM2M),
   uexp(NA),
   utra(NA),
   delta(0.0),
   calc_kp(false),
   alpha(NA),
   xd(NA),
   xwb(NA),
   vbp(NA),
   cfsox(NA)
{
  ++_count;
  mjsw = .33;
  tox = 1e-7;
  cox = NA;
  vto = NA;
  gamma = NA;
  phi = NA;
  mos_level = LEVEL;
}
/*--------------------------------------------------------------------------*/
bool MODEL_MOS2::parse_front(CS& cmd)
{
  return MODEL_MOS123::parse_front(cmd);
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS2::parse_params(CS& cmd)
{
  get(cmd, "DIODElevel", &mos_level);
  get(cmd, "KP", &kp);
  get(cmd, "NFS", &nfs, mSCALE, ICM2M2);
  get(cmd, "VMAx", &vmax);
  get(cmd, "NEFf", &neff, mPOSITIVE);
  get(cmd, "UCRit", &ucrit, mSCALE, ICM2M);
  get(cmd, "UEXp", &uexp);
  get(cmd, "UTRa", &utra);
  get(cmd, "DELta", &delta);
  MODEL_MOS123::parse_params(cmd);
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS2::parse_finish()
{
  MODEL_MOS123::parse_finish();
      
      cox = E_OX / tox;
      if (kp == NA) {
	kp = uo * cox;
	calc_kp = true;
      }
      if (nsub != NA) {
	if (phi == NA) {
	  phi = (2.*KoQ) * _tnom * log(nsub/NI);
	  if (phi < .1) {
	    untested();
	    error(bWARNING,
		  long_label() + ": calculated phi too small, using .1\n");
	    phi = .1;
	  }
	  calc_phi = true;
	}
	if (gamma == NA) {
	  gamma = sqrt(2. * E_SI * Q * nsub) / cox;
	  calc_gamma = true;
	}
	if (vto == NA) {
	  double phi_ms = (tpg == gtMETAL)
	    ? -.05 - (egap + polarity * phi) / 2.
	    : -polarity * (tpg * egap + phi) / 2.;
	  double vfb = phi_ms - Q * nss / cox;
	  vto = vfb + polarity * (phi + gamma * sqrt(phi));
	  calc_vto = true;
	}
      }

  if (cox == NA) {
    cox = E_OX/tox;
  }
  if (vto == NA) {
    vto = 0.0;
  }
  if (gamma == NA) {
    gamma = 0.0;
  }
  if (phi == NA) {
    phi = 0.6;
  }
  if (kp == NA) {
    kp = 2e-5;
  }
  alpha = ((nsub != NA)
	? (2. * E_SI) / (Q * nsub)
	: 0.);
  xd = sqrt(alpha);
  xwb = ((nsub != NA)
	? xd * sqrt(pb)
	: .25e-6);
  vbp = ucrit * E_SI / cox;
  cfsox = Q * nfs / cox;
}
/*--------------------------------------------------------------------------*/
SDP_CARD* MODEL_MOS2::new_sdp(const COMMON_COMPONENT* c)const
{
  assert(c);
  {if (dynamic_cast<const COMMON_MOS*>(c)) {
    return new SDP_MOS2(c);
  }else{
    return MODEL_MOS123::new_sdp(c);
  }}
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS2::print_front(OMSTREAM& o)const
{
  MODEL_MOS123::print_front(o);
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS2::print_params(OMSTREAM& o)const
{
  o << "level=2";
  MODEL_MOS123::print_params(o);
  if (mos_level != LEVEL)
    o << "  diodelevel=" << mos_level;
  if (!calc_kp)
    o << "  kp=" << kp;
  o << "  nfs=" << nfs/(ICM2M2);
  if (vmax != NA)
    o << "  vmax=" << vmax;
  if (neff != 1.0 || lambda == NA)
    o << "  neff=" << neff;
  if (ucrit != 1e4*ICM2M || uexp != NA)
    o << "  ucrit=" << ucrit/(ICM2M);
  if (uexp != NA)
    o << "  uexp=" << uexp;
  if (false)
    o << "  utra=" << utra;
  o << "  delta=" << delta;
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS2::print_calculated(OMSTREAM& o)const
{
  MODEL_MOS123::print_calculated(o);
  if (calc_kp)
    o << "* kp=" << kp;
}
/*--------------------------------------------------------------------------*/
void MODEL_MOS2::tr_eval(COMPONENT* brh)const
{
  DEV_MOS* d = prechecked_cast<DEV_MOS*>(brh);
  assert(d);
  const COMMON_MOS* c = prechecked_cast<const COMMON_MOS*>(d->common());
  assert(c);
  const SDP_MOS2* b = prechecked_cast<const SDP_MOS2*>(c->sdp());
  assert(b);
  const MODEL_MOS2* m = this;
  TDP_MOS2 t(d);

    #define short_channel	(m->xj != NOT_INPUT  &&  m->xj > 0.)
    #define do_subthreshold	(m->nfs != 0.)
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    trace1(d->long_label().c_str(), d->evaliter());
    trace3("", d->vds, d->vgs, d->vbs);
    assert(m->_tnom > 0);
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    d->reverse_if_needed();
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    double v_phi_s = t.phi - d->vbs;
    double sarg, dsarg_dvbs, d2sdb2, sarg3;
    {
      {if (d->vbs <= 0.) {
	sarg = sqrt(v_phi_s);
	dsarg_dvbs = -.5 / sarg;
	d2sdb2 = .5 * dsarg_dvbs / v_phi_s;
	d->sbfwd = false;
	trace3("sb-ok", sarg, v_phi_s, dsarg_dvbs);
      }else{
	{if (OPT::mosflags & 01000) {
	  sarg = t.sqrt_phi / (1. + .5 * d->vbs / t.phi);
	  dsarg_dvbs = -.5 * sarg * sarg / t.phi_sqrt_phi;
	  d2sdb2 = -dsarg_dvbs * sarg / t.phi_sqrt_phi;
	  untested();
	  trace3("***sb-reversed(01000)***", sarg, v_phi_s, dsarg_dvbs);
	}else{
	  sarg = t.sqrt_phi 
	    / (1. + .5 * d->vbs / t.phi 
	       + .375 * d->vbs * d->vbs / (t.phi * t.phi));
	  dsarg_dvbs = (-.5 * sarg * sarg / t.phi_sqrt_phi) 
	    * (1. + 1.5 * d->vbs / t.phi);
	  d2sdb2 = (-dsarg_dvbs * sarg / t.phi_sqrt_phi)
	    - (.75 * sarg / (t.phi_sqrt_phi * t.phi)) 
	    * (2. * d->vbs * dsarg_dvbs + sarg);
	  untested();
	  trace3("***sb-reversed(00000)***", sarg, v_phi_s, dsarg_dvbs);
	}}
	d->sbfwd = true;
      }}
      sarg3 = sarg*sarg*sarg;
      assert(sarg > 0.);
      assert(dsarg_dvbs < 0.);
      assert(up_order(-1/t.phi, d2sdb2, 1/t.phi));
      trace2("", d2sdb2, sarg3);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    double barg, dbarg_dvbs, d2bdb2;
    {
      double vbd = d->vbs - d->vds;
      double v_phi_d = t.phi - vbd;
      {if (vbd <= 0.) {
	barg = sqrt(v_phi_d);
	dbarg_dvbs = -.5 / barg;
	d2bdb2 = .5 * dbarg_dvbs / v_phi_d;
	d->dbfwd = false;
	trace4("db-ok", barg, v_phi_d, dbarg_dvbs, d2bdb2);
      }else{
	{if (OPT::mosflags & 01000) {
	  barg = t.sqrt_phi / (1. + .5 * vbd / t.phi);
	  dbarg_dvbs = -.5 * barg * barg / t.phi_sqrt_phi;
	  d2bdb2 = -dbarg_dvbs * barg / t.phi_sqrt_phi;
	  untested();
	  trace4("***db-reversed(00000)***",barg, v_phi_d, dbarg_dvbs, d2bdb2);
	}else{
	  barg = t.sqrt_phi 
	    / (1. + .5 * vbd / t.phi 
	       + .375 * vbd * vbd / (t.phi * t.phi));
	  dbarg_dvbs = (-.5 * barg * barg / t.phi_sqrt_phi) 
	    * (1. + 1.5 * vbd / t.phi);
	  d2bdb2 = (-dbarg_dvbs * barg / t.phi_sqrt_phi)
	    - (.75 * barg / (t.phi_sqrt_phi * t.phi)) 
	    * (2. * vbd * dbarg_dvbs + barg);
	  trace4("***db-reversed(00000)***",barg, v_phi_d, dbarg_dvbs, d2bdb2);
	}}
	d->dbfwd = true;
      }}
      assert(barg > 0.);
      assert(dbarg_dvbs < 0.);
      assert(up_order(-1/t.phi, d2bdb2, 1/t.phi));
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    double gamma_s, dgamma_s_dvds, dgamma_s_dvbs, dgddb2;
    {
      {if (short_channel) {
	double argxd = 1. + 2. * barg * m->xd / m->xj;
	assert(argxd > 0);
	double argd = sqrt(argxd);
	trace2("", argxd, argd);
	
	double alpha_d = b->relxj * (argd - 1.);
	double dalpha_d_dvds = m->xd / (4. * b->l_eff * argd * barg);
	double dalpha_d_dvbs = -dalpha_d_dvds;
	trace3("", alpha_d, dalpha_d_dvds, dalpha_d_dvbs);
	
	double argxs = 1. + 2. * sarg * m->xd / m->xj;
	assert(argxs > 0);
	double args = sqrt(argxs);
	trace2("", argxs, args);
	
	double alpha_s = b->relxj * (args - 1.);
	double dalpha_s_dvbs = -m->xd / (4. * b->l_eff * args * sarg);
	trace2("", alpha_s, dalpha_s_dvbs);
	
	gamma_s = m->gamma * (1. - alpha_s - alpha_d);
	dgamma_s_dvds = -m->gamma *  dalpha_d_dvds;
	dgamma_s_dvbs = -m->gamma * (dalpha_d_dvbs + dalpha_s_dvbs);
	
	double dasdb2=-m->xd*(d2sdb2+dsarg_dvbs*dsarg_dvbs*m->xd/(m->xj*argxs))
	  / (b->l_eff*args);
	double daddb2=-m->xd*(d2bdb2+dbarg_dvbs*dbarg_dvbs*m->xd/(m->xj*argxd))
	  / (b->l_eff*argd);
	dgddb2 = -.5 * m->gamma * (dasdb2 + daddb2);
	
	if (gamma_s <= 0. && m->gamma > 0. /* && !IO::suppresserrors */) {
	  untested();
	  error(bTRACE, d->long_label() + ": gamma is negative\n");
	  error(bTRACE, "+   gamma_s=%g, alpha_s=%g, alpha_d=%g\n",
		gamma_s,    alpha_s,    alpha_d);
	}
	trace4("no short chan", gamma_s, dgamma_s_dvds, dgamma_s_dvds, dgddb2);
      }else{
	gamma_s = m->gamma;
	dgamma_s_dvds = dgamma_s_dvbs = 0.;
	dgddb2 = 0.;
	trace4("short channel", gamma_s, dgamma_s_dvds, dgamma_s_dvds, dgddb2);
      }}
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* von, subthreshold, cutoff, vgst */
    double vc, vc_eta, dvon_dvbs;
    double xn, vtxn, dxn_dvbs;	/* subthreshold only */
    {
      double vbin = t.vbi + b->eta_1 * v_phi_s;
      d->von = vbin + gamma_s * sarg;
      dvon_dvbs = -b->eta_1 + dgamma_s_dvbs * sarg + gamma_s * dsarg_dvbs;
      trace3("guess", vbin, d->von, dvon_dvbs);
      
      {if (do_subthreshold) {
	double cdonco = -(gamma_s*dsarg_dvbs + dgamma_s_dvbs*sarg) + b->eta_1;
	xn = 1. + m->cfsox + cdonco;
	vtxn = t.vt * xn;
	dxn_dvbs = 2. * dgamma_s_dvbs * dsarg_dvbs
	  + gamma_s * d2sdb2 + dgddb2 * sarg;
	trace3("do_sub", xn, vtxn, dxn_dvbs);
	
	d->von += vtxn;
	dvon_dvbs += t.vt * dxn_dvbs;
	d->vgst = d->vgs - d->von;
	trace3("", d->von, dvon_dvbs, d->vgst);
	
	d->subthreshold = (d->vgs < d->von);
	d->cutoff = false;
      }else{
	xn = vtxn = dxn_dvbs = 0.;
	d->vgst = d->vgs - d->von;
	trace3("no_sub", xn, vtxn, dxn_dvbs);
	trace3("", d->von, dvon_dvbs, d->vgst);
	
	d->subthreshold = false;
	d->cutoff = (d->vgs < d->von);
	if (d->cutoff) {
	  trace0("***** cut off *****");
	  d->ids = 0.;
	  d->gmf = d->gmr = 0.;
	  d->gds = 0.;
	  d->gmbf = d->gmbr = 0.;
	  return;
	}
      }}
      double vgsx = (d->subthreshold) ? d->von : d->vgs;
      vc = vgsx - vbin;
      vc_eta = vc / b->eta;
      trace3("", vgsx, vc, vc_eta);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    double ufact, duf_dvgs, duf_dvds, duf_dvbs, ueff;
    {
      {if (m->uexp != NOT_INPUT  &&  d->vgst > m->vbp) {
	ufact = pow(m->vbp/d->vgst, m->uexp);
	duf_dvgs = -ufact * m->uexp / d->vgst;
	duf_dvds = 0.;	/* wrong, but as per spice2 */
	duf_dvbs = dvon_dvbs * ufact * m->uexp / d->vgst;
	trace4("calc ufact", ufact, duf_dvgs, duf_dvds, duf_dvbs);
      }else{
	ufact = 1.;
	duf_dvgs = duf_dvds = duf_dvbs = 0.;
	trace4("def ufact", ufact, duf_dvgs, duf_dvds, duf_dvbs);
      }}
      ueff = t.uo * ufact;	/* ???? */
      trace2("", ufact, ueff);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* vdsat  according to Baum's Theory of scattering velocity saturation  */
    int use_vmax = m->vmax != NOT_INPUT;
    {if (use_vmax) {
      double gammad = gamma_s / b->eta;
      double v1 = vc_eta + v_phi_s;
      double v2 = v_phi_s;
      double xv = m->vmax * b->l_eff / ueff;
      double a1 = gammad * (4./3.);
      double b1 = -2. * (v1+xv);
      double c1 = -2. * gammad * xv;			/* end of scope */
      double d1 = 2.*v1*(v2+xv) - v2*v2 - (4./3.)*gammad*sarg3;
      double a = -b1;					/* xv, v1, v2, sarg3 */
      double b = a1 * c1 - 4. * d1;
      double c = -d1 * (a1*a1 - 4.*b1) - c1*c1;
      double r = -a*a / 3. + b;
      double r3 = r*r*r;				/* r */
      double s = 2. * a*a*a / 27. - a*b / 3. + c;	/* b, c */
      double s2 = s*s;
      double p = s2 / 4. + r3 / 27.;			/* r */
      double y3;
      {if (p < 0.) {					/* p */
	double ro = pow((-r3 / 27), (1./6.));		/* s2, r3 */
	double fi = atan(-2. * sqrt(-p) / s);
	y3 = 2. * ro * cos(fi/3.) - a / 3.;
      }else{
	double p2 = sqrt(p);
	double p3 = pow((fabs(-s/2.+p2)), (1./3.));
	double p4 = pow((fabs(-s/2.-p2)), (1./3.));	/* s */
	y3 = p3 + p4 - a / 3.;				/* a */
	untested();
      }}
      
      double x4[8];
      int iknt = 0;
      {if (a1*a1 / 4. - b1 + y3  < 0.  &&  y3*y3 / 4. - d1  < 0.) {
	untested();
	error(bWARNING,
	      "%s: internal error: a3,b4, a1=%g, b1=%g, y3=%g, d1=%g\n",
	      d->long_label().c_str(),    a1,    b1,    y3,    d1);
      }else{
	double a3 = sqrt(a1*a1 / 4. - b1 + y3);
	double b3 = sqrt(y3*y3 / 4. - d1);
	for (int i = 0;   i < 4;   i++) {
	  static const double sig1[4] = {1., -1., 1., -1.};
	  static const double sig2[4] = {1., 1., -1., -1.};
	  double a4 = a1 / 2. + sig1[i] * a3;
	  double b4 = y3 / 2. + sig2[i] * b3;		/* y3 */
	  double delta4 = a4*a4 / 4. - b4;
	  if (delta4 >= 0.) {
	    double sd4 = sqrt(delta4);
	    x4[iknt++] = - a4 / 2. + sd4;
	    x4[iknt++] = - a4 / 2. - sd4;		/* i */
	  }
	}
      }}
      
      double xvalid = 0.;
      int root_count = 0;
      for (int j = 0;   j < iknt;   j++) {			/* iknt */
	if (x4[j] > 0.) {
	  double poly4 = x4[j]*x4[j]*x4[j]*x4[j]/* ~= 0, used as check	*/
	    + a1 * x4[j]*x4[j]*x4[j]		/* roundoff error not	*/
	    + b1 * x4[j]*x4[j]			/* propagated, so ok	*/
	    + c1 * x4[j]
	    + d1;				/* a1, b1, c1, d1 */
	  if (fabs(poly4) <= 1e-6) {
	    root_count++;
	    if (root_count <= 1) {		/* xvalid = min(x4[j]) */
	      xvalid=x4[j];
	    }
	    {if (x4[j] <= xvalid) {
	      xvalid=x4[j];				/* x4[], j */
	    }else{
	      untested();
	    }}
	  }
	}
      }
      {if (root_count <= 0) {				/* root_count */
	if (OPT::picky <= bTRACE || !IO::suppresserrors) {
	  untested();
	  error(bWARNING, d->long_label() + ": Baum's theory rejected\n");
	}
	use_vmax = false;
	d->vdsat = 0.;
	trace1("use_vmax rejected", d->vdsat);
      }else{
	d->vdsat = xvalid*xvalid - v_phi_s;
	trace1("use_vmax", d->vdsat);
      }}
    }else{
      d->vdsat = 0.;
      trace1("!use_vmax", d->vdsat);
    }}
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* vdsat     according to Grove-Frohman equation  */
    double dvdsat_dvgs = NOT_VALID;
    double dvdsat_dvbs = NOT_VALID;
    {if (!use_vmax) {
      {if (gamma_s > 0.) {
	double argv = vc_eta + v_phi_s;
	{if (argv > 0.) {
	  double gammad = gamma_s / b->eta;
	  double gammd2 = gammad * gammad;
	  double arg1 = sqrt(1. + 4. * argv / gammd2);
	  d->vdsat = vc_eta  +  gammd2 * (1.-arg1) / 2.;
	  dvdsat_dvgs = (1. - 1./arg1) / b->eta;
	  dvdsat_dvbs = (gammad * (1.-arg1) + 2.*argv / (gammad*arg1))
	    / b->eta * dgamma_s_dvbs
	    + 1./arg1 + b->eta_1 * dvdsat_dvgs;
	  trace3("!use_vmax,gamma>0,argv>0",d->vdsat,dvdsat_dvgs,dvdsat_dvbs);
	}else{
	  d->vdsat = 0.;
	  dvdsat_dvgs = dvdsat_dvbs = 0.;
	  untested();
	  if (!IO::suppresserrors) {
	    untested();
	    error(bWARNING, d->long_label() + ": argv is negative\n");
	  }
	  untested();
	  trace2("argv<0", argv, vc);
	  trace3("!use_vmax,gamma>0,argv<=0",d->vdsat,dvdsat_dvgs,dvdsat_dvbs);
	}}
      }else{
	d->vdsat = vc_eta;
	dvdsat_dvgs = 1.;
	dvdsat_dvbs = 0.;
	trace3("!use_vmax, gamma<=0", d->vdsat, dvdsat_dvgs, dvdsat_dvbs);
      }}
    }else{
      /* dvdsat_dvgs, dvdsat_dvbs   deferred */
      trace3("use_vmax", d->vdsat, dvdsat_dvgs, dvdsat_dvbs);
    }}
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    if (d->vdsat < 0.) {
      error(bWARNING, "%s: calculated vdsat (%g) < 0.  using vdsat = 0.\n",
	    d->long_label().c_str(), d->vdsat);
      d->vdsat = 0.;
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    double bsarg, dbsarg_dvbs;
    {
      double vbdsat = d->vbs - d->vdsat;
      {if (vbdsat <= 0.) {
	double v_phi_ds = t.phi - vbdsat;
	bsarg = sqrt(v_phi_ds);
	dbsarg_dvbs = -.5 / bsarg;
	trace3("vbdsat <= 0", vbdsat, bsarg, dbsarg_dvbs);
      }else{
	bsarg = t.sqrt_phi / (1. + .5 * vbdsat / t.phi);
	dbsarg_dvbs = -.5 * bsarg * bsarg / t.phi_sqrt_phi;
	untested();
	trace3("vbdsat > 0", vbdsat, bsarg, dbsarg_dvbs);
      }}
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* local   dvdsat_dvgs, dvdsat_dvbs   maybe */
    {
      {if (use_vmax) {
	double bodys = bsarg*bsarg*bsarg - sarg3;
	double gdbdvs =
	  2. * gamma_s * (bsarg*bsarg*dbsarg_dvbs - sarg*sarg*dsarg_dvbs);
	double argv = vc_eta - d->vdsat;
	double vqchan = argv - gamma_s * bsarg;
	double dqdsat = -1. + gamma_s * dbsarg_dvbs;
	double vl = m->vmax * b->l_eff;
	double dfunds = vl * dqdsat - ueff * vqchan;
	double dfundg = (vl - ueff * d->vdsat) / b->eta;
	double dfundb = -vl * (1. + dqdsat - b->eta_1 / b->eta)
	  + ueff * (gdbdvs - dgamma_s_dvbs * bodys / 1.5) / b->eta;
	dvdsat_dvgs = -dfundg / dfunds;
	dvdsat_dvbs = -dfundb / dfunds;
	trace2("use_vmax", dvdsat_dvgs, dvdsat_dvbs);
      }else{
	/* dvdsat_dvgs, dvdsat_dvbs   already set */
	trace2("!use_vmax", dvdsat_dvgs, dvdsat_dvbs);
      }}
      assert(dvdsat_dvgs != NOT_VALID);
      assert(dvdsat_dvbs != NOT_VALID);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    double  dl_dvgs, dl_dvds, dl_dvbs, clfact;
    {
      {if (d->vds > 0.) {
	{if (m->lambda == NOT_INPUT) {
	  double dldsat;
	  {if (use_vmax) {
	    double xdv = m->xd / sqrt(m->neff);
	    double xlv = m->vmax * xdv / (2. * ueff);
	    double argv = d->vds - d->vdsat;
	    if (argv < 0.) {
	      argv = 0.;
	    }
	    double xls = sqrt(xlv*xlv + argv);
	    double dl = (xls-xlv) * xdv;
	    /* lambda = dl / (b->l_eff * d->vds); */
	    clfact = (1. - dl / b->l_eff);
	    dldsat = xdv / (2. * xls * b->l_eff);
	  }else{
	    double argv = (d->vds - d->vdsat) / 4.;
	    double sargv = sqrt(1. + argv*argv);
	    {if (argv + sargv >= 0.) {
	      double dl = m->xd * sqrt(argv + sargv);
	      /* lambda = dl / (b->l_eff * d->vds); */
	      clfact = (1. - dl / b->l_eff);
	      /* dldsat = lambda * d->vds / (8. * sargv); */
	      dldsat = dl / (b->l_eff * 8. * sargv);
	    }else{
	      /* lambda = 0.; */
	      clfact = 1.;
	      dldsat = 0.;
	      untested();
	      error(bWARNING, "%s: internal error: vds(%g) < vdsat(%g)\n",
		    d->long_label().c_str(), d->vds,   d->vdsat);
	    }}
	  }}
	  dl_dvgs =  dvdsat_dvgs * dldsat;
	  dl_dvds =              - dldsat;
	  dl_dvbs =  dvdsat_dvbs * dldsat;
	}else{
	  /* lambda = m->lambda; */
	  clfact = (1. - m->lambda * d->vds);
	  dl_dvgs = dl_dvbs = 0.;
	  dl_dvds = -m->lambda;
	}}
	
	/* clfact = (1. - lambda * d->vds); */
	if (clfact < m->xwb/b->l_eff) {
	  double leff = m->xwb / (2. - (clfact * b->l_eff / m->xwb));
	  double dfact = (leff * leff) / (m->xwb * m->xwb);
	  clfact = leff / b->l_eff;
	  dl_dvgs *= dfact;
	  dl_dvds *= dfact;
	  dl_dvbs *= dfact;
	}
      }else{  /* vds <= 0. */
	/* lambda = 0.; */
	clfact = 1.;
	dl_dvgs = dl_dvds = dl_dvbs = 0.;
	trace1("*** vds < 0 ***", d->vds);
      }}
      trace4("", dl_dvgs, dl_dvds, dl_dvbs, clfact);
    }
    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 
    /* ids, gmf, gds, gmbf */
    {
      d->saturated = (d->vds > d->vdsat);
      double vdsx =  (d->saturated) ? d->vdsat : d->vds;
      double bargx = (d->saturated) ? bsarg : barg;
      double body = bargx*bargx*bargx - sarg3;
      double expg = (d->subthreshold) ? Exp(d->vgst / vtxn) : 1.;
      trace4("", vdsx, bargx, body, expg);
      
      double beta = t.beta * ufact / clfact;
      double ids_on = 
	beta * ((vc - b->eta_2 * vdsx) * vdsx  - (2./3.) * gamma_s * body);
      double didvds = beta * (vc  -  b->eta * vdsx  -  gamma_s * bargx);
      fixzero(&didvds, ids_on);
      trace4("", beta, ids_on, didvds, d->saturated);
      
      d->ids = ids_on * expg;
      
      d->gmf = beta * vdsx;
      d->gmf += ids_on * (duf_dvgs/ufact - dl_dvgs/clfact);
      if (d->saturated) {
	d->gmf += didvds * dvdsat_dvgs;
      }
      if (d->subthreshold) {
	d->gmf = ids_on / vtxn;
	if (d->saturated) {
	  d->gmf += didvds * dvdsat_dvgs;
	}
	d->gmf *= expg;
      }
      
      d->gds = (d->saturated) ? 0.: didvds;
      d->gds += ids_on * (duf_dvds/ufact - dl_dvds/clfact);
      if (short_channel) {
	d->gds -= beta * (2./3.) * body * dgamma_s_dvds;
      }
      if (d->subthreshold) {
	double dxndvd = dgamma_s_dvds * dsarg_dvbs;
	double dodvds = dgamma_s_dvds * sarg + t.vt * dxndvd;
	double gmw = d->ids * d->vgst / (vtxn * xn);
	d->gds *= expg;
	d->gds -= d->gmf * dodvds + gmw * dxndvd;
      }
      
      d->gmbf = beta * (b->eta_1 * vdsx - gamma_s * (sarg - bargx));
      d->gmbf += ids_on * (duf_dvbs/ufact - dl_dvbs/clfact);
      if (short_channel) {
	d->gmbf -= beta * (2./3.) * body * dgamma_s_dvbs;
      }
      if (d->saturated) {
	d->gmbf += didvds * dvdsat_dvbs;
      }
      if (d->subthreshold) {
	double gmw = d->ids * d->vgst / (vtxn * xn);
	d->gmbf += beta * dvon_dvbs * vdsx;
	d->gmbf *= expg;
	d->gmbf -= d->gmf * dvon_dvbs + gmw * dxn_dvbs;
      }
      trace4("", d->ids, d->gmf, d->gds, d->gmbf);
    }
    {if (d->reversed){
      d->ids *= -1;
      d->gmr = d->gmf;
      d->gmbr = d->gmbf;
      d->gmf = d->gmbf = 0;
    }else{
      d->gmr = d->gmbr = 0.;
    }}
}
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
