"""
Shear-family models: torsion and top-hat shear.
Concrete models in this module:
- SolidBarTorsionModel
- TopHatShearModel
"""
import numpy as np
from matcal.core.boundary_condition_calculators import get_rotation_function_from_data_collection
from matcal.core.constants import DISPLACEMENT_KEY, ROTATION_KEY, TORQUE_KEY
from matcal.sierra.input_file_writer import (
SolidMechanicsUserOutput, SolidMechanicsUserVariable
)
from matcal.cubit.geometry import SolidBarTorsionGeometry, TopHatShearGeometry
from .base import _SymmetricUniaxiallyLoadedModelContactBase
from .tension import _TensionDerivedModelBase
[docs]
class SolidBarTorsionModel(_TensionDerivedModelBase):
"""
MatCal generated SIERRA/SM solid bar torsion test model.
"""
model_type = "solid_bar_torsion_model"
_geometry_creator_class = SolidBarTorsionGeometry
_loading_bc_node_sets = ["ns_side_grip"]
_loading_bc_directions = ["cylindrical_axis"]
_loading_bc_direction_keys = ["cylindrical axis"]
_fixed_bc_node_sets = ["ns_y_symmetry"]
_fixed_bc_directions = ["y"]
_solution_termination_variable = TORQUE_KEY
def _additional_boundary_condition_setup(self, state):
ifile = self._input_file
ifile._add_prescribed_displacement_boundary_condition(
"sierra_constant_function_zero",
self._fixed_bc_node_sets,
self._loading_bc_directions,
self._loading_bc_direction_keys,
)
def _create_derived_user_output_blocks(self, state):
self._add_rotation_user_variables()
torque_rotation_output = SolidMechanicsUserOutput(
"global_torque_rotation", "ns_side_grip", "node set"
)
self._input_file.solid_mechanics_region.add_subblock(torque_rotation_output, replace=True)
self._add_variable_transforms(torque_rotation_output)
self._add_torque_calculations(torque_rotation_output)
self._add_rotation_calculations(torque_rotation_output)
def _add_rotation_user_variables(self):
quarter_rotation_count_var = SolidMechanicsUserVariable(
"quarter_rotation_count", "global", "real", 0
)
self._input_file.solid_mechanics_region.add_subblock(
quarter_rotation_count_var, replace=True
)
grip_rotation_var = SolidMechanicsUserVariable("grip_rotation", "global", "real", 0)
self._input_file.solid_mechanics_region.add_subblock(grip_rotation_var, replace=True)
half_grip_rotation_var = SolidMechanicsUserVariable(
"add_grip_half_rotation", "global", "real", 0
)
self._input_file.solid_mechanics_region.add_subblock(half_grip_rotation_var, replace=True)
previous_quadrant_var = SolidMechanicsUserVariable("previous_quadrant", "global", "real", 0)
self._input_file.solid_mechanics_region.add_subblock(previous_quadrant_var, replace=True)
current_quadrant_var = SolidMechanicsUserVariable("current_quadrant", "global", "real", 1)
self._input_file.solid_mechanics_region.add_subblock(current_quadrant_var, replace=True)
def _add_variable_transforms(self, torque_rotation_output):
torque_rotation_output.add_nodal_variable_transformation(
"displacement",
"cylindrical_displacement",
"cylindrical_coordinate_system",
)
torque_rotation_output.add_nodal_variable_transformation(
"force_external",
"cylindrical_force_external",
"cylindrical_coordinate_system",
)
def _add_torque_calculations(self, torque_rotation_output):
torque_rotation_output.add_compute_global_from_nodal_field(
f"partial_{TORQUE_KEY}",
"cylindrical_force_external(y)",
"sum",
)
grip_radius = self._current_state_geo_params["grip_radius"]
torque_rotation_output.add_compute_global_from_expression(
TORQUE_KEY, f"partial_{TORQUE_KEY}*{grip_radius}"
)
self._input_file._add_heartbeat_global_variable(TORQUE_KEY)
def _add_rotation_calculations(self, torque_rotation_output):
torque_rotation_output.add_compute_global_from_nodal_field(
"grip_cylindrical_x_disp", "cylindrical_displacement(x)"
)
torque_rotation_output.add_compute_global_from_nodal_field(
"grip_cylindrical_y_disp", "cylindrical_displacement(y)"
)
torque_rotation_output.add_compute_global_as_function(
"applied_rotation_radians", self._input_file._load_bc_function_name
)
torque_rotation_output.add_compute_global_from_expression(
"applied_rotation", "applied_rotation_radians*180/{PI}*2"
)
grip_radius = self._current_state_geo_params["grip_radius"]
torque_rotation_output.add_compute_global_from_expression(
"tangent_denominator", f"{grip_radius} - grip_cylindrical_x_disp"
)
torque_rotation_output.add_compute_global_from_expression(
"previous_quadrant", "current_quadrant"
)
current_quadrant_expression = (
"(grip_cylindrical_y_disp > -1e-15 ) ? "
"((tangent_denominator > -1e-15) ? 1 : 2) : "
"((tangent_denominator > -1e-15 ) ? 4 : 3)"
)
torque_rotation_output.add_compute_global_from_expression(
"current_quadrant", current_quadrant_expression
)
torque_rotation_output.add_compute_global_from_expression(
"add_grip_half_rotation",
"((current_quadrant == 2) || (current_quadrant == 4)) ? 180 : 0",
)
quarter_rotation_count_exp = (
"((previous_quadrant != current_quadrant )) ? "
"quarter_rotation_count+1 : quarter_rotation_count"
)
torque_rotation_output.add_compute_global_from_expression(
"quarter_rotation_count", quarter_rotation_count_exp
)
partial_grip_rotation_exp = "(atan(grip_cylindrical_y_disp/(tangent_denominator))*180/{PI})"
torque_rotation_output.add_compute_global_from_expression(
f"partial_{ROTATION_KEY}", partial_grip_rotation_exp
)
grip_rotation_exp = (
f"partial_{ROTATION_KEY}*2+90*2*(quarter_rotation_count) + add_grip_half_rotation"
)
torque_rotation_output.add_compute_global_from_expression(ROTATION_KEY, grip_rotation_exp)
self._input_file._add_heartbeat_global_variable("applied_rotation")
self._input_file._add_heartbeat_global_variable(ROTATION_KEY)
def _get_loading_boundary_condition_displacement_function(self, state, params_by_precedent):
rot_function, metadata = get_rotation_function_from_data_collection(
self._boundary_condition_data,
state,
params_by_precedent,
scale_factor=1.0,
return_metadata=True,
)
# Accounting for symmetry across the gauge length and conversion of degrees to radians
rot_function[ROTATION_KEY] *= 0.5 * np.pi / 180.0
rot_function.rename_field(ROTATION_KEY, DISPLACEMENT_KEY)
extra_lines = [
"Applied symmetry factor of 0.5 to the prescribed rotation.",
'Converted rotation from degrees to radians.',
f'Renamed "{ROTATION_KEY}" to "{DISPLACEMENT_KEY}" for the SIERRA prescribed displacement boundary condition.',
]
self._set_last_loading_bc_comment(metadata, extra_lines)
return rot_function
[docs]
class TopHatShearModel(_SymmetricUniaxiallyLoadedModelContactBase):
"""
MatCal generated SIERRA/SM top hat shear test model.
"""
model_type = "top_hat_shear_model"
_geometry_creator_class = TopHatShearGeometry
_death_blocks = ["localization_section"]
_loading_bc_node_sets = ["ns_y_bottom"]
_model_blocks = ["localization_section", "platten_interface_section"]
_temperature_blocks = ["localization_section"]
_thermal_bc_nodesets = ["ns_y_bottom", "ns_load"]
_fixed_bc_node_sets = ["ns_x_symmetry", "ns_load", "ns_z_symmetry"]
_fixed_bc_directions = ["x", "y", "z"]
@property
def self_contact(self):
"""
Returns True if self contact is on for the model. Otherwise, returns False.
"""
return self._input_file._self_contact()
def _create_derived_user_output_blocks(self, state):
self._add_disp_outputs(self._loading_bc_node_sets[0], 1)
self._add_load_outputs(self._loading_bc_node_sets[0], 4)
[docs]
def activate_full_field_data_output(self):
"""
Not implemented for this model.
"""
raise AttributeError(
f'Cannot use full field data output with the MatCal top hat shear model "{self.name}".'
)