def test_domain_grid_from_function(self): grid = Domain(x=4, y=3).scalar_grid(lambda x: math.sum(x**2, 'vector')) math.assert_close(grid.values.x[0].y[0], 0.5) self.assertEqual(grid.shape.volume, 12) grid = Domain( x=4, y=3).scalar_grid(lambda x: math.ones(x.shape.non_channel)) math.assert_close(grid.values, 1)
def test_falling_block_long(self): """ Tests if a block of liquid has a constant shape during free fall. """ DOMAIN = Domain(x=32, y=128, boundaries=STICKY, bounds=Box[0:32, 0:128]) DT = 0.05 ACCESSIBLE = DOMAIN.accessible_mask([], type=StaggeredGrid) PARTICLES = DOMAIN.distribute_points(union(Box[12:20, 110:120])) * (0, 0) extent = math.max(PARTICLES.points, dim='points') - math.min( PARTICLES.points, dim='points') state = dict(particles=PARTICLES, domain=DOMAIN, dt=DT, accessible=ACCESSIBLE) for i in range(90): state = step(**state) curr_extent = math.max(state['particles'].points, dim='points') - \ math.min(state['particles'].points, dim='points') math.assert_close(curr_extent, extent) # shape of falling block stays the same assert math.max(state['particles'].points, dim='points')[1] < \ math.max(PARTICLES.points, dim='points')[1] # block really falls extent = curr_extent
def test_runge_kutta_4(self): domain = Domain(x=4, y=3) points = domain.distribute_points(domain.bounds, points_per_cell=2) v = CenteredGrid(Noise(vector=2), x=4, y=3) field.assert_close(points, advect.runge_kutta_4(points, v, 0), advect.runge_kutta_4(points, v * 0, 0)) sv = StaggeredGrid(Noise(), x=4, y=3) field.assert_close(points, advect.runge_kutta_4(points, sv, 0), advect.runge_kutta_4(points, sv * 0, 0))
def test_domain_grid_from_constant(self): domain = Domain(x=4, y=3) # int / float grid = domain.scalar_grid(1) math.assert_close(grid.values, 1) # complex grid = domain.scalar_grid(1 + 1j) math.assert_close(grid.values, 1 + 1j) # NumPy grid = domain.scalar_grid(numpy.array(1)) math.assert_close(grid.values, 1)
def test_symmetry(self): """ Tests the symmetry of a setup where a liquid block collides with 2 rotated obstacles. """ SIZE = 64 MID = SIZE / 2 DOMAIN = Domain(x=SIZE, y=SIZE, boundaries=STICKY, bounds=Box[0:SIZE, 0:SIZE]) DT = 0.05 OBSTACLE = union([ Box[20:30, 10:12].rotated(math.tensor(20)), Box[34:44, 10:12].rotated(math.tensor(-20)) ]) ACCESSIBLE = DOMAIN.accessible_mask(OBSTACLE, type=StaggeredGrid) x_low = 26 x_high = 38 y_low = 40 y_high = 50 PARTICLES = DOMAIN.distribute_points( union(Box[x_low:x_high, y_low:y_high]), center=True) * (0, 0) x_num = int((x_high - x_low) / 2) y_num = y_high - y_low particles_per_cell = 8 total = x_num * y_num state = dict(particles=PARTICLES, domain=DOMAIN, dt=DT, accessible=ACCESSIBLE) for i in range(100): state = step(**state) particles = state['particles'].points left = particles.points[particles.vector[0] < MID] right = particles.points[particles.vector[0] > MID] self.assertEqual(left.points.size, right.points.size) mirrored = math.copy(right).numpy('points,vector') mirrored[:, 0] = 2 * MID - right[:, 0] smirrored = np.zeros_like(mirrored) # --- particle order of mirrored version differs from original one and must be fixed for MSE # (caused by ordering in phi.physics._boundaries _distribute_points) --- for p in range(particles_per_cell): for b in range(x_num): smirrored[p * total + b * y_num:p * total + (b + 1) * y_num] = \ mirrored[(p + 1) * total - (b + 1) * y_num:(p + 1) * total - b * y_num] mse = np.square(smirrored - left.numpy('points,vector')).mean() if i < 45: assert mse == 0 # block was falling until this step, hits obstacles at step 46 else: assert mse <= 1e-3 # error increases gradually after block and obstacles collide
def test_effects(self): world = World() fluid = world.add(Fluid(Domain(x=16, y=16)), physics=IncompressibleFlow()) fan = world.add(Fan(Sphere((10, 8), 5), [-1, 0])) obstacle = world.add(Obstacle(Box[0:1, 0:1])) world.step(dt=1) world.step(dt=0.5) assert fluid.age == fan.age == obstacle.age == 1.5
def test_smoke_plume(self): world = World() world.batch_size = 3 fluid = world.add(Fluid(Domain(x=16, y=16)), physics=IncompressibleFlow()) inflow = world.add(Inflow(Sphere((8, 8), radius=4))) world.step() world.step(fluid) self.assertAlmostEqual(fluid.age, 2.0) self.assertAlmostEqual(inflow.age, 1.0)
def test_respect_boundaries(self): """ Tests if particles really get puhsed outside of obstacles and domain boundaries. """ SIZE = 64 DOMAIN = Domain(x=SIZE, y=SIZE, boundaries=STICKY, bounds=Box[0:SIZE, 0:SIZE]) OBSTACLE = Box[20:40, 10:30] PARTICLES = DOMAIN.distribute_points( union(Box[20:38, 20:50], Box[50:60, 10:50]), center=True) * (10, 0) PARTICLES = advect.points(PARTICLES, PARTICLES, 1) assert math.any(OBSTACLE.lies_inside(PARTICLES.points)) assert math.any((~DOMAIN.bounds).lies_inside(PARTICLES.points)) PARTICLES = flip.respect_boundaries(PARTICLES, DOMAIN, [OBSTACLE], offset=0.1) assert math.all(~OBSTACLE.lies_inside(PARTICLES.points)) assert math.all(~(~DOMAIN.bounds).lies_inside(PARTICLES.points))
def test_block_and_pool(self): """ Tests if the impact of a block on a pool has no sideeffects (e.g. liquid explosion). """ SIZE = 32 DOMAIN = Domain(x=SIZE, y=SIZE, boundaries=STICKY, bounds=Box[0:SIZE, 0:SIZE]) DT = 0.05 ACCESSIBLE = DOMAIN.accessible_mask([], type=StaggeredGrid) PARTICLES = DOMAIN.distribute_points( union(Box[:, :5], Box[12:18, 15:20])) * (0, 0) state = dict(particles=PARTICLES, domain=DOMAIN, dt=DT, accessible=ACCESSIBLE) for i in range(100): state = step(**state) assert math.all(state['particles'].points.vector[1] < 15)
def test_single_particles(self): """ Tests if single particles at the boundaries and within the domain really fall down. """ SIZE = 32 DOMAIN = Domain(x=SIZE, y=SIZE, boundaries=STICKY, bounds=Box[0:SIZE, 0:SIZE]) DT = 0.05 ACCESSIBLE = DOMAIN.accessible_mask([], type=StaggeredGrid) PARTICLES = DOMAIN.distribute_points(union( Box[0:1, 10:11], Box[31:32, 20:21], Box[10:11, 10:11]), points_per_cell=1) * (0, 0) self.assertEqual(PARTICLES.points.shape['points'].size, 3) state = dict(particles=PARTICLES, domain=DOMAIN, dt=DT, accessible=ACCESSIBLE) for i in range(10): state = step(**state) assert math.all(state['particles'].points.vector[1] < PARTICLES.points.vector[1]) PARTICLES = state['particles']
def simulate(centers): world = World() fluid = world.add(Fluid(Domain(x=5, y=4, bounds=Box(0, [40, 32])), buoyancy_factor=0.1, batch_size=centers.shape[0]), physics=IncompressibleFlow()) world.add(Inflow(Sphere(center=centers, radius=3), rate=0.2)) world.add(Fan(Sphere(center=centers, radius=5), acceleration=[1.0, 0])) world.step(dt=1.5) world.step(dt=1.5) world.step(dt=1.5) assert not math.close(fluid.density.values, 0) print() return fluid.density.values.batch[0], fluid.velocity.values.batch[0]
def test_pool(self): """ Tests if a pool of liquid at the bottom stays constant over time. """ SIZE = 32 DOMAIN = Domain(x=SIZE, y=SIZE, boundaries=STICKY, bounds=Box[0:SIZE, 0:SIZE]) DT = 0.05 ACCESSIBLE = DOMAIN.accessible_mask([], type=StaggeredGrid) PARTICLES = DOMAIN.distribute_points(union(Box[:, :10])) * (0, 0) state = dict(particles=PARTICLES, domain=DOMAIN, dt=DT, accessible=ACCESSIBLE) for i in range(100): state = step(**state) occupied_start = PARTICLES.with_values(1) @ DOMAIN.scalar_grid() occupied_end = state['particles'].with_values(1) @ DOMAIN.scalar_grid() math.assert_close(occupied_start.values, occupied_end.values) math.assert_close(PARTICLES.points, state['particles'].points, abs_tolerance=1e-3)
def test_direct_fluid(self): fluid = Fluid(Domain(x=16, y=16)) fluid2 = IncompressibleFlow().step(fluid) assert fluid2.age == 1.0 assert fluid.age == 0.0 assert fluid2.name == fluid.name
scene = Scene.create(parent_directory=params['output']) # phiflow scene log.addHandler(logging.FileHandler(os.path.normpath(scene.path) + '/run.log')) log.info(params) log.info('tensorflow-{} ({}, {}); keras-{} ({})'.format( tf.__version__, tf.sysconfig.get_include(), tf.sysconfig.get_lib(), keras.__version__, keras.__path__)) if params['output']: with open(os.path.normpath(scene.path) + '/params.pickle', 'wb') as f: pickle.dump(params, f) domain = Domain(y=params['res'] * 2, x=params['res'], bounds=Box[0:params['len'] * 2, 0:params['len']], boundaries=OPEN) # init density & velocity d0 = phi.field.read(params['initdH']).at( domain.scalar_grid()) if params['initdH'] else domain.scalar_grid(0) if params['initvH']: v0 = phi.field.read(params['initvH']).at(domain.staggered_grid()) else: vv = np.ones(domain.staggered_grid().vector['y'].shape.sizes ) # warm start - initialize flow to 1 along y everywhere uu = np.zeros(domain.staggered_grid().vector['x'].shape.sizes) uu[uu.shape[0] // 2 + 10:uu.shape[0] // 2 + 20, uu.shape[1] // 2 - 2:uu.shape[1] // 2 +
""" Point Cloud Demo Demonstrates working with PointCloud objects and plotting them. """ from phi.physics._boundaries import Domain, STICKY as CLOSED from phi.flow import * DOMAIN = Domain(x=64, y=64, boundaries=CLOSED, bounds=Box(x=100, y=100)) # points = DOMAIN.points([(1, 1), (20, 20)], color=['#ba0a04', '#344feb']) points1 = DOMAIN.points((1, 1), color='#ba0a04') points2 = DOMAIN.points((20, 20), color='#344feb') # points = points1 & points2 points = field.concat([points1, points2], instance('points')) # Advection velocity = DOMAIN.vector_grid([-1, 1]) points = advect.advect(points, velocity, 10) # RK4 points = advect.advect(points, points * (-1, 1), -5) # Euler # Grid sampling scattered_data = field.sample(points, DOMAIN.cells) scattered_grid = points @ DOMAIN.vector_grid() scattered_sgrid = points @ DOMAIN.staggered_grid() view(namespace=globals())
v_hi = [ math.concat([ self.dataPreloaded[self.dataSims[self.epoch[self.batchIdx + i][ self.stepIdx][0]]][max([ 0, self.epoch[self.batchIdx + i][self.stepIdx][1] - j * with_skip ])][2] for i in range(self.batchSize) ], axis=0) for j in range(previous_frames) ] # TODO: additional channels return [d_hi, v_hi] domain = Domain(y=params['res'] * 2, x=params['res'], bounds=Box[0:params['len'] * 2, 0:params['len']], boundaries=OPEN) simulator_lo = KarmanFlow(domain=domain) dataset = PhifDataset(domain=domain, dirpath=params['train'], num_frames=params['simsteps'], num_sims=params['nsims'], batch_size=params['sbatch'], print_fn=log.info, skip_preprocessing=params['skip_ds']) if params['only_ds']: exit(0) if params['pretf']: with open(os.path.dirname(params['pretf']) + '/stats.pickle', 'rb') as f: ld_stats = pickle.load(f)
def test_domain_grid_from_field(self): large_grid = Domain(x=4, y=3).grid(Noise()) small_grid = Domain(x=3, y=2).grid(large_grid) math.assert_close(large_grid.values.x[:-1].y[:-1], small_grid.values)
""" FLIP simulation for liquids A liquid block collides with a rotated obstacle and falls into a liquid pool. """ from phi.physics._boundaries import Domain, STICKY as CLOSED from phi.flow import * # from phi.torch.flow import * # from phi.tf.flow import * # from phi.jax.flow import * DOMAIN = Domain(x=64, y=64, boundaries=CLOSED, bounds=Box(x=64, y=64)) GRAVITY = math.tensor([0, -9.81]) DT = 0.1 OBSTACLE = Box(x=(1, 25), y=(30, 33)).rotated(-20) ACCESSIBLE_MASK = field.stagger( CenteredGrid(~OBSTACLE, extrapolation.ZERO, x=64, y=64), math.minimum, extrapolation.ZERO) _OBSTACLE_POINTS = DOMAIN.distribute_points(OBSTACLE, color='#000000', points_per_cell=1, center=True) # only for plotting particles = DOMAIN.distribute_points( union(Box(x=(15, 30), y=(50, 60)), Box(x=None, y=(-INF, 5)))) * (0, 0) velocity = particles @ DOMAIN.staggered_grid() pressure = DOMAIN.scalar_grid() scene = particles & _OBSTACLE_POINTS * (0, 0) # only for plotting for _ in view('scene,velocity,pressure', display='scene',
def test_demo_vs_numpy(self): dt = 0.1 # Physical parameters L = 4 * 2 * np.pi # 2 * np.pi / 0.15 # 4*2*np.pi#/0.15 c1 = 5 # 0.01 # Numerical Parameters grid_pts = 64 nu = 0 # 1e-8 N = 0 # 3 arakawa_coeff = 0 kappa_coeff = 0 # Derived Parameters dx = L / grid_pts k0 = 2 * np.pi / L # Get input data rnd_noise = np.random.rand(grid_pts * grid_pts).reshape( grid_pts, grid_pts) sine = get_2d_sine((grid_pts, grid_pts), L) init_values = sine / 1000 density_coeff = 1 omega_coeff = -1 / 2 phi_coeff = -1 / 2 x = grid_pts y = grid_pts params = dict(c1=c1, nu=nu, N=N, arakawa_coeff=arakawa_coeff, kappa_coeff=kappa_coeff) # NumPy reference hw = HW( c1=c1, nu=nu, N=N, arakawa_coeff=arakawa_coeff, kappa_coeff=kappa_coeff, debug=False, ) hw_state_numpy = Namespace( density=init_values * density_coeff, omega=init_values * omega_coeff, phi=init_values * phi_coeff, age=0, dx=dx, ) # Phi version domain = Domain(x=x, y=y, boundaries=PERIODIC, bounds=Box[0:L, 0:L]) hw_state_phi = Namespace( density=domain.grid( math.tensor(init_values * density_coeff, spatial('x, y'))), omega=domain.grid( math.tensor(init_values * omega_coeff, spatial('x, y'))), phi=domain.grid( math.tensor(init_values * phi_coeff, spatial('x, y'))), # domain=domain, age=0, dx=dx, ) from functools import partial get_phi = partial(get_domain_phi, domain=domain) rk4_step2 = partial(rk4_step, params=params, get_phi=get_phi) # Run def compare(iterable): for k in iterable[0].keys(): compare = [] for state in iterable: if isinstance(state[k], field._grid.Grid): val = state[k].values.numpy(order="x,y,z")[0] else: val = state[k] compare.append(val) assert len(compare) == 2 print(f" {k:<7}:" + f" {np.max(np.abs(compare[0]-compare[1])):.7f}" + f" {np.array_equal(*compare)}" + f" {np.max(np.abs(compare[0]-compare[1])):.2g}") return True np_times = [] phi_times = [] for i in range(0, STEPS + 1): print(f"step {i:>3} {i*dt:>12.7f}") # Numpy t0 = time.time() hw_state_numpy = hw.rk4_step(hw_state_numpy, dt=dt) np_time = time.time() - t0 np_times.append(np_time) # Phi t0 = time.time() hw_state_phi = rk4_step2(dt=dt, **hw_state_phi) phi_time = time.time() - t0 phi_times.append(phi_time) compare([hw_state_numpy, hw_state_phi]) # if i % 100 == 0: # plot({'numpy': hw_state_numpy.density, # 'phi': hw_state_phi.density.values.numpy(order='zyx')[0], # 'diff': hw_state_numpy.density - hw_state_phi.density.values.numpy(order='zyx')[0]}, # title=f"step: {i}") # assert np.allclose(hw_state_numpy.density, hw_state_phi.density.values.numpy(order='zyx')[0]) print(f"Comparison | NumPy | PhiFlow") def get_str(name, func, vals): return " | ".join([ f"{name:<10}", f"{func(vals[0]):<10.4f}", f"{func(vals[1]):<10.4f}", ]) print(get_str("Mean (s)", np.mean, (np_times, phi_times))) print(get_str("Median (s)", np.median, (np_times, phi_times))) print(get_str("Min (s)", np.min, (np_times, phi_times))) print(get_str("Max (s)", np.max, (np_times, phi_times)))
def test_properties_dict(self): world = World() world.add(Fluid(Domain(x=16, y=16)), physics=IncompressibleFlow()) world.add(Inflow(Sphere((8, 8), radius=4))) world.add(Fan(Sphere((10, 8), 5), [-1, 0])) struct.properties_dict(world.state)
def test_domain_grid_from_tensor(self): domain = Domain(x=4, y=3) grid = domain.vector_grid(Noise(vector=2)) grid2 = domain.vector_grid(grid.values) math.assert_close(grid.values, grid2.values)
def test_custom_spatial_dims(self): domain = Domain(a=4, b=3) grid = domain.scalar_grid(1) self.assertEqual(spatial(a=4, b=3), grid.shape) grid = domain.staggered_grid(1) self.assertEqual(spatial(a=4, b=3) & channel(vector=2), grid.shape)
""" Profiles the common fluid operations advection and pressure solve. The profile is stored in the working directory and can be viewed with e.g. with Google chrome. """ from phi.physics._boundaries import Domain, STICKY as CLOSED from phi.flow import * # from phi.torch.flow import * # from phi.tf.flow import * # from phi.jax.flow import * DOMAIN = Domain(x=128, y=128, boundaries=CLOSED, bounds=Box(x=100, y=100)) velocity = DOMAIN.staggered_grid(Noise(vector=2)) pressure = DOMAIN.scalar_grid(0) with backend.profile(save=f'navier_stokes_{backend.default_backend()}.json'): velocity = advect.semi_lagrangian(velocity, velocity, dt=1) # velocity, pressure = fluid.make_incompressible(velocity, DOMAIN, pressure_guess=pressure)
def test_advect_points(self): domain = Domain(x=4, y=3) v = domain.distribute_points(domain.bounds, points_per_cell=2) * (1, -1) field.assert_close(v, advect.points(v, v, 0), advect.points(v, v * 0, 0))