Statistical Analysis of Slope with Spatial Variability#
Download Statistical Analysis of Slope with Spatial Variability folder for this example.
import os
from rs2.modeler.RS2Modeler import RS2Modeler
from rs2.modeler.properties.PropertyEnums import *
from rs2.interpreter.RS2Interpreter import RS2Interpreter
from rs2.interpreter.InterpreterEnums import *
from rs2.interpreter.InterpreterGraphEnums import *
import matplotlib.pyplot as plt
import numpy as np
from DiscreteFunctionHelpers import DiscreteFunctionHelpers as DFHelpers
from FileUtilities import FileUtilities as FileUtil
'''
This script demonstrates how to conduct a statistical analysis of a slope
considering spatial variability of the soil strength.
Go to the "Manage the processes to run" section to enable/disable the processes you want to try.
Note that the models use a coarse mesh to reduce the computation time as to better
showcase the functionality of this script. For a more accurate factor of safety, you will need
to refine the mesh in the model before running the script. We have provided a fine mesh model in
case you want to try this.
First, the factor of safety for the deterministic case is computed.
Depending on the selected option, 5 or 100 samples are created with varying strength properties.
Slope stability analysis using shear strength reduction method are conducted for each model.
The Critical SRF (Factor of Safety) data is extracted from computed models to generate a
histogram of Critical SRF along with statistical results:
mean, standard deviation, min/max, probability of failure and reliability index.
Custom FileUtilities and DiscreteFunctionHelpers classes are provided to show how
a user could extend the functionality of the RS2 Python API library for their specific needs:
- FileUtilies class provides support for finding and managing files.
- DiscreteFunctionHelpers imports the RS2 module DiscreteFunction to provide custom support for
creating discrete function materials in RS2 from read data. The data could come from Slide2,
MATLAB scripts, Python scripts, and so on
'''
# Current folder location
current_dir = os.path.dirname(os.path.abspath(__file__))
# Manage the processes to run ###############################################################################
# Deterministic Case
compute_deterministic_case = True # True to compute factor of safety for base model, False otherwise
# Generating samples
use_previously_computed_sample_results = False # True to skip generating samples and use pre-computed results, False otherwise
save_new_sample_models = True # True to save each generated sample model, False otherwise
generate_five_samples_only = True # True will generate 5 samples to see how this script works,
# False will generate all 100 samples which will take much longer to finish
# Analysis
analyze_results = True # True to output result plots, False otherwise
# Start analysis ###########################################################################################
# Start RS2 Modeler
RS2Modeler.startApplication(port=60054)
modeler = RS2Modeler(port=60054)
# Open RS2 interpreter
RS2Interpreter.startApplication(port=60055)
interpreter = RS2Interpreter(port=60055)
# Base file
base_file_name = 'slope example.fez' # Try 'slope example fine mesh.fez' for more accurate factor of safety, but it will take much longer to compute
base_file_path = rf'{current_dir}\{base_file_name}'
# Deterministic Case #####################################################################################
if compute_deterministic_case:
deterministic_dir = rf'{current_dir}\deterministic'
os.makedirs(deterministic_dir, exist_ok=True)
model = modeler.openFile(base_file_path)
model.saveAs(rf'{deterministic_dir}\slope example (deterministic).fez')
model.compute()
# Generate samples with spatial variability of strength ##################################################
# Set use_previously_computed_sample_results = True to use previously computed results to save time
if not use_previously_computed_sample_results:
# Output and data folders
if generate_five_samples_only:
samples_dir = rf'{current_dir}\samples 5'
data_dir = rf'{current_dir}\data 5'
else:
samples_dir = rf'{current_dir}\samples'
data_dir = rf'{current_dir}\data'
os.makedirs(samples_dir, exist_ok=True)
# Discrete function file paths
discrete_function_file_extensions = {'fn6','cust'}
discrete_function_file_paths=[]
for discrete_function_file_extension in discrete_function_file_extensions:
discrete_function_file_paths.extend(FileUtil.findFilesWithExtension(data_dir, discrete_function_file_extension))
# Collect all discrete function file data to minimize number of times models are opened in update
samplenum_to_discretefunctiondata = DFHelpers.getDiscreteFunctionDataPerSample(discrete_function_file_paths)
# Open base model
model = modeler.openFile(base_file_path)
# Save a copy that we will modify in the script, so you can easily retry this script
base_sample_file_name = 'slope example (sampling).fez'
base_sample_file_path = rf'{current_dir}\{base_sample_file_name}'
model.saveAs(base_sample_file_path)
# Key data to extract
sample_nums = []
srf_data = []
# Update model(s)
for sample_number, all_discrete_function_data in samplenum_to_discretefunctiondata.items():
# Update materials
DFHelpers.updateModelMaterialsWithDiscreteFunctionData(model,all_discrete_function_data)
# Save as a new file if required
if save_new_sample_models:
new_sample_file_name = f'{sample_number}.fez'
new_sample_file_path = os.path.join(samples_dir, new_sample_file_name)
model.saveAs(new_sample_file_path)
else:
model.save()
# Compute if required
model.compute()
# Extract results
if save_new_sample_models:
model_results = interpreter.openFile(new_sample_file_path)
else:
model_results = interpreter.openFile(base_sample_file_path)
sample_nums.append(sample_number)
srf_data.append(model_results.getCriticalSRF())
model_results.close()
# Set use_previously_computed_sample_results = True to use previously computed results to save time
else:
# Extracted data for 100 samples with a fine mesh
sample_nums =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
srf_data =[1.022, 1.071, 1.083, 1.042, 0.952, 1.04, 1.073, 1.115, 1.074, 1.023, 1.074, 1.014, 0.988, 0.975, 1.118, 1.05, 1.108, 1.011, 1.076, 1.085, 1.032, 1.012, 1.023, 1.09, 1.068, 1.05, 1.012, 1.087, 1.007, 1.044, 1.044, 1.006, 1.059, 1.004, 1.051, 1.095, 0.978, 1.076, 1.14, 1.051, 1.069, 1.041, 1.082, 1.067, 1.124, 1.035, 1.032, 1.09, 1.05, 1.186, 0.982, 1.016, 1.049, 1.017, 1.018, 1.07, 1.033, 1.038, 1.094, 1.153, 1.089, 1.131, 1.12, 1.035, 1.137, 1.016, 1.132, 1.02, 1.069, 1.115, 0.984, 1.058, 1.067, 1.076, 1.101, 1.056, 1.091, 1.13, 1.092, 1.002, 1.001, 0.98, 1.103, 1.081, 1.045, 1.079, 1.05, 0.952, 1.065, 1.076, 1.068, 1.047, 1.048, 0.996, 1.015, 0.998, 1.09, 1.017, 0.955, 1.025]
print(sample_nums)
print(srf_data)
# Analyze sample results
if analyze_results:
# SRF where values below are considered failure
srf_failure_threshold = 1
# Get typical statistical values
srf_mean = np.mean(srf_data)
srf_std = np.std(srf_data)
srf_min = np.min(srf_data)
srf_max = np.max(srf_data)
# Get srf values below threshold of failure
srf_failed = [srf for srf in srf_data if srf < srf_failure_threshold]
# Get probability of failure
srf_prob_of_failure = len(srf_failed)/len(srf_data) * 100
# Get reliability index
# assuming a normal distribution of SRF of values
srf_reliability_index_norm = (srf_mean-srf_failure_threshold)/srf_std
# assuming a lognormal distribution of SRF values
coeff_of_variation = srf_std/srf_mean
srf_reliability_index_log = np.log(srf_mean/np.sqrt(1+coeff_of_variation*coeff_of_variation))/np.sqrt(np.log(1+coeff_of_variation*coeff_of_variation))
# Create histogram
# Create the histogram for all data
srf_counts, bins = np.histogram(srf_data, bins=30, density=True)
plt.hist(bins[:-1], bins, weights=srf_counts, color='blue', edgecolor='black', alpha=0.7, label=f'Other Data')
# Create the histogram for the highlighted region
highlighted_data = [srf for srf in srf_data if srf < srf_failure_threshold]
if len(highlighted_data) > 0:
highlighted_counts = []
highlighted_bins = []
for i in range(len(bins)-1):
if bins[i] < srf_failure_threshold:
highlighted_counts.append(srf_counts[i])
highlighted_bins.append(bins[i])
else:
break
highlighted_bins.append(bins[i])
plt.hist(highlighted_bins[:-1], highlighted_bins, weights=highlighted_counts, color='orange', edgecolor='black', alpha=0.7, label=f'Highlighted')
# Add labels and legend
plt.xlabel('Factor of Safety (Critical SRF)')
plt.ylabel('Relative Frequency')
plt.title(f"Highlighted Data = Factor of Safety < {srf_failure_threshold} ({len(highlighted_data)} points)")
plt.legend(loc='upper right', bbox_transform=plt.gca().transAxes)
# Adjust the layout to make room for more items
#plt.subplots_adjust(bottom=0.5)
# Add textboxes with information about the plot
info_text = (
f'mean = {srf_mean:.3f}\n'
f's.d. = {srf_std:.3f}\n'
f'min = {srf_min:.3f}\n'
f'max = {srf_max:.3f}\n'
f'PF = {srf_prob_of_failure:.3f}%\n'
f'RI(norm) = {srf_reliability_index_norm:.3f}\n'
f'RI(log) = {srf_reliability_index_log:.3f}\n'
)
# Place the textbox on the plot
plt.text(0.05, 0.95, info_text, fontsize=12,
horizontalalignment='left', verticalalignment='top', transform=plt.gca().transAxes)
# Show the plot
plt.show()
print('done!')
import os
import re
import csv
from rs2.modeler import Model
from rs2.modeler.properties.PropertyEnums import *
from rs2.modeler.properties.DiscreteFunction import DiscreteFunction
"""
Example of how you might write helper classes to support creating
discrete function in RS2 using company-specific data files.
Please read each class carefully. Only the main functionalities required
for the Statistical Analysis example are defined below.
You may need to add functionality depending on your project requirements
"""
class DiscreteFunctionDataPoint:
"""
The DiscreteFunctionDataPoint object stores the table values to generate
a discrete strength function in RS2.
Args:
x(float): x coordinate
y(float): y coordinate
Mohr-Coulomb parameters:
coh(float): cohesion
phi(flaot): friction angle
Stiffness parameters:
mod(float): elastic modulus
res_mod(float): residual elastic modulus
Attributes:
x(float): x coordinate
y(float): y coordinate
Mohr-Coulomb parameters:
coh(float): cohesion
phi(flaot): friction angle
Stiffness parameters:
mod(float): elastic modulus
res_mod(float): residual elastic modulus
"""
def __init__(self, **kwargs):
self.x = float(kwargs.get('x', 0))
self.y = float(kwargs.get('y', 0))
self.coh = float(kwargs.get('coh', 0))
self.phi = float(kwargs.get('phi', 0))
self.mod = float(kwargs.get('mod', 0))
self.res_mod = float(kwargs.get('res_mod', 0))
def __repr__(self):
return (f"DiscreteFunctionDataPoint(x={self.x}, y={self.y}, coh={self.coh}, phi={self.phi}, "
f"mod={self.mod}, res_mod={self.res_mod}, ")
class DiscreteFunctionData:
"""
The DiscreteFunctionData object stores all possible fields exported
from RS2 or Slide2 used to create or update a discrete strength function in RS2.
Args:
name (str): The name of the discrete function.
id (int): The ID of the discrete function.
sample_num (int): The sample number.
res_strength_factor (float): The residual strength factor.
tensile_strength (float): The tensile strength.
residual_tensile_strength (float): The residual tensile strength.
modulus (float): The modulus.
modulus_res (float): The residual modulus.
correlation_flag (int): The correlation flag.
correlation_num (float): The correlation number.
randomize_selection (int): The randomize selection type
randomize_num (int): The approximate number of points for randomizing
c_dist (float): The cohesion distribution.
c_mean (float): The cohesion mean.
c_stdv (float): The cohesion standard deviation.
c_min (float): The cohesion minimum value.
c_max (float): The cohesion maximum value.
phi_dist (float): The friction angle distribution.
phi_mean (float): The friction angle mean.
phi_stdv (float): The friction angle standard deviation.
phi_min (float): The friction angle minimum value.
phi_max (float): The friction angle maximum value.
mod_dist (float): The modulus distribution.
mod_mean (float): The modulus mean.
mod_stdv (float): The modulus standard deviation.
mod_min (float): The modulus minimum value.
mod_max (float): The modulus maximum value.
mod_res_dist (float): The residual modulus distribution.
mod_res_mean (float): The residual modulus mean.
mod_res_stdv (float): The residual modulus standard deviation.
mod_res_min (float): The residual modulus minimum value.
mod_res_max (float): The residual modulus maximum value.
strength_type (str): The strength or function type, such as drained or undrained.
interpolation (str): The interpolation type.
symbol_type (str): The symbol type.
fill_interior (str): The fill interior type.
color_interior (int): The interior color represented as RGB values converted to a rgb_as_single_int integer value.
color_exterior (int): The exterior color represented as RGB values converted to a rgb_as_single_int integer value.
points (int): The number of points.
data_points (list[DiscreteFunctionDataPoint]): A list of data points for the discrete function.
Attributes:
name (str): The name of the discrete function.
id (int): The ID of the discrete function.
sample_num (int): The sample number.
res_strength_factor (float): The residual strength factor.
tensile_strength (float): The tensile strength.
residual_tensile_strength (float): The residual tensile strength.
modulus (float): The modulus.
modulus_res (float): The residual modulus.
correlation_flag (int): The correlation flag.
correlation_num (float): The correlation number.
randomize_selection (int): The randomize selection type
randomize_num (int): The approximate number of points for randomizing
c_dist (float): The cohesion distribution.
c_mean (float): The cohesion mean.
c_stdv (float): The cohesion standard deviation.
c_min (float): The cohesion minimum value.
c_max (float): The cohesion maximum value.
phi_dist (float): The friction angle distribution.
phi_mean (float): The friction angle mean.
phi_stdv (float): The friction angle standard deviation.
phi_min (float): The friction angle minimum value.
phi_max (float): The friction angle maximum value.
mod_dist (float): The modulus distribution.
mod_mean (float): The modulus mean.
mod_stdv (float): The modulus standard deviation.
mod_min (float): The modulus minimum value.
mod_max (float): The modulus maximum value.
mod_res_dist (float): The residual modulus distribution.
mod_res_mean (float): The residual modulus mean.
mod_res_stdv (float): The residual modulus standard deviation.
mod_res_min (float): The residual modulus minimum value.
mod_res_max (float): The residual modulus maximum value.
strength_type (str): The strength or function type, such as drained or undrained.
interpolation (str): The interpolation type.
symbol_type (str): The symbol type.
fill_interior (str): The fill interior type.
color_interior (int): The interior color represented as RGB values converted to a rgb_as_single_int integer value.
color_exterior (int): The exterior color represented as RGB values converted to a rgb_as_single_int integer value.
points (int): The number of points.
data_points (list[DiscreteFunctionDataPoint]): A list of data points for the discrete function.
"""
def __init__(self, **kwargs):
self.name = kwargs.get('name', '')
self.id = int(kwargs.get('id', -1))
self.sample_num = int(kwargs.get('sample_num', -1))
self.res_strength_factor = float(kwargs.get('res_strength_factor', 0.0))
self.tensile_strength = float(kwargs.get('tensile_strength', 0.0))
self.residual_tensile_strength = float(kwargs.get('residual_tensile_strength', 0.0))
self.modulus = float(kwargs.get('modulus', 0.0))
self.modulus_res = float(kwargs.get('modulus_res', 0.0))
self.correlation_flag = int(kwargs.get('correlation_flag', 0))
self.correlation_num = float(kwargs.get('correlation_num', 0.0))
self.randomize_selection = int(kwargs.get('randomize_selection', 0))
self.randomize_num = int(kwargs.get('randomize_num', 0))
self.c_dist = float(kwargs.get('c_dist', 0.0))
self.c_mean = float(kwargs.get('c_mean', 0.0))
self.c_stdv = float(kwargs.get('c_stdv', 0.0))
self.c_min = float(kwargs.get('c_min', 0.0))
self.c_max = float(kwargs.get('c_max', 0.0))
self.phi_dist = float(kwargs.get('phi_dist', 0.0))
self.phi_mean = float(kwargs.get('phi_mean', 0.0))
self.phi_stdv = float(kwargs.get('phi_stdv', 0.0))
self.phi_min = float(kwargs.get('phi_min', 0.0))
self.phi_max = float(kwargs.get('phi_max', 0.0))
self.mod_dist = float(kwargs.get('mod_dist', 0.0))
self.mod_mean = float(kwargs.get('mod_mean', 0.0))
self.mod_stdv = float(kwargs.get('mod_stdv', 0.0))
self.mod_min = float(kwargs.get('mod_min', 0.0))
self.mod_max = float(kwargs.get('mod_max', 0.0))
self.mod_res_dist = float(kwargs.get('mod_res_dist', 0.0))
self.mod_res_mean = float(kwargs.get('mod_res_mean', 0.0))
self.mod_res_stdv = float(kwargs.get('mod_res_stdv', 0.0))
self.mod_res_min = float(kwargs.get('mod_res_min', 0.0))
self.mod_res_max = float(kwargs.get('mod_res_max', 0.0))
self.strength_type = kwargs.get('strength_type', '')
self.interpolation = kwargs.get('interpolation', '')
self.symbol_type = kwargs.get('symbol_type', '')
self.fill_interior = kwargs.get('fill_interior', '')
self.color_interior = kwargs.get('color_interior', (255, 255, 255))
self.color_exterior = kwargs.get('color_exterior', (0, 0, 0))
self.points = int(kwargs.get('points', 0))
self.data_points = kwargs.get('data_points', [])
def __repr__(self):
return (f"DiscreteFunctionData(name={self.name}, id={self.id}, res_strength_factor={self.res_strength_factor}, "
f"tensile_strength={self.tensile_strength}, residual_tensile_strength={self.residual_tensile_strength}, "
f"modulus={self.modulus}, modulus_res={self.modulus_res}, correlation_flag={self.correlation_flag}, "
f"correlation_num={self.correlation_num}, randomize_selection={self.randomize_selection}, "
f"randomize_num={self.randomize_num}, c_dist={self.c_dist}, c_mean={self.c_mean}, c_stdv={self.c_stdv}, "
f"c_min={self.c_min}, c_max={self.c_max}, phi_dist={self.phi_dist}, phi_mean={self.phi_mean}, "
f"phi_stdv={self.phi_stdv}, phi_min={self.phi_min}, phi_max={self.phi_max}, mod_dist={self.mod_dist}, "
f"mod_mean={self.mod_mean}, mod_stdv={self.mod_stdv}, mod_min={self.mod_min}, mod_max={self.mod_max}, "
f"mod_res_dist={self.mod_res_dist}, mod_res_mean={self.mod_res_mean}, mod_res_stdv={self.mod_res_stdv}, "
f"mod_res_min={self.mod_res_min}, mod_res_max={self.mod_res_max}, strength_type={self.strength_type}, "
f"interpolation={self.interpolation}, symbol_type={self.symbol_type}, fill_interior={self.fill_interior}, "
f"color_interior={self.color_interior}, color_exterior={self.color_exterior}, points={self.points}, "
f"data_points={self.data_points})")
class DiscreteFunctionHelpers:
"""
The DiscreteFunctionHelpers object provides supporting functionality to
create or update discrete functions in RS2. These include functions
to read and write data related to discrete functions
Args: None
Attributes: None
"""
@staticmethod
def readDiscreteFunctionFilefn6(file_path: str, extract_sample_num_from_name:bool=True)->DiscreteFunctionData:
"""
Initialize DiscreteFunctionData object from fn6 file type
Args:
file_path(str): full file path to data file
extract_sample_num_from_name(bool): set True if file name is written as "NAME Sample 1.fn6" where "Sample" is a keyword indicating
the next string is the sample number, False otherwise
Raises: None
Returns:
a DiscreteFunctionData object that is initialized using read data from the given 'file_path' or
a DiscreteFunctionData object with default values if file is not found.
"""
data = {}
with open(file_path, 'r') as file:
for line in file:
line = line.strip()
if line.startswith('color'):
# color interior/exterior information, assuming rgb format
match = re.match(r'(\w[\w\s]*):\s+(.*)', line)
if match:
key = match.group(1).strip().replace(' ', '_')
value = match.group(2).strip()
# store rgb as a tuple of three integers
r, g, b = map(int, re.findall(r'\d+', value))
value = (int(r),int(g),int(b))
data[key] = value
else:
# header information assuming key: value format, keys and values can be multiple words
# this is a key: this is a value
# key must be alphanumeric character from the basic Latin alphabet, including the underscore [A-Za-z0-9_]
# value can be any character
match = re.match(r'(\w[\w\s]*):\s+(.*)', line)
if match:
key = match.group(1).strip().replace(' ', '_')
value = match.group(2).strip()
data[key] = value
if key == 'points':
# data points below the header information, only works if keys and values are not multiple words
# ex. x: 1 y: 2 c: 3 d: 4 e: 5
data_points = []
for _ in range(int(value)):
try:
line = next(file).strip()
key_value_pairs = re.findall(r'(\w[\w]*:\s+\S[\S]*)', line)
data_point_data = {}
for key_value_pair in key_value_pairs:
key, value = key_value_pair.split(':')
key = key.strip()
value = value.strip()
data_point_data[key] = value
data_points.append(DiscreteFunctionDataPoint(**data_point_data))
except StopIteration:
print("Reached end of file before expected number of points read")
break
data['data_points'] = data_points
# Create new discrete function data object
discrete_function_data = DiscreteFunctionData(**data)
# If you have the sample number as part of the name, you can extract it as follows
# ex. "Sandy Clay Sample 1.fn6" where "Sample" is a keyword and "1" is the value
if extract_sample_num_from_name and discrete_function_data.name:
# Extract material name and sample number
material_name = discrete_function_data.name # "Sandy Clay Sample 1"
sample_num = -1 # default if no sample number found
sample_num_str = ""
# Find the index of the last occurrence of "Sample"
index_sample = material_name.rfind("Sample")
# If "Sample" is found in the text
if index_sample != -1:
# Split the text at the found index
sample_num_str = material_name[index_sample:] # "Sample 1"
sample_num_str = sample_num_str.split()[-1].strip() # "1"
material_name = material_name[:index_sample].strip() # "Sandy Clay"
# Validate sample number found
try:
sample_num = int(sample_num_str)
except ValueError:
print(f'Sample number not found from {file_path} sample number of -1 used')
# Separate name and sample number in separate fields
discrete_function_data.name = material_name
discrete_function_data.sample_num = sample_num
return discrete_function_data
@staticmethod
def writeDiscreteFunctionFilefn6(output_file_path: str, df_data: DiscreteFunctionData):
"""
Write 'df_data' using the fn6 format to the file found in 'output_file_path'.
Does nothing if output file not found.
FOR CHECKING VALUES ONLY. File not expect to be imported directly into the RS2 discrete function feature
Args:
output_file_path(str): full file path to output data
df_data(DiscreteFunctionData): discrete function data object
Raises: None
Returns: None
"""
with open(output_file_path, 'w') as file:
file.write(f'Slide Discrete Function\n')
file.write(f' name: {df_data.name}')
if (df_data.sample_num > 0):
file.write(f' Sample {df_data.sample_num}')
file.write('\n')
file.write(f' strength type: {df_data.strength_type}\n')
file.write(f' interpolation: {df_data.interpolation}\n')
file.write(f' symbol type: {df_data.symbol_type}\n')
file.write(f' fill interior: {df_data.fill_interior}\n')
file.write(f' color interior: r: {df_data.color_interior[0]} g: {df_data.color_interior[1]} b: {df_data.color_interior[2]}\n')
file.write(f' color exterior: r: {df_data.color_exterior[0]} g: {df_data.color_exterior[1]} b: {df_data.color_exterior[2]}\n')
file.write(f' points: {df_data.points}\n')
for data_point in df_data.data_points:
file.write(f' x: {data_point.x} y: {data_point.y} coh: {data_point.coh} phi: {data_point.phi}\n')
@staticmethod
def readDiscreteFunctionFileCustom(file_path)->list[DiscreteFunctionData]:
"""
Initialize a list of DiscreteFunctionData objects from a custom data file format
Args:
file_path(str): full file path to data file
Raises: None
Returns:
a list of DiscreteFunctionData objects that is initialized using read data from the given 'file_path'.
an empty list if file is not found.
"""
all_discrete_function_data = []
with open(file_path, mode='r', newline='') as csvfile:
reader = csv.reader(csvfile)
# Extract header
header_row = next(reader)
name = header_row[0].strip()
headers = [header.strip() for header in header_row[2:] if header]
# read data till end of file
row = next(reader,None)
while row is not None:
if not any(row):
continue # skip empty rows
try:
sample_tag, sample_number_str = row[0].split()
if sample_tag != 'Sample':
raise ValueError('Expected sample data information')
sample_number = int(sample_number_str)
except ValueError:
print(f'Unexpected line in {file_path}')
return all_discrete_function_data
# Initialize the data points list
data_points = []
while (row:=next(reader,None)) is not None:
if not any(row):
continue # skip empty rows
if row[0].strip().split()[0] == 'Sample':
break
# Create a dictionary to map headers to row values
row_data = {headers[i]: row[i] for i in range(len(headers))}
# Create a DataPoint instance
data_point = DiscreteFunctionDataPoint(
x=row_data.get('x', 0),
y=row_data.get('y', 0),
coh=row_data.get('cohesion', 0),
phi=row_data.get('phi', None)
)
data_points.append(data_point)
new_df = DiscreteFunctionData(name=name, sample_num=sample_number, data_points=data_points)
all_discrete_function_data.append(new_df)
# Create and return the DiscreteFunctionData instance
return all_discrete_function_data
@staticmethod
def writeDiscreteFunctionFileCustom(output_file_path: str, all_df_data: list[DiscreteFunctionData]):
"""
Write 'all_df_data' in a custom format to the file found in 'output_file_path'.
Does nothing if output file not found.
Good for checking read values
Args:
output_file_path(str): full file path to output data
all_df_data(list[str]): list of discrete function data objects
Raises: None
Returns: None
"""
with open(output_file_path, 'w', newline='') as file:
if all_df_data:
writer = csv.writer(file)
# write header
df_data_first = all_df_data[0]
writer.writerow([df_data_first.name,'','x','y','cohesion','phi'])
for df_data in all_df_data:
writer.writerow([f'Sample {df_data.sample_num}'])
for data_point in df_data.data_points:
x = int(data_point.x) if data_point.x.is_integer() else data_point.x
y = int(data_point.y) if data_point.y.is_integer() else data_point.y
coh = int(data_point.coh) if data_point.coh.is_integer() else data_point.coh
phi = int(data_point.phi) if data_point.phi.is_integer() else data_point.phi
writer.writerow([x,y,coh,phi])
@staticmethod
def readDiscreteFunctionFile(file_path:str)->list[DiscreteFunctionData]:
"""
Initialize a list of discrete function data objects using data read from a file based on its extension
Args:
file_path(str): full file path to data file
all_df_data(list[str]): list of discrete function data objects
Raises: None
Returns:
A list of DiscreteFunctionData objects that is initialized using read data from the given 'file_path'.
An empty list if file is not found or file extension type is not supported
"""
all_discrete_function_data = []
# Read data
root, extension = os.path.splitext(file_path)
if extension == '.fn6':
# RS2 or Slide2 exported discrete function type
all_discrete_function_data.append(DiscreteFunctionHelpers.readDiscreteFunctionFilefn6(file_path))
elif extension =='.cust':
# Custom file type
all_discrete_function_data.extend(DiscreteFunctionHelpers.readDiscreteFunctionFileCustom(file_path))
else:
print(f'{discrete_function_file_path} has an unsuported extension')
return []
return all_discrete_function_data
@staticmethod
def getDiscreteFunctionDataPerSample(discrete_function_file_paths: list[str])->dict[int,list[DiscreteFunctionData]]:
"""
Read discrete function data from files and return a dictionary of data per sample id
Args:
discrete_function_file_paths (list[str]): A list of file paths containing discrete function data.
Raises:
None
Returns:
A dictionary of sample id to a list of DiscreteFunctionData objects
"""
# Collect all discrete function file data to minimize number of times models are opened in update
samplenum_to_discretefunctiondata = {}
for discrete_function_file_path in discrete_function_file_paths:
# Get list of discrete_function_data to update model
all_discrete_function_data = DiscreteFunctionHelpers.readDiscreteFunctionFile(discrete_function_file_path)
for discrete_function_data in all_discrete_function_data:
if discrete_function_data.sample_num in samplenum_to_discretefunctiondata:
samplenum_to_discretefunctiondata[discrete_function_data.sample_num].append(discrete_function_data)
else:
samplenum_to_discretefunctiondata[discrete_function_data.sample_num] = [discrete_function_data]
return samplenum_to_discretefunctiondata
@staticmethod
def updateDiscreteFunction(df_data: DiscreteFunctionData, df: DiscreteFunction)->bool:
"""
Update df, discrete function object outputted by RS2, using the data stored in df_data
Only MAIN functionality is implemented in this example. Please update if there are
discrete function parameters you want to update which aren't implemented.
Args:
file_path(str): full file path to data file
all_df_data(list[str]): list of discrete function data objects
Raises: None
Returns:
True if discrete function was updated with no warnings or issues, False othwerise
"""
# Only set parameters which are initialized in the input data
# Set general parameters, mostly leave as default if importing from Slide 2
curr_params = df.getFunctionParameters()
# Set function type, other function parameters NOT IMPLEMENTED
function_type = DiscreteDrainedMode.DRAINED #drained default
if df_data.strength_type == 'drained':
function_type = DiscreteDrainedMode.DRAINED
elif df_data.strength_type == 'undrained':
function_type = DiscreteDrainedMode.UNDRAINED
df.setFunctionParameters(function_type, curr_params[1],curr_params[2],curr_params[3],curr_params[4],curr_params[5])
# Set interpolation method
if df_data.interpolation == 'thin plate spline':
df.setInterpolationMethod(InterpolationMethod.THIN_PLATE_SPLINE)
elif df_data.interpolation == 'chugh':
df.setInterpolationMethod(InterpolationMethod.MODIFIED_CHUGH)
elif df_data.interpolation == 'local thin plate':
df.setInterpolationMethod(InterpolationMethod.LOCAL_THIN_PLATE_SPLINE)
elif df_data.interpolation == 'tin triangulation':
df.setInterpolationMethod(InterpolationMethod.TIN_TRIANGULATION)
elif df_data.interpolation == 'inverse distance':
df.setInterpolationMethod(InterpolationMethod.INVERSE_DISTANCE)
elif df_data.interpolation == 'linear by elevation':
df.setInterpolationMethod(InterpolationMethod.LINEAR_BY_ELEVATION)
elif df_data.interpolation == 'original chugh':
df.setInterpolationMethod(InterpolationMethod.CHUGH)
# Set symbol drawing, NOT IMPLEMENTED
# Set point locations, add more data types as needed
point_locations = []
coh_list = []
phi_list = []
mod_list = []
res_mod_list = []
for data_point in df_data.data_points:
point_locations.append((data_point.x, data_point.y))
coh_list.append(data_point.coh)
phi_list.append(data_point.phi)
mod_list.append(data_point.mod)
res_mod_list.append(data_point.res_mod)
if point_locations:
df.setPointLocations(point_locations)
if len(coh_list) != len(point_locations):
print(f'Sample {df_data.sample_num} for {df_data.name} discrete function has invalid number of cohesion values compared to point locations')
return False
df.setPointsC(coh_list)
if len(phi_list) != len(point_locations):
print(f'Sample {df_data.sample_num} for {df_data.name} discrete function has invalid number of phi values compared to point locations')
return False
df.setPointsPhi(phi_list)
if len(mod_list) != len(point_locations):
print(f'Sample {df_data.sample_num} for {df_data.name} discrete function has invalid number of modulus values compared to point locations')
return False
df.setPointsModulus(mod_list)
if len(res_mod_list) != len(point_locations):
print(f'Sample {df_data.sample_num} for {df_data.name} discrete function has invalid number of residual modulus values compared to point locations')
return False
df.setPointsModulusResidual(res_mod_list)
return True
@staticmethod
def updateModelMaterialsWithDiscreteFunctionData(model: Model, all_discrete_function_data: list[DiscreteFunctionData]):
"""
Update materials in model object with discrete function data of same name
Args:
model(Model): RS2 modeler Model object
all_discrete_function_data(list[DiscreteFunctionData]): list of discrete function data objects to use
Raises: None
Returns: None
"""
# Update materials
for discrete_function_data in all_discrete_function_data:
# Get header values
material_name = discrete_function_data.name
# Find discrete function
try:
discrete_function = model.getDiscreteFunctionByName(material_name)
except RuntimeError:
# Create new if not found
model.createNewDiscreteFunction(material_name)
discrete_function = model.getDiscreteFunctionByName(material_name)
# Update with read data
DiscreteFunctionHelpers.updateDiscreteFunction(discrete_function_data, discrete_function)
# Change material to use new discrete function of same name
try:
material = model.getMaterialPropertyByName(material_name)
material.Strength.setFailureCriterion(StrengthCriteriaTypes.DISCRETE_FUNCTION)
material.Strength.DiscreteFunction.setSelectedDiscreteFunctionByName(material_name)
except ValueError:
print(f'material {material_name} not found in {discrete_function_file_path}, file not used')
continue
import os
class FileUtilities:
@staticmethod
def findFilesWithExtension(directory:str, extension:str):
"""
Recursively find all files with a given extension in the directory and its subdirectories.
Args:
directory(str): full path to folder directory to carry out search
extension(str): file extension for search with or without the period at the beginning
Raises: None
Returns:
list of file paths found with extension
"""
# Add a period to the extension if it doesn't start with one
if not extension.startswith('.'):
extension = '.' + extension
matching_files = []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(extension):
matching_files.append(os.path.join(root, file))
return matching_files
import os
from DiscreteFunctionHelpers import DiscreteFunctionHelpers as DFHelpers
from FileUtilities import FileUtilities as FileUtil
"""
This example script shows how to read discrete function data and then check
the output by writing to a file using the DiscreteFunctionHelpers class
"""
# Current folder location
current_dir = os.path.dirname(os.path.abspath(__file__))
# Output folder
output_dir = rf'{current_dir}\read data'
os.makedirs(output_dir, exist_ok=True)
# Get all discrete function files (.fn6) from folder
fn6_files = FileUtil.findFilesWithExtension(os.path.join(current_dir,'data'),'fn6')
# Read and write data
for fn6_file in fn6_files:
all_df = DFHelpers.readDiscreteFunctionFile(fn6_file)
base_name = os.path.basename(fn6_file)
for df in all_df:
DFHelpers.writeDiscreteFunctionFilefn6(os.path.join(output_dir,f'read {base_name}'),df)
# Get all custom files (.cust) from folder
cust_files = FileUtil.findFilesWithExtension(os.path.join(current_dir,'data'),'cust')
# Read and write data
for cust_file in cust_files:
all_df = DFHelpers.readDiscreteFunctionFile(cust_file)
base_name = os.path.basename(cust_file)
DFHelpers.writeDiscreteFunctionFileCustom(os.path.join(output_dir,f'read {base_name}'),all_df)