import numpy as np
from py_diff_stokes_flow.env.env_base import EnvBase
from py_diff_stokes_flow.common.common import ndarray
from py_diff_stokes_flow.core.py_diff_stokes_flow_core import ShapeComposition2d, StdIntArray2d
class FluidicTwisterEnv3d(EnvBase):
def __init__(self, seed, folder):
np.random.seed(seed)
cell_nums = (32, 32, 16)
E = 100
nu = 0.499
vol_tol = 1e-2
edge_sample_num = 2
EnvBase.__init__(self, cell_nums, E, nu, vol_tol, edge_sample_num, folder)
# Initialize the parametric shapes.
self._parametric_shape_info = [ ('polar_bezier-6', 51)]
# Initialize the node conditions.
self._node_boundary_info = []
inlet_radius = 0.3
outlet_radius = 0.3
inlet_velocity = 1.0
outlet_velocity = 2.0
cx, cy, _ = self.cell_nums()
assert cx == cy
nx, ny, nz = self.node_nums()
def get_bezier(radius):
bezier = ShapeComposition2d()
params = np.concatenate([
np.full(8, radius) * cx,
ndarray([0.5 * cx, 0.5 * cy, 0])
])
bezier.AddParametricShape('polar_bezier', params.size)
cxy = StdIntArray2d((int(cx), int(cy)))
bezier.Initialize(cxy, params, True)
return bezier
inlet_bezier = get_bezier(inlet_radius)
outlet_bezier = get_bezier(outlet_radius)
for i in range(nx):
for j in range(ny):
if inlet_bezier.signed_distance((i, j)) > 0:
self._node_boundary_info.append(((i, j, 0, 0), 0))
self._node_boundary_info.append(((i, j, 0, 1), 0))
self._node_boundary_info.append(((i, j, 0, 2), inlet_velocity))
# Initialize the interface.
self._interface_boundary_type = 'free-slip'
# Compute the target velocity field (for rendering purposes only)
desired_omega = 2 * outlet_velocity / (cx * outlet_radius)
target_velocity_field = np.zeros((nx, ny, 3))
for i in range(nx):
for j in range(ny):
if outlet_bezier.signed_distance((i, j)) > 0:
x, y = i / cx, j / cy
# u = (-(j - ny / 2), (i - nx / 2), 0) * c.
# ux_pos = (-j, i + 1, 0) * c.
# uy_pos = (-j - 1, i, 0) * c.
# curl = (i + 1) * c + (j + 1) * c - i * c - j * c.
# = (i + j + 2 - i - j) * c = 2 * c.
# c = outlet_vel / (num_cells[0] * outlet_radius).
c = desired_omega / 2
target_velocity_field[i, j] = ndarray([
-(y - 0.5) * c,
(x - 0.5) * c,
0
])
# Other data members.
self._inlet_radius = inlet_radius
self._outlet_radius = outlet_radius
self._inlet_velocity = inlet_velocity
self._target_velocity_field = target_velocity_field
self._inlet_bezier = inlet_bezier
self._outlet_bezier = outlet_bezier
self._desired_omega = desired_omega
def _variables_to_shape_params(self, x):
x = ndarray(x).copy().ravel()
assert x.size == 32
cx, cy, _ = self._cell_nums
assert cx == cy
params = np.concatenate([
np.full(8, self._inlet_radius),
x,
np.full(8, self._outlet_radius),
ndarray([0.5, 0.5, 0]),
])
params[:-1] *= cx
# Jacobian.
J = np.zeros((params.size, x.size))
for i in range(x.size):
J[8 + i, i] = cx
return ndarray(params).copy(), ndarray(J).copy()
def _loss_and_grad_on_velocity_field(self, u):
u_field = self.reshape_velocity_field(u)
grad = np.zeros(u_field.shape)
nx, ny, nz = self.node_nums()
assert nx == ny
loss = 0
cnt = 0
for i in range(nx):
for j in range(ny):
if self._outlet_bezier.signed_distance((i, j)) > 0:
cnt += 1
uxy = u_field[i, j, nz - 1, :2]
ux_pos = u_field[i + 1, j, nz - 1, :2]
uy_pos = u_field[i, j + 1, nz - 1, :2]
# Compute the curl.
curl = ux_pos[1] - uy_pos[0] - uxy[1] + uxy[0]
loss += (curl - self._desired_omega) ** 2
# ux_pos[1]
grad[i + 1, j, nz - 1, 1] += 2 * (curl - self._desired_omega)
grad[i, j + 1, nz - 1, 0] += -2 * (curl - self._desired_omega)
grad[i, j, nz - 1, 1] += -2 * (curl - self._desired_omega)
grad[i, j, nz - 1, 0] += 2 * (curl - self._desired_omega)
loss /= cnt
grad /= cnt
return loss, ndarray(grad).ravel()
def _color_velocity(self, u):
return float(np.linalg.norm(u) / 2)
def sample(self):
return np.random.uniform(low=self.lower_bound(), high=self.upper_bound())
def lower_bound(self):
return np.full(32, 0.1)
def upper_bound(self):
return np.full(32, 0.4)