First commit

This commit is contained in:
Frank14f 2026-02-20 11:57:01 +08:00
commit 15c6de7243
17 changed files with 2451 additions and 0 deletions

93
.gitignore vendored Normal file
View File

@ -0,0 +1,93 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Distribution / packaging
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Unit test / coverage
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Project-specific outputs
models/
!models/.gitkeep
output/
!output/.gitkeep
tensorboard/
!tensorboard/.gitkeep
# Data files (large datasets)
*.pkl
*.h5
*.hdf5
*.npz
# Logs
*.log
# Temporary files
*.tmp
*.bak
# CUDA
*.ptx
*.cubin
# macOS
.DS_Store
.AppleDouble
.LSOverride

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Frank14f
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

291
README.md Normal file
View File

@ -0,0 +1,291 @@
# DynamisLab
**Machine Learning for Computational Fluid Dynamics**
DynamisLab is a research framework for applying reinforcement learning and machine learning techniques to computational fluid dynamics problems. Built on top of [CelerisLab](https://github.com/frank14f/CelerisLab), it provides standardized environments and training pipelines for active flow control tasks.
## Features
- 🌊 **CFD Environments**: Gymnasium-compatible environments for flow control
- 🤖 **RL Integration**: Ready-to-use with Stable-Baselines3 and other RL libraries
- 🚀 **GPU Acceleration**: Leverages CelerisLab's CUDA-accelerated LBM solver
- 📊 **Experiment Tracking**: Built-in TensorBoard integration
- 🔧 **Modular Design**: Clean separation of environments, configs, and training scripts
- 📦 **Standard Structure**: Follows Python packaging best practices (src layout)
## Project Structure
```
DynamisLabNew/
├── src/ # Source code (src layout)
│ ├── __init__.py
│ ├── config.py # Configuration management
│ └── environments/ # Gymnasium environments
│ ├── __init__.py
│ └── cfd_env.py # CFD flow control environment
├── scripts/ # Training and evaluation scripts
│ └── train_ppo.py # PPO training script
├── configs/ # Configuration files
│ ├── config_cuda.json # CUDA settings
│ ├── config_flowfield.json # Flow field parameters
│ └── config_gym.json # Environment settings
├── models/ # Trained model checkpoints (gitignored)
├── output/ # Training data and results (gitignored)
├── tensorboard/ # TensorBoard logs (gitignored)
├── docs/ # Documentation
├── requirements.txt # Python dependencies
├── pyproject.toml # Package configuration
└── README.md # This file
```
## Installation
### Prerequisites
- Python 3.8+
- NVIDIA GPU with CUDA support
- CUDA Toolkit 11.0+
### Step 1: Clone the repository
```bash
git clone --recurse-submodules <your-repo-url> DynamisLab
cd DynamisLab
```
> **Note**: If CelerisLab is a submodule, use `--recurse-submodules` to clone it automatically.
### Step 2: Install CelerisLab
#### Option A: Install from submodule (recommended for development)
```bash
cd CelerisLab
pip install -e .
cd ..
```
#### Option B: Install from pip (if published)
```bash
pip install CelerisLab
```
### Step 3: Install DynamisLab dependencies
```bash
pip install -r requirements.txt
```
### Step 4: Install DynamisLab in development mode
```bash
pip install -e .
```
## Quick Start
### Training a PPO Agent
Train a Proximal Policy Optimization agent for flow control:
```bash
python scripts/train_ppo.py \
--run-name my_first_run \
--device-id 0 \
--total-timesteps 100 \
--n-steps 3600 \
--activation sin
```
**Arguments:**
- `--run-name`: Name for this training run (used for saving models and logs)
- `--device-id`: CUDA device ID for CFD simulation
- `--cuda-device`: CUDA device ID for PyTorch training (can be different from --device-id)
- `--total-timesteps`: Number of training iterations
- `--n-steps`: Environment steps per training iteration
- `--activation`: Activation function (`sin`, `tanh`, or `relu`)
### Monitoring Training
```bash
tensorboard --logdir tensorboard/
```
Then open http://localhost:6006 in your browser.
### Using the Environment Programmatically
```python
from environments import CFDFlowControlEnv
from config import load_celeris_configs
# Load configurations
config_cuda, config_field = load_celeris_configs()
# Create environment
env = CFDFlowControlEnv(
device_id=0,
config_cuda=config_cuda,
config_field=config_field,
)
# Run episode
obs, info = env.reset()
for step in range(500):
action = env.action_space.sample() # Random action
obs, reward, terminated, truncated, info = env.step(action)
if terminated or truncated:
break
env.close()
```
## Configuration
### CFD Configuration
Edit `configs/config_flowfield.json` to change flow parameters:
```json
{
"viscosity": 0.01, # Fluid viscosity
"velocity": 0.1, # Inlet velocity
"field_dim_in_U": [400, 200, 1], # Grid dimensions
...
}
```
### CUDA Configuration
Edit `configs/config_cuda.json` for GPU settings:
```json
{
"threads_per_block": 256,
"unit_dimensions": [16, 16, 1],
...
}
```
## Advanced Usage
### Resume Training
```bash
python scripts/train_ppo.py \
--resume models/my_run_best.zip \
--run-name my_run_continued
```
### Custom Hyperparameters
```bash
python scripts/train_ppo.py \
--learning-rate 0.0003 \
--gamma 0.99 \
--batch-size 512 \
--n-steps 7200
```
### Multi-GPU Setup
```bash
# CFD simulation on GPU 0, PyTorch training on GPU 1
python scripts/train_ppo.py \
--device-id 0 \
--cuda-device 1
```
## Environment Details
### CFDFlowControlEnv
The main environment for active flow control around a cylinder.
**Observation Space:**
- Dimensionality: `n_sensors × 2 × 2` (velocity components, current + derivative)
- Default: 12 dimensions (3 sensors × 2 velocities × 2)
- Normalized to zero mean and unit variance
**Action Space:**
- Dimensionality: `n_control_cylinders`
- Default: 3 (three controllable cylinders)
- Range: [-1, 1] (scaled internally to physical velocities)
**Reward:**
- Drag reduction: `-cd × 0.1`
- Lift minimization: `-|cl| × 0.05`
- Flow similarity: `-similarity_distance × 0.5`
- Total reward is sum of components
**Episode:**
- Max steps: 500 (configurable)
- Simulation runs at 800 LBM steps per environment step
## Development
### Project Guidelines
- Follow PEP 8 style guide
- Use type hints for function signatures
- Document classes and functions with docstrings
- Keep environments in `src/dynamis/environments/`
- Keep training scripts in `scripts/`
- Use `config.py` for all path and configuration management
### Adding a New Environment
1. Create new environment class in `src/dynamis/environments/`
2. Inherit from `gym.Env`
3. Register in `src/dynamis/environments/__init__.py`
4. Create corresponding training script in `scripts/`
### Running Tests
```bash
pytest tests/
```
## Citation
If you use DynamisLab in your research, please cite:
```bibtex
@software{dynamis2026,
author = {Frank14f},
title = {DynamisLab: Machine Learning for Computational Fluid Dynamics},
year = {2026},
url = {https://github.com/frank14f/DynamisLab}
}
```
Also cite CelerisLab:
```bibtex
@software{celerislab2026,
author = {Frank14f},
title = {CelerisLab: GPU-Accelerated Lattice Boltzmann Method Solver},
year = {2026},
url = {https://github.com/frank14f/CelerisLab}
}
```
## License
MIT License - see LICENSE file for details
## Acknowledgments
- Built on [CelerisLab](https://github.com/frank14f/CelerisLab) CFD solver
- Uses [Stable-Baselines3](https://github.com/DLR-RM/stable-baselines3) for RL
- Gymnasium API for standardized environments
## Contributing
Contributions are welcome! Please open an issue or pull request.
## Contact
For questions or issues, please open a GitHub issue or contact Frank14f.

9
configs/config_cuda.json Normal file
View File

@ -0,0 +1,9 @@
{
"multi_gpu": false,
"gpu_connection": "NVLink",
"required_cuda_capability": "7.0",
"threads_per_block": 128,
"X_1U": 128,
"Y_1U": 32,
"Z_1U": 1
}

View File

@ -0,0 +1,13 @@
{
"data_type": "FP32",
"dimensionality": 2,
"lattice": 9,
"field_dim_in_U": [10, 16, 1],
"viscosity": 0.002,
"velocity": 0.01,
"boundary_conditions": {
"x": ["parabolic", "outflow"],
"y": ["noslip", "noslip"],
"z": ["none", "none"]
}
}

3
configs/config_gym.json Normal file
View File

@ -0,0 +1,3 @@
{
}

236
docs/QUICK_REFERENCE.md Normal file
View File

@ -0,0 +1,236 @@
# 项目结构优化与 Submodule 工作流总结
## 📁 优化后的目录结构
### ✅ 简化前(有冗余)
```
DynamisLab/
└── src/
└── dynamis/ # 不必要的嵌套
├── config.py
└── environments/
```
### ✅ 简化后(推荐)
```
DynamisLab/
└── src/ # 直接是包的根
├── __init__.py
├── config.py
└── environments/
```
**优势:**
- 更简洁的导入路径
- 符合Python src layout最佳实践
- 包名就是项目名DynamisLab
## 🔧 导入方式变化
### 在开发时src在PYTHONPATH中
```python
# 简化前
from dynamis.config import load_celeris_configs
from dynamis.environments import CFDFlowControlEnv
# 简化后
from config import load_celeris_configs
from environments import CFDFlowControlEnv
```
### 安装后pip install -e .
```python
# 两种方式都可以,但推荐第二种
import dynamis
from dynamis import config, environments
# 或更直接
from config import load_celeris_configs
from environments import CFDFlowControlEnv
```
## 🔄 Submodule 开发工作流(你的核心问题)
### 推荐方式:独立开发 + Submodule 同步
```
你的开发环境:
/home/frank14f/
├── CelerisLab/ # 独立仓库在这里开发CFD
└── DynamisLab/ # 独立仓库在这里开发ML
└── CelerisLab/ # submodule指向上面的仓库
```
### 典型工作流
#### 场景1只改 CelerisLab
```bash
# 1. 在独立的CelerisLab目录开发
cd ~/CelerisLab
vim src/CelerisLab/utils.py
git commit -am "feat: improve config"
git push # 推送到GitHub+Gitea
# 2. 在DynamisLab中更新submodule
cd ~/DynamisLab
git submodule update --remote CelerisLab
git add CelerisLab
git commit -m "chore: update CelerisLab"
git push
```
#### 场景2只改 DynamisLab
```bash
cd ~/DynamisLab
vim src/environments/cfd_env.py
git commit -am "feat: new environment"
git push
# submodule不需要更新
```
#### 场景3同时开发两者
```bash
# Terminal 1: CFD开发
cd ~/CelerisLab
# 改代码 → commit → push
# Terminal 2: ML开发
cd ~/DynamisLab
git submodule update --remote # 获取最新CelerisLab
# 改代码使用新的CelerisLab功能
git add CelerisLab src/
git commit -m "feat: use new CelerisLab + update env"
git push
```
### 关键命令
```bash
# 更新submodule到最新
cd ~/DynamisLab
git submodule update --remote
# 查看submodule状态
git submodule status
# 固定到特定版本
cd CelerisLab
git checkout v0.2.0
cd ..
git add CelerisLab
git commit -m "pin to v0.2.0"
```
## 📝 快速参考
### CelerisLab 开发CFD功能
```bash
cd ~/CelerisLab
# 修改 → 测试 → commit → push
git commit -am "feat: xxx"
git push
```
### DynamisLab 同步 CelerisLab
```bash
cd ~/DynamisLab
git submodule update --remote
git add CelerisLab && git commit -m "update CelerisLab" && git push
```
### DynamisLab 开发ML功能
```bash
cd ~/DynamisLab
# 修改 → 测试 → commit → push
git commit -am "feat: xxx"
git push
```
## 🎯 最佳实践
### ✅ DO推荐
1. **在 `~/CelerisLab` 开发所有CFD功能**
- 独立测试
- 完成后推送
2. **用 submodule 保持同步**
- DynamisLab定期 `git submodule update --remote`
- 提交submodule引用的更新
3. **使用开发模式安装**
```bash
pip install -e ~/CelerisLab
pip install -e ~/DynamisLab
```
4. **VSCode Workspace 管理**
- 创建 workspace 文件同时打开两个项目
- Git视图可以分别管理
### ❌ DON'T避免
1. ❌ 不要在 `~/DynamisLab/CelerisLab` submodule 内直接开发
- 容易忘记推送
- 路径混乱
2. ❌ 不要忘记提交 submodule 引用更新
- 改了 CelerisLab 但 DynamisLab 没更新引用
- 别人克隆会得到旧版本
3. ❌ 不要直接复制代码
- 用 Git submodule 管理依赖
- 保持单一数据源
## 📚 详细文档
- **Submodule工作流详解**: [docs/SUBMODULE_WORKFLOW.md](docs/SUBMODULE_WORKFLOW.md)
- **项目重构总结**: [docs/REFACTORING_SUMMARY.md](docs/REFACTORING_SUMMARY.md)
- **VSCode Git配置**: 参考CelerisLab的VSCODE_GIT_SETUP.md
## 🚀 现在可以开始工作了!
1. **上传两个项目到Git**
```bash
# CelerisLab
cd ~/CelerisLab
git init && git add . && git commit -m "Initial commit"
git remote add origin <url>
git remote set-url --add --push origin <github_url>
git remote set-url --add --push origin <gitea_url>
git push -u origin main
# DynamisLab同样配置
cd ~/DynamisLab
# ... 同上
```
2. **添加 submodule**
```bash
cd ~/DynamisLab
git submodule add <celerislab_github_url> CelerisLab
git commit -m "Add CelerisLab submodule"
git push
```
3. **开始开发**
- CFD功能`~/CelerisLab`
- ML功能`~/DynamisLab`
- 定期同步 submodule
---
**简单总结你的问题答案:**
> "我是在CelerisLab下开发CFD功能同步到git然后在DynamisLab中pull submodule吗"
**答:是的!** 这就是推荐的工作流:
1. 在 `~/CelerisLab` 开发CFD → commit → push
2. 在 `~/DynamisLab` 运行 `git submodule update --remote`
3. 提交更新:`git add CelerisLab && git commit && git push`
这样两个项目独立管理但通过submodule保持连接。✨

295
docs/REFACTORING_SUMMARY.md Normal file
View File

@ -0,0 +1,295 @@
# DynamisLab 重构总结
## 概述
已成功创建标准化、模块化的 DynamisLab 机器学习研究框架,基于最新的 gym_env.py 和 d1a3o12.py 重构而来。
## 主要改进
### 1. 标准化项目结构 (Src Layout)
```
DynamisLabNew/
├── src/dynamis/ # ✨ 主包src layout
│ ├── __init__.py # 包初始化
│ ├── config.py # ✨ 统一配置管理
│ └── environments/ # ✨ 标准化环境
│ ├── __init__.py
│ └── cfd_env.py # ✨ 重构的CFD环境
├── scripts/ # 训练和评估脚本
│ └── train_ppo.py # ✨ 重构的训练脚本
├── configs/ # 配置文件
├── models/ # 模型检查点(.gitignore
├── output/ # 训练输出(.gitignore
├── tensorboard/ # TensorBoard日志.gitignore
├── docs/ # 文档
├── README.md # ✨ 完整文档
├── requirements.txt # ✨ 依赖列表
├── pyproject.toml # ✨ 现代打包配置
├── LICENSE # MIT许可证
└── .gitignore # Git规则
```
### 2. 代码重构亮点
#### A. 统一配置管理 (`src/dynamis/config.py`)
**原代码问题:**
```python
# 硬编码路径,重复代码
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)
config_cuda = utils.load_cuda_config(os.path.join(parent_dir, "configs", "config_cuda.json"))
```
**新方案:**
```python
from dynamis.config import load_celeris_configs
# 自动查找配置支持环境变量和submodule
config_cuda, config_field = load_celeris_configs()
```
**优点:**
- ✅ 自动处理CelerisLab导入支持pip安装或submodule
- ✅ 智能配置路径查找
- ✅ 统一的输出目录管理models/, output/, tensorboard/
- ✅ 辅助函数(`get_model_path()`, `get_tensorboard_logdir()`等)
#### B. 标准化环境 (`src/dynamis/environments/cfd_env.py`)
**原代码:`gym_env.py` (259行)**
**新代码:`CFDFlowControlEnv` (更模块化318行但更清晰)**
**改进:**
- ✅ **完整docstrings**:类和所有方法都有详细文档
- ✅ **类型提示**:所有参数和返回值带类型
- ✅ **参数化设计**:所有魔法数字变为可配置参数
```python
def __init__(
self,
device_id: int = 0,
n_control_cylinders: int = 3,
n_sensors: int = 3,
max_steps: int = 500,
sample_interval: int = 800,
# ... 所有参数都可配置
):
```
- ✅ **清晰的方法分离**
- `_init_flow_field()` - 初始化CFD模拟
- `_calculate_normalization()` - 计算归一化因子
- `_normalize_state()` - 状态归一化
- `_compute_reward()` - 奖励计算
- ✅ **Gymnasium新API**:使用最新的 `terminated` / `truncated` 分离
- ✅ **丰富的info字典**返回详细的诊断信息cd, cl, 各reward分量
#### C. 专业训练脚本 (`scripts/train_ppo.py`)
**原代码:`d1a3o12.py` (72行简单循环)**
**新代码:`train_ppo.py` (319行完整功能)**
**新增功能:**
- ✅ **命令行参数**15+可配置参数
```bash
python scripts/train_ppo.py --help # 查看所有选项
```
- ✅ **实验追踪**
- TensorBoard集成
- 定期保存最佳模型
- 详细的评估指标
- ✅ **模型管理**
- 自动保存最佳模型
- 定期检查点
- 支持恢复训练 (`--resume`)
- ✅ **评估函数**
```python
evaluate_policy(model, env, n_episodes=5)
# 返回完整的评估指标和轨迹数据
```
- ✅ **自定义回调**
- `TensorboardCallback` 记录额外指标
- 可扩展的回调系统
### 3. 文档和可维护性
#### README.md
- 📖 完整的安装指南
- 🚀 Quick Start示例
- 🔧 配置说明
- 📊 环境详细规格
- 💡 高级用法恢复训练、多GPU等
- 📝 引用格式
#### 类型提示和Docstrings
所有代码都包含:
```python
def reset(
self,
seed: Optional[int] = None,
options: Optional[Dict[str, Any]] = None
) -> Tuple[np.ndarray, Dict[str, Any]]:
"""
Reset the environment to initial state.
Args:
seed: Random seed for reproducibility
options: Additional options
Returns:
Tuple of (observation, info)
"""
```
#### 配置文件
- `pyproject.toml` - 现代Python打包标准
- `requirements.txt` - 清晰的依赖列表
- `.gitignore` - 完善的忽略规则
## 使用方法
### 快速开始
```bash
# 1. 假设CelerisLab已安装作为submodule或pip
cd DynamisLabNew
# 2. 安装依赖
pip install -r requirements.txt
# 3. 安装DynamisLab开发模式
pip install -e .
# 4. 训练
python scripts/train_ppo.py \
--run-name test_run \
--device-id 0 \
--total-timesteps 50 \
--activation sin
# 5. 监控
tensorboard --logdir tensorboard/
```
### 编程使用
```python
from dynamis.environments import CFDFlowControlEnv
from dynamis.config import load_celeris_configs
# 加载配置
config_cuda, config_field = load_celeris_configs()
# 创建环境
env = CFDFlowControlEnv(
device_id=0,
config_cuda=config_cuda,
config_field=config_field,
max_steps=500,
)
# 训练或评估
obs, info = env.reset()
for step in range(100):
action = env.action_space.sample()
obs, reward, terminated, truncated, info = env.step(action)
print(f"Step {step}: Reward={reward:.3f}, CD={info['cd']:.4f}")
if terminated or truncated:
break
env.close()
```
## 代码质量改进对比
| 方面 | 原代码 | 新代码 | 改进 |
|------|--------|--------|------|
| **结构** | 单文件,混杂 | src layout模块化 | ✅ 专业结构 |
| **配置** | 硬编码路径 | 统一config模块 | ✅ 灵活可配 |
| **类型提示** | 无 | 完整类型提示 | ✅ IDE支持 |
| **Docstrings** | 最小 | 完整文档 | ✅ 可维护性 |
| **参数化** | 魔法数字 | 可配置参数 | ✅ 可调试 |
| **错误处理** | 基本 | 友好错误信息 | ✅ 用户友好 |
| **日志** | print语句 | TensorBoard | ✅ 专业追踪 |
| **测试** | 无 | 结构支持测试 | ✅ 可测试 |
| **文档** | README基本 | 完整文档 | ✅ 易上手 |
| **Git** | 基本ignore | 完善.gitignore | ✅ 清洁仓库 |
## 与CelerisLab集成
### 方式1Git Submodule推荐
```bash
cd DynamisLabNew
git submodule add https://github.com/frank14f/CelerisLab.git
cd CelerisLab
pip install -e .
cd ..
```
`config.py` 会自动检测submodule并添加到Python path。
### 方式2独立安装
```bash
# 在CelerisLab目录
pip install -e ../CelerisLabNew
# 设置环境变量(可选)
export CELERISLAB_CONFIG_DIR=/path/to/DynamisLab/configs
```
## 下一步
### 上传到Git
```bash
cd DynamisLabNew
git init
git add .
git commit -m "Initial commit: DynamisLab v0.1.0 - Refactored ML framework"
# 配置双远程
git remote add origin <github_url>
git remote set-url --add --push origin <github_url>
git remote set-url --add --push origin <gitea_url>
git push -u origin main
```
### 添加CelerisLab Submodule
```bash
git submodule add https://github.com/frank14f/CelerisLab.git
git commit -m "Add CelerisLab as submodule"
git push
```
## 主要文件说明
| 文件 | 行数 | 功能 | 状态 |
|------|------|------|------|
| `src/dynamis/__init__.py` | 11 | 包初始化 | ✅ 完成 |
| `src/dynamis/config.py` | 118 | 配置管理 | ✅ 完成 |
| `src/dynamis/environments/__init__.py` | 7 | 环境注册 | ✅ 完成 |
| `src/dynamis/environments/cfd_env.py` | 318 | CFD环境 | ✅ 完成 |
| `scripts/train_ppo.py` | 319 | 训练脚本 | ✅ 完成 |
| `README.md` | 291 | 项目文档 | ✅ 完成 |
| `requirements.txt` | 29 | 依赖列表 | ✅ 完成 |
| `pyproject.toml` | 97 | 打包配置 | ✅ 完成 |
| `.gitignore` | 89 | Git规则 | ✅ 完成 |
| `LICENSE` | 21 | MIT许可 | ✅ 完成 |
## 总结
**代码质量**:从研究脚本提升到生产级代码
**可维护性**:清晰的结构,完整的文档
**可扩展性**:模块化设计,易于添加新环境和算法
**专业性**遵循Python最佳实践和Gymnasium标准
**用户友好**详细的README和命令行接口
**Git友好**:完善的.gitignore准备双远程推送
🎉 **DynamisLab 已准备好用于生产和发布!**

480
docs/SUBMODULE_WORKFLOW.md Normal file
View File

@ -0,0 +1,480 @@
# Git Submodule 开发工作流指南
## 项目结构
你的开发环境:
```
/home/frank14f/
├── CelerisLab/ # 独立仓库 - CFD库
│ ├── .git/
│ ├── src/
│ │ └── CelerisLab/
│ └── ...
└── DynamisLab/ # 独立仓库 - ML框架
├── .git/
├── CelerisLab/ # 作为submodule指向上面的CelerisLab仓库
│ ├── .git # 这是软链接指向真实的git仓库
│ └── ...
├── src/
├── scripts/
└── ...
```
## 开发工作流
### 场景1开发 CelerisLabCFD功能
**在 `/home/frank14f/CelerisLab` 下工作**
```bash
cd /home/frank14f/CelerisLab
# 1. 创建功能分支(可选)
git checkout -b feature/new-cfd-feature
# 2. 修改代码
vim src/CelerisLab/driver.py
# 3. 测试改动
python -c "from CelerisLab import FlowField; print('OK')"
# 4. 提交改动
git add src/CelerisLab/driver.py
git commit -m "feat: add new CFD feature"
# 5. 推送到远程双推送到GitHub和Gitea
git push origin main
# 因为配置了双push URL这会自动推送到两个远程
```
### 场景2在 DynamisLab 中使用更新的 CelerisLab
**方式A更新submodule到最新版本**
```bash
cd /home/frank14f/DynamisLab
# 1. 进入submodule目录
cd CelerisLab
# 2. 拉取最新的CelerisLab代码
git fetch origin
git checkout main # 或特定的tag/branch
git pull origin main
# 3. 回到DynamisLab主目录
cd ..
# 4. 提交submodule引用的更新
git add CelerisLab
git commit -m "chore: update CelerisLab submodule to latest"
# 5. 推送DynamisLab的更新
git push
```
**方式B自动更新submodule**
```bash
cd /home/frank14f/DynamisLab
# 一行命令更新所有submodule到远程最新版本
git submodule update --remote --merge
# 提交更新
git add CelerisLab
git commit -m "chore: update CelerisLab submodule"
git push
```
### 场景3同时开发 CelerisLab 和 DynamisLab
**这是你问的核心场景!**
#### 方法1在独立目录开发推荐
```bash
# Terminal 1: 开发CelerisLab
cd /home/frank14f/CelerisLab
# 修改 CFD 功能
vim src/CelerisLab/utils.py
git commit -am "fix: improve config loading"
git push # 推送到远程
# Terminal 2: 开发DynamisLab
cd /home/frank14f/DynamisLab
# 更新submodule获取最新CelerisLab
git submodule update --remote CelerisLab
# 修改ML代码使用新功能
vim src/environments/cfd_env.py
git add CelerisLab src/ # 同时提交submodule更新和代码修改
git commit -m "feat: use new CelerisLab config feature"
git push
```
#### 方法2在DynamisLab的submodule中开发CelerisLab
> ⚠️ **不推荐**:容易混淆,但技术上可行
```bash
cd /home/frank14f/DynamisLab/CelerisLab # 进入submodule
# 这个目录实际上是一个完整的git仓库
git checkout -b feature/my-fix
# 修改代码
vim src/CelerisLab/driver.py
git commit -am "fix: bug in driver"
# 推送到CelerisLab远程仓库
git push origin feature/my-fix
# 回到DynamisLab主目录
cd ..
git add CelerisLab
git commit -m "chore: update CelerisLab with bug fix"
git push
```
### 场景4克隆项目时的工作流
**新电脑/新环境上开始工作**
```bash
# 1. 克隆DynamisLab包含submodule
git clone --recurse-submodules https://github.com/frank14f/DynamisLab.git
cd DynamisLab
# 2. 安装CelerisLab
cd CelerisLab
pip install -e .
cd ..
# 3. 安装DynamisLab
pip install -r requirements.txt
pip install -e .
# 4. 开始工作
python scripts/train_ppo.py --help
```
**如果忘记 `--recurse-submodules`**
```bash
git clone https://github.com/frank14f/DynamisLab.git
cd DynamisLab
# 初始化submodule
git submodule init
git submodule update
# 或简化为:
git submodule update --init --recursive
```
## 常用 Submodule 命令
### 查看状态
```bash
cd /home/frank14f/DynamisLab
# 查看submodule状态
git submodule status
# 输出示例:
# a1b2c3d4 CelerisLab (v0.2.0)
# 前面的hash是当前指向的commit
# 查看submodule的URL
git config --file .gitmodules --get-regexp url
```
### 更新 Submodule
```bash
# 方法1更新到远程最新版本
git submodule update --remote CelerisLab
# 方法2手动进入submodule更新
cd CelerisLab
git pull origin main
cd ..
git add CelerisLab
# 方法3更新所有submodule并合并
git submodule update --remote --merge
```
### 固定 Submodule 到特定版本
```bash
cd /home/frank14f/DynamisLab/CelerisLab
# 切换到特定commit或tag
git checkout v0.2.0 # 或 commit hash
cd ..
git add CelerisLab
git commit -m "pin CelerisLab to v0.2.0"
git push
```
### 修改 Submodule URL
```bash
# 如果CelerisLab的仓库地址变了
git config --file=.gitmodules submodule.CelerisLab.url https://new-url.git
git submodule sync
git submodule update --remote
```
## Python 包安装策略
### 开发模式(推荐)
```bash
# 在DynamisLab下
pip install -e ./CelerisLab # submodule作为editable安装
pip install -e . # DynamisLab自己也是editable
# 好处:修改代码立即生效,无需重新安装
```
### 环境变量方式
```bash
# 在 ~/.bashrc 中添加
export PYTHONPATH="/home/frank14f/DynamisLab/CelerisLab/src:$PYTHONPATH"
# 重新加载
source ~/.bashrc
```
## 推荐的工作流程
### 日常开发循环
**CFD功能开发**
```bash
# === Terminal 1: CelerisLab ===
cd ~/CelerisLab
# 1. 修改CFD代码
vim src/CelerisLab/utils.py
# 2. 本地测试
python test_utils_only.py
# 3. 提交
git commit -am "feat: smart config loading"
git push
# === Terminal 2: DynamisLab ===
cd ~/DynamisLab
# 4. 拉取最新CelerisLab
git submodule update --remote CelerisLab
# 5. 测试集成
python scripts/train_ppo.py --total-timesteps 5
# 6. 如果工作正常提交submodule更新
git add CelerisLab
git commit -m "chore: update CelerisLab"
git push
```
**ML功能开发**
```bash
cd ~/DynamisLab
# 1. 修改ML代码
vim src/environments/cfd_env.py
# 2. 测试
python scripts/train_ppo.py --total-timesteps 10
# 3. 提交
git commit -am "feat: improve reward function"
git push
# CelerisLab submodule保持不变
```
## VSCode 多仓库开发
### Workspace 配置
创建 `~/DynamisLab.code-workspace`
```json
{
"folders": [
{
"path": ".",
"name": "DynamisLab (Root)"
},
{
"path": "CelerisLab",
"name": "CelerisLab (Submodule)"
}
],
"settings": {
"python.analysis.extraPaths": [
"./CelerisLab/src"
],
"git.detectSubmodules": true,
"git.showSubmoduleStatus": true
}
}
```
在VSCode中
1. `File``Open Workspace from File`
2. 选择 `DynamisLab.code-workspace`
3. 左侧会显示两个文件夹可以分别管理Git
## 常见问题排查
### Q1: Submodule 显示 "modified content"
```bash
cd CelerisLab
git status # 查看有什么改动
# 如果不需要这些改动
git checkout .
git clean -fd
# 如果需要保存
git commit -am "local changes"
```
### Q2: Submodule 指向错误的 commit
```bash
cd DynamisLab
# 查看submodule应该指向哪个commit
git diff CelerisLab # 看HEAD和实际的差异
# 重置到正确的commit
cd CelerisLab
git fetch
git checkout <正确的hash>
cd ..
git add CelerisLab
```
### Q3: 推送时忘记推送 submodule 的改动
```bash
# 先推送submodule
cd CelerisLab
git push
# 再推送主仓库
cd ..
git push
```
设置自动检查:
```bash
git config --global push.recurseSubmodules check
# 这样push主仓库时会检查submodule是否已推送
```
### Q4: 多人协作时 submodule 冲突
```bash
# 拉取主仓库
git pull
# 更新submodule到正确版本
git submodule update --init --recursive
```
## 版本发布策略
### 发布 CelerisLab 新版本
```bash
cd ~/CelerisLab
# 1. 更新版本号
vim src/CelerisLab/__init__.py # __version__ = '0.3.0'
vim setup.py # version='0.3.0'
# 2. 提交
git commit -am "chore: bump version to 0.3.0"
# 3. 打tag
git tag -a v0.3.0 -m "Release v0.3.0"
git push origin main --tags
```
### DynamisLab 使用特定 CelerisLab 版本
```bash
cd ~/DynamisLab/CelerisLab
# 切换到tag
git checkout v0.3.0
cd ..
git add CelerisLab
git commit -m "chore: pin CelerisLab to v0.3.0"
git push
```
## 最佳实践总结
✅ **DO - 推荐做法**
1. ✅ 在独立的 `~/CelerisLab` 目录开发CFD功能
2. ✅ 开发完成后push然后在 `~/DynamisLab` 中update submodule
3. ✅ 使用 `pip install -e` 安装两个包(开发模式)
4. ✅ 经常运行 `git submodule update --remote` 保持同步
5. ✅ CelerisLab稳定时打tagDynamisLab引用tag而不是main
6. ✅ VSCode使用workspace配置同时管理两个仓库
❌ **DON'T - 避免的做法**
1. ❌ 不要在 `~/DynamisLab/CelerisLab` submodule内直接开发除非临时修复
2. ❌ 不要忘记提交submodule引用的更新
3. ❌ 不要在DynamisLab中硬编码CelerisLab版本用submodule管理
4. ❌ 推送DynamisLab前确保CelerisLab的改动已推送
5. ❌ 不要手动复制粘贴代码在两个项目间用git管理
## 快速参考
```bash
# === 开发CelerisLab ===
cd ~/CelerisLab
# 改代码 → commit → push
# === 同步到DynamisLab ===
cd ~/DynamisLab
git submodule update --remote
git add CelerisLab
git commit -m "update CelerisLab"
git push
# === 开发DynamisLab ===
cd ~/DynamisLab
# 改代码 → commit → push
# (submodule不变)
# === 检查submodule状态 ===
git submodule status
# === 重置submodule ===
git submodule update --init --recursive
```
---
这样你就可以高效地在两个独立项目中开发同时通过submodule保持它们的连接🚀

97
pyproject.toml Normal file
View File

@ -0,0 +1,97 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "DynamisLab"
version = "0.1.0"
description = "Machine Learning for Computational Fluid Dynamics"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "Frank14f"}
]
keywords = [
"machine-learning",
"reinforcement-learning",
"cfd",
"fluid-dynamics",
"deep-learning",
"active-flow-control"
]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: Physics",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
dependencies = [
"numpy>=1.19.0",
"scipy>=1.5.0",
"stable-baselines3>=2.0.0",
"sb3-contrib>=2.0.0",
"gymnasium>=0.28.0",
"torch>=2.0.0",
"matplotlib>=3.5.0",
"tensorboard>=2.10.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=22.0.0",
"flake8>=4.0.0",
"mypy>=0.950",
]
jupyter = [
"jupyter>=1.0.0",
"ipykernel>=6.0.0",
"pandas>=1.3.0",
"seaborn>=0.11.0",
]
[project.urls]
Homepage = "https://github.com/frank14f/DynamisLab"
Repository = "https://github.com/frank14f/DynamisLab.git"
Documentation = "https://github.com/frank14f/DynamisLab/tree/main/docs"
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
[tool.black]
line-length = 100
target-version = ['py38', 'py39', 'py310', 'py311']
include = '\.pyi?$'
extend-exclude = '''
/(
| \.git
| \.venv
| build
| dist
)/
'''
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
ignore_missing_imports = true
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
addopts = "-v --tb=short"

32
requirements.txt Normal file
View File

@ -0,0 +1,32 @@
# Core dependencies
numpy>=1.19.0
scipy>=1.5.0
# CFD backend (install separately from submodule or pip)
# CelerisLab should be installed via: pip install -e ../CelerisLab
# Reinforcement Learning
stable-baselines3>=2.0.0
sb3-contrib>=2.0.0
gymnasium>=0.28.0
# Deep Learning
torch>=2.0.0
# Visualization and Logging
matplotlib>=3.5.0
seaborn>=0.11.0
tensorboard>=2.10.0
# Data processing
pandas>=1.3.0
# Development tools (optional)
pytest>=7.0.0
black>=22.0.0
flake8>=4.0.0
mypy>=0.950
# Jupyter (optional, for analysis)
jupyter>=1.0.0
ipykernel>=6.0.0

316
scripts/train_ppo.py Normal file
View File

@ -0,0 +1,316 @@
#!/usr/bin/env python3
"""
Train PPO agent for CFD flow control.
This script trains a Proximal Policy Optimization (PPO) agent to control
flow around a cylinder using the CFD environment.
"""
import argparse
import os
import pickle
from pathlib import Path
import sys
# Set threading layers
os.environ['MKL_THREADING_LAYER'] = 'GNU'
os.environ["OMP_NUM_THREADS"] = "8"
os.environ["MKL_NUM_THREADS"] = "8"
import numpy as np
import torch
from torch.nn import Module
from stable_baselines3 import PPO
from stable_baselines3.common.callbacks import BaseCallback
from torch.utils.tensorboard import SummaryWriter
# Add src to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent / 'src'))
from environments import CFDFlowControlEnv
from config import (
load_celeris_configs,
get_model_path,
get_tensorboard_logdir,
get_output_path,
)
class SinActivation(Module):
"""Sine activation function for neural networks."""
def __init__(self):
super().__init__()
def forward(self, x):
return torch.sin(x)
class TensorboardCallback(BaseCallback):
"""
Custom callback for logging additional metrics to TensorBoard.
"""
def __init__(self, check_freq: int = 360, verbose: int = 0):
super().__init__(verbose)
self.check_freq = check_freq
self.episode_rewards = []
self.episode_cd = []
self.episode_cl = []
def _on_step(self) -> bool:
if self.n_calls % self.check_freq == 0:
# Extract episode info
if len(self.locals.get('infos', [])) > 0:
info = self.locals['infos'][0]
if 'cd' in info:
self.logger.record('flow/cd', info['cd'])
if 'cl' in info:
self.logger.record('flow/cl', info['cl'])
if 'reward_cd' in info:
self.logger.record('reward/cd', info['reward_cd'])
if 'reward_cl' in info:
self.logger.record('reward/cl', info['reward_cl'])
if 'reward_sim' in info:
self.logger.record('reward/sim', info['reward_sim'])
return True
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description='Train PPO for CFD control')
# Environment settings
parser.add_argument('--device-id', type=int, default=0,
help='CUDA device ID for simulation')
# Training hyperparameters
parser.add_argument('--total-timesteps', type=int, default=100,
help='Number of training iterations (each = n_steps)')
parser.add_argument('--n-steps', type=int, default=3600,
help='Steps to collect per training iteration')
parser.add_argument('--batch-size', type=int, default=360,
help='Batch size for PPO updates')
parser.add_argument('--learning-rate', type=float, default=3e-4,
help='Learning rate')
parser.add_argument('--gamma', type=float, default=0.99,
help='Discount factor')
# Model settings
parser.add_argument('--activation', choices=['tanh', 'relu', 'sin'], default='sin',
help='Activation function for policy network')
parser.add_argument('--cuda-device', type=int, default=0,
help='CUDA device for PyTorch training')
# Experiment settings
parser.add_argument('--run-name', type=str, default='ppo_cfd_control',
help='Name for this training run')
parser.add_argument('--save-freq', type=int, default=10,
help='Save model every N iterations')
parser.add_argument('--eval-episodes', type=int, default=1,
help='Number of episodes to evaluate')
# Resume training
parser.add_argument('--resume', type=str, default=None,
help='Path to model checkpoint to resume from')
return parser.parse_args()
def create_env(device_id: int):
"""Create the CFD environment."""
config_cuda, config_field = load_celeris_configs()
env = CFDFlowControlEnv(
device_id=device_id,
config_cuda=config_cuda,
config_field=config_field,
)
return env
def get_activation_fn(name: str):
"""Get activation function by name."""
if name == 'sin':
return SinActivation
elif name == 'tanh':
return torch.nn.Tanh
elif name == 'relu':
return torch.nn.ReLU
else:
raise ValueError(f"Unknown activation: {name}")
def evaluate_policy(model, env, n_episodes: int = 1):
"""
Evaluate the trained policy.
Returns:
Dictionary of evaluation metrics
"""
episode_rewards = []
episode_data = []
for episode in range(n_episodes):
obs, info = env.reset()
done = False
episode_reward = 0
steps = 0
ep_data = {
'actions': [],
'observations': [],
'rewards': [],
'cd': [],
'cl': [],
}
while not done:
action, _states = model.predict(obs, deterministic=True)
obs, reward, terminated, truncated, info = env.step(action)
done = terminated or truncated
episode_reward += reward
steps += 1
# Record data
ep_data['actions'].append(action)
ep_data['observations'].append(obs)
ep_data['rewards'].append(reward)
ep_data['cd'].append(info.get('cd', 0))
ep_data['cl'].append(info.get('cl', 0))
episode_rewards.append(episode_reward)
episode_data.append(ep_data)
print(f" Episode {episode + 1}/{n_episodes}: "
f"Reward = {episode_reward:.2f}, "
f"Steps = {steps}, "
f"Avg CD = {np.mean(ep_data['cd']):.4f}")
return {
'mean_reward': np.mean(episode_rewards),
'std_reward': np.std(episode_rewards),
'episodes': episode_data,
}
def main():
"""Main training loop."""
args = parse_args()
print("=" * 70)
print(f"DynamisLab - CFD Flow Control Training")
print(f"Run: {args.run_name}")
print("=" * 70)
# Create environment
print(f"\n[1/4] Creating CFD environment on GPU:{args.device_id}...")
env = create_env(args.device_id)
print(f" Action space: {env.action_space}")
print(f" Observation space: {env.observation_space}")
# Create or load model
print(f"\n[2/4] Setting up PPO model...")
device = torch.device(f"cuda:{args.cuda_device}")
if args.resume:
print(f" Resuming from: {args.resume}")
model = PPO.load(args.resume, env=env, device=device)
else:
activation_fn = get_activation_fn(args.activation)
model = PPO(
"MlpPolicy",
env=env,
learning_rate=args.learning_rate,
n_steps=args.n_steps,
batch_size=args.batch_size,
gamma=args.gamma,
policy_kwargs=dict(activation_fn=activation_fn),
device=device,
verbose=1,
)
print(f" Activation: {args.activation}")
print(f" Device: {device}")
print(f" Learning rate: {args.learning_rate}")
print(f" Steps per iteration: {args.n_steps}")
print(f" Batch size: {args.batch_size}")
# Setup logging
tensorboard_dir = get_tensorboard_logdir(args.run_name)
writer = SummaryWriter(log_dir=str(tensorboard_dir))
print(f" TensorBoard: {tensorboard_dir}")
# Training loop
print(f"\n[3/4] Training for {args.total_timesteps} iterations...")
best_reward = -np.inf
history_data = []
for iteration in range(args.total_timesteps):
# Train
model.learn(total_timesteps=args.n_steps, reset_num_timesteps=False)
# Evaluate
print(f"\n--- Iteration {iteration + 1}/{args.total_timesteps} ---")
eval_results = evaluate_policy(model, env, n_episodes=args.eval_episodes)
mean_reward = eval_results['mean_reward']
std_reward = eval_results['std_reward']
# Log to TensorBoard
writer.add_scalar('eval/mean_reward', mean_reward, iteration)
writer.add_scalar('eval/std_reward', std_reward, iteration)
# Extract CD/CL from last episode
if len(eval_results['episodes']) > 0:
last_ep = eval_results['episodes'][-1]
avg_cd = np.mean(last_ep['cd'])
avg_cl = np.mean(last_ep['cl'])
writer.add_scalar('eval/avg_cd', avg_cd, iteration)
writer.add_scalar('eval/avg_cl', avg_cl, iteration)
# Save best model
if mean_reward > best_reward:
best_reward = mean_reward
model_path = get_model_path(f"{args.run_name}_best")
model.save(str(model_path))
print(f" ✓ New best model saved: {model_path} (reward: {mean_reward:.2f})")
# Periodic save
if (iteration + 1) % args.save_freq == 0:
model_path = get_model_path(f"{args.run_name}_iter{iteration + 1}")
model.save(str(model_path))
print(f" Checkpoint saved: {model_path}")
# Store history
history_data.append(eval_results['episodes'])
# Final evaluation
print(f"\n[4/4] Final evaluation...")
final_results = evaluate_policy(model, env, n_episodes=5)
print(f" Final mean reward: {final_results['mean_reward']:.2f} ± {final_results['std_reward']:.2f}")
# Save final model and history
final_model_path = get_model_path(f"{args.run_name}_final")
model.save(str(final_model_path))
print(f" Final model saved: {final_model_path}")
history_path = get_output_path(f"{args.run_name}_history.pkl")
with open(history_path, 'wb') as f:
pickle.dump(history_data, f)
print(f" Training history saved: {history_path}")
# Cleanup
writer.close()
env.close()
print("\n" + "=" * 70)
print("Training complete!")
print("=" * 70)
if __name__ == '__main__':
main()

