Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import sys
import os
from shutil import rmtree

import pytest

from stem.additional_processes import ParameterFieldParameters
from stem.field_generator import ElasticFieldsFromCptGenerator
from stem.model import Model
from stem.soil_material import OnePhaseSoil, LinearElasticSoil, SoilMaterial, SaturatedBelowPhreaticLevelLaw
from stem.boundary import DisplacementConstraint
from stem.solver import AnalysisType, SolutionType, TimeIntegration, DisplacementConvergenceCriteria, StressInitialisationType, SolverSettings, Problem, NewtonRaphsonStrategy
from stem.output import VtkOutputParameters, GaussPointOutput
from stem.stem import Stem

from benchmark_tests.utils import assert_files_equal


def test_stem():
# Define geometry, conditions and material parameters
# --------------------------------

# Specify dimension and initiate the model
ndim = 3
model = Model(ndim)
model.extrusion_length = 20

soil_formulation = OnePhaseSoil(ndim, IS_DRAINED=True, DENSITY_SOLID=2650, POROSITY=0.3)
constitutive_law = LinearElasticSoil(YOUNG_MODULUS=10, POISSON_RATIO=0.3)
soil_material = SoilMaterial(name="soil",
soil_formulation=soil_formulation,
constitutive_law=constitutive_law,
retention_parameters=SaturatedBelowPhreaticLevelLaw())
width = 20
height = 20
# add soil layers
model.add_soil_layer_by_coordinates([(0, 0, 0), (width, 0, 0), (width, height, 0), (0, height, 0)], soil_material,
"layer1")

# Define the field generator
field_generator = ElasticFieldsFromCptGenerator(cpt_folder=r"benchmark_tests\test_elastic_field_from_cpt\cpts", ref_coordinates=(0, 0, 0), orientation_x_axis=0.0)


field_parameters_json = ParameterFieldParameters(property_names=["YOUNG_MODULUS", "DENSITY_SOLID"],
function_type="json_file",
field_generator=field_generator)

model.add_field(part_name="layer1", field_parameters=field_parameters_json)

model.synchronise_geometry()

no_displacement_parameters = DisplacementConstraint(active=[True, True, True],
is_fixed=[True, True, True],
value=[0, 0, 0])
roller_displacement_parameters = DisplacementConstraint(active=[True, True, True],
is_fixed=[True, False, True],
value=[0, 0, 0])

# Add boundary conditions to the model (geometry ids are shown in the show_geometry)
model.add_boundary_condition_by_geometry_ids(2, [2], no_displacement_parameters, "base_fixed")
model.add_boundary_condition_by_geometry_ids(2, [1, 3, 5, 6], roller_displacement_parameters, "roller_fixed")

# set mesh size
model.set_mesh_size(element_size=1)

analysis_type = AnalysisType.MECHANICAL
solution_type = SolutionType.QUASI_STATIC
# Set up start and end time of calculation, time step and etc
time_integration = TimeIntegration(start_time=0.0,
end_time=1.0,
delta_time=1.0,
reduction_factor=1.0,
increase_factor=1.0,
max_delta_time_factor=1000)
convergence_criterion = DisplacementConvergenceCriteria(displacement_relative_tolerance=1.0e-4,
displacement_absolute_tolerance=1.0e-9)
stress_initialisation_type = StressInitialisationType.NONE
strategy = NewtonRaphsonStrategy()
solver_settings = SolverSettings(analysis_type=analysis_type,
solution_type=solution_type,
stress_initialisation_type=stress_initialisation_type,
time_integration=time_integration,
is_stiffness_matrix_constant=False,
are_mass_and_damping_constant=False,
convergence_criteria=convergence_criterion,
strategy_type=strategy,
rayleigh_k=0.0,
rayleigh_m=0.0)

# Set up problem data
problem = Problem(problem_name="create_random_field_3d", number_of_threads=1, settings=solver_settings)
model.project_parameters = problem

