Virtual Fields Calibration Verification

In this example, we use MatCal’s VFM tools to calibrate to the 0_degree synthetic data described in Full-field Study Verification. Due to the numerical methods used in optimization process and the errors introduced by the plane stress assumption inherent in VFM, we expect there to be some error in the parameters, An ideal result would produce calibrated parameters within a few percent of the actual values.

As we will see, this does not occur using the VFM tool with limited data.

To begin we import the MatCal tools necessary for this study and import the data that will be used for the calibration.

from matcal import *
import matplotlib.pyplot as plt

plt.rc('text', usetex=True)
plt.rc('font', family='serif')
plt.rcParams.update({'font.size': 12})

synthetic_data = FieldSeriesData("../../../docs_support_files/synthetic_surf_results_0_degree.e")
Opening exodus file: ../../../docs_support_files/synthetic_surf_results_0_degree.e
Opening exodus file: ../../../docs_support_files/synthetic_surf_results_0_degree.e
Closing exodus file: ../../../docs_support_files/synthetic_surf_results_0_degree.e
Closing exodus file: ../../../docs_support_files/synthetic_surf_results_0_degree.e

Since VFM requires a plane stress assumption, we must calibrate to portions of the data that most closely adhere to this assumption. For this problem, we must ensure that the data doesn’t include significant plastic localization. To investigate this, we plot the data load-displacement curve. If the data shows structural load loss, we know the specimen has necked.

dc = DataCollection("synthetic", synthetic_data)
dc.plot("displacement", "load")
matcal_default_state

We can see peak load for this simulation occurs at a displacement of 0.036 m. Next, we remove all data past the 0.036 m displacement and then plot the X and Y displacement fields on the deformed geometry for the last time step. We plot the deformed configuration colored by the correct displacement field on top of the undeformed configuration in grey.

synthetic_data = synthetic_data[synthetic_data["displacement"] < 0.036]

import matplotlib.pyplot as plt
def plot_field(data, field, ax):
    c = ax.scatter(1e3*(data.spatial_coords[:,0]),
                   1e3*(data.spatial_coords[:,1]),
                   c="#bdbdbd", marker='.', s=1, alpha=0.5)
    c = ax.scatter(1e3*(data.spatial_coords[:,0]+data["U"][-1, :]),
                   1e3*(data.spatial_coords[:,1]+data["V"][-1, :]),
                   c=1e3*data[field][-1, :], marker='.', s=3)
    ax.set_xlabel("X (mm)")
    ax.set_ylabel("Y (mm)")
    ax.set_aspect('equal')
    fig.colorbar(c, ax=ax, label=f"{field} mm")

fig, axes = plt.subplots(1,2, figsize=(10,4), constrained_layout=True)
plot_field(synthetic_data, "U", axes[0])
plot_field(synthetic_data, "V", axes[1])
plt.show()
plot vfm one angle calibration verification

After importing and preparing the data, we create the VFM model that will be used to simulate the characterization test. We use a VFMUniaxialTensionHexModel for this example. This model will need a SierraSM material model input file. We create it next using python string and file tools.

mat_file_string = """begin material test_material
  density = 1
  begin parameters for model hill_plasticity
    youngs modulus  = {elastic_modulus*1e9}
    poissons ratio  = {poissons}
    yield_stress    = {yield_stress*1e6}

    hardening model = voce
    hardening modulus = {A*1e6}
    exponential coefficient = {n}

    coordinate system = rectangular_coordinate_system

    R11 = {R11}
    R22 = {R22}
    R33 = {R33}
    R12 = {R12}
    R23 = {R23}
    R31 = {R31}
  end
end
"""

with open("modular_plasticity.inc", 'w') as fn:
    fn.write(mat_file_string)

The VFM model requires a Material object. After creating the material object, we create the VFM model with the correct surface mesh that corresponds to our output surface mesh and the total specimen thickness. Next, we use the correct methods to prepare the model for the study. Most importantly we pass the correct model constants to it and pass the field data to it that includes the displacements the model will use as its boundary conditions. The model constants passed to the model are the uncalibrated parameters described in Full-field Verification Problem Material Model.

material = Material("test_material", "modular_plasticity.inc", "hill_plasticity")
vfm_model = VFMUniaxialTensionHexModel(material,
                                       "synthetic_data_files/test_mesh_surf.g",
                                       0.0625*0.0254)
vfm_model.add_boundary_condition_data(synthetic_data)
vfm_model.set_name("test_model")
vfm_model.set_number_of_cores(36)
vfm_model.set_number_of_time_steps(450)
vfm_model.set_displacement_field_names(x_displacement="U", y_displacement="V")
vfm_model.add_constants(elastic_modulus=200, poissons=0.27, R22=1.0, R33=0.9,
                        R23=1.0, R31=1.0)