11
src/__init__.py Normal file
View File

@ -0,0 +1,11 @@
"""
DynamisLab: Machine Learning for Computational Fluid Dynamics
"""
__version__ = '0.1.0'
__author__ = 'Frank14f'
from . import config
from . import environments
__all__ = ['config', 'environments', '__version__']

125
src/config.py Normal file
View File

@ -0,0 +1,125 @@
"""
Configuration management for DynamisLab.
Handles loading of CelerisLab configurations and project settings.
"""
import os
import sys
from pathlib import Path
from typing import Optional, Tuple
# Determine project root directory
_current_file = Path(__file__).resolve()
_project_root = _current_file.parent.parent.parent # Go up to DynamisLabNew/
# Configuration directory (relative to project root)
CONFIG_DIR = _project_root / 'configs'
# Output directories
MODELS_DIR = _project_root / 'models'
OUTPUT_DIR = _project_root / 'output'
TENSORBOARD_DIR = _project_root / 'tensorboard'
# Create output directories if they don't exist
MODELS_DIR.mkdir(exist_ok=True)
OUTPUT_DIR.mkdir(exist_ok=True)
TENSORBOARD_DIR.mkdir(exist_ok=True)
def setup_celeris_import() -> None:
"""
Setup CelerisLab import path.
Assumes CelerisLab is either:
1. Installed as a package (pip install CelerisLab)
2. Available as a git submodule in project root
3. Available via PYTHONPATH environment variable
"""
try:
# Try to import CelerisLab (it might be installed)
import CelerisLab
return
except ImportError:
pass
# Check for CelerisLab as submodule
celeris_submodule = _project_root / 'CelerisLab' / 'src'
if celeris_submodule.exists():
sys.path.insert(0, str(celeris_submodule))
return
# If still not found, raise error with helpful message
raise ImportError(
"CelerisLab not found. Please either:\n"
" 1. Install it: pip install -e ../CelerisLab\n"
" 2. Add as git submodule: git submodule add <url> CelerisLab\n"
" 3. Set PYTHONPATH to include CelerisLab src directory"
)
def load_celeris_configs(
cuda_config_path: Optional[str] = None,
field_config_path: Optional[str] = None
) -> Tuple:
"""
Load CelerisLab configurations.
Args:
cuda_config_path: Optional path to CUDA config. If None, uses CONFIG_DIR.
field_config_path: Optional path to field config. If None, uses CONFIG_DIR.
Returns:
Tuple of (config_cuda, config_field)
"""
# Setup CelerisLab import
setup_celeris_import()
from CelerisLab import utils
# Set environment variable to point to our configs
os.environ['CELERISLAB_CONFIG_DIR'] = str(CONFIG_DIR)
# Load configurations - CelerisLab will find them automatically
if cuda_config_path is None:
config_cuda = utils.load_cuda_config()
else:
config_cuda = utils.load_cuda_config(cuda_config_path)
if field_config_path is None:
config_field = utils.load_flow_field_config()
else:
config_field = utils.load_flow_field_config(field_config_path)
return config_cuda, config_field
def get_model_path(model_name: str) -> Path:
"""Get full path for a model file."""
return MODELS_DIR / f"{model_name}.zip"
def get_tensorboard_logdir(run_name: str) -> Path:
"""Get TensorBoard log directory for a run."""
logdir = TENSORBOARD_DIR / run_name
logdir.mkdir(exist_ok=True)
return logdir
def get_output_path(filename: str) -> Path:
"""Get full path for an output file."""
return OUTPUT_DIR / filename
# Expose project paths for convenience
__all__ = [
'CONFIG_DIR',
'MODELS_DIR',
'OUTPUT_DIR',
'TENSORBOARD_DIR',
'setup_celeris_import',
'load_celeris_configs',
'get_model_path',
'get_tensorboard_logdir',
'get_output_path',
]

