8 #include "element-families.h"
10 #include "precompute.h"
16 #include <xtensor/xtensor.hpp>
17 #include <xtensor/xview.hpp>
18 #include <xtl/xspan.hpp>
39 std::tuple<std::array<std::vector<xt::xtensor<double, 2>>, 4>,
40 std::array<std::vector<xt::xtensor<double, 3>>, 4>>
42 const std::array<std::vector<xt::xtensor<double, 3>>, 4>& M,
43 int tdim,
int value_size);
224 const xt::xtensor<double, 2>&
wcoeffs,
225 const std::array<std::vector<xt::xtensor<double, 2>>, 4>&
x,
226 const std::array<std::vector<xt::xtensor<double, 3>>, 4>&
M,
230 std::vector<std::tuple<std::vector<FiniteElement>, std::vector<int>>>
238 const xt::xtensor<double, 2>&
wcoeffs,
239 const std::array<std::vector<xt::xtensor<double, 2>>, 4>&
x,
240 const std::array<std::vector<xt::xtensor<double, 3>>, 4>&
M,
243 std::vector<std::tuple<std::vector<FiniteElement>, std::vector<int>>>
251 const xt::xtensor<double, 2>&
wcoeffs,
252 const std::array<std::vector<xt::xtensor<double, 2>>, 4>&
x,
253 const std::array<std::vector<xt::xtensor<double, 3>>, 4>&
M,
256 std::vector<std::tuple<std::vector<FiniteElement>, std::vector<int>>>
264 const xt::xtensor<double, 2>&
wcoeffs,
265 const std::array<std::vector<xt::xtensor<double, 2>>, 4>&
x,
266 const std::array<std::vector<xt::xtensor<double, 3>>, 4>&
M,
269 std::vector<std::tuple<std::vector<FiniteElement>, std::vector<int>>>
304 std::size_t num_points)
const;
327 xt::xtensor<double, 4>
tabulate(
int nd,
328 const xt::xtensor<double, 2>&
x)
const;
355 void tabulate(
int nd,
const xt::xtensor<double, 2>&
x,
356 xt::xtensor<double, 4>& basis)
const;
429 xt::xtensor<double, 3>
push_forward(
const xt::xtensor<double, 3>& U,
430 const xt::xtensor<double, 3>& J,
431 const xtl::span<const double>& detJ,
432 const xt::xtensor<double, 3>& K)
const;
440 xt::xtensor<double, 3>
pull_back(
const xt::xtensor<double, 3>& u,
441 const xt::xtensor<double, 3>& J,
442 const xtl::span<const double>& detJ,
443 const xt::xtensor<double, 3>& K)
const;
476 template <
typename O,
typename P,
typename Q,
typename R>
477 std::function<void(O&,
const P&,
const Q&,
double,
const R&)>
map_fn()
const
481 case maps::type::identity:
482 return [](O& u,
const P& U,
const Q&, double,
const R&) { u.assign(U); };
483 case maps::type::L2Piola:
484 return [](O& u,
const P& U,
const Q& J,
double detJ,
const R& K)
486 case maps::type::covariantPiola:
487 return [](O& u,
const P& U,
const Q& J,
double detJ,
const R& K)
489 case maps::type::contravariantPiola:
490 return [](O& u,
const P& U,
const Q& J,
double detJ,
const R& K)
492 case maps::type::doubleCovariantPiola:
493 return [](O& u,
const P& U,
const Q& J,
double detJ,
const R& K)
495 case maps::type::doubleContravariantPiola:
496 return [](O& u,
const P& U,
const Q& J,
double detJ,
const R& K)
499 throw std::runtime_error(
"Map not implemented");
530 const std::vector<std::vector<std::vector<int>>>&
entity_dofs()
const;
633 std::uint32_t cell_info)
const;
643 std::uint32_t cell_info)
const;
653 template <
typename T>
655 std::uint32_t cell_info)
const;
665 template <
typename T>
668 std::uint32_t cell_info)
const;
678 template <
typename T>
680 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const;
690 template <
typename T>
693 std::uint32_t cell_info)
const;
703 template <
typename T>
706 std::uint32_t cell_info)
const;
716 template <
typename T>
718 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const;
728 template <
typename T>
730 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const;
740 template <
typename T>
742 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const;
748 const xt::xtensor<double, 2>&
points()
const;
839 const xt::xtensor<double, 2>&
wcoeffs()
const;
844 const std::array<std::vector<xt::xtensor<double, 2>>, 4>&
x()
const;
878 const std::array<std::vector<xt::xtensor<double, 3>>, 4>&
M()
const;
903 std::vector<std::tuple<std::vector<FiniteElement>, std::vector<int>>>
915 std::size_t _cell_tdim;
918 std::vector<std::vector<cell::type>> _cell_subentity_types;
936 int _highest_complete_degree;
939 std::vector<int> _value_shape;
949 xt::xtensor<double, 2> _coeffs;
957 std::vector<std::vector<int>> _num_edofs;
961 std::vector<std::vector<int>> _num_e_closure_dofs;
964 std::vector<std::vector<std::vector<int>>> _edofs;
967 std::vector<std::vector<std::vector<int>>> _e_closure_dofs;
970 std::map<cell::type, xt::xtensor<double, 3>> _entity_transformations;
977 xt::xtensor<double, 2> _points;
981 std::array<std::vector<xt::xtensor<double, 2>>, 4> _x;
984 xt::xtensor<double, 2> _matM;
988 bool _dof_transformations_are_permutations;
991 bool _dof_transformations_are_identity;
996 std::map<cell::type, std::vector<std::vector<std::size_t>>> _eperm;
1001 std::map<cell::type, std::vector<std::vector<std::size_t>>> _eperm_rev;
1005 std::vector<std::tuple<std::vector<std::size_t>, std::vector<double>,
1006 xt::xtensor<double, 2>>>>
1011 std::vector<std::tuple<std::vector<std::size_t>, std::vector<double>,
1012 xt::xtensor<double, 2>>>>
1017 std::vector<std::tuple<std::vector<std::size_t>, std::vector<double>,
1018 xt::xtensor<double, 2>>>>
1023 std::vector<std::tuple<std::vector<std::size_t>, std::vector<double>,
1024 xt::xtensor<double, 2>>>>
1029 bool _discontinuous;
1032 xt::xtensor<double, 2> _dual_matrix;
1039 std::vector<std::tuple<std::vector<FiniteElement>, std::vector<int>>>
1043 bool _interpolation_is_identity;
1047 xt::xtensor<double, 2> _wcoeffs;
1050 std::array<std::vector<xt::xtensor<double, 3>>, 4> _M;
1074 cell::type cell_type,
const std::vector<std::size_t>& value_shape,
1075 const xt::xtensor<double, 2>& wcoeffs,
1076 const std::array<std::vector<xt::xtensor<double, 2>>, 4>& x,
1077 const std::array<std::vector<xt::xtensor<double, 3>>, 4>& M,
1078 maps::type map_type,
bool discontinuous,
int highest_complete_degree,
1079 int highest_degree);
1092 bool discontinuous);
1119 bool discontinuous);
1132 int degree,
bool discontinuous);
1181 template <
typename T>
1184 std::uint32_t cell_info)
const
1186 if (_dof_transformations_are_identity)
1189 if (_cell_tdim >= 2)
1193 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1195 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1198 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1201 if (cell_info >> (face_start + e) & 1)
1203 dofstart, block_size);
1204 dofstart += _num_edofs[1][e];
1207 if (_cell_tdim == 3)
1210 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1213 if (cell_info >> (3 * f) & 1)
1215 data, dofstart, block_size);
1218 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1220 data, dofstart, block_size);
1221 dofstart += _num_edofs[2][f];
1227 template <
typename T>
1229 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const
1231 if (_dof_transformations_are_identity)
1234 if (_cell_tdim >= 2)
1238 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1240 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1243 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1246 if (cell_info >> (face_start + e) & 1)
1248 dofstart, block_size);
1249 dofstart += _num_edofs[1][e];
1252 if (_cell_tdim == 3)
1255 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1258 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1260 data, dofstart, block_size);
1262 if (cell_info >> (3 * f) & 1)
1264 data, dofstart, block_size);
1265 dofstart += _num_edofs[2][f];
1271 template <
typename T>
1273 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const
1275 if (_dof_transformations_are_identity)
1278 if (_cell_tdim >= 2)
1282 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1284 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1287 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1290 if (cell_info >> (face_start + e) & 1)
1292 dofstart, block_size);
1293 dofstart += _num_edofs[1][e];
1296 if (_cell_tdim == 3)
1299 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1302 if (cell_info >> (3 * f) & 1)
1304 _etrans_invT.at(_cell_subentity_types[2][f])[1], data, dofstart,
1308 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1310 _etrans_invT.at(_cell_subentity_types[2][f])[0], data, dofstart,
1312 dofstart += _num_edofs[2][f];
1318 template <
typename T>
1320 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const
1322 if (_dof_transformations_are_identity)
1325 if (_cell_tdim >= 2)
1329 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1331 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1334 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1337 if (cell_info >> (face_start + e) & 1)
1339 dofstart, block_size);
1340 dofstart += _num_edofs[1][e];
1343 if (_cell_tdim == 3)
1346 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1349 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1351 _etrans_inv.at(_cell_subentity_types[2][f])[0], data, dofstart,
1354 if (cell_info >> (3 * f) & 1)
1356 _etrans_inv.at(_cell_subentity_types[2][f])[1], data, dofstart,
1358 dofstart += _num_edofs[2][f];
1364 template <
typename T>
1366 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const
1368 if (_dof_transformations_are_identity)
1371 if (_cell_tdim >= 2)
1375 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1377 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1380 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1383 if (cell_info >> (face_start + e) & 1)
1385 _etrans.at(cell::type::interval)[0], data, dofstart, block_size);
1386 dofstart += _num_edofs[1][e];
1389 if (_cell_tdim == 3)
1392 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1395 if (cell_info >> (3 * f) & 1)
1397 _etrans.at(_cell_subentity_types[2][f])[1], data, dofstart,
1401 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1403 _etrans.at(_cell_subentity_types[2][f])[0], data, dofstart,
1405 dofstart += _num_edofs[2][f];
1411 template <
typename T>
1413 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const
1415 if (_dof_transformations_are_identity)
1418 if (_cell_tdim >= 2)
1422 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1424 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1427 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1430 if (cell_info >> (face_start + e) & 1)
1432 _etrans_invT.at(cell::type::interval)[0], data, dofstart,
1434 dofstart += _num_edofs[1][e];
1437 if (_cell_tdim == 3)
1440 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1443 if (cell_info >> (3 * f) & 1)
1445 _etrans_invT.at(_cell_subentity_types[2][f])[1], data, dofstart,
1449 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1451 _etrans_invT.at(_cell_subentity_types[2][f])[0], data, dofstart,
1453 dofstart += _num_edofs[2][f];
1459 template <
typename T>
1461 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const
1463 if (_dof_transformations_are_identity)
1466 if (_cell_tdim >= 2)
1470 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1472 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1475 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1478 if (cell_info >> (face_start + e) & 1)
1480 _etransT.at(cell::type::interval)[0], data, dofstart, block_size);
1481 dofstart += _num_edofs[1][e];
1484 if (_cell_tdim == 3)
1487 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1490 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1492 _etransT.at(_cell_subentity_types[2][f])[0], data, dofstart,
1496 if (cell_info >> (3 * f) & 1)
1498 _etransT.at(_cell_subentity_types[2][f])[1], data, dofstart,
1501 dofstart += _num_edofs[2][f];
1507 template <
typename T>
1509 const xtl::span<T>& data,
int block_size, std::uint32_t cell_info)
const
1511 if (_dof_transformations_are_identity)
1514 if (_cell_tdim >= 2)
1518 int face_start = _cell_tdim == 3 ? 3 * _num_edofs[2].size() : 0;
1520 = std::accumulate(_num_edofs[0].cbegin(), _num_edofs[0].cend(), 0);
1523 for (std::size_t e = 0; e < _num_edofs[1].size(); ++e)
1526 if (cell_info >> (face_start + e) & 1)
1528 _etrans_inv.at(cell::type::interval)[0], data, dofstart,
1530 dofstart += _num_edofs[1][e];
1533 if (_cell_tdim == 3)
1536 for (std::size_t f = 0; f < _num_edofs[2].size(); ++f)
1539 for (std::uint32_t r = 0; r < (cell_info >> (3 * f + 1) & 3); ++r)
1541 _etrans_inv.at(_cell_subentity_types[2][f])[0], data, dofstart,
1545 if (cell_info >> (3 * f) & 1)
1547 _etrans_inv.at(_cell_subentity_types[2][f])[1], data, dofstart,
1550 dofstart += _num_edofs[2][f];
A finite element.
Definition: finite-element.h:53
FiniteElement & operator=(const FiniteElement &element)=default
Assignment operator.
xt::xtensor< double, 4 > tabulate(int nd, const xt::xtensor< double, 2 > &x) const
Definition: finite-element.cpp:819
cell::type cell_type() const
Definition: finite-element.cpp:857
const std::array< std::vector< xt::xtensor< double, 2 > >, 4 > & x() const
Definition: finite-element.cpp:1165
FiniteElement & operator=(FiniteElement &&element)=default
Move assignment operator.
int highest_complete_degree() const
Definition: finite-element.cpp:863
xt::xtensor< double, 3 > push_forward(const xt::xtensor< double, 3 > &U, const xt::xtensor< double, 3 > &J, const xtl::span< const double > &detJ, const xt::xtensor< double, 3 > &K) const
Definition: finite-element.cpp:978
xt::xtensor< double, 3 > pull_back(const xt::xtensor< double, 3 > &u, const xt::xtensor< double, 3 > &J, const xtl::span< const double > &detJ, const xt::xtensor< double, 3 > &K) const
Definition: finite-element.cpp:1006
element::lagrange_variant lagrange_variant() const
Definition: finite-element.cpp:1188
FiniteElement(FiniteElement &&element)=default
Move constructor.
const xt::xtensor< double, 2 > & dual_matrix() const
Definition: finite-element.cpp:1152
std::map< cell::type, xt::xtensor< double, 3 > > entity_transformations() const
Definition: finite-element.cpp:1147
bool operator==(const FiniteElement &e) const
Definition: finite-element.cpp:785
bool dof_transformations_are_permutations() const
Definition: finite-element.cpp:881
maps::type map_type() const
Definition: finite-element.cpp:877
const xt::xtensor< double, 2 > & points() const
Definition: finite-element.cpp:976
void apply_inverse_dof_transformation_to_transpose(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1508
std::vector< std::tuple< std::vector< FiniteElement >, std::vector< int > > > get_tensor_product_representation() const
Definition: finite-element.cpp:1201
const std::vector< std::vector< std::vector< int > > > & entity_dofs() const
Definition: finite-element.cpp:902
FiniteElement(element::family family, cell::type cell_type, int degree, const std::vector< std::size_t > &value_shape, const xt::xtensor< double, 2 > &wcoeffs, const std::array< std::vector< xt::xtensor< double, 2 >>, 4 > &x, const std::array< std::vector< xt::xtensor< double, 3 >>, 4 > &M, maps::type map_type, bool discontinuous, int highest_complete_degree, int highest_degree, element::lagrange_variant lvariant, element::dpc_variant dvariant, std::vector< std::tuple< std::vector< FiniteElement >, std::vector< int >>> tensor_factors={})
Definition: finite-element.cpp:516
const std::array< std::vector< xt::xtensor< double, 3 > >, 4 > & M() const
Definition: finite-element.cpp:1171
const xt::xtensor< double, 2 > & interpolation_matrix() const
Definition: finite-element.cpp:891
void apply_transpose_dof_transformation(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1228
const std::vector< std::vector< std::vector< int > > > & entity_closure_dofs() const
Definition: finite-element.cpp:914
const std::vector< std::vector< int > > & num_entity_dofs() const
Definition: finite-element.cpp:896
int degree() const
Definition: finite-element.cpp:859
void apply_inverse_transpose_dof_transformation_to_transpose(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1412
std::function< void(O &, const P &, const Q &, double, const R &)> map_fn() const
Definition: finite-element.h:477
const std::vector< int > & value_shape() const
Definition: finite-element.cpp:868
bool has_tensor_product_factorisation() const
Definition: finite-element.cpp:1183
~FiniteElement()=default
Destructor.
const std::vector< std::vector< int > > & num_entity_closure_dofs() const
Definition: finite-element.cpp:908
int dim() const
Definition: finite-element.cpp:873
int highest_degree() const
Definition: finite-element.cpp:861
void apply_inverse_transpose_dof_transformation(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1272
std::array< std::size_t, 4 > tabulate_shape(std::size_t nd, std::size_t num_points) const
Definition: finite-element.cpp:805
element::dpc_variant dpc_variant() const
Definition: finite-element.cpp:1193
element::family family() const
Definition: finite-element.cpp:875
const xt::xtensor< double, 2 > & wcoeffs() const
Definition: finite-element.cpp:1157
const xt::xtensor< double, 2 > & coefficient_matrix() const
Definition: finite-element.cpp:1178
void permute_dofs(const xtl::span< std::int32_t > &dofs, std::uint32_t cell_info) const
Definition: finite-element.cpp:1035
bool dof_transformations_are_identity() const
Definition: finite-element.cpp:886
FiniteElement(const FiniteElement &element)=default
Copy constructor.
bool interpolation_is_identity() const
Definition: finite-element.cpp:1195
void apply_dof_transformation_to_transpose(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1365
void unpermute_dofs(const xtl::span< std::int32_t > &dofs, std::uint32_t cell_info) const
Definition: finite-element.cpp:1091
void apply_transpose_dof_transformation_to_transpose(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1460
void apply_dof_transformation(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1182
bool discontinuous() const
Definition: finite-element.cpp:879
void apply_inverse_dof_transformation(const xtl::span< T > &data, int block_size, std::uint32_t cell_info) const
Definition: finite-element.h:1319
xt::xtensor< double, 3 > base_transformations() const
Definition: finite-element.cpp:919
type
Cell type.
Definition: cell.h:19
std::tuple< std::array< std::vector< xt::xtensor< double, 2 > >, 4 >, std::array< std::vector< xt::xtensor< double, 3 > >, 4 > > make_discontinuous(const std::array< std::vector< xt::xtensor< double, 2 >>, 4 > &x, const std::array< std::vector< xt::xtensor< double, 3 >>, 4 > &M, int tdim, int value_size)
Definition: finite-element.cpp:336
lagrange_variant
Variants of a Lagrange space that can be created.
Definition: element-families.h:12
dpc_variant
Definition: element-families.h:32
family
Available element families.
Definition: element-families.h:45
void l2_piola(O &&r, const P &U, const Q &, double detJ, const R &)
L2 Piola map.
Definition: maps.h:63
void covariant_piola(O &&r, const P &U, const Q &, double, const R &K)
Covariant Piola map.
Definition: maps.h:71
void contravariant_piola(O &&r, const P &U, const Q &J, double detJ, const R &)
Contravariant Piola map.
Definition: maps.h:85
void double_contravariant_piola(O &&r, const P &U, const Q &J, double detJ, const R &)
Double contravariant Piola map.
Definition: maps.h:115
void double_covariant_piola(O &&r, const P &U, const Q &J, double, const R &K)
Double covariant Piola map.
Definition: maps.h:100
type
Map type.
Definition: maps.h:20
void apply_matrix(const std::tuple< std::vector< std::size_t >, std::vector< T >, xt::xtensor< T, 2 >> &matrix, const xtl::span< E > &data, std::size_t offset=0, std::size_t block_size=1)
Definition: precompute.h:319
void apply_matrix_to_transpose(const std::tuple< std::vector< std::size_t >, std::vector< T >, xt::xtensor< T, 2 >> &matrix, const xtl::span< E > &data, std::size_t offset=0, std::size_t block_size=1)
Definition: precompute.h:351
Basix: FEniCS runtime basis evaluation library.
Definition: cell.h:15
FiniteElement create_element(element::family family, cell::type cell, int degree, element::lagrange_variant lvariant, bool discontinuous)
Definition: finite-element.cpp:289
std::string version()
Definition: finite-element.cpp:1208
FiniteElement create_custom_element(cell::type cell_type, const std::vector< std::size_t > &value_shape, const xt::xtensor< double, 2 > &wcoeffs, const std::array< std::vector< xt::xtensor< double, 2 >>, 4 > &x, const std::array< std::vector< xt::xtensor< double, 3 >>, 4 > &M, maps::type map_type, bool discontinuous, int highest_complete_degree, int highest_degree)
Definition: finite-element.cpp:392