# IntactSDK 1.0.5 User's Manual ## Overview This document provides a comprehensive guide to using IntactSDK, a C++ interface for Intact.Simulation. IntactSDK allows users to define geometries, material properties, components, assemblies, and boundary conditions. Users can specify units, create simulation scenarios, execute the simulation, and query the results. The simulations can be run with different solver types and the results can be written to a file for visualization. The document also provides code examples and snippets for each step of the simulation process. Key features and capabilities - Physics Support - Stress - Modal - Thermal - Geometry Support - Brep: Surface mesh ## Prerequisites IntactSDK requires the Intel oneAPI Math Kernel Library. Specifically, at runtime, it requires `libiomp5md.dll`, which can be obtained from [Intel's redistributable downloads](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl-download.html?operatingsystem=windows&windows-install=offline). ## Getting Started The following is simple example, broken down step-by-step, of running a structural simulation. The entire [source file](../wrappers/cpp/examples/cantilever_beam.cpp) and the three required geometry files are available: [beam geometry](../wrappers/python/doc/examples/beam.stl), [restraint geometry](../wrappers/python/doc/examples/restraint.stl), [load geometry](../wrappers/python/doc/examples/load.stl). - Include the Intact header ```cpp #include "Intact.hpp" ``` - Create geometry ```cpp auto beam_geometry = Intact::MeshModel("beam.stl"); beam_geometry.instance_id = "simple_cantilever"; beam_geometry.refine(0.02); // refine stl for smoother visualization ``` - Create material (material properties are in MKS unit system) ```cpp // Create steel material auto material = std::make_shared(); material->density = 7800.0; // kg/m^3 material->poisson_ratio = 0.3; material->youngs_modulus = 2.1e11; // Pa ``` - Create restraints and loads ```cpp // Create a fixed boundary condition at one end of the beam auto fixed_boundary = std::make_shared(); fixed_boundary->boundary = Intact::MeshModel("restraint.stl"); // Create a pressure load at the free end of the beam auto pressure_load = std::make_shared(); pressure_load->units = Intact::UnitSystem::MeterKilogramSecond; pressure_load->magnitude = 1000; // Applying pressure in Pascals pressure_load->boundary = Intact::MeshModel("load.stl"); ``` - Setup simulation and solve ```cpp // Setup the simulation scenario descriptor Intact::LinearElasticScenarioDescriptor scenario; scenario.boundary_conditions = {fixed_boundary, pressure_load}; scenario.metadata.resolution = 10000; scenario.metadata.units = Intact::UnitSystem::MeterKilogramSecond; scenario.materials = {{"Steel", material}}; // Associate the material with the model auto component = Intact::MaterialDomain(beam_geometry, "Steel", scenario); Intact::Assembly assembly = {component}; // Create the simulator and solve Intact::StressSimulator simulator(assembly, scenario); simulator.solve(); ``` - Query result and create output file to visualize ```cpp // Query results for stress distribution Intact::QueryResult results(component); Intact::FieldQuery stress_query(Intact::FieldType::VonMisesStress); simulator.sample(stress_query, results); // Write results to a file (unit system is saved as a metadata in the vtu file) results.writeVTK("cantilever_beam_results.vtu", Intact::UnitSystem::MeterKilogramSecond); ``` ## How To ### Creating Simulation Input #### Units The default Unit System is **MKS** or `MeterKilogramSecond`. We allow the following customizations: - Specify custom unit for geometry through the `Scenario` unit (default **MKS**) - Specify custom unit for loads (default **MKS**) - Specify custom unit for materials (default **MKS**) | Unit System Keywords | Mass | Force | Stress | Length | | --- | --- | --- | --- | --- | | MeterKilogramSecond | kg | N | Pa | m | | CentimeterGramSecond | g | dyne | dyne/cm² | cm | | MillimeterMegagramSecond | Mg | N | MPa | mm | | FootPoundSecond | slug | lbf | lbf/ft² | ft | | InchPoundSecond | lbf s²/in | lbf | psi | in | #### Geometry Geometry is specified through a `Model` object. `MeshModel` created from a surface mesh can be defined in two ways: 1. By specifying a `filename` to define mesh from a file (STL, PLY) ```cpp auto mesh_geometry = Intact::MeshModel("beam.stl"); mesh_geometry.instance_id = "beam"; ``` 2. By constructing mesh face-by-face ```cpp // define an empty mesh auto mesh_geometry = Intact::MeshModel(); mesh_geometry.instance_id = "fixed_boundary"; // add vertices to the mesh and get the vertex id as output auto v0 = mesh_geometry.addVertex({0.0, 0.0, 0.0}); auto v1 = mesh_geometry.addVertex({1.0, 0.0, 0.0}); auto v2 = mesh_geometry.addVertex({0.0, 1.0, 0.0}); // add faces defined by the vertex id mesh_geometry.addFacet(v0, v1, v2); ``` Meshes can be refined, usually for finer interpolation of results during visualization, using the `MeshModel::refine` method with `refinement_level` argument. `refinement_level` is the largest allowed triangle edge as a ratio of the bounding box diagonal. ```cpp // mesh geometry created above is further refined mesh_geometry.refine(0.02); ``` #### Structural Material Properties - `IsotropicMaterialDescriptor` to create an **Isotropic** material. (Unit system is `MeterKilogramSecond`) - `density` is the material density - `youngs_modulus` is the elastic modulus - `poisson_ratio` is the Poisson ratio ```cpp auto steel_structural = std::make_shared(); // Set the density to 7845 kg/m^3, modulus to 200 GPa and poison ratio to 0.29 steel_structural->density = 7845 steel_structural->youngs_modulus = 200e9 steel_structural->poisson_ratio = 0.29 ``` - `OrthotropicMaterialDescriptor` to create an **Orthotropic** material. Orthotropic materials are often used to represent composites and wood, where the material properties along one axis are significantly different from the properties along the other axes. (Unit system is `MeterKilogramSecond`) - `density` is the material density - `Ex` is the elastic modulus in the material's x-direction - `Ey` is the elastic modulus in the material's y-direction - `Ez` is the elastic modulus in the material's z-direction - `Gxy` is the shear modulus in the xy plane - `Gxz` is the shear modulus in the xz plane - `Gyz` is the shear modulus in the yz plane - `vxy` is the poisson ratio in the xy plane - `vxz` is the poisson ratio in the xx plane - `vyz` is the poisson ratio in the yz plane - `transform` is the optional material transform to arbitrarily rotate the axes along which the material properties are defined. Specified as a list of 9 floating point numbers corresponding to a 3x3 matrix. Note the first 3 floating point numbers corresponds to the first row. ```cpp // Create an orthotropic material (Red Pine) auto material = std::make_shared(); material->density = 460.0; // kg/m³ material->Ex = 11.2e9; // Pa material->Ey = 492.8e6; // Pa material->Ez = 985.6e6; // Pa material->Gxy = 907.2e6; // Pa material->Gxz = 1.08e9; // Pa material->Gyz = 123.2e6; // Pa material->vxy = 0.315; material->vxz = 0.347; material->vyz = 0.308; material->transform = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0} // transform is optional ``` #### Thermal Material Properties - `ThermalMaterialDescriptor` to create a thermal material. (Unit system is `MeterKilogramSecond`) - `density` is the material density - `conductivity` is the conductivity of the material - `specific_heat` is the specific heat of the material ```cpp auto thermal_material = std::make_shared(); // Set the density, conductivity, and specific heat of the material thermal_material->density = 2700.0; // kg/m^3 thermal_material->conductivity = 170; // W/(m·K) thermal_material->specific_heat = 500; // J/(kg·K) ``` #### Component and Assembly - A **Component** is created as a `MaterialDomain` and takes three inputs - A `Model` object (`MeshModel` specifically) - A material name - A `ScenarioDescriptor` object ```cpp auto bar1_structural = Intact::MaterialDomain(mesh_geometry, "Steel", structural_scenario); auto bar2_structural = Intact::MaterialDomain(mesh_geometry, "Red Pine", structural_scenario); ``` - **Assembly** is a `std::vector` of components that are **bonded** together ```cpp Intact::Assembly structural_assembly = {bar1_structural, bar2_structural}; ``` ### Boundary Conditions - **Restraints** - **Fixed Boundary** A "Fixed Boundary" restraint fixes the selected geometry in all directions. The fixed boundary only has one input: - the `boundary` surface to be fixed ```cpp auto fixed = std::make_shared(); // Set the geometry "restraint.ply" to be fixed fixed->boundary = Intact::MeshModel("restraint.ply"); ``` - **Fixed Vector** A "Fixed Vector" restraint allows for each direction to be optionally set to a specified displacement value, 0 being fixed and no value(`std::nullopt`) being un-restrained. Note that a structural problem must have all three directions restrained somewhere to be valid. Also, note that this boundary condition **doesn’t take custom units** and it is always in the units of the scenario metadata. A fixed vector requires four inputs: - the `boundary` surface to be fixed - the `x_value` to set the displacement for the x-axis direction (optional) - the `y_value` to set the displacement for the y-axis direction (optional) - the `z_value` to set the displacement for the z-axis direction (optional) ```cpp auto fixed_vector = std::make_shared(); // Set the geometry "restraint.stl" to be restrained fixed_vector->boundary = Intact::MeshModel("restraint.stl"); // Set the displacements in each direction fixed_vector->x_value = 0.2; // X-direction displacement of 0.2 m fixed_vector->y_value = std::nullopt; // un-restrained in the Y-direction at this surface fixed_vector->z_value = 0.0; // fixed Z-direction ``` - **Sliding Restraint** A "Sliding Restraint" allows for motion tangential to a specified surface, but fixes motion normal to the specified surface. A sliding restraint only has one input: - the `boundary` surface for the sliding restraint condition ```cpp auto sliding_boundary = std::make_shared(); // Set the geometry "restraint.stl" for the sliding restraint condition sliding_boundary->boundary = Intact::MeshModel("restraint.stl"); ``` - **Structural Loads (`TractionDescriptor`)** - **Vector Force** "Vector Force" load is a surface load applied to a face in a specified direction. An example of this load is pressing on the top of a book to push it across a table. A vector load requires four inputs: - the `boundary` surfaces where the load is applied - the `direction` vector of the force - the `UnitSystem` that applies to the magnitude - the `magnitude` of the force. ```cpp auto load = std::make_shared(); // Set the geometry "load.stl" the vector load is applied to load->boundary = Intact::MeshModel("load.stl"); // Set the vector load direction to be in the -Z direction with a magnitude of 100 lbf load->direction = {0, 0, -1}; load->units = Intact::UnitSystem::InchPoundSecond; load->magnitude = 100; // lbf ``` - **Torque** "Torque" load is a surface load that applies a twisting force around an axis. The direction of the torque is determined using the right-hand rule: using your right hand, point your thumb in the direction of the axis. A positive torque value applies a torque acting in the direction the fingers of your right hand would wrap around the axis. The torque load is applied among the load faces with a distribution that varies linearly from zero at the axis. A Torque load requires five inputs: - the `boundary` surfaces where the load is applied - the `axis` of rotation about which the torque acts - `origin` is the starting point of the axis of rotation - the `UnitSystem` that applies to the magnitude - `magnitude` of the torque. ```cpp auto torque_load = std::make_shared(); // Set the geometry "load.stl" the torque load is applied to torque_load->boundary = Intact::MeshModel("load.stl"); // Set the axis of the torque axis torque_load->origin = {10, 1, 1}; torque_load->axis = {1, 0, 0}; // Set the torque to be about the +X (right-hand rule) torque_load->units = Intact::UnitSystem::MeterKilogramSecond; torque_load->magnitude = 10; // Set the torque magnitude to 10 N*m ``` - **Pressure** A "Pressure" load is a surface load specified in terms of force per unit area. Positive pressures ‘push’ into the surface, and negative pressures ‘pull’. A Pressure Load requires three inputs: - the `boundary` surfaces where the load is applied - the `UnitSystem` that applies to the magnitude - the `magnitude` of the pressure. ```cpp auto pressure_load = std::make_shared(); // Set the geometry "load.stl" the pressure load is applied to pressure_load->boundary = Intact::MeshModel("load.stl"); // Set the pressure magnitude to 10 Pa pressure_load->units = Intact::UnitSystem::MeterKilogramSecond; pressure_load->magnitude = 10; ``` - **Bearing Force** A "Bearing Force" is a surface load applied to a (typically) cylindrical face to approximate the effects of a shaft pressing against the side of a hole. The applied force gets converted to a varying pressure distribution on the portion of the face experiencing compressive pressure. The pressure distribution is computed automatically to achieve the specified overall bearing force. A Bearing Force requires three inputs: - the `boundary` surfaces where the load is applied - the `direction` vector of the bearing force - the `UnitSystem` that applies to the magnitude - the `magnitude` of the force ```cpp auto bearing_load = std::make_sharedr(); // Set the geometry "load.stl" the bearing load is applied to bearing_load->boundary = Intact::MeshModel("load.stl"); // Set the loading direction to be in the -Z bearing_load->direction = {0, 0, -1}; // Set the magnitude of the load to 100 N bearing_load->units = Intact::UnitSystem::MeterKilogramSecond; bearing_load->magnitude = 100; ``` - **Thermal Loads** - **Fixed Boundary (Fixed Temperature)** A "Fixed Boundary" load fixes the selected geometry to a specified temperature when the `value` is specified and non-zero. The fixed boundary has two inputs: - the `boundary` surface to be fixed - the `value` of the temperature to fix at the boundary surface ```cpp auto fixed = std::make_shared(); // Set the geometry "fixed_temp.ply" to set a fixed temperaure of 320 K fixed_boundary->boundary = Intact::MeshModel("fixed_temp.ply"); fixed_boundary->value = 320; // Kelvin ``` - **Convection** A "Convection" load specifies the transfer of heat from a surrounding medium. A thermal convection boundary condition requires three inputs: - the `boundary` surface(s) where the convection is applied. - the heat transfer `coefficient` - the `environment_temperature` of the surrounding medium - the `units` (default MKS) ```cpp // Create an instance of ConvectionDescriptor auto convection = std::make_shared(); convection->units = Intact::UnitSystem::MeterKilogramSecond; // Set the geometry "face.ply" the convection is applied to convection->boundary = Intact::MeshModel("face.ply"); // Set the heat transfer coefficient to 25 W/m^2K and environment temperature convection->coefficient = 25; // W/m^2K convection->environment_temperature = 300; // Kelvin ``` - **Surface Flux** Surface "Thermal or Heat Flux" specifies the heat flow per unit of surface area. A surface thermal flux requires two inputs: - the `boundary` surface(s) where the flux is applied. - the `magnitude` of the heat flux - the `units` (default MKS) ```cpp // Create an instance of ConstantFluxDescriptor auto flux = std::make_shared(); flux->units = Intact::UnitSystem::MeterKilogramSecond; // Set the geometry "face.ply" the constant flux is applied to flux->boundary = Intact::MeshModel("face.ply"); // Set the flux magnitude to 500 W/m^2 flux->magnitude = 500; // W/m^2 ``` - **Body Loads/Internal Conditions** Add body load to the scenario as shown below. (see Scenario Setup section for more details) ```cpp scenario.internal_conditions = {rotational_load, gravity_load}; ``` - **Body Load (Linear Acceleration, Gravity)** Body loads comprise forces that are distributed over a solid volume. They are specified by entering the components of a linear acceleration vector field in which the body is immersed. The material in the body will tend to be pulled in the direction of the acceleration vector. The inputs to the body load are: - the `direction` vector of the acceleration field - the `UnitSystem` that applies to the magnitude - the `magnitude` of acceleration ```cpp // Example for a "gravity load" auto body_load = std::make_shared(); // Set the direction vector to be downward (-Z) body_load->direction = {0, 0, -1}; // Set the magnitude to 9.80655 m/s^2 body_load->units = Intact::UnitSystem::MeterKilogramSecond; body_load->magnitude = 9.80665; ``` - **Rotational Load** Rotational body loads simulate the effect of a body rotating around an axis. Two contributions are considered in a rotational body load: angular velocity and angular acceleration. The angular velocity term simulates the centrifugal effects that tend to throw a body's material away from the axis of rotation. The angular acceleration term simulates the effect of a rotational acceleration field around the axis of rotation. A positive angular acceleration tends to drag the body's material in the positive rotational direction according to the right-hand rule. A rotational body load has 4 inputs: - `origin` point for the axis of rotation - vector defining the `axis` of rotation - `angular_velocity` (in radians/sec) - `angular_acceleration` (in radians/sec²) ```cpp auto rotational_load = std::make_shared(); // Set the origin at the coordinate system origin rotational_load->origin = {0, 0, 0}; // Set the axis of rotation about the y-axis rotational_load->axis = {0, 1, 0}; // Set angular velocity to 10 rad/s and angular acceleration to 0.5 rad/s^2 rotational_load->angular_velocity = 10; rotational_load->angular_acceleration = 0.5; ``` - **Thermal Loads** - **Constant Heat** A "Constant Heat" or body heat flux load applies uniform heat generation over a specified volume. Constant heat flux has 2 inputs - the `instance_id` of the components which are producing heat flux - the `magnitude` of the body heat flux - the `units` (default MKS) ```cpp auto constant_heat = std::make_shared(); constant_heat->units = Intact::UnitSystem::MeterKilogramSecond; // Set the heat generation to -200,000 W for a beam component constant_heat->instance_id = "beam"; constant_heat->magnitude = -200000.0; // W ``` ### Simulation Scenario Setup and Solution - **Scenario Setup** using `ScenarioDescriptor` - **Linear Elasticity** scenario is created using `LinearElasticScenarioDescriptor`. It takes the following inputs: - `boundary_conditions`, a list of boundary conditions - `internal_conditions`, a list of internal conditions (body loads) - **Scenario Metadata,** `metadata`, a set of solver parameters *to control the accuracy and speed of the simulation*: - `resolution` is the target number of finite elements. An iterative process determines a `cell_size` that achieves approximately the specified number of elements. You can directly specify the `cell_size` instead of `resolution`. Note that decreasing `cell_size` can quickly result in large numbers of finite elements and long solve times. `resolution` is recommended for most cases. - `units` sets the unit of the scenario (geometry and results will be in this unit) **for example, a geometry in 'MKS' that is 6 m long would be 6 mm long when set to 'MMS'** - `basis_order` is the type of finite element used. The default is 1, which would be linear elements. 2 is for quadratic elements. - `solver_type` is the solver type used in the simulation with the following options: - `MKL_PardisoLDLT` (**default**) is the direct solver and is typically faster for lower resolution (< 200K cells on 32 GB memory), but gets slower and consumes more memory at higher resolutions - `AMGCL_amg_rigid_body` is the iterative solver which is typically faster at high resolution. ```cpp Intact::LinearElasticScenarioDescriptor scenario; scenario.materials = {{"Aluminum 6061-T6": material}}; scenario.boundary_conditions = {fixed_boundary, vector_load, torque_load}; scenario.internal_conditions = {body_load}; scenario.metadata.resolution = 10000; scenario.metadata.units = Intact::UnitSystem::MeterKilogramSecond; // Optional settings scenario.metadata.basis_order = 2; // 2 = quadratic elements scenario.metadata.solver_override = Intact::SolverType::AMGCL_amg_rigid_body; // iterative solver ``` - Similarly, **Modal** scenario is created using `ModalScenarioDescriptor` which takes `boundary_conditions`, a list of boundary conditions, as input. - The `metadata` is the same as for the linear elastic scenario except there is necessary input `metadata.desired_eigenvalues` that takes in an integer for the number of eigenvalues. ```cpp // Setup the modal simulation scenario Intact::ModalScenarioDescriptor modal_scenario; modal_scenario.materials = {{"Aluminum 6061-T6": material}}; modal_scenario.boundary_conditions = {fixed_boundary}; modal_scenario.metadata.resolution = 10000; modal_scenario.modal_metadata.desired_eigenvalues = 10; // Optional settings modal_scenario.metadata.basis_order = 2; // 2 = quadratic elements modal_scenario.metadata.solver_override = Intact::SolverType::AMGCL_amg_rigid_body; // iterative solver ``` - Similarly, a thermal scenario is created using `StaticThermalScenarioDescriptor` which takes a list of `boundary_conditions` and a list of `internal_conditions` as input. Note that the `material` must be a thermal material for this scenario type. - The `metadata` is the same as for the linear elastic scenario with additional input `metadata.environment_temperature` which specifies the temperature of the environment. ```cpp // Setup the thermal simulation scenario StaticThermalScenarioDescriptor thermal_scenario; thermal_scenario.materials = {{"Aluminum 6061-T6": material}}; thermal_scenario.boundary_conditions = {fixed_temp, convection}; thermal_scenario.internal_conditions = {constant_heat}; thermal_scenario.thermal_metadata.environment_temperature = 0.0; thermal_scenario.metadata.resolution = 10000; // Optional settings thermal_scenario.metadata.basis_order = 2; // 2 = quadratic elements thermal_scenario.metadata.solver_override = Intact::SolverType::AMGCL_amg; // iterative solver for thermal scenarios. - **Finalize Simulation Setup and Execute** - **Stress Simulation** is created using `StressSimulator`. It takes the following inputs: - An `assembly` or the list of `MaterialDomains` - `LinearElasticScenarioDescriptor` that describes the simulation scenario ```cpp // Initialize and run the linear elastic scenario Intact::StressSimulator simulator = StressSimulator(assembly, scenario); simulator.solve(); ``` - **Modal Simulation** is created using `ModalSimulator`. It takes the following inputs: - An `assembly` or the list of `MaterialDomains` - `ModalScenarioDescriptor` that describes the simulation scenario ```cpp // Intialize and run the modal scenario Intact::Modalimulator modal_simulator = ModalSimulator(assembly, modal_scenario); modal_simulator.solve(); ``` ### Query and Result Output - Create a `QueryResult` object that specifies the domain to be sampled. It takes a single `component` or an `assembly` as input ```cpp Intact::Assembly assembly = {component_1, component_2}; Intact::QueryResult results(component_1); // sample only the defined component Intact::QueryResult results(assembly); // sample on the full assembly ``` - Specify a **query class and type** that defines what quantity you are querying. The query classes and types available are as follows : - **Global Query** is for querying quantities defined for the entire domain/structure. The two `GlobalQueryType` subclasses available are `Frequency` & `Compliance`. ```cpp // GlobalQuery example // Create a GlobalQuery to query the first mode of a modal simulation Intact::QueryResult results(assembly); Intact::GlobalQuery frequency_query(Intact::GlobalQueryType::Frequency, Intact::DiscreteIndex(0)); // Sample the simulation for the specified query auto freq_1 = simulator.sample(frequency_query, results); std::cout << freq_1.get(0, 0) << std::endl; // Print out the first mode in Hz // Example use to get all the modes // Note, the simulator would need this: // modal_scenario.modal_metadata.desired_eigenvalues = 10 for (auto i = 0; i < modal_scenario.metadata.desired_eigenvalues; i++) { // Create the query for the current mode Intact::GlobalQuery query(Intact::GlobalQueryType::Frequency, Intact::DiscreteIndex(i)); // Sample the query using the simulator auto r = simulator.sample(query, results); std::cout << r.get(0, 0) << std::endl; } ``` - **Field Query** is for querying field quantities defined at any point in the domain. This depends on the physics type. - For **Stress Simulation**, the following `FieldType` inputs are available - `Displacement` tuple of dimension 3 for each point ```cpp [x-displacement, y-displacement, z-displacement] ``` - `Strain` and `Stress` are each a tuple of dimension 6 for each point ```cpp [stress_xx, stress_yy, stress_zz, stress_yz, stress_xz, stress_xy] [strain_xx, strain_yy, strain_zz, strain_yz, strain_xz, strain_xy] ``` - `TopologicalSensitivity`, `StrainEnergyDensity`, and `VonMisesStress` are each a tuple of dimension 1 for each point - **Field Query Interface usage** ```cpp // FieldQuery example // Create a FieldQuery to query displacement Intact::QueryResult results(assembly); Intact::FieldQuery displacement_query(Intact::FieldType::Displacement); // Create a FieldQuery with optional input to query the y-component Intact::FieldQuery y_displacement_query(Intact::FieldType::Displacement, 1); // FieldQuery with optional input to query the norm of displacement Intact::FieldQuery displacement_magnitude_query(Intact::FieldType::Displacement, true); // Sample the simulation with the given query to create a VectorArray // * more info on VectorArrays are provided in the subsequent section * auto displacement_VectorArray1 = simulator.sample(displacement_query, results); auto displacement_VectorArray2 = simulator.sample(y_displacement_query, results); auto displacement_VectorArray3 = simulator.sample(displacement_magnitude_query, results); auto displacement1 = displacement_VectorArray1.get(i, j); // jth component of the displacement (0 = X, 1 = Y, or 2 = Z) at the ith sampling point auto displacement2 = displacement_VectorArray2.get(1, 0); // y-displacement value at the second sampling point auto displacement3 = displacement_VectorArray3.get(1, 0); // displacement magnitude at the second sampling point ``` - For **Modal Simulation**, the only `FieldType` available is `Displacement`, which corresponds to the mode shape information and requires an index for the specified mode. ```cpp // Create a field query for the displacement/mode shape of mode 1 auto mode_num = 0; // first mode Intact::FieldQuery modal_field_query(Intact::Field::Displacement, Intact::DiscreteIndex(mode_num)) ``` #### Sample and Export Results - **Sampling results in-memory** - Methods to sample results of the simulation and store/print `VectorArray` results from the queries created above. ```cpp // Query results Intact::QueryResult results(assembly); Intact::FieldQuery stress_query(Intact::FieldType::Stress); // a field is needed to create a field query auto stress_VectorArray = simulator.sample(stress_query, results); ``` - This sampling is stored in a `VectorArray` of size `n_tuple` by `dimension`, `n_tuple` is the number of points sampled in the component/assembly and dimension depends on the query type. For example, `FieldQuery(Intact::FieldType::Displacement)` has a dimension of 3 for each displacement component, thus `VectorArray.get(i, 2)` would get the z-displacement of point `i`. ```cpp // Get the number of tuples (vectors) in the VectorArray (one per point results are sampled at) auto n_tuples = stress_VectorArray.n_tuples(); // Get the dimension of each vector - in this case 6, one value for each stress component auto dimension = stress_VectorArray.dimension(); auto stress_xx = stress_VectorArray.get(i, 0); // stress_xx at i-th sample point auto stress_yy = stress_VectorArray.get(i, 1); // stress_yy at i-th sample point auto stress_ij = stress_VectorArray.get(i, j); // jth stress component (0, 1, 2, 3, 4, or 5) of the ith tuple ``` - **Export results to a file**: Once the query is created, `results` can be used to write a file which contains **all** solution fields in [VTK UnstructuredGrid format](https://docs.vtk.org/en/latest/design_documents/VTKFileFormats.html#unstructuredgrid) via the `results.writeVTK` method which has two inputs: - file name `"*.vtu"` - `UnitSystem` is an attribute in the `*.vtu` ( Note that this argument is just metadata in the `*.vtu` file. The result magnitudes are in the unit of the scenario regardless of the unit system specified. ) ```cpp // Write results to a file (unit system is saved as a metadata in the vtu file) Intact::QueryResult results(assembly); Intact::FieldQuery stress_query(Intact::FieldType::VonMisesStress); simulator.sample(stress_query, results); // Write results to a file (unit system is saved as a metadata in the vtu file) results.writeVTK("cantilever_beam_results.vtu", Intact::UnitSystem::MeterKilogramSecond); ``` #### Sampling on Custom Geometries - Another important functionality is the ability to specify a point set to sample on. This can be done by creating a new `sampling_component` consisting of vertices at the desired sampling locations. **Note** that the `sampling_component` is not to be included in the assembly that is being simulated. ```cpp // Create an empty MeshModel to store vertices for desired sampling locations auto sampling_geometry = Intact::MeshModel(); // Add the desired sampling point locations and store their ID for later use auto test_index1 = sampling_geometry.addVertex({6.0, 0.0, 0.0}); auto test_index2 = sampling_geometry.addVertex({3.0, 0.0, 0.0}); auto sampling_component = Intact::MaterialDomain(sample_geometry, "material_name", scenario); // Define the assembly to be simulated (note that the sampling_component is not included) auto component = Intact::MaterialDomain(component_geometry, "material_name", scenario); IntactAssembly assembly = {component}; Intact::StressSimulator simulator(assembly, scenario); simulator.solve(); Intact::FieldQuery displacement_query(Intact::Field::Displacement); Intact::QueryResult sampled_results(sampling_component) // sample only the custom point set auto sampled_VectorArray = simulator.sample(displacement_query, sampled_results); auto displacement_index1 = sampled_VectorArray.get(test_index1, 0); // x-displacement at above specified index1 auto displacement_index2 = sampled_VectorArray.get(test_index2, 1); // y-displacement at above specified index2 ``` ## Examples Complete examples of performing a [structural simulation](../wrappers/cpp/examples/cantilever_beam.cpp), a [modal simulation](../wrappers/cpp/examples/cantilever_modal.cpp), or a [static thermal simulation](../wrappers/cpp/examples/thermal_beam.cpp) are available, as well as the required geometry files: [beam geometry](../wrappers/python/doc/examples/beam.stl), [restraint geometry](../wrappers/python/doc/examples/restraint.stl), [load geometry](../wrappers/python/doc/examples/load.stl).