xref: /honee/src/boundary_condition.c (revision d6cac2202708b595f7f0c5ad1c28f21a66db1a8c)
1 // SPDX-FileCopyrightText: Copyright (c) 2017-2024, HONEE contributors.
2 // SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause
3 
4 #include <navierstokes.h>
5 #include "petscerror.h"
6 
7 /**
8    @brief Add `BCDefinition` to a `PetscSegBuffer`
9 
10    @param[in]     bc_def      `BCDefinition` to add
11    @param[in,out] bc_defs_seg `PetscSegBuffer` to add to
12 **/
13 static PetscErrorCode AddBCDefinitionToSegBuffer(BCDefinition bc_def, PetscSegBuffer bc_defs_seg) {
14   BCDefinition *bc_def_ptr;
15 
16   PetscFunctionBeginUser;
17   if (bc_def == NULL) PetscFunctionReturn(PETSC_SUCCESS);
18   PetscCall(PetscSegBufferGet(bc_defs_seg, 1, &bc_def_ptr));
19   *bc_def_ptr = bc_def;
20   PetscFunctionReturn(PETSC_SUCCESS);
21 }
22 
23 /**
24    @brief Create and setup `BCDefinition`s and `SimpleBC` from commandline options
25 
26    @param[in]     honee   `Honee`
27    @param[in,out] problem `ProblemData`
28    @param[in]     app_ctx `AppCtx`
29    @param[in,out] bc      `SimpleBC`
30 **/
31 PetscErrorCode BoundaryConditionSetUp(Honee honee, ProblemData problem, AppCtx app_ctx, SimpleBC bc) {
32   PetscSegBuffer bc_defs_seg;
33   PetscBool      flg;
34   BCDefinition   bc_def;
35 
36   PetscFunctionBeginUser;
37   PetscCall(PetscSegBufferCreate(sizeof(BCDefinition), 4, &bc_defs_seg));
38 
39   PetscOptionsBegin(honee->comm, NULL, "Boundary Condition Options", NULL);
40 
41   PetscCall(PetscOptionsBCDefinition("-bc_wall", "Face IDs to apply wall BC", NULL, "wall", &bc_def, NULL));
42   PetscCall(AddBCDefinitionToSegBuffer(bc_def, bc_defs_seg));
43   if (bc_def) {
44     PetscInt num_essential_comps = 16, essential_comps[16];
45 
46     PetscCall(PetscOptionsIntArray("-wall_comps", "An array of constrained component numbers", NULL, essential_comps, &num_essential_comps, &flg));
47     PetscCall(BCDefinitionSetEssential(bc_def, num_essential_comps, essential_comps));
48 
49     app_ctx->wall_forces.num_wall = bc_def->num_label_values;
50     PetscCall(PetscMalloc1(bc_def->num_label_values, &app_ctx->wall_forces.walls));
51     PetscCall(PetscArraycpy(app_ctx->wall_forces.walls, bc_def->label_values, bc_def->num_label_values));
52   }
53 
54   {  // Symmetry Boundary Conditions
55     const char *deprecated[3] = {"-bc_slip_x", "-bc_slip_y", "-bc_slip_z"};
56     const char *flags[3]      = {"-bc_symmetry_x", "-bc_symmetry_y", "-bc_symmetry_z"};
57 
58     for (PetscInt j = 0; j < 3; j++) {
59       PetscCall(PetscOptionsDeprecated(deprecated[j], flags[j], "libCEED 0.12.0",
60                                        "Use -bc_symmetry_[x,y,z] for direct equivalency, or -bc_slip for weak, Riemann-based, direction-invariant "
61                                        "slip/no-penatration boundary conditions"));
62       PetscCall(PetscOptionsBCDefinition(flags[j], "Face IDs to apply symmetry BC", NULL, "symmetry", &bc_def, NULL));
63       if (!bc_def) {
64         PetscCall(PetscOptionsBCDefinition(deprecated[j], "Face IDs to apply symmetry BC", NULL, "symmetry", &bc_def, NULL));
65       }
66       PetscCall(AddBCDefinitionToSegBuffer(bc_def, bc_defs_seg));
67       if (bc_def) {
68         PetscInt essential_comps[1] = {j + 1};
69 
70         PetscCall(BCDefinitionSetEssential(bc_def, 1, essential_comps));
71       }
72     }
73   }
74 
75   PetscCall(PetscOptionsBCDefinition("-bc_inflow", "Face IDs to apply inflow BC", NULL, "inflow", &bc_def, NULL));
76   PetscCall(AddBCDefinitionToSegBuffer(bc_def, bc_defs_seg));
77 
78   PetscCall(PetscOptionsBCDefinition("-bc_outflow", "Face IDs to apply outflow BC", NULL, "outflow", &bc_def, NULL));
79   PetscCall(AddBCDefinitionToSegBuffer(bc_def, bc_defs_seg));
80 
81   PetscCall(PetscOptionsBCDefinition("-bc_freestream", "Face IDs to apply freestream BC", NULL, "freestream", &bc_def, NULL));
82   PetscCall(AddBCDefinitionToSegBuffer(bc_def, bc_defs_seg));
83 
84   PetscCall(PetscOptionsBCDefinition("-bc_slip", "Face IDs to apply slip BC", NULL, "slip", &bc_def, NULL));
85   PetscCall(AddBCDefinitionToSegBuffer(bc_def, bc_defs_seg));
86 
87   PetscOptionsEnd();
88 
89   PetscCall(PetscSegBufferGetSize(bc_defs_seg, &problem->num_bc_defs));
90   PetscCall(PetscSegBufferExtractAlloc(bc_defs_seg, &problem->bc_defs));
91   PetscCall(PetscSegBufferDestroy(&bc_defs_seg));
92 
93   //TODO: Verify that the BCDefinition don't have overlapping claims to boundary faces
94 
95   PetscFunctionReturn(PETSC_SUCCESS);
96 }
97 
98 /**
99   @brief Destroy `HoneeBCStruct` object
100 
101   @param[in] ctx Pointer to `HoneeBCStruct`
102 **/
103 PetscErrorCode HoneeBCDestroy(void **ctx) {
104   HoneeBCStruct honee_bc = *(HoneeBCStruct *)ctx;
105   Ceed          ceed     = honee_bc->honee->ceed;
106 
107   PetscFunctionBeginUser;
108   if (honee_bc->qfctx) PetscCallCeed(ceed, CeedQFunctionContextDestroy(&honee_bc->qfctx));
109   if (honee_bc->DestroyCtx) PetscCall((*(honee_bc->DestroyCtx))(&honee_bc->ctx));
110   PetscCall(PetscFree(honee_bc));
111   *ctx = NULL;
112   PetscFunctionReturn(PETSC_SUCCESS);
113 }
114 
115 /**
116   @brief Create a QFunction matching the "standard" HONEE inputs for IFunctions
117 
118   This assumes the `bc_def` context is a `HoneeBCStruct` and inputs/outputs of the IFunction QFunction are in the correct order.
119 
120   @param[in]  bc_def      `BCDefinition` that hosts the IFunction
121   @param[in]  qf_func_ptr `CeedQFunctionUser` for the IFunction
122   @param[in]  qf_loc      Absolute path to source of `CeedQFunctionUser`
123   @param[in]  qfctx       `CeedQFunctionContext` for the IFunction (also shared with the IJacobian, if applicable)
124   @param[out] qf_ifunc    QFunction for the IFunction
125 **/
126 PetscErrorCode HoneeBCCreateIFunctionQF(BCDefinition bc_def, CeedQFunctionUser qf_func_ptr, const char *qf_loc, CeedQFunctionContext qfctx,
127                                         CeedQFunction *qf_ifunc) {
128   Ceed          ceed;
129   DM            dm;
130   PetscInt      dim, dim_sur, height = 1, num_comp_x, num_comp_q;
131   CeedInt       q_data_size_sur, jac_data_size_sur;
132   HoneeBCStruct honee_bc;
133 
134   PetscFunctionBeginUser;
135   PetscCall(BCDefinitionGetDM(bc_def, &dm));
136   PetscCall(BCDefinitionGetContext(bc_def, &honee_bc));
137   ceed              = honee_bc->honee->ceed;
138   jac_data_size_sur = honee_bc->jac_data_size_sur;
139 
140   PetscCall(DMGetDimension(dm, &dim));
141   dim_sur = dim - height;
142   PetscCall(QDataBoundaryGetNumComponents(dm, &q_data_size_sur));
143   PetscCall(DMGetCoordinateNumComps(dm, &num_comp_x));
144   {
145     PetscSection section;
146 
147     PetscCall(DMGetLocalSection(dm, &section));
148     PetscCall(PetscSectionGetFieldComponents(section, bc_def->dm_field, &num_comp_q));
149   }
150 
151   PetscCallCeed(ceed, CeedQFunctionCreateInterior(ceed, 1, qf_func_ptr, qf_loc, qf_ifunc));
152   PetscCallCeed(ceed, CeedQFunctionSetContext(*qf_ifunc, qfctx));
153   PetscCallCeed(ceed, CeedQFunctionSetUserFlopsEstimate(*qf_ifunc, 0));
154   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ifunc, "q", num_comp_q, CEED_EVAL_INTERP));
155   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ifunc, "Grad_q", num_comp_q * dim_sur, CEED_EVAL_GRAD));
156   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ifunc, "surface qdata", q_data_size_sur, CEED_EVAL_NONE));
157   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ifunc, "x", num_comp_x, CEED_EVAL_INTERP));
158   PetscCallCeed(ceed, CeedQFunctionAddOutput(*qf_ifunc, "v", num_comp_q, CEED_EVAL_INTERP));
159   if (jac_data_size_sur) PetscCallCeed(ceed, CeedQFunctionAddOutput(*qf_ifunc, "surface jacobian data", jac_data_size_sur, CEED_EVAL_NONE));
160   PetscFunctionReturn(PETSC_SUCCESS);
161 }
162 
163 /**
164   @brief Create a QFunction matching the "standard" HONEE inputs for IJacobian
165 
166   This assumes the `bc_def` context is a `HoneeBCStruct` and inputs/outputs of the IJacobian QFunction are in the correct order.
167 
168   @param[in]  bc_def      `BCDefinition` that hosts the IJacobian
169   @param[in]  qf_func_ptr `CeedQFunctionUser` for the IJacobian
170   @param[in]  qf_loc      Absolute path to source of `CeedQFunctionUser`
171   @param[in]  qfctx       `CeedQFunctionContext` for the IJacobian (also shared with the IFunction)
172   @param[out] qf_ijac     QFunction for the IJacobian
173 **/
174 PetscErrorCode HoneeBCCreateIJacobianQF(BCDefinition bc_def, CeedQFunctionUser qf_func_ptr, const char *qf_loc, CeedQFunctionContext qfctx,
175                                         CeedQFunction *qf_ijac) {
176   Ceed          ceed;
177   DM            dm;
178   PetscInt      dim, dim_sur, height = 1, num_comp_x, num_comp_q;
179   CeedInt       q_data_size_sur, jac_data_size_sur;
180   HoneeBCStruct honee_bc;
181 
182   PetscFunctionBeginUser;
183   PetscCall(BCDefinitionGetDM(bc_def, &dm));
184   PetscCall(BCDefinitionGetContext(bc_def, &honee_bc));
185   ceed              = honee_bc->honee->ceed;
186   jac_data_size_sur = honee_bc->jac_data_size_sur;
187 
188   PetscCall(DMGetDimension(dm, &dim));
189   dim_sur = dim - height;
190   PetscCall(QDataBoundaryGetNumComponents(dm, &q_data_size_sur));
191   PetscCall(DMGetCoordinateNumComps(dm, &num_comp_x));
192   {
193     PetscSection section;
194 
195     PetscCall(DMGetLocalSection(dm, &section));
196     PetscCall(PetscSectionGetFieldComponents(section, bc_def->dm_field, &num_comp_q));
197   }
198 
199   PetscCallCeed(ceed, CeedQFunctionCreateInterior(ceed, 1, qf_func_ptr, qf_loc, qf_ijac));
200   PetscCallCeed(ceed, CeedQFunctionSetContext(*qf_ijac, qfctx));
201   PetscCallCeed(ceed, CeedQFunctionSetUserFlopsEstimate(*qf_ijac, 0));
202   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ijac, "dq", num_comp_q, CEED_EVAL_INTERP));
203   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ijac, "Grad_dq", num_comp_q * dim_sur, CEED_EVAL_GRAD));
204   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ijac, "surface qdata", q_data_size_sur, CEED_EVAL_NONE));
205   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ijac, "x", num_comp_x, CEED_EVAL_INTERP));
206   PetscCallCeed(ceed, CeedQFunctionAddInput(*qf_ijac, "surface jacobian data", jac_data_size_sur, CEED_EVAL_NONE));
207   PetscCallCeed(ceed, CeedQFunctionAddOutput(*qf_ijac, "v", num_comp_q, CEED_EVAL_INTERP));
208   PetscFunctionReturn(PETSC_SUCCESS);
209 }
210 
211 /**
212   @brief Setups and adds IFunction operator to given composite operator
213 
214   @param[in]     bc_def       `BCDefinition` for the boundary condition IFunction
215   @param[in]     domain_label `DMLabel` for the face orientation label
216   @param[in]     label_value  Orientation value
217   @param[in]     qf_ifunc     QFunction for the IFunction
218   @param[in,out] op_ifunc     Composite operator to be added to
219   @param[out]    sub_op_ifunc Non-composite operator that is added in this function. To be passed to `HoneeBCAddIJacobianOp()`
220 **/
221 PetscErrorCode HoneeBCAddIFunctionOp(BCDefinition bc_def, DMLabel domain_label, PetscInt label_value, CeedQFunction qf_ifunc, CeedOperator op_ifunc,
222                                      CeedOperator *sub_op_ifunc) {
223   Honee               honee;
224   Ceed                ceed;
225   DM                  dm, dm_coord;
226   HoneeBCStruct       honee_bc;
227   PetscInt            dim, height = 1, num_comp_x, num_comp_q;
228   CeedInt             q_data_size, jac_data_size;
229   CeedVector          q_data, jac_data;
230   CeedOperator        sub_op_ifunc_;
231   CeedElemRestriction elem_restr_qd, elem_restr_q, elem_restr_x, elem_restr_jac;
232   CeedBasis           basis_x, basis_q;
233 
234   PetscFunctionBeginUser;
235   PetscCall(BCDefinitionGetDM(bc_def, &dm));
236   PetscCall(BCDefinitionGetContext(bc_def, &honee_bc));
237   honee         = honee_bc->honee;
238   ceed          = honee_bc->honee->ceed;
239   jac_data_size = honee_bc->jac_data_size_sur;
240 
241   PetscCall(DMGetDimension(dm, &dim));
242   PetscCall(QDataBoundaryGetNumComponents(dm, &q_data_size));
243   PetscCall(DMGetCoordinateNumComps(dm, &num_comp_x));
244 
245   {
246     PetscSection section;
247 
248     PetscCall(DMGetLocalSection(dm, &section));
249     PetscCall(PetscSectionGetFieldComponents(section, bc_def->dm_field, &num_comp_q));
250   }
251 
252   PetscCall(DMGetCoordinateDM(dm, &dm_coord));
253   PetscCall(CreateBasisFromPlex(ceed, dm, domain_label, label_value, height, bc_def->dm_field, &basis_q));
254   PetscCall(CreateBasisFromPlex(ceed, dm_coord, domain_label, label_value, height, bc_def->dm_field, &basis_x));
255   PetscCall(DMPlexCeedElemRestrictionCreate(ceed, dm, domain_label, label_value, height, bc_def->dm_field, &elem_restr_q));
256   PetscCall(DMPlexCeedElemRestrictionCoordinateCreate(ceed, dm, domain_label, label_value, height, &elem_restr_x));
257   if (jac_data_size > 0) {
258     PetscCall(DMPlexCeedElemRestrictionQDataCreate(ceed, dm, domain_label, label_value, height, jac_data_size, &elem_restr_jac));
259     PetscCallCeed(ceed, CeedElemRestrictionCreateVector(elem_restr_jac, &jac_data, NULL));
260   }
261 
262   PetscCall(QDataBoundaryGet(ceed, dm, domain_label, label_value, elem_restr_x, basis_x, honee->x_coord, &elem_restr_qd, &q_data, &q_data_size));
263 
264   PetscCallCeed(ceed, CeedOperatorCreate(ceed, qf_ifunc, NULL, NULL, &sub_op_ifunc_));
265   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ifunc_, "q", elem_restr_q, basis_q, CEED_VECTOR_ACTIVE));
266   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ifunc_, "Grad_q", elem_restr_q, basis_q, CEED_VECTOR_ACTIVE));
267   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ifunc_, "surface qdata", elem_restr_qd, CEED_BASIS_NONE, q_data));
268   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ifunc_, "x", elem_restr_x, basis_x, honee->x_coord));
269   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ifunc_, "v", elem_restr_q, basis_q, CEED_VECTOR_ACTIVE));
270   if (jac_data_size > 0) PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ifunc_, "surface jacobian data", elem_restr_jac, CEED_BASIS_NONE, jac_data));
271 
272   PetscCallCeed(ceed, CeedCompositeOperatorAddSub(op_ifunc, sub_op_ifunc_));
273   *sub_op_ifunc = sub_op_ifunc_;
274 
275   PetscCallCeed(ceed, CeedVectorDestroy(&q_data));
276   PetscCallCeed(ceed, CeedBasisDestroy(&basis_q));
277   PetscCallCeed(ceed, CeedBasisDestroy(&basis_x));
278   PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_qd));
279   PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_q));
280   PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_x));
281   if (jac_data_size > 0) {
282     PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_jac));
283     PetscCallCeed(ceed, CeedVectorDestroy(&jac_data));
284   }
285   PetscFunctionReturn(PETSC_SUCCESS);
286 }
287 
288 /**
289   @brief Setups and adds IJacobian operator to given composite operator
290 
291   The field data (element restriction, vector, etc) is read from `sub_op_ifunc` and used for the IJacobian.
292 
293   @param[in]     bc_def       `BCDefinition` for the boundary condition IJacobian
294   @param[out]    sub_op_ifunc IFunction operator corresponding to this IJacobian operator.
295   @param[in]     domain_label `DMLabel` for the face orientation label
296   @param[in]     label_value  Orientation value
297   @param[in]     qf_ijac      QFunction for the IJacobian
298   @param[in,out] op_ijac      Composite operator to be added to
299 **/
300 PetscErrorCode HoneeBCAddIJacobianOp(BCDefinition bc_def, CeedOperator sub_op_ifunc, DMLabel domain_label, PetscInt label_value,
301                                      CeedQFunction qf_ijac, CeedOperator op_ijac) {
302   Honee               honee;
303   Ceed                ceed;
304   DM                  dm, dm_coord;
305   HoneeBCStruct       honee_bc;
306   PetscInt            dim, height = 1, num_comp_x, num_comp_q;
307   CeedInt             q_data_size;
308   CeedVector          q_data, jac_data;
309   CeedElemRestriction elem_restr_qd, elem_restr_q, elem_restr_x, elem_restr_jac;
310   CeedOperator        sub_op_ijac;
311   CeedBasis           basis_x, basis_q;
312 
313   PetscFunctionBeginUser;
314   PetscCall(BCDefinitionGetContext(bc_def, &honee_bc));
315   if (honee_bc->jac_data_size_sur == 0) PetscFunctionReturn(PETSC_SUCCESS);
316   PetscCall(BCDefinitionGetDM(bc_def, &dm));
317   honee = honee_bc->honee;
318   ceed  = honee_bc->honee->ceed;
319 
320   PetscCall(DMGetDimension(dm, &dim));
321   PetscCall(QDataBoundaryGetNumComponents(dm, &q_data_size));
322   PetscCall(DMGetCoordinateNumComps(dm, &num_comp_x));
323 
324   {
325     PetscSection section;
326 
327     PetscCall(DMGetLocalSection(dm, &section));
328     PetscCall(PetscSectionGetFieldComponents(section, bc_def->dm_field, &num_comp_q));
329   }
330 
331   PetscCall(DMGetCoordinateDM(dm, &dm_coord));
332   PetscCall(CreateBasisFromPlex(ceed, dm, domain_label, label_value, height, bc_def->dm_field, &basis_q));
333   PetscCall(CreateBasisFromPlex(ceed, dm_coord, domain_label, label_value, height, bc_def->dm_field, &basis_x));
334 
335   {  // Get restriction and basis from the RHS function
336     CeedOperatorField op_field;
337 
338     PetscCallCeed(ceed, CeedOperatorGetFieldByName(sub_op_ifunc, "q", &op_field));
339     PetscCallCeed(ceed, CeedOperatorFieldGetElemRestriction(op_field, &elem_restr_q));
340 
341     PetscCallCeed(ceed, CeedOperatorGetFieldByName(sub_op_ifunc, "x", &op_field));
342     PetscCallCeed(ceed, CeedOperatorFieldGetElemRestriction(op_field, &elem_restr_x));
343 
344     PetscCallCeed(ceed, CeedOperatorGetFieldByName(sub_op_ifunc, "surface jacobian data", &op_field));
345     PetscCallCeed(ceed, CeedOperatorFieldGetData(op_field, NULL, &elem_restr_jac, NULL, &jac_data));
346   }
347 
348   PetscCall(QDataBoundaryGet(ceed, dm, domain_label, label_value, elem_restr_x, basis_x, honee->x_coord, &elem_restr_qd, &q_data, &q_data_size));
349 
350   PetscCallCeed(ceed, CeedOperatorCreate(ceed, qf_ijac, NULL, NULL, &sub_op_ijac));
351   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ijac, "dq", elem_restr_q, basis_q, CEED_VECTOR_ACTIVE));
352   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ijac, "Grad_dq", elem_restr_q, basis_q, CEED_VECTOR_ACTIVE));
353   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ijac, "surface qdata", elem_restr_qd, CEED_BASIS_NONE, q_data));
354   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ijac, "x", elem_restr_x, basis_x, honee->x_coord));
355   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ijac, "surface jacobian data", elem_restr_jac, CEED_BASIS_NONE, jac_data));
356   PetscCallCeed(ceed, CeedOperatorSetField(sub_op_ijac, "v", elem_restr_q, basis_q, CEED_VECTOR_ACTIVE));
357 
358   PetscCallCeed(ceed, CeedCompositeOperatorAddSub(op_ijac, sub_op_ijac));
359 
360   PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_q));
361   PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_x));
362   PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_jac));
363   PetscCallCeed(ceed, CeedVectorDestroy(&jac_data));
364   PetscCallCeed(ceed, CeedVectorDestroy(&q_data));
365   PetscCallCeed(ceed, CeedElemRestrictionDestroy(&elem_restr_qd));
366   PetscCallCeed(ceed, CeedBasisDestroy(&basis_q));
367   PetscCallCeed(ceed, CeedBasisDestroy(&basis_x));
368   PetscCallCeed(ceed, CeedOperatorDestroy(&sub_op_ijac));
369   PetscFunctionReturn(PETSC_SUCCESS);
370 }
371