Xyce  6.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
N_NLS_Sensitivity.C
Go to the documentation of this file.
1 //-----------------------------------------------------------------------------
2 // Copyright Notice
3 //
4 // Copyright 2002 Sandia Corporation. Under the terms
5 // of Contract DE-AC04-94AL85000 with Sandia Corporation, the U.S.
6 // Government retains certain rights in this software.
7 //
8 // Xyce(TM) Parallel Electrical Simulator
9 // Copyright (C) 2002-2014 Sandia Corporation
10 //
11 // This program is free software: you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation, either version 3 of the License, or
14 // (at your option) any later version.
15 //
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 //-----------------------------------------------------------------------------
24 
25 //-------------------------------------------------------------------------
26 // Filename : $RCSfile: N_NLS_Sensitivity.C,v $
27 //
28 // Purpose : Body for the sensitivity class.
29 //
30 // Special Notes :
31 //
32 // Creator : Eric R. Keiter, SNL, Parallel Computational Sciences
33 //
34 // Creation Date : 10/30/02
35 //
36 // Revision Information:
37 // ---------------------
38 //
39 // Revision Number: $Revision: 1.82 $
40 //
41 // Revision Date : $Date $
42 //
43 // Current Owner : $Author: tvrusso $
44 //-------------------------------------------------------------------------
45 
46 #include <Xyce_config.h>
47 
48 
49 // ---------- Standard Includes ----------
50 
51 #include <N_UTL_Misc.h>
52 #include <sstream>
53 
54 // ---------- Xyce Includes ----------
55 
56 #include <N_NLS_Sensitivity.h>
57 #include <N_NLS_Manager.h>
58 
59 #include <N_LOA_Loader.h>
60 
61 #include <N_UTL_OptionBlock.h>
62 
63 #include <N_LAS_Matrix.h>
64 #include <N_LAS_Vector.h>
65 
66 #include <N_LAS_Solver.h>
67 #include <N_LAS_Problem.h>
68 
69 #include <N_LAS_System.h>
70 #include <N_LAS_Builder.h>
71 
72 #include <N_ERH_ErrorMgr.h>
74 
75 #include <N_TOP_Topology.h>
76 
77 #include <N_UTL_Expression.h>
78 
79 #include <N_PDS_ParMap.h>
80 #ifdef Xyce_PARALLEL_MPI
81 #include <N_PDS_ParComm.h>
82 #include <mpi.h>
83 #else
84 #include <N_PDS_SerialComm.h>
85 #endif
86 
87 
88 // ---------- Static Declarations ----------
89 
90 //-----------------------------------------------------------------------------
91 // Function : N_NLS_Sensitivity::N_NLS_Sensitivity
92 // Purpose : constructor
93 // Special Notes :
94 // Scope : public
95 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
96 // Creation Date : 10/30/02
97 //-----------------------------------------------------------------------------
99  N_TOP_Topology & topTmp,
100  N_IO_CmdParse & cp)
101  : N_NLS_NonLinearSolver(cp),
102  allocateddXVec_ (false),
103  debugLevel_(1),
104  solutionSize_(0),
105  solveDirectFlag_(true),
106  solveAdjointFlag_(true),
107  outputScaledFlag_(false),
108  outputUnscaledFlag_(true),
109  maxParamStringSize_(0),
110  stdOutputFlag_(true),
111  fileOutputFlag_(false),
112  dakotaFileOutputFlag_(false),
113  numSolves_(0),
114  difference(SENS_FWD),
115  objFuncGiven_(false),
116  objFuncGIDsetup_(false),
117  expNumVars_(0),
118  expVal_(0.0),
119  objFuncString_(""),
120  curValue_(0.0),
121  objFuncEval_(0.0),
122  dOdp_(0.0),
123  sqrtEta_(1.0e-8),
124  sqrtEtaGiven_(false),
125  dOdXVectorPtr_(0),
126  lambdaVectorPtr_(0),
127  savedRHSVectorPtr_(0),
128  savedNewtonVectorPtr_(0),
129  origFVectorPtr_(0),
130  pertFVectorPtr_(0),
131  testFVectorPtr_(0),
132  nls_(nls),
133  top_(topTmp),
134  expPtr_(0),
135  numSensParams_(0)
136 {
137  // if the base nonlinear solver class had a copy constructor, I could use
138  // that here... maybe I'll set that up later. ERK 11/15/02.
143 
148 
149  dOdXVectorPtr_ = lasSysPtr_->builder().createVector();
150  savedRHSVectorPtr_ = lasSysPtr_->builder().createVector();
151  savedNewtonVectorPtr_ = lasSysPtr_->builder().createVector();
152 
153  origFVectorPtr_ = lasSysPtr_->builder().createVector();
154  pertFVectorPtr_ = lasSysPtr_->builder().createVector();
155  testFVectorPtr_ = lasSysPtr_->builder().createVector();
156 
157  solutionSize_ = lasSysPtr_->getSolutionSize();
158 }
159 
160 //-----------------------------------------------------------------------------
161 // Function : N_NLS_Sensitivity::~N_NLS_Sensitivity
162 // Purpose : destructor
163 // Special Notes :
164 // Scope : public
165 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
166 // Creation Date : 10/30/02
167 //-----------------------------------------------------------------------------
169 {
170  delete dOdXVectorPtr_;
171  dOdXVectorPtr_ = 0;
172 
173  delete savedRHSVectorPtr_;
174  savedRHSVectorPtr_ = 0;
175 
176  delete savedNewtonVectorPtr_;
178 
179  delete origFVectorPtr_;
180  origFVectorPtr_ = 0;
181 
182  delete pertFVectorPtr_;
183  pertFVectorPtr_ = 0;
184 
185  delete testFVectorPtr_;
186  testFVectorPtr_ = 0;
187 
188  if (lambdaVectorPtr_) // only allocated for adjoint solves
189  {
190  delete lambdaVectorPtr_;
191  lambdaVectorPtr_ = 0;
192  }
193 
194  if (expPtr_)
195  {
196  delete expPtr_;
197  expPtr_ = 0;
198  }
199 
200  // For all the stuff that is to be deleted in the nonlinear solver
201  // base class destructor, just set those pointers to zero because
202  // they will have been deleted already.
203  //
204  // This is the consequence of having the sensitivity class derived
205  // off of the nonlinear solver base class, but having it use all
206  // the same linear solver objects, etc., as the nonlinear solver used
207  // to solve the problem.
208 
209  NewtonVectorPtr_ = 0;
210  gradVectorPtr_ = 0;
211  solWtVectorPtr_ = 0;
213  lasSolverPtr_ = 0;
214 
215  // delete contents of dfdp vector:
216  for (int iparam=0;iparam<numSensParams_; ++iparam)
217  {
218  if( dfdpPtrVector_[iparam] != 0)
219  {
220  delete dfdpPtrVector_[iparam];
221  dfdpPtrVector_[iparam] = 0;
222  }
223  if (dXdpPtrVector_[iparam] != 0)
224  {
225  delete dXdpPtrVector_[iparam];
226  dXdpPtrVector_[iparam] = 0;
227  }
228  }
229  dfdpPtrVector_.clear();
230  dXdpPtrVector_.clear();
231 }
232 
233 
234 
235 //-----------------------------------------------------------------------------
236 // Function : N_NLS_Sensitivity::stdOutput
237 // Purpose :
238 // Special Notes :
239 // Scope : public
240 // Creator : Eric Keiter, SNL
241 // Creation Date : 2/18/2014
242 //-----------------------------------------------------------------------------
244  std::string idString,
245  std::vector<double> & paramVals,
246  std::vector<double> & sensitivities,
247  std::vector<double> & scaled_sensitivities)
248 {
249  // Send the sensitivity information to the screen:
250 #ifdef Xyce_PARALLEL_MPI
251  N_PDS_Comm *pdsCommPtr = pdsMgrPtr_->getPDSComm();
252  int myPID = pdsCommPtr->procID();
253  if (myPID==0)
254 #endif
255  {
256  Xyce::dout() << "\n"<<idString << " Sensitivities of objective function:"
257  << objFuncString_ << std::endl;
258 
259  Xyce::dout() << std::setw(maxParamStringSize_)<<"Name";
260  Xyce::dout() << "\t"<<std::setw(13)<<"Value";
261  Xyce::dout() << "\t"<<std::setw(13)<<"Sensitivity";
262  Xyce::dout() << "\t"<<std::setw(13)<<"Normalized"<<std::endl;
263 
264  for (int iparam=0; iparam< numSensParams_; ++iparam)
265  {
266  Xyce::dout() << std::setw(maxParamStringSize_)<<paramNameVec_[iparam];
267 
268  Xyce::dout() << "\t" << std::setw(13)<< std::scientific<< std::setprecision(4)
269  << paramVals[iparam];
270 
271  Xyce::dout() << "\t" << std::setw(13)<< std::scientific<< std::setprecision(4)
272  << sensitivities[iparam];
273 
274  Xyce::dout() << "\t" << std::setw(13)<< std::scientific<< std::setprecision(4)
275  << scaled_sensitivities[iparam] << std::endl;
276  }
277  }
278 #ifdef Xyce_PARALLEL_MPI
279  pdsCommPtr->barrier();
280 #endif
281 }
282 
283 //-----------------------------------------------------------------------------
284 // Function : N_NLS_Sensitivity::fileOutput
285 // Purpose : Dump sensitivity information out to a file.
286 // Special Notes :
287 // Scope : public
288 // Creator : Eric Keiter, SNL
289 // Creation Date : 2/18/2014
290 //-----------------------------------------------------------------------------
292  std::string idString,
293  std::vector<double> & paramVals,
294  std::vector<double> & sensitivities,
295  std::vector<double> & scaled_sensitivities)
296 {
297 
298 #ifdef Xyce_PARALLEL_MPI
299  N_PDS_Comm *pdsCommPtr = pdsMgrPtr_->getPDSComm();
300  int myPID = pdsCommPtr->procID();
301  if (myPID==0)
302 #endif
303  {
304  std::ostringstream numSolvesOStr;
305  numSolvesOStr << numSolves_;
306  std::string dodpFileName = netlistFileName_ + numSolvesOStr.str() + "_dodp" + idString +".txt";
307  FILE *fp = fopen(dodpFileName.c_str(),"w");
308  for (int iparam=0;iparam< numSensParams_; ++iparam)
309  {
310  fprintf(fp,"\t%16.8e\n", sensitivities[iparam]);
311  }
312  fclose(fp);
313  }
314 #ifdef Xyce_PARALLEL_MPI
315  pdsCommPtr->barrier();
316 #endif
317 }
318 
319 //-----------------------------------------------------------------------------
320 // Function : N_NLS_Sensitivity::dakOutput
321 // Purpose : Dump sensitivity information out to a dakota-style file.
322 // Special Notes :
323 // Scope : public
324 // Creator : Eric Keiter, SNL
325 // Creation Date : 2/19/2014
326 //-----------------------------------------------------------------------------
328  std::string idString,
329  std::vector<double> & paramVals,
330  std::vector<double> & sensitivities,
331  std::vector<double> & scaled_sensitivities)
332 {
333 #ifdef Xyce_PARALLEL_MPI
334  N_PDS_Comm *pdsCommPtr = pdsMgrPtr_->getPDSComm();
335  int myPID = pdsCommPtr->procID();
336  if (myPID==0)
337 #endif
338  {
339  // write a file format that can be used by dakota :
340  // Note that currently, this will simply overwrite the same
341  // file every time this function is called.
342  std::string dakotaFileName = netlistFileName_ + "_dodp" + idString + "_all.txt";
343  FILE *fp2 = fopen(dakotaFileName.c_str(),"w");
344  fprintf(fp2,"%16.8e", objFuncEval_ );
345  fprintf(fp2,"%s","\n[\n");
346  for (int iparam=0;iparam< numSensParams_; ++iparam)
347  {
348  fprintf(fp2,"\t%16.8e\n", sensitivities[iparam]);
349  }
350  fprintf(fp2,"%s","]\n");
351  fclose(fp2);
352  }
353 #ifdef Xyce_PARALLEL_MPI
354  pdsCommPtr->barrier();
355 #endif
356 }
357 
358 //-----------------------------------------------------------------------------
359 // Function : N_NLS_Sensitivity::solve
360 // Purpose :
361 // Special Notes :
362 // Scope : public
363 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
364 // Creation Date : 11/21/02
365 //-----------------------------------------------------------------------------
367  std::vector<double> & objectiveVec,
368  std::vector<double> & dOdpVec,
369  std::vector<double> & dOdpAdjVec,
370  std::vector<double> & scaled_dOdpVec,
371  std::vector<double> & scaled_dOdpAdjVec)
372 {
373  if (!solveDirectFlag_ && !solveAdjointFlag_) return 1;
374 
375  dOdpVec_.clear();
376  dOdpAdjVec_.clear();
377 
378  scaled_dOdpVec_.clear();
379  scaled_dOdpAdjVec_.clear();
380 
381  // It may now be neccessary to re-load the jacobian and rhs vectors.
382  // It is necccessary, for example, if the two-level Newton solver is the
383  // solver being used.
385 
386  // first get the derivatives of the RHS vector w.r.t. the
387  // user-specified optimization parameters.
389 
391 
392  objectiveVec.clear();
393  objectiveVec.push_back(objFuncEval_);
394 
395  if (solveDirectFlag_)
396  {
397  solveDirect ();
398 
400  {
401  dOdpVec = dOdpVec_;
402  }
403 
404  if (outputScaledFlag_)
405  {
406  scaled_dOdpVec = scaled_dOdpVec_;
407  }
408  }
409 
410  if (solveAdjointFlag_)
411  {
412  solveAdjoint ();
414  {
415  dOdpAdjVec = dOdpAdjVec_;
416  }
417 
418  if (outputScaledFlag_)
419  {
420  scaled_dOdpAdjVec = scaled_dOdpAdjVec_;
421  }
422  }
423 
424  numSolves_++;
425 
426  return 1;
427 }
428 
429 //-----------------------------------------------------------------------------
430 // Function : N_NLS_Sensitivity::solveDirect
431 //
432 // Purpose : This function calculates the direct sensitivities for
433 // the user specified parameters.
434 //
435 // The ultimate goal of this function is to obtain dO/dp,
436 // where O is the objective function, and p is a
437 // user-defined optimization parameter.
438 //
439 // This is a bit confusing because the DAKOTA folks use
440 // a different naming convention than I have tended to
441 // use. In Dakota's documentation, dO/dp would be referred
442 // to as df/du. In Xyce, f is already considered to be the
443 // right-hand-side residual vector. For clarity, this is
444 // the key between my notation and DAKOTA's:
445 // DAK Xyce
446 // f O
447 // y x
448 // u p
449 // c f
450 //
451 // To obtain dOdp, the device manager is first called, and
452 // told to calculate dfdp, which is the derivative of the
453 // residual vector w.r.t. user-defined params. It does
454 // this by numerical differentiation. Then, after that,
455 // this function solves the equation:
456 //
457 // J dx/dp = df/dp -> dx/dp = J^-1 df/dp
458 //
459 // for each p.
460 //
461 // J is the jacobian matrix, so J=df/dx. (dc/dy)
462 //
463 // After performing these linear solves, dO/dp is to be
464 // obtained using the chain rule by:
465 //
466 // dO/dp = - dO/dx * dx/dp + dO/dp
467 //
468 // The O in the dO/dp on the left side of this equation
469 // should have a hat over it "^", to indicate that it is
470 // different than the O on the right hand side.
471 //
472 // Note, this method is best if you have lots of objective
473 // functions, and a small number of parameters. For adjoint
474 // calculations it is the other way around.
475 //
476 // 11/19/02.
477 // It is assumed (for now) that dO/dp on the right hand side
478 // is zero, i.e., there is no direct
479 // dependence on p by O. So, for example, if a user
480 // defined parameter to be used is the length of a MOSFET,
481 // the MOSFET length will NOT appear in the analytical
482 // expression for O.
483 //
484 //
485 // Special Notes :
486 // Scope : public
487 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
488 // Creation Date : 10/31/02
489 //-----------------------------------------------------------------------------
491 {
492 #ifdef Xyce_DEBUG_NONLINEAR
493  if (debugLevel_ > 0)
494  {
495  Xyce::dout() << std::endl;
496  Xyce::dout() << "In N_NLS_Sensitivity::solveDirect" << std::endl;
497  }
498 #endif
499 
500  int iparam;
501 
502  if( !allocateddXVec_ )
503  {
504  dXdpPtrVector_.resize(numSensParams_,0);
505  for (iparam=0;iparam<numSensParams_;++iparam)
506  dXdpPtrVector_[iparam] = lasSysPtr_->builder().createVector();
507  allocateddXVec_ = true;
508  }
509 
510  // first save a copy of the rhs vector, in case we want it later.
511  savedRHSVectorPtr_->update(1.0, *(rhsVectorPtr_), 0.0);
512  savedNewtonVectorPtr_->update(1.0, *(NewtonVectorPtr_), 0.0);
513 
514  // Now solve the series of linear systems to get dXdp.
515  for (iparam=0; iparam< numSensParams_; ++iparam)
516  {
517  // copy the current dfdp vector into the f vector data structure.
518  rhsVectorPtr_->update(1.0, *(dfdpPtrVector_[iparam]), 0.0);
519 
520  lasSolverPtr_->solve();
521 
522  // allocate the dxdp vector for this param, and
523  // copy the resulting deltax vector into the dxdp data structure.
524  (dXdpPtrVector_[iparam])->update(1.0, *(NewtonVectorPtr_), 0.0);
525 
526 #ifdef Xyce_DEBUG_NONLINEAR
527  // do debug output.
528  if (debugLevel_ > 0)
529  {
530  Xyce::dout() << "iparam="<<iparam << "\t" << paramNameVec_[iparam] <<std::endl;
531  for (int k = 0; k < solutionSize_; ++k)
532  {
533  Xyce::dout() << "k = " << std::setw(4) << k
534  <<" dXdp = "<< std::setw(11)<< std::scientific
535  << std::setprecision(4)<< (*(dXdpPtrVector_[iparam]))[k]
536  <<std::endl;
537  }
538 
539  std::ostringstream filename;
540  filename << netlistFileName_ << "_dxdp";
541  filename << std::setw(3) << std::setfill('0') << iparam;
542  filename << ".txt";
543  dXdpPtrVector_[iparam]->writeToFile(const_cast<char *>(filename.str().c_str()));
544  }
545 #endif
546 
547  }// end of for loop
548 
549  // Restore the RHS and Newton vectors.
550  rhsVectorPtr_->update(1.0, *(savedRHSVectorPtr_),0.0);
551  NewtonVectorPtr_->update(1.0, *(savedNewtonVectorPtr_),0.0);
552 
553  // Now get the final dOdp's (one for each param).
554  for (iparam=0; iparam< numSensParams_; ++iparam)
555  {
556  double tmp = (-1.0) * dOdXVectorPtr_->dotProduct( (*(dXdpPtrVector_[iparam])) );
557  tmp += dOdp_;
558 
559  dOdpVec_.push_back(tmp);
560 
561  // get scaled value. dO/dp*(p/100)
562  double normalize = paramOrigVals_[iparam]/100.0;
563  tmp *= normalize;
564  scaled_dOdpVec_.push_back(tmp);
565  }
566 
567  if (stdOutputFlag_)
568  {
569  stdOutput(std::string("Direct"), paramOrigVals_, dOdpVec_, scaled_dOdpVec_);
570  }
571 
572  if (fileOutputFlag_)
573  {
574  fileOutput(std::string("Direct"), paramOrigVals_, dOdpVec_, scaled_dOdpVec_);
575  }
576 
578  {
579  dakOutput(std::string("Direct"), paramOrigVals_, dOdpVec_, scaled_dOdpVec_);
580  }
581 
582  return 1;
583 }
584 
585 
586 
587 //-----------------------------------------------------------------------------
588 // Function : N_NLS_Sensitivity::calcObjFuncDerivs
589 //
590 // Purpose : This function assumes an objective function, O, and
591 // uses it to calculate other stuff, including dO'/du,
592 // dO/dy, dO/du.
593 //
594 // The objective function simply assumes that you are
595 // trying to match a solution variable with a
596 // user-specified number. (objective=1)
597 //
598 // Or that you are trying to match your final solution
599 // with a specified solution. (objective=2)
600 //
601 // Special Notes : This is mostly for testing purposes. Usually, the
602 // objective function would be set and managed from
603 // the DAKOTA side.
604 //
605 // Scope : public
606 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
607 // Creation Date : 11/15/02
608 //-----------------------------------------------------------------------------
610 {
611  bool bsuccess = true;
612  int i;
613 
614  dOdXVectorPtr_->putScalar(0.0);
615 
616  // first check if the user specified a function via the expressions
617  // class:
618  if (objFuncGiven_)
619  {
620  if (!objFuncGIDsetup_)
621  {
622  // set up the gid's:
623  expVarGIDs_.resize( expNumVars_ );
624  expVarLocal_.resize( expNumVars_ );
625 
626  for (i = 0; i < expNumVars_; ++i)
627  {
628  std::list<int> svGIDList1, dummyList;
629  char type1;
630  bool found = top_.getNodeSVarGIDs(NodeID(expVarNames_[i], Xyce::_VNODE), svGIDList1, dummyList, type1);
631 
632  bool foundLocal = found;
633 #ifdef Xyce_PARALLEL_MPI
634  N_PDS_Comm *pdsCommPtr = pdsMgrPtr_->getPDSComm();
635  {
636  // synchronize in parallel
637  double found_glob=0.0;
638  double found_local = found?1.0:0.0;
639  pdsCommPtr->barrier();
640  pdsCommPtr->sumAll(&found_local, &found_glob, 1);
641  found = (found_glob != 0.0)?true:false;
642 #ifdef Xyce_DEBUG_NONLINEAR
643  Xyce::dout() << "global found = " << found_glob <<std::endl;
644 #endif
645  }
646 #endif
647 
648  bool found2 = false;
649  if (!found)// if looking for this as a voltage node failed, try a "device" (i.e. current) node.
650  {
651  found2 = top_.getNodeSVarGIDs(NodeID(expVarNames_[i], Xyce::_DNODE), svGIDList1, dummyList, type1);
652  }
653 
654  bool foundLocal2 = found2;
655 #ifdef Xyce_PARALLEL_MPI
656  if (!found)
657  {
658  // synchronize in parallel
659  double found_glob=0.0;
660  double found_local = found2?1.0:0.0;
661  pdsCommPtr->barrier();
662  pdsCommPtr->sumAll(&found_local, &found_glob, 1);
663  found2 = (found_glob != 0.0)?true:false;
664 #ifdef Xyce_DEBUG_NONLINEAR
665  Xyce::dout() << "global found2 = " << found_glob <<std::endl;
666 #endif
667  }
668 #endif
669 
670  if (!found && !found2)
671  {
672  static std::string tmp = "objective function variable not found!\n";
673  N_ERH_ErrorMgr::report( N_ERH_ErrorMgr::USR_FATAL, tmp);
674  }
675 
676  int tmpGID = -1;
677  if (foundLocal || foundLocal2)
678  {
679  tmpGID = svGIDList1.front();
680  }
681 
682 #ifdef Xyce_DEBUG_NONLINEAR
683  Xyce::dout() << "tmpGID = " << tmpGID <<std::endl;
684 #endif
685  expVarGIDs_[i] = tmpGID;
686  }
687  objFuncGIDsetup_ = true;
688  }
689 
690  // obtain the expression variable values:
691  expVarVals_.resize (expNumVars_);
692  expVarDerivs_.resize (expNumVars_);
693  for (i = 0; i < expNumVars_; ++i)
694  {
695  if (expVarGIDs_[i] != -1)
696  {
697  expVarVals_[i] =
698  (*nextSolVectorPtrPtr_)->getElementByGlobalIndex(expVarGIDs_[i], 0);
699  }
700  else
701  {
702  expVarVals_[i] = 0.0;
703  }
704  }
705 
706  //get expression value and partial derivatives
707  expPtr_->evaluate( expVal_, expVarDerivs_, expVarVals_ );
709  dOdXVectorPtr_->putScalar(0.0);
710  for (i=0;i<expNumVars_;++i)
711  {
712  int tmpGID = expVarGIDs_[i];
713  double tmpDODX = expVarDerivs_[i];
714 
715 #ifdef Xyce_DEBUG_NONLINEAR
716  Xyce::dout() << "i="<<i<<" gid = " << tmpGID << " dodx = "<< tmpDODX << std::endl;
717 #endif
718 
719  if (tmpGID != -1)
720  {
721  dOdXVectorPtr_->setElementByGlobalIndex(tmpGID, tmpDODX, 0);
722  }
723  }
724 
725  // Assuming this is zero:
726  dOdp_ = 0.0;
727  }
728  else // give a warning.
729  {
730  static std::string tmp = " ***** WARNING: objective function was not specified.\n\n";
731  N_ERH_ErrorMgr::report(N_ERH_ErrorMgr::USR_FATAL_0, tmp);
732  }
733 
734 #ifdef Xyce_DEBUG_NONLINEAR
735  if (debugLevel_ > 0)
736  {
737  std::string filename = netlistFileName_ + "_dodx.txt";
738  dOdXVectorPtr_->writeToFile(const_cast<char *>(filename.c_str()));
739  }
740 #endif
741 
742  return bsuccess;
743 }
744 
745 //-----------------------------------------------------------------------------
746 // Function : N_NLS_Sensitivity::solveAdjoint
747 // Purpose : Solves first for the vector, lambda, of adjoint variables.
748 // Afterwards, it solves for dO/dp.
749 // Special Notes :
750 // Scope : public
751 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
752 // Creation Date : 11/20/02
753 //-----------------------------------------------------------------------------
755 {
756  if( !allocateddXVec_ )
757  {
758  dXdpPtrVector_.resize(numSensParams_,0);
759  for (int iparam=0;iparam<numSensParams_;++iparam)
760  dXdpPtrVector_[iparam] = lasSysPtr_->builder().createVector();
761  allocateddXVec_ = true;
762  }
763 
764  // first save a copy of the rhs vector, in case we want it later.
765  savedRHSVectorPtr_->update(1.0, *(rhsVectorPtr_),0.0);
766  savedNewtonVectorPtr_->update(1.0, *(NewtonVectorPtr_),0.0);
767 
768  lambdaVectorPtr_ = lasSysPtr_->builder().createVector();
769  bool useTran = jacobianMatrixPtr_->useTranspose ();
770  jacobianMatrixPtr_->setUseTranspose (true);
771 
772 #ifdef Xyce_DEBUG_NONLINEAR
773  if (debugLevel_ > 0)
774  {
775  std::string matrixFile = netlistFileName_ + "_adjointMatrix.txt";
776  jacobianMatrixPtr_->writeToFile(const_cast<char *>(matrixFile.c_str()));
777  }
778 #endif
779 
780  // copy the current dOdx vector into the f vector data structure.
781  rhsVectorPtr_->update(1.0, *(dOdXVectorPtr_),0.0);
782 
783  int status = lasSolverPtr_->solve();
784  if (status!=0)
785  {
786  std::string msg("N_NLS_Sensitivity::solveAdjoint. Solver failed\n");
787  N_ERH_ErrorMgr::report (N_ERH_ErrorMgr::DEV_FATAL, msg);
788  }
789 
790  // allocate the dxdp vector for this param, and
791  // copy the resulting deltax vector into the dxdp data structure.
792  lambdaVectorPtr_->update(1.0, *(NewtonVectorPtr_),0.0);
793 
794 #ifdef Xyce_DEBUG_NONLINEAR
795  if (debugLevel_ > 0)
796  {
797  std::string filename = netlistFileName_ + "_lambda.txt";
798  lambdaVectorPtr_->writeToFile(const_cast<char *>(filename.c_str()));
799  }
800 #endif
801 
802  // Now that we have lambda, get the dOdp's by doing dot products of
803  // lambda * df/dp.
804 
805  // do the final dot products, one for each param.
806  for (int iparam=0; iparam< numSensParams_; ++iparam)
807  {
808  double tmp = -1.0 * lambdaVectorPtr_->dotProduct(*(dfdpPtrVector_[iparam]));
809  dOdpAdjVec_.push_back(tmp);
810 
811  // get scaled value. dO/dp*(p/100)
812  double normalize = paramOrigVals_[iparam]/100.0;
813  tmp *= normalize;
814  scaled_dOdpAdjVec_.push_back(tmp);
815  }
816 
817 
818  // restore the useTranspose flag to the original setting. (probably
819  // false)
820  jacobianMatrixPtr_->setUseTranspose (useTran);
821 
822  // Restore the RHS and Newton vectors.
823  rhsVectorPtr_->update(1.0, *(savedRHSVectorPtr_),0.0);
824  NewtonVectorPtr_->update(1.0, *(savedNewtonVectorPtr_),0.0);
825 
826  // set the sensitivity information to the screen:
827  if (stdOutputFlag_)
828  {
829  stdOutput(std::string("Adjoint"), paramOrigVals_, dOdpAdjVec_, scaled_dOdpAdjVec_);
830  }
831  if (fileOutputFlag_)
832  {
833  fileOutput(std::string("Adjoint"), paramOrigVals_, dOdpVec_, scaled_dOdpVec_);
834  }
835 
837  {
838  dakOutput(std::string("Adjoint"), paramOrigVals_, dOdpVec_, scaled_dOdpVec_);
839  }
840 
841  return 1;
842 }
843 
844 
845 //-----------------------------------------------------------------------------
846 // Function : Sensitivity::calcSensitivities
847 //
848 // Purpose : This function is to be called after a calculation has
849 // converged. It calculates vectors of derivatives: df/dp,
850 // where f is the residual, and p is a varried parameter.
851 //
852 // Special Notes :
853 // Scope : public
854 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
855 // Creation Date : 7/15/02
856 //-----------------------------------------------------------------------------
858 {
859  int iparam;
860  std::string msg;
861  N_LAS_System & lasSys_ = (*lasSysPtr_);
862 
863 #ifdef Xyce_PARALLEL_MPI
864  N_PDS_Comm *pdsCommPtr = pdsMgrPtr_->getPDSComm();
865  pdsCommPtr->barrier();
866 #endif
867 
868  paramOrigVals_.clear();
869 
870  // save a copy of the f vector.
871  origFVectorPtr_->update(1.0, *(rhsVectorPtr_), 0.0);
872 
873  // Loop over the vector of parameters. For each parameter, find the
874  // device entity (a model or an instance) which corresponds to it, and
875  // perform the finite difference calculation.
876  std::vector<std::string>::iterator firstParam = paramNameVec_.begin ();
877  std::vector<std::string>::iterator lastParam = paramNameVec_.end ();
878  std::vector<std::string>::iterator iterParam;
879  for ( iterParam=firstParam, iparam=0;
880  iterParam!=lastParam; ++iterParam, ++iparam )
881  {
882 
883 #ifdef Xyce_DEBUG_NONLINEAR
884  if (debugLevel_ > 0)
885  {
886  Xyce::dout() << std::endl << " Calculating df/dp for: ";
887  Xyce::dout() << *iterParam << std::endl;
888  }
889 #endif
890 
891  // save a copy of the rhs vector, in case we want it later.
892  // (-RHS, as it supports a Newton solve)
893  origFVectorPtr_->update(-1.0, *(rhsVectorPtr_), 0.0);
894 
895  // now perturb the value of this parameter.
896  std::string paramName(*iterParam);
897  double paramOrig = 0.0;
898  bool found = loaderPtr_->getParamAndReduce(paramName, paramOrig);
899  if (!found)
900  {
901  std::string msg("Sensitivity::calcSensitivities: cannot find parameter ");
902  msg += paramName;
903  N_ERH_ErrorMgr::report (N_ERH_ErrorMgr::DEV_FATAL, msg);
904  }
905 
906  double dp = sqrtEta_ * (1.0 + fabs(paramOrig));
907  double paramPerturbed = paramOrig;
908  paramOrigVals_.push_back(paramOrig);
909 
910  if (difference==SENS_FWD)
911  {
912  paramPerturbed += dp;
913  }
914  else if (difference==SENS_REV)
915  {
916  paramPerturbed -= dp;
917  }
918  else if (difference==SENS_CNT)
919  {
920  static std::string tmp = "difference=central not supported.\n";
921  N_ERH_ErrorMgr::report( N_ERH_ErrorMgr::USR_FATAL_0, tmp);
922  }
923  else
924  {
925  static std::string tmp = "difference not recognized!\n";
926  N_ERH_ErrorMgr::report( N_ERH_ErrorMgr::USR_FATAL_0, tmp);
927  }
928 
929 #ifdef Xyce_DEBUG_NONLINEAR
930  if (debugLevel_ > 0)
931  {
932  Xyce::dout() << std::setw(maxParamStringSize_)<< *iterParam
933  << " dp = " << std::setw(11)<< std::scientific<< std::setprecision(4) << dp
934  << " original value = " << std::setw(16)<< std::scientific<< std::setprecision(9) << paramOrig
935  << " modified value = " << std::setw(16)<< std::scientific<< std::setprecision(9) << paramPerturbed
936  <<std::endl;
937  }
938 #endif
939  loaderPtr_->setParam (paramName, paramPerturbed);
940 
941  // Now that the parameter has been perturbed,
942  // calculate the numerical derivative.
943 
944  // rhs_(); // this is the same as loadPtr_->loadRHS, except that it keeps statistics
945  loaderPtr_->loadRHS();
946 
947  // save the pertF vector
948  // (-RHS, as it supports a Newton solve)
949  pertFVectorPtr_->update(-1.0, *(rhsVectorPtr_), 0.0);
950 
951  // calculate the df/dp vector.
952  double rdp=1/dp;
953  dfdpPtrVector_[iparam]->putScalar(0.0);
954  dfdpPtrVector_[iparam]->addVec (+1.0, *(pertFVectorPtr_)); //+Fperturb
955  dfdpPtrVector_[iparam]->addVec (-1.0, *(origFVectorPtr_)); //-Forig
956  dfdpPtrVector_[iparam]->scale(rdp);
957 
958 #ifdef Xyce_DEBUG_NONLINEAR
959  if (debugLevel_ > 0)
960  {
961  Xyce::dout() << *iterParam << ": ";
962  Xyce::dout().width(15); Xyce::dout().precision(7); Xyce::dout().setf(std::ios::scientific);
963  Xyce::dout() << "deviceSens_dp = " << dp << std::endl;
964 
965  for (int k1 = 0; k1 < solutionSize_; ++k1)
966  {
967  Xyce::dout() << "k = " << std::setw(4) << k1
968  <<" fpert = "<< std::setw(11)<< std::scientific<< std::setprecision(4)<< (*(pertFVectorPtr_))[k1]
969  <<" forig = "<< std::setw(11)<< std::scientific<< std::setprecision(4)<< (*(origFVectorPtr_))[k1]
970  <<" dfdp = "<< std::setw(11)<< std::scientific<< std::setprecision(4)<< (*(dfdpPtrVector_[iparam]))[k1]
971  <<std::endl;
972  }
973 
974  std::ostringstream filename;
975  filename << netlistFileName_ << "_dfdp";
976  filename << std::setw(3) << std::setfill('0') << iparam;
977  filename << ".txt";
978  dfdpPtrVector_[iparam]->writeToFile(const_cast<char *>(filename.str().c_str()));
979 
980  filename.str("");
981  filename << netlistFileName_ << "_fpert";
982  filename << std::setw(3) << std::setfill('0') << iparam;
983  filename << ".txt";
984  pertFVectorPtr_->writeToFile(const_cast<char *>(filename.str().c_str()));
985  }
986 #endif
987 
988  // now reset the parameter and rhs to previous values.
989  loaderPtr_->setParam (paramName, paramOrig);
990  rhsVectorPtr_->update(-1.0, *(origFVectorPtr_), 0.0);
991  }
992 
993 #ifdef Xyce_PARALLEL_MPI
994  pdsCommPtr->barrier();
995 #endif
996 
997  return true;
998 }
999 
1000 //-----------------------------------------------------------------------------
1001 // Function : N_NLS_Sensitivity::setOptions
1002 //
1003 // Purpose : This function processes the .SENS line
1004 //
1005 // Special Notes :
1006 // Scope : public
1007 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
1008 // Creation Date : 11/15/02
1009 //-----------------------------------------------------------------------------
1010 bool N_NLS_Sensitivity::setOptions(const N_UTL_OptionBlock& OB)
1011 {
1012  bool bsuccess = true;
1013  std::list<N_UTL_Param>::const_iterator iter = OB.getParams().begin();
1014  std::list<N_UTL_Param>::const_iterator end = OB.getParams().end();
1015 
1016  numSensParams_ = 0;
1017  for ( ; iter != end; ++ iter)
1018  {
1019  if (iter->uTag() == "OBJFUNC")
1020  {
1021  objFuncString_ = iter->stringValue();
1022  expPtr_ = new N_UTL_Expression(iter->stringValue());
1023  objFuncGiven_ = true;
1024  }
1025  else if ( std::string( iter->uTag() ,0,5) == "PARAM") // this is a vector
1026  {
1027  ExtendedString tag = iter->stringValue();
1028  tag.toUpper();
1029  // set up the initial skeleton of the maps:
1030  ++numSensParams_;
1031  paramNameVec_.push_back(tag);
1032  int sz = tag.size();
1033  if (sz > maxParamStringSize_)
1034  {
1035  maxParamStringSize_ = sz;
1036  }
1037  }
1038  else
1039  {
1040  Xyce::Report::UserWarning() << iter->uTag() << " is not a recognized sensitivity solver option.\n" << std::endl;
1041  }
1042  }
1043 
1044  // parse the expression now, so if there are any errors, they will come
1045  // up early in the simulation.
1046  if (objFuncGiven_)
1047  {
1048  // setup the names:
1049  expVarNames_.clear();
1050 
1051  std::vector<std::string> nodes;
1052  expPtr_->get_names(XEXP_NODE, nodes);
1053  std::vector<std::string> instances;
1054  expPtr_->get_names(XEXP_INSTANCE, instances);
1055 
1056  // Make the current (instance) strings all upper case.
1057  // The topology directory apparently requires this.
1058  std::vector<std::string>::iterator iter;
1059  for (iter=instances.begin();iter!=instances.end();++iter)
1060  {
1061  ExtendedString tmpString = *iter;
1062  tmpString.toUpper ();
1063  *iter = tmpString;
1064  }
1065 
1066  expVarNames_.insert(expVarNames_.end(), nodes.begin(), nodes.end());
1067  expVarNames_.insert(expVarNames_.end(), instances.begin(), instances.end());
1068 
1069  // Order the names in the expression so that it agrees with the order
1070  // in expVarNames.
1071  if ( !expVarNames_.empty() )
1072  {
1073  expPtr_->order_names( expVarNames_ );
1074  }
1075 
1076  expNumVars_ = expVarNames_.size();
1077  }
1078 
1079 #ifdef Xyce_DEBUG_NONLINEAR
1080  if (debugLevel_ > 0)
1081  {
1082  std::vector<std::string>::iterator iter;
1083  std::vector<std::string>::iterator begin = paramNameVec_.begin();
1084  std::vector<std::string>::iterator end = paramNameVec_.end ();
1085 
1086  for (iter=begin;iter!=end;++iter)
1087  {
1088  Xyce::dout() << *iter<<std::endl;
1089  }
1090  }
1091 #endif
1092 
1093  // now that the number of parameters is known, allocate the dfdp vector:
1094  dfdpPtrVector_.resize (numSensParams_, 0);
1095  N_LAS_System & lasSys_ = (*lasSysPtr_);
1096 
1097  for (int iparam=0;iparam<numSensParams_; ++iparam)
1098  {
1099  dfdpPtrVector_[iparam] = lasSys_.builder().createVector();
1100  }
1101 
1102  return bsuccess;
1103 }
1104 
1105 //-----------------------------------------------------------------------------
1106 // Function : N_NLS_Sensitivity::setSensitivityOptions
1107 // Purpose : This function processes the .options SENSITIVITY line
1108 // Special Notes :
1109 // Scope : public
1110 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
1111 // Creation Date : 06/20/2013
1112 //-----------------------------------------------------------------------------
1113 bool N_NLS_Sensitivity::setSensitivityOptions(const N_UTL_OptionBlock& OB)
1114 {
1115  bool bsuccess = true;
1116  std::list<N_UTL_Param>::const_iterator iter = OB.getParams().begin();
1117  std::list<N_UTL_Param>::const_iterator end = OB.getParams().end();
1118 
1119  for ( ; iter != end; ++ iter)
1120  {
1121  if (iter->uTag() == "ADJOINT")
1122  {
1124  static_cast<bool>(iter->getImmutableValue<bool>());
1125  }
1126  else if (iter->uTag() == "DIRECT")
1127  {
1128  solveDirectFlag_ =
1129  static_cast<bool>(iter->getImmutableValue<bool>());
1130  }
1131  else if (iter->uTag() == "OUTPUTSCALED")
1132  {
1134  static_cast<bool>(iter->getImmutableValue<bool>());
1135  }
1136  else if (iter->uTag() == "OUTPUTUNSCALED")
1137  {
1139  static_cast<bool>(iter->getImmutableValue<bool>());
1140  }
1141  else if (iter->uTag() == "STDOUTPUT")
1142  {
1143  stdOutputFlag_ =
1144  static_cast<bool>(iter->getImmutableValue<bool>());
1145  }
1146  else if (iter->uTag() == "DAKOTAFILE")
1147  {
1149  static_cast<bool>(iter->getImmutableValue<bool>());
1150  }
1151  else if (iter->uTag() == "DIAGNOSTICFILE")
1152  {
1153  fileOutputFlag_ =
1154  static_cast<bool>(iter->getImmutableValue<bool>());
1155  }
1156  else if (iter->uTag() == "DIFFERENCE")
1157  {
1158  ExtendedString sval=iter->stringValue();
1159  sval.toUpper();
1160  if(sval=="FORWARD")
1161  {
1163  }
1164  else if(sval=="REVERSE")
1165  {
1167  }
1168  else if(sval=="CENTRAL")
1169  {
1171  static std::string tmp = "difference=central not supported.\n";
1172  N_ERH_ErrorMgr::report( N_ERH_ErrorMgr::USR_FATAL_0, tmp);
1173  }
1174  else
1175  {
1176  static std::string tmp = "difference not recognized!\n";
1177  N_ERH_ErrorMgr::report( N_ERH_ErrorMgr::USR_FATAL_0, tmp);
1178  }
1179  }
1180  else if (iter->uTag() == "SQRTETA")
1181  {
1182  sqrtEta_ = iter->getImmutableValue<double>();
1183  sqrtEtaGiven_ = true;
1184  }
1185 #ifdef Xyce_DEBUG_NONLINEAR
1186  else if (iter->uTag() == "DEBUGLEVEL")
1187  {
1188  debugLevel_ = iter->getImmutableValue<int>();
1189  }
1190 #endif
1191  else
1192  {
1193  Xyce::Report::UserWarning() << iter->uTag() << " is not a recognized sensitivity solver option.\n" << std::endl;
1194  }
1195  }
1196 
1197  return true;
1198 }
1199 
1200 //-----------------------------------------------------------------------------
1201 // Function : N_NLS_Sensitivity::setTranOptions
1202 //
1203 // Purpose : Not used yet. Same as setOptions, but for transient
1204 // mode, if and when the sensitivity stuff is ever set
1205 // up to work in transient.
1206 //
1207 // Special Notes :
1208 // Scope : public
1209 // Creator : Eric Keiter, SNL, Parallel Computational Sciences
1210 // Creation Date : 10/31/02
1211 //-----------------------------------------------------------------------------
1212 bool N_NLS_Sensitivity::setTranOptions(const N_UTL_OptionBlock& OB)
1213 {
1214  bool bsuccess = true;
1215  return bsuccess;
1216 }
1217 
1218 //-----------------------------------------------------------------------------
1219 // Function : N_NLS_Sensitivity::setHBOptions
1220 // Purpose :
1221 // Special Notes :
1222 // Scope : public
1223 // Creator :
1224 // Creation Date :
1225 //-----------------------------------------------------------------------------
1226 bool N_NLS_Sensitivity::setHBOptions(const N_UTL_OptionBlock& OB)
1227 {
1228  bool bsuccess = true;
1229  return bsuccess;
1230 }
1231 
1232 //-----------------------------------------------------------------------------
1233 // Function : N_NLS_Sensitivity::getMaxNormF
1234 // Purpose :
1235 // Special Notes :
1236 // Scope : public
1237 // Creator : Rich Schiek, Electrical and MEMS Modeling
1238 // Creation Date : 9/28/2009
1239 //-----------------------------------------------------------------------------
1241 {
1242  return nls_.getMaxNormF();
1243 }
1244 
1245 //-----------------------------------------------------------------------------
1246 // Function : N_NLS_Sensitivity::getMaxNormFindex
1247 // Purpose :
1248 // Special Notes :
1249 // Scope : public
1250 // Creator : Rich Schiek, Electrical and MEMS Modeling
1251 // Creation Date : 9/28/2009
1252 //-----------------------------------------------------------------------------
1254 {
1255  return nls_.getMaxNormFindex ();
1256 }
1257