# Define the results to be written to the output file
# Gauss point results
gauss_point_results = [GaussPointOutput.YOUNG_MODULUS, GaussPointOutput.DENSITY_SOLID]

# Define the output process
model.add_output_settings(output_parameters=VtkOutputParameters(file_format="ascii",
output_interval=1,
nodal_results=[],
gauss_point_results=gauss_point_results,
output_control_type="step"),
part_name="porous_computational_model_part",
output_dir="output",
output_name="vtk_output")

# Write KRATOS input files
# --------------------------------

input_folder = "benchmark_tests/test_elastic_field_from_cpt/inputs_kratos"

stem = Stem(model, input_folder)
stem.write_all_input_files()

# Run Kratos calculation
# --------------------------------
stem.run_calculation()

if sys.platform == "win32":
expected_output_dir = "benchmark_tests/test_random_field_3d/output_windows/output_vtk_porous_computational_model_part"
elif sys.platform == "linux":
expected_output_dir = "benchmark_tests/test_random_field_3d/output_linux/output_vtk_porous_computational_model_part"
else:
raise Exception("Unknown platform")

result = assert_files_equal(expected_output_dir,
os.path.join(input_folder, "output/output_vtk_porous_computational_model_part"))

assert result is True
rmtree(input_folder)
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_stem():
model_name="Gaussian",
seed=14)

field_parameters_json = ParameterFieldParameters(property_name="YOUNG_MODULUS",
field_parameters_json = ParameterFieldParameters(property_names=["YOUNG_MODULUS"],
function_type="json_file",
field_generator=random_field_generator)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_stem():
model_name="Gaussian",
seed=14)

field_parameters_json = ParameterFieldParameters(property_name="YOUNG_MODULUS",
field_parameters_json = ParameterFieldParameters(property_names=["YOUNG_MODULUS"],
function_type="json_file",
field_generator=random_field_generator)

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -958,7 +958,7 @@ The mean of the property is automatically obtained from the material property al
)

field_parameters_json = ParameterFieldParameters(
property_name="YOUNG_MODULUS",
property_names=["YOUNG_MODULUS"],
function_type="json_file",
field_generator=random_field_generator
)
Expand Down
80 changes: 44 additions & 36 deletions stem/IO/kratos_additional_processes_io.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, Union
from typing import Any, Dict, Union, List

from stem.additional_processes import *

Expand Down Expand Up @@ -48,7 +48,8 @@ def __create_excavation_dict(self, part_name: str, parameters: Excavation) -> Di

return process_dict

def __create_parameter_field_dict(self, part_name: str, parameters: ParameterFieldParameters) -> Dict[str, Any]:
def __create_parameter_field_dict(self, part_name: str,
parameters: ParameterFieldParameters) -> List[Dict[str, Any]]:
"""
Creates a dictionary containing the parameters for the parameter field process

Expand All @@ -58,37 +59,45 @@ def __create_parameter_field_dict(self, part_name: str, parameters: ParameterFie
object

Returns:
- Dict[str, Any]: dictionary containing the additional process parameters
- List[Dict[str, Any]]: list of dictionaries containing the parameter field process parameters
"""

# initialize boundary dictionary
process_dict: Dict[str, Any] = {
"python_module": "set_parameter_field_process",
"kratos_module": "KratosMultiphysics.GeoMechanicsApplication",
"process_name": "SetParameterFieldProcess",
"Parameters": {},
}

process_dict["Parameters"]["model_part_name"] = f"{self.domain}.{part_name}"
process_dict["Parameters"]["variable_name"] = parameters.property_name
process_dict["Parameters"]["func_type"] = parameters.function_type

# initialise to dummy
process_dict["Parameters"]["function"] = "dummy"
process_dict["Parameters"]["dataset"] = "dummy"

if parameters.function_type == "json_file":

process_dict["Parameters"]["dataset_file_name"] = parameters.field_file_name
elif parameters.function_type == "input":
process_dict["Parameters"]["function"] = parameters.tiny_expr_function
else:
raise ValueError(f"function type {parameters.function_type} not supported.")

