def exact_polar(self, freqs, azimuths=None): '''Retrieves the MTF at the specified frequency-azimuth pairs Args: freqs (`iterable`): radial frequencies to retrieve MTF for. azimuths (`iterable`): corresponding azimuths to retrieve MTF for. Returns: list: MTF at the given points. ''' self._make_interp_function() # handle user-unspecified azimuth if azimuths is None: if type(freqs) in (int, float): # single azimuth azimuths = 0 else: azimuths = [0] * len(freqs) # handle single azimuth, multiple freqs elif type(azimuths) in (int, float): azimuths = [azimuths] * len(freqs) azimuths = np.radians(azimuths) # handle single value case if type(freqs) in (int, float): x, y = polar_to_cart(freqs, azimuths) return float(self.interpf((x, y), method='linear')) outs = [] for freq, az in zip(freqs, azimuths): x, y = polar_to_cart(freq, az) outs.append(float(self.interpf((x, y), method='linear'))) return outs
def test_polar_to_cart(rho, phi): x, y = coordinates.polar_to_cart(rho, phi) assert np.allclose(x, rho * np.cos(phi)) assert np.allclose(y, rho * np.sin(phi))
def generate_collimated_ray_fan(nrays, maxr, z=0, minr=None, azimuth=90, yangle=0, xangle=0, distribution='uniform', aim_at=None): """Generate a 1D fan of rays. Colloquially, an extended field in Y for an object at inf is represented by a ray fan with yangle != 0. Parameters ---------- nrays : int the number of rays in the fan maxr : float maximum radial value of the fan z : float z position for the ray fan minr : float, optional minimum radial value of the fan, -maxr if None azimuth: float angle in the XY plane, degrees. 0=X ray fan, 90=Y ray fan yangle : float propagation angle of the rays with respect to the Y axis, clockwise xangle : float propagation angle of the rays with respect to the X axis, clockwise distribution : str, {'uniform', 'random', 'cheby'} The distribution to use when placing the rays a uniform distribution has rays which are equally spaced from minr to maxr, random has rays randomly distributed in minr and maxr, while cheby has the Cheby-Gauss-Lobatto roots as its locations from minr to maxr aim_at : numpy.ndarray or float position [X,Y,Z] aim the rays such that the gut ray of the fan, when propagated in an open medium, hits (aim_at) if a float, interpreted as if x=0,y=0, z=aim_at This argument mimics ray aiming in commercial optical design software, and is used in conjunction with xangle/yangle to form off-axis ray bundles which properly go through the center of the stop Returns ------- numpy.ndarray, numpy.ndarray "P" and "S" variables, positions and direction cosines of the rays """ dtype = config.precision distribution = distribution.lower() if minr is None: minr = -maxr S = np.array([0, 0, 1], dtype=dtype) R = make_rotation_matrix((0, yangle, -xangle)) S = np.matmul(R, S) # need to see a copy of S for each ray, -> add empty dim and broadcast S = S[np.newaxis, :] S = np.broadcast_to(S, (nrays, 3)) # now generate the radial part of P if distribution == 'uniform': r = np.linspace(minr, maxr, nrays, dtype=dtype) elif distribution == 'random': r = np.random.uniform(low=minr, high=maxr, size=nrays).astype(dtype) t = np.asarray(np.radians(azimuth), dtype=dtype) t = np.broadcast_to(t, r.shape) x, y = polar_to_cart(r, t) z = np.array(z, dtype=x.dtype) z = np.broadcast_to(z, x.shape) xyz = np.stack([x, y, z], axis=1) return xyz, S