def _extract_theta_romb(fields: np.ndarray, ics: np.ndarray) -> np.ndarray: """Compute Eq. (5) of [1] using Romberg integration.""" if not can_romberg(fields): fine_fields, fine_ics = resample_evenly(fields, ics) else: fine_fields, fine_ics = fields, ics step = abs(fine_fields[0] - fine_fields[1]) theta = np.empty_like(fields) for i, (field, ic) in enumerate(zip(fields, ics)): # don't divide by zero denom = field**2 - fine_fields**2 denom[denom == 0] = 1e-9 theta[i] = field / np.pi * romb( (np.log(fine_ics) - np.log(ic)) / denom, step) return theta
def produce_fraunhofer_fast( magnetic_field: np.ndarray, f2k: float, # field-to-wavevector conversion factor cd: np.ndarray, # current distribution xs: np.ndarray, ret_fourier: Optional[bool] = False) -> np.ndarray: """Generate Fraunhofer from current density using Romberg integration. If ret_fourier is True, return the Fourier transform instead of the critical current (useful for debugging). """ g = np.empty_like(magnetic_field, dtype=complex) if not can_romberg(xs): xs, cd = resample_evenly(xs, cd) dx = abs(xs[0] - xs[1]) for i, field in enumerate(magnetic_field): g[i] = romb(cd * np.exp(1j * f2k * field * xs), dx) return g if ret_fourier else np.abs(g)
def extract_current_distribution( fields: np.ndarray, ics: np.ndarray, f2k: float, jj_width: float, jj_points: int, debug: bool = False, theta: Optional[np.ndarray] = None, ) -> Tuple[np.ndarray, np.ndarray]: """Extract the current distribution from Ic(B). Parameters ---------- fields : np.ndarray 1D array of the magnetic field at which the critical current was measured. ics : np.ndarray 1D array of the measured value of the critical current. f2k : float Field to wave-vector conversion factor. This can be estimated from the Fraunhofer periodicity. jj_width : float Size of the junction. The current distribution will be reconstructed on a larger region (2 * jj_width) jj_points : int Number of points used to describe the junction inside jj_width. theta : np.ndarray, optional Phase distribution to use in the current reconstruction. If None, it will be extracted from the given Fraunhofer pattern. Returns ------- np.ndarray Positions at which the current density was calculated. np.ndarray Current density. """ if not can_romberg(fields): fine_fields, fine_ics = resample_evenly(fields, ics) else: fine_fields, fine_ics = fields, ics fine_ics[np.less_equal(fine_ics, 1e-10)] = 1e-10 if theta is None: theta = extract_theta(fine_fields, fine_ics, f2k, jj_width) # scale from B to beta fine_fields = f2k * fine_fields step = abs(fine_fields[0] - fine_fields[1]) xs = np.linspace(-jj_width, jj_width, int(2 * jj_points)) j = np.empty(xs.shape, dtype=complex) for i, x in enumerate(xs): j[i] = (1 / (2 * np.pi) * romb(fine_ics * np.exp(1j * (theta - fine_fields * x)), step)) if debug: f, axes = plt.subplots(2, 1) axes[0].plot(f2k * fields, ics) axes[1].plot(xs, j.real) plt.show() return xs, j.real
def test_unevenly_spaced(self): """Unevenly spaced data return false.""" x = np.arange(5) # length is good self.assertTrue(can_romberg(x)) x[-1] = 6 # spacing is bad self.assertFalse(can_romberg(x))
def test_length_less_than_2(self): """Lengths less than 2 return false.""" self.assertFalse(can_romberg([])) self.assertFalse(can_romberg([1]))
def test_length_is_1_plus_power_of_2(self): """Lengths like 2**n + 1 return true.""" result = [n for n in range(1026) if can_romberg(np.arange(n))] expected = [2, 3, 5, 9, 17, 33, 65, 129, 257, 513, 1025] self.assertEqual(result, expected)
def extract_theta( fields: np.ndarray, ics: np.ndarray, f2k: float, # field-to-k conversion factor (i.e. beta/B) jj_width: float, integ_method: Optional[Literal['romb', 'quad']] = 'romb' ) -> np.ndarray: """Compute the Ic Hilbert transform. Note the expression for θ(β) differs from Dynes and Fulton (1971) by a factor of 2, but gives the correct output. (The source of this discrepancy is as yet unknown.) Parameters ---------- fields : np.ndarray Magnetic field at which the critical current was measured. For ND input the sweep should occur on the last axis. ics : np.ndarray Measured value of the critical current. For ND input the sweep should occur on the last axis. Returns ------- np.ndarray Hilbert tranform of Ic to be used when rebuilding the current distribution. """ # scale B to beta first; then forget about it fields = fields * f2k if integ_method == 'romb': if not can_romberg(fields): fine_fields, fine_ics = resample_evenly(fields, ics) else: fine_fields, fine_ics = fields, ics step = abs(fine_fields[0] - fine_fields[1]) def integrand(beta, ic): denom = beta**2 - fine_fields**2 denom[denom == 0] = 1e-9 return (np.log(fine_ics) - np.log(ic)) / denom elif integ_method == 'quad': fine_ics = interp1d(fields, ics, 'cubic') def integrand(b, beta, ic): # quad will provide b when calling this denom = beta**2 - b**2 if denom == 0: denom = 1e-9 return (np.log(fine_ics(b)) - np.log(ic)) / denom else: raise ValueError(f"Integration method '{integ_method}' unsupported") theta = np.empty_like(fields) for i, (field, ic) in enumerate(zip(fields, ics)): if integ_method == 'romb': theta[i] = field / np.pi * romb(integrand(field, ic), step) \ - field * jj_width / 2 elif integ_method == 'quad': theta[i] = (field / np.pi * quad(integrand, np.min(fields), np.max(fields), args=(field, ic))[0] - field * jj_width / 2) return theta