return process_dict

def create_additional_processes_dict(self, part_name: str,
parameters: AdditionalProcessesParametersABC) -> Union[Dict[str, Any], None]:
# add 1 process for each property
processes = []
for i, property_name in enumerate(parameters.property_names):
# initialize boundary dictionary
process_dict: Dict[str, Any] = {
"python_module": "set_parameter_field_process",
"kratos_module": "KratosMultiphysics.GeoMechanicsApplication",
"process_name": "SetParameterFieldProcess",
"Parameters": {},
}

process_dict["Parameters"]["model_part_name"] = f"{self.domain}.{part_name}"
process_dict["Parameters"]["variable_name"] = property_name
process_dict["Parameters"]["func_type"] = parameters.function_type

# initialise to dummy
process_dict["Parameters"]["function"] = "dummy"
process_dict["Parameters"]["dataset"] = "dummy"

if parameters.function_type == "json_file":

if parameters.field_file_names is None or parameters.field_file_names[i] == "":
raise ValueError(
"`field_file_names` should be provided when `json_file` function type is selected.")

process_dict["Parameters"]["dataset_file_name"] = parameters.field_file_names[i]
elif parameters.function_type == "input":
process_dict["Parameters"]["function"] = parameters.tiny_expr_function
else:
raise ValueError(f"function type {parameters.function_type} not supported.")

processes.append(process_dict)

return processes

def create_additional_processes_dict(
self, part_name: str, parameters: AdditionalProcessesParametersABC) -> Union[List[Dict[str, Any]], None]:
"""
Creates a dictionary containing the boundary parameters

Expand All @@ -98,13 +107,12 @@ def create_additional_processes_dict(self, part_name: str,
parameters object

Returns:
- Dict[str, Any]: dictionary containing the parameters for the additional process
- List[Dict[str, Any]]: list of dictionaries containing the parameters for the additional process
"""

# add boundary parameters to dictionary based on boundary type.

