def test_solve_spherical(): pde = laplacian_spherical generator = ExampleGeneratorSpherical(512) # 0-boundary condition; solution should be u(r, theta, phi) = 0 identically f = lambda th, ph: 0. g = lambda th, ph: 0. condition = DirichletBVPSpherical(r_0=0., f=f, r_1=1., g=g) solution, loss_history = solve_spherical(pde, condition, 0.0, 1.0, max_epochs=500, return_best=True) rs, thetas, phis = generator.get_examples() us = solution(rs, thetas, phis, as_type='np') assert np.isclose(us, np.zeros_like(us), atol=0.005).all(), f"Solution is not straight 0s: {us}" # 1-boundary condition; solution should be u(r, theta, phi) = 1 identically f = lambda th, ph: 1. g = lambda th, ph: 1. condition = DirichletBVPSpherical(r_0=0., f=f, r_1=1., g=g) solution, loss_history = solve_spherical(pde, condition, 0.0, 1.0, max_epochs=500, return_best=True) rs, thetas, phis = generator.get_examples() us = solution(rs, thetas, phis, as_type='np') assert np.isclose(us, np.ones_like(us), atol=0.005).all(), f"Solution is not straight 1s: {us}" print("solve_spherical tests passed")
def test_monitor_spherical(): pde = laplacian_spherical f = lambda th, ph: 0. g = lambda th, ph: 0. condition = DirichletBVPSpherical(r_0=0., f=f, r_1=1., g=g) monitor = MonitorSpherical(0.0, 1.0, check_every=1) solve_spherical(pde, condition, 0.0, 1.0, max_epochs=50, monitor=monitor) print("MonitorSpherical test passed")
def test_train_generator_spherical(): pde = laplacian_spherical condition = NoConditionSpherical() train_generator = ExampleGeneratorSpherical(size=64, r_min=0., r_max=1., method='equally-spaced-noisy') r, th, ph = train_generator.get_examples() assert (0. < r.min()) and (r.max() < 1.) assert (0. <= th.min()) and (th.max() <= np.pi) assert (0. <= ph.min()) and (ph.max() <= 2 * np.pi) valid_generator = ExampleGeneratorSpherical(size=64, r_min=1., r_max=1., method='equally-radius-noisy') r, th, ph = valid_generator.get_examples() assert (r == 1).all() assert (0. <= th.min()) and (th.max() <= np.pi) assert (0. <= ph.min()) and (ph.max() <= 2 * np.pi) solve_spherical(pde, condition, 0.0, 1.0, train_generator=train_generator, valid_generator=valid_generator, max_epochs=5) with raises(ValueError): _ = ExampleGeneratorSpherical(64, method='bad_generator') with raises(ValueError): _ = ExampleGeneratorSpherical(64, r_min=-1.0) with raises(ValueError): _ = ExampleGeneratorSpherical(64, r_min=1.0, r_max=0.0) print("GeneratorSpherical tests passed")
def test_electric_potential_uniformly_charged_ball(): """ electric potential on uniformly charged solid sphere refer to http://www.phys.uri.edu/~gerhard/PHY204/tsl94.pdf """ # free charge volume density rho = 1. # medium permittivity epsilon = 1. # Coulomb constant k = 1. / (4 * np.pi * epsilon) # radius of the ball R = 1. # total electric charge on solid sphere Q = (4 / 3) * np.pi * (R**3) * rho # electric potential at sphere center v_0 = 1.5 * k * Q / R # electric potential on sphere boundary v_R = k * Q / R # analytic solution of electric potential analytic_solution = lambda r, th, ph: k * Q / (2 * R) * (3 - (r / R)**2) pde = lambda u, r, theta, phi: laplacian_spherical(u, r, theta, phi ) + rho / epsilon condition = DirichletBVPSpherical(r_0=0., f=lambda th, ph: v_0, r_1=R, g=lambda th, ph: v_R) monitor = MonitorSpherical(0.0, R, check_every=50) solution, loss_history, analytic_mse = solve_spherical( pde, condition, 0., R, max_epochs=500, return_best=True, analytic_solution=analytic_solution, monitor=monitor) generator = ExampleGeneratorSpherical(512) rs, thetas, phis = generator.get_examples() us = solution(rs, thetas, phis, as_type="np") vs = analytic_solution(rs, thetas, phis).detach().cpu().numpy() abs_diff = abs(us - vs) assert np.isclose(us, vs, atol=0.008).all(), \ f"Solution doesn't match analytic expectation {us} != {vs}, abs_diff={abs_diff}" print("electric-potential-on-uniformly-charged-solid-sphere passed")
def test_solve_spherical(): pde = laplacian_spherical generator = GeneratorSpherical(512) # 0-boundary condition; solution should be u(r, theta, phi) = 0 identically f = lambda th, ph: 0. g = lambda th, ph: 0. condition = DirichletBVPSpherical(r_0=0., f=f, r_1=1., g=g) with pytest.warns(FutureWarning): solution, loss_history = solve_spherical(pde, condition, 0.0, 1.0, max_epochs=2, return_best=True) assert isinstance(solution, SolutionSpherical)
def test_solve_spherical(): pde = laplacian_spherical generator = GeneratorSpherical(512) # 0-boundary condition; solution should be u(r, theta, phi) = 0 identically f = lambda th, ph: 0. g = lambda th, ph: 0. condition = DirichletBVPSpherical(r_0=0., f=f, r_1=1., g=g) solution, loss_history = solve_spherical(pde, condition, 0.0, 1.0, max_epochs=2, return_best=True) rs, thetas, phis = generator.get_examples() us = solution(rs, thetas, phis, as_type='np')
def test_electric_potential_gaussian_charged_density(): # total charge Q = 1. # standard deviation of gaussian sigma = 1. # medium permittivity epsilon = 1. # Coulomb constant k = 1 / (4 * np.pi * epsilon) # coefficient of gaussian term gaussian_coeff = Q / (sigma**3) / np.power(2 * np.pi, 1.5) # distribution of charge rho_f = lambda r: gaussian_coeff * torch.exp(-r.pow(2) / (2 * sigma**2)) # analytic solution, refer to https://en.wikipedia.org/wiki/Poisson%27s_equation analytic_solution = lambda r, th, ph: (k * Q / r) * torch.erf(r / (np.sqrt( 2) * sigma)) pde = lambda u, r, th, ph: laplacian_spherical(u, r, th, ph) + rho_f( r) / epsilon r_0, r_1 = 0.1, 3. v_0 = (k * Q / r_0) * erf(r_0 / (np.sqrt(2) * sigma)) v_1 = (k * Q / r_1) * erf(r_1 / (np.sqrt(2) * sigma)) condition = DirichletBVPSpherical(r_0, lambda th, ph: v_0, r_1, lambda th, ph: v_1) monitor = MonitorSpherical(r_0, r_1, check_every=50) solution, loss_history, analytic_mse = solve_spherical( pde, condition, r_0, r_1, max_epochs=500, return_best=True, analytic_solution=analytic_solution, monitor=monitor) generator = ExampleGeneratorSpherical(512, r_min=r_0, r_max=r_1) rs, thetas, phis = generator.get_examples() us = solution(rs, thetas, phis, as_type="np") vs = analytic_solution(rs, thetas, phis).detach().cpu().numpy() rdiff = abs(us - vs) / vs assert np.isclose(us, vs, rtol=0.05).all(), \ f"Solution doesn't match analytic expectattion {us} != {vs}, relative-diff={rdiff}" print("electric-potential-on-gaussian-charged-density passed")
def test_electric_potential_gaussian_charged_density(): # total charge Q = 1. # standard deviation of gaussian sigma = 1. # medium permittivity epsilon = 1. # Coulomb constant k = 1 / (4 * np.pi * epsilon) # coefficient of gaussian term gaussian_coeff = Q / (sigma**3) / np.power(2 * np.pi, 1.5) # distribution of charge rho_f = lambda r: gaussian_coeff * torch.exp(-r.pow(2) / (2 * sigma**2)) # analytic solution, refer to https://en.wikipedia.org/wiki/Poisson%27s_equation analytic_solution = lambda r, th, ph: (k * Q / r) * torch.erf(r / (np.sqrt( 2) * sigma)) # interior and exterior radius r_0, r_1 = 0.1, 3. # values at interior and exterior boundary v_0 = (k * Q / r_0) * erf(r_0 / (np.sqrt(2) * sigma)) v_1 = (k * Q / r_1) * erf(r_1 / (np.sqrt(2) * sigma)) def validate(solution): generator = GeneratorSpherical(512, r_min=r_0, r_max=r_1) rs, thetas, phis = generator.get_examples() us = solution(rs, thetas, phis, to_numpy=True) vs = analytic_solution(rs, thetas, phis).detach().cpu().numpy() assert us.shape == vs.shape # solving the problem using normal network (subject to the influence of polar singularity of laplacian operator) pde1 = lambda u, r, th, ph: laplacian_spherical(u, r, th, ph) + rho_f( r) / epsilon condition1 = DirichletBVPSpherical(r_0, lambda th, ph: v_0, r_1, lambda th, ph: v_1) monitor1 = MonitorSpherical(r_0, r_1, check_every=50) with pytest.warns(FutureWarning): solution1, metrics_history = solve_spherical( pde1, condition1, r_0, r_1, max_epochs=2, return_best=True, analytic_solution=analytic_solution, monitor=monitor1, ) validate(solution1) # solving the problem using spherical harmonics (laplcian computation is optimized) max_degree = 2 harmonics_fn = RealSphericalHarmonics(max_degree=max_degree) harmonic_laplacian = HarmonicsLaplacian(max_degree=max_degree) pde2 = lambda R, r, th, ph: harmonic_laplacian(R, r, th, ph) + rho_f( r) / epsilon R_0 = torch.tensor([v_0 * 2] + [0 for _ in range((max_degree + 1)**2 - 1)]) R_1 = torch.tensor([v_1 * 2] + [0 for _ in range((max_degree + 1)**2 - 1)]) def analytic_solution2(r, th, ph): sol = torch.zeros(r.shape[0], (max_degree + 1)**2) sol[:, 0:1] = 2 * analytic_solution(r, th, ph) return sol condition2 = DirichletBVPSphericalBasis(r_0=r_0, R_0=R_0, r_1=r_1, R_1=R_1, max_degree=max_degree) monitor2 = MonitorSphericalHarmonics(r_0, r_1, check_every=50, harmonics_fn=harmonics_fn) net2 = FCNN(n_input_units=1, n_output_units=(max_degree + 1)**2) with pytest.warns(FutureWarning): solution2, metrics_history = solve_spherical( pde2, condition2, r_0, r_1, net=net2, max_epochs=2, return_best=True, analytic_solution=analytic_solution2, monitor=monitor2, harmonics_fn=harmonics_fn, ) validate(solution2)