First commit
This commit is contained in:
commit
15c6de7243
93
.gitignore
vendored
Normal file
93
.gitignore
vendored
Normal 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
21
LICENSE
Normal 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
291
README.md
Normal 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
9
configs/config_cuda.json
Normal 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
|
||||||
|
}
|
||||||
13
configs/config_flowfield.json
Normal file
13
configs/config_flowfield.json
Normal 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
3
configs/config_gym.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
236
docs/QUICK_REFERENCE.md
Normal file
236
docs/QUICK_REFERENCE.md
Normal 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
295
docs/REFACTORING_SUMMARY.md
Normal 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集成
|
||||||
|
|
||||||
|
### 方式1:Git 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
480
docs/SUBMODULE_WORKFLOW.md
Normal 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:开发 CelerisLab(CFD功能)
|
||||||
|
|
||||||
|
**在 `/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稳定时打tag,DynamisLab引用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
97
pyproject.toml
Normal 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
32
requirements.txt
Normal 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
316
scripts/train_ppo.py
Normal 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
11
src/__init__.py
Normal 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
125
src/config.py
Normal 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',
|
||||||
|
]
|
||||||
7
src/environments/__init__.py
Normal file
7
src/environments/__init__.py
Normal 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
328
src/environments/cfd_env.py
Normal 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
94
test_structure.py
Normal 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)
|
||||||
Loading…
Reference in New Issue
Block a user