from site_matcal.sandia.computing_platforms import is_sandia_cluster
from site_matcal.sandia.tests.utilities import MATCAL_WCID

if is_sandia_cluster():
    vfm_model.run_in_queue(MATCAL_WCID, 10.0/60.0)
    vfm_model.continue_when_simulation_fails()
Opening exodus file: synthetic_data_files/test_mesh_surf.g
Closing exodus file: synthetic_data_files/test_mesh_surf.g

We now create the objective that will be used for the calibration. Since our “load” and “time” fields match the default names for those fields in the MechanicalVFMObjective, no additional input is needed. We do name the objective for convenience.

vfm_objective = MechanicalVFMObjective()
vfm_objective.set_name("vfm_objective")

We then create the material model input parameters for the study. As was done in the previous examples, we provide realistic bounds that one may expect for an austenitic stainless steel based on our experience with the material. This results in an initial point far from the true values used for the synthetic data generation and is a stressing test for a local gradient based method.

Y = Parameter("yield_stress", 100, 500.0)
A = Parameter("A", 100, 4000)
n = Parameter("n", 1, 10)
R11 = Parameter("R11", 0.8, 1.1)
R12 = Parameter("R12", 0.8, 1.1)

param_collection = ParameterCollection("hill voce", Y, A, n, R11, R12)

Finally, we create the calibration study and pass the parameters relevant to the study during its initialization. We then set the total cores it can use locally and pass the data, model and objective to it as an evaluation set.

study = GradientCalibrationStudy(param_collection)
study.set_results_storage_options(results_save_frequency=len(param_collection)+1)
study.set_core_limit(48)
study.add_evaluation_set(vfm_model, vfm_objective, synthetic_data)
study.do_not_save_evaluation_cache()
study.set_working_directory("vfm_one_angle", remove_existing=True)

For this example, we limit the number of maximum evaluations. This is to save computation time. It will not converge to the correct solution with more iterations, it over fits the model to the available data and is likely traversing down a “valley” in the objective spave.

study.set_max_function_evaluations(200)
results = study.launch()
Opening exodus file: matcal_template/test_model/matcal_default_state/test_model.g
Closing exodus file: matcal_template/test_model/matcal_default_state/test_model.g
Opening exodus file: matcal_template/test_model/matcal_default_state/test_model.g
Closing exodus file: matcal_template/test_model/matcal_default_state/test_model.g
Opening exodus file: matcal_template/test_model/matcal_default_state/test_model.g
Closing exodus file: matcal_template/test_model/matcal_default_state/test_model.g
Opening exodus file: matcal_template/test_model/matcal_default_state/test_model.g
Opening exodus file: matcal_template/test_model/matcal_default_state/test_model_exploded.g
Closing exodus file: matcal_template/test_model/matcal_default_state/test_model_exploded.g
Closing exodus file: matcal_template/test_model/matcal_default_state/test_model.g

When the study completes, we extract the calibrated parameters and evaluate the error. The optimization has moved far from the initial point and provides low error for some of the parameters. It completes with RELATIVE FUNCTION CONVERGENCE indicating a quality local minima has been identified

calibrated_params = results.best.to_dict()
print(calibrated_params)

goal_results = {"yield_stress":200,
                "A":1500,
                "n":2,
                "R11":0.95,
                "R12":0.85}

def pe(result, goal):
    return (result-goal)/goal*100

for param in goal_results.keys():
    print(f"Parameter {param} error: {pe(calibrated_params[param], goal_results[param])}")
OrderedDict({'yield_stress': 193.16830661, 'A': 1526.0136645, 'n': 1.7069568852, 'R11': 0.80379719168, 'R12': 1.1})
Parameter yield_stress error: -3.4158466949999995
Parameter A error: 1.7342443000000003
Parameter n error: -14.652155739999994
Parameter R11 error: -15.389769296842104
Parameter R12 error: 29.411764705882366

Using MatCal’s standard plot, it is clear that the gradient method quickly heads toward a minimum that is near the true values. However, once it gets to that minimum, it continues to change the parameters while the objective only decreases a small amount. This is showing that the objective has a shallow trough in this objective space. This is likely due to the model over fitting the data. The single data set is insufficient to accurately identify the parameters and the model form error allows the algorithm to continue to slowly reduce the objective by moving the parameters away from the values used to generate the synthetic data. We believe that adding data to constrain this drift will alleviate this issue. We do so in the next example Virtual Fields Calibration Verification - Three Data Sets where we see improved results.

import os
init_dir = os.getcwd()
os.chdir("vfm_one_angle")
make_standard_plots("time")
os.chdir(init_dir)

# sphinx_gallery_thumbnail_number = 5
  • plot vfm one angle calibration verification
  • plot vfm one angle calibration verification
  • plot vfm one angle calibration verification

Total running time of the script: (281 minutes 36.801 seconds)

Gallery generated by Sphinx-Gallery