https://mooseframework.inl.gov
NonlinearThread.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #include "NonlinearThread.h"
11 #include "NonlinearSystem.h"
12 #include "Problem.h"
13 #include "FEProblem.h"
14 #include "KernelBase.h"
15 #include "IntegratedBCBase.h"
16 #include "DGKernelBase.h"
17 #include "InterfaceKernelBase.h"
18 #include "Material.h"
19 #include "TimeKernel.h"
20 #include "SwapBackSentinel.h"
21 #include "FVTimeKernel.h"
22 #include "ComputeJacobianThread.h"
23 
24 #include "libmesh/threads.h"
25 
27  : ThreadedElementLoop<ConstElemRange>(fe_problem),
28  _nl(fe_problem.currentNonlinearSystem()),
29  _num_cached(0),
30  _integrated_bcs(_nl.getIntegratedBCWarehouse()),
31  _dg_kernels(_nl.getDGKernelWarehouse()),
32  _interface_kernels(_nl.getInterfaceKernelWarehouse()),
33  _kernels(_nl.getKernelWarehouse()),
34  _hdg_kernels(_nl.getHDGKernelWarehouse()),
35  _has_active_objects(_integrated_bcs.hasActiveObjects() || _dg_kernels.hasActiveObjects() ||
36  _interface_kernels.hasActiveObjects() || _kernels.hasActiveObjects() ||
37  _fe_problem.haveFV()),
38  _should_execute_dg(false)
39 {
40 }
41 
42 // Splitting Constructor
45  _nl(x._nl),
46  _num_cached(x._num_cached),
47  _integrated_bcs(x._integrated_bcs),
48  _dg_kernels(x._dg_kernels),
49  _interface_kernels(x._interface_kernels),
50  _kernels(x._kernels),
51  _tag_kernels(x._tag_kernels),
52  _hdg_kernels(x._hdg_kernels),
53  _has_active_objects(x._has_active_objects),
54  _should_execute_dg(x._should_execute_dg)
55 {
56 }
57 
59 
60 void
61 NonlinearThread::operator()(const ConstElemRange & range, bool bypass_threading)
62 {
64  ThreadedElementLoop<ConstElemRange>::operator()(range, bypass_threading);
65 }
66 
67 void
69 {
70  // This should come first to setup the residual objects before we do dependency determination of
71  // material properties and variables
73 
75 
76  // Update variable Dependencies
77  std::set<MooseVariableFEBase *> needed_moose_vars;
82 
83  // Update FE variable coupleable vector tags
84  std::set<TagID> needed_fe_var_vector_tags;
86  _subdomain, needed_fe_var_vector_tags, _tid);
88  _subdomain, needed_fe_var_vector_tags, _tid);
90  _subdomain, needed_fe_var_vector_tags, _tid);
91 
92  // Update material dependencies
93  std::unordered_set<unsigned int> needed_mat_props;
98 
99  if (_fe_problem.haveFV())
100  {
101  // Re-query the finite volume elemental kernels
102  _fv_kernels.clear();
104  .query()
105  .template condition<AttribSysNum>(_nl.number())
106  .template condition<AttribSystem>("FVElementalKernel")
107  .template condition<AttribSubdomains>(_subdomain)
108  .template condition<AttribThread>(_tid)
110  for (const auto fv_kernel : _fv_kernels)
111  {
112  const auto & fv_mv_deps = fv_kernel->getMooseVariableDependencies();
113  needed_moose_vars.insert(fv_mv_deps.begin(), fv_mv_deps.end());
114  const auto & fv_mp_deps = fv_kernel->getMatPropDependencies();
115  needed_mat_props.insert(fv_mp_deps.begin(), fv_mp_deps.end());
116  }
117  }
118 
119  // Cache these to avoid computing them on every side
122 
124  _fe_problem.setActiveFEVariableCoupleableVectorTags(needed_fe_var_vector_tags, _tid);
125  _fe_problem.prepareMaterials(needed_mat_props, _subdomain, _tid);
126 }
127 
128 void
129 NonlinearThread::onElement(const Elem * const elem)
130 {
131  // Set up Sentinel class so that, even if reinitMaterials() throws in prepareElement, we
132  // still remember to swap back during stack unwinding.
134 
135  prepareElement(elem);
136 
137  if (dynamic_cast<ComputeJacobianThread *>(this))
138  if (_nl.getScalarVariables(_tid).size() > 0)
140 
142 }
143 
144 void
146 {
148  {
149  const auto & kernels = _tag_kernels->getActiveBlockObjects(_subdomain, _tid);
150  for (const auto & kernel : kernels)
151  compute(*kernel);
152  }
153 
154  if (_fe_problem.haveFV())
155  for (auto kernel : _fv_kernels)
156  compute(*kernel);
157 }
158 
159 void
160 NonlinearThread::onBoundary(const Elem * const elem,
161  const unsigned int side,
162  const BoundaryID bnd_id,
163  const Elem * const lower_d_elem /*=nullptr*/)
164 {
166  {
167  // Set up Sentinel class so that, after we swap in reinitMaterialsFace in prepareFace, even if
168  // one of our callees throws we remember to swap back during stack unwinding. We put our
169  // sentinel here as opposed to in prepareFace because we certainly don't want our materials
170  // swapped back before we proceed to residual/Jacobian computation
172 
173  prepareFace(elem, side, bnd_id, lower_d_elem);
174  computeOnBoundary(bnd_id, lower_d_elem);
175 
176  if (lower_d_elem)
177  accumulateLower();
178  }
179 }
180 
181 void
182 NonlinearThread::computeOnBoundary(BoundaryID bnd_id, const Elem * /*lower_d_elem*/)
183 {
184  const auto & bcs = _ibc_warehouse->getActiveBoundaryObjects(bnd_id, _tid);
185  for (const auto & bc : bcs)
186  if (bc->shouldApply())
187  compute(*bc);
188 }
189 
190 void
191 NonlinearThread::onInterface(const Elem * elem, unsigned int side, BoundaryID bnd_id)
192 {
194  {
195 
196  // Pointer to the neighbor we are currently working on.
197  const Elem * neighbor = elem->neighbor_ptr(side);
198 
199  if (neighbor->active())
200  {
201  _fe_problem.reinitNeighbor(elem, side, _tid);
202 
203  // Set up Sentinels so that, even if one of the reinitMaterialsXXX() calls throws, we
204  // still remember to swap back during stack unwinding. Note that face, boundary, and interface
205  // all operate with the same MaterialData object
207  _fe_problem.reinitMaterialsFaceOnBoundary(bnd_id, elem->subdomain_id(), _tid);
209 
211  _fe_problem.reinitMaterialsNeighborOnBoundary(bnd_id, neighbor->subdomain_id(), _tid);
212 
213  // Has to happen after face and neighbor properties have been computed. Note that we don't use
214  // a sentinel here because FEProblem::swapBackMaterialsFace is going to handle face materials,
215  // boundary materials, and interface materials (e.g. it queries the boundary material data
216  // with the current element and side
218 
219  computeOnInterface(bnd_id);
220 
222  }
223  }
224 }
225 
226 void
228 {
229  const auto & int_ks = _ik_warehouse->getActiveBoundaryObjects(bnd_id, _tid);
230  for (const auto & interface_kernel : int_ks)
231  compute(*interface_kernel);
232 }
233 
234 void
235 NonlinearThread::onInternalSide(const Elem * elem, unsigned int side)
236 {
237  if (_should_execute_dg)
238  {
239  // Pointer to the neighbor we are currently working on.
240  const Elem * neighbor = elem->neighbor_ptr(side);
241 
243 
244  // Set up Sentinels so that, even if one of the reinitMaterialsXXX() calls throws, we
245  // still remember to swap back during stack unwinding.
247  _fe_problem.reinitMaterialsFace(elem->subdomain_id(), _tid);
248 
250  _fe_problem.reinitMaterialsNeighbor(neighbor->subdomain_id(), _tid);
251 
252  computeOnInternalFace(neighbor);
253 
255  }
256  if (_subdomain_has_hdg)
257  {
258  // Set up Sentinel class so that, after we swap in reinitMaterialsFace in prepareFace, even if
259  // one of our callees throws we remember to swap back during stack unwinding. We put our
260  // sentinel here as opposed to in prepareFace because we certainly don't want our materials
261  // swapped back before we proceed to residual/Jacobian computation
263 
264  prepareFace(elem, side, Moose::INVALID_BOUNDARY_ID, nullptr);
266  }
267 }
268 
269 void
271 {
272  const auto & dgks = _dg_warehouse->getActiveBlockObjects(_subdomain, _tid);
273  for (const auto & dg_kernel : dgks)
274  if (dg_kernel->hasBlocks(neighbor->subdomain_id()))
275  compute(*dg_kernel, neighbor);
276 }
277 
278 void
280 {
281  compute(static_cast<ResidualObject &>(kernel));
282 }
283 
284 void
286 {
287  compute(static_cast<ResidualObject &>(kernel));
288 }
289 
290 void
292 {
293  compute(static_cast<ResidualObject &>(bc));
294 }
295 
296 void
297 NonlinearThread::compute(DGKernelBase & dg, const Elem * /*neighbor*/)
298 {
299  compute(static_cast<ResidualObject &>(dg));
300 }
301 
302 void
304 {
305  compute(static_cast<ResidualObject &>(ik));
306 }
307 
308 void
309 NonlinearThread::postElement(const Elem * /*elem*/)
310 {
311  accumulate();
312 }
313 
314 void
316 {
318 }
319 
320 void
322 {
324  {
325  const auto & console = _fe_problem.console();
326  const auto execute_on = _fe_problem.getCurrentExecuteOnFlag();
327  console << "[DBG] Beginning elemental loop to compute " + objectType() + " on " << execute_on
328  << std::endl;
329  mooseDoOnce(
330  console << "[DBG] Execution order on each element:" << std::endl;
331  console << "[DBG] - kernels on element quadrature points" << std::endl;
332  console << "[DBG] - finite volume elemental kernels on element" << std::endl;
333  console << "[DBG] - integrated boundary conditions on element side quadrature points"
334  << std::endl;
335  console << "[DBG] - DG kernels on element side quadrature points" << std::endl;
336  console << "[DBG] - interface kernels on element side quadrature points" << std::endl;);
337  }
338 }
339 
340 void
342 {
343  // Number of objects executing is approximated by size of warehouses
344  const int num_objects = _kernels.size() + _fv_kernels.size() + _integrated_bcs.size() +
346  const auto & console = _fe_problem.console();
347  const auto block_name = _mesh.getSubdomainName(_subdomain);
348 
349  if (_fe_problem.shouldPrintExecution(_tid) && num_objects > 0)
350  {
351  if (_blocks_exec_printed.count(_subdomain))
352  return;
353  console << "[DBG] Ordering of " + objectType() + " Objects on block " << block_name << " ("
354  << _subdomain << ")" << std::endl;
356  {
357  console << "[DBG] Ordering of kernels:" << std::endl;
358  console << _kernels.activeObjectsToFormattedString() << std::endl;
359  }
360  if (_fv_kernels.size())
361  {
362  console << "[DBG] Ordering of FV elemental kernels:" << std::endl;
363  std::string fvkernels =
364  std::accumulate(_fv_kernels.begin() + 1,
365  _fv_kernels.end(),
366  _fv_kernels[0]->name(),
367  [](const std::string & str_out, FVElementalKernel * kernel)
368  { return str_out + " " + kernel->name(); });
369  console << ConsoleUtils::formatString(fvkernels, "[DBG]") << std::endl;
370  }
372  {
373  console << "[DBG] Ordering of DG kernels:" << std::endl;
374  console << _dg_kernels.activeObjectsToFormattedString() << std::endl;
375  }
376  }
377  else if (_fe_problem.shouldPrintExecution(_tid) && num_objects == 0 &&
379  console << "[DBG] No Active " + objectType() + " Objects on block " << block_name << " ("
380  << _subdomain << ")" << std::endl;
381 
383 }
384 
385 void
387 {
391  return;
392 
393  const auto & console = _fe_problem.console();
394  const auto b_name = _mesh.getBoundaryName(bid);
395  console << "[DBG] Ordering of " + objectType() + " Objects on boundary " << b_name << " (" << bid
396  << ")" << std::endl;
397 
399  {
400  console << "[DBG] Ordering of integrated boundary conditions:" << std::endl;
401  console << _integrated_bcs.activeObjectsToFormattedString() << std::endl;
402  }
403 
404  // We have not checked if we have a neighbor. This could be premature for saying we are executing
405  // interface kernels. However, we should assume the execution will happen on another side of the
406  // same boundary
408  {
409  console << "[DBG] Ordering of interface kernels:" << std::endl;
410  console << _interface_kernels.activeObjectsToFormattedString() << std::endl;
411  }
412 
413  _boundaries_exec_printed.insert(bid);
414 }
415 
416 void
417 NonlinearThread::prepareFace(const Elem * const elem,
418  const unsigned int side,
419  const BoundaryID bnd_id,
420  const Elem * const lower_d_elem)
421 {
422  _fe_problem.reinitElemFace(elem, side, _tid);
423 
424  // Needed to use lower-dimensional variables on Materials
425  if (lower_d_elem)
426  _fe_problem.reinitLowerDElem(lower_d_elem, _tid);
427 
428  if (bnd_id != Moose::INVALID_BOUNDARY_ID)
429  {
430  _fe_problem.reinitMaterialsFaceOnBoundary(bnd_id, elem->subdomain_id(), _tid);
432  }
433  // Currently only used by HDG
434  else
435  _fe_problem.reinitMaterialsFace(elem->subdomain_id(), _tid);
436 }
437 
438 bool
439 NonlinearThread::shouldComputeInternalSide(const Elem & elem, const Elem & neighbor) const
440 {
441  // ThreadedElementLoop<ConstElemRange>::shouldComputeInternalSide gets expensive on high
442  // h-refinement cases so we avoid it if possible
443  _should_execute_dg = false;
444  if (_subdomain_has_dg)
448 }
std::string activeObjectsToFormattedString(THREAD_ID tid=0, const std::string &prefix="[DBG]") const
Output the active content of the warehouse to a string, meant to be output to the console...
unsigned int size(THREAD_ID tid=0) const
Return how many kernels we store in the current warehouse.
virtual bool shouldComputeInternalSide(const Elem &elem, const Elem &neighbor) const
Whether to compute the internal side for the provided element-neighbor pair.
Base class for assembly-like calculations.
virtual void accumulate()=0
Add element residual/Jacobian into assembly global data.
const std::string & getBoundaryName(BoundaryID boundary_id) const
Return the name of the boundary given the id.
Definition: MooseMesh.C:1842
virtual void accumulateLower()=0
Add lower-d residual/Jacobian into assembly global data.
const std::vector< MooseVariableScalar * > & getScalarVariables(THREAD_ID tid)
Definition: SystemBase.h:756
bool hasActiveBlockObjects(THREAD_ID tid=0) const
virtual void compute(ResidualObject &ro)=0
Will dispatch to computeResidual/computeJacobian/computeResidualAndJacobian based on the derived clas...
const std::map< SubdomainID, std::vector< std::shared_ptr< T > > > & getActiveBlockObjects(THREAD_ID tid=0) const
MooseObjectWarehouse< InterfaceKernelBase > * _ik_warehouse
virtual void computeOnBoundary(BoundaryID bnd_id, const Elem *lower_d_elem)
virtual bool haveFV() const override
returns true if this problem includes/needs finite volume functionality.
const BoundaryID INVALID_BOUNDARY_ID
Definition: MooseTypes.C:22
MooseObjectTagWarehouse< IntegratedBCBase > & _integrated_bcs
Reference to BC storage structures.
void reinitMaterialsFaceOnBoundary(const BoundaryID boundary_id, const SubdomainID blk_id, const THREAD_ID tid, const bool swap_stateful=true, const std::deque< MaterialBase *> *const reinit_mats=nullptr)
reinit materials on element faces on a boundary (internal or external) This specific routine helps us...
const ExecFlagType & getCurrentExecuteOnFlag() const
Return/set the current execution flag.
std::vector< T * > & queryInto(std::vector< T *> &results, Args &&... args)
queryInto executes the query and stores the results in the given vector.
Definition: TheWarehouse.h:311
void reinitMaterialsBoundary(BoundaryID boundary_id, const THREAD_ID tid, bool swap_stateful=true, const std::deque< MaterialBase *> *reinit_mats=nullptr)
reinit materials on a boundary
const MaterialWarehouse & getMaterialWarehouse() const
Specialization of SubProblem for solving nonlinear equations plus auxiliary equations.
void prepareFace(const Elem *elem, unsigned int side, BoundaryID bnd_id=Moose::INVALID_BOUNDARY_ID, const Elem *lower_d_elem=nullptr)
Reinitialize variables and materials on a face.
void updateBlockVariableDependency(SubdomainID id, std::set< MooseVariableFieldBase *> &needed_moose_vars, THREAD_ID tid=0) const
const std::string & getSubdomainName(SubdomainID subdomain_id) const
Return the name of a block given an id.
Definition: MooseMesh.C:1813
void reinitMaterialsFace(SubdomainID blk_id, const THREAD_ID tid, bool swap_stateful=true, const std::deque< MaterialBase *> *reinit_mats=nullptr)
reinit materials on element faces
bool shouldComputeInternalSide(const Elem &elem, const Elem &neighbor) const override
Whether to compute the internal side for the provided element-neighbor pair.
Serves as a base class for DGKernel and ADDGKernel.
Definition: DGKernelBase.h:32
virtual void reinitElemNeighborAndLowerD(const Elem *elem, unsigned int side, const THREAD_ID tid) override
virtual void onBoundary(const Elem *elem, unsigned int side, BoundaryID bnd_id, const Elem *lower_d_elem=nullptr) override
Called when doing boundary assembling.
virtual void accumulateNeighbor()=0
Add neighbor residual/Jacobian into assembly global data.
bool hasActiveBoundaryObjects(THREAD_ID tid=0) const
virtual void swapBackMaterialsFace(const THREAD_ID tid)
FVElemental is used for calculating residual contributions from volume integral terms of a PDE where ...
virtual void setActiveElementalMooseVariables(const std::set< MooseVariableFEBase *> &moose_vars, const THREAD_ID tid) override
Set the MOOSE variables to be reinited on each element.
const bool _has_active_objects
Whether there are any active residual objects; otherwise we will do an early return.
bool shouldPrintExecution(const THREAD_ID tid) const
Check whether the problem should output execution orders at this time.
MooseObjectWarehouse< HDGKernel > * _hdg_warehouse
This is the common base class for the three main kernel types implemented in MOOSE, Kernel, VectorKernel and ArrayKernel.
Definition: KernelBase.h:23
TheWarehouse & theWarehouse() const
void printBlockExecutionInformation() const override
Print list of specific objects executed on each block and in which order.
void reinitMaterialsNeighbor(SubdomainID blk_id, const THREAD_ID tid, bool swap_stateful=true, const std::deque< MaterialBase *> *reinit_mats=nullptr)
reinit materials on the neighboring element face
MooseObjectTagWarehouse< KernelBase > & _kernels
void reinitMaterialsNeighborOnBoundary(const BoundaryID boundary_id, const SubdomainID blk_id, const THREAD_ID tid, const bool swap_stateful=true, const std::deque< MaterialBase *> *const reinit_mats=nullptr)
reinit materials on neighbor element (usually faces) on a boundary (internal or external) This specif...
std::string formatString(std::string message, const std::string &prefix)
Add new lines and prefixes to a string for pretty display in output NOTE: This makes a copy of the st...
Definition: ConsoleUtils.C:581
boundary_id_type BoundaryID
virtual void computeOnElement()
bool _subdomain_has_hdg
Whether the subdomain has HDGKernels.
virtual std::string objectType() const
Return what the loops is meant to compute.
NonlinearThread(FEProblemBase &fe_problem)
void printGeneralExecutionInformation() const override
Print information about the loop, mostly order of execution of objects.
unsigned int number() const
Gets the number of this system.
Definition: SystemBase.C:1157
virtual void computeOnInterface(BoundaryID bnd_id)
NonlinearSystemBase & _nl
virtual void postElement(const Elem *) override
Called after the element assembly is done (including surface assembling)
virtual void operator()(const RangeType &range, bool bypass_threading=false)
virtual void operator()(const ConstElemRange &range, bool bypass_threading=false) override
void updateBoundaryMatPropDependency(std::unordered_set< unsigned int > &needed_mat_props, THREAD_ID tid=0) const
const std::map< BoundaryID, std::vector< std::shared_ptr< T > > > & getActiveBoundaryObjects(THREAD_ID tid=0) const
virtual void swapBackMaterialsNeighbor(const THREAD_ID tid)
virtual void onInternalSide(const Elem *elem, unsigned int side) override
Called when doing internal edge assembling.
virtual void computeOnInternalFace()=0
std::set< SubdomainID > _blocks_exec_printed
Keep track of which blocks were visited.
tbb::split split
MooseObjectWarehouse< IntegratedBCBase > * _ibc_warehouse
virtual void reinitLowerDElem(const Elem *lower_d_elem, const THREAD_ID tid, const std::vector< Point > *const pts=nullptr, const std::vector< Real > *const weights=nullptr) override
const ConsoleStream & console() const
Return console handle.
Definition: Problem.h:48
virtual void swapBackMaterials(const THREAD_ID tid)
void updateBlockMatPropDependency(SubdomainID id, std::unordered_set< unsigned int > &needed_mat_props, THREAD_ID tid=0) const
void updateBlockFEVariableCoupledVectorTagDependency(SubdomainID id, std::set< TagID > &needed_fe_var_vector_tags, THREAD_ID tid=0) const
Update FE variable coupleable vector tag vector.
std::set< BoundaryID > _boundaries_exec_printed
Keep track of which boundaries were visited.
virtual void subdomainSetup(SubdomainID subdomain, const THREAD_ID tid)
bool _should_execute_dg
Whether DG kernels should be executed for a given elem-neighbor pairing.
Query query()
query creates and returns an initialized a query object for querying objects from the warehouse...
Definition: TheWarehouse.h:466
virtual void accumulateNeighborLower()=0
Add neighbor and lower residual/Jacobian into assembly global data.
MooseObjectTagWarehouse< InterfaceKernelBase > & _interface_kernels
Reference to interface kernel storage structure.
Base class for deriving any boundary condition of a integrated type.
void printBoundaryExecutionInformation(const unsigned int bid) const override
Print list of specific objects executed on each boundary and in which order.
virtual void onInterface(const Elem *elem, unsigned int side, BoundaryID bnd_id) override
Called when doing interface assembling.
void reinitElemFace(const Elem *elem, unsigned int side, BoundaryID, const THREAD_ID tid)
InterfaceKernelBase is the base class for all InterfaceKernel type classes.
virtual void determineObjectWarehouses()=0
Determine the objects we will actually compute based on vector/matrix tag information.
SubdomainID _subdomain
The subdomain for the current element.
MooseObjectWarehouse< KernelBase > * _tag_kernels
MooseObjectWarehouse< DGKernelBase > * _dg_warehouse
virtual void reinitNeighbor(const Elem *elem, unsigned int side, const THREAD_ID tid) override
virtual void setActiveFEVariableCoupleableVectorTags(std::set< TagID > &vtags, const THREAD_ID tid) override
virtual void onElement(const Elem *elem) override
Assembly of the element (not including surface assembly)
void reinitMaterialsInterface(BoundaryID boundary_id, const THREAD_ID tid, bool swap_stateful=true)
void updateBoundaryVariableDependency(std::set< MooseVariableFieldBase *> &needed_moose_vars, THREAD_ID tid=0) const
std::vector< FVElementalKernel * > _fv_kernels
Current subdomain FVElementalKernels.
void prepareMaterials(const std::unordered_set< unsigned int > &consumer_needed_mat_props, const SubdomainID blk_id, const THREAD_ID tid)
Add the MooseVariables and the material properties that the current materials depend on to the depend...
virtual void reinitOffDiagScalars(const THREAD_ID tid) override
virtual void post() override
Called after the element range loop.
MooseObjectTagWarehouse< DGKernelBase > & _dg_kernels
Reference to DGKernel storage structure.
virtual ~NonlinearThread()
bool _subdomain_has_dg
Whether the subdomain has DGKernels.
virtual void subdomainChanged() override
Called every time the current subdomain changes (i.e.
The "SwapBackSentinel" class&#39;s destructor guarantees that FEProblemBase::swapBackMaterials{Face,Neighbor}() is called even when an exception is thrown from FEProblemBase::reinitMaterials{Face,Neighbor}.