def test_fourier_synthesis_1D_input(): H = 0.7 c0 = 1. n = 512 s = n * 4. ls = 8 np.random.seed(0) fourier_synthesis((n, ), (s, ), H, c0=c0, long_cutoff=s / 2, short_cutoff=ls, amplitude_distribution=lambda n: np.ones(n))
def test_shortcut_vs_isotropic_filter(): print(SurfaceTopography.__file__) n = 100 # t = SurfaceTopography(np.zeros(n,n), (2,3)) np.random.seed(0) t = fourier_synthesis((n, n), (13, 13), 0.9, 1.) cutoff_wavevector = 2 * np.pi / 13 * 0.4 * n hc = t.shortcut(cutoff_wavevector=cutoff_wavevector) fhc = t.filter(filter_function=lambda q: q <= cutoff_wavevector) assert hc.is_filter_isotropic assert fhc.is_filter_isotropic if False: import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.loglog(*hc.power_spectrum_2D(), "+", label="shortcut") ax.loglog(*fhc.power_spectrum_2D(), "x", label="filter") ax.loglog(*t.power_spectrum_2D(), label="original") ax.legend() fig.show() np.testing.assert_allclose(fhc.heights(), hc.heights())
def test_uniform_scaled_topography(): surf = fourier_synthesis((5, 7), (1.2, 1.1), 0.8, rms_height=1) sx, sy = surf.physical_sizes for fac in [1.0, 2.0, np.pi]: surf2 = surf.scale(fac) np.testing.assert_almost_equal(fac * surf.rms_height_from_profile(), surf2.rms_height_from_profile()) np.testing.assert_almost_equal(surf.positions(), surf2.positions()) surf2 = surf.scale(fac, 2 * fac) np.testing.assert_almost_equal(fac * surf.rms_height_from_profile(), surf2.rms_height_from_profile()) sx2, sy2 = surf2.physical_sizes np.testing.assert_almost_equal(2 * fac * sx, sx2) np.testing.assert_almost_equal(2 * fac * sy, sy2) x, y = surf.positions() x2, y2 = surf2.positions() np.testing.assert_almost_equal(2 * fac * x, x2) np.testing.assert_almost_equal(2 * fac * y, y2) x2, y2, h2 = surf2.positions_and_heights() np.testing.assert_almost_equal(2 * fac * x, x2) np.testing.assert_almost_equal(2 * fac * y, y2)
def test_wrapped_bicubic_vs_fourier(sx, sy): # test against fourier interpolation # sx and sy are varied to ensure the unit conversions of the slopes are # correct nx, ny = [35, 42] hc = 0.2 * sx np.random.seed(0) topography = fourier_synthesis( (nx, ny), (sx, sy), 0.8, rms_height=1., short_cutoff=hc, long_cutoff=hc + 1e-9, ) topography = topography.scale(1 / topography.rms_height()) interp = topography.interpolate_bicubic() fine_topography = topography.interpolate_fourier((4 * nx, 4 * ny)) interp_height, interp_slopex, interp_slopey = interp( *fine_topography.positions(), derivative=1) np.testing.assert_allclose(interp_height, fine_topography.heights(), atol=1e-2) derx, dery = fine_topography.fourier_derivative() rms_slope = topography.rms_slope() np.testing.assert_allclose(interp_slopex, derx, atol=1e-1 * rms_slope) np.testing.assert_allclose(interp_slopey, dery, atol=1e-1 * rms_slope)
def test_rms_slope_from_area(): r = 2048 res = [r, r] for H in [0.3, 0.8]: for s in [(1, 1), (1.4, 3.3)]: t = fourier_synthesis(res, s, H, short_cutoff=8 / r * np.mean(s), rms_slope=0.1, amplitude_distribution=lambda n: 1.0) last_rms_slope = t.rms_gradient() np.testing.assert_almost_equal(last_rms_slope, 0.1, decimal=2) np.testing.assert_almost_equal(last_rms_slope, t.scale(1.3).rms_gradient() / 1.3) np.testing.assert_almost_equal(last_rms_slope, t.scale(1.3, 1.3).rms_gradient()) # rms slope should not depend on filter for these cutoffs... for cutoff in [4]: rms_slope = t.rms_gradient(short_wavelength_cutoff=s[0] / r * cutoff) np.testing.assert_almost_equal(rms_slope, last_rms_slope) # ...but starts being a monotonously decreasing function here for cutoff in [16, 32]: rms_slope = t.rms_gradient(short_wavelength_cutoff=s[0] / r * cutoff) assert rms_slope < last_rms_slope last_rms_slope = rms_slope
def test_load_no_physical_sizes(comm_self): nb_grid_pts = (128, 128) size = (3, 3) np.random.seed(1) t = fourier_synthesis(nb_grid_pts, size, 0.8, rms_slope=0.1) # Topographies always have physical size information, we need to create a # NetCDF file without any manually with Dataset('no_physical_sizes.nc', 'w', format='NETCDF3_CLASSIC') as nc: nc.createDimension('x', nb_grid_pts[0]) nc.createDimension('y', nb_grid_pts[1]) nc.createVariable('heights', 'f8', ('x', 'y')) nc.variables['heights'] = t.heights() # Attempt to open full file on each process # with pytest.raises(ValueError): # # This raises an error because the physical sizes are not present # t2 = read_topography('no_physical_sizes.nc') t2 = read_topography('no_physical_sizes.nc', physical_sizes=size) assert t.physical_sizes == t2.physical_sizes assert 'unit' not in t2.info np.testing.assert_array_almost_equal(t.heights(), t2.heights()) os.remove('no_physical_sizes.nc')
def test_fourier_synthesis_linescan_c0(n): H = 0.7 c0 = 8. s = n * 4. ls = 32 qs = 2 * np.pi / ls np.random.seed(0) t = fourier_synthesis((n, ), (s, ), c0=c0, hurst=H, long_cutoff=s / 2, short_cutoff=ls, amplitude_distribution=lambda n: np.ones(n)) if False: import matplotlib.pyplot as plt q, psd = t.power_spectrum_1D() fig, ax = plt.subplots() ax.plot(q, psd) ax.plot(q, c0 * q**(-1 - 2 * H)) ax.set_xscale("log") ax.set_yscale("log") ax.set_ylim(bottom=1) fig.show() ref_slope = np.sqrt(1 / (2 * np.pi) * c0 / (1 - H) * qs**(2 - 2 * H)) assert abs(t.rms_slope() - ref_slope) / ref_slope < 1e-1
def test_fourier_synthesis_rms_height_more_wavevectors(comm_self): """ Set amplitude to 0 (rolloff = 0) outside the self affine region. Long cutoff wavelength is smaller then the box size so that we get closer to a continuum of wavevectors """ n = 256 H = 0.74 rms_height = 7. s = 1. realised_rms_heights = [] for i in range(50): topography = fourier_synthesis( (n, n), (s, s), H, rms_height=rms_height, rolloff=0, long_cutoff=s / 8, short_cutoff=4 * s / n, # amplitude_distribution=lambda n: np.ones(n) ) realised_rms_heights.append(topography.rms_height()) # print(abs(np.mean(realised_rms_heights) - rms_height) / rms_height) # TODO: this is not very accurate ! assert abs(np.mean(realised_rms_heights) - rms_height) / rms_height < 0.1
def test_fourier_synthesis_linescan_hrms_more_wavevectors(): """ Set amplitude to 0 (rolloff = 0) outside the self affine region. Long cutoff wavelength is smaller then the box size so that we get closer to a continuum of wavevectors """ H = 0.7 hrms = 4. n = 4096 s = n * 4. ls = 8 np.random.seed(0) realised_rms_heights = [] for i in range(50): t = fourier_synthesis( (n, ), (s, ), rms_height=hrms, hurst=H, rolloff=0, long_cutoff=s / 8, short_cutoff=ls, ) realised_rms_heights.append(t.rms_height()) realised_rms_heights = np.array(realised_rms_heights) ref_height = hrms # print(np.sqrt(np.mean( # (realised_rms_heights - np.mean(realised_rms_heights))**2))) assert abs(np.mean(realised_rms_heights) - ref_height) / ref_height < 0.1 #
def test_delegation(): t1 = fourier_synthesis((128,), (1,), 0.8, rms_slope=0.1) t2 = t1.detrend() t3 = t2.to_nonuniform() t4 = t3.scale(2.0) t4.scale_factor with pytest.raises(AttributeError): t2.scale_factor t2.detrend_mode # detrend_mode should not be delegated to the parent class with pytest.raises(AttributeError): t4.detrend_mode # t2 should have 'to_nonuniform' t2.to_nonuniform() # but it should not have 'to_uniform' with pytest.raises(AttributeError): t2.to_uniform(100, 10) # t4 should have 'to_uniform' t5 = t4.to_uniform(100, 10) # but it should not have 'to_nonuniform' with pytest.raises(AttributeError): t4.to_nonuniform() # t5 should have 'to_nonuniform' t5.to_nonuniform() # but it should not have 'to_uniform' with pytest.raises(AttributeError): t5.to_uniform(100, 10)
def test_uniform_vs_nonuniform(): t1 = fourier_synthesis([12], [6], 0.8, rms_slope=0.1, periodic=False) t2 = t1.to_nonuniform() d1 = t1.derivative(1) d2 = t2.derivative(1) np.testing.assert_allclose(d1, d2)
def test_q0_1D(): surf = fourier_synthesis([1024, 512], [2.3, 1.5], 0.8, rms_height=0.87) rms_height = surf.rms_height_from_profile( ) # Need to measure it since it can fluctuate wildly q, C = surf.power_spectrum_from_profile() ratio = rms_height**2 / (np.trapz(C, q) / np.pi) assert ratio > 0.2 assert ratio < 5
def test_scale_dependent_rms_slope_from_profile(): t = fourier_synthesis((1024, 1024), (1, 1), 0.8, rms_slope=0.1) x, A = t.autocorrelation_from_profile() s = t.scale_dependent_statistical_property(lambda x, y: np.var(x), distance=x[1::20]) np.testing.assert_allclose(2*A[1::20]/x[1::20]**2, s)
def test_autocompletion(): t1 = fourier_synthesis((128,), (1,), 0.8, rms_slope=0.1) t2 = t1.detrend() t3 = t2.to_nonuniform() assert 'detrend' in dir(t1) assert 'to_nonuniform' in dir(t2) assert 'to_uniform' in dir(t3)
def test_isotropic_1d(): n = 32 t = fourier_synthesis((n, ), (13, ), 0.9, 1.) cutoff_wavevector = 2 * np.pi / 13 * n / 4 q, psd = t.filter( filter_function=lambda q: q > cutoff_wavevector).power_spectrum_1D() assert (psd[q < 0.9 * cutoff_wavevector] < 1e-10).all()
def test_q0_2D(): surf = fourier_synthesis([1024, 512], [2.3, 1.5], 0.8, rms_height=0.87) rms_height = surf.rms_height_from_area( ) # Need to measure it since it can fluctuate wildly q, C = surf.power_spectrum_from_area(nbins=200, bin_edges='quadratic') # This is really not quantitative, it's just checking whether it's the right ballpark. # Any bug in normalization would show up here as an order of magnitude ratio = rms_height**2 / (np.trapz(q * C, q) / np.pi) assert ratio > 0.2 assert ratio < 5
def test_noniform_mean_zero(self): surface = fourier_synthesis((512,), (1.3,), 0.8, rms_height=1).to_nonuniform() self.assertTrue(not surface.is_uniform) x, h = surface.positions_and_heights() s, = surface.physical_sizes self.assertAlmostEqual(surface.mean(), np.trapz(h, x) / s) detrended_surface = surface.detrend(detrend_mode='height') self.assertAlmostEqual(detrended_surface.mean(), 0) x, h = detrended_surface.positions_and_heights() self.assertAlmostEqual(np.trapz(h, x), 0)
def test_randomly_rough(self): surface = fourier_synthesis((511, 511), (1., 1.), 0.8, rms_height=1) self.assertTrue(surface.is_uniform) cut = Topography(surface[:64, :64], physical_sizes=(64., 64.)) self.assertTrue(cut.is_uniform) untilt1 = cut.detrend(detrend_mode='height') untilt2 = cut.detrend(detrend_mode='slope') self.assertTrue(untilt1.is_uniform) self.assertTrue(untilt2.is_uniform) self.assertTrue(untilt1.rms_height_from_area() < untilt2.rms_height_from_area()) self.assertTrue(untilt1.rms_gradient() > untilt2.rms_gradient())
def test_third_derivatives_fourier_vs_finite_differences(plot=False): nx, ny = [512] * 2 sx, sy = [1.] * 2 lc = 0.5 topography = fourier_synthesis((nx, ny), (sx, sy), 0.8, rms_height=1., short_cutoff=lc, long_cutoff=lc + 1e-9) topography = topography.scale(1 / topography.rms_height_from_area()) # Fourier derivative dx3_topography = topography.filter(lambda qx, qy: (1j * qx)**3, isotropic=False) dx3 = dx3_topography.heights() dy3 = topography.filter(lambda qx, qy: (1j * qy)**3, isotropic=False).heights() # Finite-differences. We use central differences because this produces the # derivative at the same point as the Fourier derivative dx3_num, dy3_num = topography.derivative(3, operator=third_2d) np.testing.assert_allclose(dx3, dx3_num, atol=dx3_topography.rms_height_from_area() * 1e-1) np.testing.assert_allclose(dy3, dy3_num, atol=dx3_topography.rms_height_from_area() * 1e-1) if plot: import matplotlib.pyplot as plt fig, ax = plt.subplots() x, y = topography.positions() ax.plot(x[:, 0], topography.heights()[:, 0], label="height") ax.plot(x[:, 0], dx3[:, 0], label="fourier der") ax.plot(x[:, 0], dx3_num[:, 0], label="FD") ax.set_title("x") ax.legend() fig.show() fig, ax = plt.subplots() ax.set_title("y") x, y = topography.positions() ax.plot(y[-1, :], topography.heights()[-1, :], label="height") ax.plot(y[-1, :], dy3[-1, :], label="fourier der") ax.plot(y[-1, :], dy3_num[-1, :], label="FD") ax.legend() fig.show()
def test_transposed_topography(): surf = fourier_synthesis([124, 368], [6, 3], 0.8, rms_slope=0.1) nx, ny = surf.nb_grid_pts sx, sy = surf.physical_sizes surf2 = surf.transpose() nx2, ny2 = surf2.nb_grid_pts sx2, sy2 = surf2.physical_sizes assert nx == ny2 assert ny == nx2 assert sx == sy2 assert sy == sx2 assert (surf.heights() == surf2.heights().T).all()
def test_self_affine_topography_2d(self): r = 2048 res = [r, r] for H in [0.3, 0.8]: t = fourier_synthesis(res, (1, 1), H, rms_slope=0.1, amplitude_distribution=lambda n: 1.0) mag, bwidth, rms = t.variable_bandwidth(nb_grid_pts_cutoff=r // 32) self.assertAlmostEqual(rms[0], t.detrend().rms_height()) # Since this is a self-affine surface, rms(mag) ~ mag^-H b, a = np.polyfit(np.log(mag[1:]), np.log(rms[1:]), 1) # The error is huge... self.assertTrue(abs(H + b) < 0.1)
def test_self_affine_uniform_autocorrelation(): r = 2048 s = 1 H = 0.8 slope = 0.1 t = fourier_synthesis((r,), (s,), H, rms_slope=slope, amplitude_distribution=lambda n: 1.0) r, A = t.autocorrelation_from_profile() m = np.logical_and(r > 1e-3, r < 10 ** (-1.5)) b, a = np.polyfit(np.log(r[m]), np.log(A[m]), 1) assert abs(b / 2 - H) < 0.1
def test_nonuniform_rms_height(): r = 128 s = 1.3 H = 0.8 slope = 0.1 t = fourier_synthesis((r,), (s,), H, rms_slope=slope, amplitude_distribution=lambda n: 1.0) \ .to_nonuniform().detrend(detrend_mode='center') assert_almost_equal(t.mean(), 0) r, A = height_height_autocorrelation(t, distances=[0]) s, = t.physical_sizes assert_almost_equal(t.rms_height_from_profile() ** 2 * s, A[0])
def test_write(): nx, ny = 1782, 1302 t = fourier_synthesis((nx, ny), (1, 1), 0.8, rms_slope=0.1) with tempfile.TemporaryDirectory() as d: t.to_dzi('synthetic', d) assert os.path.exists(f'{d}/synthetic_files') for i in range(12): assert os.path.exists(f'{d}/synthetic_files/{i}') root = ET.parse(open(f'{d}/synthetic.xml')).getroot() assert root.attrib['TileSize'] == '256' assert root.attrib['Overlap'] == '1' assert root.attrib['Format'] == 'jpg' assert root[0].attrib['Width'] == f'{nx}' assert root[0].attrib['Height'] == f'{ny}'
def test_self_affine_topography_1d(self): r = 16384 for H in [0.3, 0.8]: t0 = fourier_synthesis((r,), (1,), H, rms_slope=0.1, amplitude_distribution=lambda n: 1.0) for t in [t0, t0.to_nonuniform()]: mag, bwidth, rms = t.variable_bandwidth( nb_grid_pts_cutoff=r // 32) self.assertAlmostEqual(rms[0], t.detrend().rms_height()) np.testing.assert_allclose(bwidth, t.physical_sizes[0] / mag) # Since this is a self-affine surface, rms(mag) ~ mag^-H b, a = np.polyfit(np.log(mag[1:]), np.log(rms[1:]), 1) # The error is huge... self.assertTrue(abs(H + b) < 0.1)
def test_save_and_load(comm): nb_grid_pts = (128, 128) size = (3, 3) np.random.seed(1) t = fourier_synthesis(nb_grid_pts, size, 0.8, rms_slope=0.1, unit='µm') fft = FFT(nb_grid_pts, communicator=comm, fft="mpi") fft.create_plan(1) dt = t.domain_decompose(fft.subdomain_locations, fft.nb_subdomain_grid_pts, communicator=comm) assert t.unit == 'µm' assert dt.unit == 'µm' assert t.info['unit'] == 'µm' assert dt.info['unit'] == 'µm' if comm.size > 1: assert dt.is_domain_decomposed # Save file dt.to_netcdf('parallel_save_test.nc') # Attempt to open full file on each MPI process t2 = read_topography('parallel_save_test.nc') assert t.physical_sizes == t2.physical_sizes assert t.unit == t2.unit assert t.info['unit'] == t2.info['unit'] np.testing.assert_array_almost_equal(t.heights(), t2.heights()) # Attempt to open file in parallel r = NCReader('parallel_save_test.nc', communicator=comm) assert r.channels[0].nb_grid_pts == nb_grid_pts t3 = r.topography(subdomain_locations=fft.subdomain_locations, nb_subdomain_grid_pts=fft.nb_subdomain_grid_pts) assert t.physical_sizes == t3.physical_sizes assert t.unit == t3.unit assert t.info['unit'] == t3.info['unit'] np.testing.assert_array_almost_equal(dt.heights(), t3.heights()) assert t3.is_periodic comm.barrier() if comm.rank == 0: os.remove('parallel_save_test.nc')
def test_c_vs_py_reference(): from _SurfaceTopography import nonuniform_autocorrelation r = 16 s = 1 H = 0.8 slope = 0.1 t = fourier_synthesis((r,), (s,), H, rms_slope=slope, amplitude_distribution=lambda n: 1.0).to_nonuniform() r1, A1 = py_autocorrelation_from_profile(t) s, = t.physical_sizes r2, A2 = nonuniform_autocorrelation(*t.positions_and_heights(), s) assert_array_almost_equal(r1, r2) assert_array_almost_equal(A1, A2)
def test_fourier_synthesis(n): H = 0.74 rms_slope = 1.2 s = 2e-6 topography = fourier_synthesis((n, n), (s, s), H, rms_slope=rms_slope, long_cutoff=s / 4, short_cutoff=4 * s / n) qx, psdx = topography.power_spectrum_1D() qy, psdy = topography.transpose().power_spectrum_1D() assert psdy[-1] < 10 * psdx[-1] # assert psdy is not much bigger assert abs(topography.rms_slope() - rms_slope) / rms_slope < 1e-1
def test_constrained_conjugate_gradients(): nb_grid_pts = (512, 512) physical_sizes = (1., 1.) hurst = 0.8 rms_slope = 0.1 modulus = 1 np.random.seed(999) topography = fourier_synthesis(nb_grid_pts, physical_sizes, hurst, rms_slope=rms_slope) substrate = PeriodicFFTElasticHalfSpace(nb_grid_pts, modulus, physical_sizes) system = make_system(substrate, topography) system.minimize_proxy(offset=0.1)
def test_save_and_load_no_unit(comm_self): nb_grid_pts = (128, 128) size = (3, 3) np.random.seed(1) t = fourier_synthesis(nb_grid_pts, size, 0.8, rms_slope=0.1) # Save file t.to_netcdf('no_unit.nc') t2 = read_topography('no_unit.nc') assert t.physical_sizes == t2.physical_sizes assert 'unit' not in t2.info np.testing.assert_array_almost_equal(t.heights(), t2.heights()) os.remove('no_unit.nc')