# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# LICENSE
#
# Copyright (C) 2010-2023 GEM Foundation, G. Weatherill, M. Pagani,
# D. Monelli.
#
# The Hazard Modeller's Toolkit is free software: you can redistribute
# it and/or modify it under the terms of the GNU Affero General Public
# License as published by the Free Software Foundation, either version
# 3 of the License, or (at your option) any later version.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>
#
# DISCLAIMER
#
# The software Hazard Modeller's Toolkit (openquake.hmtk) provided herein
# is released as a prototype implementation on behalf of
# scientists and engineers working within the GEM Foundation (Global
# Earthquake Model).
#
# It is distributed for the purpose of open collaboration and in the
# hope that it will be useful to the scientific, engineering, disaster
# risk and software design communities.
#
# The software is NOT distributed as part of GEM's OpenQuake suite
# (https://www.globalquakemodel.org/tools-products) and must be considered as a
# separate entity. The software provided herein is designed and implemented
# by scientific staff. It is not developed to the design standards, nor
# subject to same level of critical review by professional software
# developers, as GEM's OpenQuake software suite.
#
# Feedback and contribution to the software is welcome, and can be
# directed to the hazard scientific staff of the GEM Model Facility
# (hazard@globalquakemodel.org).
#
# The Hazard Modeller's Toolkit (openquake.hmtk) is therefore distributed WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# The GEM Foundation, and the authors of the software, assume no
# liability for use of the software.
"""
Module: openquake.hmtk.parsers.fault.fault_yaml_parser implements parser of a fault
model from the TOML format
"""
import toml
import numpy as np
from math import fabs
from openquake.hazardlib.geo.point import Point
from openquake.hazardlib.geo.line import Line
from openquake.hazardlib.scalerel import get_available_scalerel
from openquake.hmtk.faults.fault_geometries import (
SimpleFaultGeometry,
ComplexFaultGeometry,
)
from openquake.hmtk.faults.fault_models import mtkActiveFault
from openquake.hmtk.faults.active_fault_model import mtkActiveFaultModel
from openquake.hmtk.faults.tectonic_regionalisation import (
TectonicRegionalisation,
)
SCALE_REL_MAP = get_available_scalerel()
[docs]def weight_list_to_tuple(data, attr_name):
"""
Converts a list of values and corresponding weights to a tuple of values
"""
if len(data["Value"]) != len(data["Weight"]):
raise ValueError(
"Number of weights do not correspond to number of "
"attributes in %s" % attr_name
)
weight = np.array(data["Weight"])
if fabs(np.sum(weight) - 1.0) > 1e-7:
raise ValueError("Weights do not sum to 1.0 in %s" % attr_name)
data_tuple = []
for iloc, value in enumerate(data["Value"]):
data_tuple.append((value, weight[iloc]))
return data_tuple
[docs]def parse_tect_region_dict_to_tuples(region_dict):
"""
Parses the tectonic regionalisation dictionary attributes to tuples
"""
output_region_dict = []
tuple_keys = ["Displacement_Length_Ratio", "Shear_Modulus"]
# Convert MSR string name to openquake.hazardlib.scalerel object
for region in region_dict:
for val_name in tuple_keys:
region[val_name] = weight_list_to_tuple(region[val_name], val_name)
# MSR works differently - so call get_scaling_relation_tuple
region["Magnitude_Scaling_Relation"] = weight_list_to_tuple(
region["Magnitude_Scaling_Relation"], "Magnitude Scaling Relation"
)
output_region_dict.append(region)
return output_region_dict
[docs]def get_scaling_relation_tuple(msr_dict):
"""
For a dictionary of scaling relation values convert string list to
object list and then to tuple
"""
# Convert MSR string name to openquake.hazardlib.scalerel object
for iloc, value in enumerate(msr_dict["Value"]):
if value not in SCALE_REL_MAP:
raise ValueError("Scaling relation %s not supported!" % value)
msr_dict["Value"][iloc] = SCALE_REL_MAP[value]()
return weight_list_to_tuple(msr_dict, "Magnitude Scaling Relation")
[docs]class FaultYmltoSource(object):
"""
Class to parse a fault model definition from TOML format to a fault model
class
"""
def __init__(self, filename):
"""
:param str filename:
Name of input file (in yml format)
"""
self.data = toml.load(open(filename, "rt"))
if "Fault_Model" not in self.data:
raise ValueError("Fault Model not defined in input file!")
[docs] def read_file(self, mesh_spacing=1.0):
"""
Reads the file and returns an instance of the FaultSource class.
:param float mesh_spacing:
Fault mesh spacing (km)
"""
# Process the tectonic regionalisation
tectonic_reg = self.process_tectonic_regionalisation()
model = mtkActiveFaultModel(
self.data["Fault_Model_ID"], self.data["Fault_Model_Name"]
)
for fault in self.data["Fault_Model"]:
fault_geometry = self.read_fault_geometry(
fault["Fault_Geometry"], mesh_spacing
)
if fault["Shear_Modulus"]:
fault["Shear_Modulus"] = weight_list_to_tuple(
fault["Shear_Modulus"], "%s Shear Modulus" % fault["ID"]
)
if fault["Displacement_Length_Ratio"]:
fault["Displacement_Length_Ratio"] = weight_list_to_tuple(
fault["Displacement_Length_Ratio"],
"%s Displacement to Length Ratio" % fault["ID"],
)
fault_source = mtkActiveFault(
fault["ID"],
fault["Fault_Name"],
fault_geometry,
weight_list_to_tuple(fault["Slip"], "%s - Slip" % fault["ID"]),
float(fault["Rake"]),
fault["Tectonic_Region"],
float(fault["Aseismic"]),
weight_list_to_tuple(
fault["Scaling_Relation_Sigma"],
"%s Scaling_Relation_Sigma" % fault["ID"],
),
neotectonic_fault=None,
scale_rel=get_scaling_relation_tuple(
fault["Magnitude_Scaling_Relation"]
),
aspect_ratio=fault["Aspect_Ratio"],
shear_modulus=fault["Shear_Modulus"],
disp_length_ratio=fault["Displacement_Length_Ratio"],
)
if tectonic_reg:
fault_source.get_tectonic_regionalisation(
tectonic_reg, fault["Tectonic_Region"]
)
assert isinstance(fault["MFD_Model"], list)
fault_source.generate_config_set(fault["MFD_Model"])
model.faults.append(fault_source)
return model, tectonic_reg
[docs] def process_tectonic_regionalisation(self):
"""
Processes the tectonic regionalisation from the TOML file
"""
if "tectonic_regionalisation" in self.data:
tectonic_reg = TectonicRegionalisation()
tectonic_reg.populate_regions(
parse_tect_region_dict_to_tuples(
self.data["tectonic_regionalisation"]
)
)
else:
tectonic_reg = None
return tectonic_reg
[docs] def read_fault_geometry(self, geo_dict, mesh_spacing=1.0):
"""
Creates the fault geometry from the parameters specified in the
dictionary.
:param dict geo_dict:
Sub-dictionary of main fault dictionary containing only
the geometry attributes
:param float mesh_spacing:
Fault mesh spacing (km)
:returns:
Instance of SimpleFaultGeometry or ComplexFaultGeometry, depending
on typology
"""
if geo_dict["Fault_Typology"] == "Simple":
# Simple fault geometry
raw_trace = geo_dict["Fault_Trace"]
trace = Line(
[
Point(raw_trace[ival], raw_trace[ival + 1])
for ival in range(0, len(raw_trace), 2)
]
)
geometry = SimpleFaultGeometry(
trace,
geo_dict["Dip"],
geo_dict["Upper_Depth"],
geo_dict["Lower_Depth"],
mesh_spacing,
)
elif geo_dict["Fault_Typology"] == "Complex":
# Complex Fault Typology
trace = []
for raw_trace in geo_dict["Fault_Trace"]:
fault_edge = Line(
[
Point(
raw_trace[ival],
raw_trace[ival + 1],
raw_trace[ival + 2],
)
for ival in range(0, len(raw_trace), 3)
]
)
trace.append(fault_edge)
geometry = ComplexFaultGeometry(trace, mesh_spacing)
else:
raise ValueError("Unrecognised or unsupported fault geometry!")
return geometry