* trsolver.cpp - transient solver class implementation
*
* Copyright (C) 2004, 2005, 2006, 2007, 2009 Stefan Jahn <stefan@lkcc.org>
*
* This 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 software 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 package; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* $Id$
*
*/
#if HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <float.h>
#include <algorithm>
#include "compat.h"
#include "object.h"
#include "logging.h"
#include "complex.h"
#include "circuit.h"
#include "sweep.h"
#include "net.h"
#include "netdefs.h"
#include "analysis.h"
#include "nasolver.h"
#include "history.h"
#include "trsolver.h"
#include "transient.h"
#include "exception.h"
#include "exceptionstack.h"
#define STEPDEBUG 0
#define BREAKPOINTS 0
#define dState 0
#define sState 1
#define SOL(state) (solution[(int) getState (sState, (state))])
namespace qucs {
using namespace transient;
trsolver::trsolver ()
: nasolver<nr_double_t> (), states<nr_double_t> ()
{
swp = NULL;
type = ANALYSIS_TRANSIENT;
setDescription ("transient");
for (int i = 0; i < 8; i++) solution[i] = NULL;
tHistory = NULL;
relaxTSR = false;
initialDC = true;
}
trsolver::trsolver (const std::string &n)
: nasolver<nr_double_t> (n), states<nr_double_t> ()
{
swp = NULL;
type = ANALYSIS_TRANSIENT;
setDescription ("transient");
for (int i = 0; i < 8; i++) solution[i] = NULL;
tHistory = NULL;
relaxTSR = false;
initialDC = true;
}
trsolver::~trsolver ()
{
delete swp;
for (int i = 0; i < 8; i++)
{
if (solution[i] != NULL)
{
delete solution[i];
}
}
delete tHistory;
}
based on the given trsolver object. */
trsolver::trsolver (trsolver & o)
: nasolver<nr_double_t> (o), states<nr_double_t> (o)
{
swp = o.swp ? new sweep (*o.swp) : NULL;
for (int i = 0; i < 8; i++) solution[i] = NULL;
tHistory = o.tHistory ? new history (*o.tHistory) : NULL;
relaxTSR = o.relaxTSR;
initialDC = o.initialDC;
}
void trsolver::initSteps (void)
{
delete swp;
swp = createSweep ("time");
}
int trsolver::dcAnalysis (void)
{
int error = 0;
setDescription ("initial DC");
initDC ();
setCalculation ((calculate_func_t) &calcDC);
solve_pre ();
applyNodeset ();
try_running ()
{
error = solve_nonlinear ();
}
catch_exception ()
{
case EXCEPTION_NO_CONVERGENCE:
pop_exception ();
convHelper = CONV_LineSearch;
logprint (LOG_ERROR, "WARNING: %s: %s analysis failed, using line search "
"fallback\n", getName (), getDescription ().c_str());
applyNodeset ();
error = solve_nonlinear ();
break;
default:
estack.print ();
error++;
break;
}
storeSolution ();
solve_post ();
if (error)
{
logprint (LOG_ERROR, "ERROR: %s: %s analysis failed\n",
getName (), getDescription ().c_str());
}
return error;
}
for each requested time and solves it then. */
int trsolver::solve (void)
{
nr_double_t time, saveCurrent;
int error = 0, convError = 0;
const char * const solver = getPropertyString ("Solver");
relaxTSR = !strcmp (getPropertyString ("relaxTSR"), "yes") ? true : false;
initialDC = !strcmp (getPropertyString ("initialDC"), "yes") ? true : false;
runs++;
saveCurrent = current = 0;
stepDelta = -1;
converged = 0;
fixpoint = 0;
statRejected = statSteps = statIterations = statConvergence = 0;
if (!strcmp (solver, "CroutLU"))
eqnAlgo = ALGO_LU_DECOMPOSITION;
else if (!strcmp (solver, "DoolittleLU"))
eqnAlgo = ALGO_LU_DECOMPOSITION_DOOLITTLE;
else if (!strcmp (solver, "HouseholderQR"))
eqnAlgo = ALGO_QR_DECOMPOSITION;
else if (!strcmp (solver, "HouseholderLQ"))
eqnAlgo = ALGO_QR_DECOMPOSITION_LS;
else if (!strcmp (solver, "GolubSVD"))
eqnAlgo = ALGO_SV_DECOMPOSITION;
if (initialDC)
{
error = dcAnalysis ();
if (error)
return -1;
}
setDescription ("transient");
initTR ();
setCalculation ((calculate_func_t) &calcTR);
solve_pre ();
initSteps ();
swp->reset ();
recallSolution ();
applyNodeset (false);
fillSolution (x);
setMode (MODE_INIT);
int running = 0;
rejected = 0;
delta /= 10;
fillState (dState, delta);
adjustOrder (1);
for (int i = 0; i < swp->getSize (); i++)
{
time = swp->next ();
if (progress) logprogressbar (i, swp->getSize (), 40);
#if DEBUG && 0
logprint (LOG_STATUS, "NOTIFY: %s: solving netlist for t = %e\n",
getName (), (double) time);
#endif
do
{
#if STEPDEBUG
if (delta == deltaMin)
{
logprint (LOG_ERROR,
"WARNING: %s: minimum delta h = %.3e at t = %.3e\n",
getName (), (double) delta, (double) current);
}
#endif
updateCoefficients (delta);
error += predictor ();
if (rejected)
{
restartNR ();
rejected = 0;
}
try_running ()
{
error += corrector ();
}
catch_exception ()
{
case EXCEPTION_NO_CONVERGENCE:
pop_exception ();
if (current > 0) current -= delta;
delta /= 2;
if (delta <= deltaMin)
{
delta = deltaMin;
adjustOrder (1);
}
if (current > 0) current += delta;
statRejected++;
statConvergence++;
rejected++;
converged = 0;
error = 0;
convHelper = CONV_SteepestDescent;
convError = 2;
#if DEBUG
logprint (LOG_ERROR, "WARNING: delta rejected at t = %.3e, h = %.3e "
"(no convergence)\n", (double) saveCurrent, (double) delta);
#endif
break;
default:
estack.print ();
error++;
break;
}
if (error) return -1;
if (rejected) continue;
if (!A->isFinite ())
{
logprint (LOG_ERROR, "ERROR: %s: Jacobian singular at t = %.3e, "
"aborting %s analysis\n", getName (), (double) current,
getDescription ().c_str());
return -1;
}
statIterations += iterations;
if (--convError < 0) convHelper = 0;
if (running > 1)
{
adjustDelta (time);
adjustOrder ();
}
else
{
fillStates ();
nextStates ();
rejected = 0;
}
saveCurrent = current;
current += delta;
running++;
converged++;
setMode (MODE_NONE);
if (running > 1)
{
updateHistory (saveCurrent);
}
else
{
initHistory (saveCurrent);
}
}
while (saveCurrent < time);
#if STEPDEBUG
logprint (LOG_STATUS, "DEBUG: save point at t = %.3e, h = %.3e\n",
(double) saveCurrent, (double) delta);
#endif
#if BREAKPOINTS
saveAllResults (saveCurrent);
#else
saveAllResults (time);
#endif
}
solve_post ();
if (progress) logprogressclear (40);
logprint (LOG_STATUS, "NOTIFY: %s: average time-step %g, %d rejections\n",
getName (), (double) (saveCurrent / statSteps), statRejected);
logprint (LOG_STATUS, "NOTIFY: %s: average NR-iterations %g, "
"%d non-convergences\n", getName (),
(double) statIterations / statSteps, statConvergence);
deinitTR ();
return 0;
}
void trsolver::initHistory (nr_double_t t)
{
tHistory = new history ();
tHistory->push_back(t);
tHistory->self ();
nr_double_t age = 0.0;
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
if (c->hasHistory ())
{
c->applyHistory (tHistory);
saveHistory (c);
if (c->getHistoryAge () > age)
{
age = c->getHistoryAge ();
}
}
}
tHistory->setAge (age);
}
requested them. */
void trsolver::updateHistory (nr_double_t t)
{
if (t > tHistory->last ())
{
tHistory->push_back (t);
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
if (c->hasHistory ()) saveHistory (c);
}
tHistory->drop ();
}
}
void trsolver::saveHistory (circuit * c)
{
int N = countNodes ();
int r, i, s = c->getSize ();
for (i = 0; i < s; i++)
{
r = findAssignedNode (c, i);
if (r < 0)
c->appendHistory (i, 0.0);
else
c->appendHistory (i, x->get (r));
}
for (i = 0; i < c->getVoltageSources (); i++)
{
r = c->getVoltageSource () + i;
c->appendHistory (i + s, x->get (r + N));
}
}
the successive iterative corrector process. */
int trsolver::predictor (void)
{
int error = 0;
switch (predType)
{
case INTEGRATOR_GEAR:
predictGear ();
break;
case INTEGRATOR_ADAMSBASHFORD:
predictBashford ();
break;
case INTEGRATOR_EULER:
predictEuler ();
break;
default:
*x = *SOL (1);
break;
}
saveSolution ();
*SOL (0) = *x;
return error;
}
void trsolver::fillSolution (tvector<nr_double_t> * s)
{
for (int i = 0; i < 8; i++) *SOL (i) = *s;
}
explicit Adams-Bashford integration formula. */
void trsolver::predictBashford (void)
{
int N = countNodes ();
int M = countVoltageSources ();
nr_double_t xn, dd, hn;
for (int r = 0; r < N + M; r++)
{
xn = predCoeff[0] * SOL(1)->get (r);
for (int o = 1; o <= predOrder; o++)
{
hn = getState (dState, o);
dd = (SOL(o)->get (r) - SOL(o + 1)->get (r)) / hn;
xn += predCoeff[o] * dd;
}
x->set (r, xn);
}
}
explicit forward Euler integration formula. Actually this is
Adams-Bashford order 1. */
void trsolver::predictEuler (void)
{
int N = countNodes ();
int M = countVoltageSources ();
nr_double_t xn, dd, hn;
for (int r = 0; r < N + M; r++)
{
xn = predCoeff[0] * SOL(1)->get (r);
hn = getState (dState, 1);
dd = (SOL(1)->get (r) - SOL(2)->get (r)) / hn;
xn += predCoeff[1] * dd;
x->set (r, xn);
}
}
explicit Gear integration formula. */
void trsolver::predictGear (void)
{
int N = countNodes ();
int M = countVoltageSources ();
nr_double_t xn;
for (int r = 0; r < N + M; r++)
{
xn = 0;
for (int o = 0; o <= predOrder; o++)
{
xn += predCoeff[o] * SOL(o + 1)->get (r);
}
x->set (r, xn);
}
}
process until a certain error tolerance has been reached. */
int trsolver::corrector (void)
{
int error = 0;
error += solve_nonlinear ();
return error;
}
void trsolver::nextStates (void)
{
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
c->nextState ();
}
*SOL (0) = *x;
nextState ();
statSteps++;
}
other states as well. It is useful for higher order integration
methods in order to initialize the states after the initial
transient solution. */
void trsolver::fillStates (void)
{
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
for (int s = 0; s < c->getStates (); s++)
c->fillState (s, c->getState (s));
}
}
void trsolver::setMode (int state)
{
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
c->setMode (state);
}
void trsolver::setDelta (void)
{
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
c->setDelta (deltas);
}
global truncation error. */
void trsolver::adjustDelta (nr_double_t t)
{
deltaOld = delta;
delta = checkDelta ();
if (delta > deltaMax) delta = deltaMax;
if (delta < deltaMin) delta = deltaMin;
int good = 0;
if (!relaxTSR)
{
if (!statConvergence || converged > 64)
{
if (stepDelta > 0.0)
{
delta = stepDelta;
stepDelta = -1.0;
}
else
{
if ((t - (current + delta) < deltaMin) && ((current + delta) < t))
{
delta /= 2.0;
}
else
{
if (delta > (t - current) && t > current)
{
stepDelta = deltaOld;
delta = t - current;
good = 1;
}
else
{
stepDelta = -1.0;
}
}
}
if (delta > deltaMax) delta = deltaMax;
if (delta < deltaMin) delta = deltaMin;
}
}
if (delta > 0.9 * deltaOld || good)
{
nextStates ();
rejected = 0;
#if STEPDEBUG
logprint (LOG_STATUS,
"DEBUG: delta accepted at t = %.3e, h = %.3e\n",
(double) current, (double) delta);
#endif
}
else if (deltaOld > delta)
{
rejected++;
statRejected++;
#if STEPDEBUG
logprint (LOG_STATUS,
"DEBUG: delta rejected at t = %.3e, h = %.3e\n",
(double) current, (double) delta);
#endif
if (current > 0) current -= deltaOld;
}
else
{
nextStates ();
rejected = 0;
}
}
integration method or to reduce it. */
void trsolver::adjustOrder (int reduce)
{
if ((corrOrder < corrMaxOrder && !rejected) || reduce)
{
if (reduce)
{
corrOrder = 1;
}
else if (!rejected)
{
corrOrder++;
}
corrType = correctorType (CMethod, corrOrder);
predType = predictorType (corrType, corrOrder, predOrder);
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
c->setOrder (corrOrder);
setIntegrationMethod (c, corrType);
}
}
}
function. */
void trsolver::calcDC (trsolver * self)
{
circuit * root = self->getNet()->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
c->calcDC ();
}
}
function. */
void trsolver::calcTR (trsolver * self)
{
circuit * root = self->getNet()->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
c->calcTR (self->current);
}
}
function. */
void trsolver::initDC (void)
{
circuit * root = subnet->getRoot ();
for (circuit * c = root; c != NULL; c = (circuit *) c->getNext ())
{
c->initDC ();
}
}
function. */
void trsolver::initTR (void)
{
const char * const IMethod = getPropertyString ("IntegrationMethod");
nr_double_t start = getPropertyDouble ("Start");
nr_double_t stop = getPropertyDouble ("Stop");
nr_double_t points = getPropertyDouble ("Points");
corrMaxOrder = getPropertyInteger ("Order");
corrType = CMethod = correctorType (IMethod, corrMaxOrder);
predType = PMethod = predictorType (CMethod, corrMaxOrder, predMaxOrder);
corrOrder = corrMaxOrder;
predOrder = predMaxOrder;
delta = getPropertyDouble ("InitialStep");
deltaMin = getPropertyDouble ("MinStep");
deltaMax = getPropertyDouble ("MaxStep");
if (deltaMax == 0.0)
deltaMax = std::min ((stop - start) / (points - 1), stop / 200);
if (deltaMin == 0.0)
deltaMin = NR_TINY * 10 * deltaMax;
if (delta == 0.0)
delta = std::min (stop / 200, deltaMax) / 10;
if (delta < deltaMin) delta = deltaMin;
if (delta > deltaMax) delta = deltaMax;
setStates (2);
initStates ();
fillState (dState, delta);
saveState (dState, deltas);
setDelta ();
calcCorrectorCoeff (corrType, corrOrder, corrCoeff, deltas);
calcPredictorCoeff (predType, predOrder, predCoeff, deltas);
for (int i = 0; i < 8; i++)
{
solution[i] = new tvector<nr_double_t>;
setState (sState, (nr_double_t) i, i);
}
circuit *c, * root = subnet->getRoot ();
for (c = root; c != NULL; c = (circuit *) c->getNext ())
initCircuitTR (c);
for (c = root; c != NULL; c = (circuit *) c->getPrev ())
initCircuitTR (c);
}
void trsolver::deinitTR (void)
{
for (int i = 0; i < 8; i++)
{
delete solution[i];
solution[i] = NULL;
}
if (tHistory)
{
delete tHistory;
tHistory = NULL;
}
}
void trsolver::initCircuitTR (circuit * c)
{
c->initTR ();
c->initStates ();
c->setCoefficients (corrCoeff);
c->setOrder (corrOrder);
setIntegrationMethod (c, corrType);
}
(for the given timestamp) into the output dataset. */
void trsolver::saveAllResults (nr_double_t time)
{
qucs::vector * t;
if ((t = data->findDependency ("time")) == NULL)
{
t = new qucs::vector ("time");
data->addDependency (t);
}
if (runs == 1) t->add (time);
saveResults ("Vt", "It", 0, t);
}
analysis advanced. For the computation of the new time-step the
truncation error depending on the integration method is used. */
nr_double_t trsolver::checkDelta (void)
{
nr_double_t LTEreltol = getPropertyDouble ("LTEreltol");
nr_double_t LTEabstol = getPropertyDouble ("LTEabstol");
nr_double_t LTEfactor = getPropertyDouble ("LTEfactor");
nr_double_t dif, rel, tol, lte, q, n = std::numeric_limits<nr_double_t>::max();
int N = countNodes ();
int M = countVoltageSources ();
nr_double_t cec = getCorrectorError (corrType, corrOrder);
nr_double_t pec = getPredictorError (predType, predOrder);
for (int r = 0; r < N + M; r++)
{
if (r >= N)
{
if (findVoltageSource(r - N)->isVSource ())
continue;
}
dif = x->get (r) - SOL(0)->get (r);
if (std::isfinite (dif) && dif != 0)
{
rel = MAX (fabs (x->get (r)), fabs (SOL(0)->get (r)));
tol = LTEreltol * rel + LTEabstol;
lte = LTEfactor * (cec / (pec - cec)) * dif;
q = delta * exp (log (fabs (tol / lte)) / (corrOrder + 1));
n = std::min (n, q);
}
}
#if STEPDEBUG
logprint (LOG_STATUS, "DEBUG: delta according to local truncation "
"error h = %.3e\n", (double) n);
#endif
delta = std::min ((n > 1.9 * delta) ? 2 * delta : delta, n);
return delta;
}
void trsolver::updateCoefficients (nr_double_t delta)
{
setState (dState, delta);
saveState (dState, deltas);
calcCorrectorCoeff (corrType, corrOrder, corrCoeff, deltas);
calcPredictorCoeff (predType, predOrder, predCoeff, deltas);
}
PROP_REQ [] =
{
{
"Type", PROP_STR, { PROP_NO_VAL, "lin" },
PROP_RNG_STR2 ("lin", "log")
},
{ "Start", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
{ "Stop", PROP_REAL, { 1e-3, PROP_NO_STR }, PROP_POS_RANGE },
{ "Points", PROP_INT, { 10, PROP_NO_STR }, PROP_MIN_VAL (2) },
PROP_NO_PROP
};
PROP_OPT [] =
{
{
"IntegrationMethod", PROP_STR, { PROP_NO_VAL, "Trapezoidal" },
PROP_RNG_STR4 ("Euler", "Trapezoidal", "Gear", "AdamsMoulton")
},
{ "Order", PROP_INT, { 2, PROP_NO_STR }, PROP_RNGII (1, 6) },
{ "InitialStep", PROP_REAL, { 1e-9, PROP_NO_STR }, PROP_POS_RANGE },
{ "MinStep", PROP_REAL, { 1e-16, PROP_NO_STR }, PROP_POS_RANGE },
{ "MaxStep", PROP_REAL, { 0, PROP_NO_STR }, PROP_POS_RANGE },
{ "MaxIter", PROP_INT, { 150, PROP_NO_STR }, PROP_RNGII (2, 10000) },
{ "abstol", PROP_REAL, { 1e-12, PROP_NO_STR }, PROP_RNG_X01I },
{ "vntol", PROP_REAL, { 1e-6, PROP_NO_STR }, PROP_RNG_X01I },
{ "reltol", PROP_REAL, { 1e-3, PROP_NO_STR }, PROP_RNG_X01I },
{ "LTEabstol", PROP_REAL, { 1e-6, PROP_NO_STR }, PROP_RNG_X01I },
{ "LTEreltol", PROP_REAL, { 1e-3, PROP_NO_STR }, PROP_RNG_X01I },
{ "LTEfactor", PROP_REAL, { 1, PROP_NO_STR }, PROP_RNGII (1, 16) },
{ "Temp", PROP_REAL, { 26.85, PROP_NO_STR }, PROP_MIN_VAL (K) },
{ "Solver", PROP_STR, { PROP_NO_VAL, "CroutLU" }, PROP_RNG_SOL },
{ "relaxTSR", PROP_STR, { PROP_NO_VAL, "no" }, PROP_RNG_YESNO },
{ "initialDC", PROP_STR, { PROP_NO_VAL, "yes" }, PROP_RNG_YESNO },
PROP_NO_PROP
};
struct define_t trsolver::anadef =
{ "TR", 0, PROP_ACTION, PROP_NO_SUBSTRATE, PROP_LINEAR, PROP_DEF };
}