View File

@ -0,0 +1,7 @@
"""
Gymnasium environments for CFD control tasks.
"""
from .cfd_env import CFDFlowControlEnv
__all__ = ['CFDFlowControlEnv']

328
src/environments/cfd_env.py Normal file
View File

@ -0,0 +1,328 @@
"""
CFD Flow Control Environment using CelerisLab.
A Gymnasium environment for active flow control using lattice Boltzmann simulation.
"""
import os
from collections import deque
from typing import Optional, Tuple, Dict, Any
import gymnasium as gym
import numpy as np
from gymnasium import spaces
# Set threading to avoid conflicts with GPU
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
class CFDFlowControlEnv(gym.Env):
"""
CFD flow control environment with cylinder and sensors.
The environment simulates flow around a cylinder with multiple control cylinders
and sensors to measure flow properties. The agent controls the cylinder velocities
to optimize flow characteristics.
Args:
device_id: CUDA device ID to use for simulation
config_cuda: CelerisLab CUDA configuration (optional, will load from config if None)
config_field: CelerisLab flow field configuration (optional, will load from config if None)
n_control_cylinders: Number of controllable cylinders (default: 3)
n_sensors: Number of flow sensors (default: 3)
max_steps: Maximum steps per episode (default: 500)
sample_interval: Simulation steps between observations (default: 800)
fifo_length: Length of state history (default: 120)
convergence_length: Steps to check for convergence (default: 60)
warmup_steps_factor: Multiple of grid size for warmup (default: 4)
"""
metadata = {"render_modes": ["human"], "render_fps": 30}
def __init__(
self,
device_id: int = 0,
config_cuda = None,
config_field = None,
n_control_cylinders: int = 3,
n_sensors: int = 3,
max_steps: int = 500,
sample_interval: int = 800,
fifo_length: int = 120,
convergence_length: int = 60,
warmup_steps_factor: int = 4,
):
super().__init__()
# Load configurations if not provided
if config_cuda is None or config_field is None:
from ..config import load_celeris_configs
config_cuda, config_field = load_celeris_configs()
self.config_cuda = config_cuda
self.config_field = config_field
self.device_id = device_id
# Environment parameters
self.n_control = n_control_cylinders
self.n_sensors = n_sensors
self.max_steps = max_steps
self.sample_interval = sample_interval
self.fifo_length = fifo_length
self.convergence_length = convergence_length
self.warmup_steps_factor = warmup_steps_factor
# Determine data type
if config_field.data_type == "FP32":
self.dtype = np.float32
elif config_field.data_type == "FP64":
self.dtype = np.float64
else:
raise ValueError(f"Unsupported data type: {config_field.data_type}")
# Action and observation dimensions
# Action: velocity control for n cylinders (x, y, rotation)
self.action_dim = n_control_cylinders
# Observation: sensor readings (u, v) from n sensors
self.obs_dim = n_sensors * 2 * 2 # 2 velocity components × 2 (current + derivative)
# Gym spaces
self.action_space = spaces.Box(
low=-1.0,
high=1.0,
shape=(self.action_dim,),
dtype=self.dtype
)
self.observation_space = spaces.Box(
low=-np.inf,
high=np.inf,
shape=(self.obs_dim,),
dtype=self.dtype
)
# State tracking
self.fifo_states = deque(maxlen=fifo_length)
self.target_states = np.empty((0, self.n_sensors * 2), dtype=self.dtype)
self.current_step = 0
# Normalization factors (will be set during warmup)
self.sens_norm_fact = np.ones(self.n_sensors * 2, dtype=self.dtype)
self.sens_deviation = np.zeros(self.n_sensors * 2, dtype=self.dtype)
# Reward tracking
self.reward_cd = 0.0
self.reward_cl = 0.0
self.reward_sim = 0.0
# Initialize flow field
self._init_flow_field()
def _init_flow_field(self):
"""Initialize the CelerisLab flow field simulation."""
from CelerisLab import FlowField
self.flow_field = FlowField(
self.config_field,
self.config_cuda,
self.device_id
)
# Get grid parameters
L0 = 20 # Characteristic length
U0 = self.config_field.velocity
NX = self.flow_field.FIELD_SHAPE[0]
NY = self.flow_field.FIELD_SHAPE[1]
# Add main cylinder (obstacle)
center = (10 * L0, (NY - 1) / 2, 0)
self.flow_field.add_cylinder(center, L0)
# Add sensors
sensor_y_positions = [
(NY - 1) / 2 + 2 * L0, # Above centerline
(NY - 1) / 2, # At centerline
(NY - 1) / 2 - 2 * L0, # Below centerline
]
for i in range(min(self.n_sensors, len(sensor_y_positions))):
center = (40 * L0, sensor_y_positions[i], 0)
self.flow_field.add_sensor(center, L0 / 4)
# Warmup simulation
warmup_steps = int(self.warmup_steps_factor * NX / U0)
self.flow_field.run(warmup_steps, np.zeros(self.n_control + 1, dtype=self.dtype))
# Collect baseline states for normalization
for _ in range(self.fifo_length):
self.flow_field.run(
self.sample_interval,
np.zeros(self.n_control + 1, dtype=self.dtype)
)
new_state = self.flow_field.obs.copy()[2:2 + self.n_sensors * 2]
self.target_states = np.vstack((self.target_states, new_state))
self.fifo_states.append(new_state)
# Calculate normalization factors
self._calculate_normalization()
def _calculate_normalization(self):
"""Calculate normalization factors from baseline states."""
if len(self.target_states) > 0:
self.sens_norm_fact = np.std(self.target_states, axis=0) + 1e-6
self.sens_deviation = np.mean(self.target_states, axis=0)
def _normalize_state(self, state: np.ndarray) -> np.ndarray:
"""Normalize state using calculated factors."""
return (state - self.sens_deviation) / self.sens_norm_fact
def _compute_reward(self, state: np.ndarray, action: np.ndarray) -> float:
"""
Compute reward based on drag reduction and flow similarity.
Args:
state: Current state observation
action: Applied action
Returns:
Total reward
"""
# Get force measurements from simulation
obs = self.flow_field.obs
cd = obs[0] # Drag coefficient
cl = obs[1] # Lift coefficient
# Drag reduction reward (negative drag is good)
self.reward_cd = -cd * 0.1
# Lift minimization (want symmetric flow)
self.reward_cl = -abs(cl) * 0.05
# Flow similarity to baseline (want smooth control)
if len(self.fifo_states) >= self.convergence_length:
recent_states = np.array(list(self.fifo_states)[-self.convergence_length:])
target_recent = self.target_states[-self.convergence_length:]
# Dynamic Time Warping distance (simplified)
diff = np.mean(np.abs(recent_states - target_recent))
self.reward_sim = -diff * 0.5
else:
self.reward_sim = 0.0
# Total reward
total_reward = self.reward_cd + self.reward_cl + self.reward_sim
return float(total_reward)
def reset(
self,
seed: Optional[int] = None,
options: Optional[Dict[str, Any]] = None
) -> Tuple[np.ndarray, Dict[str, Any]]:
"""
Reset the environment to initial state.
Args:
seed: Random seed for reproducibility
options: Additional options
Returns:
Tuple of (observation, info)
"""
super().reset(seed=seed)
self.current_step = 0
self.fifo_states.clear()
# Run a few steps to get initial state
for _ in range(10):
self.flow_field.run(
self.sample_interval,
np.zeros(self.n_control + 1, dtype=self.dtype)
)
state = self.flow_field.obs.copy()[2:2 + self.n_sensors * 2]
self.fifo_states.append(state)
# Get current state
current_state = self.fifo_states[-1]
# Compute state derivative (approximation)
if len(self.fifo_states) >= 2:
state_derivative = self.fifo_states[-1] - self.fifo_states[-2]
else:
state_derivative = np.zeros_like(current_state)
# Normalize and concatenate
obs = np.concatenate([
self._normalize_state(current_state),
self._normalize_state(state_derivative)
]).astype(self.dtype)
info = {
'step': self.current_step,
'cd': self.flow_field.obs[0],
'cl': self.flow_field.obs[1],
}
return obs, info
def step(self, action: np.ndarray) -> Tuple[np.ndarray, float, bool, bool, Dict[str, Any]]:
"""
Take a step in the environment.
Args:
action: Action to take (cylinder velocities)
Returns:
Tuple of (observation, reward, terminated, truncated, info)
"""
# Convert action to control input
# Action is in [-1, 1], scale to appropriate velocity range
control = np.zeros(self.n_control + 1, dtype=self.dtype)
control[:self.n_control] = action * 0.1 * self.config_field.velocity
# Run simulation
self.flow_field.run(self.sample_interval, control)
# Get new state
new_state = self.flow_field.obs.copy()[2:2 + self.n_sensors * 2]
self.fifo_states.append(new_state)
# Compute observation
if len(self.fifo_states) >= 2:
state_derivative = self.fifo_states[-1] - self.fifo_states[-2]
else:
state_derivative = np.zeros_like(new_state)
obs = np.concatenate([
self._normalize_state(new_state),
self._normalize_state(state_derivative)
]).astype(self.dtype)
# Compute reward
reward = self._compute_reward(new_state, action)
# Check termination
self.current_step += 1
terminated = False # CFD simulations typically don't have natural termination
truncated = self.current_step >= self.max_steps
# Info
info = {
'step': self.current_step,
'cd': float(self.flow_field.obs[0]),
'cl': float(self.flow_field.obs[1]),
'reward_cd': float(self.reward_cd),
'reward_cl': float(self.reward_cl),
'reward_sim': float(self.reward_sim),
}
return obs, reward, terminated, truncated, info
def render(self):
"""Render the environment (not implemented)."""
pass
def close(self):
"""Clean up resources."""
if hasattr(self, 'flow_field'):
del self.flow_field

