Stratimikos  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Thyra_MLPreconditionerFactory.cpp
1 // @HEADER
2 // *****************************************************************************
3 // Stratimikos: Thyra-based strategies for linear solvers
4 //
5 // Copyright 2006 NTESS and the Stratimikos contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
10 #include "Thyra_MLPreconditionerFactory.hpp"
11 
12 #include "Thyra_EpetraOperatorViewExtractorStd.hpp"
13 #include "Thyra_EpetraLinearOp.hpp"
14 #include "Thyra_DefaultPreconditioner.hpp"
15 #include "ml_MultiLevelPreconditioner.h"
16 #include "ml_MultiLevelOperator.h"
17 #include "ml_ValidateParameters.h"
18 #include "ml_RefMaxwell.h"
19 #include "ml_GradDiv.h"
20 #include "Epetra_RowMatrix.h"
21 #include "Teuchos_StandardParameterEntryValidators.hpp"
22 #include "Teuchos_dyn_cast.hpp"
23 #include "Teuchos_TimeMonitor.hpp"
24 #include "Teuchos_implicit_cast.hpp"
25 #include "Teuchos_ValidatorXMLConverterDB.hpp"
26 #include "Teuchos_StaticSetupMacro.hpp"
27 #include "Teuchos_iostream_helpers.hpp"
28 
29 
30 namespace {
31 
32 
33 enum EMLProblemType {
34  ML_PROBTYPE_NONE,
35  ML_PROBTYPE_SMOOTHED_AGGREGATION,
36  ML_PROBTYPE_NONSYMMETRIC_SMOOTHED_AGGREGATION,
37  ML_PROBTYPE_DOMAIN_DECOMPOSITION,
38  ML_PROBTYPE_DOMAIN_DECOMPOSITION_ML,
39  ML_PROBTYPE_MAXWELL,
40  ML_PROBTYPE_REFMAXWELL,
41  ML_PROBTYPE_GRADDIV
42 };
43 const std::string BaseMethodDefaults_valueNames_none = "none";
44 const Teuchos::Array<std::string> BaseMethodDefaults_valueNames
45 = Teuchos::tuple<std::string>(
46  BaseMethodDefaults_valueNames_none,
47  "SA",
48  "NSSA",
49  "DD",
50  "DD-ML",
51  "maxwell",
52  "refmaxwell",
53  "graddiv"
54  );
55 
56 
57 TEUCHOS_ENUM_INPUT_STREAM_OPERATOR(EMLProblemType)
58 
59 
60 TEUCHOS_STATIC_SETUP()
61 {
63 }
64 
65 const std::string BaseMethodDefaults_name = "Base Method Defaults";
66 const std::string BaseMethodDefaults_default = "SA";
69  >
70 BaseMethodDefaults_validator;
71 
72 const std::string ReuseFineLevelSmoother_name = "Reuse Fine Level Smoother";
73 const bool ReuseFineLevelSmoother_default = false;
74 
75 const std::string MLSettings_name = "ML Settings";
76 
77 
78 } // namespace
79 
80 
81 namespace Thyra {
82 
83 
84 using Teuchos::RCP;
86 
87 
88 // Constructors/initializers/accessors
89 
90 
92  :epetraFwdOpViewExtractor_(Teuchos::rcp(new EpetraOperatorViewExtractorStd()))
93 {}
94 
95 
96 // Overridden from PreconditionerFactoryBase
97 
98 
100  const LinearOpSourceBase<double> &fwdOpSrc
101  ) const
102 {
103  using Teuchos::outArg;
105  EOpTransp epetraFwdOpTransp;
106  EApplyEpetraOpAs epetraFwdOpApplyAs;
107  EAdjointEpetraOp epetraFwdOpAdjointSupport;
108  double epetraFwdOpScalar;
110  fwdOp = fwdOpSrc.getOp();
111  epetraFwdOpViewExtractor_->getEpetraOpView(
112  fwdOp,
113  outArg(epetraFwdOp),outArg(epetraFwdOpTransp),
114  outArg(epetraFwdOpApplyAs),
115  outArg(epetraFwdOpAdjointSupport),
116  outArg(epetraFwdOpScalar)
117  );
118  if( !dynamic_cast<const Epetra_RowMatrix*>(&*epetraFwdOp) )
119  return false;
120  return true;
121 }
122 
123 
125 {
126  return true;
127 }
128 
129 
131 {
132  return false; // See comment below
133 }
134 
135 
138 {
140 }
141 
142 
144  const Teuchos::RCP<const LinearOpSourceBase<double> > &fwdOpSrc,
146  const ESupportSolveUse /* supportSolveUse */
147  ) const
148 {
149  using Teuchos::outArg;
150  using Teuchos::OSTab;
151  using Teuchos::dyn_cast;
152  using Teuchos::RCP;
153  using Teuchos::null;
154  using Teuchos::rcp;
155  using Teuchos::rcp_dynamic_cast;
156  using Teuchos::rcp_const_cast;
157  using Teuchos::set_extra_data;
158  using Teuchos::get_optional_extra_data;
160  Teuchos::Time totalTimer(""), timer("");
161  totalTimer.start(true);
162  const RCP<Teuchos::FancyOStream> out = this->getOStream();
163  const Teuchos::EVerbosityLevel verbLevel = this->getVerbLevel();
164  Teuchos::OSTab tab(out);
165  if(out.get() && implicit_cast<int>(verbLevel) > implicit_cast<int>(Teuchos::VERB_LOW))
166  *out << "\nEntering Thyra::MLPreconditionerFactory::initializePrec(...) ...\n";
167 
168  // Get the problem type
169  const EMLProblemType problemType = BaseMethodDefaults_validator->getIntegralValue(*paramList_,BaseMethodDefaults_name,BaseMethodDefaults_default);
170 
171  Teuchos::RCP<const LinearOpBase<double> > fwdOp = fwdOpSrc->getOp();
172 #ifdef _DEBUG
173  TEUCHOS_TEST_FOR_EXCEPT(fwdOp.get()==NULL);
174  TEUCHOS_TEST_FOR_EXCEPT(prec==NULL);
175 #endif
176  //
177  // Unwrap and get the forward Epetra_Operator object
178  //
180  EOpTransp epetraFwdOpTransp;
181  EApplyEpetraOpAs epetraFwdOpApplyAs;
182  EAdjointEpetraOp epetraFwdOpAdjointSupport;
183  double epetraFwdOpScalar;
184  epetraFwdOpViewExtractor_->getEpetraOpView(
185  fwdOp,outArg(epetraFwdOp),outArg(epetraFwdOpTransp),outArg(epetraFwdOpApplyAs),
186  outArg(epetraFwdOpAdjointSupport),outArg(epetraFwdOpScalar)
187  );
188  // Validate what we get is what we need
190  epetraFwdRowMat = rcp_dynamic_cast<const Epetra_RowMatrix>(epetraFwdOp,true);
192  epetraFwdOpApplyAs != EPETRA_OP_APPLY_APPLY, std::logic_error
193  ,"Error, incorrect apply mode for an Epetra_RowMatrix"
194  );
195  RCP<const Epetra_CrsMatrix> epetraFwdCrsMat = rcp_dynamic_cast<const Epetra_CrsMatrix>(epetraFwdRowMat);
196 
197  //
198  // Get the concrete preconditioner object
199  //
201  *defaultPrec = &Teuchos::dyn_cast<DefaultPreconditioner<double> >(*prec);
202  //
203  // Get the EpetraLinearOp object that is used to implement the preconditoner linear op
204  //
206  epetra_precOp = rcp_dynamic_cast<EpetraLinearOp>(defaultPrec->getNonconstUnspecifiedPrecOp(),true);
207  //
208  // Get the embedded ML_Epetra Preconditioner object if it exists
209  //
213  if(epetra_precOp.get()) {
214  if(problemType == ML_PROBTYPE_REFMAXWELL)
215  rm_precOp = rcp_dynamic_cast<ML_Epetra::RefMaxwellPreconditioner>(epetra_precOp->epetra_op(),true);
216  else if(problemType == ML_PROBTYPE_GRADDIV)
217  gd_precOp = rcp_dynamic_cast<ML_Epetra::GradDivPreconditioner>(epetra_precOp->epetra_op(),true);
218  else
219  ml_precOp = rcp_dynamic_cast<ML_Epetra::MultiLevelPreconditioner>(epetra_precOp->epetra_op(),true);
220  }
221  //
222  // Get the attached forward operator if it exists and make sure that it matches
223  //
224  if(ml_precOp!=Teuchos::null) {
225  // Get the forward operator and make sure that it matches what is
226  // already being used!
227  const Epetra_RowMatrix & rm = ml_precOp->RowMatrix();
228 
229  TEUCHOS_TEST_FOR_EXCEPTION(
230  &rm!=&*epetraFwdRowMat, std::logic_error
231  ,"ML requires Epetra_RowMatrix to be the same for each initialization of the preconditioner"
232  );
233  }
234  // NOTE: No such check exists for RefMaxwell
235 
236  //
237  // Perform initialization if needed
238  //
239  const bool startingOver = (ml_precOp.get() == NULL && rm_precOp.get() == NULL);
240  if(startingOver)
241  {
242  if(out.get() && implicit_cast<int>(verbLevel) >= implicit_cast<int>(Teuchos::VERB_LOW))
243  *out << "\nCreating the initial ML_Epetra::MultiLevelPreconditioner object...\n";
244  timer.start(true);
245  // Create the initial preconditioner: DO NOT compute it yet
246 
247  if(problemType==ML_PROBTYPE_REFMAXWELL)
248  rm_precOp = rcp(new ML_Epetra::RefMaxwellPreconditioner(*epetraFwdCrsMat, paramList_->sublist(MLSettings_name),false));
249  else if(problemType==ML_PROBTYPE_GRADDIV)
250  gd_precOp = rcp(new ML_Epetra::GradDivPreconditioner(*epetraFwdCrsMat, paramList_->sublist(MLSettings_name),false));
251  else
252  ml_precOp = rcp(new ML_Epetra::MultiLevelPreconditioner(*epetraFwdRowMat, paramList_->sublist(MLSettings_name),false));
253 
254 
255  timer.stop();
256  if(out.get() && implicit_cast<int>(verbLevel) >= implicit_cast<int>(Teuchos::VERB_LOW))
257  OSTab(out).o() <<"> Creation time = "<<timer.totalElapsedTime()<<" sec\n";
258  // RAB: Above, I am just passing a string to ML::Create(...) in order
259  // get this code written. However, in the future, it would be good to
260  // copy the contents of what is in ML::Create(...) into a local
261  // function and then use switch(...) to create the initial
262  // ML_Epetra::MultiLevelPreconditioner object. This would result in better validation
263  // and faster code.
264  // Set parameters if the list exists
265  if(paramList_.get()) {
266  if (problemType==ML_PROBTYPE_REFMAXWELL) {
267  TEUCHOS_TEST_FOR_EXCEPT(0!=rm_precOp->SetParameterList(paramList_->sublist(MLSettings_name)));
268  }
269  else if (problemType==ML_PROBTYPE_GRADDIV) {
270  TEUCHOS_TEST_FOR_EXCEPT(0!=gd_precOp->SetParameterList(paramList_->sublist(MLSettings_name)));
271  }
272  else {
273  TEUCHOS_TEST_FOR_EXCEPT(0!=ml_precOp->SetParameterList(paramList_->sublist(MLSettings_name)));
274  }
275  }
276  }
277  //
278  // Attach the epetraFwdOp to the ml_precOp to guarantee that it will not go away
279  //
280  if (problemType==ML_PROBTYPE_REFMAXWELL)
281  set_extra_data(epetraFwdOp, "IFPF::epetraFwdOp", Teuchos::inOutArg(rm_precOp),
282  Teuchos::POST_DESTROY, false);
283  else if (problemType==ML_PROBTYPE_GRADDIV)
284  set_extra_data(epetraFwdOp, "IFPF::epetraFwdOp", Teuchos::inOutArg(gd_precOp),
285  Teuchos::POST_DESTROY, false);
286  else
287  set_extra_data(epetraFwdOp, "IFPF::epetraFwdOp", Teuchos::inOutArg(ml_precOp),
288  Teuchos::POST_DESTROY, false);
289  //
290  // Update the factorization
291  //
292  if(out.get() && implicit_cast<int>(verbLevel) >= implicit_cast<int>(Teuchos::VERB_LOW))
293  *out << "\nComputing the preconditioner ...\n";
294  timer.start(true);
295  if (problemType==ML_PROBTYPE_REFMAXWELL) {
296  if (startingOver) {
297  TEUCHOS_TEST_FOR_EXCEPT(0!=rm_precOp->ComputePreconditioner());
298  }
299  else {
300  TEUCHOS_TEST_FOR_EXCEPT(0!=rm_precOp->ReComputePreconditioner());
301  }
302  }
303  else if (problemType==ML_PROBTYPE_GRADDIV) {
304  if (startingOver) {
305  TEUCHOS_TEST_FOR_EXCEPT(0!=gd_precOp->ComputePreconditioner());
306  }
307  else {
308  TEUCHOS_TEST_FOR_EXCEPT(0!=gd_precOp->ReComputePreconditioner());
309  }
310  }
311  else {
312  if (startingOver) {
313  TEUCHOS_TEST_FOR_EXCEPT(0!=ml_precOp->ComputePreconditioner());
314  }
315  else {
316  TEUCHOS_TEST_FOR_EXCEPT(0!=ml_precOp->ReComputePreconditioner(paramList_->get<bool>(ReuseFineLevelSmoother_name)));
317  }
318  }
319  timer.stop();
320  if(out.get() && implicit_cast<int>(verbLevel) >= implicit_cast<int>(Teuchos::VERB_LOW))
321  OSTab(out).o() <<"=> Setup time = "<<timer.totalElapsedTime()<<" sec\n";
322  //
323  // Compute the conditioner number estimate if asked
324  //
325 
326  // ToDo: Implement
327 
328  //
329  // Attach fwdOp to the ml_precOp
330  //
331  if (problemType==ML_PROBTYPE_REFMAXWELL)
332  set_extra_data(fwdOp, "IFPF::fwdOp", Teuchos::inOutArg(rm_precOp),
333  Teuchos::POST_DESTROY, false);
334  else if (problemType==ML_PROBTYPE_GRADDIV)
335  set_extra_data(fwdOp, "IFPF::fwdOp", Teuchos::inOutArg(gd_precOp),
336  Teuchos::POST_DESTROY, false);
337  else
338  set_extra_data(fwdOp, "IFPF::fwdOp", Teuchos::inOutArg(ml_precOp),
339  Teuchos::POST_DESTROY, false);
340  //
341  // Initialize the output EpetraLinearOp
342  //
343  if(startingOver) {
344  epetra_precOp = rcp(new EpetraLinearOp);
345  }
346  // ToDo: Look into adjoints again.
347  if (problemType==ML_PROBTYPE_REFMAXWELL)
348  epetra_precOp->initialize(rm_precOp,epetraFwdOpTransp,EPETRA_OP_APPLY_APPLY_INVERSE,EPETRA_OP_ADJOINT_UNSUPPORTED);
349  else if (problemType==ML_PROBTYPE_GRADDIV)
350  epetra_precOp->initialize(gd_precOp,epetraFwdOpTransp,EPETRA_OP_APPLY_APPLY_INVERSE,EPETRA_OP_ADJOINT_UNSUPPORTED);
351  else
352  epetra_precOp->initialize(ml_precOp,epetraFwdOpTransp,EPETRA_OP_APPLY_APPLY_INVERSE,EPETRA_OP_ADJOINT_UNSUPPORTED);
353  //
354  // Initialize the preconditioner
355  //
356  defaultPrec->initializeUnspecified(
357  Teuchos::rcp_implicit_cast<LinearOpBase<double> >(epetra_precOp)
358  );
359  totalTimer.stop();
360  if(out.get() && implicit_cast<int>(verbLevel) >= implicit_cast<int>(Teuchos::VERB_LOW))
361  *out << "\nTotal time in MLPreconditionerFactory = "<<totalTimer.totalElapsedTime()<<" sec\n";
362  if(out.get() && implicit_cast<int>(verbLevel) > implicit_cast<int>(Teuchos::VERB_LOW))
363  *out << "\nLeaving Thyra::MLPreconditionerFactory::initializePrec(...) ...\n";
364 }
365 
366 
368  PreconditionerBase<double> * /* prec */,
369  Teuchos::RCP<const LinearOpSourceBase<double> > * /* fwdOp */,
370  ESupportSolveUse * /* supportSolveUse */
371  ) const
372 {
374 }
375 
376 
377 // Overridden from ParameterListAcceptor
378 
379 
381  Teuchos::RCP<ParameterList> const& paramList
382  )
383 {
384  TEUCHOS_TEST_FOR_EXCEPT(paramList.get()==NULL);
385 
386  // Do not recurse
387  paramList->validateParameters(*this->getValidParameters(),0);
388  paramList_ = paramList;
389 
390  // set default for reuse of fine level smoother
391  if(!paramList_->isType<bool>(ReuseFineLevelSmoother_name))
392  paramList_->set<bool>(ReuseFineLevelSmoother_name,ReuseFineLevelSmoother_default);
393 
394  const EMLProblemType
395  defaultType = BaseMethodDefaults_validator->getIntegralValue(
396  *paramList_,BaseMethodDefaults_name,BaseMethodDefaults_default
397  );
398  if( ML_PROBTYPE_NONE != defaultType ) {
399  const std::string defaultTypeStr = BaseMethodDefaults_valueNames[defaultType];
400 
401  // ML will do validation on its own. We don't need to duplicate that here.
402  Teuchos::ParameterList defaultParams;
403  if(defaultType == ML_PROBTYPE_REFMAXWELL) {
404  ML_Epetra::SetDefaultsRefMaxwell(defaultParams);
405  }
406  else if(defaultType == ML_PROBTYPE_GRADDIV) {
407  ML_Epetra::SetDefaultsGradDiv(defaultParams);
408  }
409  else {
410  TEUCHOS_TEST_FOR_EXCEPTION(0!=ML_Epetra::SetDefaults(defaultTypeStr,defaultParams)
412  ,"Error, the ML problem type \"" << defaultTypeStr << "\' is not recognized by ML!"
413  );
414  }
415 
416  // Note, the only way the above exception message could be generated is if
417  // a default problem type was removed from ML_Epetra::SetDefaults(...).
418  // When a new problem type is added to this function, it must be added to
419  // our enum EMLProblemType along with associated objects ... In other
420  // words, this adapter must be maintained as ML is maintained. An
421  // alternative design would be to just pass in whatever string to this
422  // function. This would improve maintainability but it would not generate
423  // very good error messages when a bad string was passed in. Currently,
424  // the error message attached to the exception will contain the list of
425  // valid problem types.
426  paramList_->sublist(MLSettings_name).setParametersNotAlreadySet(
427  defaultParams);
428  }
429 
430 }
431 
432 
435 {
436  return paramList_;
437 }
438 
439 
442 {
443  Teuchos::RCP<ParameterList> _paramList = paramList_;
444  paramList_ = Teuchos::null;
445  return _paramList;
446 }
447 
448 
451 {
452  return paramList_;
453 }
454 
455 
458 {
459  // NOTE: We're only going to use this function to genrate valid *Stratimikos* parameters.
460  // Since ML's parameters can be validated internally, we'll handle those separarely.
461 
462 
463  using Teuchos::rcp;
464  using Teuchos::tuple;
466  using Teuchos::rcp_implicit_cast;
468 
469  static RCP<const ParameterList> validPL;
470 
471  if(is_null(validPL)) {
472 
474  pl = rcp(new ParameterList());
475 
476  BaseMethodDefaults_validator = rcp(
478  BaseMethodDefaults_valueNames,
479  tuple<std::string>(
480  "Do not set any default parameters",
481  "Set default parameters for a smoothed aggregation method",
482  "Set default parameters for a nonsymmetric smoothed aggregation method",
483  "Set default parameters for a domain decomposition method",
484  "Set default parameters for a domain decomposition method special to ML",
485  "Set default parameters for a Maxwell-type of preconditioner",
486  "Set default parameters for a RefMaxwell-type preconditioner",
487  "Set default parameters for a Grad-Div-type preconditioner"
488  ),
489  tuple<EMLProblemType>(
490  ML_PROBTYPE_NONE,
491  ML_PROBTYPE_SMOOTHED_AGGREGATION,
492  ML_PROBTYPE_NONSYMMETRIC_SMOOTHED_AGGREGATION,
493  ML_PROBTYPE_DOMAIN_DECOMPOSITION,
494  ML_PROBTYPE_DOMAIN_DECOMPOSITION_ML,
495  ML_PROBTYPE_MAXWELL,
496  ML_PROBTYPE_REFMAXWELL,
497  ML_PROBTYPE_GRADDIV
498  ),
499  BaseMethodDefaults_name
500  )
501  );
502 
503  pl->set(BaseMethodDefaults_name,BaseMethodDefaults_default,
504  "Select the default method type which also sets parameter defaults\n"
505  "in the sublist \"" + MLSettings_name + "\"!",
506  rcp_implicit_cast<const PEV>(BaseMethodDefaults_validator)
507  );
508 
509  pl->set(ReuseFineLevelSmoother_name,ReuseFineLevelSmoother_default,
510  "Enables/disables the reuse of the fine level smoother.");
511 
512  ParameterList mlpl;
513  pl->set(MLSettings_name,mlpl,
514  "Sampling of the parameters directly accepted by ML\n"
515  "This list of parameters is generated by combining all of\n"
516  "the parameters set for all of the default problem types supported\n"
517  "by ML. Therefore, do not assume these parameters are at values that\n"
518  "are reasonable to ML. This list is just to give a sense of some of\n"
519  "the parameters that ML accepts. Consult ML documentation on how to\n"
520  "set these parameters. Also, you can print the parameter list after\n"
521  "it is used and see what defaults where set for each default problem\n"
522  "type. Warning! the parameters in this sublist are currently *not*\n"
523  "being validated by ML!"
524  );
525 
526  validPL = pl;
527 
528  }
529 
530  return validPL;
531 
532 }
533 
534 
535 // Public functions overridden from Teuchos::Describable
536 
537 
539 {
540  std::ostringstream oss;
541  oss << "Thyra::MLPreconditionerFactory";
542  return oss.str();
543 }
544 
545 
546 } // namespace Thyra
bool is_null(const boost::shared_ptr< T > &p)
EOpTransp
basic_OSTab< char > OSTab
T & get(const std::string &name, T def_value)
#define TEUCHOS_TEST_FOR_EXCEPTION(throw_exception_test, Exception, msg)
Teuchos::RCP< const Teuchos::ParameterList > getParameterList() const
T_To & dyn_cast(T_From &from)
T * get() const
ParameterList & set(std::string const &name, T &&value, std::string const &docString="", RCP< const ParameterEntryValidator > const &validator=null)
Teuchos::RCP< const Teuchos::ParameterList > getValidParameters() const
Teuchos::RCP< Teuchos::ParameterList > unsetParameterList()
IntegralType getIntegralValue(ParameterList const &paramList, std::string const &paramName)
void setParameterList(Teuchos::RCP< Teuchos::ParameterList > const &paramList)
void start(bool reset=false)
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
double stop()
EAdjointEpetraOp
TypeTo implicit_cast(const TypeFrom &t)
void initializePrec(const Teuchos::RCP< const LinearOpSourceBase< double > > &fwdOp, PreconditionerBase< double > *prec, const ESupportSolveUse supportSolveUse) const
ESupportSolveUse
#define TEUCHOS_ADD_STRINGTOINTEGRALVALIDATOR_CONVERTER(INTEGRALTYPE)
ParameterList & setParametersNotAlreadySet(const ParameterList &source)
void uninitializePrec(PreconditionerBase< double > *prec, Teuchos::RCP< const LinearOpSourceBase< double > > *fwdOp, ESupportSolveUse *supportSolveUse) const
Teuchos::RCP< PreconditionerBase< double > > createPrec() const
bool isType(const std::string &name) const
double totalElapsedTime(bool readCurrentTime=false) const
EApplyEpetraOpAs
ParameterList & sublist(const std::string &name, bool mustAlreadyExist=false, const std::string &docString="")
EConj
virtual Teuchos::RCP< const LinearOpBase< Scalar > > getOp() const =0
Teuchos::RCP< Teuchos::ParameterList > getNonconstParameterList()
#define TEUCHOS_TEST_FOR_EXCEPT(throw_exception_test)
bool isCompatible(const LinearOpSourceBase< double > &fwdOp) const

Generated on Thu Nov 21 2024 09:22:16 for Stratimikos by doxygen 1.8.5