# add additional processes dictionary
if isinstance(parameters, Excavation):
return self.__create_excavation_dict(part_name, parameters)
return [self.__create_excavation_dict(part_name, parameters)]
elif isinstance(parameters, ParameterFieldParameters):
return self.__create_parameter_field_dict(part_name, parameters)
else:
Expand Down
34 changes: 24 additions & 10 deletions stem/IO/kratos_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ def __create_process_model_parts_dictionary(self, model: Model) -> Dict[str, Any

# write the additional process model part parameters for the
# project parameters file
processes_dict["processes"]["constraints_process_list"].append(
processes_dict["processes"]["constraints_process_list"].extend(
self.additional_process_io.create_additional_processes_dict(mp.name, mp.parameters))

return processes_dict
Expand Down Expand Up @@ -974,25 +974,39 @@ def __adjust_parameter_field_parameters_and_write_json_file(self, process_model_
return None

# check that the name is not none!
if process_model_part.parameters.field_file_name is None:
if process_model_part.parameters.field_file_names is None:
raise ValueError("No name was provided for the json file containing the "
f"field parameters of model part {process_model_part.name} and property"
f" {process_model_part.parameters.property_name}.")
f"field parameters of model part {process_model_part.name} and properties"
f" {process_model_part.parameters.property_names}.")

# adjust extension of filename name is not none, check that extension is json and change it if not.
process_model_part.parameters.field_file_name = Utils.replace_extensions(
process_model_part.parameters.field_file_name, ".json")
for i in range(len(process_model_part.parameters.field_file_names)):
process_model_part.parameters.field_file_names[i] = Utils.replace_extensions(
process_model_part.parameters.field_file_names[i], ".json")

# check that the name is not none!
if process_model_part.parameters.field_generator is None:
raise ValueError("Field generator object not provided for the field generation"
f" of model part {process_model_part.name} and "
f"property {process_model_part.parameters.property_name}.")
f"properties {process_model_part.parameters.property_names}.")

if process_model_part.parameters.field_generator.generated_fields is None:
raise ValueError("No field values were generated for the field generator"
f" of model part {process_model_part.name} and "
f"properties {process_model_part.parameters.property_names}.")

if len(process_model_part.parameters.field_file_names) != len(
process_model_part.parameters.field_generator.generated_fields):
raise ValueError("The number of field file names and the number of generated fields do not match"
f" for model part {process_model_part.name} and "
f"properties {process_model_part.parameters.property_names}.")

# write field values in the json input file
IOUtils.write_json_file(output_folder=self.project_folder,
file_name=process_model_part.parameters.field_file_name,
dictionary={"values": process_model_part.parameters.field_generator.generated_field})
for i in range(len(process_model_part.parameters.field_file_names)):
IOUtils.write_json_file(
output_folder=self.project_folder,
file_name=process_model_part.parameters.field_file_names[i],
dictionary={"values": process_model_part.parameters.field_generator.generated_fields[i]})

@staticmethod
def __create_set_nodal_parameters_process_dictionary(model_part: BodyModelPart) -> Dict[str, Any]:
Expand Down
25 changes: 19 additions & 6 deletions stem/additional_processes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from dataclasses import dataclass
from abc import ABC
from typing import Optional
from typing import Optional, List

from stem.field_generator import FieldGeneratorABC
from stem.field_generator import FieldGeneratorABC, RandomFieldGenerator

FIELD_INPUT_TYPES = ["json_file", "input"]

Expand Down Expand Up @@ -41,10 +41,11 @@ class ParameterFieldParameters(AdditionalProcessesParametersABC):
- python: A python script needs to be provided for the purpose. This is currently not supported in STEM.\

Attributes:
- property_name (str): the name of the (material) property that needs to be changed (e.g. YOUNG_MODULUS)
- property_names (List[str]): the names of the (material) properties that needs to be changed \
(e.g. [YOUNG_MODULUS])
- function_type (str): the type of function to be provided. It can be either `json_file` or `input`,
as provided in the function documentation.
- field_file_name (Optional[str]): Name for the json file where the field parameters will be stored.
- field_file_names (Optional[List[str]]): Name for the json file where the field parameters will be stored. \
This is optional for `json` function_type.
- field_generator (Optional[:class:`stem.field_generator.FieldGeneratorABC`]): the field generator to produce \
the values in the json file. Currently only random fields is supported but will be in the future \
Expand All @@ -57,9 +58,9 @@ class ParameterFieldParameters(AdditionalProcessesParametersABC):

"""

property_name: str
property_names: List[str]
function_type: str
field_file_name: Optional[str] = None
field_file_names: Optional[List[str]] = None
field_generator: Optional[FieldGeneratorABC] = None
tiny_expr_function: Optional[str] = None

Expand All @@ -71,6 +72,9 @@ def __post_init__(self):
- ValueError: if the function type is not `input` or `json_file`.
- ValueError: if the field_generator is not provided when function_type is `json_file`.`input`
- ValueError: if the tiny_expr_function is not provided when function_type is `input`.
- ValueError: if the length of the field_file_names is not equal to the length of the property_names.
- ValueError: if the length of the property_names is not equal to 1 when field_generator:
'RandomFieldGenerator' is used.

"""
self.function_type = self.function_type.lower()
Expand All @@ -87,3 +91,12 @@ def __post_init__(self):
if self.function_type == "input" and self.tiny_expr_function is None:
raise ValueError("`tiny_expr_function` parameter is a required when `input` field parameter is "
"selected for `function_type`.")

if self.field_file_names is not None:
if len(self.field_file_names) != len(self.property_names):
raise ValueError("`field_file_names` should have the same length as `property_names`.")

if isinstance(self.field_generator, RandomFieldGenerator):
if len(self.property_names) != 1:
raise ValueError("Only one property name can be provided for the field generator class "
"'RandomFieldGenerator'.")
Loading