def build_pd(size, glass_thickness): """Returns a simple photodetector Solid. The photodetector is a cube of size `size` constructed out of a glass envelope with a photosensitive face on the inside of the glass envelope facing up.""" # outside of the glass envelope outside_mesh = make.cube(size) # inside of the glass envelope inside_mesh = make.cube(size - glass_thickness * 2) # outside solid with water on the outside, and glass on the inside outside_solid = Solid(outside_mesh, glass, water) # now we need to determine the triangles which make up # the top face of the inside mesh, because we are going to place # the photosensitive surface on these triangles # do this by seeing which triangle centers are at the maximum z # coordinate z = inside_mesh.get_triangle_centers()[:, 2] top = z == max(z) # see np.where() documentation # Here we make the photosensitive surface along the top face of the inside # mesh. The rest of the inside mesh is perfectly absorbing. inside_surface = np.where(top, r7081hqe_photocathode, black_surface) inside_color = np.where(top, 0x00ff00, 0x33ffffff) # construct the inside solid inside_solid = Solid(inside_mesh, vacuum, glass, surface=inside_surface, color=inside_color) # you can add solids and meshes! return outside_solid + inside_solid
def setUp(self): self.cache_dir = random_tempdir('chroma_cache_test') self.cache = Cache(self.cache_dir) self.a = Geometry() self.a.add_solid(Solid(box(1, 1, 1))) self.a.add_solid(Solid(box(1, 1, 1)), displacement=(10, 10, 10)) self.a.flatten() self.b = Geometry() self.b.add_solid(Solid(box(2, 2, 2))) self.b.add_solid(Solid(box(2, 2, 2)), displacement=(10, 10, 10)) self.b.add_solid(Solid(box(2, 2, 2)), displacement=(-10, -10, -10)) self.b.flatten()
def render_particle_track(self): x = 10.0 h = x * np.sqrt(3) / 2 pyramid = make.linear_extrude([-x / 2, 0, x / 2], [-h / 2, h / 2, -h / 2], h, [0] * 3, [0] * 3) marker = Solid(pyramid, vacuum, vacuum) if self.photon_display_mode == 'beg': photons = self.ev.photons_beg else: photons = self.ev.photons_end geometry = Geometry() sample_factor = max(1, len(photons.pos) / 10000) for pos in photons.pos[::sample_factor]: geometry.add_solid(marker, displacement=pos, rotation=make_rotation_matrix( np.random.uniform(0, 2 * np.pi), uniform_sphere())) geometry = create_geometry_from_obj(geometry) gpu_geometry = gpu.GPUGeometry(geometry) self.gpu_geometries = [self.gpu_geometry, gpu_geometry]
def render_photon_track(self,geometry,photon_track,sz=1.0,color='wavelength'): origin = photon_track.pos[:-1] extent = photon_track.pos[1:]-photon_track.pos[:-1] perp1 = np.cross(origin,extent) perp1 = np.inner(sz/2.0/np.linalg.norm(perp1,axis=1),perp1.T).T perp2 = np.cross(perp1,extent) perp2 = np.inner(sz/2.0/np.linalg.norm(perp2,axis=1),perp2.T).T verts = [perp1+perp2,-perp1+perp2,perp1-perp2,-perp1-perp2] bot = [vert+origin for vert in verts] top = [vert+origin+extent for vert in verts] vertices = [origin,origin+extent,bot[0],top[0],bot[1],top[1],bot[2],top[2],bot[3],top[3]] vertices = np.transpose(np.asarray(vertices,np.float32),(1,0,2)) triangles = np.asarray([[1, 3, 5], [1, 5, 7], [1, 7, 9], [1, 9, 3], [3, 2, 4], [5, 4, 6], [7, 6, 8], [9, 8, 2], [2, 0, 0], [4, 0, 0], [6, 0, 0], [8, 0, 0], [1, 5, 1], [1, 7, 1], [1, 9, 1], [1, 3, 1], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 2, 3], [2, 0, 4], [4, 0, 6], [6, 0, 8], [8, 0, 2]], dtype=np.int32) if color == 'wavelength': r = np.asarray(np.interp(photon_track.wavelengths[:-1],[300,550,800],[0,0,255]),dtype=np.uint32) g = np.asarray(np.interp(photon_track.wavelengths[:-1],[300,550,800],[0,255,0]),dtype=np.uint32) b = np.asarray(np.interp(photon_track.wavelengths[:-1],[300,550,800],[255,0,0]),dtype=np.uint32) colors = np.bitwise_or(b,np.bitwise_or(np.left_shift(g,8),np.left_shift(r,16))) else: r,g,b = color colors = np.full_like(photon_track.wavelengths[:-1],((r<<16)|(g<<8)|b),dtype=np.uint32) markers = [Solid(Mesh(v,triangles), vacuum, vacuum, color=c) for v,c in zip(vertices,colors)] [geometry.add_solid(marker) for marker in markers]
def build_detector(): """Returns a cubic detector made of cubic photodetectors.""" g = Geometry(water) for pos, dir in iter_box(nx, ny, nz, spacing): # convert to arrays pos, dir = np.array(pos), np.array(dir) # we need to figure out what rotation matrix to apply to each # photodetector so that the photosensitive surface will be facing # `dir`. if tuple(dir) == (0, 0, 1): rotation = None elif tuple(dir) == (0, 0, -1): rotation = make_rotation_matrix(np.pi, (1, 0, 0)) else: rotation = make_rotation_matrix(np.arccos(dir.dot((0, 0, 1))), np.cross(dir, (0, 0, 1))) # add the photodetector g.add_solid(build_pd(size, glass_thickness), rotation=rotation, displacement=pos) world = Solid(make.box(spacing * nx, spacing * ny, spacing * nz), water, vacuum, color=0x33ffffff) g.add_solid(world) return g
def create_geometry_from_obj(obj, bvh_name="default", auto_build_bvh=True, read_bvh_cache=True, update_bvh_cache=True, cache_dir=None, cuda_device=None): if callable(obj): obj = obj() if isinstance(obj, Detector): geometry = obj if isinstance(obj, Geometry): geometry = obj elif isinstance(obj, Solid): geometry = Geometry() geometry.add_solid(obj) elif isinstance(obj, Mesh): geometry = Geometry() geometry.add_solid(Solid(obj, vacuum, vacuum, color=0x33ffffff)) else: raise TypeError('cannot build type %s' % type(obj)) geometry.flatten() if geometry.bvh is None: geometry.bvh = load_bvh(geometry, auto_build_bvh=auto_build_bvh, read_bvh_cache=read_bvh_cache, update_bvh_cache=update_bvh_cache, cache_dir=cache_dir, cuda_device=cuda_device) return geometry
def detector(pmt_radius=14000.0, sphere_radius=14500.0, spiral_step=350.0): pmt = build_8inch_pmt_with_lc() geo = Detector(water) geo.add_solid(Solid(sphere(sphere_radius,nsteps=200), water, water, surface=black_surface, color=0xBBFFFFFF)) for position in spherical_spiral(pmt_radius, spiral_step): direction = -normalize(position) # Orient PMT that starts facing Y axis y_axis = np.array((0.0,1.0,0.0)) axis = np.cross(direction, y_axis) angle = np.arccos(np.dot(y_axis, direction)) rotation = make_rotation_matrix(angle, axis) # Place PMT (note that position is front face of PMT) geo.add_pmt(pmt, rotation, position) time_rms = 1.5 # ns charge_mean = 1.0 charge_rms = 0.1 # Don't I wish! geo.set_time_dist_gaussian(time_rms, -5 * time_rms, 5*time_rms) geo.set_charge_dist_gaussian(charge_mean, charge_rms, 0.0, charge_mean + 5*charge_rms) logger.info('Demo detector: %d PMTs' % geo.num_channels()) logger.info(' %1.1f ns time RMS' % time_rms) logger.info(' %1.1f%% charge RMS' % (100.0*charge_rms/charge_mean)) return geo
def build_pmt(filename, glass_thickness, outer_material, glass, vacuum, photocathode_surface, back_surface, nsteps=16): profile = read_csv(filename) # slice profile in half profile = profile[profile[:, 0] < 0] profile[:, 0] = -profile[:, 0] # order profile from base to face profile = profile[np.argsort(profile[:, 1])] # set x coordinate to 0.0 for first and last profile along the profile # so that the mesh is closed profile[0, 0] = 0.0 profile[-1, 0] = 0.0 offset_profile = offset(profile, -glass_thickness) outer_envelope_mesh = rotate_extrude(profile[:, 0], profile[:, 1], nsteps) inner_envelope_mesh = rotate_extrude(offset_profile[:, 0], offset_profile[:, 1], nsteps) outer_envelope = Solid(outer_envelope_mesh, glass, outer_material) photocathode = np.mean(inner_envelope_mesh.assemble(), axis=1)[:, 1] > 0 inner_envelope = Solid(inner_envelope_mesh, vacuum, glass, surface=np.where(photocathode, photocathode_surface, back_surface), color=np.where(photocathode, 0xff00, 0xff0000)) pmt = outer_envelope + inner_envelope # profile points, outer_material, and theta are used to construct the # light collector pmt.profile = profile pmt.outer_material = outer_material pmt.nsteps = nsteps return pmt
def build_light_collector_from_file(filename, outer_material, surface, nsteps=48): profile = read_csv(filename) mesh = rotate_extrude(profile[:, 0], profile[:, 1], nsteps) solid = Solid(mesh, outer_material, outer_material, surface=surface) return solid
def testAbort(self): '''Photons that hit a triangle at normal incidence should not abort. Photons that hit a triangle at exactly normal incidence can sometimes produce a dot product that is outside the range allowed by acos(). Trigger these with axis aligned photons in a box. ''' # Setup geometry cube = Geometry(vacuum) cube.add_solid(Solid(box(100, 100, 100), vacuum, vacuum)) geo = create_geometry_from_obj(cube, update_bvh_cache=False) # Initialize simulation (without geant4) sim = Simulation(geo, geant4_processes=0) # Create initial photons nphotons = 5 pos = np.tile([0, 0, 0], (nphotons, 1)).astype(np.float32) dir = np.tile([0, 0, 1], (nphotons, 1)).astype(np.float32) pol = np.zeros_like(pos) phi = np.random.uniform(0, 2 * np.pi, nphotons).astype(np.float32) pol[:, 0] = np.cos(phi) pol[:, 1] = np.sin(phi) t = np.zeros(nphotons, dtype=np.float32) wavelengths = np.empty(nphotons, np.float32) wavelengths.fill(400.0) photons = Photons(pos=pos, dir=dir, pol=pol, t=t, wavelengths=wavelengths) # First make one step to check for strangeness photons_end = sim.simulate([photons], keep_photons_end=True, max_steps=1).next().photons_end print "FIRST STEP" print photons_end.pos[0:10] self.assertFalse(np.isnan(photons_end.pos).any()) self.assertFalse(np.isnan(photons_end.dir).any()) self.assertFalse(np.isnan(photons_end.pol).any()) self.assertFalse(np.isnan(photons_end.t).any()) self.assertFalse(np.isnan(photons_end.wavelengths).any()) # Now let it run the usual ten steps photons_end = sim.simulate([photons], keep_photons_end=True, max_steps=10).next().photons_end aborted = (photons_end.flags & (1 << 31)) > 0 print 'aborted photon fracttion: %1.1f' % ( float(count_nonzero(aborted)) / nphotons) self.assertFalse(aborted.any()) print "LAST STEPS" print photons_end.pos[0:10]
def bvh_mesh(geometry, layer): lower_bounds, upper_bounds = geometry.bvh.get_layer(layer).get_bounds() if len(lower_bounds) == 0 or len(upper_bounds) == 0: raise Exception('no nodes at layer %i' % layer) dx, dy, dz = upper_bounds[0] - lower_bounds[0] center = np.mean([upper_bounds[0],lower_bounds[0]], axis=0) geometry = Geometry() geometry.add_solid(Solid(make.box(dx,dy,dz,center), vacuum, vacuum, color=0x33ffffff)) for center, dx, dy, dz in list(zip(np.mean([lower_bounds,upper_bounds],axis=0), *list(zip(*upper_bounds-lower_bounds))))[1:]: geometry.add_solid(Solid(make.box(dx,dy,dz,center), vacuum, vacuum, color=0x33ffffff)) return create_geometry_from_obj(geometry)
def build_detector(self, detector=None, volume_classifier=_default_volume_classifier): ''' Add the meshes defined by this GDML to the detector. If detector is not specified, a new detector will be created. The volume_classifier should be a function that returns a classification of the volume ('pmt','solid','omit') and kwargs passed to the Solid constructor for that volume: material1, material2, color, surface The different classifications have different behaviors: 'pmt' should specify channel_type in the kwargs to identify the channel, calls add_pmt 'solid' will add a normal solid to the Chroma geometry, calls add_solid 'omit' will not add the Solid to the Chroma geometry ''' if detector is None: detector = Detector(vacuum) q = deque() q.append([self.world, np.zeros(3), np.identity(3), None]) while len(q): v, pos, rot, parent_material_ref = q.pop() for child, c_pos, c_rot in zip(v.children, v.child_pos, v.child_rot): c_pos = self.get_vals(c_pos) if c_pos is not None else np.zeros(3) c_rot = self.get_vals(c_rot) if c_rot is not None else np.identity(3) c_pos = np.matmul(c_pos,rot)+pos x_rot = make_rotation_matrix(c_rot[0], [1, 0, 0]) y_rot = make_rotation_matrix(c_rot[1], [0, 1, 0]) z_rot = make_rotation_matrix(c_rot[2], [0, 0, 1]) c_rot = np.matmul(rot, np.matmul(x_rot, np.matmul(y_rot, z_rot))) #FIXME verify this order q.append([child, c_pos, c_rot, v.material_ref]) m = self.get_mesh(v.solid_ref) mesh = Mesh(m.vertices, m.faces) # convert PyMesh mesh to Chroma mesh classification, kwargs = volume_classifier(v.name, v.material_ref, parent_material_ref) if classification == 'pmt': channel_type = kwargs.pop('channel_type',None) solid = Solid(mesh, **kwargs) detector.add_pmt(solid, displacement=pos, rotation=rot, channel_type=channel_type) elif classification == 'solid': solid = Solid(mesh, **kwargs) detector.add_solid(solid, displacement=pos, rotation=rot) elif classification == 'omit': pass else: raise Exception('Unknown volume classification: '+classification) return detector
def render_photons(self,geometry,photons): x = 10.0 h = x*np.sqrt(3)/2 pyramid = make.linear_extrude([-x/2,0,x/2], [-h/2,h/2,-h/2], h, [0]*3, [0]*3) marker = Solid(pyramid, vacuum, vacuum) sample_factor = 1 subset = photons[::sample_factor] for p,d,w in zip(subset.pos,subset.dir,subset.wavelengths): geometry.add_solid(marker, displacement=p, rotation=gen_rot([0,1,0],d))
def build_kabamland(kabamland, configname): # focal_length sets dist between lens plane and PMT plane (or back of curved detecting surface); #(need not equal true lens focal length) config = detectorconfig.configdict(configname) _, lens = kb.get_lens_triangle_centers( config.edge_length, config.base, config.diameter_ratio, config.thickness_ratio, config.half_EPD, config.blockers, blocker_thickness_ratio=config.blocker_thickness_ratio, light_confinement=config.light_confinement, focal_length=config.focal_length, lens_system_name=config.lens_system_name) surf = mh.rotate( kb.curved_surface2(config.detector_r, diameter=2 * kb.find_max_radius(config.edge_length, config.base), nsteps=9), make_rotation_matrix(-np.pi / 2, (1, 0, 0))) surf = mh.shift(surf, (0, 0, -config.focal_length)) kabamland.add_solid(Solid(surf, lm.ls, lm.ls, lm.fulldetect, 0x0000FF), rotation=None, displacement=(0, 0, 0)) kabamland.add_solid(Solid(lens, lm.lensmat, lm.ls), rotation=None, displacement=None) blocker = BuildBlocker( np.max(lens.assemble()[:, :, 0].flatten()), lens.assemble()[:, :, 2].flatten()[np.argmax(lens.assemble()[:, :, 0].flatten())]) kabamland.add_solid(Solid(blocker, lm.ls, lm.ls, lm.fullabsorb, 0x0000FF), rotation=None, displacement=(0, 0, 0)) kabamland.channel_index_to_channel_id = [1] return lens, surf
def setUp(self): self.cache_dir = random_tempdir('chroma_cache_test') self.cache = Cache(self.cache_dir) self.a = Geometry() self.a.add_solid(Solid(box(1, 1, 1))) self.a.add_solid(Solid(box(1, 1, 1)), displacement=(10, 10, 10)) self.a.flatten() self.b = Geometry() self.b.add_solid(Solid(box(2, 2, 2))) self.b.add_solid(Solid(box(2, 2, 2)), displacement=(10, 10, 10)) self.b.add_solid(Solid(box(2, 2, 2)), displacement=(-10, -10, -10)) self.b.flatten() # c is not in cache self.c = Geometry() self.c.add_solid(Solid(box(2, 2, 2))) self.c.flatten() self.a_hash = self.a.mesh.md5() self.b_hash = self.b.mesh.md5() self.c_hash = self.c.mesh.md5() self.cache.save_geometry('a', self.a) self.cache.save_geometry('b', self.b)
def build_pmt_icosahedron(kabamland, vtx, focal_length=1.0): offset = 1.2 * (vtx + focal_length) angles = np.linspace(np.pi / 4, 2 * np.pi + np.pi / 4, 4, endpoint=False) square = make.linear_extrude(offset * np.sqrt(2) * np.cos(angles), offset * np.sqrt(2) * np.sin(angles), 2.0) vrs = np.eye(3) for vr in vrs: if np.array_equal(vr, [0, 0, 1]): kabamland.add_pmt(Solid(square, glass, kabamland.detector_material, lm.fullabsorb, 0xBBFFFFFF), displacement=offset * vr) kabamland.add_pmt(Solid(square, glass, kabamland.detector_material, lm.fullabsorb, 0xBBFFFFFF), displacement=-offset * vr) else: trasl = np.cross(vr, [0, 0, 1]) kabamland.add_pmt(Solid(square, glass, kabamland.detector_material, lm.fullabsorb, 0xBBFFFFFF), rotation=make_rotation_matrix(np.pi / 2, vr), displacement=offset * trasl) kabamland.add_pmt(Solid(square, glass, kabamland.detector_material, lm.fullabsorb, 0xBBFFFFFF), rotation=make_rotation_matrix(np.pi / 2, vr), displacement=-offset * trasl)
def setUp(self): # Setup geometry cube = Detector(vacuum) cube.add_pmt( Solid(box(10.0, 10, 10), vacuum, vacuum, surface=r7081hqe_photocathode)) cube.set_time_dist_gaussian(1.2, -6.0, 6.0) cube.set_charge_dist_gaussian(1.0, 0.1, 0.5, 1.5) geo = create_geometry_from_obj(cube, update_bvh_cache=False) self.geo = geo self.sim = Simulation(self.geo, geant4_processes=0)
def build_pmt_shell(filename, outer_material, glass, nsteps=16): profile = read_csv(filename) # slice profile in half profile = profile[profile[:, 0] < 0] profile[:, 0] = -profile[:, 0] # order profile from base to face profile = profile[np.argsort(profile[:, 1])] # set x coordinate to 0.0 for first and last profile along the profile # so that the mesh is closed profile[0, 0] = 0.0 profile[-1, 0] = 0.0 return Solid(rotate_extrude(profile[:, 0], profile[:, 1], nsteps), glass, outer_material, color=0xeeffffff)
def build_light_collector(pmt, a, b, d, rmin, rmax, surface, npoints=10): if not isinstance(pmt, Solid): raise Exception('`pmt` must be an instance of %s' % Solid) lc_radii = np.linspace(rmin, rmax, npoints) lc_profile = get_lc_profile(lc_radii, a, b, d, rmin, rmax) pmt_face_profile = pmt.profile[pmt.profile[:, 1] > -1e-3] lc_offset = np.interp(lc_radii[0], list(reversed(pmt_face_profile[:, 0])), list(reversed(pmt_face_profile[:, 1]))) lc_mesh = rotate_extrude(lc_radii, lc_profile + lc_offset, pmt.nsteps) return Solid(lc_mesh, pmt.outer_material, pmt.outer_material, surface=surface)
def setUp(self): self.cube = Geometry(water) self.cube.add_solid(Solid(box(100,100,100), water, water)) self.geo = create_geometry_from_obj(self.cube, update_bvh_cache=False) self.sim = Simulation(self.geo, geant4_processes=0) nphotons = 100000 pos = np.tile([0,0,0], (nphotons,1)).astype(np.float32) dir = np.tile([0,0,1], (nphotons,1)).astype(np.float32) pol = np.zeros_like(pos) phi = np.random.uniform(0, 2*np.pi, nphotons).astype(np.float32) pol[:,0] = np.cos(phi) pol[:,1] = np.sin(phi) t = np.zeros(nphotons, dtype=np.float32) wavelengths = np.empty(nphotons, np.float32) wavelengths.fill(400.0) self.photons = Photons(pos=pos, dir=dir, pol=pol, t=t, wavelengths=wavelengths)
def render_vertex( self, geometry, vertex, children=2, sz=5.0, colors={ 'mu+': 0x50E0FF, 'mu-': 0xFFE050, 'e+': 0x0000FF, 'e-': 0xFF0000, 'gamma': 0x00FF00 }): steps = np.vstack((vertex.steps.x, vertex.steps.y, vertex.steps.z)).T for index in range(len(steps) - 1): vec = steps[index + 1] - steps[index] mag = np.linalg.norm(vec) u = vec / mag axis = np.cross(u, [0, 0, 1]) ang = np.arccos(np.dot(u, [0, 0, 1])) rotmat = make_rotation_matrix(ang, axis) x = sz / 2 y = x * np.sqrt(3) / 2 segment = make.linear_extrude([-x, 0, x], [-y, y, -y], mag, [0, 0, 0], [0, 0, 0]) segment.vertices[:, 2] += mag / 2.0 marker = Solid(segment, vacuum, vacuum, color=colors[vertex.particle_name] if vertex.particle_name in colors else 0xAAAAAA) geometry.add_solid(marker, displacement=steps[index], rotation=rotmat) if children and vertex.children: for child in vertex.children: if isinstance(children, bool): self.render_vertex(geometry, child, children) else: self.render_vertex(geometry, child, children - 1)
def build_curvedsurface_icosahedron(kabamland, vtx, rad, diameter_ratio, focal_length=1.0, detector_r=1.0, nsteps=10, b_pxl=4): initial_curved_surf = mh.rotate( curved_surface2(detector_r, diameter=rad * 2, nsteps=nsteps, base_pxl=b_pxl), make_rotation_matrix(-np.pi / 2, (1, 0, 0))) face = Solid(initial_curved_surf, kabamland.detector_material, kabamland.detector_material, lm.fulldetect, 0x0000FF) phi, axs = rot_axis([0, 0, 1], vtx) for vx, ph, ax in zip(vtx, -phi, axs): kabamland.add_solid(face, rotation=make_rotation_matrix(ph, ax), displacement=-normalize(vx) * (np.linalg.norm(vx) + focal_length))
def setUp(self): # Setup geometry cube = Detector(vacuum) cube.add_pmt( Solid(box(10.0, 10.0, 10.0), vacuum, vacuum, surface=r7081hqe_photocathode)) cube.set_time_dist_gaussian(1.2, -6.0, 6.0) cube.set_charge_dist_gaussian(1.0, 0.1, 0.5, 1.5) geo = create_geometry_from_obj(cube, update_bvh_cache=True, read_bvh_cache=False) print "Number of channels in detector: ", geo.num_channels() self.geo = geo self.sim = Simulation(self.geo, geant4_processes=0, nthreads_per_block=1, max_blocks=1) self.rfile = rt.TFile("output_test_detector.root", "recreate") self.htime = rt.TH1D("htime", "Time;ns", 120, 80, 120) self.hcharge = rt.TH1D("hcharge", "Charge;pe", 100, 0.5, 1.5)
def testBulkReemission(self): '''Test bulk reemission Start a bunch of monoenergetic photons at the center of a wavelength- shifting sphere, forcing reemission, and check that the final wavelength distribution matches the wls spectrum. ''' import scipy.stats nphotons = 1e5 # set up detector -- a sphere of 'scintillator' surrounded by a # detecting sphere scint = Material('scint') scint.set('refractive_index', 1) scint.set('absorption_length', 1.0) scint.set('scattering_length', 1e7) scint.set('reemission_prob', 1) x = np.arange(0,1000,10) norm = scipy.stats.norm(scale=50, loc=600) pdf = 10 * norm.pdf(x) cdf = norm.cdf(x) scint.reemission_cdf = np.array(zip(x, cdf)) detector = Surface('detector') detector.set('detect', 1) world = Geometry(vacuum) world.add_solid(Solid(sphere(1000), vacuum, vacuum, surface=detector)) world.add_solid(Solid(sphere(500), scint, vacuum)) w = create_geometry_from_obj(world, update_bvh_cache=False) sim = Simulation(w, geant4_processes=0) # initial photons -- isotropic 250 nm at the origin pos = np.tile([0,0,0], (nphotons,1)).astype(np.float32) dir = np.random.rand(nphotons, 3).astype(np.float32) * 2 - 1 dir /= np.sqrt(dir[:,0]**2 + dir[:,1]**2 + dir[:,2]**2)[:,np.newaxis] pol = np.zeros_like(pos) t = np.zeros(nphotons, dtype=np.float32) wavelengths = np.ones(nphotons).astype(np.float32) * 250 photons = Photons(pos=pos, dir=dir, pol=pol, t=t, wavelengths=wavelengths) # run simulation and extract final wavelengths event = sim.simulate([photons], keep_photons_end=True).next() mask = (event.photons_end.flags & SURFACE_DETECT) > 0 final_wavelengths = event.photons_end.wavelengths[mask] # compare wavelength distribution to scintillator's reemission pdf hist, edges = np.histogram(final_wavelengths, bins=x) print 'detected', hist.sum(), 'of', nphotons, 'photons' hist_norm = 1.0 * hist / (1.0 * hist.sum() / 1000) pdf /= (1.0 * pdf.sum() / 1000) chi2 = scipy.stats.chisquare(hist_norm, pdf[:-1])[1] print 'chi2 =', chi2 # show histogram comparison #plt.figure(1) #width = edges[1] - edges[0] #plt.bar(left=edges, height=pdf, width=width, color='red') #plt.bar(left=edges[:-1], height=hist_norm, width=width) #plt.show() self.assertTrue(chi2 > 0.75)
def main(suppress=False): ##### Loading in Results ##### print '-'*90 print 'ANALYSIS SCRIPT FOR VUV SIMULATION'.center(90,'=') print '-'*90 print 'Available files:\n' filenames = [] display = ['#','Date:','Time:','n:',u'\u03bb (nm)'.encode('utf-8'),u'd (\u03bcm)'.encode('utf-8'),u's (\u03bcm)'.encode('utf-8')] print '{:<3}{:<12}{:<9}{:<9}{:<9}{:<9}{:<9}'.format(*display) for pos,filename in enumerate(os.listdir('results/')): filenames.append(filename) date = re.findall('\d+\-\d+\-\d+',filename)[0] time = re.findall('\d{2}\:\d{2}',filename)[0] args = re.findall('\d+\:\d+:\d+\.\d+\:\d+\.\d+',filename)[0] display = [str(pos),date,time] + args.split(':') print '{:<3}{:<12}{:<9}{:<9}{:<8}{:<8}{:<9}'.format(*display) print '(d - Slit Width, s - Beam Width)' print '='*90 proceed = False while proceed == False: usrin = raw_input('Please select a file: ').strip() if usrin not in [str(i) for i in list(range(len(filenames)))]: print 'Bad input!' else: proceed = True selection = filenames[int(usrin)] n,wavelength,width,beamsize = [float(i) for i in re.findall('\d+\:\d+:\d+\.\d+\:\d+\.\d+',selection)[0].split(':')] #print '\nSelected: '+ selection + '\n' ##### Data Loading and Results ##### itm = 25.4 #Inches to millimetre conversion tol = 0.05 #Tolerance for selecting photons res = 10 #Seed resolution seed = np.genfromtxt('config/seed.dat',delimiter=',') #display seed x = 0.01 #marker params h = x*np.sqrt(3)/2 pyramid = make.linear_extrude([-x/2,0,x/2],[-h/2,h/2,-h/2],h,[0]*3,[0]*3) beg_marker = Solid(pyramid,vacuum,vacuum,color=0x32a8a2) #photon start marker end_marker = Solid(pyramid,vacuum,vacuum,color=0xfc0303) #photon end marker dir_marker = Solid(pyramid,vacuum,vacuum,color=0x00ff00) #Direction data = p.read_csv('results/'+selection,sep = '\t').values[:] photon_ids = data[:,0].astype(int) beg_pos = data[:,1:4] end_pos = data[:,4:7] wavelengths = data[:,7].astype(int) flags = data[:,8].astype(int) if suppress == False: if os.path.exists('config/geometry.pickle'): print 'Loading geometry from file...' geometry_path = open('config/geometry.pickle','rb') world = cPickle.load(geometry_path) world.add_solid(dir_marker,displacement = mfunctions.get_center('161')+np.array((0,1,0))) geometry_path.close() else: world = builder.build() #Regenerate geometry sipm_ids = [] pmt_ids = [] for p_id in photon_ids: if (flags[p_id] & 0x1 << 2) == 4: if abs(end_pos[p_id,2]-9.08933)<tol: #Photons detected at SiPM sipm_ids.append(p_id) elif abs(end_pos[p_id,0]-23.6555999)<tol: #Photons detected at PMT pmt_ids.append(p_id) if suppress == False and seed[p_id] <= 1.0/res: world.add_solid(beg_marker,displacement=beg_pos[p_id]) world.add_solid(end_marker,displacement=end_pos[p_id]) pcount = len(pmt_ids) scount = len(sipm_ids) print 'Total photons:\t\t', n print 'Detections (PMT):\t', pcount print 'Detections (SiPM):\t',scount print 'Relative detection rate: {:.2f}%'.format(100*float(scount+pcount)/len(photon_ids)) if suppress == False: view(world) elif suppress == True and scount == 0: print 'No photons detected!' exit() else: detectedx = np.asarray(list(end_pos[i,0]*itm for i in sipm_ids)) #Compiling points to plot and converting to mm detectedy = np.asarray(list(end_pos[i,1]*itm for i in sipm_ids)) detectedwavs = np.array(list(wavelengths[i] for i in pmt_ids)) meanx = np.mean(detectedx) #Mean detected positions meany = np.mean(detectedy) detectedx -= meanx #Centering plot about detected photons detectedy -= meany sampleset = np.sqrt(detectedx**2 + detectedy**2) #Heatmap info binwidth = 2*ss.iqr(sampleset)/np.cbrt(len(sampleset)) #Freedman-Diaconis rule bins = (max(sampleset)-min(sampleset))/binwidth heatmap,xedges,yedges = np.histogram2d(detectedx,detectedy,bins=bins) extent = [xedges[0],xedges[-1],yedges[0],yedges[-1]] fig = plt.figure() ax1 = fig.add_subplot(121) ax2 = fig.add_subplot(122) ax1.axis('equal') ax2.axis('equal') fig.suptitle(u'n: {} | \u03bb: {}nm | d: {}\u03bcm | s: {}\u03bcm'.format(int(n),int(wavelength),width,beamsize)) r = 0.059813*itm #Iris radius theta = np.linspace(0,2*np.pi,1000) iris_center = np.array((18.4429,-21.8872))*itm iris_x = r*np.cos(theta)+iris_center[0]-meanx iris_y = r*np.sin(theta)+iris_center[1]-meany ax1.scatter(detectedx,detectedy,s=0.3,c='k',label='Photon Hits') #ax1.plot(iris_x,iris_y,c='r',label='Iris Overlay') ax1.set_xlabel('x (mm)') ax1.set_ylabel('y (mm)') ax1.set_title('SiPM Hits') ax1.legend() im = ax2.imshow(heatmap.T,extent=extent,origin='lower',cmap='hot_r',interpolation='gaussian') ax2.set_xlabel('x (mm)') ax2.set_ylabel('y (mm)') ax2.set_title('SiPM Heat Map') fig.colorbar(im,ax=ax2) plt.show()
def build(slit_width): directory = 'stls/' #directory containing individual detector .stls ##### Generating World ##### print 'Generating world...' size = (40,40,40) #Bounds for world box world = Geometry(vacuum) bounds = Solid(make.box(*size),vacuum,vacuum,color=0x33ffffff) world.add_solid(bounds,displacement=(18,-18,10)) ##### Generating Non-Special Solids ##### print 'Generating non-special solids...' blacklist = mfunctions.get_blacklist() #Mesh read-in blacklist tol = 0.005 #Tolerance for painting meshes using conditionals setup_solid = Solid(make.cube(1),vacuum,vacuum,color=0xf0fc03) #origin for filename in os.listdir(directory): path = os.path.join(directory,filename) tmp_mesh = mesh_from_stl(path) mesh_no = re.findall('\d+',path)[0] #Select mesh number if mesh_no in ['141','142']: #Angled mirrors setup_surf, setup_col = mfunctions.mirror(mesh_no) """ elif mesh_no in ['161','206']: #Slit housing setup_surf = shiny_surface setup_col = 0x7d6b56 #grey """ else: #Non-optical components setup_surf = black_surface setup_col = 0x7d6b56 #grey #print 'Adding solid '+mesh_no+'/249' if mesh_no not in blacklist: #Remove blacklisted meshes from setup setup_solid += Solid(tmp_mesh,vacuum,vacuum,surface=setup_surf,color=setup_col) world.add_solid(setup_solid) ##### Generating Special Solids ##### print 'Generating special solids...' """ #Laser Absorb laser_absorb = Solid(make.segmented_cylinder(1,0.1),vacuum,vacuum,surface=black_surface,color=0xffff00) cap_center = np.array((18.470600,-26.771999,19.469999)) world.add_solid(laser_absorb,displacement=cap_center) """ #SiPM Plate sipm_plate = Solid(mesh_from_stl(directory+'vuv_setup - Mesh 188.stl'),vacuum,vacuum,surface=black_surface,color=0xfc7f03) rotation_matrix = mfunctions.Ry(np.arctan(0.0755832)) #Correction for SiPM plate rotation in the STL sipm_center = np.array((18.48695,-22.89630,9.08933)) #Centre of SiPM plate correction = sipm_center-np.matmul(rotation_matrix,sipm_center) #Off-centre rotation induces displacement, this corrects for this world.add_solid(sipm_plate,rotation=rotation_matrix,displacement=correction+np.array((0,1,0))) #Detector Plate/SiPM detector = Solid(make.box(2,3,0.01),vacuum,vacuum,surface=mfunctions.get_sipm_surface(),color=0x0dff00) world.add_solid(detector,displacement=sipm_center+np.array((0,0.8,0))) #Closed Mirror #mirror_surf,mirror_col = mfunctions.mirror('250') mirror_surf = mfunctions.get_mirror_surface() mirror_col = 0xe6ad12 closed_mirror = Solid(mesh_from_stl(directory+'vuv_setup - Mesh 250.stl'),vacuum,vacuum,surface=mirror_surf,color=mirror_col) world.add_solid(closed_mirror,rotation=mfunctions.Ry(np.pi),displacement=np.array((18.470901,-21.916050,19.602501))+np.array((0,0,0.613293))) #Post-Monochromator Slit Doors basex = np.array((0.0,0.0,0.074744,0.6,0.6))#x-points for door base basey = np.array((0.0,0.00488564,0.0800017,0.0800017,0.0))#y-points height = 0.65 #Max slit width: 1000micron door1 = make.linear_extrude(basex,basey,height) door2 = make.linear_extrude(-basex,basey,height) slit_center = mfunctions.get_center('161') door1_solid = Solid(door1,vacuum,vacuum,surface=shiny_surface,color=0xffff00) door2_solid = Solid(door2,vacuum,vacuum,surface=shiny_surface,color=0xffff00) world.add_solid(door1_solid,displacement=slit_center+np.array((0.5*slit_width,-0.144076,-0.05))) world.add_solid(door2_solid,displacement=slit_center+np.array((-0.5*slit_width,-0.144076,-0.05))) #PMT plate pmt_center = mfunctions.get_center('210') pmt_center += np.array((0.4,0,0)) pmt_solid = Solid(make.segmented_cylinder(1.5,0.1),vacuum,vacuum,surface=mfunctions.get_pmt_surface(),color=0x0dff00) world.add_solid(pmt_solid,rotation=mfunctions.Rz(np.pi/2),displacement=pmt_center) ##### Saving Geometry to File ##### config_path = open('config/geometry.pickle','wb') cPickle.dump(world,config_path) config_path.close() config_path = open('config/vars.txt','wb') config_path.write(str(slit_width)+'\n') config_path.write('This is the config file for geometry.pickle.\nAltering this file will cause geometry.pickle to regenerate!') config_path.close() print 'Done.' return world
def build_checkerboard_scene(checkers_per_side=10, squares_per_checker=50): x = np.linspace(-5000.0, 5000.0, checkers_per_side * squares_per_checker + 1) y = np.linspace(-5000.0, 5000.0, checkers_per_side * squares_per_checker + 1) vertices = np.array(tuple(product(x, y, [0]))) triangles = [] for j in range(y.size - 1): for i in range(x.size - 1): triangles.append([ j * len(x) + i, (j + 1) * len(x) + i, (j + 1) * len(x) + i + 1 ]) triangles.append( [j * len(x) + i, j * len(x) + i + 1, (j + 1) * len(x) + i + 1]) checkerboard_mesh = Mesh(vertices, triangles, remove_duplicate_vertices=True) checkerboard_color_line1 = take( checkers_per_side * squares_per_checker * 2, cycle([0] * 2 * squares_per_checker + [0xffffff] * 2 * squares_per_checker)) * squares_per_checker checkerboard_color_line2 = take( checkers_per_side * squares_per_checker * 2, cycle([0xffffff] * 2 * squares_per_checker + [0] * 2 * squares_per_checker)) * squares_per_checker checkerboard_color = take( len(checkerboard_mesh.triangles), cycle(checkerboard_color_line1 + checkerboard_color_line2)) checkerboard_surface_line1 = take( checkers_per_side * squares_per_checker * 2, cycle([black_surface] * 2 * squares_per_checker + [lambertian_surface] * 2 * squares_per_checker)) * squares_per_checker checkerboard_surface_line2 = take( checkers_per_side * squares_per_checker * 2, cycle([lambertian_surface] * 2 * squares_per_checker + [black_surface] * 2 * squares_per_checker)) * squares_per_checker checkerboard_surface = take( len(checkerboard_mesh.triangles), cycle(checkerboard_surface_line1 + checkerboard_surface_line2)) checkerboard = Solid(checkerboard_mesh, vacuum, vacuum, surface=checkerboard_surface, color=checkerboard_color) sphere1 = Solid(sphere(1000.0, nsteps=512), water, vacuum) sphere2 = Solid(sphere(1000.0, nsteps=512), vacuum, vacuum, surface=shiny_surface) sphere3 = Solid(sphere(1000.0, nsteps=512), vacuum, vacuum, surface=lambertian_surface) checkerboard_scene = Geometry() checkerboard_scene.add_solid(checkerboard, displacement=(0, 0, -1500.0)) checkerboard_scene.add_solid(sphere1, displacement=(2000.0, -2000.0, 0)) checkerboard_scene.add_solid(sphere2, displacement=(-2000.0, -2000.0, 0)) checkerboard_scene.add_solid(sphere3, displacement=(0.0, 2000.0, 0)) return checkerboard_scene
def build_lens_icosahedron(kabamland, vtx, rad, diameter_ratio, thickness_ratio, half_EPD, blockers=True, blocker_thickness_ratio=1.0 / 1000, light_confinement=False, focal_length=1.0, lens_system_name=None): """input edge length of icosahedron 'edge_length', the number of small triangles in the base of each face 'base', the ratio of the diameter of each lens to the maximum diameter possible 'diameter_ratio' (or the fraction of the default such ratio, if a curved detector lens system), the ratio of the thickness of the lens to the chosen (not maximum) diameter 'thickness_ratio', the radius of the blocking entrance pupil 'half_EPD', and the ratio of the thickness of the blockers to that of the lenses 'blocker_thickness_ratio' to return the icosahedron of lenses in kabamland. Light_confinment=True adds cylindrical shells behind each lens that absorb all the light that touches them, so that light doesn't overlap between lenses. If lens_system_name is a string that matches one of the lens systems in lenssystem.py, the corresponding lenses and detectors will be built. Otherwise, a default simple lens will be built, with parameters hard-coded below. """ # Get the list of lens meshes from the appropriate lens system as well as the lens material''' scale_rad = rad * diameter_ratio #max_radius->rad of the lens assembly lenses = lenssystem.get_lens_mesh_list(lens_system_name, scale_rad) lensmat = lenssystem.get_lens_material(lens_system_name) face = None for lns in lenses: #lns = mh.rotate(lns,make_rotation_matrix(ph,ax)) if not face: face = Solid(lns, lensmat, kabamland.detector_material) else: face += Solid(lns, lensmat, kabamland.detector_material) if light_confinement: shield = mh.rotate( cylindrical_shell(rad * (1 - 0.001), rad, focal_length, 32), make_rotation_matrix(np.pi / 2.0, (1, 0, 0))) baffle = Solid(shield, lensmat, kabamland.detector_material, black_surface, 0xff0000) if blockers: blocker_thickness = 2 * rad * blocker_thickness_ratio if half_EPD < rad: c1 = lenssystem.get_lens_sys( lens_system_name).c1 * lenssystem.get_scale_factor( lens_system_name, scale_rad) offset = [0, 0, c1 - np.sqrt(c1 * c1 - rad * rad)] anulus_blocker = mh.shift( mh.rotate( cylindrical_shell(half_EPD, rad, blocker_thickness, 32), make_rotation_matrix(np.pi / 2.0, (1, 0, 0))), offset) face += Solid(anulus_blocker, lensmat, kabamland.detector_material, black_surface, 0xff0000) phi, axs = rot_axis([0, 0, 1], vtx) for vx, ph, ax in zip(vtx, -phi, axs): kabamland.add_solid(face, rotation=make_rotation_matrix(ph, ax), displacement=-vx) if light_confinement: kabamland.add_solid(baffle, rotation=make_rotation_matrix(ph, ax), displacement=-normalize(vx) * (np.linalg.norm(vx) + focal_length / 2.0))
def visit(self, node, debug=False): """ :param node: DAENode instance DAENode instances and their pycollada underpinnings meet chroma here Chroma needs sensitive detectors to have an associated surface with detect property ... """ #assert node.__class__.__name__ == 'DAENode' self.vcount += 1 if self.vcount < 10: log.debug("visit : vcount %s node.index %s node.id %s " % (self.vcount, node.index, node.id)) bps = list(node.boundgeom.primitives()) bpl = bps[0] assert len(bps) == 1 and bpl.__class__.__name__ == 'BoundPolylist' tris = bpl.triangleset() vertices = tris._vertex triangles = tris._vertex_index mesh = Mesh(vertices, triangles, remove_duplicate_vertices=False) material2, material1 = self.find_outer_inner_materials(node) surface = self.find_surface( node) # lookup Chroma surface corresponding to the node if surface == None: surfacename = "NOT SPECIFIED" else: surfacename = surface.name if self.dump_node_info: print "[NODE %05d:%s]" % ( node.index, node.lv.id ), " NTriangles=%d OuterMat=%s InnerMat=%s Surface=%s" % (len( mesh.triangles), material2.name, material1.name, surfacename) color = 0x33ffffff solid = Solid(mesh, material1, material2, surface, color) solid.node = node # # hmm a PMT is comprised of several volumes all of which # have the same associated channel_id # channel_id = getattr(node, 'channel_id', None) if not channel_id is None and channel_id > 0: self.channel_count += 1 # nodes with associated non zero channel_id self.channel_ids.add(channel_id) self.chroma_geometry.add_pmt(solid, channel_id=channel_id) else: self.chroma_geometry.add_solid(solid) pass if debug and self.vcount % 1000 == 0: print node.id print self.vcount, bpl, tris, tris.material print mesh #print mesh.assemble() bounds = mesh.get_bounds() extent = bounds[1] - bounds[0] print extent
def load_geometry_from_string(geometry_str, auto_build_bvh=True, read_bvh_cache=True, update_bvh_cache=True, cache_dir=None, cuda_device=None): '''Create or load a geometry and optionally load/build a BVH for it. This is a convenience interface to the geometry and BVH construction code, as well as the Chroma caching layer. Most applications should use this function rather than manually building a Geometry and BVH. The geometry string passed to this function has several forms: "" (empty string) - Load the default geometry from the cache and the default BVH for that geometry. "filename.stl" or "filename.stl.bz2" - Create a geometry from a 3D mesh on disk. This model will not be cached, but the BVH can be, depending on whether update_bvh_cache is True. "geometry_name" - Load a geometry from the cache with this name and the default BVH for that geometry. "geometry_name:bvh_name" - Load a geometry from the cache and the requested BVH by name. "@chroma.models.lionsolid" - Run this function inside a Python module, found in the current $PYTHONPATH, to create the geometry, and load the default BVH. For convenience, the current directory is also added to the $PYTHONPATH. "@chroma.models.lionsolid:bvh_name" - Run this function to create the Geometry and load a BVH by name. By default, the Chroma cache in the user's home directory is consulted for both the geometry and the BVH. A different cache directory can be selected by passing the path in via the ``cache_dir`` parameter. If ``read_bvh_cache`` is set to False, then the BVH cache will not be inspected for BVH objects. If the requested BVH (default, or named) does not exist for this geometry (checked by MD5 hashing the geometry mesh) and ``auto_build_bvh`` is true, then a BVH will be automatically generated using the "simple" BVH algorithm. The simple algorithm is very fast, but produces a poor quality BVH. Any newly created BVH will be saved in the Chroma cache if the ``update_cache_bvh`` parameter is True. BVH construction requires a GPU, so the CUDA device number can be specified with the ``cuda_device`` parameter. Returns: a Geometry object (or subclass) with the ``bvh`` property set if the options allow. ''' # Find BVH id if given bvh_name = 'default' if ':' in geometry_str: geometry_id, bvh_name = geometry_str.split(':') else: geometry_id = geometry_str if cache_dir is None: cache = Cache() else: cache = Cache(cache_dir) # Where is the geometry coming from? if os.path.exists(geometry_id) and \ geometry_id.lower().endswith(('.stl', '.bz2')): # Load from file mesh = mesh_from_stl(geometry_id) geometry = Geometry() geometry.add_solid(Solid(mesh, vacuum, vacuum, color=0x33ffffff)) geometry.flatten() elif geometry_id.startswith('@'): # Load from function function_path = geometry_id[1:] module_name, obj_name = function_path.rsplit('.', 1) orig_sys_path = list(sys.path) try: sys.path.append('.') module = __import__(module_name, fromlist=[obj_name]) sys.path = orig_sys_path except ImportError: sys.path = orig_sys_path raise obj = getattr(module, obj_name) geometry = create_geometry_from_obj(obj, bvh_name=bvh_name, auto_build_bvh=auto_build_bvh, read_bvh_cache=read_bvh_cache, update_bvh_cache=update_bvh_cache, cache_dir=cache_dir, cuda_device=cuda_device) return geometry # RETURN EARLY HERE! ALREADY GOT BVH else: # Load from cache if geometry_id == '': geometry = cache.load_default_geometry() else: geometry = cache.load_geometry(geometry_id) # Cached geometries are flattened already geometry.bvh = load_bvh(geometry, bvh_name=bvh_name, auto_build_bvh=auto_build_bvh, read_bvh_cache=read_bvh_cache, update_bvh_cache=update_bvh_cache, cache_dir=cache_dir, cuda_device=cuda_device) return geometry