Beispiel #1
0
Gear::Gear( stk::mesh::MetaData & S ,
            const std::string & name ,
            const GearFields & gear_fields ,
            const double center[] ,
            const double rad_min ,
            const double rad_max ,
            const size_t rad_num ,
            const double z_min ,
            const double z_max ,
            const size_t z_num ,
            const size_t angle_num ,
            const int      turn_direction )
  : m_mesh_meta_data( S ),
    m_mesh( NULL ),
    m_gear( S.declare_part(std::string("Gear_").append(name), stk::topology::ELEMENT_RANK) ),
    m_surf( S.declare_part(std::string("Surf_").append(name), m_mesh_meta_data.side_rank()) ),
    m_gear_coord( gear_fields.gear_coord ),
    m_model_coord(gear_fields.model_coord )
{
  enum { SpatialDimension = GearFields::SpatialDimension };

  stk::io::put_io_part_attribute(m_gear);
  stk::io::put_io_part_attribute(m_surf);

  stk::mesh::set_topology( m_gear, stk::topology::HEX_8 );
  stk::mesh::set_topology( m_surf, stk::topology::QUAD_8 );

  // Meshing parameters for this gear:

  const double TWO_PI = 2.0 * acos( static_cast<double>(-1.0) );

  m_center[0] = center[0] ;
  m_center[1] = center[1] ;
  m_center[2] = center[2] ;

  m_z_min     = z_min ;
  m_z_max     = z_max ;
  m_z_inc     = (z_max - z_min) / static_cast<double>(z_num - 1);

  m_rad_min   = rad_min ;
  m_rad_max   = rad_max ;
  m_rad_inc   = (rad_max - rad_min) / static_cast<double>(rad_num - 1);

  m_ang_inc   = TWO_PI / static_cast<double>(angle_num) ;

  m_rad_num   = rad_num ;
  m_z_num     = z_num ;
  m_angle_num = angle_num ;
  m_turn_dir  = turn_direction ;
}
// ========================================================================
void process_surface_entity(Ioss::SideSet *sset, stk::mesh::MetaData &meta,
                            stk::mesh::EntityRank sset_rank)
{
    assert(sset->type() == Ioss::SIDESET);
    Ioss::SideSet *fs = dynamic_cast<Ioss::SideSet *>(sset);
    assert(fs != NULL);
    const Ioss::SideBlockContainer& blocks = fs->get_side_blocks();
    stk::io::default_part_processing(blocks, meta, sset_rank);

    stk::mesh::Part* const fs_part = meta.get_part(sset->name());
    STKIORequire(fs_part != NULL);

    stk::mesh::Field<double, stk::mesh::ElementNode> *distribution_factors_field = NULL;
    bool surface_df_defined = false; // Has the surface df field been defined yet?


    int block_count = sset->block_count();
    for (int i=0; i < block_count; i++) {
        Ioss::SideBlock *side_block = sset->get_block(i);
        if (stk::io::include_entity(side_block)) {
            stk::mesh::Part * const side_block_part = meta.get_part(side_block->name());
            STKIORequire(side_block_part != NULL);
            meta.declare_part_subset(*fs_part, *side_block_part);

            const stk::mesh::EntityRank part_rank = side_block_part->primary_entity_rank();

            if (side_block->field_exists("distribution_factors")) {
                if (!surface_df_defined) {
                    std::string field_name = sset->name() + "_distribution_factors";
                    distribution_factors_field =
                        &meta.declare_field<stk::mesh::Field<double, stk::mesh::ElementNode> >(static_cast<stk::topology::rank_t>(part_rank), field_name);
                    stk::io::set_distribution_factor_field(*fs_part, *distribution_factors_field);
                    surface_df_defined = true;
                }
                stk::io::set_distribution_factor_field(*side_block_part, *distribution_factors_field);
                int side_node_count = side_block->topology()->number_nodes();
                stk::mesh::put_field(*distribution_factors_field,
                                     *side_block_part, side_node_count);
            }

            /** \todo IMPLEMENT truly handle fields... For this case we
             * are just defining a field for each transient field that is
             * present in the mesh...
             */
            stk::io::define_io_fields(side_block, Ioss::Field::TRANSIENT,
                                      *side_block_part,
                                      part_rank);
        }
    }
}
Beispiel #3
0
void clone_part_to_other_meta(const stk::mesh::Part &oldPart, stk::mesh::MetaData &newMeta)
{
    stk::mesh::Part *newPart = create_new_part(oldPart.name(), oldPart.topology(), newMeta);
    newMeta.set_part_id(*newPart, oldPart.id());
    copy_part_supersets(oldPart, *newPart, newMeta);
    copy_io_part_attributes(oldPart, *newPart);
}
void createNodalVectorFields(stk::mesh::MetaData& meshMetaData)
{
    createNodalVectorField(meshMetaData, "disp");
    createNodalVectorField(meshMetaData, "vel");
    createNodalVectorField(meshMetaData, "acc");
    createNodalVectorField(meshMetaData, "force");
    meshMetaData.commit();
}
Beispiel #5
0
void copy_fields(const stk::mesh::MetaData &oldMeta, stk::mesh::MetaData &newMeta)
{
    const stk::mesh::FieldVector &fields = oldMeta.get_fields();
    for(size_t i = 0; i < fields.size(); i++)
    {
        stk::mesh::FieldBase* newField = clone_field(*fields[i], newMeta);
        copy_field_restrictions(*fields[i], newMeta, newField);
    }
}
Beispiel #6
0
GearFields::GearFields( stk::mesh::MetaData & S )
  : gear_coord(          S.declare_field<CylindricalField>(stk::topology::NODE_RANK, std::string("gear_coordinates") ) ),
    model_coord(         S.declare_field<CartesianField>(stk::topology::NODE_RANK, std::string("coordinates") ) )
{
  const stk::mesh::Part & universe = S.universal_part();

  stk::mesh::put_field( gear_coord    , universe , SpatialDimension );
  stk::mesh::put_field( model_coord   , universe , SpatialDimension );
}
// ========================================================================
void process_nodeblocks(Ioss::Region &region, stk::mesh::MetaData &meta)
{
    const Ioss::NodeBlockContainer& node_blocks = region.get_node_blocks();
    assert(node_blocks.size() == 1);

    Ioss::NodeBlock *nb = node_blocks[0];

    assert(nb->field_exists("mesh_model_coordinates"));
    Ioss::Field coordinates = nb->get_field("mesh_model_coordinates");
    int spatial_dim = coordinates.transformed_storage()->component_count();

    stk::mesh::Field<double,stk::mesh::Cartesian> & coord_field =
        meta.declare_field<stk::mesh::Field<double,stk::mesh::Cartesian> >(stk::topology::NODE_RANK, "coordinates");

    stk::mesh::put_field( coord_field, meta.universal_part(),
                          spatial_dim);

    /** \todo IMPLEMENT truly handle fields... For this case we are
     * just defining a field for each transient field that is present
     * in the mesh...
     */
    stk::io::define_io_fields(nb, Ioss::Field::TRANSIENT, meta.universal_part(),stk::topology::NODE_RANK);
}
Beispiel #8
0
void copy_field_restrictions(const stk::mesh::FieldBase& field, stk::mesh::MetaData& newMeta, stk::mesh::FieldBase* newField)
{
    const stk::mesh::FieldRestrictionVector& oldRestrictions = field.restrictions();
    for(const stk::mesh::FieldRestriction& res : oldRestrictions)
    {
//        stk::mesh::Selector selectNewParts = res.selector().clone_for_different_mesh(newMeta);
        stk::mesh::Selector selectNewParts = res.selector(); // WRONG: needs to use above function
        newMeta.declare_field_restriction(*newField,
                                          selectNewParts,
                                          res.num_scalars_per_entity(),
                                          res.dimension(),
                                          field.get_initial_value());
    }
}
// ========================================================================
void process_sidesets(Ioss::Region &region, stk::mesh::MetaData &meta)
{
    const stk::mesh::EntityRank side_rank = meta.side_rank();

    const Ioss::SideSetContainer& side_sets = region.get_sidesets();
    stk::io::default_part_processing(side_sets, meta, side_rank);

    for(Ioss::SideSetContainer::const_iterator it = side_sets.begin();
            it != side_sets.end(); ++it) {
        Ioss::SideSet *entity = *it;

        if (stk::io::include_entity(entity)) {
            process_surface_entity(entity, meta, side_rank);
        }
    }
}
Beispiel #10
0
void heterogeneous_mesh_meta_data(
  stk::mesh::MetaData & meta_data ,
  const VectorFieldType & node_coord )
{
  stk::mesh::Part & universal        = meta_data.universal_part();
  stk::io::put_io_part_attribute(meta_data.declare_part_with_topology("hexes", stk::topology::HEX_8));
  stk::io::put_io_part_attribute(meta_data.declare_part_with_topology("wedges", stk::topology::WEDGE_6));
  stk::io::put_io_part_attribute(meta_data.declare_part_with_topology("tets", stk::topology::TET_4));
  stk::io::put_io_part_attribute(meta_data.declare_part_with_topology("pyramids", stk::topology::PYRAMID_5));
  stk::io::put_io_part_attribute(meta_data.declare_part_with_topology("quad_shells", stk::topology::SHELL_QUAD_4));
  stk::io::put_io_part_attribute(meta_data.declare_part_with_topology("tri_shells", stk::topology::SHELL_TRI_3));
  
  const stk::mesh::FieldBase::Restriction & res =
    stk::mesh::find_restriction(node_coord, stk::topology::NODE_RANK , universal );

  if ( res.num_scalars_per_entity() != 3 ) {
    std::ostringstream msg ;
    msg << "stk_mesh/unit_tests/heterogenous_mesh_meta_data FAILED, coordinate dimension must be 3 != " << res.num_scalars_per_entity() ;
    throw std::runtime_error( msg.str() );
  }
}
// ========================================================================
void process_nodesets(Ioss::Region &region, stk::mesh::MetaData &meta)
{
    const Ioss::NodeSetContainer& node_sets = region.get_nodesets();
    stk::io::default_part_processing(node_sets, meta, stk::topology::NODE_RANK);

    /** \todo REFACTOR should "distribution_factor" be a default field
     * that is automatically declared on all objects that it exists
     * on as is done in current framework?
     */
    stk::mesh::Field<double> & distribution_factors_field =
        meta.declare_field<stk::mesh::Field<double> >(stk::topology::NODE_RANK, "distribution_factors");

    /** \todo REFACTOR How to associate distribution_factors field
     * with the nodeset part if a node is a member of multiple
     * nodesets
     */

    for(Ioss::NodeSetContainer::const_iterator it = node_sets.begin();
            it != node_sets.end(); ++it) {
        Ioss::NodeSet *entity = *it;

        if (stk::io::include_entity(entity)) {
            stk::mesh::Part* const part = meta.get_part(entity->name());
            STKIORequire(part != NULL);
            STKIORequire(entity->field_exists("distribution_factors"));

            stk::mesh::put_field(distribution_factors_field, *part);

            /** \todo IMPLEMENT truly handle fields... For this case we
             * are just defining a field for each transient field that is
             * present in the mesh...
             */
            stk::io::define_io_fields(entity, Ioss::Field::TRANSIENT,
                                      *part,
                                      part->primary_entity_rank() ) ;
        }
    }
}
// ========================================================================
void process_elementblocks(Ioss::Region &region, stk::mesh::MetaData &meta)
{
    const stk::mesh::EntityRank element_rank = stk::topology::ELEMENT_RANK;

    const Ioss::ElementBlockContainer& elem_blocks = region.get_element_blocks();
    stk::io::default_part_processing(elem_blocks, meta, element_rank);

    // Parts were created above, now handle element block specific
    // information (topology, attributes, ...);
    for(Ioss::ElementBlockContainer::const_iterator it = elem_blocks.begin();
            it != elem_blocks.end(); ++it) {
        Ioss::ElementBlock *entity = *it;

        if (stk::io::include_entity(entity)) {
            stk::mesh::Part* const part = meta.get_part(entity->name());
            STKIORequire(part != NULL);

            const stk::mesh::EntityRank part_rank = part->primary_entity_rank();

            // Element Block attributes (if any)...
            /** \todo IMPLEMENT truly handle attribute fields... For this
             * case we are just defining a field for each attribute field
             * that is present in the mesh...
             */
            stk::io::define_io_fields(entity, Ioss::Field::ATTRIBUTE,
                                      *part,
                                      part_rank);

            /** \todo IMPLEMENT truly handle fields... For this case we
             * are just defining a field for each transient field that is
             * present in the mesh...
             */
            stk::io::define_io_fields(entity, Ioss::Field::TRANSIENT,
                                      *part,
                                      part_rank);
        }
    }
}
Beispiel #13
0
PromotedElementIO::PromotedElementIO(
  const ElementDescription& elem,
  const stk::mesh::MetaData& metaData,
  stk::mesh::BulkData& bulkData,
  const stk::mesh::PartVector& baseParts,
  const std::string& fileName,
  const VectorFieldType& coordField
) : elem_(elem),
    metaData_(metaData),
    bulkData_(bulkData),
    fileName_(fileName),
    coordinates_(coordField),
    nDim_(metaData.spatial_dimension())
{
  Ioss::Init::Initializer init_db;

  Ioss::PropertyManager properties;

  Ioss::Property intSizeAPI("INTEGER_SIZE_API", 8);
  properties.add(intSizeAPI);

  Ioss::Property intSizeDB("INTEGER_SIZE_DB", 8);
  properties.add(intSizeDB);

  databaseIO = Ioss::IOFactory::create(
        "exodus",
        fileName_,
        Ioss::WRITE_RESULTS,
        bulkData_.parallel(),
        properties
  );
  ThrowRequire(databaseIO != nullptr && databaseIO->ok(true));

  output_ = make_unique<Ioss::Region>(databaseIO, "HighOrderOutput"); //sink for databaseIO
  ThrowRequire(output_ != nullptr);

  const stk::mesh::BucketVector& elem_buckets = bulkData_.get_buckets(
    stk::topology::ELEM_RANK, stk::mesh::selectUnion(baseParts));

  size_t numSubElems = num_sub_elements(nDim_, elem_buckets, elem_.polyOrder);
  std::vector<stk::mesh::EntityId> subElemIds;

  bulkData.generate_new_ids(stk::topology::ELEM_RANK,  numSubElems,  subElemIds);
  ThrowRequire(subElemIds.size() == numSubElems);

  superElemParts_ = super_elem_part_vector(baseParts);
  ThrowRequireMsg(part_vector_is_valid_and_nonempty(superElemParts_),
    "Not all element parts have a super-element mirror");

  output_->begin_mode(Ioss::STATE_DEFINE_MODEL);
  write_node_block_definitions(superElemParts_);
  write_elem_block_definitions(superElemParts_);
  write_sideset_definitions(baseParts);
  output_->end_mode(Ioss::STATE_DEFINE_MODEL);

  output_->begin_mode(Ioss::STATE_MODEL);
  write_coordinate_list(superElemParts_);
  write_element_connectivity(superElemParts_, subElemIds);
  write_sideset_connectivity(baseParts);
  output_->end_mode(Ioss::STATE_MODEL);
}
Beispiel #14
0
void copy_part_supersets(const stk::mesh::Part &oldPart, stk::mesh::Part &newPart, stk::mesh::MetaData &newMeta)
{
    stk::mesh::OrdinalVector oldSupersets = get_part_supersets(oldPart);
    for(stk::mesh::PartOrdinal partOrd : oldSupersets)
        newMeta.declare_part_subset(newMeta.get_part(partOrd), newPart);
}
Beispiel #15
0
void copy_parts(const stk::mesh::MetaData &oldMeta, stk::mesh::MetaData &newMeta)
{
    const stk::mesh::PartVector &allParts = oldMeta.get_mesh_parts();
    for(size_t i = 0; i < allParts.size(); i++)
        clone_part_to_other_meta(*allParts[i], newMeta);
}
void make_small_hybrid_mesh(stk::mesh::MetaData &meta, stk::mesh::BulkData &mesh,
                            bool user_attempt_no_induce = false, bool user_parts_force_no_induce = true)
{
    stk::ParallelMachine pm = MPI_COMM_WORLD;
    int p_size = stk::parallel_machine_size(pm);

    if(p_size > 2)
    {
        return;
    }

    const unsigned p_rank = mesh.parallel_rank();

    stk::mesh::Part * hexPart = &meta.get_topology_root_part(stk::topology::HEX_8);
    stk::mesh::Part * pyrPart = &meta.get_topology_root_part(stk::topology::PYRAMID_5);
    stk::mesh::Part * tetPart = &meta.get_topology_root_part(stk::topology::TET_4);

    if (user_attempt_no_induce)
    {
        hexPart = &meta.declare_part_with_topology("my_hex_part",stk::topology::HEX_8, user_parts_force_no_induce);
        pyrPart = &meta.declare_part_with_topology("my_pyr_part",stk::topology::PYRAMID_5, user_parts_force_no_induce);
        tetPart = &meta.declare_part_with_topology("my_tet_part",stk::topology::TET_4, user_parts_force_no_induce);

        EXPECT_EQ(user_parts_force_no_induce, hexPart->force_no_induce());
        EXPECT_EQ(user_parts_force_no_induce, pyrPart->force_no_induce());
        EXPECT_EQ(user_parts_force_no_induce, tetPart->force_no_induce());
    }

    meta.commit();

    const size_t numHex = 1;
    stk::mesh::EntityIdVector hexNodeIDs[] {
        { 1, 2, 3, 4, 5, 6, 7, 8 }
    };
    stk::mesh::EntityId hexElemIDs[] = { 1 };

    const size_t numPyr = 1;
    stk::mesh::EntityIdVector pyrNodeIDs[] {
        { 5, 6, 7, 8, 9 }
    };
    stk::mesh::EntityId pyrElemIDs[] = { 2 };

    const size_t numTet = 4;
    stk::mesh::EntityIdVector tetNodeIDs[] {
        { 7, 8, 9, 12 },
        { 6, 9, 10, 7 },
        { 7, 9, 10, 12 },
        { 7, 12, 10, 11 }
    };
    stk::mesh::EntityId tetElemIDs[] = { 3, 4, 5, 6 };

    // list of triplets: (owner-proc, shared-nodeID, sharing-proc)
    std::vector< std::vector<unsigned> > shared_nodeIDs_and_procs
    {
        { 0, 5, 1 },  // proc 0
        { 0, 6, 1 },
        { 0, 7, 1 },
        { 0, 8, 1 },
        { 1, 5, 0 },  // proc 1
        { 1, 6, 0 },
        { 1, 7, 0 },
        { 1, 8, 0 }
    };

    mesh.modification_begin();

    if (0 == p_rank) {
        for (size_t i = 0; i < numHex; ++i) {
          stk::mesh::declare_element(mesh, *hexPart, hexElemIDs[i], hexNodeIDs[i]);
        }
    }
    if ( (1 == p_rank) || (1 == p_size) )  { // setup the pyramids/tets for either np 2 or serial
        for (size_t i = 0; i < numPyr; ++i) {
          stk::mesh::declare_element(mesh, *pyrPart, pyrElemIDs[i], pyrNodeIDs[i]);
        }
        for (size_t i = 0; i < numTet; ++i) {
          stk::mesh::declare_element(mesh, *tetPart, tetElemIDs[i], tetNodeIDs[i]);
        }
    }

    if (p_size > 1)
    {
        for (size_t nodeIdx = 0, end = shared_nodeIDs_and_procs.size(); nodeIdx < end; ++nodeIdx) {
            if (p_rank == shared_nodeIDs_and_procs[nodeIdx][0]) {
                stk::mesh::EntityId nodeID = shared_nodeIDs_and_procs[nodeIdx][1];
                int sharingProc = shared_nodeIDs_and_procs[nodeIdx][2];
                stk::mesh::Entity node = mesh.get_entity(stk::topology::NODE_RANK, nodeID);
                mesh.add_node_sharing(node, sharingProc);
            }
        }
    }

    mesh.modification_end();
}
void stk::app::use_case_14_declare_fields(Fields &fields, stk::mesh::MetaData &meta_data)
{
  // Nodal vector fields
  fields.model_coordinates     = &declare_vector_field_on_all_nodes(meta_data, "coordinates", SpatialDim);
  fields.coordinates_field     = &declare_vector_field_on_all_nodes(meta_data, "current_coordinates", SpatialDim);
  fields.velocity_field        = &declare_vector_field_on_all_nodes(meta_data, "velocity",    SpatialDim);
  fields.fint_field            = &declare_vector_field_on_all_nodes(meta_data, "force_internal", SpatialDim);

  // Element vector fields:
  fields.Vorticity             = &declare_vector_field_on_all_elements(meta_data, "Vorticity" , SpatialDim);

  // Element scalar fields:
  fields.Shear_Modulus         = &declare_scalar_field_on_all_elements(meta_data, "shear_modulus" );
  fields.Dilatational_Modulus  = &declare_scalar_field_on_all_elements(meta_data, "dilatational_modulus");
  fields.Material_eff_twomu    = &declare_scalar_field_on_all_elements(meta_data, "material_effictive_two_mu");
  fields.Material_eff_bulk_mod = &declare_scalar_field_on_all_elements(meta_data, "material_effective_bulk_moduli");
  fields.Midstep_volume        = &declare_scalar_field_on_all_elements(meta_data, "mid_step_volume");
  fields.Element_time_step     = &declare_scalar_field_on_all_elements(meta_data, "element_time_step");
  fields.Element_mass          = &declare_scalar_field_on_all_elements(meta_data, "element_mass");
  fields.Hourglass_energy      = &declare_scalar_field_on_all_elements(meta_data, "hourglass_energy");
  fields.Internal_energy       = &declare_scalar_field_on_all_elements(meta_data, "internal_energy");

  // Element symmetric tensor fields:
  fields.Stretch               = &declare_symmetric_tensor_field_on_all_elements(meta_data, "stretch",    6 );
  fields.StrainRate            = &declare_symmetric_tensor_field_on_all_elements(meta_data, "StrainRate", 6 );
  fields.RotatedStress         = &declare_symmetric_tensor_field_on_all_elements(meta_data, "RotatedStress", 6 );

  //--------------------------------
  // The multi-state fields don't have the 'declare and put' convenience functions (yet)
  //
  //  For clarity declare a integer to used for the number of states arguments.
  //
  const unsigned two_states = 2 ;

  // Element two state symmetric tensor field, on all elements (as two function calls):
  fields.StressNew = &meta_data.declare_field< SymmetricTensorField >( "Stress" , two_states );
  put_field_on_all_elements( *fields.StressNew , 6 );

  // Element two state full tensor field on all elements (as nested function calls):
  fields.RotationNew = &put_field_on_all_elements( meta_data.declare_field< FullTensorField >("Rotation", two_states ) , 9 );

  //--------------------------------
  // Hourglass fields, these don't have the convenience functions for 'declare and put'

  fields.HourglassResistanceNew =
    &put_field_on_all_elements( meta_data.declare_field< HourglassArrayField >("HourglassResistance" , two_states ) , 12 );

  fields.MidHourglassOp =
    &put_field_on_all_elements( meta_data.declare_field< HourglassOpField >("mid_hourglass_operator") , 32 );

  //--------------------------------
  // Declare aggressive "gather" fields which are an array of
  // pointers to the element's nodes' coordinate, velocity, and
  // internal force field data.
  //
  // The declarations specify element fields of the following form:
  //
  //     double * coord_gather   [ nodes_per_element ]
  //     double * velocity_gather[ nodes_per_element ]
  //
  // where
  //
  //     coord_gather[i]    == field_data( coordinates_field , element_node[i] )
  //     velocity_gather[i] == field_data( velocity ,          element_node[i] )
  //
  // The number of nodes per element could vary, so the field is put
  // on each element block with a size of the number of nodes per
  // element in that element block.

  fields.coord_gather    = &declare_element_node_pointer_field( meta_data , "coord_gather" ,    *fields.coordinates_field );
  fields.velocity_gather = &declare_element_node_pointer_field( meta_data , "velocity_gather" , *fields.velocity_field );

  //----------------------------------
  // Declare an element field with one value per connected node in
  // which to temporarily store nodal force that is calculated by
  // the internal force algorithm.

  fields.force_new_field = &meta_data.declare_field<ElementNodeVectorField>( "force_new_field" , 1 /* 1 state */ );

  // All parts of the meta data:
  {
    const mesh::PartVector & all_parts = meta_data.get_parts();
    for (mesh::PartVector::const_iterator i = all_parts.begin();
	 i != all_parts.end(); ++i) {
      mesh::Part * const part = *i ;
      if ( part->primary_entity_rank() == get_element_rank(meta_data) ) {
	put_field_on_elements(*fields.coord_gather,    *part, shards::Hexahedron<> ::node_count );
	put_field_on_elements(*fields.velocity_gather, *part, shards::Hexahedron<> ::node_count );
	put_field_on_elements(*fields.force_new_field, *part, SpatialDim, shards::Hexahedron<>::node_count );
      }
    }
  }
}