1.1 MiB
1.1 MiB
In [1]:
from typing import Tuple, Union
from collections import deque
import matplotlib.pyplot as plt
import numpy as np
from stable_baselines3 import PPO
import pycuda.driver as cuda
import pandas as pd
import pickle
import sys
import os
from gym_dummy import CustomEnv as DummyEnv
current_dir = os.path.dirname(os.path.abspath("__file__"))
parent_dir = os.path.abspath(os.path.join(current_dir, os.pardir))
sys.path.append(parent_dir)
from CelerisLab import FlowField
from CelerisLab import utils
env_12 = DummyEnv(s_dim=12)
env_14 = DummyEnv(s_dim=14)
model_cloak_re100 = PPO.load(os.path.join(parent_dir, "models", "old", "d1a3o12_re100.zip"), env=env_12, device="cuda:0")
model_illusion = PPO.load(os.path.join(parent_dir, "models", "250525", "d1a3o14_250525_imit_1L_2U_600S.zip"), env=env_14, device="cuda:0")
model_illusion_075L = PPO.load(os.path.join(parent_dir, "models", "250525", "d1a3o14_250525_imit_075L_2U_400S.zip"), env=env_14, device="cuda:0")
model_illusion_15L = PPO.load(os.path.join(parent_dir, "models", "250525", "d1a3o14_250525_imit_15L_2U.zip"), env=env_14, device="cuda:0")
model_erase = PPO.load(os.path.join(parent_dir, "models", "250729", "d1a3o12_250729_250326_erase_250804_20D_retrain2.zip"), env=env_12, device="cuda:0")
model_cloak_lamb = PPO.load(os.path.join(parent_dir, "models", "old", "vortex_lamb.zip"), env=env_12, device="cuda:0")
model_cloak_taylor = PPO.load(os.path.join(parent_dir, "models", "old", "vortex_taylor.zip"), env=env_12, device="cuda:0")
model_cloak_re100.set_random_seed(0)
model_illusion.set_random_seed(19)
model_illusion_075L.set_random_seed(19)
model_illusion_15L.set_random_seed(19)
model_erase.set_random_seed(19)
model_cloak_lamb.set_random_seed(0)
model_cloak_taylor.set_random_seed(0)
cuda.init()
context = cuda.Device(0).make_context()
config_cuda = utils.load_cuda_config(os.path.join(parent_dir, "configs", "config_cuda.json"))
config_field = utils.load_flow_field_config(os.path.join(parent_dir, "configs", "config_flowfield.json"))
L0 = 20
U0 = config_field.velocity
DATA_TYPE = np.float32
CONV_LEN = 36
context.push()
flow_field = FlowField(config_field, config_cuda, device_id=0)
NX = flow_field.FIELD_SHAPE[0]
NY = flow_field.FIELD_SHAPE[1]In [2]:
def save_field(flow_field, filename):
NX = flow_field.FIELD_SHAPE[0]
NY = flow_field.FIELD_SHAPE[1]
flow_field.get_ddf()
ddf_plot = flow_field.ddf.copy().reshape((9, NY, NX)).transpose(2, 1, 0)
flag_plot = flow_field.flag.copy().reshape((NY, NX)).transpose(1, 0)
ux = (ddf_plot[:, :, 1] + ddf_plot[:, :, 5] + ddf_plot[:, :, 8] - ddf_plot[:, :, 3] - ddf_plot[:, :, 6] - ddf_plot[:, :, 7]) / U0
uy = (ddf_plot[:, :, 2] + ddf_plot[:, :, 5] + ddf_plot[:, :, 6] - ddf_plot[:, :, 4] - ddf_plot[:, :, 7] - ddf_plot[:, :, 8]) / U0
with open(os.path.join(parent_dir, "output", filename), "w") as f:
f.write("Title= \"LBM 2D\"\r\n")
f.write("VARIABLES= \"X\",\"Y\",\"flag\",\"U\",\"V\",\r\n")
f.write(f"ZONE T= \"BOX\",I= {NX},J= {NY},F=POINT\r\n")
for j in range(NY):
for i in range(NX):
f.write(f"{i},{j},{flag_plot[i, j]},{ux[i, j]},{uy[i, j]}\r\n")
class SimpleMeta:
pass
def analyze_harmonics(states, n_harmonics):
N, D = states.shape
result = []
for d in range(D):
y = states[:, d]
fft_coef = np.fft.rfft(y)
freqs = np.fft.rfftfreq(N, d=1)
amps = 2 * np.abs(fft_coef) / N
phases = np.angle(fft_coef)
idx = np.argsort(amps[1:])[::-1][:n_harmonics] + 1
harmonics = {
'dc': np.real(fft_coef[0]) / N,
'amps': amps[idx],
'freqs': freqs[idx],
'phases': phases[idx]
}
result.append(harmonics)
return resultIn [3]:
target_states = np.empty((0, 6), dtype=DATA_TYPE)
meta_cloak_steady = SimpleMeta()
meta_cloak_dipole = SimpleMeta()
meta_cloak_monopole = SimpleMeta()
meta_illusion = SimpleMeta()
meta_cloak_karman = SimpleMeta()
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 + 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 - 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
flow_field.run(int(2*NX/U0), np.zeros(3, dtype=DATA_TYPE))
for i in range(150):
flow_field.run(600, np.zeros(3, dtype=DATA_TYPE))
new_state = flow_field.obs.copy()[0:6]
target_states = np.vstack((target_states, new_state))
meta_cloak_steady.target_states = np.mean(target_states, axis=0)
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", "target_steady.dat"))
target_states = np.empty((0, 6), dtype=DATA_TYPE)
flow_field.get_ddf()
flow_field.save_ddf()
center_vor: Tuple[float, float, float] = (15 * L0, (NY - 1) / 2, 0)
flow_field.add_vortex(center_vor, L0 * 2, 0.5*U0, 0, "lamb")
for i in range(150):
flow_field.run(800, np.zeros(3, dtype=DATA_TYPE))
new_state = flow_field.obs.copy()[0:6]
target_states = np.vstack((target_states, new_state))
meta_cloak_dipole.target_states = np.mean(target_states, axis=0)
# flow_field.restore_ddf()
# flow_field.apply_ddf()
# flow_field.add_vortex(center_vor, L0 * 2, 0.5*U0, 0, "lamb")
# for i in range(100):
# flow_field.run(1000, np.zeros(3, dtype=DATA_TYPE))
# file_name = f"target_lamb.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
target_states = np.empty((0, 6), dtype=DATA_TYPE)
flow_field.restore_ddf()
flow_field.apply_ddf()
flow_field.add_vortex(center_vor, L0 * 2, 0.03*U0, 0, "taylor")
for i in range(150):
flow_field.run(800, np.zeros(3, dtype=DATA_TYPE))
new_state = flow_field.obs.copy()[0:6]
target_states = np.vstack((target_states, new_state))
meta_cloak_monopole.target_states = np.mean(target_states, axis=0)
# flow_field.restore_ddf()
# flow_field.apply_ddf()
# flow_field.add_vortex(center_vor, L0 * 2, 0.03*U0, 0, "taylor")
# for i in range(100):
# flow_field.run(1000, np.zeros(3, dtype=DATA_TYPE))
# file_name = f"target_taylor.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
In [4]:
target_states = np.empty((0, 6), dtype=DATA_TYPE)
fifo_states = deque(maxlen=150)
flow_field.restore_ddf()
flow_field.apply_ddf()
center: Tuple[float, float, float] = (30 * L0, (NY - 1) / 2, 0)
flow_field.add_cylinder(center, L0 / 2)
center: Tuple[float, float, float] = (31.3 * L0, (NY - 1) / 2 + 0.75 * L0, 0)
flow_field.add_cylinder(center, L0 / 2)
center: Tuple[float, float, float] = (31.3 * L0, (NY - 1) / 2 - 0.75 * L0, 0)
flow_field.add_cylinder(center, L0 / 2)
flow_field.run(int(4*NX/U0), np.zeros(6, dtype=DATA_TYPE))
flow_field.get_ddf()
flow_field.save_ddf()
for i in range(150):
flow_field.run(600, np.zeros(6, dtype=DATA_TYPE))
fifo_states.append(flow_field.obs.copy()[0:12])
temp_states = np.array(fifo_states)
meta_illusion.force_norm_fact = 6 * np.max(np.abs(temp_states[:, 6:12]))
meta_illusion.sens_deviation = np.zeros(6, dtype=DATA_TYPE)
meta_illusion.sens_norm_fact = np.zeros(6, dtype=DATA_TYPE)
for i in range(6):
meta_illusion.sens_deviation[i] = np.mean(temp_states[:, i])
meta_illusion.sens_norm_fact[i] = 5 * np.max(np.abs(temp_states[:, i] - meta_illusion.sens_deviation[i]))
fifo_states = deque(maxlen=150)
flow_field.restore_ddf()
flow_field.apply_ddf()
flow_field.run(int(2*NX/U0), np.array([0.0, 0.0, 0.0, 0.0, -5*U0, 5*U0], dtype=DATA_TYPE))
flow_field.add_vortex(center_vor, L0 * 2, 0.5*U0, 0, "lamb")
for i in range(150):
flow_field.run(800, np.zeros(6, dtype=DATA_TYPE))
fifo_states.append(flow_field.obs.copy()[0:12])
temp_states = np.array(fifo_states)
meta_cloak_dipole.force_norm_fact = 6 * np.max(np.abs(temp_states[:, 6:12]))
meta_cloak_dipole.sens_deviation = np.zeros(6, dtype=DATA_TYPE)
meta_cloak_dipole.sens_norm_fact = np.zeros(6, dtype=DATA_TYPE)
for i in range(6):
meta_cloak_dipole.sens_deviation[i] = np.mean(temp_states[:, i])
meta_cloak_dipole.sens_norm_fact[i] = 5 * np.max(np.abs(temp_states[:, i] - meta_cloak_dipole.sens_deviation[i]))
fifo_states = deque(maxlen=150)
flow_field.restore_ddf()
flow_field.apply_ddf()
flow_field.run(int(2*NX/U0), np.array([0.0, 0.0, 0.0, 0.0, -5*U0, 5*U0], dtype=DATA_TYPE))
flow_field.add_vortex(center_vor, L0 * 2, 0.03*U0, 0, "taylor")
for i in range(150):
flow_field.run(800, np.zeros(6, dtype=DATA_TYPE))
fifo_states.append(flow_field.obs.copy()[0:12])
temp_states = np.array(fifo_states)
meta_cloak_monopole.force_norm_fact = 6 * np.max(np.abs(temp_states[:, 6:12]))
meta_cloak_monopole.sens_deviation = np.zeros(6, dtype=DATA_TYPE)
meta_cloak_monopole.sens_norm_fact = np.zeros(6, dtype=DATA_TYPE)
for i in range(6):
meta_cloak_monopole.sens_deviation[i] = np.mean(temp_states[:, i])
meta_cloak_monopole.sens_norm_fact[i] = 5 * np.max(np.abs(temp_states[:, i] - meta_cloak_monopole.sens_deviation[i]))In [5]:
fifo_states = deque(maxlen=150)
flow_field.restore_ddf()
flow_field.apply_ddf()
center: Tuple[float, float, float] = (10 * L0, (NY - 1) / 2, 0)
flow_field.add_cylinder(center, 1*L0)
flow_field.run(int(4*NX/U0), np.zeros(7, dtype=DATA_TYPE))
for i in range(150):
flow_field.run(800, np.zeros(7, dtype=DATA_TYPE))
fifo_states.append(flow_field.obs.copy()[0:12])
temp_states = np.array(fifo_states)
meta_cloak_karman.force_norm_fact = 6 * np.max(np.abs(temp_states[:, 6:12]))
meta_cloak_karman.sens_deviation = np.zeros(6, dtype=DATA_TYPE)
meta_cloak_karman.sens_norm_fact = np.zeros(6, dtype=DATA_TYPE)
for i in range(6):
meta_cloak_karman.sens_deviation[i] = np.mean(temp_states[:, i])
meta_cloak_karman.sens_norm_fact[i] = 5 * np.max(np.abs(temp_states[:, i] - meta_cloak_karman.sens_deviation[i]))In [6]:
del flow_field
flow_field = FlowField(config_field, config_cuda, device_id=0)
center: Tuple[float, float, float] = (10 * L0, (NY - 1) / 2, 0)
flow_field.add_cylinder(center, 1*L0)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 + 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 - 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
flow_field.run(int(4*NX/U0), np.zeros(4, dtype=DATA_TYPE))
target_states = np.empty((0, 6), dtype=DATA_TYPE)
for i in range(150):
flow_field.run(800, np.zeros(4, dtype=DATA_TYPE))
new_state = flow_field.obs.copy()[2:8]
target_states = np.vstack((target_states, new_state))
meta_cloak_karman.target_states = target_states
# for i in range(100):
# flow_field.run(1000, np.zeros(4, dtype=DATA_TYPE))
# file_name = f"target_karman.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [7]:
del flow_field
flow_field = FlowField(config_field, config_cuda, device_id=0)
center: Tuple[float, float, float] = (31 * L0, (NY - 1) / 2, 0)
flow_field.add_cylinder(center, 1*L0)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 + 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 - 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
flow_field.run(int(4*NX/U0), np.zeros(4, dtype=DATA_TYPE))
target_states = np.empty((0, 8), dtype=DATA_TYPE)
for i in range(150):
flow_field.run(800, np.zeros(4, dtype=DATA_TYPE))
new_state = flow_field.obs.copy()[0:8]
target_states = np.vstack((target_states, new_state))
meta_illusion.target_states_1L = target_states
meta_illusion.target_harmonics_1L = analyze_harmonics(target_states, n_harmonics=5)
# for i in range(100):
# flow_field.run(1000, np.zeros(4, dtype=DATA_TYPE))
# file_name = f"target_1L.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [8]:
del flow_field
flow_field = FlowField(config_field, config_cuda, device_id=0)
center: Tuple[float, float, float] = (31 * L0, (NY - 1) / 2, 0)
flow_field.add_cylinder(center, 0.75*L0)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 + 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 - 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
flow_field.run(int(4*NX/U0), np.zeros(4, dtype=DATA_TYPE))
target_states = np.empty((0, 8), dtype=DATA_TYPE)
for i in range(150):
flow_field.run(400, np.zeros(4, dtype=DATA_TYPE))
new_state = flow_field.obs.copy()[0:8]
target_states = np.vstack((target_states, new_state))
meta_illusion.target_states_075L = target_states
meta_illusion.target_harmonics_075L = analyze_harmonics(target_states, n_harmonics=5)
# for i in range(100):
# flow_field.run(1000, np.zeros(4, dtype=DATA_TYPE))
# file_name = f"target_075L.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [9]:
del flow_field
flow_field = FlowField(config_field, config_cuda, device_id=0)
center: Tuple[float, float, float] = (31 * L0, (NY - 1) / 2, 0)
flow_field.add_cylinder(center, 1.5*L0)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 + 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 - 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
flow_field.run(int(4*NX/U0), np.zeros(4, dtype=DATA_TYPE))
target_states = np.empty((0, 8), dtype=DATA_TYPE)
for i in range(150):
flow_field.run(800, np.zeros(4, dtype=DATA_TYPE))
new_state = flow_field.obs.copy()[0:8]
target_states = np.vstack((target_states, new_state))
meta_illusion.target_states_15L = target_states
meta_illusion.target_harmonics_15L = analyze_harmonics(target_states, n_harmonics=5)
# for i in range(100):
# flow_field.run(1000, np.zeros(4, dtype=DATA_TYPE))
# file_name = f"target_15L.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [10]:
del flow_field
flow_field = FlowField(config_field, config_cuda, device_id=0)
center: Tuple[float, float, float] = (30 * L0, (NY - 1) / 2, 0)
flow_field.add_cylinder(center, L0 / 2)
center: Tuple[float, float, float] = (31.3 * L0, (NY - 1) / 2 + 0.75 * L0, 0)
flow_field.add_cylinder(center, L0 / 2)
center: Tuple[float, float, float] = (31.3 * L0, (NY - 1) / 2 - 0.75 * L0, 0)
flow_field.add_cylinder(center, L0 / 2)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 + 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2, 0)
flow_field.add_sensor(center, L0 / 4)
center: Tuple[float, float, float] = (40 * L0, (NY - 1) / 2 - 2 * L0, 0)
flow_field.add_sensor(center, L0 / 4)
flow_field.run(int(4*NX/U0), np.zeros(6, dtype=DATA_TYPE))
flow_field.get_ddf()
flow_field.save_ddf()In [11]:
# flow_field.restore_ddf()
# flow_field.apply_ddf()
fifo_states = deque(maxlen=150)
for i in range(100):
flow_field.run(1000, np.zeros(6, dtype=DATA_TYPE))
file_name = f"act_nc.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
fifo_states.append(flow_field.obs.copy()[0:12])In [12]:
for i in range(75):
flow_field.run(1000, np.array([0.0, -5.1*U0, 5.1*U0, 0.0, 0.0, 0.0], dtype=DATA_TYPE))
file_name = f"act_cloak_steady.{i:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
fifo_states.append(flow_field.obs.copy()[0:12])In [29]:
flow_field.get_ddf()
flow_field.save_ddf()In [16]:
flow_field.restore_ddf()
flow_field.apply_ddf()
flow_field.add_vortex(center_vor, L0 * 2, 0.5*U0, 0, "lamb")
obs = np.zeros(12, dtype=np.float32)
for i in range(125):
action, _states = model_cloak_lamb.predict(observation=obs, deterministic=True)
temp = np.zeros(6, dtype=DATA_TYPE)
if i < 25:
temp_action = np.array(action*4 + [0, -4, 4], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (i/25) + temp_transition * (1 - i/25)
elif 45 <= i < 70:
temp_action = np.array(action*4 + [0, -4, 4], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (1-(i-45)/25) + temp_transition * ((i-45)/25)
elif i >= 70:
temp[0:3] = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
else:
temp_action = np.array(action*4 + [0, -4, 4], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0
flow_field.run(800, temp)
states = np.array(flow_field.obs.copy()[0:12])
forces = states[0:6] / meta_cloak_dipole.force_norm_fact
cd = (forces[0] + forces[2] + forces[4]) / 3
cl = (forces[1] + forces[3] + forces[5]) / 3
sens = (states[6:12] - meta_cloak_dipole.sens_deviation) / meta_cloak_dipole.sens_norm_fact
obs = np.hstack([forces, sens])
file_name = f"act_cloak_dipole.{i:03d}"
save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
fifo_states.append(flow_field.obs.copy()[0:12])In [20]:
flow_field.restore_ddf()
flow_field.apply_ddf()
flow_field.add_vortex(center_vor, L0 * 2, 0.03*U0, 0, "taylor")
obs = np.zeros(12, dtype=np.float32)
for i in range(125):
action, _states = model_cloak_taylor.predict(observation=obs, deterministic=True)
temp = np.zeros(6, dtype=DATA_TYPE)
if i < 20:
temp_action = np.array(action*4 + [0, -4, 4], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (i/20) + temp_transition * (1 - i/20)
elif 45 <= i < 70:
temp_action = np.array(action*4 + [0, -4, 4], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (1-(i-45)/25) + temp_transition * ((i-45)/25)
elif i >= 70:
temp[0:3] = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
else:
temp_action = np.array(action*4 + [0, -4, 4], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0
flow_field.run(800, temp)
states = np.array(flow_field.obs.copy()[0:12])
forces = states[0:6] / meta_cloak_monopole.force_norm_fact
cd = (forces[0] + forces[2] + forces[4]) / 3
cl = (forces[1] + forces[3] + forces[5]) / 3
sens = (states[6:12] - meta_cloak_monopole.sens_deviation) / meta_cloak_monopole.sens_norm_fact
obs = np.hstack([forces, sens])
file_name = f"act_cloak_monopole.{i:03d}"
save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
fifo_states.append(flow_field.obs.copy()[0:12])In [22]:
def gen_target_states_at(t, harmonics):
t = np.asarray(t)
D = len(harmonics)
result = np.zeros((t.size, D), dtype=np.float32)
for d, h in enumerate(harmonics):
val = np.full(t.shape, h['dc'], dtype=np.float32)
for amp, freq, phase in zip(h['amps'], h['freqs'], h['phases']):
val += amp * np.cos(2 * np.pi * freq * t + phase)
result[:, d] = val
if result.shape[0] == 1:
return result[0]
return resultIn [24]:
flow_field.restore_ddf()
flow_field.apply_ddf()
obs = np.zeros(14, dtype=np.float32)
for i in range(200):
action, _states = model_illusion.predict(observation=obs, deterministic=True)
temp = np.zeros(6, dtype=DATA_TYPE)
if i < 10:
temp_action = np.array(action*8 + [0, -2, 2], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (i/10) + temp_transition * (1 - i/10)
else:
temp_action = np.array(action*8 + [0, -2, 2], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0
flow_field.run(800, temp)
states = np.array(flow_field.obs.copy()[0:12])
forces = states[0:6] / meta_illusion.force_norm_fact
cd = (forces[0] + forces[2] + forces[4]) / 3
cl = (forces[1] + forces[3] + forces[5]) / 3
sens = (states[6:12] - meta_illusion.sens_deviation) / meta_illusion.sens_norm_fact
target_states = gen_target_states_at(i, meta_illusion.target_harmonics_1L)
target_cd = target_states[0] / meta_illusion.force_norm_fact
target_cl = target_states[1] / meta_illusion.force_norm_fact
obs = np.hstack([forces, sens, target_cd, target_cl])
file_name = f"act_illusion_1L.{i:03d}"
save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
# if i % 2 == 0:
# index = i // 2
# file_name = f"act_illusion_1L.{index:03d}"
# save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [25]:
# flow_field.apply_ddf()
obs = np.zeros(14, dtype=np.float32)
for i in range(400):
action, _states = model_illusion_075L.predict(observation=obs, deterministic=True)
temp = np.zeros(6, dtype=DATA_TYPE)
if i < 20:
temp_action = np.array(action*8 + [0, -2, 2], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (i/10) + temp_transition * (1 - i/10)
else:
temp_action = np.array(action*8 + [0, -2, 2], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0
flow_field.run(400, temp)
states = np.array(flow_field.obs.copy()[0:12])
forces = states[0:6] / meta_illusion.force_norm_fact
cd = (forces[0] + forces[2] + forces[4]) / 3
cl = (forces[1] + forces[3] + forces[5]) / 3
sens = (states[6:12] - meta_illusion.sens_deviation) / meta_illusion.sens_norm_fact
target_states = gen_target_states_at(i, meta_illusion.target_harmonics_075L)
target_cd = target_states[0] / meta_illusion.force_norm_fact
target_cl = target_states[1] / meta_illusion.force_norm_fact
obs = np.hstack([forces, sens, target_cd, target_cl])
if i % 2 == 0:
index = i // 2
file_name = f"act_illusion_075L.{index:03d}"
save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [26]:
# flow_field.apply_ddf()
obs = np.zeros(14, dtype=np.float32)
for i in range(200):
action, _states = model_illusion_15L.predict(observation=obs, deterministic=True)
temp = np.zeros(6, dtype=DATA_TYPE)
if i < 10:
temp_action = np.array(action*8 + [0, -2, 2], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (i/10) + temp_transition * (1 - i/10)
else:
temp_action = np.array(action*8 + [0, -2, 2], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0
flow_field.run(800, temp)
states = np.array(flow_field.obs.copy()[0:12])
forces = states[0:6] / meta_illusion.force_norm_fact
cd = (forces[0] + forces[2] + forces[4]) / 3
cl = (forces[1] + forces[3] + forces[5]) / 3
sens = (states[6:12] - meta_illusion.sens_deviation) / meta_illusion.sens_norm_fact
target_states = gen_target_states_at(i, meta_illusion.target_harmonics_15L)
target_cd = target_states[0] / meta_illusion.force_norm_fact
target_cl = target_states[1] / meta_illusion.force_norm_fact
obs = np.hstack([forces, sens, target_cd, target_cl])
file_name = f"act_illusion_15L.{i:03d}"
save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [31]:
# center: Tuple[float, float, float] = (10 * L0, (NY - 1) / 2, 0)
# flow_field.add_cylinder(center, 1*L0)
flow_field.restore_ddf()
flow_field.apply_ddf()
obs = np.zeros(12, dtype=np.float32)
for i in range(200):
action, _states = model_cloak_re100.predict(observation=obs, deterministic=True)
temp = np.zeros(7, dtype=DATA_TYPE)
if i < 10:
temp_action = np.array([0, 0, 0], dtype=DATA_TYPE)
temp_transition = np.array([0.0, -5.1*U0, 5.1*U0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (i/10) + temp_transition * (1 - i/10)
else:
temp_action = np.array([0, 0, 0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0
flow_field.run(1000, temp)
states = np.array(flow_field.obs.copy()[0:12])
forces = states[0:6] / meta_cloak_karman.force_norm_fact
cd = (forces[0] + forces[2] + forces[4]) / 3
cl = (forces[1] + forces[3] + forces[5]) / 3
sens = (states[6:12] - meta_cloak_karman.sens_deviation) / meta_cloak_karman.sens_norm_fact
obs = np.hstack([forces, sens])
file_name = f"act_karman_nc.{i:03d}"
save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))
for i in range(200):
action, _states = model_cloak_re100.predict(observation=obs, deterministic=True)
temp = np.zeros(7, dtype=DATA_TYPE)
if i < 10:
temp_action = np.array(action*8 + [0, -4, 4], dtype=DATA_TYPE)
temp_transition = np.array([0, 0, 0], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0 * (i/10) + temp_transition * (1 - i/10)
else:
temp_action = np.array(action*8 + [0, -4, 4], dtype=DATA_TYPE)
temp[0:3] = temp_action * U0
flow_field.run(800, temp)
states = np.array(flow_field.obs.copy()[0:12])
forces = states[0:6] / meta_cloak_karman.force_norm_fact
cd = (forces[0] + forces[2] + forces[4]) / 3
cl = (forces[1] + forces[3] + forces[5]) / 3
sens = (states[6:12] - meta_cloak_karman.sens_deviation) / meta_cloak_karman.sens_norm_fact
obs = np.hstack([forces, sens])
file_name = f"act_karman_cloak.{i:03d}"
save_field(flow_field, os.path.join(parent_dir, "output", "250823", "data", file_name))In [ ]:
In [1]:
import os
import re
import glob
from pathlib import Path
import numpy as np
import pandas as pd
def _parse_zone_ij(file_path: str) -> tuple[int, int]:
with open(file_path, "r") as f:
_ = f.readline()
_ = f.readline()
zone_line = f.readline()
m_i = re.search(r"I=\s*(\d+)", zone_line)
m_j = re.search(r"J=\s*(\d+)", zone_line)
if m_i is None or m_j is None:
raise ValueError(f"Cannot parse I/J in: {file_path}")
return int(m_i.group(1)), int(m_j.group(1))
def load_tecplot_snapshot(file_path: str):
"""Load one snapshot saved by save_field().
Returns
-------
flag, u, v: ndarray with shape (NX, NY)
"""
NX, NY = _parse_zone_ij(file_path)
df = pd.read_csv(
file_path,
skiprows=3,
header=None,
names=["i", "j", "flag", "u", "v"],
)
arr = df[["flag", "u", "v"]].to_numpy().reshape(NY, NX, 3)
flag = arr[:, :, 0].T
u = arr[:, :, 1].T
v = arr[:, :, 2].T
return flag, u, v
def collect_case_files(data_dir: str, prefix: str, n_snapshots: int | None = None):
files = sorted(glob.glob(str(Path(data_dir) / f"{prefix}.*")))
if n_snapshots is not None:
files = files[:n_snapshots]
if len(files) == 0:
raise FileNotFoundError(f"No files found for prefix={prefix} in {data_dir}")
return files
def build_snapshot_matrix(file_list: list[str]):
"""Build snapshot matrix X for POD from velocity fields.
X shape: (n_features, n_snapshots)
n_features = 2 * n_fluid_points (u + v).
"""
flags = []
uv_fields = []
for fp in file_list:
flag, u, v = load_tecplot_snapshot(fp)
flags.append(flag)
uv_fields.append((u, v))
# Heuristic: the most frequent flag value is considered fluid.
first_flag = flags[0]
vals, cnts = np.unique(first_flag, return_counts=True)
fluid_flag = vals[np.argmax(cnts)]
fluid_mask = first_flag == fluid_flag
cols = []
for u, v in uv_fields:
cols.append(np.hstack([u[fluid_mask], v[fluid_mask]]))
X = np.stack(cols, axis=1)
return X, fluid_mask, uv_fields, fluid_flag
def pod_from_snapshots(X: np.ndarray):
"""Compute POD by SVD after mean subtraction."""
Xc = X - X.mean(axis=1, keepdims=True)
U, S, Vt = np.linalg.svd(Xc, full_matrices=False)
eig = S**2
ratio = eig / np.sum(eig)
cumsum = np.cumsum(ratio)
return {
"U": U,
"S": S,
"Vt": Vt,
"ratio": ratio,
"cumsum": cumsum,
}
def z2_metrics_for_field(u: np.ndarray, v: np.ndarray, fluid_mask: np.ndarray):
"""Compute Z2 symmetry metrics for one snapshot.
Reflection operator: R(u, v) = (u(x,-y), -v(x,-y)).
"""
u_ref = u[:, ::-1]
v_ref = -v[:, ::-1]
u_sym = 0.5 * (u + u_ref)
v_sym = 0.5 * (v + v_ref)
u_anti = 0.5 * (u - u_ref)
v_anti = 0.5 * (v - v_ref)
E_sym = np.sum(u_sym[fluid_mask] ** 2 + v_sym[fluid_mask] ** 2)
E_anti = np.sum(u_anti[fluid_mask] ** 2 + v_anti[fluid_mask] ** 2)
eta_z2 = E_anti / (E_sym + E_anti + 1e-12)
# Signed order parameter: mean cross-stream velocity (normalized by kinetic norm)
signed_m = np.sum(v[fluid_mask]) / np.sqrt(np.sum(u[fluid_mask] ** 2 + v[fluid_mask] ** 2) + 1e-12)
return {
"eta_z2": float(eta_z2),
"E_sym": float(E_sym),
"E_anti": float(E_anti),
"signed_m": float(signed_m),
}
def analyze_case(data_dir: str, prefix: str, n_snapshots: int = 40):
files = collect_case_files(data_dir, prefix, n_snapshots=n_snapshots)
X, fluid_mask, uv_fields, fluid_flag = build_snapshot_matrix(files)
pod = pod_from_snapshots(X)
z2 = [z2_metrics_for_field(u, v, fluid_mask) for (u, v) in uv_fields]
eta = np.array([z["eta_z2"] for z in z2])
signed_m = np.array([z["signed_m"] for z in z2])
return {
"prefix": prefix,
"n_snapshots": len(files),
"fluid_flag": float(fluid_flag),
"pod_ratio": pod["ratio"],
"pod_cumsum": pod["cumsum"],
"eta_z2_series": eta,
"signed_m_series": signed_m,
}
def summarize_case(result: dict):
r = result["pod_ratio"]
c = result["pod_cumsum"]
eta = result["eta_z2_series"]
m = result["signed_m_series"]
return {
"case": result["prefix"],
"n": result["n_snapshots"],
"POD_r1": float(r[0]),
"POD_r2": float(r[1]) if len(r) > 1 else np.nan,
"POD_r3": float(r[2]) if len(r) > 2 else np.nan,
"POD_c3": float(c[2]) if len(c) > 2 else np.nan,
"POD_c5": float(c[4]) if len(c) > 4 else np.nan,
"etaZ2_mean": float(np.mean(eta)),
"etaZ2_std": float(np.std(eta)),
"signed_m_mean": float(np.mean(m)),
"signed_m_std": float(np.std(m)),
}
resolved_parent_dir = globals().get("parent_dir")
if resolved_parent_dir is None:
# Fallback: notebook is under scripts/, so parent is workspace root.
resolved_parent_dir = os.path.abspath(os.path.join(os.getcwd(), os.pardir))
DATA_DIR = os.path.join(resolved_parent_dir, "output", "250823", "data")
print("POD/Z2 helper functions ready.")
print("Data dir:", DATA_DIR)POD/Z2 helper functions ready. Data dir: /home/frank14f/Frank_LBM/output/250823/data
In [2]:
# 你可以按需增减 case。这里先给一个与你当前结果最相关的集合。
cases = [
"act_nc", # 无控制基线(steady)
"act_cloak_steady", # steady cloaking
"act_karman_nc", # karman 来流无控制
"act_karman_cloak", # karman 来流 cloaking
"act_illusion_1L", # illusion (1.0L target)
]
results = []
for c in cases:
res = analyze_case(DATA_DIR, c, n_snapshots=20)
results.append(res)
summary_df = pd.DataFrame([summarize_case(r) for r in results])
summary_dfOut [2]:
| case | n | POD_r1 | POD_r2 | POD_r3 | POD_c3 | POD_c5 | etaZ2_mean | etaZ2_std | signed_m_mean | signed_m_std | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | act_nc | 20 | 0.545310 | 0.418448 | 0.010115 | 0.973874 | 0.991136 | 0.027126 | 0.000603 | 0.010536 | 0.125305 |
| 1 | act_cloak_steady | 20 | 0.484615 | 0.392044 | 0.070973 | 0.947632 | 0.977535 | 0.024850 | 0.002165 | 0.004569 | 0.113498 |
| 2 | act_karman_nc | 20 | 0.430712 | 0.400573 | 0.086937 | 0.918222 | 0.970708 | 0.039163 | 0.005227 | 0.038732 | 0.224643 |
| 3 | act_karman_cloak | 20 | 0.474362 | 0.417549 | 0.070438 | 0.962349 | 0.986613 | 0.043868 | 0.001102 | -0.022893 | 0.103950 |
| 4 | act_illusion_1L | 20 | 0.789446 | 0.152319 | 0.040029 | 0.981793 | 0.996635 | 0.000188 | 0.000117 | -0.001334 | 0.004723 |
In [3]:
import matplotlib.pyplot as plt
import numpy as np
# 可视化:POD 前三模态能量占比 + Z2 指数分布
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
x = np.arange(len(summary_df))
barw = 0.25
axes[0].bar(x - barw, summary_df["POD_r1"], width=barw, label="r1")
axes[0].bar(x, summary_df["POD_r2"], width=barw, label="r2")
axes[0].bar(x + barw, summary_df["POD_r3"], width=barw, label="r3")
axes[0].set_xticks(x)
axes[0].set_xticklabels(summary_df["case"], rotation=30, ha="right")
axes[0].set_ylabel("Energy ratio")
axes[0].set_title("POD modal energy (top 3)")
axes[0].legend()
axes[1].errorbar(
x,
summary_df["etaZ2_mean"],
yerr=summary_df["etaZ2_std"],
fmt="o",
capsize=4,
)
axes[1].set_xticks(x)
axes[1].set_xticklabels(summary_df["case"], rotation=30, ha="right")
axes[1].set_ylabel("$\\eta_{Z_2}$")
axes[1].set_title("Z2 symmetry-breaking index")
plt.tight_layout()
plt.show()
# 打印一段自动化文字结论(初步)
for _, row in summary_df.iterrows():
print(
f"[{row['case']}] POD c3={row['POD_c3']:.3f}, c5={row['POD_c5']:.3f}; "
f"etaZ2={row['etaZ2_mean']:.4f}±{row['etaZ2_std']:.4f}"
)[act_nc] POD c3=0.974, c5=0.991; etaZ2=0.0271±0.0006 [act_cloak_steady] POD c3=0.948, c5=0.978; etaZ2=0.0249±0.0022 [act_karman_nc] POD c3=0.918, c5=0.971; etaZ2=0.0392±0.0052 [act_karman_cloak] POD c3=0.962, c5=0.987; etaZ2=0.0439±0.0011 [act_illusion_1L] POD c3=0.982, c5=0.997; etaZ2=0.0002±0.0001
In [5]:
# PyDMD 可用性检查(可选)
try:
import pydmd
print("PyDMD import OK")
print("PyDMD path:", getattr(pydmd, "__file__", "N/A"))
available = ["DMD", "BOPDMD", "HODMD", "MrDMD", "DMDc"]
print("Core classes:", [name for name in available if hasattr(pydmd, name)])
except Exception as e:
print("PyDMD not available in current env:", repr(e))
print("Tip 1: pip install pydmd")
print("Tip 2: or install local repo /home/frank14f/Frank_LBM/JFM_WYQ/PyDMD with dependencies")
print("Note: POD 本身不依赖 PyDMD,已由 SVD 完成;PyDMD 主要可用于后续 DMD 频率/模态分析。")PyDMD import OK PyDMD path: /home/frank14f/anaconda3/envs/pycuda_3_10/lib/python3.10/site-packages/pydmd/__init__.py Core classes: ['DMD', 'BOPDMD', 'HODMD', 'MrDMD', 'DMDc'] Note: POD 本身不依赖 PyDMD,已由 SVD 完成;PyDMD 主要可用于后续 DMD 频率/模态分析。
In [6]:
# 进阶:POD 模态级别的 Z2 对称性(前6模态)
def pod_mode_z2_table(data_dir: str, prefix: str, n_snapshots: int = 40, n_modes: int = 6):
files = collect_case_files(data_dir, prefix, n_snapshots=n_snapshots)
X, fluid_mask, uv_fields, _ = build_snapshot_matrix(files)
pod = pod_from_snapshots(X)
n_fluid = int(np.sum(fluid_mask))
Umat = pod["U"]
out = []
for k in range(min(n_modes, Umat.shape[1])):
mode_vec = Umat[:, k]
u = np.zeros_like(uv_fields[0][0])
v = np.zeros_like(uv_fields[0][1])
u[fluid_mask] = mode_vec[:n_fluid]
v[fluid_mask] = mode_vec[n_fluid:]
z2 = z2_metrics_for_field(u, v, fluid_mask)
out.append(
{
"mode": k + 1,
"energy_ratio": float(pod["ratio"][k]),
"etaZ2_mode": z2["eta_z2"],
"signed_m_mode": z2["signed_m"],
}
)
return pd.DataFrame(out)
for c in ["act_nc", "act_cloak_steady", "act_karman_nc", "act_karman_cloak", "act_illusion_1L"]:
print(f"\n=== {c}: POD mode Z2 (top 6) ===")
display(pod_mode_z2_table(DATA_DIR, c, n_snapshots=20, n_modes=6))=== act_nc: POD mode Z2 (top 6) ===
| mode | energy_ratio | etaZ2_mode | signed_m_mode | |
|---|---|---|---|---|
| 0 | 1 | 0.545310 | 0.999408 | 0.452750 |
| 1 | 2 | 0.418448 | 0.999942 | -1.052688 |
| 2 | 3 | 0.010115 | 0.852112 | -0.190033 |
| 3 | 4 | 0.009767 | 0.734457 | -0.556253 |
| 4 | 5 | 0.007495 | 0.265782 | -0.314500 |
| 5 | 6 | 0.007301 | 0.146551 | -0.119727 |
=== act_cloak_steady: POD mode Z2 (top 6) ===
| mode | energy_ratio | etaZ2_mode | signed_m_mode | |
|---|---|---|---|---|
| 0 | 1 | 0.484615 | 0.938028 | -0.649696 |
| 1 | 2 | 0.392044 | 0.913858 | -0.640627 |
| 2 | 3 | 0.070973 | 0.274806 | -0.927843 |
| 3 | 4 | 0.018648 | 0.577703 | -1.039376 |
| 4 | 5 | 0.011255 | 0.417061 | 0.938371 |
| 5 | 6 | 0.009138 | 0.866436 | 0.214711 |
=== act_karman_nc: POD mode Z2 (top 6) ===
| mode | energy_ratio | etaZ2_mode | signed_m_mode | |
|---|---|---|---|---|
| 0 | 1 | 0.430712 | 0.805058 | 0.929242 |
| 1 | 2 | 0.400573 | 0.871105 | 0.056902 |
| 2 | 3 | 0.086937 | 0.585435 | 0.316279 |
| 3 | 4 | 0.038222 | 0.713888 | 0.541526 |
| 4 | 5 | 0.014265 | 0.571667 | -0.538116 |
| 5 | 6 | 0.009712 | 0.768921 | 0.424069 |
=== act_karman_cloak: POD mode Z2 (top 6) ===
| mode | energy_ratio | etaZ2_mode | signed_m_mode | |
|---|---|---|---|---|
| 0 | 1 | 0.474362 | 0.875132 | 0.676988 |
| 1 | 2 | 0.417549 | 0.958074 | -0.044610 |
| 2 | 3 | 0.070438 | 0.782419 | -0.366165 |
| 3 | 4 | 0.016490 | 0.598034 | -0.306447 |
| 4 | 5 | 0.007774 | 0.398814 | 0.351811 |
| 5 | 6 | 0.005982 | 0.804229 | -0.189635 |
=== act_illusion_1L: POD mode Z2 (top 6) ===
| mode | energy_ratio | etaZ2_mode | signed_m_mode | |
|---|---|---|---|---|
| 0 | 1 | 0.789446 | 0.004089 | -0.017435 |
| 1 | 2 | 0.152319 | 0.082741 | -0.115729 |
| 2 | 3 | 0.040029 | 0.273500 | -0.021073 |
| 3 | 4 | 0.011282 | 0.409877 | -0.230239 |
| 4 | 5 | 0.003560 | 0.460897 | -0.463985 |
| 5 | 6 | 0.001731 | 0.368499 | -0.723188 |
In [7]:
def analyze_case_tail(data_dir: str, prefix: str, n_tail: int = 30):
files = sorted(glob.glob(str(Path(data_dir) / f"{prefix}.*")))[-n_tail:]
if len(files) == 0:
raise FileNotFoundError(f"No files found for {prefix}")
X, fluid_mask, uv_fields, fluid_flag = build_snapshot_matrix(files)
pod = pod_from_snapshots(X)
z2 = [z2_metrics_for_field(u, v, fluid_mask) for (u, v) in uv_fields]
eta = np.array([z["eta_z2"] for z in z2])
signed_m = np.array([z["signed_m"] for z in z2])
return {
"prefix": prefix,
"n_tail": len(files),
"fluid_flag": float(fluid_flag),
"pod_ratio": pod["ratio"],
"pod_cumsum": pod["cumsum"],
"pod_U": pod["U"],
"eta_z2_series": eta,
"signed_m_series": signed_m,
"fluid_mask": fluid_mask,
}
def summarize_case_tail(result: dict):
r = result["pod_ratio"]
c = result["pod_cumsum"]
eta = result["eta_z2_series"]
m = result["signed_m_series"]
return {
"case": result["prefix"],
"n_tail": result["n_tail"],
"POD_r1": float(r[0]),
"POD_r2": float(r[1]) if len(r) > 1 else np.nan,
"POD_r3": float(r[2]) if len(r) > 2 else np.nan,
"POD_c3": float(c[2]) if len(c) > 2 else np.nan,
"POD_c5": float(c[4]) if len(c) > 4 else np.nan,
"etaZ2_mean": float(np.mean(eta)),
"etaZ2_std": float(np.std(eta)),
"signed_m_mean": float(np.mean(m)),
"signed_m_std": float(np.std(m)),
}
cases_tail = ["act_nc", "act_cloak_steady", "act_karman_nc", "act_karman_cloak", "act_illusion_1L"]
results_tail = [analyze_case_tail(DATA_DIR, c, n_tail=30) for c in cases_tail]
summary_tail_df = pd.DataFrame([summarize_case_tail(r) for r in results_tail])
summary_tail_dfOut [7]:
| case | n_tail | POD_r1 | POD_r2 | POD_r3 | POD_c3 | POD_c5 | etaZ2_mean | etaZ2_std | signed_m_mean | signed_m_std | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | act_nc | 30 | 0.526596 | 0.437099 | 0.010337 | 0.974032 | 0.991332 | 0.027001 | 0.000608 | -0.019124 | 0.128511 |
| 1 | act_cloak_steady | 30 | 0.740269 | 0.188710 | 0.050305 | 0.979284 | 0.998620 | 0.000253 | 0.000713 | 0.001067 | 0.014348 |
| 2 | act_karman_nc | 30 | 0.431001 | 0.397740 | 0.067790 | 0.896532 | 0.963662 | 0.047405 | 0.002245 | 0.000857 | 0.110081 |
| 3 | act_karman_cloak | 30 | 0.545640 | 0.422812 | 0.010409 | 0.978861 | 0.993134 | 0.041057 | 0.000858 | 0.017560 | 0.131044 |
| 4 | act_illusion_1L | 30 | 0.539437 | 0.422263 | 0.010111 | 0.971812 | 0.988518 | 0.029124 | 0.000780 | -0.032461 | 0.124692 |
In [8]:
# tail-30 汇总可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
x = np.arange(len(summary_tail_df))
barw = 0.25
axes[0].bar(x - barw, summary_tail_df["POD_r1"], width=barw, label="r1")
axes[0].bar(x, summary_tail_df["POD_r2"], width=barw, label="r2")
axes[0].bar(x + barw, summary_tail_df["POD_r3"], width=barw, label="r3")
axes[0].set_xticks(x)
axes[0].set_xticklabels(summary_tail_df["case"], rotation=30, ha="right")
axes[0].set_ylabel("Energy ratio")
axes[0].set_title("POD modal energy (tail-30)")
axes[0].legend()
axes[1].errorbar(
x,
summary_tail_df["etaZ2_mean"],
yerr=summary_tail_df["etaZ2_std"],
fmt="o",
capsize=4,
)
axes[1].set_xticks(x)
axes[1].set_xticklabels(summary_tail_df["case"], rotation=30, ha="right")
axes[1].set_ylabel("eta_Z2")
axes[1].set_title("Z2 symmetry-breaking index (tail-30)")
plt.tight_layout()
plt.show()In [9]:
# tail-30 的 POD 模态图(每个 case 前3模态,u/v 分量)
def plot_top3_modes_from_result(res: dict):
U = res["pod_U"]
mask = res["fluid_mask"]
n_fluid = int(np.sum(mask))
fig, axes = plt.subplots(2, 3, figsize=(12.5, 6.5))
for k in range(3):
mode = U[:, k]
um = np.zeros(mask.shape, dtype=np.float32)
vm = np.zeros(mask.shape, dtype=np.float32)
um[mask] = mode[:n_fluid]
vm[mask] = mode[n_fluid:]
lim_u = np.max(np.abs(um)) + 1e-12
lim_v = np.max(np.abs(vm)) + 1e-12
im_u = axes[0, k].imshow(um.T, origin="lower", cmap="RdBu_r", vmin=-lim_u, vmax=lim_u)
axes[0, k].set_title(f"Mode {k+1} - u")
axes[0, k].set_xticks([])
axes[0, k].set_yticks([])
fig.colorbar(im_u, ax=axes[0, k], fraction=0.046, pad=0.04)
im_v = axes[1, k].imshow(vm.T, origin="lower", cmap="RdBu_r", vmin=-lim_v, vmax=lim_v)
axes[1, k].set_title(f"Mode {k+1} - v")
axes[1, k].set_xticks([])
axes[1, k].set_yticks([])
fig.colorbar(im_v, ax=axes[1, k], fraction=0.046, pad=0.04)
fig.suptitle(f"POD spatial modes (tail-30): {res['prefix']}", y=1.02)
plt.tight_layout()
plt.show()
for rr in results_tail:
plot_top3_modes_from_result(rr)