Line | Branch | Exec | Source |
---|---|---|---|
1 | // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- | ||
2 | // vi: set et ts=4 sw=4 sts=4: | ||
3 | // | ||
4 | // SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder | ||
5 | // SPDX-License-Identifier: GPL-3.0-or-later | ||
6 | // | ||
7 | /*! | ||
8 | * \file | ||
9 | * \ingroup CCMpfaDiscretization | ||
10 | * \brief Flux variable caches on a gridview | ||
11 | */ | ||
12 | #ifndef DUMUX_DISCRETIZATION_CCMPFA_GRID_FLUXVARSCACHE_HH | ||
13 | #define DUMUX_DISCRETIZATION_CCMPFA_GRID_FLUXVARSCACHE_HH | ||
14 | |||
15 | // make the local view function available whenever we use this class | ||
16 | #include <dumux/discretization/localview.hh> | ||
17 | #include <dumux/discretization/cellcentered/mpfa/elementfluxvariablescache.hh> | ||
18 | |||
19 | namespace Dumux { | ||
20 | |||
21 | /*! | ||
22 | * \ingroup CCMpfaDiscretization | ||
23 | * \brief Data handle physics traits | ||
24 | */ | ||
25 | template<class ModelTraits> | ||
26 | struct IvDataHandlePhysicsTraits | ||
27 | { | ||
28 | static constexpr bool enableAdvection = ModelTraits::enableAdvection(); | ||
29 | static constexpr bool enableMolecularDiffusion = ModelTraits::enableMolecularDiffusion(); | ||
30 | static constexpr bool enableHeatConduction = ModelTraits::enableEnergyBalance(); | ||
31 | |||
32 | static constexpr int numPhases = ModelTraits::numFluidPhases(); | ||
33 | static constexpr int numComponents = ModelTraits::numFluidComponents(); | ||
34 | }; | ||
35 | |||
36 | /*! | ||
37 | * \ingroup CCMpfaDiscretization | ||
38 | * \brief Data handle physics traits | ||
39 | */ | ||
40 | template<class P, | ||
41 | class FVC, class FVCF, | ||
42 | class PIV, class SIV, | ||
43 | class PDH, class SDH> | ||
44 | struct CCMpfaDefaultGridFluxVariablesCacheTraits | ||
45 | { | ||
46 | using Problem = P; | ||
47 | using FluxVariablesCache = FVC; | ||
48 | using FluxVariablesCacheFiller = FVCF; | ||
49 | |||
50 | using PrimaryInteractionVolume = PIV; | ||
51 | using SecondaryInteractionVolume = SIV; | ||
52 | using PrimaryIvDataHandle = PDH; | ||
53 | using SecondaryIvDataHandle = SDH; | ||
54 | |||
55 | template<class GridFluxVariablesCache, bool cachingEnabled> | ||
56 | using LocalView = CCMpfaElementFluxVariablesCache<GridFluxVariablesCache, cachingEnabled>; | ||
57 | |||
58 | // Reserve memory (over-) estimate for interaction volumes and corresponding data. | ||
59 | // The overestimate doesn't hurt as we are not in a memory-limited configuration. | ||
60 | // We need to avoid reallocation because in the caches we store pointers to the data handles. | ||
61 | // Default -> each facet has two neighbors (local adaption) and all scvfs belongs to different ivs. | ||
62 | // If you want to use higher local differences change the parameter below. | ||
63 | static constexpr std::size_t maxLocalElementLevelDifference() | ||
64 | { return 2; }; | ||
65 | }; | ||
66 | |||
67 | /*! | ||
68 | * \ingroup CCMpfaDiscretization | ||
69 | * \brief Flux variable caches on a gridview | ||
70 | * \note The class is specialized for a version with and without grid caching | ||
71 | */ | ||
72 | template<class Traits, bool cachingEnabled> | ||
73 | class CCMpfaGridFluxVariablesCache; | ||
74 | |||
75 | /*! | ||
76 | * \ingroup CCMpfaDiscretization | ||
77 | * \brief Flux variable caches on a gridview with grid caching enabled | ||
78 | * \note The flux caches of the gridview are stored which is memory intensive but faster | ||
79 | */ | ||
80 | template<class TheTraits> | ||
81 | class CCMpfaGridFluxVariablesCache<TheTraits, true> | ||
82 | { | ||
83 | using Problem = typename TheTraits::Problem; | ||
84 | using ThisType = CCMpfaGridFluxVariablesCache<TheTraits, true>; | ||
85 | |||
86 | //! the flux variable cache filler type | ||
87 | using FluxVariablesCacheFiller = typename TheTraits::FluxVariablesCacheFiller; | ||
88 | public: | ||
89 | //! export the Traits | ||
90 | using Traits = TheTraits; | ||
91 | |||
92 | //! export the interaction volume types | ||
93 | using PrimaryInteractionVolume = typename Traits::PrimaryInteractionVolume; | ||
94 | using SecondaryInteractionVolume = typename Traits::SecondaryInteractionVolume; | ||
95 | |||
96 | //! export the data handle types used | ||
97 | using PrimaryIvDataHandle = typename Traits::PrimaryIvDataHandle; | ||
98 | using SecondaryIvDataHandle = typename Traits::SecondaryIvDataHandle; | ||
99 | |||
100 | //! export the flux variable cache type | ||
101 | using FluxVariablesCache = typename Traits::FluxVariablesCache; | ||
102 | |||
103 | //! make it possible to query if caching is enabled | ||
104 | static constexpr bool cachingEnabled = true; | ||
105 | |||
106 | //! export the type of the local view | ||
107 | using LocalView = typename Traits::template LocalView<ThisType, cachingEnabled>; | ||
108 | |||
109 | //! The constructor | ||
110 | 15 | CCMpfaGridFluxVariablesCache(const Problem& problem) | |
111 | 45 | : problemPtr_(&problem) | |
112 | {} | ||
113 | |||
114 | //! When global caching is enabled, precompute transmissibilities for all scv faces | ||
115 | template<class GridGeometry, class GridVolumeVariables, class SolutionVector> | ||
116 | 2141 | void update(const GridGeometry& gridGeometry, | |
117 | const GridVolumeVariables& gridVolVars, | ||
118 | const SolutionVector& sol, | ||
119 | bool forceUpdate = false) | ||
120 | { | ||
121 | // Update only if the filler puts solution-dependent | ||
122 | // stuff into the caches or if update is enforced | ||
123 | if (FluxVariablesCacheFiller::isSolDependent || forceUpdate) | ||
124 | { | ||
125 | // clear previous data if forced update is desired | ||
126 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2126 times.
|
2141 | if (forceUpdate) |
127 | { | ||
128 | 15 | clear_(); | |
129 | |||
130 | 15 | const auto& gridIvIndexSets = gridGeometry.gridInteractionVolumeIndexSets(); | |
131 | 15 | const auto numPrimaryIvs = gridIvIndexSets.numPrimaryInteractionVolumes(); | |
132 | 15 | const auto numSecondaryIVs = gridIvIndexSets.numSecondaryInteractionVolumes(); | |
133 | 15 | ivDataStorage_.primaryInteractionVolumes.reserve(numPrimaryIvs); | |
134 | 15 | ivDataStorage_.secondaryInteractionVolumes.reserve(numSecondaryIVs); | |
135 | 15 | ivDataStorage_.primaryDataHandles.reserve(numPrimaryIvs); | |
136 | 15 | ivDataStorage_.secondaryDataHandles.reserve(numSecondaryIVs); | |
137 | |||
138 | // reserve memory estimate for caches, interaction volumes and corresponding data | ||
139 | 30 | fluxVarsCache_.resize(gridGeometry.numScvf()); | |
140 | } | ||
141 | |||
142 | // instantiate helper class to fill the caches | ||
143 | 2141 | FluxVariablesCacheFiller filler(problem()); | |
144 | |||
145 | // set all the caches to "outdated" | ||
146 |
4/4✓ Branch 0 taken 33054600 times.
✓ Branch 1 taken 2141 times.
✓ Branch 2 taken 33054600 times.
✓ Branch 3 taken 2141 times.
|
66115623 | for (auto& cache : fluxVarsCache_) |
147 | 66109200 | cache.setUpdateStatus(false); | |
148 | |||
149 |
1/4✓ Branch 1 taken 1373 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
2141 | auto fvGeometry = localView(gridGeometry); |
150 |
2/4✓ Branch 1 taken 1373 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 159 times.
✗ Branch 4 not taken.
|
3254 | auto elemVolVars = localView(gridVolVars); |
151 |
17/19✓ Branch 1 taken 1373 times.
✓ Branch 2 taken 1290432 times.
✓ Branch 3 taken 768 times.
✓ Branch 4 taken 1291805 times.
✓ Branch 5 taken 768 times.
✓ Branch 6 taken 2581976 times.
✓ Branch 7 taken 345 times.
✓ Branch 8 taken 68080 times.
✓ Branch 9 taken 186 times.
✓ Branch 10 taken 159 times.
✓ Branch 11 taken 68080 times.
✓ Branch 12 taken 583053 times.
✓ Branch 13 taken 159 times.
✓ Branch 14 taken 651133 times.
✓ Branch 15 taken 159 times.
✓ Branch 17 taken 583053 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 583053 times.
✗ Branch 21 not taken.
|
8275354 | for (const auto& element : elements(gridGeometry.gridView())) |
152 | { | ||
153 |
1/2✓ Branch 1 taken 651133 times.
✗ Branch 2 not taken.
|
4454433 | fvGeometry.bind(element); |
154 |
1/2✓ Branch 1 taken 4454433 times.
✗ Branch 2 not taken.
|
4454433 | elemVolVars.bind(element, fvGeometry, sol); |
155 | |||
156 | // Prepare all caches of the scvfs inside the corresponding interaction volume. Skip | ||
157 | // those ivs that are touching a boundary, we only store the data on interior ivs here. | ||
158 |
6/6✓ Branch 0 taken 33054600 times.
✓ Branch 1 taken 4454433 times.
✓ Branch 2 taken 33054600 times.
✓ Branch 3 taken 4454433 times.
✓ Branch 4 taken 31166232 times.
✓ Branch 5 taken 1888368 times.
|
41963466 | for (const auto& scvf : scvfs(fvGeometry)) |
159 |
6/6✓ Branch 0 taken 3668875 times.
✓ Branch 1 taken 27505781 times.
✓ Branch 2 taken 3603670 times.
✓ Branch 3 taken 27562562 times.
✓ Branch 4 taken 9315 times.
✓ Branch 5 taken 65205 times.
|
62266368 | if (!isEmbeddedInBoundaryIV_(scvf, gridGeometry) && !fluxVarsCache_[scvf.index()].isUpdated()) |
160 |
1/2✓ Branch 1 taken 3603670 times.
✗ Branch 2 not taken.
|
3603670 | filler.fill(*this, fluxVarsCache_[scvf.index()], ivDataStorage_, fvGeometry, elemVolVars, scvf, forceUpdate); |
161 | } | ||
162 | } | ||
163 | 2141 | } | |
164 | |||
165 | template<class FVElementGeometry, class ElementVolumeVariables> | ||
166 | 5740501 | void updateElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, | |
167 | const FVElementGeometry& fvGeometry, | ||
168 | const ElementVolumeVariables& elemVolVars) | ||
169 | { | ||
170 | // Update only if the filler puts | ||
171 | // solution-dependent stuff into the caches | ||
172 | if (FluxVariablesCacheFiller::isSolDependent) | ||
173 | { | ||
174 | 5740501 | const auto& gridGeometry = fvGeometry.gridGeometry(); | |
175 | 11481002 | const auto& assemblyMapI = gridGeometry.connectivityMap()[gridGeometry.elementMapper().index(element)]; | |
176 | |||
177 | // helper class to fill flux variables caches | ||
178 | 5740501 | FluxVariablesCacheFiller filler(problem()); | |
179 | |||
180 | // first, set all the caches to "outdated" | ||
181 |
4/4✓ Branch 0 taken 38245112 times.
✓ Branch 1 taken 5740501 times.
✓ Branch 2 taken 38245112 times.
✓ Branch 3 taken 5740501 times.
|
49726114 | for (const auto& scvf : scvfs(fvGeometry)) |
182 | 114735336 | fluxVarsCache_[scvf.index()].setUpdateStatus(false); | |
183 |
4/4✓ Branch 0 taken 60432483 times.
✓ Branch 1 taken 5740501 times.
✓ Branch 2 taken 60432483 times.
✓ Branch 3 taken 5740501 times.
|
77653986 | for (const auto& dataJ : assemblyMapI) |
184 |
4/4✓ Branch 0 taken 158237224 times.
✓ Branch 1 taken 60432483 times.
✓ Branch 2 taken 158237224 times.
✓ Branch 3 taken 60432483 times.
|
497771897 | for (const auto scvfIdx : dataJ.scvfsJ) |
185 | 474711672 | fluxVarsCache_[scvfIdx].setUpdateStatus(false); | |
186 | |||
187 | // go through the caches maybe update them | ||
188 |
6/6✓ Branch 0 taken 38245112 times.
✓ Branch 1 taken 5740501 times.
✓ Branch 2 taken 38245112 times.
✓ Branch 3 taken 5740501 times.
✓ Branch 4 taken 34909446 times.
✓ Branch 5 taken 3335666 times.
|
49726114 | for (const auto& scvf : scvfs(fvGeometry)) |
189 | { | ||
190 |
2/2✓ Branch 0 taken 34909446 times.
✓ Branch 1 taken 3335666 times.
|
38245112 | auto& scvfCache = fluxVarsCache_[scvf.index()]; |
191 |
4/4✓ Branch 0 taken 17562371 times.
✓ Branch 1 taken 17371427 times.
✓ Branch 2 taken 107648 times.
✓ Branch 3 taken 107648 times.
|
34933798 | if (!isEmbeddedInBoundaryIV_(scvf, gridGeometry) && !scvfCache.isUpdated()) |
192 | 17454723 | filler.fill(*this, scvfCache, ivDataStorage_, fvGeometry, elemVolVars, scvf); | |
193 | } | ||
194 | |||
195 |
4/4✓ Branch 0 taken 60432483 times.
✓ Branch 1 taken 5740501 times.
✓ Branch 2 taken 60432483 times.
✓ Branch 3 taken 5740501 times.
|
77653986 | for (const auto& dataJ : assemblyMapI) |
196 | { | ||
197 |
4/4✓ Branch 0 taken 158237224 times.
✓ Branch 1 taken 60432483 times.
✓ Branch 2 taken 158237224 times.
✓ Branch 3 taken 60432483 times.
|
339534673 | for (const auto scvfIdx : dataJ.scvfsJ) |
198 | { | ||
199 |
2/2✓ Branch 0 taken 151833182 times.
✓ Branch 1 taken 6404042 times.
|
158237224 | auto& scvfCache = fluxVarsCache_[scvfIdx]; |
200 |
2/2✓ Branch 0 taken 151833182 times.
✓ Branch 1 taken 6404042 times.
|
158237224 | const auto& scvf = fvGeometry.scvf(scvfIdx); |
201 |
3/4✓ Branch 0 taken 645888 times.
✓ Branch 1 taken 151211022 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 645888 times.
|
151856910 | if (!isEmbeddedInBoundaryIV_(scvf, gridGeometry) && !scvfCache.isUpdated()) |
202 | ✗ | filler.fill(*this, scvfCache, ivDataStorage_, fvGeometry, elemVolVars, scvf); | |
203 | } | ||
204 | } | ||
205 | } | ||
206 | 5740501 | } | |
207 | |||
208 | //! access operators in the case of caching | ||
209 | template<class SubControlVolumeFace> | ||
210 | const FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) const | ||
211 | 1051147112 | { return fluxVarsCache_[scvf.index()]; } | |
212 | |||
213 | //! access operators in the case of caching | ||
214 | template<class SubControlVolumeFace> | ||
215 | FluxVariablesCache& operator [](const SubControlVolumeFace& scvf) | ||
216 |
4/4✓ Branch 0 taken 68631250 times.
✓ Branch 1 taken 67581854 times.
✓ Branch 2 taken 68631250 times.
✓ Branch 3 taken 67581854 times.
|
435817720 | { return fluxVarsCache_[scvf.index()]; } |
217 | |||
218 | //! access to the interaction volume an scvf is embedded in | ||
219 | template<class SubControlVolumeFace> | ||
220 | const PrimaryInteractionVolume& primaryInteractionVolume(const SubControlVolumeFace& scvf) const | ||
221 | { return ivDataStorage_.primaryInteractionVolumes[ (*this)[scvf].ivIndexInContainer() ]; } | ||
222 | |||
223 | //! access to the data handle of an interaction volume an scvf is embedded in | ||
224 | template<class SubControlVolumeFace> | ||
225 | const PrimaryIvDataHandle& primaryDataHandle(const SubControlVolumeFace& scvf) const | ||
226 | { return ivDataStorage_.primaryDataHandles[ (*this)[scvf].ivIndexInContainer() ]; } | ||
227 | |||
228 | //! access to the interaction volume an scvf is embedded in | ||
229 | template<class SubControlVolumeFace> | ||
230 | const SecondaryInteractionVolume& secondaryInteractionVolume(const SubControlVolumeFace& scvf) const | ||
231 | { return ivDataStorage_.secondaryInteractionVolumes[ (*this)[scvf].ivIndexInContainer() ]; } | ||
232 | |||
233 | //! access to the data handle of an interaction volume an scvf is embedded in | ||
234 | template<class SubControlVolumeFace> | ||
235 | const SecondaryIvDataHandle& secondaryDataHandle(const SubControlVolumeFace& scvf) const | ||
236 | { return ivDataStorage_.secondaryDataHandles[ (*this)[scvf].ivIndexInContainer() ]; } | ||
237 | |||
238 | ✗ | const Problem& problem() const | |
239 | ✗ | { return *problemPtr_; } | |
240 | |||
241 | private: | ||
242 | //! returns true if an scvf is contained in an interaction volume that touches the boundary | ||
243 | template<class SubControlVolumeFace, class GridGeometry> | ||
244 | 992208 | bool isEmbeddedInBoundaryIV_(const SubControlVolumeFace& scvf, const GridGeometry& gridGeometry) const | |
245 | { | ||
246 |
6/6✓ Branch 0 taken 34750654 times.
✓ Branch 1 taken 4247018 times.
✓ Branch 2 taken 151187294 times.
✓ Branch 3 taken 6380314 times.
✓ Branch 4 taken 31091712 times.
✓ Branch 5 taken 1879944 times.
|
229536936 | const auto& gridIvIndexSets = gridGeometry.gridInteractionVolumeIndexSets(); |
247 |
4/4✓ Branch 0 taken 56504 times.
✓ Branch 1 taken 935704 times.
✓ Branch 2 taken 56504 times.
✓ Branch 3 taken 935704 times.
|
230529144 | if (gridGeometry.vertexUsesSecondaryInteractionVolume(scvf.vertexIndex())) |
248 | 113008 | return gridIvIndexSets.secondaryIndexSet(scvf).nodalIndexSet().numBoundaryScvfs() > 0; | |
249 | else | ||
250 |
12/12✓ Branch 0 taken 34694150 times.
✓ Branch 1 taken 3311314 times.
✓ Branch 2 taken 34694150 times.
✓ Branch 3 taken 3311314 times.
✓ Branch 4 taken 151187294 times.
✓ Branch 5 taken 6380314 times.
✓ Branch 6 taken 151187294 times.
✓ Branch 7 taken 6380314 times.
✓ Branch 8 taken 31091712 times.
✓ Branch 9 taken 1879944 times.
✓ Branch 10 taken 31091712 times.
✓ Branch 11 taken 1879944 times.
|
458960864 | return gridIvIndexSets.primaryIndexSet(scvf).nodalIndexSet().numBoundaryScvfs() > 0; |
251 | } | ||
252 | |||
253 | //! clear all containers | ||
254 | 15 | void clear_() | |
255 | { | ||
256 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | fluxVarsCache_.clear(); |
257 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | ivDataStorage_.primaryInteractionVolumes.clear(); |
258 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | ivDataStorage_.secondaryInteractionVolumes.clear(); |
259 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | ivDataStorage_.primaryDataHandles.clear(); |
260 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
15 | ivDataStorage_.secondaryDataHandles.clear(); |
261 | 15 | } | |
262 | |||
263 | const Problem* problemPtr_; | ||
264 | std::vector<FluxVariablesCache> fluxVarsCache_; | ||
265 | |||
266 | // stored interaction volumes and handles | ||
267 | using IVDataStorage = InteractionVolumeDataStorage<PrimaryInteractionVolume, | ||
268 | PrimaryIvDataHandle, | ||
269 | SecondaryInteractionVolume, | ||
270 | SecondaryIvDataHandle>; | ||
271 | IVDataStorage ivDataStorage_; | ||
272 | }; | ||
273 | |||
274 | /*! | ||
275 | * \ingroup CCMpfaDiscretization | ||
276 | * \brief Flux variable caches on a gridview with grid caching disabled | ||
277 | */ | ||
278 | template<class TheTraits> | ||
279 | class CCMpfaGridFluxVariablesCache<TheTraits, false> | ||
280 | { | ||
281 | using Problem = typename TheTraits::Problem; | ||
282 | using ThisType = CCMpfaGridFluxVariablesCache<TheTraits, false>; | ||
283 | |||
284 | //! the flux variable cache filler type | ||
285 | using FluxVariablesCacheFiller = typename TheTraits::FluxVariablesCacheFiller; | ||
286 | public: | ||
287 | //! export the Traits | ||
288 | using Traits = TheTraits; | ||
289 | |||
290 | //! export the interaction volume types | ||
291 | using PrimaryInteractionVolume = typename Traits::PrimaryInteractionVolume; | ||
292 | using SecondaryInteractionVolume = typename Traits::SecondaryInteractionVolume; | ||
293 | |||
294 | //! export the data handle types used | ||
295 | using PrimaryIvDataHandle = typename Traits::PrimaryIvDataHandle; | ||
296 | using SecondaryIvDataHandle = typename Traits::SecondaryIvDataHandle; | ||
297 | |||
298 | //! export the flux variable cache type | ||
299 | using FluxVariablesCache = typename Traits::FluxVariablesCache; | ||
300 | |||
301 | //! make it possible to query if caching is enabled | ||
302 | static constexpr bool cachingEnabled = false; | ||
303 | |||
304 | //! export the type of the local view | ||
305 | using LocalView = typename Traits::template LocalView<ThisType, cachingEnabled>; | ||
306 | |||
307 | //! The constructor | ||
308 | ✗ | CCMpfaGridFluxVariablesCache(const Problem& problem) : problemPtr_(&problem) {} | |
309 | |||
310 | //! When global flux variables caching is disabled, we don't need to update the cache | ||
311 | template<class GridGeometry, class GridVolumeVariables, class SolutionVector> | ||
312 | void update(const GridGeometry& gridGeometry, | ||
313 | const GridVolumeVariables& gridVolVars, | ||
314 | const SolutionVector& sol, | ||
315 | bool forceUpdate = false) {} | ||
316 | |||
317 | //! When global flux variables caching is disabled, we don't need to update the cache | ||
318 | template<class FVElementGeometry, class ElementVolumeVariables> | ||
319 | void updateElement(const typename FVElementGeometry::GridGeometry::GridView::template Codim<0>::Entity& element, | ||
320 | const FVElementGeometry& fvGeometry, | ||
321 | const ElementVolumeVariables& elemVolVars) {} | ||
322 | |||
323 | ✗ | const Problem& problem() const | |
324 | ✗ | { return *problemPtr_; } | |
325 | |||
326 | private: | ||
327 | const Problem* problemPtr_; | ||
328 | }; | ||
329 | |||
330 | } // end namespace Dumux | ||
331 | |||
332 | #endif | ||
333 |