94
test_structure.py Normal file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Quick test to verify DynamisLab package structure and imports.
"""
import sys
from pathlib import Path
# Add src to path
sys.path.insert(0, str(Path(__file__).parent / 'src'))
print("=" * 70)
print("DynamisLab Package Structure Test")
print("=" * 70)
# Test 1: Import main package
print("\n[1] Testing package import...")
try:
import config
import environments
print("✓ Package imports successful")
print(f" - config module: {config.__file__}")
print(f" - environments module: {environments.__file__}")
except ImportError as e:
print(f"✗ Import failed: {e}")
sys.exit(1)
# Test 2: Import config functions
print("\n[2] Testing config module...")
try:
from config import (
load_celeris_configs,
get_model_path,
get_tensorboard_logdir,
get_output_path,
CONFIG_DIR,
)
print("✓ Config functions imported")
print(f" - CONFIG_DIR: {CONFIG_DIR}")
except ImportError as e:
print(f"✗ Config import failed: {e}")
sys.exit(1)
# Test 3: Import environment
print("\n[3] Testing environments module...")
try:
import environments
print("✓ Environments module imported")
print(f" - Module: {environments.__file__}")
# Try to import CFDFlowControlEnv (might fail if dependencies not installed)
try:
from environments import CFDFlowControlEnv
print("✓ CFDFlowControlEnv imported")
print(f" - Class: {CFDFlowControlEnv}")
except ImportError as e:
print(f"⚠ CFDFlowControlEnv import failed (missing dependencies): {e}")
print(" This is OK if gymnasium/torch not installed yet")
except ImportError as e:
print(f"✗ Environments module import failed: {e}")
sys.exit(1)
# Test 4: Check version
print("\n[4] Checking package info...")
try:
# Try importing as installed package (might not work in src layout without install)
# This is just to show what will work after pip install -e .
print(" Note: Run 'pip install -e .' to enable 'import dynamis' style")
print(" Current import style: 'from config import ...' (src in PYTHONPATH)")
except Exception as e:
print(f" {e}")
# Test 5: Config paths
print("\n[5] Testing path helpers...")
try:
model_path = get_model_path("test_model")
tb_logdir = get_tensorboard_logdir("test_run")
output_path = get_output_path("test.pkl")
print(f"✓ Path helpers working")
print(f" - Model path: {model_path}")
print(f" - TensorBoard logdir: {tb_logdir}")
print(f" - Output path: {output_path}")
except Exception as e:
print(f"✗ Path helpers failed: {e}")
sys.exit(1)
print("\n" + "=" * 70)
print("✅ All structure tests passed!")
print("=" * 70)
print("\nNext steps:")
print(" 1. pip install -e . # Install in development mode")
print(" 2. python scripts/train_ppo.py --help # Test training script")
print("=" * 70)