def write_gmm_data_file(model_name, mag, dist, result_type, periods, file_out, component_type="AVERAGE_HORIZONTAL",): """ Create a file of input and output parameters for the sommerville GMM. params: model_name: The ground motion model, as a string. mag: dictionary, key - the mag column name, values, the mag vectors, as a list dist: dictionary, key - the distance column name, value, the distance vectors, as a list. result_type: MEAN or TOTAL_STDDEV periods: A list of periods requiring SA values. The first value has to be 0.0. Mag, distance and periods will be iterated over to give a single SA for each combination. file_out: The file name and location of the produced data file. """ assert periods[0] == 0.0 handle = open(file_out, 'wb') writer = csv.writer(handle, delimiter=',', quoting=csv.QUOTE_NONE) # write title title = [mag[0], dist[0], 'result_type', 'component_type'] + periods[1:] + \ ['pga'] writer.writerow(title) # prepare the coefficients model = Ground_motion_specification(model_name) coeff = model.calc_coefficient(periods) coeff = reshape(coeff, (coeff.shape[0], 1, 1, coeff.shape[1])) sigma_coeff = model.calc_sigma_coefficient(periods) sigma_coeff = reshape(sigma_coeff, (sigma_coeff.shape[0], 1, 1, sigma_coeff.shape[1])) # Iterate for magi in mag[1]: for disti in dist[1]: dist_args = {'mag': array([[[magi]]]), dist[0]: array([[[disti]]]), 'coefficient': coeff, 'sigma_coefficient': sigma_coeff} log_mean, log_sigma = model.distribution(**dist_args) sa_mod = list(log_mean.reshape(-1)) sa_mod = [math.exp(x) for x in sa_mod] sigma_mod = list(log_sigma.reshape(-1)) if result_type == 'MEAN': row = [magi, disti, result_type, component_type] + \ sa_mod[1:] + \ [sa_mod[0]] else: row = [magi, disti, result_type, component_type] + \ sigma_mod[1:] + \ [sigma_mod[0]] writer.writerow(row) handle.close()
def write_gmm_data_file_depth( model_name, mag, dist, depth, result_type, periods, file_out, component_type="AVERAGE_HORIZONTAL" ): """ Create a file of input and output parameters for the sommerville GMM. params: model_name: The ground motion model, as a string. mag: dictionary, key - the mag column name, values, the mag vectors, as a list dist: dictionary, key - the distance column name, value, the distance vectors, as a list. depth: depth in km. result_type: MEAN or TOTAL_STDDEV periods: A list of periods requiring SA values. The first value has to be 0.0. Mag, distance and periods will be iterated over to give a single SA for each combination. file_out: The file name and location of the produced data file. """ assert periods[0] == 0.0 handle = open(file_out, "wb") writer = csv.writer(handle, delimiter=",", quoting=csv.QUOTE_NONE) # write title title = [depth[0], mag[0], dist[0], "result_type", "component_type"] + periods[1:] + ["pga"] writer.writerow(title) # prepare the coefficients model = Ground_motion_specification(model_name) coeff = model.calc_coefficient(periods) coeff = reshape(coeff, (coeff.shape[0], 1, 1, coeff.shape[1])) sigma_coeff = model.calc_sigma_coefficient(periods) sigma_coeff = reshape(sigma_coeff, (sigma_coeff.shape[0], 1, 1, sigma_coeff.shape[1])) # Iterate for depi in depth[1]: for magi in mag[1]: for disti in dist[1]: dist_args = { "mag": array([[[magi]]]), dist[0]: array([[[disti]]]), "depth": array([[[depi]]]), "coefficient": coeff, "sigma_coefficient": sigma_coeff, } log_mean, log_sigma = model.distribution(**dist_args) sa_mod = list(log_mean.reshape(-1)) sa_mod = [math.exp(x) for x in sa_mod] sigma_mod = list(log_sigma.reshape(-1)) if result_type == "MEAN": row = [depi, magi, disti, result_type, component_type] + sa_mod[1:] + [sa_mod[0]] else: row = [depi, magi, disti, result_type, component_type] + sigma_mod[1:] + [sigma_mod[0]] writer.writerow(row) handle.close()
class Ground_motion_calculator(object): """Ground_motion_calculator instances are used to calculate the ground motion given a ground motion specifiction. Attributes: GM_spec: instance of Ground_motion_specification coefficient: ground motion coefficient for the given periods sigma_coefficient: ground motion sigma coefficient for the given periods periods: the periods """ def __init__(self, ground_motion_model_name, periods): """ Args: ground_motion_model_name: A string, naming the ground motion model periods: The periods that will be used for this simulation. Used to calculate coefficient and sigma_coefficient. RESIZING NOTES Adding lots of extra dimensions. 'distance' had dimension [site]*[events] 'magnitude' had dimension [events] 'coefficient' had dimension [number of coefficients]*[Period] 'sigma_coefficient' had dimension [number of coefficients]*[Period] Now 'distance' and 'magnitude' have dimension: [bonus dimension!]*[site]*[events] once 'coefficient' and 'sigma_coefficient are unpacked (ie c1,c2,c4,c6,c7,c10=c), they have dimension: [bonus dimension!]*[site]*[events] Note that some of these dimensions are degenerate (newaxises), such as [site] for 'magnitude'. newaxis is used to broadcast arrays into higher dimensions: a=array([1,2,3]) b=array([0,1]) a=a[...,newaxis] print a >[[1] > [2] > [3]] # if a is added to a 1D array of length n; a will act as: # [[1, 1, ... (n times], # [2, 2, ... (n times], # [3, 3, ... (n times]] # (this is just broadcasting rules) a+b >array([[1, 2], > [2, 3], > [3, 4]]) Note that all [bonus dimensions] are degenerate. They are there because once the distribution is sampled, I want it to maintain the same number of dimension. I don't want it to add an extra dimension for the spawnings. If that happens, then it is hard to address futher samplings (from soil) or multi-models in a uniform manner. so ground_motion_from_toro =[gmd_torro] sampled ground_motion_from_toro = [gmd1,gmd2,gmd3, ... (n samples)] Uniform behaviour. """ self.GM_spec = Ground_motion_specification(ground_motion_model_name) periods = asarray(periods) # calc the coefficient and sigma_coefficient for the input periods coefficient = self.GM_spec.calc_coefficient(periods) sigma_coefficient = self.GM_spec.calc_sigma_coefficient(periods) # Adding extra dimensions. self.coefficient = coefficient[:, newaxis, newaxis, :] self.sigma_coefficient = sigma_coefficient[:, newaxis, newaxis, :] def distribution_function(self, dist_object, dist_types, mag_dict, periods=None, depth=None, depth_to_top=None, fault_type=None, Vs30=None, mag_type=None, Z25=None, dip=None, width=None, event_activity=None): """ dist_object must give distance info if dist_object.distance(dist_type) is called. The distance info must be an array. Returns: log_mean - dimensions are log_sigma FIXME: Why should we let depth be None? """ # dist_type and mag_type are attributes of self.GM_spec # we shouldn't pass them around. distances = {} for dist_type in dist_types: distances[dist_type] = dist_object.distance(dist_type) mag = mag_dict[mag_type] if depth is not None: depth = asarray(depth) (mag, depth, depth_to_top, fault_type, dip, width) = self.resize_mag_depth(mag, depth, depth_to_top, fault_type, dip, width) for dist_type in dist_types: distances[dist_type] = self.resize_dist(distances[dist_type], mag.size) # This is calling the distribution functions described in the # ground_motion_interface module. # We add the new 'dist_object' parameter to cater to models that # require more than one distance. Once all existing models use # the new parameter we can remove the 'distance' parameter. distribution_args = { 'mag': mag, 'coefficient': self.coefficient, 'sigma_coefficient': self.sigma_coefficient, 'depth': depth, 'depth_to_top': depth_to_top, 'fault_type': fault_type, 'Vs30': Vs30, 'Z25': Z25, 'dip': dip, 'width': width, 'periods': periods } for dist_type in dist_types: distribution_args[dist_type] = distances[dist_type] (log_mean, log_sigma) = self.GM_spec.distribution(**distribution_args) # FIXME when will this fail? Maybe let it fail then? # If it does not fail here it fails in analysis.py" #, line 427, in main # assert isfinite(bedrock_SA).all() # example of log_mean when this failed log_mean [[[ NaN NaN NaN]]] # An Mw of 0.0 caused it. # this is a good place to catch this error assert isfinite(log_mean).all() assert isfinite(log_sigma).all() return (log_mean, log_sigma) def resize_mag_depth(self, mag, depth, depth_to_top, fault_type, dip, width): """ Warning, Toro_1997_midcontinent_distribution assumes that this occurs. So if resizing is changed, Toro_1997_midcontinent_distribution needs to be changed as well """ if mag.size == 1: mag = mag.reshape([1]) if depth is not None: depth = depth.reshape([1]) # Don't know if we have to do this if depth_to_top is not None: depth_to_top = depth_to_top.reshape([1]) if fault_type is not None: fault_type = fault_type.reshape([1]) # resize depth, depth_to_top, etc if depth is not None: depth = depth[newaxis, :, newaxis] # collapsed arrays are a bad idea... if depth_to_top is not None: depth_to_top = array(depth_to_top)[newaxis, :, newaxis] if fault_type is not None: fault_type = array(fault_type)[newaxis, :, newaxis] if dip is not None: dip = array(dip)[newaxis, :, newaxis] if width is not None: width = array(width)[newaxis, :, newaxis] assert len(mag.shape) == 1 mag = mag[newaxis, :, newaxis] return (mag, depth, depth_to_top, fault_type, dip, width) def resize_dist(self, dist, mag_size): """ Warning, Toro_1997_midcontinent_distribution assumes that this occurs. So if resizing is changed, Toro_1997_midcontinent_distribution needs to be changed as well """ # [4.5,5.5,6.0] => site * mag * T assert len(dist.shape) < 3 if not len(dist.shape) == 2: # if distances is collapsed if dist.size == 1: # if distances is size 1 dist = dist.reshape((1, 1)) else: assert len(dist.shape) == 1 if mag_size > 1: assert dist.size == mag_size # therefore distance is 1 site * n magnitudes dist = dist.reshape((1, mag_size)) else: # therefore distance is n site * 1 magnitudes dist = dist.reshape((dist.size, 1)) # collapsed arrays are a bad idea... dist = dist[:, :, newaxis] # [[30.0,35.0],[45.0,20.0]]=> [site*mag] * T return dist
class Ground_motion_calculator(object): """Ground_motion_calculator instances are used to calculate the ground motion given a ground motion specifiction. Attributes: GM_spec: instance of Ground_motion_specification coefficient: ground motion coefficient for the given periods sigma_coefficient: ground motion sigma coefficient for the given periods periods: the periods """ def __init__(self, ground_motion_model_name, periods): """ Args: ground_motion_model_name: A string, naming the ground motion model periods: The periods that will be used for this simulation. Used to calculate coefficient and sigma_coefficient. RESIZING NOTES Adding lots of extra dimensions. 'distance' had dimension [site]*[events] 'magnitude' had dimension [events] 'coefficient' had dimension [number of coefficients]*[Period] 'sigma_coefficient' had dimension [number of coefficients]*[Period] Now 'distance' and 'magnitude' have dimension: [bonus dimension!]*[site]*[events] once 'coefficient' and 'sigma_coefficient are unpacked (ie c1,c2,c4,c6,c7,c10=c), they have dimension: [bonus dimension!]*[site]*[events] Note that some of these dimensions are degenerate (newaxises), such as [site] for 'magnitude'. newaxis is used to broadcast arrays into higher dimensions: a=array([1,2,3]) b=array([0,1]) a=a[...,newaxis] print a >[[1] > [2] > [3]] # if a is added to a 1D array of length n; a will act as: # [[1, 1, ... (n times], # [2, 2, ... (n times], # [3, 3, ... (n times]] # (this is just broadcasting rules) a+b >array([[1, 2], > [2, 3], > [3, 4]]) Note that all [bonus dimensions] are degenerate. They are there because once the distribution is sampled, I want it to maintain the same number of dimension. I don't want it to add an extra dimension for the spawnings. If that happens, then it is hard to address futher samplings (from soil) or multi-models in a uniform manner. so ground_motion_from_toro =[gmd_torro] sampled ground_motion_from_toro = [gmd1,gmd2,gmd3, ... (n samples)] Uniform behaviour. """ self.GM_spec = Ground_motion_specification(ground_motion_model_name) periods = asarray(periods) # calc the coefficient and sigma_coefficient for the input periods coefficient = self.GM_spec.calc_coefficient(periods) sigma_coefficient = self.GM_spec.calc_sigma_coefficient(periods) # Adding extra dimensions. self.coefficient = coefficient[:, newaxis, newaxis, :] self.sigma_coefficient = sigma_coefficient[:, newaxis, newaxis, :] def distribution_function(self, dist_object, dist_types, mag_dict, periods=None, depth=None, depth_to_top=None, fault_type=None, Vs30=None, mag_type=None, Z25=None, dip=None, width=None, event_activity=None): """ dist_object must give distance info if dist_object.distance(dist_type) is called. The distance info must be an array. Returns: log_mean - dimensions are log_sigma FIXME: Why should we let depth be None? """ # dist_type and mag_type are attributes of self.GM_spec # we shouldn't pass them around. distances = {} for dist_type in dist_types: distances[dist_type] = dist_object.distance(dist_type) mag = mag_dict[mag_type] if depth is not None: depth = asarray(depth) (mag, depth, depth_to_top, fault_type, dip, width) = self.resize_mag_depth(mag, depth, depth_to_top, fault_type, dip, width) for dist_type in dist_types: distances[dist_type] = self.resize_dist(distances[dist_type], mag.size) # This is calling the distribution functions described in the # ground_motion_interface module. # We add the new 'dist_object' parameter to cater to models that # require more than one distance. Once all existing models use # the new parameter we can remove the 'distance' parameter. distribution_args = {'mag': mag, 'coefficient': self.coefficient, 'sigma_coefficient': self.sigma_coefficient, 'depth': depth, 'depth_to_top': depth_to_top, 'fault_type': fault_type, 'Vs30': Vs30, 'Z25': Z25, 'dip': dip, 'width': width, 'periods': periods} for dist_type in dist_types: distribution_args[dist_type] = distances[dist_type] (log_mean, log_sigma) = self.GM_spec.distribution(**distribution_args) # FIXME when will this fail? Maybe let it fail then? # If it does not fail here it fails in analysis.py" #, line 427, in main # assert isfinite(bedrock_SA).all() # example of log_mean when this failed log_mean [[[ NaN NaN NaN]]] # An Mw of 0.0 caused it. # this is a good place to catch this error assert isfinite(log_mean).all() assert isfinite(log_sigma).all() return (log_mean, log_sigma) def resize_mag_depth(self, mag, depth, depth_to_top, fault_type, dip, width): """ Warning, Toro_1997_midcontinent_distribution assumes that this occurs. So if resizing is changed, Toro_1997_midcontinent_distribution needs to be changed as well """ if mag.size == 1: mag = mag.reshape([1]) if depth is not None: depth = depth.reshape([1]) # Don't know if we have to do this if depth_to_top is not None: depth_to_top = depth_to_top.reshape([1]) if fault_type is not None: fault_type = fault_type.reshape([1]) # resize depth, depth_to_top, etc if depth is not None: depth = depth[newaxis, :, newaxis] # collapsed arrays are a bad idea... if depth_to_top is not None: depth_to_top = array(depth_to_top)[newaxis, :, newaxis] if fault_type is not None: fault_type = array(fault_type)[newaxis, :, newaxis] if dip is not None: dip = array(dip)[newaxis, :, newaxis] if width is not None: width = array(width)[newaxis, :, newaxis] assert len(mag.shape) == 1 mag = mag[newaxis, :, newaxis] return (mag, depth, depth_to_top, fault_type, dip, width) def resize_dist(self, dist, mag_size): """ Warning, Toro_1997_midcontinent_distribution assumes that this occurs. So if resizing is changed, Toro_1997_midcontinent_distribution needs to be changed as well """ # [4.5,5.5,6.0] => site * mag * T assert len(dist.shape) < 3 if not len(dist.shape) == 2: # if distances is collapsed if dist.size == 1: # if distances is size 1 dist = dist.reshape((1, 1)) else: assert len(dist.shape) == 1 if mag_size > 1: assert dist.size == mag_size # therefore distance is 1 site * n magnitudes dist = dist.reshape((1, mag_size)) else: # therefore distance is n site * 1 magnitudes dist = dist.reshape((dist.size, 1)) # collapsed arrays are a bad idea... dist = dist[:, :, newaxis] # [[30.0,35.0],[45.0,20.0]]=> [site*mag] * T return dist