def lf_lfc(time, reflectogram, id_dir, freq, time_cat, intensity_cat, cos_cat): ''' This function is used to calculate LF and LFC ''' # bi-directional intensityes for LF intensity_cos2 = np.multiply(intensity_cat, cos_cat**2) # log.info(cos_cat**2) reflecto_cos2 = reflectogram_hist(time, time_cat, intensity_cos2) # bi-directional intensityes for LFC intensity_cosabs = np.multiply(intensity_cat, np.abs(cos_cat)) reflecto_cosabs = reflectogram_hist(time, time_cat, intensity_cosabs) ## The next two lines get direct sound without any information from source and receiver LF = np.zeros(freq.size, dtype = np.float32) LFC = np.zeros(freq.size, dtype = np.float32) jf = 0 # log.info("time dir: {}".format(time[id_dir])) id_0to80 = np.where(time <= 0.080 + time[id_dir]) # id_0to800 = np.where(time[id_0to80[0]] >= time[id_0to80[0]]) # log.info("time 0-80: {}".format(time[id_0to800[0]])) id_5to80 = np.where(time[id_0to80[0]] >= 0.005 + time[id_dir]) # log.info("time 5-80: {}".format(time[id_5to80[0]])) for jref, ref in enumerate(reflectogram): np.seterr(divide = 'ignore') try: refcos2 = reflecto_cos2[jf,:] LF[jref] = 100 * np.sum(refcos2[id_5to80[0]]) / np.sum(ref[id_0to80[0]]) refcosabs = reflecto_cosabs[jf,:] LFC[jref] = 100 * np.sum(refcosabs[id_5to80[0]]) / np.sum(ref[id_0to80[0]]) except: log.info("I could not calculate LF and LFC for the {}.".format(freq[jf])+ "[Hz] frequency band. Try to use more rays or"+ "extend the length of h(t) of the simmulation.") jf=+1 return LF, LFC
def start(self): # loop through every single ray for src_rays in self.rays: log.info(src_rays.nrays) progress = tqdm(np.ndindex((src_rays.nrays, src_rays.niters))) for (rayid, rayit) in progress: if src_rays.length[rayid] != src_rays.niters: continue plane, rhit = self.ray_x_planes(r0=src_rays.ris[rayid, rayit], rd=src_rays.rds[rayid, rayit]) try: src_rays.ris[rayid, rayit + 1] = rhit except IndexError: # trying to add an element beyond the array size. Using a # try/except block is faster than using an if statement pass else: # compute the new direction but first check if this plane # is part of the bounding box if plane.bbox: src_rays.length[rayid] = rayit + 2 rd = src_rays.rds[rayid, rayit] n = plane.normal rr = -2 * np.dot(rd, n) * n + rd src_rays.rds[rayid, rayit + 1] = rr
def __init__(self, geo_cfg, alpha, s): ''' Set up the room geometry from the .dae file Geometry consists of: Volume, Total ara and an array of plane objects. Each plane object will be processed in a c++ class and have the following att: - name (string) - bounding box (bool) - list of vertices (Eigen<double> - Nvert x 3) - normal (Eigen<double> - 1 x 3) - vert_x - 2D polygon x coord (Eigen<double> - 1 x Nvert) - vert_y - 2D polygon y coord (Eigen<double> - 1 x Nvert) - nig - 2D normal components index (Eigen<int> - 1 x 2) - area (double) - centroid (Eigen<double> - 1 x 3) - alpha - absorption coefficient (Eigen<double> - 1 x Nfreq) - s - scattering coefficient (double) ''' # toml file daepath = geo_cfg['room'] # path to .dae # Load an array of plane objects self.planes = [] # A list of planes (object with attributes) mesh = co.Collada(daepath) for obj in mesh.scene.objects('geometry'): #loop every obj for triset in obj.primitives(): #loop every primitives # First if excludes the non-triangles objects if type(triset) != co.triangleset.BoundTriangleSet: log.info('Warning: non-supported primitive ignored!') continue # Loop trhough all triangles for jp, tri in enumerate(triset): # Define a single plane object name = '{}-{}'.format(obj.original.name, jp) vertices = np.array(tri.vertices) normal = np.float32(tri.normals[0] / np.linalg.norm(tri.normals[0])) vert_x, vert_y, normal_nig = vert_2d(normal, vertices) area = np.float64(triangle_area(vertices)) centroid = np.float32(triangle_centroid(vertices)) alpha_v = np.float32(alpha[jp]) ################### cpp plane class ################# plane = ra_cpp.Planecpp(name, False, vertices, normal, vert_x, vert_y, normal_nig, area, centroid, alpha_v, s[jp]) ################### py plane class ################ # plane = PyPlane(name, False, vertices, normal, # vert_x, vert_y, normal_nig, area, centroid, # alpha[jp], s[jp]) # Append plane object self.planes.append(plane) # total area and volume self.total_area = total_area(self.planes) self.volume = volume(self.planes)
def load_dae_geometry(self, daepath): planes = [] mesh = co.Collada(daepath) for obj in mesh.scene.objects('geometry'): for triset in obj.primitives(): if type(triset) != co.triangleset.BoundTriangleSet: log.info('Warning: non-supported primitive ignored!') continue for i, tri in enumerate(triset): plane = Plane(name='{}-{}'.format(obj.original.name, i), vertices=tri.vertices, normal=tri.normals[0] / np.linalg.norm(tri.normals[0])) planes.append(plane) return planes
def dump(self): log.info('number of escaped rays: {}/{}={}%'.format( self.n_escaped_rays, self.rays[0].nrays, np.around(100 * self.n_escaped_rays / self.rays[0].nrays, decimals=2))) rays = [] for r in self.rays: rays.append({'ris': r.ris.tolist(), 'length': r.length.tolist()}) sim_json = {'rays': rays} json.dump( sim_json, codecs.open('sim.json', 'w', encoding='utf-8'), separators=(',', ':'), sort_keys=True, # indent=4 )
def process_results(Dt, ht_length, freq, sources, receivers): ''' This function process all the relevant source-receiver data, such as: reflectogram, decay and acoustical parameters. Each receiver will be appended to each source to store the results of each source-receiver (vs. time or vs. frequency) pair. ''' log.info("processing results...") time_bins = np.arange(0.0, 1.2 * ht_length, Dt) sou = [] for s in sources: rec = [] #SRPairRec() for jrec, r in enumerate(receivers): rec.append(RecResults(s, jrec, time_bins, freq)) sou.append(SouResults(rec, time_bins, freq)) return sou
def main(): time_start = time.time() sim = Simulation(cfgfile='simulation.toml') log.info('Starting simulation...') sim.start() log.info('Simulation over.\nDumping data now...') sim.dump() log.info('All data has been dumped!') log.info('Terminated in: %.4f s' % (time.time() - time_start))
def ts(time, reflectogram, id_dir, freq): ''' This function is used to calculate T30 by curve fitting the decay from -5 dB to -25dB ''' ## The next two lines get direct sound without any information from source and receiver Ts = np.zeros(freq.size, dtype = np.float32) jf = 0 for jref, ref in enumerate(reflectogram): np.seterr(divide = 'ignore') try: Ts[jref] = 1000 * (np.sum(np.multiply(time[id_dir:], ref[id_dir:]))/np.sum(ref[id_dir:]) - time[id_dir]) except: log.info("I could not calculate Ts for the {}.".format(freq[jf])+ "[Hz] frequency band. Try to use more rays or"+ "extend the length of h(t) of the simmulation.") jf=+1 return Ts
def __init__(self, source, jrec, time_bins, freq): start_time = time.time() # Time concatenation # time_cat = np.array(ra_cpp._time_cat( # source.rays, source.reccrossdir[jrec].time_dir, jrec), dtype = np.float32) time_cat = np.array(ra_cpp._time_cat( source.rays, source.reccrossdir[jrec].time_dir, jrec, source.reccrossdir[jrec].size_of_time), dtype = np.float32) # log.info("time cat size: {}.".format(len(time_cat))) # log.info("size_of_tme: {}.".format(source.reccrossdir[jrec].size_of_time)) # sort time vector # id_sorted_time = np.argsort(time_cat) # time_sorted = time_cat[id_sorted_time] # concatenate sound intensities # start_time = time.time() intensity_cat = np.array(ra_cpp._intensity_cat( source.rays, source.reccrossdir[jrec].i_dir, jrec, time_cat.size), dtype = np.float32) # Cossine concatenation cos_cat = np.array(ra_cpp._cos_cat( source.rays, source.reccrossdir[jrec].cos_dir, jrec, source.reccrossdir[jrec].size_of_time), dtype = np.float32) # log.info(" {} seconds to concatenate intensity (c++).".format(time.time() - start_time)) # sort intensity # intensity_sorted = intensity_cat[:, id_sorted_time] # reflectogram self.reflectogram = reflectogram_hist(time_bins, time_cat, intensity_cat) # self.reflectogram = reflectogram_hist(time_bins, time_sorted, intensity_sorted) self.decay = decay_curve(self.reflectogram) log.info(" {} seconds to calc reflectogram (c++).".format(time.time() - start_time)) # Calculate the direct sound id direct_sound_idarr = np.nonzero(self.reflectogram[0,:]) id_dir = direct_sound_idarr[0] # Calculate acoustical parameters self.EDT = edt(time_bins, self.decay, id_dir[0], freq) self.T20 = t20(time_bins, self.decay, id_dir[0], freq) self.T30 = t30(time_bins, self.decay, id_dir[0], freq) self.C80 = c80(time_bins, self.reflectogram, id_dir[0], freq) self.D50 = d50(time_bins, self.reflectogram, id_dir[0], freq) self.Ts = ts(time_bins, self.reflectogram, id_dir[0], freq) self.G = g_db(self.reflectogram, source.power_lin, freq) self.LF, self.LFC = lf_lfc(time_bins, self.reflectogram, id_dir[0], freq, time_cat, intensity_cat, cos_cat)
def g_db(reflectogram, Wlin, freq): ''' This function is used to calculate T30 by curve fitting the decay from -5 dB to -25dB ''' ## The next two lines get direct sound without any information from source and receiver G = np.zeros(freq.size, dtype = np.float32) - np.inf jf = 0 for jref, ref in enumerate(reflectogram): np.seterr(divide = 'ignore') try: G[jref] = 10.0 * np.log10(np.sum(ref)) -\ 10.0 * np.log10(Wlin[jref] / (4.0 * np.pi * 100.0)) except: log.info("I could not calculate G for the {}.".format(freq[jf])+ "[Hz] frequency band. Try to use more rays or"+ "extend the length of h(t) of the simmulation.") jf=+1 return G
def d50(time, reflectogram, id_dir, freq): ''' This function is used to calculate T30 by curve fitting the decay from -5 dB to -25dB ''' ## The next two lines get direct sound without any information from source and receiver D50 = np.zeros(freq.size, dtype = np.float32)-0.1 jf = 0 for jref, ref in enumerate(reflectogram): np.seterr(divide = 'ignore') try: id_to_sum = np.where(time <= 0.050 + time[id_dir]) D50[jref] = 100 * np.sum(ref[id_to_sum[0]]) / np.sum(ref) except: log.info("I could not calculate D50 for the {}.".format(freq[jf])+ "[Hz] frequency band. Try to use more rays or"+ "extend the length of h(t) of the simmulation.") jf=+1 return D50
def t30(time, decay, id_dir, freq): ''' This function is used to calculate T30 by curve fitting the decay from -5 dB to -25dB ''' ## The next two lines get direct sound without any information from source and receiver T30 = np.zeros(freq.size, dtype = np.float32) jf = 0 for jdec, dec in enumerate(decay): np.seterr(divide = 'ignore') decdB = 10 * np.log10(dec[id_dir:]/ np.amax(dec[id_dir:])) # log.info(time.shape) id_to_fit = np.where((decdB < -5.0) & (decdB > -35.0)) try: p = np.polyfit(time[id_to_fit], decdB[id_to_fit], 1) T30[jdec] = -60 / p[0] except: log.info("I could not calculate T30 for the {}.".format(freq[jf])+ "[Hz] frequency band. Try to use more rays or"+ "extend the length of h(t) of the simmulation.") jf=+1 return T30
def run(cfgs): ##### Setup algorithm controls ######## # FIXME: use pathlib instead of string concatenation controls = AlgControls(cfgs['sim_cfg']['controls']) ##### Setup air properties ######## air = AirProperties(cfgs['sim_cfg']['air']) air_m = air.air_absorption(controls.freq) ##### Setup materials ############# alpha_list = load_matdata_from_mat(cfgs['sim_cfg']['material']) alpha, s = get_alpha_s(cfgs['sim_cfg']['geometry'], cfgs['mat_cfg']['material'], alpha_list) ##### Setup Geometry ########### geo = GeometryMat(cfgs['sim_cfg']['geometry'], alpha, s) # geo.plot_mat_room(normals = 'on') # geo = Geometry('simulation.toml', alpha, s) # geo.plot_dae_room(normals = 'on') ##### Statistical theory ############ res_stat = StatisticalMat(geo, controls.freq, air.c0, air_m) res_stat.t60_sabine() res_stat.t60_eyring() # res_stat.t60_kutruff(gamma=0.4) # res_stat.t60_araup() # res_stat.t60_fitzroy() # res_stat.t60_milsette() # res_stat.plot_t60() ##### ray's initial direction ######## rays_i_v = RayInitialDirections() # rays_i_v.single_ray([0.0, -1.0, 0.0])#([0.7236, -0.5257, 0.4472]) # rays_i_v.isotropic_rays(controls.Nrays) # 15 # mat = spio.loadmat('ra/vin_matlab.mat') # rays_i_v.vinit = mat['vin'] rays_i_v.random_rays(controls.Nrays) # rays_i_v.single_ray(rays_i_v.vinit[41]) log.info("The number of rays is {}.".format(rays_i_v.Nrays)) ##### Setup receiver, reccross and reccrossdir ######## receivers, reccross, reccrossdir = setup_receivers( cfgs['sim_cfg']['receivers']) #### Allocate some memory in python for geometrical ray tracing ######################## # Estimate max reflection order N_max_ref = math.ceil(1.5 * air.c0 * controls.ht_length * \ (geo.total_area / (4 * geo.volume))) # Allocate according to max reflection order rays = ray_initializer(rays_i_v, N_max_ref, controls.transition_order, reccross) ##### Setup sources - ray history goes inside source object ######## sources = setup_sources(cfgs['sim_cfg']['sources'], rays, reccrossdir) ############# Now - the calculations #################### ############### direct sound ############################ sources = ra_cpp._direct_sound(sources, receivers, controls.rec_radius_init, geo.planes, air.c0, rays_i_v.vinit) ############### ray tracing ############## sources = ra_cpp._raytracer_main( controls.ht_length, controls.allow_scattering, controls.transition_order, controls.rec_radius_init, controls.alow_growth, controls.rec_radius_final, sources, receivers, geo.planes, air.c0, rays_i_v.vinit) ######## Calculate intensities ################### sources = ra_cpp._intensity_main(controls.rec_radius_init, sources, air.c0, air.m, res_stat.alphas_mtx) ########### Process reflectograms and acoustical parameters ##################### sou = process_results(controls.Dt, controls.ht_length, controls.freq, sources, receivers) ########## Statistics ########################################################## stats = SRStats(sou) ######## some plotting ############################## # sou[0].plot_single_reflecrogram(band = 4, jrec = 2) # plt.show() # sou[0].plot_single_reflecrogram(band = 4, jrec = 1) sou[0].plot_decays() # sou[0].plot_edt() # sou[0].plot_t20() # sou[0].plot_t30() # sou[0].plot_c80() # sou[0].plot_d50() # sou[0].plot_ts() # sou[0].plot_g() # sou[0].plot_lf() # sou[0].plot_lfc() # log.info(sources[0].rays[0].refpts_hist) # geo.plot_raypath(sources[0].coord, sources[0].rays[0].refpts_hist, # <-- sources[0].rays[0].refpts_hist # receivers) # log.info(sources[0].reccrossdir[0].cos_dir) ############# Save trial ######################### # import pickle # path = '/home/eric/dev/ra/data/legacy/ptb_studio_ph3/' # pkl_fname_res = 'ptb_studio_ph3_open' # simulation results name # with open(path+pkl_fname_res+'.pkl', 'wb') as output: # pickle.dump(res_stat, output, pickle.HIGHEST_PROTOCOL) # pickle.dump(sou, output, pickle.HIGHEST_PROTOCOL) # pickle.dump(stats, output, pickle.HIGHEST_PROTOCOL) # pickle.dump(geo, output, pickle.HIGHEST_PROTOCOL) # pickle.dump(sources, output, pickle.HIGHEST_PROTOCOL) # class Simulation(): # def __init__(self,): # self.cfgs = {} # self.sources = [] # self.receivers = [] # self.geometry = {} # def set_configs(self, cfgs): # self.cfgs = cfgs['sim_cfg'] # self.mat_cfg = cfgs['mat_cfg'] # self.controls = AlgControls(self.cfgs['controls']) # self.air = AirProperties(self.cfgs['air']) # self.air_m = self.air.air_absorption(self.controls.freq) # def set_sources(self, srcs): # ''' # Parameters: # ---------- # srcs: list of Source's # ''' # # self.rays_i_v = RayInitialDirections() # # self.rays_i_v.random_rays(self.controls.Nrays) # # log.info("The number of rays is {}.".format(self.rays_i_v.Nrays)) # # c0 = self.air.c0 # # htl = self.controls.ht_length # # area = self.geo.total_area # # volume = self.geo.volume # # N_max_ref = math.ceil(1.5 * c0 * htl * area / (4 * volume)) # # rays = ray_initializer( # # self.rays_i_v, N_max_ref, self.controls.transition_order, # # self.reccross # # ) # # self.sources = setup_sources( # # self.cfgs['sources'], rays, self.reccrossdir # # ) # def set_receivers(self, rcvrs): # ''' # Parameters: # ----------- # rcvrs: list of Receiver`s # ''' # new_rcvrs = [] # for r in rcvrs: # r.append({ # 'position': r.coord, # 'orientation': r.orientation # }) # self.receivers, self.reccross, self.reccrossdir = setup_receivers( # new_rcvrs # ) # # self.receivers, self.reccross, self.reccrossdir = setup_receivers( # # self.cfgs['receivers'] # # ) # def set_geometry(self, geom): # ''' # Parameters: # ----------- # geom: list of dicts with the following parameters: 'name', # 'vertices', 'normal', alpha, s. # ''' # # to be populated # # ... # pass # # cfgs = self.cfgs # # alpha_list = load_matdata_from_mat(cfgs['material']) # # alpha, s = get_alpha_s( # # cfgs['geometry'], self.mat_cfg['material'], alpha_list # # ) # # self.geo = GeometryMat(cfgs['geometry'], alpha, s) # def run(self, state): # ''' # Parameters: # ---------- # Return: # ------- # dict with the following structure: # { # 'rays':, # '' # } # ''' # res_stat = StatisticalMat( # self.geo, self.controls.freq, self.air.c0, self.air_m # ) # res_stat.t60_sabine() # res_stat.t60_eyring() # srcs, rcvrs = self.sources, self.receivers # srcs = ra_cpp._direct_sound( # srcs, rcvrs, self.controls.rec_radius_init, # self.geo.planes, self.air.c0, self.rays_i_v.vinit # ) # ctls = self.controls # srcs = ra_cpp._raytracer_main( # ctls.ht_length, ctls.allow_scattering, ctls.transition_order, # ctls.rec_radius_init, ctls.alow_growth, ctls.rec_radius_final, srcs, # rcvrs, self.geo.planes, self.air.c0, self.rays_i_v.vinit # ) # srcs = ra_cpp._intensity_main(ctls.rec_radius_init, # srcs, self.air.c0, self.air.m, res_stat.alphas_mtx) # sou = process_results(ctls.Dt, ctls.ht_length, # ctls.freq, srcs, rcvrs) # stats = SRStats(sou) # def stats(self,): # pass # def save(self,): # ''' # Return: # ------ # a dict with the state of the simulation. # ''' # pass