import numpy as np
from numpy.random import multivariate_normal
class BR:
def __init__(self, omega_all, phi_all, CV_omega, sigma_phi, prior, N = 10**4, print_code = False):
# all parameters for later use
self.omega_all = omega_all
self.phi_all = phi_all
# No damage parameters
self.omega_H0 = omega_all[0]
self.phi_H0 = phi_all[0]
# Damage parameters
self.omega_H1 = omega_all[1:]
self.phi_H1 = phi_all[1:]
# Coefficient of variation for omega and phi
self.CV_omega = CV_omega
self.sigma_phi = sigma_phi
# number of Monte Carlo samples
self.N = N
# prior probabilities for theta
self.prior = prior
# print code
self.print_code = print_code
# def feature_vector(self, features_vector):
# return
# Statical parameters for the multivariate normal distribution
def covariance_matrix(self, S):
# Sensor vector
S = np.array(S)
# Standard deviation
sigma_omega = self.CV_omega * self.omega_H0
sigma_phi = self.sigma_phi * np.ones(self.phi_H0.shape[0])
# modal observability
mo = np.array([np.linalg.norm(np.multiply(S, phi_i)) for phi_i in self.phi_H0])
# Variance Omega
var_omega = sigma_omega**2 / (mo**2)
# Variance Phi
var_phi = sigma_phi**2 / (mo**2)
# Diagonal covariance matrix
# mu = np.concatenate([self.omega_H0, self.mask_and_normalize(self.phi_H0, S).flatten()]) # mean vector
mu = self.mu_vector(self.omega_H0, self.phi_H0, S) # mean vector
diagonal = np.concatenate([var_omega, np.repeat(var_phi, np.sum(S, dtype=int))]) # variances for omega and phi
cov = np.diag(diagonal) # covariance matrix
# save for later use
self.mu = mu
self.cov = cov
def mu_vector(self, omega, phi, S):
# Sensor vector
S = np.array(S)
# mean vector
mu = np.concatenate([omega, self.mask_and_normalize(phi, S).flatten()]) # mean vector
return mu
def mask_and_normalize(self,X, S):
mask = S == 1
# Step 1: apply mask on last axis
filtered = X[..., mask]
# Step 2: compute max along last axis
max_vals = np.max(np.abs(filtered), axis=-1, keepdims=True)
# Step 3: avoid division by zero
max_vals[max_vals == 0] = 1
# Step 4: normalize
return filtered / max_vals
def MCS_modal(self, S, prior_Importance = None):
# S is the sensor vector
S = np.array(S)
# get covariance matrix and mean vector
self.covariance_matrix(S)
if prior_Importance is not None:
# Imoportance sampling for structural states: theta ~ Q(theta) where Q is the importance distribution
states = np.random.choice(range(len(self.prior)), size=self.N, p=prior_Importance)
H_vec = (states > 0).astype(int) # 0 if state == 0, else 1
self.states = states
self.H_vec = H_vec
elif prior_Importance is None:
# Generate all states and H labels at once: theta ~ P(theta)
states = np.random.choice(range(len(self.prior)), size=self.N, p=self.prior)
H_vec = (states > 0).astype(int) # 0 if state == 0, else 1
self.states = states
self.H_vec = H_vec
# generate errors for omega and phi
# errors = np.random.multivariate_normal(mean=np.zeros(self.cov.shape[0]), cov=self.cov, size=self.N)
# model properties
# lambda_star = np.random.multivariate_normal(mean=self.mu, cov=self.cov, size=self.N)
lambda_star = np.random.multivariate_normal(mean=np.zeros(self.cov.shape[0]), cov=self.cov, size=self.N)
lambda_star[:, :len(self.omega_H0)] += self.omega_all[states]
lambda_star[:, len(self.omega_H0):] += self.mask_and_normalize(self.phi_all[states], S).reshape(self.N, -1)
self.lambda_star = lambda_star
# extract omega and phi from lambda_star
omega_bar = lambda_star[:, :self.omega_H0.shape[0]]
# phi_bar = lambda_star[:, self.omega_H0.shape[0]:]
phi_bar = lambda_star[:, self.omega_H0.shape[0]:].reshape(self.N, self.phi_H0.shape[0], np.sum(S, dtype=int))
self.phi_no_norm = phi_bar.copy() # save the non-normalized phi for later use
# normize phi_bar by by the maximum absolute value across all samples for each mode
# normalize each mode-shape vector per sample (along the last axis)
max_abs = np.max(np.abs(phi_bar), axis=2, keepdims=True) # shape: (N, N_phi, 1)
phi_bar = phi_bar / np.where(max_abs == 0, 1, max_abs)
# phi_bar = self.mask_and_normalize(phi_bar, S)
self.omega_bar = omega_bar
self.phi_bar = phi_bar
def MAC(self, phi1, phi2):
# Compute the Modal Assurance Criterion (MAC) between two mode shapes
numerator = np.abs(np.dot(phi1, phi2))**2
denominator = np.dot(phi1, phi1) * np.dot(phi2, phi2)
return numerator / denominator if denominator != 0 else 0
def TMAC(self, phi_theta_1, phi_theta_2):
N_samples = phi_theta_1.shape[0]
TMAC_matrix = np.zeros((N_samples, N_samples))
for i in range(N_samples):
for j in range(N_samples):
TMAC_matrix[i, j] = self.MAC(phi_theta_1[i], phi_theta_2[j])
# sum of diagonal of the MAC
self.print2("TMAC matrix:\n", TMAC_matrix)
TMAC = np.sum(np.diag(TMAC_matrix)) / (N_samples)
return TMAC
# Modal Flexibility (MF)
def MF(self, phi_vec, omega_vec):
# Compute the Modal Flexibility (MF) for a given mode shape and natural frequency
return sum(np.diag(np.abs(np.dot(phi_vec, phi_vec.T) / (omega_vec**2))))
def print2(self, *args):
if self.print_code:
print(*args)