├── drlfoam
├── tests
│ ├── __init__.py
│ └── test_utils.py
├── agent
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_ppo_agent.py
│ │ └── test_agent.py
│ ├── __init__.py
│ ├── agent.py
│ └── ppo_agent.py
├── environment
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_environment.py
│ │ ├── test_rotating_cylinder.py
│ │ └── test_rotating_pinball.py
│ ├── __init__.py
│ ├── environment.py
│ ├── rotating_cylinder.py
│ └── rotating_pinball.py
├── execution
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_local.py
│ │ ├── test_manager.py
│ │ └── test_slurm.py
│ ├── __init__.py
│ ├── local.py
│ ├── manager.py
│ ├── buffer.py
│ ├── slurm.py
│ └── setup.py
├── __init__.py
├── version.py
├── constants.py
└── utils.py
├── requirements.txt
├── openfoam
├── src
│ ├── agentRotatingWallVelocity
│ │ ├── Make
│ │ │ ├── files
│ │ │ └── options
│ │ ├── agentRotatingWallVelocityFvPatchVectorField.H
│ │ └── agentRotatingWallVelocityFvPatchVectorField.C
│ └── pinballRotatingWallVelocity
│ │ ├── Make
│ │ ├── files
│ │ └── options
│ │ └── pinballRotatingWallVelocityFvPatchVectorField.H
└── test_cases
│ ├── rotatingPinball2D
│ ├── policy.pt
│ ├── Allclean
│ ├── Allrun
│ ├── Allrun.pre
│ ├── constant
│ │ ├── turbulenceProperties
│ │ └── transportProperties
│ ├── system
│ │ ├── decomposeParDict
│ │ ├── setExprBoundaryFieldsDict
│ │ ├── fvSchemes
│ │ ├── fvSolution
│ │ ├── topoSetDict
│ │ ├── controlDict
│ │ └── blockMeshDict
│ └── 0.org
│ │ ├── p
│ │ └── U
│ └── rotatingCylinder2D
│ ├── policy.pt
│ ├── Allclean
│ ├── Allrun
│ ├── Allrun.pre
│ ├── constant
│ ├── turbulenceProperties
│ └── transportProperties
│ ├── system
│ ├── decomposeParDict
│ ├── setExprBoundaryFieldsDict
│ ├── fvSchemes
│ ├── fvSolution
│ ├── controlDict
│ └── blockMeshDict
│ └── 0.org
│ ├── p
│ └── U
├── create_test_data
├── Allwmake
├── Allwclean
├── examples
├── jobscript
├── create_dummy_policy.py
├── config_orig.yml
└── run_training.py
├── references.md
├── setup-env
├── .gitignore
└── README.md
/drlfoam/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/drlfoam/agent/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/drlfoam/environment/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/drlfoam/execution/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/drlfoam/__init__.py:
--------------------------------------------------------------------------------
1 | from .utils import *
--------------------------------------------------------------------------------
/drlfoam/version.py:
--------------------------------------------------------------------------------
1 | """drlfoam package version."""
2 |
3 | __version__ = "0.1"
--------------------------------------------------------------------------------
/drlfoam/agent/__init__.py:
--------------------------------------------------------------------------------
1 | from .agent import FCPolicy, FCValue
2 | from .ppo_agent import PPOAgent
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | --find-links https://download.pytorch.org/whl/torch_stable.html
2 | torch==1.13.1
3 | pandas
4 | numpy
5 | PyYAML
--------------------------------------------------------------------------------
/drlfoam/execution/__init__.py:
--------------------------------------------------------------------------------
1 | from .buffer import Buffer
2 | from .local import LocalBuffer
3 | from .slurm import SlurmConfig, SlurmBuffer
--------------------------------------------------------------------------------
/openfoam/src/agentRotatingWallVelocity/Make/files:
--------------------------------------------------------------------------------
1 | agentRotatingWallVelocityFvPatchVectorField.C
2 | LIB = $(DRL_LIBBIN)/libAgentRotatingWallVelocity
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/policy.pt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OFDataCommittee/drlfoam/HEAD/openfoam/test_cases/rotatingPinball2D/policy.pt
--------------------------------------------------------------------------------
/openfoam/src/pinballRotatingWallVelocity/Make/files:
--------------------------------------------------------------------------------
1 | pinballRotatingWallVelocityFvPatchVectorField.C
2 | LIB = $(DRL_LIBBIN)/libPinballRotatingWallVelocity
3 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/policy.pt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OFDataCommittee/drlfoam/HEAD/openfoam/test_cases/rotatingCylinder2D/policy.pt
--------------------------------------------------------------------------------
/drlfoam/environment/__init__.py:
--------------------------------------------------------------------------------
1 | from .environment import Environment
2 | from .rotating_cylinder import RotatingCylinder2D
3 | from .rotating_pinball import RotatingPinball2D
--------------------------------------------------------------------------------
/drlfoam/environment/tests/test_environment.py:
--------------------------------------------------------------------------------
1 |
2 | from pytest import raises
3 | from ..environment import Environment
4 |
5 |
6 | def test_environment():
7 | with raises(TypeError):
8 | env = Environment("", "", "", "", "", 1, 1, 1)
9 |
10 |
--------------------------------------------------------------------------------
/create_test_data:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 |
3 | # create test data for unittests
4 | test_case_path="openfoam/test_cases"
5 | test_data_path="test_data"
6 | env_name="rotatingCylinder2D"
7 |
8 | mkdir -p $test_data_path
9 | cp -r "${test_case_path}/${env_name}" "${test_data_path}/"
10 | cd "${test_data_path}/${env_name}" && ./Allrun.pre
--------------------------------------------------------------------------------
/Allwmake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | cmd="wmake all openfoam/src"
4 |
5 | if [[ $* == *--container* ]]
6 | then
7 | # compile with container
8 | source setup-env --container
9 | singularity exec $DRL_IMAGE bash -c "source $DRL_BASHRC && $cmd"
10 | else
11 | # compile without container
12 | source setup-env && $cmd
13 | fi
14 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/Allclean:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "${0%/*}" || exit
3 | . "${DRL_BASE:?}"/openfoam/RunFunctions
4 | #------------------------------------------------------------------------------
5 |
6 | cleanCase
7 | rm -rf 0 trajectory.csv
8 |
9 | #------------------------------------------------------------------------------
10 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/Allrun:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "${0%/*}" || exit
3 | . "${DRL_BASE:?}"/openfoam/RunFunctions
4 | #------------------------------------------------------------------------------
5 |
6 | # run case
7 | runParallel pimpleFoam
8 |
9 | #------------------------------------------------------------------------------
10 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/Allclean:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "${0%/*}" || exit
3 | . "${DRL_BASE:?}"/openfoam/RunFunctions
4 | #------------------------------------------------------------------------------
5 |
6 | cleanCase
7 | rm -rf 0 trajectory.csv
8 |
9 | #------------------------------------------------------------------------------
10 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/Allrun:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "${0%/*}" || exit
3 | . "${DRL_BASE:?}"/openfoam/RunFunctions
4 | #------------------------------------------------------------------------------
5 |
6 | # run case
7 | runParallel pimpleFoam
8 |
9 | #------------------------------------------------------------------------------
10 |
--------------------------------------------------------------------------------
/drlfoam/constants.py:
--------------------------------------------------------------------------------
1 |
2 | from os import environ
3 | from os.path import join
4 | from torch import DoubleTensor
5 |
6 |
7 | DEFAULT_TENSOR_TYPE = DoubleTensor
8 | EPS_SP = 1.0e-6
9 | EPS_DP = 1.0e-14
10 | BASE_PATH = environ.get("DRL_BASE", "")
11 | TESTCASE_PATH = join(BASE_PATH, "openfoam", "test_cases")
12 | TESTDATA_PATH = join(BASE_PATH, "test_data")
--------------------------------------------------------------------------------
/Allwclean:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | cmd="wclean all openfoam/src"
4 |
5 | if [[ $* == *--container* ]]
6 | then
7 | # compile with container
8 | source setup-env --container
9 | singularity exec $DRL_IMAGE bash -c "source $DRL_BASHRC && $cmd"
10 | else
11 | # compile without container
12 | source setup-env && $cmd
13 | fi
14 |
15 | rm -vf ${DRL_LIBBIN}/*
--------------------------------------------------------------------------------
/examples/jobscript:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #SBATCH --job-name=lr2_pinball_main
3 | #SBATCH --ntasks=6
4 | #SBATCH --output=%x_%j.out
5 | #SBATCH --partition=queue-1
6 | #SBATCH --constraint=c5a.24xlarge
7 |
8 | source /fsx/OpenFOAM/OpenFOAM-v2206/etc/bashrc
9 | source /fsx/drlfoam_main/setup-env
10 | source /fsx/drlfoam_main/pydrl/bin/activate
11 |
12 | python3 run_training.py -o test_pinball_lr2 -i 100 -r 10 -b 10 -f 300 -s rotatingPinball2D -e slurm &> log.pinball_main_lr2
13 |
--------------------------------------------------------------------------------
/references.md:
--------------------------------------------------------------------------------
1 | ## Student projects
2 |
3 | - J. Geise, “Learning of optimized multigrid solver settings for CFD applications”, Zenodo, 2023. doi: 10.5281/zenodo.10361535
4 | - Janis Geise, “Robust model-based deep reinforcement learning for flow control”, Zenodo, 2023. doi: 10.5281/zenodo.7642927
5 | - Tom Krogmann, “Optimal sensor placement for active flow control with deep reinforcement learning”, Zenodo, 2023. doi: 10.5281/zenodo.7636959
6 | - E. Schulze, “Model-based Reinforcement Learning for Accelerated Learning From CFD Simulations”, Zenodo, 2022. doi: 10.5281/zenodo.6375575
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/Allrun.pre:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "${0%/*}" || exit
3 | . "${DRL_BASE:?}"/openfoam/RunFunctions
4 | #------------------------------------------------------------------------------
5 |
6 | # create mesh
7 | runApplication blockMesh
8 | runApplication topoSet
9 |
10 | # decompose and run case
11 | cp -r 0.org 0
12 | runApplication setExprBoundaryFields
13 | runApplication decomposePar
14 | runParallel renumberMesh -overwrite
15 | runParallel -s "pre" pimpleFoam
16 |
17 | #------------------------------------------------------------------------------
18 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/Allrun.pre:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "${0%/*}" || exit
3 | . "${DRL_BASE:?}"/openfoam/RunFunctions
4 | #------------------------------------------------------------------------------
5 |
6 | # create mesh
7 | runApplication blockMesh
8 |
9 | # set inlet velocity
10 | cp -r 0.org 0
11 | runApplication setExprBoundaryFields
12 |
13 | # decompose and run case
14 | runApplication decomposePar
15 | runParallel renumberMesh -overwrite
16 | runParallel -s "pre" pimpleFoam
17 |
18 | #------------------------------------------------------------------------------
19 |
--------------------------------------------------------------------------------
/openfoam/src/agentRotatingWallVelocity/Make/options:
--------------------------------------------------------------------------------
1 | EXE_INC = \
2 | -std=c++17 \
3 | -Wno-deprecated-declarations -Wno-old-style-cast -Wno-redundant-move \
4 | -I$(LIB_SRC)/finiteVolume/lnInclude \
5 | -I$(LIB_SRC)/sampling/lnInclude \
6 | -I$(LIB_SRC)/OpenFOAM/lnInclude \
7 | -I$(DRL_TORCH)/include \
8 | -I$(DRL_TORCH)/include/torch/csrc/api/include
9 |
10 | LIB_LIBS = \
11 | -lfiniteVolume \
12 | -rdynamic \
13 | -Wl,-rpath,$(DRL_TORCH)/lib $(DRL_TORCH)/lib/libtorch.so $(DRL_TORCH)/lib/libc10.so \
14 | -Wl,--no-as-needed,$(DRL_TORCH)/lib/libtorch_cpu.so \
15 | -Wl,--as-needed $(DRL_TORCH)/lib/libc10.so \
16 | -Wl,--no-as-needed,$(DRL_TORCH)/lib/libtorch.so
17 |
--------------------------------------------------------------------------------
/openfoam/src/pinballRotatingWallVelocity/Make/options:
--------------------------------------------------------------------------------
1 | EXE_INC = \
2 | -std=c++17 \
3 | -Wno-deprecated-declarations -Wno-old-style-cast -Wno-redundant-move \
4 | -I$(LIB_SRC)/finiteVolume/lnInclude \
5 | -I$(LIB_SRC)/sampling/lnInclude \
6 | -I$(LIB_SRC)/OpenFOAM/lnInclude \
7 | -I$(DRL_TORCH)/include \
8 | -I$(DRL_TORCH)/include/torch/csrc/api/include
9 |
10 | LIB_LIBS = \
11 | -lfiniteVolume \
12 | -rdynamic \
13 | -Wl,-rpath,$(DRL_TORCH)/lib $(DRL_TORCH)/lib/libtorch.so $(DRL_TORCH)/lib/libc10.so \
14 | -Wl,--no-as-needed,$(DRL_TORCH)/lib/libtorch_cpu.so \
15 | -Wl,--as-needed $(DRL_TORCH)/lib/libc10.so \
16 | -Wl,--no-as-needed,$(DRL_TORCH)/lib/libtorch.so
17 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/constant/turbulenceProperties:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "constant";
14 | object turbulenceProperties;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | simulationType laminar;
19 |
20 | // ************************************************************************* //
21 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/constant/turbulenceProperties:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "constant";
14 | object turbulenceProperties;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | simulationType laminar;
19 |
20 |
21 | // ************************************************************************* //
22 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/constant/transportProperties:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "constant";
14 | object transportProperties;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | transportModel Newtonian;
19 |
20 | nu 0.01;
21 |
22 | // ************************************************************************* //
23 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/constant/transportProperties:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "constant";
14 | object transportProperties;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | transportModel Newtonian;
19 |
20 | nu 1.0e-3;
21 |
22 |
23 | // ************************************************************************* //
24 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/system/decomposeParDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object decomposeParDict;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | numberOfSubdomains 2;
19 |
20 | method hierarchical;
21 |
22 | coeffs
23 | {
24 | n (2 1 1);
25 | }
26 |
27 |
28 | // ************************************************************************* //
29 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/system/decomposeParDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object decomposeParDict;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | numberOfSubdomains 8;
19 |
20 | method hierarchical;
21 |
22 | coeffs
23 | {
24 | n (4 2 1);
25 | }
26 |
27 |
28 | // ************************************************************************* //
29 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/0.org/p:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class volScalarField;
13 | object p;
14 | }
15 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
16 |
17 | dimensions [0 2 -2 0 0 0 0];
18 |
19 | internalField uniform 0;
20 |
21 | boundaryField
22 | {
23 | "(inlet|cylinders|outlet)"
24 | {
25 | type zeroGradient;
26 | }
27 |
28 | "(front|back)"
29 | {
30 | type empty;
31 | }
32 | }
33 |
34 |
35 | // ************************************************************************* //
36 |
--------------------------------------------------------------------------------
/examples/create_dummy_policy.py:
--------------------------------------------------------------------------------
1 | """
2 | Create a randomly initialized policy network.
3 | """
4 | import sys
5 | import torch as pt
6 | from os import environ
7 | from os.path import join
8 | from typing import Union
9 |
10 | BASE_PATH = environ.get("DRL_BASE", "")
11 | sys.path.insert(0, BASE_PATH)
12 |
13 | from drlfoam.agent import FCPolicy
14 |
15 |
16 | def create_dummy_policy(n_probes: int, n_actions: int, target_dir: str,
17 | abs_action: Union[int, float, pt.Tensor]) -> None:
18 | """
19 | initializes new policy
20 |
21 | :param n_probes: number of probes placed in the flow field
22 | :param n_actions: number of actions
23 | :param target_dir: path to the training directory
24 | :param abs_action: absolute value of the action boundaries
25 | :return: None
26 | """
27 | policy = FCPolicy(n_probes, n_actions, -abs_action, abs_action)
28 | script = pt.jit.script(policy)
29 | script.save(join(target_dir, "policy.pt"))
30 |
31 |
32 | if __name__ == "__main__":
33 | # rotatingCylinder2D
34 | create_dummy_policy(12, 1, join("..", "openfoam", "test_cases", "rotatingCylinder2D"), 5)
35 | # rotatingPinball2D
36 | create_dummy_policy(14, 3, join("..", "openfoam", "test_cases", "rotatingCylinder2D"), 5)
37 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/system/setExprBoundaryFieldsDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v2006 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | object setExprBoundaryFieldsDict;
14 | }
15 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
16 |
17 | U_inlet
18 | {
19 | field U;
20 |
21 | expressions
22 | (
23 | {
24 | patch inlet;
25 | target value;
26 | vel { dir (1 0 0); }
27 | expression #{ (pos().y() < 0.0) ? 1.01*$[(vector)vel.dir] : 0.99*$[(vector)vel.dir] #};
28 | }
29 | );
30 | }
31 |
32 | // ************************************************************************* //
33 |
34 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/0.org/p:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class volScalarField;
13 | object p;
14 | }
15 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
16 |
17 | dimensions [0 2 -2 0 0 0 0];
18 |
19 | internalField uniform 0;
20 |
21 | boundaryField
22 | {
23 | "(inlet|cylinder|top|bottom)"
24 | {
25 | type zeroGradient;
26 | }
27 |
28 | outlet
29 | {
30 | type fixedValue;
31 | value $internalField;
32 | }
33 |
34 | "(front|back)"
35 | {
36 | type empty;
37 | }
38 | }
39 |
40 |
41 | // ************************************************************************* //
42 |
--------------------------------------------------------------------------------
/drlfoam/execution/local.py:
--------------------------------------------------------------------------------
1 | """
2 | implements a local buffer
3 | """
4 | from typing import Union
5 | from subprocess import Popen
6 |
7 | from .buffer import Buffer
8 | from ..environment import Environment
9 |
10 |
11 | def submit_and_wait(cmd: str, cwd: str, timeout: int = 1e15):
12 | proc = Popen([cmd], cwd=cwd)
13 | proc.wait(timeout)
14 |
15 |
16 | class LocalBuffer(Buffer):
17 | def __init__(
18 | self,
19 | path: str,
20 | base_env: Environment,
21 | buffer_size: int,
22 | n_runners_max: int,
23 | keep_trajectories: bool = True,
24 | timeout: Union[int, float] = 1e15,
25 | ):
26 | super(LocalBuffer, self).__init__(
27 | path, base_env, buffer_size, n_runners_max, keep_trajectories, timeout
28 | )
29 |
30 | def prepare(self) -> None:
31 | cmd = f"./{self._base_env.initializer_script}"
32 | cwd = self._base_env.path
33 | self._manager.add(submit_and_wait, cmd, cwd, self._timeout)
34 | self._manager.run()
35 | self._base_env.initialized = True
36 |
37 | def fill(self) -> None:
38 | for env in self.envs:
39 | self._manager.add(submit_and_wait, f"./{env.run_script}", env.path, self._timeout)
40 | self._manager.run()
41 | if self._keep_trajectories:
42 | self.save_trajectories()
43 | self._n_fills += 1
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/system/setExprBoundaryFieldsDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v2006 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | object setExprBoundaryFieldsDict;
14 | }
15 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
16 |
17 | U_inlet
18 | {
19 | field U;
20 |
21 | expressions
22 | (
23 | {
24 | patch inlet;
25 | target value;
26 | //Um 1.5; - maximum velocity at the center of the channel
27 | //H 0.41; - channel height
28 | vel { dir (1 0 0); }
29 | expression #{ 4*1.5*pos().y()*(0.41-pos().y())/(0.41*0.41)*$[(vector)vel.dir] #};
30 | }
31 | );
32 | }
33 |
34 | // ************************************************************************* //
35 |
36 |
--------------------------------------------------------------------------------
/setup-env:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # location of repository
4 | export DRL_BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
5 | # location of compiled libraries
6 | export DRL_LIBBIN="${DRL_BASE}/openfoam/libs"
7 | mkdir -p $DRL_LIBBIN
8 |
9 | if [[ $* == *--container* ]]
10 | then
11 | # workflow with Singularity container
12 | echo "Setting environment variables for execution with Singularity"
13 | # image location
14 | export DRL_IMAGE="${DRL_BASE}/of2206-py1.12.1-cpu.sif"
15 | # location of OpenFOAM bashrc file inside the container
16 | export DRL_BASHRC="/usr/lib/openfoam/openfoam2206/etc/bashrc"
17 | # path to libTorch inside the container
18 | export DRL_TORCH="/opt/libtorch"
19 | else
20 | # workflow with local installation of dependencies
21 | echo "Setting environment variables for execution without containers"
22 | unset DRL_IMAGE DRL_BASHRC
23 | # path to libTorch
24 | export DRL_TORCH="${DRL_BASE}/libtorch"
25 | # download libTorch if necessary
26 | if [ ! -d $DRL_TORCH ];
27 | then
28 | echo "Could not find libTorch dependencies. Downloading libTorch to ${DRL_TORCH}"
29 | wget https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-1.12.1%2Bcpu.zip && \
30 | unzip libtorch-cxx11-abi-shared-with-deps-1.12.1+cpu.zip
31 | rm libtorch-cxx11-abi-shared-with-deps-1.12.1+cpu.zip
32 | fi
33 | fi
34 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/system/fvSchemes:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object fvSchemes;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | ddtSchemes
19 | {
20 | default Euler;
21 | }
22 |
23 | gradSchemes
24 | {
25 | default Gauss linear;
26 | }
27 |
28 | divSchemes
29 | {
30 | default Gauss linear;
31 | div((nuEff*dev(T(grad(U))))) Gauss linear;
32 | }
33 |
34 | laplacianSchemes
35 | {
36 | default Gauss linear corrected;
37 | }
38 |
39 | interpolationSchemes
40 | {
41 | default linear;
42 | }
43 |
44 | snGradSchemes
45 | {
46 | default corrected;
47 | }
48 |
49 | fluxRequired
50 | {
51 | default no;
52 | p ;
53 | }
54 |
55 |
56 | // ************************************************************************* //
57 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/system/fvSchemes:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object fvSchemes;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | ddtSchemes
19 | {
20 | default Euler;
21 | }
22 |
23 | gradSchemes
24 | {
25 | default Gauss linear;
26 | }
27 |
28 | divSchemes
29 | {
30 | default Gauss linear;
31 | div(phi,U) Gauss limitedLinear 1.0;
32 | div((nuEff*dev(T(grad(U))))) Gauss linear;
33 | }
34 |
35 | laplacianSchemes
36 | {
37 | default Gauss linear corrected;
38 | }
39 |
40 | interpolationSchemes
41 | {
42 | default linear;
43 | }
44 |
45 | snGradSchemes
46 | {
47 | default corrected;
48 | }
49 |
50 | fluxRequired
51 | {
52 | default no;
53 | p ;
54 | }
55 |
56 |
57 | // ************************************************************************* //
58 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/0.org/U:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: com |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class volVectorField;
13 | arch "LSB;label=32;scalar=64";
14 | location "0";
15 | object U;
16 | }
17 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
18 |
19 | dimensions [ 0 1 -1 0 0 0 0 ];
20 |
21 | internalField uniform ( 0 0 0 );
22 |
23 | boundaryField
24 | {
25 | inlet
26 | {
27 | type fixedValue;
28 | value uniform (1 0 0);
29 | }
30 |
31 | outlet
32 | {
33 | type zeroGradient;
34 | }
35 |
36 | cylinder
37 | {
38 | type agentRotatingWallVelocity;
39 | origin (0.2 0.2 0.0);
40 | axis (0 0 1);
41 | policy "policy.pt";
42 | probesDict "probes";
43 | startTime 4.0;
44 | interval 20;
45 | train true;
46 | seed 0;
47 | absOmegaMax 5.0;
48 | value uniform (0 0 0);
49 | }
50 |
51 | "(top|bottom)"
52 | {
53 | type noSlip;
54 | }
55 |
56 | "(front|back)"
57 | {
58 | type empty;
59 | }
60 | }
61 |
62 |
63 | // ************************************************************************* //
64 |
--------------------------------------------------------------------------------
/drlfoam/agent/tests/test_ppo_agent.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | from os import remove
4 | from os.path import join, isfile
5 | import torch as pt
6 | from ..ppo_agent import PPOAgent
7 | from ...constants import DEFAULT_TENSOR_TYPE
8 |
9 | pt.set_default_tensor_type(DEFAULT_TENSOR_TYPE)
10 |
11 |
12 | class TestPPOAgent():
13 | def test_update(self):
14 | states = [pt.rand((10, 100)) for _ in range(5)]
15 | rewards = [pt.rand(10) for _ in range(5)]
16 | actions = [pt.rand(10) for _ in range(5)]
17 | agent = PPOAgent(100, 1, pt.tensor(-10), pt.tensor(10))
18 | agent.update(states, actions, rewards)
19 | hist = agent.history
20 | assert "policy_loss" in hist
21 | assert len(hist["policy_loss"]) == 1
22 | assert "value_loss" in hist
23 | assert len(hist["value_loss"]) == 1
24 | # test multiple actions
25 | actions = [pt.rand((10, 3)) for _ in range(5)]
26 | agent = PPOAgent(100, 3, pt.tensor(-10), pt.tensor(10))
27 | agent.update(states, actions, rewards)
28 | assert "policy_loss" in hist
29 | assert len(hist["policy_loss"]) == 1
30 |
31 |
32 | def test_save_load(self):
33 | states = pt.rand((5, 100))
34 | agent = PPOAgent(100, 1, pt.tensor(-10), pt.tensor(10))
35 | p_out_ref = agent._policy(states)
36 | v_out_ref = agent._value(states)
37 | checkpoint_path = join("/tmp", "checkpoint.pt")
38 | agent.save_state(checkpoint_path)
39 | assert isfile(checkpoint_path)
40 | agent.load_state(checkpoint_path)
41 | p_out = agent._policy(states)
42 | v_out = agent._value(states)
43 | assert pt.allclose(p_out_ref, p_out)
44 | assert pt.allclose(v_out_ref, v_out)
45 |
46 | def test_trace_policy(self):
47 | states = pt.rand((5, 100))
48 | agent = PPOAgent(100, 1, pt.tensor(-10), pt.tensor(10))
49 | p_out_ref = agent._policy(states)
50 | trace = agent.trace_policy()
51 | p_out = trace(states)
52 | assert pt.allclose(p_out_ref, p_out)
53 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/system/fvSolution:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object fvSolution;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | solvers
19 | {
20 | p
21 | {
22 | solver GAMG;
23 | smoother DICGaussSeidel;
24 | tolerance 1e-6;
25 | relTol 0.01;
26 | }
27 |
28 | pFinal
29 | {
30 | $p;
31 | relTol 0;
32 | }
33 |
34 | U
35 | {
36 | solver smoothSolver;
37 | smoother symGaussSeidel;
38 | tolerance 1e-05;
39 | relTol 0.1;
40 | }
41 |
42 | UFinal
43 | {
44 | $U;
45 | relTol 0;
46 | }
47 |
48 | }
49 |
50 | PIMPLE
51 | {
52 | momentumPredictor yes;
53 | transonic no;
54 | nOuterCorrectors 20;
55 | nCorrectors 3;
56 | nNonOrthogonalCorrectors 1;
57 | consistent yes;
58 | pRefCell 0;
59 | pRefValue 0;
60 |
61 | residualControl
62 | {
63 | U
64 | {
65 | relTol 0;
66 | tolerance 0.0001;
67 | }
68 | p
69 | {
70 | relTol 0;
71 | tolerance 0.001;
72 | }
73 | }
74 | }
75 |
76 |
77 | // ************************************************************************* //
78 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/0.org/U:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: com |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class volVectorField;
13 | arch "LSB;label=32;scalar=64";
14 | location "0";
15 | object U;
16 | }
17 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
18 |
19 | dimensions [ 0 1 -1 0 0 0 0 ];
20 |
21 | internalField uniform ( 0 0 0 );
22 |
23 | boundaryField
24 | {
25 | inlet // Note that the inlet patch consists of the boundary surface area at the inlet, top and bottom of the mesh
26 | {
27 | type fixedValue;
28 | value uniform (1 0 0);
29 | }
30 |
31 | outlet
32 | {
33 | type inletOutlet;
34 | inletValue uniform (0 0 0);
35 | value $internalField;
36 | }
37 |
38 | cylinders
39 | {
40 | type pinballRotatingWallVelocity;
41 | origin_a (-1.299 0.0 0.0);
42 | origin_b (0.0 0.75 0.0);
43 | origin_c (0.0 -0.75 0.0);
44 | axis (0 0 1);
45 | policy "policy.pt";
46 | probesDict "probes";
47 | train true;
48 | seed 0;
49 | absOmegaMax 5.0;
50 | value uniform (0 0 0);
51 | }
52 |
53 | "(front|back)"
54 | {
55 | type empty;
56 | }
57 | }
58 |
59 |
60 | // ************************************************************************* //
61 |
--------------------------------------------------------------------------------
/drlfoam/execution/manager.py:
--------------------------------------------------------------------------------
1 | """
2 | implements a class for handling the execution of runner for filling the buffer
3 | """
4 | import logging
5 |
6 | from queue import Queue
7 | from threading import Thread
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | def string_args(args: list, kwargs: dict) -> str:
13 | args_str = ", ".join([str(arg) for arg in args])
14 | kwargs_str = ", ".join(f"{key}={str(value)}" for key, value in kwargs.items())
15 | if args_str and kwargs_str:
16 | return args_str + ", " + kwargs_str
17 | elif args_str and not kwargs_str:
18 | return args_str
19 | elif not args_str and kwargs_str:
20 | return kwargs_str
21 | else:
22 | return ""
23 |
24 |
25 | class Runner(Thread):
26 | def __init__(self, tasks: Queue, name: str):
27 | super(Runner, self).__init__()
28 | self._tasks = tasks
29 | self._name = name
30 | self.daemon = True
31 | self.start()
32 |
33 | def run(self) -> None:
34 | while not self._tasks.empty():
35 | try:
36 | func, args, kwargs = self._tasks.get()
37 | logger.info(f"{self._name}: {func.__name__}({string_args(args, kwargs)})")
38 | func(*args, **kwargs)
39 | except Exception as e:
40 | logger.warning(f"{self._name}: " + str(e))
41 | finally:
42 | self._tasks.task_done()
43 |
44 | logger.info(f"{self._name}: all tasks done")
45 |
46 |
47 | class TaskManager(Queue):
48 | def __init__(self, n_runners_max: int):
49 | super(TaskManager, self).__init__()
50 | self._n_runners_max = n_runners_max
51 | self._runners = None
52 |
53 | def add(self, task, *args, **kwargs) -> None:
54 | self.put((task, args, kwargs))
55 |
56 | def run(self, wait: bool = True) -> None:
57 | n_runners = min(self._n_runners_max, self.qsize())
58 | self._runners = [Runner(self, f"Runner {i}") for i in range(n_runners)]
59 | if wait:
60 | self.wait()
61 |
62 | def wait(self) -> None:
63 | self.join()
64 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/system/fvSolution:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object fvSolution;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | solvers
19 | {
20 | p
21 | {
22 | solver GAMG;
23 | smoother DICGaussSeidel;
24 | tolerance 1e-6;
25 | relTol 0.01;
26 | }
27 |
28 | pFinal
29 | {
30 | $p;
31 | relTol 0;
32 | }
33 |
34 | U
35 | {
36 | solver smoothSolver;
37 | smoother symGaussSeidel;
38 | tolerance 1e-05;
39 | relTol 0.1;
40 | }
41 |
42 | UFinal
43 | {
44 | $U;
45 | relTol 0;
46 | }
47 |
48 | "cellDisplacement.*"
49 | {
50 | solver GAMG;
51 | tolerance 1e-14;
52 | relTol 0;
53 | smoother GaussSeidel;
54 | }
55 | }
56 |
57 | PIMPLE
58 | {
59 | momentumPredictor yes;
60 | transonic no;
61 | nOuterCorrectors 50;
62 | nCorrectors 1;
63 | nNonOrthogonalCorrectors 0;
64 | consistent yes;
65 |
66 | residualControl
67 | {
68 | "(U|p)"
69 | {
70 | relTol 0;
71 | tolerance 0.0001;
72 | }
73 | }
74 | }
75 |
76 |
77 | // ************************************************************************* //
78 |
--------------------------------------------------------------------------------
/drlfoam/tests/test_utils.py:
--------------------------------------------------------------------------------
1 |
2 | from os import remove, makedirs
3 | from os.path import join
4 | from shutil import rmtree
5 | from ..utils import (get_latest_time, replace_line_in_file,
6 | replace_line_latest, fetch_line_from_file)
7 |
8 |
9 | def test_get_latest_time():
10 | test_folder = join("/tmp", "test_get_latest_time")
11 | folders = ["constant", "system", "0.org", "1e-6", "0", "1", "1.01"]
12 | for f in folders:
13 | makedirs(join(test_folder, f), exist_ok=True)
14 | latest = get_latest_time(test_folder)
15 | rmtree(test_folder)
16 | assert latest == "1.01"
17 |
18 |
19 | def test_fetch_line_from_file():
20 | testfile = "/tmp/test_replace_line_in_file.txt"
21 | with open(testfile, "w+") as tf:
22 | tf.write(
23 | """
24 | policy model;
25 | seed 1;
26 | train true;
27 | """
28 | )
29 | line = fetch_line_from_file(testfile, "train")
30 | remove(testfile)
31 | assert "true" in line
32 |
33 |
34 | def test_replace_line_in_file():
35 | testfile = "/tmp/test_replace_line_in_file.txt"
36 | with open(testfile, "w+") as tf:
37 | tf.write(
38 | """
39 | policy model;
40 | seed 1;
41 | train true;
42 | """
43 | )
44 | keyword, new = "seed", "seed 0;"
45 | replace_line_in_file(testfile, keyword, new)
46 | with open(testfile, "r") as tf:
47 | found_new_line = False
48 | for line in tf.readlines():
49 | if new in line:
50 | found_new_line = True
51 | remove(testfile)
52 | assert found_new_line
53 |
54 |
55 | def test_replace_line_latest():
56 | test_folder = join("/tmp", "test_replace_latest")
57 | for p in ["processor0", "processor1"]:
58 | makedirs(join(test_folder, p, "2"), exist_ok=True)
59 | with open(join(test_folder, p, "2", "U"), "w+") as tf:
60 | tf.write("seed 0;")
61 | replace_line_latest(test_folder, "U", "seed", "seed 1;")
62 | found = 0
63 | for p in ["processor0", "processor1"]:
64 | with open(join(test_folder, p, "2", "U"), "r") as tf:
65 | for line in tf.readlines():
66 | if "seed 1;" in line:
67 | found += 1
68 | rmtree(test_folder)
69 | assert found == 2
70 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/system/topoSetDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v2106 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | object topoSetDict;
14 | }
15 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
16 |
17 | actions
18 | (
19 | {
20 | name cylinder_a;
21 | type faceSet;
22 | action new;
23 | source cylinderToFace;
24 | p1 (-1.299 0 -1e3);
25 | p2 (-1.299 0 1e3);
26 | radius 0.5;
27 | }
28 |
29 | {
30 | name cylinder_b;
31 | type faceSet;
32 | action new;
33 | source cylinderToFace;
34 | p1 (0 0.75 -1e3);
35 | p2 (0 0.75 1e3);
36 | radius 0.5;
37 | }
38 |
39 | {
40 | name cylinder_c;
41 | type faceSet;
42 | action new;
43 | source cylinderToFace;
44 | p1 (0 -0.75 -1e3);
45 | p2 (0 -0.75 1e3);
46 | radius 0.5;
47 | }
48 |
49 | {
50 | name faceZone_a;
51 | type faceZoneSet;
52 | action new;
53 | source setToFaceZone;
54 | faceSet cylinder_a;
55 | }
56 |
57 | {
58 | name faceZone_b;
59 | type faceZoneSet;
60 | action new;
61 | source setToFaceZone;
62 | faceSet cylinder_b;
63 | }
64 |
65 | {
66 | name faceZone_c;
67 | type faceZoneSet;
68 | action new;
69 | source setToFaceZone;
70 | faceSet cylinder_c;
71 | }
72 |
73 | );
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # repository-specific
2 | libtorch
3 | lnInclude
4 | linux64GccDPInt32Opt
5 | .vscode
6 | test_data
7 | test_training
8 | openfoam/libs
9 | *.sif
10 | pydrl
11 | examples/training*
12 | examples/visual
13 | examples/analyze_training.ipynb
14 | examples/test*
15 | *.out
16 | log.*
17 |
18 | # Byte-compiled / optimized / DLL files
19 | __pycache__/
20 | *.py[cod]
21 | *$py.class
22 |
23 | # C extensions
24 | *.so
25 |
26 | # Distribution / packaging
27 | .Python
28 | build/
29 | develop-eggs/
30 | dist/
31 | downloads/
32 | eggs/
33 | .eggs/
34 | lib/
35 | lib64/
36 | parts/
37 | sdist/
38 | var/
39 | wheels/
40 | pip-wheel-metadata/
41 | share/python-wheels/
42 | *.egg-info/
43 | .installed.cfg
44 | *.egg
45 | MANIFEST
46 |
47 | # PyInstaller
48 | # Usually these files are written by a python script from a template
49 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
50 | *.manifest
51 | *.spec
52 |
53 | # Installer logs
54 | pip-log.txt
55 | pip-delete-this-directory.txt
56 |
57 | # Unit test / coverage reports
58 | htmlcov/
59 | .tox/
60 | .nox/
61 | .coverage
62 | .coverage.*
63 | .cache
64 | nosetests.xml
65 | coverage.xml
66 | *.cover
67 | *.py,cover
68 | .hypothesis/
69 | .pytest_cache/
70 |
71 | # Translations
72 | *.mo
73 | *.pot
74 |
75 | # Django stuff:
76 | *.log
77 | local_settings.py
78 | db.sqlite3
79 | db.sqlite3-journal
80 |
81 | # Flask stuff:
82 | instance/
83 | .webassets-cache
84 |
85 | # Scrapy stuff:
86 | .scrapy
87 |
88 | # Sphinx documentation
89 | docs/_build/
90 |
91 | # PyBuilder
92 | target/
93 |
94 | # Jupyter Notebook
95 | .ipynb_checkpoints
96 |
97 | # IPython
98 | profile_default/
99 | ipython_config.py
100 |
101 | # pyenv
102 | .python-version
103 |
104 | # pipenv
105 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
106 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
107 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
108 | # install all needed dependencies.
109 | #Pipfile.lock
110 |
111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
112 | __pypackages__/
113 |
114 | # Celery stuff
115 | celerybeat-schedule
116 | celerybeat.pid
117 |
118 | # SageMath parsed files
119 | *.sage.py
120 |
121 | # Environments
122 | .env
123 | .venv
124 | env/
125 | venv/
126 | ENV/
127 | env.bak/
128 | venv.bak/
129 |
130 | # Spyder project settings
131 | .spyderproject
132 | .spyproject
133 |
134 | # Rope project settings
135 | .ropeproject
136 |
137 | # mkdocs documentation
138 | /site
139 |
140 | # mypy
141 | .mypy_cache/
142 | .dmypy.json
143 | dmypy.json
144 |
145 | # Pyre type checker
146 | .pyre/
147 | post_processing/
148 | run/
149 |
--------------------------------------------------------------------------------
/drlfoam/agent/tests/test_agent.py:
--------------------------------------------------------------------------------
1 |
2 | from os import remove
3 | from os.path import join, isfile
4 | import torch as pt
5 | from ..agent import compute_returns, compute_gae, FCPolicy, FCValue
6 | from ...constants import DEFAULT_TENSOR_TYPE
7 |
8 |
9 | pt.set_default_tensor_type(DEFAULT_TENSOR_TYPE)
10 |
11 |
12 | def test_compute_rewards():
13 | gamma = 0.99
14 | rewards = pt.tensor([1.0, 0.1, 2.0])
15 | r0 = 0.99**0*1.0 + 0.99**1*0.1 + 0.99**2*2.0
16 | r1 = 0.99**0*0.1 + 0.99**1*2.0
17 | r2 = 0.99**0*2.0
18 | expected = pt.tensor([r0, r1, r2])
19 | assert pt.allclose(expected, compute_returns(rewards, 0.99))
20 |
21 |
22 | def test_compute_gae():
23 | values = pt.tensor([0.1, 0.5, 0.2, 1.0])
24 | rewards = pt.tensor([1.0, 0.1, 2.0, 0.2])
25 | d0 = 1.0 + 0.99 * 0.5 - 0.1
26 | d1 = 0.1 + 0.99 * 0.2 - 0.5
27 | d2 = 2.0 + 0.99 * 1.0 - 0.2
28 | t0, t1, t2 = (0.99*0.97)**0, (0.99*0.97)**1, (0.99*0.97)**2
29 | A0 = t0*d0 + t1*d1 + t2*d2
30 | A1 = t0*d1 + t1*d2
31 | A2 = t0*d2
32 | expected = pt.tensor([A0, A1, A2])
33 | assert pt.allclose(expected, compute_gae(rewards, values, 0.99, 0.97))
34 |
35 |
36 | class TestFCPolicy():
37 | def test_scale(self):
38 | policy = FCPolicy(100, 1, pt.tensor(-10), pt.tensor(10))
39 | scaled = policy._scale(pt.tensor([-10.0, 0.0, 10.0]))
40 | expected = pt.tensor([0.0, 0.5, 1.0])
41 | assert pt.allclose(scaled, expected)
42 |
43 | def test_forward(self):
44 | policy = FCPolicy(100, 1, pt.tensor(-10), pt.tensor(10))
45 | states = pt.rand((20, 100))
46 | out = policy(states)
47 | assert out.shape == (20, 2)
48 | assert (out > 1.0).sum().item() == 40
49 |
50 | def test_predict(self):
51 | states, actions = pt.rand((20, 100)), pt.rand(20)
52 | policy = FCPolicy(100, 1, pt.tensor(-10), pt.tensor(10))
53 | logp, entropy = policy.predict(states, actions)
54 | assert logp.shape == (20,)
55 | assert entropy.shape == (20,)
56 | states, actions = pt.rand((20, 100)), pt.rand((20, 2))
57 | policy = FCPolicy(100, 2, pt.tensor(-10), pt.tensor(10))
58 | logp, entropy = policy.predict(states, actions)
59 | assert logp.shape == (20*2,)
60 | assert entropy.shape == (20*2,)
61 |
62 | def test_tracing(self):
63 | policy = FCPolicy(100, 1, pt.tensor(-10), pt.tensor(10))
64 | test_in = pt.rand((2, 100))
65 | out_ref = policy(test_in)
66 | trace = pt.jit.script(policy)
67 | dest = join("/tmp/traced_test_model.pt")
68 | trace.save(dest)
69 | assert isfile(dest)
70 | policy_tr = pt.jit.load(dest)
71 | out = policy_tr(test_in)
72 | assert pt.allclose(out_ref, out)
73 | remove(dest)
74 |
75 |
76 | def test_FCValue():
77 | value = FCValue(100)
78 | states = pt.rand((10, 100))
79 | out = value(states)
80 | assert out.shape == (10,)
81 |
--------------------------------------------------------------------------------
/drlfoam/execution/tests/test_local.py:
--------------------------------------------------------------------------------
1 |
2 | from os import makedirs, remove
3 | from os.path import join, isdir, isfile
4 | from shutil import copytree, rmtree
5 | from copy import deepcopy
6 | from pytest import fixture, raises
7 | import torch as pt
8 | from ..local import LocalBuffer
9 | from ...environment import RotatingCylinder2D
10 | from ...agent import FCPolicy
11 | from ...constants import TESTDATA_PATH, TESTCASE_PATH
12 |
13 |
14 | @fixture()
15 | def temp_training():
16 | training = join("/tmp", "test_training")
17 | makedirs(training, exist_ok=True)
18 | case = "rotatingCylinder2D"
19 | source = join(TESTCASE_PATH, case)
20 | dest = join(training, case)
21 | copytree(source, dest, dirs_exist_ok=True)
22 | env = RotatingCylinder2D()
23 | env.path = dest
24 | yield (training, env)
25 | rmtree(training)
26 |
27 |
28 | class TestLocalBuffer():
29 | def test_create_copies(self, temp_training):
30 | path, base_env = temp_training
31 | buffer = LocalBuffer(path, base_env, 2, 2)
32 | envs = buffer.envs
33 | assert isdir(join(path, "copy_0"))
34 | assert isdir(join(path, "copy_1"))
35 | assert envs[0].path == join(path, "copy_0")
36 | assert envs[1].path == join(path, "copy_1")
37 |
38 | def test_update_policy(self, temp_training):
39 | path, base_env = temp_training
40 | buffer = LocalBuffer(path, base_env, 1, 1)
41 | envs = buffer.envs
42 | remove(join(envs[0].path, envs[0].policy))
43 | policy = FCPolicy(1, 1, -1, 1)
44 | buffer.update_policy(pt.jit.script(policy))
45 | assert isfile(join(envs[0].path, envs[0].policy))
46 |
47 | def test_prepare_fill_reset_clean(self, temp_training):
48 | path, base_env = temp_training
49 | base_env.start_time = 0.0
50 | base_env.end_time = 0.015
51 | buffer = LocalBuffer(path, base_env, 2, 2)
52 | buffer.prepare()
53 | assert isfile(join(path, base_env.path, "log.blockMesh"))
54 | assert isfile(join(path, base_env.path, "trajectory.csv"))
55 | assert isdir(join(path, base_env.path, "postProcessing"))
56 | buffer.reset()
57 | assert not isfile(join(path, "copy_0", "trajectory.csv"))
58 | assert not isdir(join(path, "copy_0", "postProcessing"))
59 | buffer.fill()
60 | assert isfile(join(path, "copy_0", "trajectory.csv"))
61 | assert isfile(join(path, "copy_0", "log.pimpleFoam"))
62 | assert isdir(join(path, "copy_0", "postProcessing"))
63 | # implicit test of save_trajectory()
64 | assert isfile(join(path, "observations_0.pt"))
65 | assert buffer._n_fills == 1
66 | buffer.clean()
67 | assert not isfile(join(path, "copy_0", "log.blockMesh"))
68 |
69 | def test_timeout(self, temp_training):
70 | path, base_env = temp_training
71 | base_env.start_time = 0.0
72 | base_env.end_time = 0.015
73 | buffer = LocalBuffer(path, base_env, 2, 2, timeout=1)
74 | assert not isfile(join(path, base_env.path, "log.pimpleFoam.pre"))
75 |
--------------------------------------------------------------------------------
/examples/config_orig.yml:
--------------------------------------------------------------------------------
1 | # drlfoam Setup Configuration; any mandatory entries, which are not present here will be replaced with default values
2 |
3 | # general settings for the training
4 | training:
5 | executer: "local" # executer, either 'local' or 'slurm'
6 | simulation: "rotatingCylinder2D" # simulation environment either 'rotatingCylinder2D' or 'rotatingPinball2D'
7 | training_path: "test_training" # path to the directory in which the training should be executed
8 | n_runner: 2 # number of runners for parallel execution
9 | buffer_size: 4 # buffer size
10 | end_time: 8 # finish time of the simulation
11 | seed: 0 # seed value
12 | episodes: 20 # number of episodes to run the training
13 | checkpoint: null # start fresh or from a checkpoint.pt file null means no checkpoint provided
14 | timeout: 1e15 # execution time before a job gets killed, only relevant if executer is slurm
15 |
16 | # settings for the policy network
17 | policy_network:
18 | n_layers: 2 # number of hidden layers
19 | n_neurons: 64 # number of neurons per layer
20 | activation: "relu" # activation function
21 |
22 | # settings for training the policy network
23 | policy_training:
24 | epochs: 100 # max. number of epochs to run
25 | lr: 4e-4 # initial learning rate for the policy network
26 | clip: 0.1 # value for clipping the update of the policy network
27 | grad_norm: "inf" # clipping value for the gradient of the policy network
28 | kl_stop: 0.2 # value for KL-divergence criteria for stopping the training
29 |
30 | # settings for the value network
31 | value_network:
32 | n_layers: 2 # number of hidden layers
33 | n_neurons: 64 # number of neurons per layer
34 | activation: "relu" # activation function
35 |
36 | # settings for training the value network
37 | value_training:
38 | epochs: 100 # max. number of epochs to run
39 | lr: 5e-4 # initial learning rate for the value network
40 | clip: 0.1 # value for clipping the update of the value network
41 | grad_norm: "inf" # clipping value for the gradient of the value network
42 | mse_stop: 25.0 # value for MSE-divergence criteria for stopping the training
43 |
44 | # settings for the PPO hyperparameter
45 | ppo_settings:
46 | gamma: 0.99 # discount factor
47 | lambda: 0.97 # hyperparameter lambda for computing the GAE
48 | entropy_weight: 0.01 # value for weighing the entropy
49 |
--------------------------------------------------------------------------------
/drlfoam/execution/buffer.py:
--------------------------------------------------------------------------------
1 | """
2 | implements a base class for storing the buffer
3 | """
4 | import logging
5 | import torch as pt
6 |
7 | from copy import deepcopy
8 | from shutil import copytree
9 | from subprocess import Popen
10 | from typing import Tuple, List
11 | from os.path import join, exists
12 | from abc import ABC, abstractmethod
13 |
14 | from ..agent import FCPolicy
15 | from .manager import TaskManager
16 | from ..environment import Environment
17 |
18 | logger = logging.getLogger(__name__)
19 |
20 |
21 | class Buffer(ABC):
22 | def __init__(
23 | self,
24 | path: str,
25 | base_env: Environment,
26 | buffer_size: int,
27 | n_runners_max: int,
28 | keep_trajectories: bool,
29 | timeout: int,
30 | ):
31 | self._path = path
32 | self._base_env = base_env
33 | self._buffer_size = buffer_size
34 | self._n_runners_max = n_runners_max
35 | self._keep_trajectories = keep_trajectories
36 | self._timeout = timeout
37 | self._manager = TaskManager(self._n_runners_max)
38 | self._envs = None
39 | self._n_fills = 0
40 |
41 | @abstractmethod
42 | def prepare(self):
43 | pass
44 |
45 | @abstractmethod
46 | def fill(self):
47 | pass
48 |
49 | def create_copies(self):
50 | envs = []
51 | for i in range(self._buffer_size):
52 | dest = join(self._path, f"copy_{i}")
53 | if not exists(dest):
54 | copytree(self._base_env.path, dest, dirs_exist_ok=True)
55 | envs.append(deepcopy(self._base_env))
56 | envs[-1].path = dest
57 | envs[-1].end_time = envs[-1].end_time
58 | envs[-1].start_time = envs[-1].start_time
59 | envs[-1].seed = i
60 | self._envs = envs
61 |
62 | def update_policy(self, policy: FCPolicy):
63 | for env in self.envs:
64 | policy.save(join(env.path, env.policy))
65 | policy.save(join(self.base_env.path, self.base_env.policy))
66 |
67 | def reset(self):
68 | for env in self.envs:
69 | env.reset()
70 |
71 | def clean(self):
72 | for env in self.envs:
73 | proc = Popen([f"./{env.clean_script}"], cwd=env.path)
74 | proc.wait()
75 |
76 | def save_trajectories(self):
77 | pt.save(
78 | [env.observations for env in self.envs],
79 | join(self._path, f"observations_{self._n_fills}.pt")
80 | )
81 |
82 | @property
83 | def base_env(self) -> Environment:
84 | return self._base_env
85 |
86 | @property
87 | def envs(self):
88 | if self._envs is None:
89 | self.create_copies()
90 | return self._envs
91 |
92 | @property
93 | def observations(self) -> Tuple[List[pt.Tensor], List[pt.Tensor], List[pt.Tensor]]:
94 | states, actions, rewards = [], [], []
95 | for env in self.envs:
96 | obs = env.observations
97 | if all([key in obs for key in ("states", "actions", "rewards")]):
98 | states.append(obs["states"])
99 | actions.append(obs["actions"])
100 | rewards.append(obs["rewards"])
101 | else:
102 | logger.warning(f"Warning: environment {env.path} returned empty observations")
103 | return states, actions, rewards
104 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/system/controlDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object controlDict;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | libs ("${DRL_LIBBIN}/libAgentRotatingWallVelocity.so");
19 |
20 | application pimpleFoam;
21 |
22 | startFrom latestTime;
23 |
24 | startTime 0;
25 |
26 | stopAt endTime;
27 |
28 | endTime 4.0;
29 |
30 | deltaT 5.0e-4;
31 |
32 | writeControl runTime;
33 |
34 | writeInterval 1.0;
35 |
36 | purgeWrite 1;
37 |
38 | writeFormat ascii;
39 |
40 | writePrecision 8;
41 |
42 | timeFormat general;
43 |
44 | timePrecision 8;
45 |
46 | runTimeModifiable false;
47 |
48 | functions
49 | {
50 | forces
51 | {
52 | type forceCoeffs;
53 | libs (forces);
54 | executeControl runTime;
55 | executeInterval 0.01;
56 | writeControl runTime;
57 | writeInterval 0.01;
58 | timeStart 4.0;
59 | log false;
60 | patches
61 | (
62 | cylinder
63 | );
64 | coefficients (Cd Cl);
65 | rhoInf 1;
66 | rho rhoInf;
67 | CofR (0.2 0.2 0.005);
68 | liftDir (0 1 0);
69 | dragDir (1 0 0);
70 | magUInf 1.0;
71 | lRef 0.1;
72 | Aref 0.001;
73 | }
74 |
75 | probes
76 | {
77 | type probes;
78 | libs (sampling);
79 |
80 | // Name of the directory for probe data
81 | name probes;
82 |
83 | // Write at same frequency as fields
84 | executeControl runTime;
85 | executeInterval 0.01;
86 | writeControl runTime;
87 | writeInterval 0.01;
88 | timeStart 4.0;
89 |
90 | // Fields to be probed
91 | fields (p);
92 |
93 | // Optional: do not recalculate cells if mesh moves
94 | fixedLocations true;
95 |
96 | // Optional: interpolation scheme to use (default is cell)
97 | interpolationScheme cell;
98 |
99 | probeLocations
100 | (
101 | (0.3 0.15 0.005)
102 | (0.3 0.2 0.005)
103 | (0.3 0.25 0.005)
104 | (0.4 0.15 0.005)
105 | (0.4 0.2 0.005)
106 | (0.4 0.25 0.005)
107 | (0.5 0.15 0.005)
108 | (0.5 0.2 0.005)
109 | (0.5 0.25 0.005)
110 | (0.6 0.15 0.005)
111 | (0.6 0.2 0.005)
112 | (0.6 0.25 0.005)
113 | );
114 |
115 | // Optional: filter out points that haven't been found. Default
116 | // is to include them (with value -VGREAT)
117 | includeOutOfBounds false;
118 | }
119 |
120 | }
121 |
122 | // ************************************************************************* //
123 |
--------------------------------------------------------------------------------
/drlfoam/utils.py:
--------------------------------------------------------------------------------
1 | """
2 | helper functions
3 | """
4 | import sys
5 | import logging
6 | import fileinput
7 |
8 | from glob import glob
9 | from typing import Any, Union
10 | from os.path import isdir, isfile, basename, join
11 |
12 | logger = logging.getLogger(__name__)
13 |
14 |
15 | def get_time_folders(path: str) -> list:
16 | def is_float(element: Any) -> bool:
17 | # taken from:
18 | # https://stackoverflow.com/questions/736043/checking-if-a-string-can-be-converted-to-float-in-python
19 | try:
20 | float(element)
21 | return True
22 | except ValueError:
23 | return False
24 | folders = [basename(x) for x in glob(join(path, "[0-9]*"))
25 | if isdir(x) and is_float(basename(x))]
26 | return folders
27 |
28 |
29 | def get_latest_time(path: str) -> str:
30 | folders = get_time_folders(path)
31 | if not folders:
32 | if isdir(join(path, "0.org")):
33 | return "0.org"
34 | else:
35 | raise ValueError(f"Could not find time folder in {path}")
36 | return sorted(folders, key=float)[-1]
37 |
38 |
39 | def fetch_line_from_file(path: str, keyword: str) -> str:
40 | with open(path) as f:
41 | lines = []
42 | for line in f.readlines():
43 | if keyword in line:
44 | lines.append(line)
45 | return lines if len(lines) > 1 else lines[0]
46 |
47 |
48 | def replace_line_in_file(path: str, keyword: str, new: str) -> None:
49 | """Keyword-based replacement of one or more lines in a file.
50 |
51 | :param path: file location
52 | :type path: str
53 | :param keyword: keyword based on which lines are selected
54 | :type keyword: str
55 | :param new: the new line replacing the old one
56 | :type new: str
57 | """
58 | new = new + "\n" if not new.endswith("\n") else new
59 | fin = fileinput.input(path, inplace=True)
60 | for line in fin:
61 | if keyword in line:
62 | line = new
63 | sys.stdout.write(line)
64 | fin.close()
65 |
66 |
67 | def replace_line_latest(path: str, filename: str, keyword: str, new: str,
68 | processor: bool = True) -> None:
69 | search_path = join(path, "processor0") if processor else path
70 | latest_time = get_latest_time(search_path)
71 | if processor:
72 | for p in glob(join(path, "processor*")):
73 | replace_line_in_file(
74 | join(p, latest_time, filename), keyword, new
75 | )
76 | else:
77 | replace_line_in_file(
78 | join(path, latest_time, filename), keyword, new
79 | )
80 |
81 |
82 | def check_path(path: str) -> None:
83 | if not isdir(path):
84 | raise ValueError(f"Could not find path {path}")
85 |
86 |
87 | def check_file(file_path: str) -> None:
88 | if not isfile(file_path):
89 | raise ValueError(f"Could not find file {file_path}")
90 |
91 |
92 | def check_pos_int(value: int, name: str, with_zero=False) -> None:
93 | message = f"Argument {name} must be a positive integer; got {value}"
94 | if not isinstance(value, int):
95 | raise ValueError(message)
96 | lb = 0 if with_zero else 1
97 | if value < lb:
98 | raise ValueError(message)
99 |
100 |
101 | def check_pos_float(value: float, name: str, with_zero=False) -> None:
102 | message = f"Argument {name} must be a positive float; got {value}"
103 | if not isinstance(value, (float, int)):
104 | raise ValueError(message)
105 | if with_zero and value < 0.0:
106 | raise ValueError(message)
107 | if not with_zero and value <= 0.0:
108 | raise ValueError(message)
109 |
--------------------------------------------------------------------------------
/drlfoam/execution/tests/test_manager.py:
--------------------------------------------------------------------------------
1 |
2 | from os import remove
3 | from os.path import join, isfile
4 | from queue import Queue
5 | from pathlib import Path
6 | from time import sleep
7 | from ..manager import Runner, TaskManager
8 |
9 |
10 | class TestRunner():
11 | def test_run_no_args(self):
12 | queue = Queue()
13 | path = join("/tmp", "runner_testfile_no_args.txt")
14 | def create_file():
15 | if isfile(path):
16 | remove(path)
17 | Path(path).touch()
18 | queue.put((create_file, [], {}))
19 | runner = Runner(queue, "Runner 0")
20 | runner.run()
21 | runner.join()
22 | assert isfile(path)
23 | remove(path)
24 |
25 | def test_run_args(self):
26 | queue = Queue()
27 | def create_file(path):
28 | if isfile(path):
29 | remove(path)
30 | Path(path).touch()
31 | path = join("/tmp", "runner_testfile_args.txt")
32 | queue.put((create_file, [path], {}))
33 | runner = Runner(queue, "Runner 0")
34 | runner.run()
35 | runner.join()
36 | assert isfile(path)
37 | remove(path)
38 |
39 | def test_run_kwargs(self):
40 | queue = Queue()
41 | def create_file(path):
42 | if isfile(path):
43 | remove(path)
44 | Path(path).touch()
45 | path = join("/tmp", "runner_testfile_kwargs.txt")
46 | queue.put((create_file, [], {"path": path}))
47 | runner = Runner(queue, "Runner 0")
48 | runner.run()
49 | runner.join()
50 | assert isfile(path)
51 | remove(path)
52 |
53 | def test_run_args_kwargs(self):
54 | queue = Queue()
55 | def create_file(path_1, path_2):
56 | for p in (path_1, path_2):
57 | if isfile(p):
58 | remove(p)
59 | Path(p).touch()
60 | path_1 = join("/tmp", "runner_testfile_1.txt")
61 | path_2 = join("/tmp", "runner_testfile_2.txt")
62 | queue.put((create_file, [path_1], {"path_2": path_2}))
63 | runner = Runner(queue, "Runner 0")
64 | runner.run()
65 | runner.join()
66 | for p in (path_1, path_2):
67 | assert isfile(p)
68 | remove(p)
69 |
70 |
71 | class TestTaskManager():
72 | def test_add_and_run_no_args(self):
73 | manager = TaskManager(1)
74 | path = join("/tmp", "runner_testfile_no_args.txt")
75 | def create_file():
76 | if isfile(path):
77 | remove(path)
78 | Path(path).touch()
79 | manager.add(create_file)
80 | assert manager.qsize() == 1
81 | manager.run()
82 | assert isfile(path)
83 | remove(path)
84 |
85 |
86 | def test_add_and_run_args(self):
87 | manager = TaskManager(1)
88 | def create_file(path_1, path_2):
89 | for p in (path_1, path_2):
90 | if isfile(p):
91 | remove(p)
92 | Path(p).touch()
93 | path_1 = join("/tmp", "runner_testfile_1.txt")
94 | path_2 = join("/tmp", "runner_testfile_2.txt")
95 | manager.add(create_file, path_1, path_2)
96 | manager.run()
97 | for p in (path_1, path_2):
98 | assert isfile(p)
99 | remove(p)
100 |
101 | def test_add_and_run_args_kwargs(self):
102 | manager = TaskManager(1)
103 | def create_file(path_1, path_2):
104 | for p in (path_1, path_2):
105 | if isfile(p):
106 | remove(p)
107 | Path(p).touch()
108 | path_1 = join("/tmp", "runner_testfile_1.txt")
109 | path_2 = join("/tmp", "runner_testfile_2.txt")
110 | manager.add(create_file, path_1, path_2=path_2)
111 | manager.run()
112 | for p in (path_1, path_2):
113 | assert isfile(p)
114 | remove(p)
--------------------------------------------------------------------------------
/examples/run_training.py:
--------------------------------------------------------------------------------
1 | """
2 | Example training script.
3 | """
4 | import sys
5 | import logging
6 |
7 | from time import time
8 | from os import environ
9 | from os.path import join
10 |
11 | BASE_PATH = environ.get("DRL_BASE", "")
12 | sys.path.insert(0, BASE_PATH)
13 |
14 | from drlfoam.agent import PPOAgent
15 | # from examples.debug import DebugTraining
16 | from drlfoam.execution.setup import ParseSetup
17 | from examples.create_dummy_policy import create_dummy_policy
18 | from drlfoam.execution import LocalBuffer, SlurmBuffer, SlurmConfig
19 |
20 | logging.basicConfig(level=logging.INFO)
21 | logger = logging.getLogger(__name__)
22 |
23 |
24 | def print_statistics(actions, rewards):
25 | rt = [r.mean().item() for r in rewards]
26 | at_mean = [a.mean().item() for a in actions]
27 | at_std = [a.std().item() for a in actions]
28 | reward_msg = f"Reward mean/min/max: {sum(rt) / len(rt):2.4f}/{min(rt):2.4f}/{max(rt):2.4f}"
29 | action_mean_msg = f"Mean action mean/min/max: {sum(at_mean) / len(at_mean):2.4f}/{min(at_mean):2.4f}/{max(at_mean):2.4f}"
30 | action_std_msg = f"Std. action mean/min/max: {sum(at_std) / len(at_std):2.4f}/{min(at_std):2.4f}/{max(at_std):2.4f}"
31 | logger.info("\n".join((reward_msg, action_mean_msg, action_std_msg)))
32 |
33 |
34 | def main():
35 | # if we want to debug from an IDE, we need to set all required paths first
36 | # if DEBUG:
37 | # debug = DebugTraining()
38 |
39 | # load the setup
40 | setup = ParseSetup(BASE_PATH)
41 | training_path = str(join(BASE_PATH, setup.buffer["training_path"]))
42 | setup.env.path = join(training_path, "base")
43 |
44 | # add the path to openfoam to the Allrun scripts
45 | # if DEBUG:
46 | # debug.set_openfoam_bashrc(training_path=setup.env.path)
47 |
48 | # create buffer
49 | if setup.training["executer"] == "local":
50 | buffer = LocalBuffer(training_path, setup.env, setup.buffer["buffer_size"], setup.buffer["n_runner"])
51 | elif setup.training["executer"] == "slurm":
52 | # Typical Slurm configs for TU Dresden cluster
53 | config = SlurmConfig(
54 | n_tasks_per_node=setup.env.mpi_ranks, n_nodes=1, time="03:00:00", job_name="drl_train",
55 | modules=["development/24.04 GCC/12.3.0", "OpenMPI/4.1.5", "OpenFOAM/v2312"],
56 | commands_pre=["source $FOAM_BASH", f"source {BASE_PATH}/setup-env"]
57 | )
58 | buffer = SlurmBuffer(training_path, setup.env, setup.buffer["buffer_size"],
59 | setup.buffer["n_runner"], config, timeout=setup.buffer["timeout"])
60 | else:
61 | raise ValueError(
62 | f"Unknown executer {setup.training['executer']}; available options are 'local' and 'slurm'.")
63 |
64 | # create PPO agent
65 | agent = PPOAgent(setup.env.n_states, setup.env.n_actions, -setup.env.action_bounds, setup.env.action_bounds,
66 | **setup.agent)
67 |
68 | # load checkpoint if provided
69 | if setup.training["checkpoint"] is not None:
70 | logging.info(f"Loading checkpoint from file {setup.training['checkpoint']}")
71 | agent.load_state(join(training_path, setup.training["checkpoint"]))
72 | starting_episode = agent.history["episode"][-1] + 1
73 | buffer._n_fills = starting_episode
74 | else:
75 | starting_episode = 0
76 |
77 | # create fresh random policy and execute the base case
78 | create_dummy_policy(setup.env.n_states, setup.env.n_actions, setup.env.path, setup.env.action_bounds)
79 |
80 | # execute the base simulation
81 | buffer.prepare()
82 |
83 | buffer.base_env.start_time = buffer.base_env.end_time
84 | buffer.base_env.end_time = setup.training["end_time"]
85 | buffer.reset()
86 |
87 | # begin training
88 | start_time = time()
89 | for e in range(starting_episode, setup.training["episodes"]):
90 | logger.info(f"Start of episode {e}")
91 | buffer.fill()
92 | states, actions, rewards = buffer.observations
93 | print_statistics(actions, rewards)
94 | agent.update(states, actions, rewards)
95 | agent.save_state(join(training_path, f"checkpoint_{e}.pt"))
96 | current_policy = agent.trace_policy()
97 | buffer.update_policy(current_policy)
98 | current_policy.save(join(training_path, f"policy_trace_{e}.pt"))
99 | if not e == setup.training["episodes"] - 1:
100 | buffer.reset()
101 | logger.info(f"Training time (s): {time() - start_time}")
102 |
103 |
104 | if __name__ == "__main__":
105 | # option for running the training in IDE, e.g. in debugger
106 | # DEBUG = False
107 | main()
108 |
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingCylinder2D/system/blockMeshDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | object blockMeshDict;
14 | }
15 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
16 |
17 | lengthX 2.2;
18 | lengthY 0.41;
19 | cylinderX 0.2;
20 | cylinderY 0.2;
21 | radius 0.05;
22 | xPartOfR #eval{ sqrt(2)/2 * $radius};
23 | blocks 25;
24 | blocks2 #eval{ round((($lengthX-$cylinderX * 2)) / ($cylinderX * 2) * $blocks)};
25 |
26 | thickness 0.01;
27 |
28 |
29 | scale 1;
30 |
31 | vertices #codeStream
32 | {
33 | codeInclude
34 | #{
35 | #include "pointField.H"
36 | #};
37 |
38 | code
39 | #{
40 | pointField points({
41 | /* 0*/ {0, 0, 0},
42 | /* 1*/ {$cylinderX * 2, 0, 0},
43 | /* 2*/ {$lengthX, 0, 0},
44 | /* 3*/ {$lengthX, $lengthY, 0},
45 | /* 4*/ {$cylinderX * 2, $lengthY, 0},
46 | /* 5*/ {0, $lengthY, 0},
47 | /* 6*/ {$cylinderX - $xPartOfR, $cylinderY - $xPartOfR, 0},
48 | /* 7*/ {$cylinderX + $xPartOfR, $cylinderY - $xPartOfR, 0},
49 | /* 8*/ {$cylinderX - $xPartOfR, $cylinderY + $xPartOfR, 0},
50 | /* 9*/ {$cylinderX + $xPartOfR, $cylinderY + $xPartOfR, 0}
51 | });
52 |
53 | // Duplicate z points for thickness
54 | const label sz = points.size();
55 | points.resize(2*sz);
56 | for (label i = 0; i < sz; ++i)
57 | {
58 | const point& pt = points[i];
59 | points[i + sz] = point(pt.x(), pt.y(), $thickness);
60 | }
61 |
62 | os << points;
63 | #};
64 | };
65 |
66 | blocks
67 | (
68 | hex (0 1 7 6 10 11 17 16) ($blocks $blocks 1) simpleGrading (1 0.25 1)
69 | hex (1 4 9 7 11 14 19 17) ($blocks $blocks 1) simpleGrading (1 0.25 1)
70 | hex (4 5 8 9 14 15 18 19) ($blocks $blocks 1) simpleGrading (1 0.25 1)
71 | hex (5 0 6 8 15 10 16 18) ($blocks $blocks 1) simpleGrading (1 0.25 1)
72 | hex (1 2 3 4 11 12 13 14) ($blocks2 $blocks 1) simpleGrading (4 1 1)
73 | );
74 |
75 | edges
76 | (
77 | arc 6 7 ($cylinderX #eval{$cylinderY-$radius} 0)
78 | arc 7 9 (#eval{$cylinderX+$radius} $cylinderY 0)
79 | arc 9 8 ($cylinderX #eval{$cylinderY+$radius} 0)
80 | arc 8 6 (#eval{$cylinderX-$radius} $cylinderY 0)
81 | arc 16 17 ($cylinderX #eval{$cylinderY-$radius} $thickness)
82 | arc 17 19 (#eval{$cylinderX+$radius} $cylinderY $thickness)
83 | arc 19 18 ($cylinderX #eval{$cylinderY+$radius} $thickness)
84 | arc 18 16 (#eval{$cylinderX-$radius} $cylinderY $thickness)
85 | );
86 |
87 | boundary
88 | (
89 | top
90 | {
91 | type patch;
92 | faces
93 | (
94 | (4 5 15 14)
95 | (3 4 14 13)
96 | );
97 | }
98 | bottom
99 | {
100 | type patch;
101 | faces
102 | (
103 | (0 1 11 10)
104 | (1 2 12 11)
105 | );
106 | }
107 | inlet
108 | {
109 | type patch;
110 | faces
111 | (
112 | (5 0 10 15)
113 | );
114 | }
115 | outlet
116 | {
117 | type patch;
118 | faces
119 | (
120 | (2 3 13 12)
121 | );
122 | }
123 | back
124 | {
125 | type empty;
126 | faces
127 | (
128 | (0 1 7 6)
129 | (1 4 9 7)
130 | (4 5 8 9)
131 | (5 0 6 8)
132 | (1 2 3 4)
133 | );
134 | }
135 | front
136 | {
137 | type empty;
138 | faces
139 | (
140 | (10 11 17 16)
141 | (11 14 19 17)
142 | (14 15 18 19)
143 | (15 10 16 18)
144 | (11 12 13 14)
145 | );
146 | }
147 | cylinder
148 | {
149 | type patch;
150 | faces
151 | (
152 | (6 7 17 16)
153 | (7 9 19 17)
154 | (9 8 18 19)
155 | (8 6 16 18)
156 | );
157 | }
158 | );
159 |
160 | mergePatchPairs
161 | (
162 | );
163 |
164 | // ************************************************************************* //
165 |
--------------------------------------------------------------------------------
/drlfoam/environment/tests/test_rotating_cylinder.py:
--------------------------------------------------------------------------------
1 |
2 | from os.path import join, exists
3 | from shutil import copytree, rmtree
4 | from pytest import raises, fixture
5 | from ..rotating_cylinder import (RotatingCylinder2D, _parse_forces,
6 | _parse_trajectory, _parse_probes)
7 | from ...constants import TESTDATA_PATH
8 | from ...utils import fetch_line_from_file
9 |
10 |
11 | @fixture()
12 | def temp_case():
13 | case = "rotatingCylinder2D"
14 | source = join(TESTDATA_PATH, case)
15 | dest = join("/tmp", case)
16 | yield copytree(source, dest, dirs_exist_ok=True)
17 | rmtree(dest)
18 |
19 |
20 | def test_parse_forces(temp_case):
21 | path = join(temp_case, "postProcessing", "forces", "0", "coefficient.dat")
22 | forces = _parse_forces(path)
23 | assert all(forces.columns == ["t", "cd", "cl"])
24 | assert len(forces) == 1
25 |
26 |
27 | def test_parse_probes(temp_case):
28 | path = join(temp_case, "postProcessing", "probes", "0", "p")
29 | n_probes = 12
30 | probes = _parse_probes(path, n_probes)
31 | assert len(probes.columns) == n_probes + 1
32 | assert len(probes) == 1
33 |
34 |
35 | def test_parse_trajectory(temp_case):
36 | path = join(temp_case, "trajectory.csv")
37 | tr = _parse_trajectory(path)
38 | assert all(tr.columns == ["t", "omega", "alpha", "beta"])
39 | assert len(tr) == 1
40 |
41 |
42 | class TestRotatingCylinder2D(object):
43 | def test_common(self, temp_case):
44 | env = RotatingCylinder2D()
45 | assert True
46 |
47 | def test_start_time(self, temp_case):
48 | env = RotatingCylinder2D()
49 | env.initialized = True
50 | env.path = temp_case
51 | env.start_time = 2.0
52 | lines = fetch_line_from_file(
53 | join(env.path, "system", "controlDict"),
54 | "timeStart"
55 | )
56 | assert all(["2.0" in line for line in lines])
57 |
58 | def test_end_time(self, temp_case):
59 | env = RotatingCylinder2D()
60 | env.initialized = True
61 | env.path = temp_case
62 | env.end_time = 8.0
63 | line = fetch_line_from_file(
64 | join(env.path, "system", "controlDict"),
65 | "endTime "
66 | )
67 | assert "8.0" in line
68 |
69 | def test_control_interval(self, temp_case):
70 | env = RotatingCylinder2D()
71 | env.initialized = True
72 | env.path = temp_case
73 | env.control_interval = 0.001
74 | lines = fetch_line_from_file(
75 | join(env.path, "system", "controlDict"),
76 | "executeInterval"
77 | )
78 | assert all(["0.001" in line for line in lines])
79 | lines = fetch_line_from_file(
80 | join(env.path, "system", "controlDict"),
81 | "writeInterval"
82 | )
83 | assert all(["0.001" in line for line in lines])
84 |
85 | def test_action_bounds(self, temp_case):
86 | env = RotatingCylinder2D()
87 | env.initialized = True
88 | env.path = temp_case
89 | env.action_bounds = 10.0
90 | line = fetch_line_from_file(
91 | join(env.path, "processor0", "4", "U"),
92 | "absOmegaMax"
93 | )
94 | assert "10.0" in line
95 |
96 | def test_seed(self, temp_case):
97 | env = RotatingCylinder2D()
98 | env.initialized = True
99 | env.path = temp_case
100 | env.seed = 10
101 | line = fetch_line_from_file(
102 | join(env.path, "processor0", "4", "U"),
103 | "seed"
104 | )
105 | assert "10" in line
106 |
107 | def test_policy(self, temp_case):
108 | env = RotatingCylinder2D()
109 | env.initialized = True
110 | env.path = temp_case
111 | env.policy = "model.pt"
112 | line = fetch_line_from_file(
113 | join(env.path, "processor0", "4", "U"),
114 | "policy"
115 | )
116 | assert "model.pt" in line
117 |
118 | def test_train(self, temp_case):
119 | env = RotatingCylinder2D()
120 | env.initialized = True
121 | env.path = temp_case
122 | env.train = False
123 | line = fetch_line_from_file(
124 | join(env.path, "processor0", "4", "U"),
125 | "train"
126 | )
127 | assert "false" in line
128 |
129 | def test_reset(self, temp_case):
130 | env = RotatingCylinder2D()
131 | env.initialized = True
132 | env.path = temp_case
133 | env.start_time = 4.0
134 | env.reset()
135 | assert not exists(join(env.path, "log.pimpleFoam"))
136 | assert not exists(join(env.path, "trajectory.csv"))
137 | assert not exists(join(env.path, "postProcessing"))
138 |
139 | def test_observations(self, temp_case):
140 | env = RotatingCylinder2D()
141 | env.initialized = True
142 | env.path = temp_case
143 | obs = env.observations
144 | assert len(obs.keys()) == 7
145 | assert all([obs[key].shape[0] == 1 for key in obs])
146 |
--------------------------------------------------------------------------------
/drlfoam/environment/environment.py:
--------------------------------------------------------------------------------
1 | """Base class for all environments.
2 |
3 | The base class provides a common interface for all derived environments
4 | and implements shared functionality. New environments should be derived
5 | from this class.
6 | """
7 | from abc import ABC, abstractmethod
8 | from os.path import join
9 | from typing import Union, Tuple
10 | from torch import Tensor
11 |
12 | from ..utils import check_path, check_file, check_pos_int
13 |
14 |
15 | class Environment(ABC):
16 | def __init__(self, path: str, initializer_script: str, run_script: str,
17 | clean_script: str, mpi_ranks: int, n_states: int,
18 | n_actions: int):
19 | """
20 | implements a base class for environments
21 |
22 | :param path: path to the current test case inside the 'openfoam' directory
23 | :type path: str
24 | :param initializer_script: name of the script which should be executed for the base case
25 | :type initializer_script: str
26 | :param run_script: name of the script which should be executed for the simulations
27 | :type run_script: str
28 | :param clean_script: name of the script which should be executed for resetting the simulations
29 | :type clean_script: str
30 | :param mpi_ranks: number of MPI ranks for executing the simulation
31 | :type mpi_ranks: int
32 | :param n_states: number of states
33 | :type n_states: int
34 | :param n_actions: number of actions
35 | :type n_actions: int
36 | """
37 | self.path = path
38 | self.initializer_script = initializer_script
39 | self.run_script = run_script
40 | self.clean_script = clean_script
41 | self.mpi_ranks = mpi_ranks
42 | self.n_states = n_states
43 | self.n_actions = n_actions
44 | self._initialized = False
45 | self._start_time = None
46 | self._end_time = None
47 | self._control_interval = None
48 | self._action_bounds = None
49 | self._seed = None
50 | self._policy = None
51 | self._train = None
52 | self._observations = None
53 |
54 | @abstractmethod
55 | def reset(self):
56 | pass
57 |
58 | @property
59 | def path(self) -> str:
60 | return self._path
61 |
62 | @path.setter
63 | def path(self, value: str):
64 | check_path(value)
65 | self._path = value
66 |
67 | @property
68 | def initializer_script(self) -> str:
69 | return self._initializer_script
70 |
71 | @initializer_script.setter
72 | def initializer_script(self, value: str):
73 | check_file(join(self.path, value))
74 | self._initializer_script = value
75 |
76 | @property
77 | def run_script(self) -> str:
78 | return self._run_script
79 |
80 | @run_script.setter
81 | def run_script(self, value: str):
82 | check_file(join(self.path, value))
83 | self._run_script = value
84 |
85 | @property
86 | def clean_script(self) -> str:
87 | return self._clean_script
88 |
89 | @clean_script.setter
90 | def clean_script(self, value: str):
91 | check_file(join(self.path, value))
92 | self._clean_script = value
93 |
94 | @property
95 | def mpi_ranks(self) -> int:
96 | return self._mpi_ranks
97 |
98 | @mpi_ranks.setter
99 | def mpi_ranks(self, value: int):
100 | check_pos_int(value, "mpi_ranks")
101 | self._mpi_ranks = value
102 |
103 | @property
104 | def n_states(self) -> int:
105 | return self._n_states
106 |
107 | @n_states.setter
108 | def n_states(self, value: int):
109 | check_pos_int(value, "n_states")
110 | self._n_states = value
111 |
112 | @property
113 | def n_actions(self) -> int:
114 | return self._n_actions
115 |
116 | @n_actions.setter
117 | def n_actions(self, value: int):
118 | check_pos_int(value, "n_actions")
119 | self._n_actions = value
120 |
121 | @property
122 | def initialized(self):
123 | return self._initialized
124 |
125 | @initialized.setter
126 | def initialized(self, _):
127 | self._initialized = True
128 |
129 | @property
130 | @abstractmethod
131 | def start_time(self) -> float:
132 | pass
133 |
134 | @property
135 | @abstractmethod
136 | def end_time(self) -> float:
137 | pass
138 |
139 | @property
140 | @abstractmethod
141 | def control_interval(self) -> int:
142 | pass
143 |
144 | @property
145 | @abstractmethod
146 | def action_bounds(self) -> Union[Tensor, float]:
147 | pass
148 |
149 | @property
150 | @abstractmethod
151 | def seed(self) -> int:
152 | pass
153 |
154 | @property
155 | @abstractmethod
156 | def policy(self) -> str:
157 | pass
158 |
159 | @property
160 | @abstractmethod
161 | def train(self) -> bool:
162 | pass
163 |
164 | @property
165 | @abstractmethod
166 | def observations(self) -> Tuple[Tensor]:
167 | pass
168 |
169 | @start_time.setter
170 | def start_time(self, value):
171 | self._start_time = value
172 |
173 | @end_time.setter
174 | def end_time(self, value):
175 | self._end_time = value
176 |
177 | @seed.setter
178 | def seed(self, value):
179 | self._seed = value
--------------------------------------------------------------------------------
/openfoam/test_cases/rotatingPinball2D/system/controlDict:
--------------------------------------------------------------------------------
1 | /*--------------------------------*- C++ -*----------------------------------*\
2 | | ========= | |
3 | | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4 | | \\ / O peration | Version: v1912 |
5 | | \\ / A nd | Website: www.openfoam.com |
6 | | \\/ M anipulation | |
7 | \*---------------------------------------------------------------------------*/
8 | FoamFile
9 | {
10 | version 2.0;
11 | format ascii;
12 | class dictionary;
13 | location "system";
14 | object controlDict;
15 | }
16 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
17 |
18 | libs ("${DRL_LIBBIN}/libPinballRotatingWallVelocity.so");
19 |
20 | application pimpleFoam;
21 |
22 | startFrom latestTime;
23 |
24 | startTime 0;
25 |
26 | stopAt endTime;
27 |
28 | endTime 200.0;
29 |
30 | deltaT 1.0e-2;
31 |
32 | writeControl runTime;
33 |
34 | writeInterval 0.5;
35 |
36 | purgeWrite 1;
37 |
38 | writeFormat ascii;
39 |
40 | writePrecision 8;
41 |
42 | timeFormat general;
43 |
44 | timePrecision 8;
45 |
46 | runTimeModifiable false;
47 |
48 | functions
49 | {
50 | forces
51 | {
52 | type forceCoeffs;
53 | libs (forces);
54 | executeControl runTime;
55 | executeInterval 0.5;
56 | writeControl runTime;
57 | writeInterval 0.5;
58 | timeStart 200;
59 | log yes;
60 | writeFields yes;
61 |
62 | patches
63 | (
64 | cylinders
65 | );
66 | coefficients (Cd Cl);
67 | rhoInf 1;
68 | rho rhoInf;
69 | CofR (-0.433 0.0 0.0);
70 | liftDir (0 1 0);
71 | dragDir (1 0 0);
72 | magUInf 1.0;
73 | lRef 2.5;
74 | Aref 0.05;
75 | }
76 |
77 | field_cylinder_a
78 | {
79 | type surfaceFieldValue;
80 | libs (fieldFunctionObjects);
81 | fields (forceCoeff);
82 | executeControl runTime;
83 | executeInterval 0.5;
84 | writeControl runTime;
85 | writeInterval 0.5;
86 | timeStart 200;
87 | operation sum;
88 | regionType faceZone;
89 | name faceZone_a;
90 | weightField none;
91 | writeFields yes;
92 | surfaceFormat raw;
93 | }
94 |
95 | field_cylinder_b
96 | {
97 | type surfaceFieldValue;
98 | libs (fieldFunctionObjects);
99 | fields (forceCoeff);
100 | executeControl runTime;
101 | executeInterval 0.5;
102 | writeControl runTime;
103 | writeInterval 0.5;
104 | timeStart 200;
105 | operation sum;
106 | regionType faceZone;
107 | name faceZone_b;
108 | weightField none;
109 | writeFields yes;
110 | surfaceFormat raw;
111 | }
112 |
113 | field_cylinder_c
114 | {
115 |
116 | type surfaceFieldValue;
117 | libs (fieldFunctionObjects);
118 | fields (forceCoeff);
119 | executeControl runTime;
120 | executeInterval 0.5;
121 | writeControl runTime;
122 | writeInterval 0.5;
123 | timeStart 200;
124 | operation sum;
125 | regionType faceZone;
126 | name faceZone_c;
127 | weightField none;
128 | writeFields yes;
129 | surfaceFormat raw;
130 | }
131 |
132 | probes
133 | {
134 | type probes;
135 | libs (sampling);
136 |
137 | // Name of the directory for probe data
138 | name probes;
139 |
140 | // Write at same frequency as fields
141 | executeControl runTime;
142 | executeInterval 0.5;
143 | writeControl runTime;
144 | writeInterval 0.5;
145 | timeStart 200;
146 |
147 | // Fields to be probed
148 | fields (p);
149 |
150 | // Optional: do not recalculate cells if mesh moves
151 | fixedLocations true;
152 |
153 | // Optional: interpolation scheme to use (default is cell)
154 | interpolationScheme cell;
155 |
156 | probeLocations
157 | (
158 | (-0.55 0.75 0.005)
159 | (-0.55 -0.75 0.005)
160 | (0.0 1.33 0.005)
161 | (0.0 -1.33 0.005)
162 | (0.45 1.1 0.005)
163 | (0.45 -1.1 0.005)
164 | (1.35 1.55 0.005)
165 | (1.35 -1.55 0.005)
166 | (1.35 0.8 0.005)
167 | (1.35 -0.8 0.005)
168 | (2.35 1.55 0.005)
169 | (2.35 -1.55 0.005)
170 | (2.35 0.8 0.005)
171 | (2.35 -0.8 0.005)
172 | );
173 |
174 | // Optional: filter out points that haven't been found. Default
175 | // is to include them (with value -VGREAT)
176 | includeOutOfBounds false;
177 | }
178 |
179 | }
180 |
181 | // ************************************************************************* //
182 |
--------------------------------------------------------------------------------
/drlfoam/execution/tests/test_slurm.py:
--------------------------------------------------------------------------------
1 |
2 | from os import remove
3 | from os.path import join, isfile, isdir
4 | from re import findall
5 | from subprocess import Popen, PIPE
6 | from shutil import which
7 | import pytest
8 | from .test_local import temp_training
9 | from ..slurm import (SlurmBuffer, SlurmConfig, submit_job,
10 | get_job_status, submit_and_wait)
11 |
12 |
13 | slurm_available = pytest.mark.skipif(
14 | which("sbatch") is None, reason="Slurm workload manager not available"
15 | )
16 |
17 |
18 | @slurm_available
19 | def test_submit_job():
20 | config = SlurmConfig(
21 | commands=["sleep 1"], job_name="testjob", n_tasks=1, mem_per_cpu=1
22 | )
23 | path = join("/tmp", "testjob.sh")
24 | config.write(path)
25 | job_id = submit_job(path)
26 | assert isinstance(job_id, int)
27 | if isfile(path):
28 | remove(path)
29 |
30 |
31 | @slurm_available
32 | def test_get_job_status():
33 | config = SlurmConfig(
34 | commands=["sleep 1"], job_name="testjob", n_tasks=1, mem_per_cpu=1
35 | )
36 | path = join("/tmp", "testjob.sh")
37 | config.write(path)
38 | job_id = submit_job(path)
39 | status = get_job_status(job_id)
40 | assert status in ["R", "CF", "PD"]
41 | if isfile(path):
42 | remove(path)
43 | with pytest.raises(Exception):
44 | get_job_status(0)
45 |
46 |
47 | @slurm_available
48 | def test_submit_and_wait():
49 | config = SlurmConfig(
50 | commands=["sleep 2"], job_name="testjob", n_tasks=1, mem_per_cpu=1
51 | )
52 | path = join("/tmp", "testjob.sh")
53 | config.write(path)
54 | submit_and_wait(path, 1)
55 | assert True
56 |
57 |
58 | class TestSlurmConfig():
59 | def test_empty_write(self):
60 | config = SlurmConfig()
61 | path = join("/tmp", "emptyjob.sh")
62 | config.write(path)
63 | assert isfile(path)
64 | with open(path, "r") as job:
65 | content = job.read()
66 | assert "bash" in content
67 | remove(path)
68 |
69 | def test_write_options(self):
70 | config = SlurmConfig(
71 | job_name="testjob",
72 | n_tasks=8,
73 | mail_user="name@mail.com",
74 | n_nodes=1,
75 | time="120:00:00",
76 | partition="debug"
77 | )
78 | path = join("/tmp", "testjob.sh")
79 | config.write(path)
80 | assert isfile(path)
81 | with open(path, "r") as job:
82 | content = job.read()
83 | assert "testjob" in content
84 | assert "name@mail.com" in content
85 | assert "120:00:00" in content
86 | assert "debug" in content
87 | remove(path)
88 |
89 | def test_write_modules(self):
90 | modules = [
91 | "singularity/3.6.0rc2",
92 | "mpi/openmpi/4.1.1/gcc"
93 | ]
94 | config = SlurmConfig(job_name="testjob")
95 | config.modules = modules
96 | path = join("/tmp", "testjob.sh")
97 | config.write(path)
98 | assert isfile(path)
99 | with open(path, "r") as job:
100 | content = job.read()
101 | for m in modules:
102 | assert f"module load {m}" in content
103 | remove(path)
104 |
105 | def test_write_commands(self):
106 | modules = [
107 | "singularity/3.6.0rc2",
108 | "mpi/openmpi/4.1.1/gcc"
109 | ]
110 | commands_pre = [
111 | "source environment.sh",
112 | "source OpenFOAM commands"
113 | ]
114 | commands = [
115 | "cd where/to/run",
116 | "./Allrun"
117 | ]
118 | config = SlurmConfig(job_name="testjob")
119 | config.modules = modules
120 | config.commands_pre = commands_pre
121 | config.commands = commands
122 | path = join("/tmp", "testjob.sh")
123 | config.write(path)
124 | assert isfile(path)
125 | with open(path, "r") as job:
126 | content = job.read()
127 | for c in commands_pre + commands:
128 | assert c in content
129 | remove(path)
130 |
131 |
132 | @slurm_available
133 | class TestSlurmBuffer():
134 | def test_prepare_reset_fill_clean(self, temp_training):
135 | path, base_env = temp_training
136 | base_env.start_time = 0.0
137 | base_env.end_time = 0.015
138 | config = SlurmConfig(n_tasks=2, mem_per_cpu=2)
139 | buffer = SlurmBuffer(path, base_env, 2, 2, config)
140 | buffer.prepare()
141 | base_env.end_time = 0.03
142 | assert isfile(join(path, base_env.path, "log.blockMesh"))
143 | assert isfile(join(path, base_env.path, "trajectory.csv"))
144 | assert isdir(join(path, base_env.path, "postProcessing"))
145 | envs = buffer.envs
146 | buffer.reset()
147 | assert not isfile(join(path, "copy_0", "trajectory.csv"))
148 | assert not isdir(join(path, "copy_0", "postProcessing"))
149 | buffer.fill()
150 | assert isfile(join(path, "copy_0", "trajectory.csv"))
151 | assert isfile(join(path, "copy_0", "log.pimpleFoam"))
152 | assert isdir(join(path, "copy_0", "postProcessing"))
153 | # implicit test of save_trajectory()
154 | assert isfile(join(path, "observations_0.pt"))
155 | assert buffer._n_fills == 1
156 | buffer.clean()
157 | assert not isfile(join(path, "copy_0", "log.blockMesh"))
158 |
159 | def test_timeout(self, temp_training):
160 | path, base_env = temp_training
161 | base_env.start_time = 0.0
162 | base_env.end_time = 0.015
163 | config = SlurmConfig(n_tasks=2, mem_per_cpu=2)
164 | buffer = SlurmBuffer(path, base_env, 2, 2, config, timeout=1, wait=1)
165 | buffer.prepare()
166 | assert not isfile(join(path, base_env.path, "log.pimpleFoam.pre"))
167 |
--------------------------------------------------------------------------------
/drlfoam/environment/tests/test_rotating_pinball.py:
--------------------------------------------------------------------------------
1 | from os.path import join, exists
2 | from shutil import copytree, rmtree
3 | from pytest import raises, fixture
4 | from ..rotating_pinball import (RotatingPinball2D,
5 | _parse_surface_field_sum,
6 | _parse_forces, _parse_probes,
7 | _parse_trajectory)
8 | from ...constants import TESTDATA_PATH
9 | from ...utils import fetch_line_from_file
10 |
11 |
12 | @fixture()
13 | def temp_case():
14 | case = "rotatingPinball2D"
15 | source = join(TESTDATA_PATH, case)
16 | dest = join("/tmp", case)
17 | yield copytree(source, dest, dirs_exist_ok=True)
18 | rmtree(dest)
19 |
20 |
21 | def test_parse_surface_field_sum(temp_case):
22 | path = join(temp_case, "postProcessing", "field_cylinder_a",
23 | "0", "surfaceFieldValue.dat")
24 | sfs = _parse_surface_field_sum(path)
25 | assert all(sfs.columns == ["t", "cx", "cy", "cz"])
26 | assert len(sfs) == 3
27 | assert not sfs.isnull().values.any()
28 |
29 |
30 | def test_parse_forces(temp_case):
31 | forces = _parse_forces(temp_case)
32 | columns = ["t_a", "cx_a", "cy_a", "cz_a",
33 | "t_b", "cx_b", "cy_b", "cz_b",
34 | "t_c", "cx_c", "cy_c", "cz_c"
35 | ]
36 | assert all(forces.columns == columns)
37 | assert len(forces) == 3
38 | assert not forces.isnull().values.any()
39 |
40 |
41 | def test_parse_probes(temp_case):
42 | path = join(temp_case, "postProcessing", "probes", "0", "p")
43 | probes = _parse_probes(path, 14)
44 | columns = ["t"] + [f"p{i}" for i in range(14)]
45 | assert all(probes.columns == columns)
46 | assert len(probes) == 3
47 | assert not probes.isnull().values.any()
48 |
49 |
50 | def test_parse_trajectory(temp_case):
51 | path = join(temp_case, "trajectory.csv")
52 | tr = _parse_trajectory(path)
53 | columns = [
54 | "t",
55 | "omega_a", "alpha_a", "beta_a",
56 | "omega_b", "alpha_b", "beta_b",
57 | "omega_c", "alpha_c", "beta_c"
58 | ]
59 | assert all(tr.columns == columns)
60 | assert len(tr) == 3
61 | assert not tr.isnull().values.any()
62 |
63 |
64 | class TestRotatingPinball(object):
65 | def test_common(self, temp_case):
66 | _ = RotatingPinball2D()
67 | assert True
68 |
69 | def test_start_time(self, temp_case):
70 | env = RotatingPinball2D()
71 | env.initialized = True
72 | env.path = temp_case
73 | env.start_time = 3.0
74 | lines = fetch_line_from_file(
75 | join(env.path, "system", "controlDict"),
76 | "timeStart"
77 | )
78 | assert len(lines) == 5
79 | assert all(["3.0" in line for line in lines])
80 |
81 | def test_end_time(self, temp_case):
82 | env = RotatingPinball2D()
83 | env.initialized = True
84 | env.path = temp_case
85 | env.end_time = 300.0
86 | line = fetch_line_from_file(
87 | join(env.path, "system", "controlDict"),
88 | "endTime "
89 | )
90 | assert "300.0" in line
91 |
92 | def test_control_interval(self, temp_case):
93 | env = RotatingPinball2D()
94 | env.initialized = True
95 | env.path = temp_case
96 | env.control_interval = 2.0
97 | lines = fetch_line_from_file(
98 | join(env.path, "system", "controlDict"),
99 | "executeInterval"
100 | )
101 | assert len(lines) == 5
102 | assert all(["2.0" in line for line in lines])
103 | lines = fetch_line_from_file(
104 | join(env.path, "system", "controlDict"),
105 | "writeInterval"
106 | )
107 | assert len(lines) == 6
108 | assert all(["2.0" in line for line in lines])
109 |
110 | def test_action_bounds(self, temp_case):
111 | env = RotatingPinball2D()
112 | env.initialized = True
113 | env.path = temp_case
114 | env.action_bounds = 10.0
115 | line = fetch_line_from_file(
116 | join(env.path, "processor1", "2", "U"),
117 | "absOmegaMax"
118 | )
119 | assert "10.0" in line
120 |
121 | def test_seed(self, temp_case):
122 | env = RotatingPinball2D()
123 | env.initialized = True
124 | env.path = temp_case
125 | env.seed = 10
126 | line = fetch_line_from_file(
127 | join(env.path, "processor2", "2", "U"),
128 | "seed"
129 | )
130 | assert "10" in line
131 |
132 | def test_policy(self, temp_case):
133 | env = RotatingPinball2D()
134 | env.initialized = True
135 | env.path = temp_case
136 | env.policy = "model.pt"
137 | line = fetch_line_from_file(
138 | join(env.path, "processor3", "2", "U"),
139 | "policy"
140 | )
141 | assert "model.pt" in line
142 |
143 | def test_train(self, temp_case):
144 | env = RotatingPinball2D()
145 | env.initialized = True
146 | env.path = temp_case
147 | env.train = False
148 | line = fetch_line_from_file(
149 | join(env.path, "processor0", "2", "U"),
150 | "train"
151 | )
152 | assert "false" in line
153 |
154 | def test_reset(self, temp_case):
155 | env = RotatingPinball2D()
156 | env.initialized = True
157 | env.path = temp_case
158 | env.start_time = 2.0
159 | env.reset()
160 | assert not exists(join(env.path, "log.pimpleFoam"))
161 | assert not exists(join(env.path, "trajectory.csv"))
162 | assert not exists(join(env.path, "postProcessing"))
163 |
164 | def test_observations(self, temp_case):
165 | env = RotatingPinball2D()
166 | env.initialized = True
167 | env.path = temp_case
168 | obs = env.observations
169 | assert len(obs.keys()) == 15
170 | assert all([obs[key].shape[0] == 3 for key in obs])
171 | assert obs["states"].shape == (3, 14)
172 | assert obs["actions"].shape == (3, 3)
173 | assert obs["rewards"].shape == (3,)
174 |
175 |
--------------------------------------------------------------------------------
/drlfoam/agent/agent.py:
--------------------------------------------------------------------------------
1 | """
2 | Implements functions for computing the returns and GAE as well as classes for policy - and value network.
3 | Provides further a base class for all agents.
4 | """
5 | from typing import Callable, Tuple, Union
6 | from abc import ABC, abstractmethod
7 | import torch as pt
8 | from ..constants import DEFAULT_TENSOR_TYPE
9 |
10 |
11 | pt.set_default_tensor_type(DEFAULT_TENSOR_TYPE)
12 |
13 |
14 | def compute_returns(rewards: pt.Tensor, gamma: Union[int, float] = 0.99) -> pt.Tensor:
15 | """
16 | compute the returns based on given rewards and discount factor
17 |
18 | :param rewards: rewards
19 | :type rewards: pt.Tensor
20 | :param gamma: discount factor
21 | :type gamma: Union[int, float]
22 | :return: returns
23 | :rtype: pt.Tensor
24 | """
25 | n_steps = len(rewards)
26 | discounts = pt.logspace(0, n_steps-1, n_steps, gamma)
27 | returns = [(discounts[:n_steps-t] * rewards[t:]).sum()
28 | for t in range(n_steps)]
29 | return pt.tensor(returns)
30 |
31 |
32 | def compute_gae(rewards: pt.Tensor, values: pt.Tensor, gamma: Union[int, float] = 0.99,
33 | lam: Union[int, float] = 0.97) -> pt.Tensor:
34 | """
35 | Compute the generalized advantage estimate (GAE) based on
36 |
37 | 'High-Dimensional Continuous Control Using Generalized Advantage Estimation', https://arxiv.org/abs/1506.02438
38 |
39 | :param rewards: rewards
40 | :type rewards: pt.Tensor
41 | :param values: values of the states (output of value network)
42 | :type values: pt.Tensor
43 | :param gamma: discount factor
44 | :type gamma: Union[int, float]
45 | :param lam: hyperparameter lambda
46 | :type lam: Union[int, float]
47 | :return: GAE
48 | :rtype: pt.Tensor
49 | """
50 | n_steps = len(rewards)
51 | factor = pt.logspace(0, n_steps-1, n_steps, gamma*lam)
52 | delta = rewards[:-1] + gamma * values[1:] - values[:-1]
53 | gae = [(factor[:n_steps-t-1] * delta[t:]).sum()
54 | for t in range(n_steps - 1)]
55 | return pt.tensor(gae)
56 |
57 |
58 | class FCPolicy(pt.nn.Module):
59 | def __init__(self, n_states: int, n_actions: int, action_min: Union[int, float, pt.Tensor],
60 | action_max: Union[int, float, pt.Tensor], n_layers: int = 2, n_neurons: int = 64,
61 | activation: Callable = pt.nn.functional.relu):
62 | """
63 | implements policy network
64 |
65 | :param n_states: number of states
66 | :type n_states: int
67 | :param n_actions: number of actions
68 | :type n_actions: int
69 | :param action_min: lower bound of the actions
70 | :type action_min: Union[int, float, pt.Tensor]
71 | :param action_max: upper bound of the actions
72 | :type action_max: Union[int, float, pt.Tensor]
73 | :param n_layers: number of hidden layers
74 | :type n_layers: int
75 | :param n_neurons: number of neurons per layer
76 | :type n_neurons: int
77 | :param activation: activation function
78 | :type activation: pt.Callable
79 | """
80 | super(FCPolicy, self).__init__()
81 | self._n_states = n_states
82 | self._n_actions = n_actions
83 | self._action_min = action_min
84 | self._action_max = action_max
85 | self._n_layers = n_layers
86 | self._n_neurons = n_neurons
87 | self._activation = activation
88 |
89 | # set up policy network
90 | self._layers = pt.nn.ModuleList()
91 | self._layers.append(pt.nn.Linear(self._n_states, self._n_neurons))
92 | if self._n_layers > 1:
93 | for hidden in range(self._n_layers - 1):
94 | self._layers.append(pt.nn.Linear(self._n_neurons, self._n_neurons))
95 | self._layers.append(pt.nn.LayerNorm(self._n_neurons))
96 | self._last_layer = pt.nn.Linear(self._n_neurons, 2*self._n_actions)
97 |
98 | @pt.jit.ignore
99 | def _scale(self, actions: pt.Tensor) -> pt.Tensor:
100 | """
101 | perform min-max-scaling of the actions
102 |
103 | :param actions: unscaled actions
104 | :type actions: pt.Tensor
105 | :return: actions scaled to an interval of [0, 1]
106 | :rtype pt.Tensor
107 | """
108 | return (actions - self._action_min) / (self._action_max - self._action_min)
109 |
110 | def forward(self, x: pt.Tensor) -> pt.Tensor:
111 | for layer in self._layers:
112 | x = self._activation(layer(x))
113 | return 1.0 + pt.nn.functional.softplus(self._last_layer(x))
114 |
115 | @pt.jit.ignore
116 | def predict(self, states: pt.Tensor, actions: pt.Tensor) -> Tuple[pt.Tensor, pt.Tensor]:
117 | """
118 | predict log-probability and associated entropy based on given states and actions based on a beta distribution
119 | for each action
120 |
121 | :param states: unscaled states
122 | :type states: pt.Tensor
123 | :param actions: unscaled actions
124 | :type actions: pt.Tensor
125 | :return: log-probability and entropy of the beta distribution(s); in case of multiple distributions, the sum
126 | is taken over the second axis
127 | :rtype Tuple[pt.Tensor, pt.Tensor]
128 | """
129 | out = self.forward(states)
130 | c0 = out[:, :self._n_actions]
131 | c1 = out[:, self._n_actions:]
132 | beta = pt.distributions.Beta(c0, c1)
133 | if len(actions.shape) == 1:
134 | scaled_actions = self._scale(actions.unsqueeze(-1))
135 | else:
136 | scaled_actions = self._scale(actions)
137 | log_p = beta.log_prob(scaled_actions)
138 | if len(actions.shape) == 1:
139 | return log_p.squeeze(), beta.entropy().squeeze()
140 | else:
141 | return log_p.sum(dim=1), beta.entropy().sum(dim=1)
142 |
143 |
144 | class FCValue(pt.nn.Module):
145 | def __init__(self, n_states: int, n_layers: int = 2, n_neurons: int = 64,
146 | activation: Callable = pt.nn.functional.relu):
147 | """
148 | implements value network
149 |
150 | :param n_states: number of states
151 | :type n_states: int
152 | :param n_layers: number of hidden layers
153 | :type n_layers: int
154 | :param n_neurons: number of neurons per layer
155 | :type n_neurons: int
156 | :param activation: activation function
157 | :type activation: pt.Callable
158 | """
159 | super(FCValue, self).__init__()
160 | self._n_states = n_states
161 | self._n_layers = n_layers
162 | self._n_neurons = n_neurons
163 | self._activation = activation
164 |
165 | # set up value network
166 | self._layers = pt.nn.ModuleList()
167 | self._layers.append(pt.nn.Linear(self._n_states, self._n_neurons))
168 | if self._n_layers > 1:
169 | for hidden in range(self._n_layers - 1):
170 | self._layers.append(pt.nn.Linear(self._n_neurons, self._n_neurons))
171 | self._layers.append(pt.nn.LayerNorm(self._n_neurons))
172 | self._layers.append(pt.nn.Linear(self._n_neurons, 1))
173 |
174 | def forward(self, x: pt.Tensor) -> pt.Tensor:
175 | for i_layer in range(len(self._layers) - 1):
176 | x = self._activation(self._layers[i_layer](x))
177 | return self._layers[-1](x).squeeze()
178 |
179 |
180 | class Agent(ABC):
181 | """Common interface for all agents.
182 | """
183 |
184 | @abstractmethod
185 | def update(self, states, actions, rewards):
186 | pass
187 |
188 | @abstractmethod
189 | def save_state(self, path: str):
190 | pass
191 |
192 | @abstractmethod
193 | def load_state(self, state: Union[str, dict]):
194 | pass
195 |
196 | @abstractmethod
197 | def trace_policy(self):
198 | pass
199 |
200 | @property
201 | @abstractmethod
202 | def history(self):
203 | pass
204 |
205 | @property
206 | @abstractmethod
207 | def state(self):
208 | pass
--------------------------------------------------------------------------------
/openfoam/src/agentRotatingWallVelocity/agentRotatingWallVelocityFvPatchVectorField.H:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------*\
2 | ========= |
3 | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4 | \\ / O peration |
5 | \\ / A nd | www.openfoam.com
6 | \\/ M anipulation |
7 | -------------------------------------------------------------------------------
8 | Copyright (C) 2011-2016 OpenFOAM Foundation
9 | -------------------------------------------------------------------------------
10 | License
11 | This file is part of OpenFOAM.
12 |
13 | OpenFOAM is free software: you can redistribute it and/or modify it
14 | under the terms of the GNU General Public License as published by
15 | the Free Software Foundation, either version 3 of the License, or
16 | (at your option) any later version.
17 |
18 | OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 | for more details.
22 |
23 | You should have received a copy of the GNU General Public License
24 | along with OpenFOAM. If not, see .
25 |
26 | Class
27 | Foam::agentRotatingWallVelocityFvPatchVectorField
28 |
29 | Group
30 | grpWallBoundaryConditions grpGenericBoundaryConditions
31 |
32 | Description
33 | This boundary condition provides a rotational velocity condition.
34 |
35 | Usage
36 | \table
37 | Property | Description | Required | Default value
38 | origin | origin of rotation in Cartesian coordinates | yes|
39 | axis | axis of rotation | yes |
40 | \endtable
41 |
42 | Example of the boundary condition specification:
43 | \verbatim
44 |
45 | {
46 | type agentRotatingWallVelocity;
47 | origin (0 0 0);
48 | axis (0 0 1);
49 | policy "policy.pt"
50 | seed 0;
51 | train true;
52 | absOmegaMax 0.05;
53 | }
54 | \endverbatim
55 |
56 |
57 | See also
58 | Foam::fixedValueFvPatchField
59 |
60 | SourceFiles
61 | agentRotatingWallVelocityFvPatchVectorField.C
62 |
63 | \*---------------------------------------------------------------------------*/
64 |
65 | #ifndef agentRotatingWallVelocityFvPatchVectorField_H
66 | #define agentRotatingWallVelocityFvPatchVectorField_H
67 |
68 | #include
69 | #include
70 | #include "fixedValueFvPatchFields.H"
71 | #include "probes.H"
72 | #include "timeControl.H"
73 |
74 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
75 |
76 | namespace Foam
77 | {
78 |
79 | /*---------------------------------------------------------------------------*\
80 | Class agentRotatingWallVelocityFvPatchVectorField Declaration
81 | \*---------------------------------------------------------------------------*/
82 |
83 | class agentRotatingWallVelocityFvPatchVectorField
84 | :
85 | public fixedValueFvPatchVectorField
86 | {
87 | // Private data
88 |
89 | //- origin of the rotation
90 | vector origin_;
91 |
92 | //- axis of the rotation
93 | vector axis_;
94 |
95 | //- training or evaluation mode
96 | bool train_;
97 |
98 | //- name of the PyTorch angular velocity model
99 | word policy_name_;
100 |
101 | //- PyTorch model of angular velocity
102 | torch::jit::script::Module policy_;
103 |
104 | //- largest allowed absolute value of angular velocity
105 | scalar abs_omega_max_;
106 |
107 | //- seed for random sampling
108 | int seed_;
109 |
110 | //- name of the probes function object dictionary
111 | word probes_name_;
112 |
113 | //- random number generator
114 | std::mt19937 gen_;
115 |
116 | //- next predicted angular velocity
117 | scalar omega_;
118 |
119 | //- previously predicted angular velocity
120 | scalar omega_old_;
121 |
122 | //- last time at which angular velocity was computed
123 | scalar control_time_;
124 |
125 | //- update of next angular velocity value if true
126 | bool update_omega_;
127 |
128 | //- when to start controlling
129 | scalar start_time_;
130 |
131 | //- time increment between control actions
132 | scalar dt_control_;
133 |
134 | //- probes function object
135 | Foam::probes probes_;
136 |
137 | //- timeControls consistent with function objects
138 | Foam::timeControl control_;
139 |
140 | public:
141 |
142 | //- Runtime type information
143 | TypeName("agentRotatingWallVelocity");
144 |
145 |
146 | // Constructors
147 |
148 | //- Construct from patch and internal field
149 | agentRotatingWallVelocityFvPatchVectorField
150 | (
151 | const fvPatch&,
152 | const DimensionedField&
153 | );
154 |
155 | //- Construct from patch, internal field and dictionary
156 | agentRotatingWallVelocityFvPatchVectorField
157 | (
158 | const fvPatch&,
159 | const DimensionedField&,
160 | const dictionary&
161 | );
162 |
163 | //- Construct by mapping given agentRotatingWallVelocityFvPatchVectorField
164 | // onto a new patch
165 | agentRotatingWallVelocityFvPatchVectorField
166 | (
167 | const agentRotatingWallVelocityFvPatchVectorField&,
168 | const fvPatch&,
169 | const DimensionedField&,
170 | const fvPatchFieldMapper&
171 | );
172 |
173 | //- Construct as copy
174 | agentRotatingWallVelocityFvPatchVectorField
175 | (
176 | const agentRotatingWallVelocityFvPatchVectorField&
177 | );
178 |
179 | //- Construct and return a clone
180 | virtual tmp clone() const
181 | {
182 | return tmp
183 | (
184 | new agentRotatingWallVelocityFvPatchVectorField(*this)
185 | );
186 | }
187 |
188 | //- Construct as copy setting internal field reference
189 | agentRotatingWallVelocityFvPatchVectorField
190 | (
191 | const agentRotatingWallVelocityFvPatchVectorField&,
192 | const DimensionedField&
193 | );
194 |
195 | //- Construct and return a clone setting internal field reference
196 | virtual tmp clone
197 | (
198 | const DimensionedField& iF
199 | ) const
200 | {
201 | return tmp
202 | (
203 | new agentRotatingWallVelocityFvPatchVectorField(*this, iF)
204 | );
205 | }
206 |
207 |
208 |
209 | // Member functions
210 |
211 | //- Update the coefficients associated with the patch field
212 | virtual void updateCoeffs();
213 |
214 | //- Write
215 | virtual void write(Ostream&) const;
216 |
217 | //- Save trajectory to file
218 | void saveTrajectory(scalar alpha, scalar beta) const;
219 |
220 | //- Get probes function object dictionary
221 | const Foam::dictionary& getProbesDict();
222 |
223 | //- initialize probes function object
224 | Foam::probes initializeProbes();
225 |
226 | //- initialize control
227 | Foam::timeControl initializeControl();
228 |
229 | };
230 |
231 |
232 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
233 |
234 | } // End namespace Foam
235 |
236 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
237 |
238 | #endif
239 |
240 | // ************************************************************************* //
241 |
--------------------------------------------------------------------------------
/drlfoam/environment/rotating_cylinder.py:
--------------------------------------------------------------------------------
1 | """
2 | implements the environment for the rotatingCylinder2D
3 | """
4 | import logging
5 | import torch as pt
6 |
7 | from re import sub
8 | from os import remove
9 | from glob import glob
10 | from io import StringIO
11 | from typing import Union
12 | from shutil import rmtree
13 | from pandas import read_csv, DataFrame
14 | from os.path import join, isfile, isdir
15 |
16 | from .environment import Environment
17 | from ..constants import TESTCASE_PATH, DEFAULT_TENSOR_TYPE
18 | from ..utils import (check_pos_int, check_pos_float, replace_line_in_file,
19 | get_time_folders, replace_line_latest)
20 |
21 |
22 | pt.set_default_tensor_type(DEFAULT_TENSOR_TYPE)
23 | logger = logging.getLogger(__name__)
24 |
25 |
26 | def _parse_forces(path: str) -> DataFrame:
27 | return read_csv(path, sep="\t", comment="#", header=None, names=["t", "cx_a", "cy_a"])
28 |
29 |
30 | def _parse_probes(path: str, n_probes: int) -> DataFrame:
31 | with open(path, "r") as pfile:
32 | pdata = sub("[()]", "", pfile.read())
33 | names = ["t"] + [f"p{i}" for i in range(n_probes)]
34 | return read_csv(StringIO(pdata), header=None, names=names, comment="#", delim_whitespace=True)
35 |
36 |
37 | def _parse_trajectory(path: str) -> DataFrame:
38 | return read_csv(path, sep=",", header=0, names=["t", "omega_a", "alpha_a", "beta_a"])
39 |
40 |
41 | class RotatingCylinder2D(Environment):
42 | def __init__(self, r1: Union[int, float] = 3.0, r2: Union[int, float] = 1.0, r3: Union[int, float] = 0.1):
43 | """
44 | implements the RotatingCylinder2D environment
45 |
46 | :param r1: offset in reward function
47 | :type r1: Union[int, float]
48 | :param r2: weighing factor for cd in reward function
49 | :type r2: Union[int, float]
50 | :param r3: weighing factor for cl in reward function
51 | :type r3: Union[int, float]
52 | """
53 | super(RotatingCylinder2D, self).__init__(
54 | join(TESTCASE_PATH, "rotatingCylinder2D"), "Allrun.pre",
55 | "Allrun", "Allclean", 2, 12, 1
56 | )
57 | self._r1 = r1
58 | self._r2 = r2
59 | self._r3 = r3
60 | self._initialized = False
61 | self._start_time = 0
62 | self._end_time = 4
63 | self._control_interval = 0.01
64 | self._train = True
65 | self._seed = 0
66 | self._action_bounds = 5.0
67 | self._policy = "policy.pt"
68 |
69 | def reward(self, data: dict) -> pt.Tensor:
70 | return self._r1 - (self._r2 * data["cx_a"] + self._r3 * data["cy_a"].abs())
71 |
72 | @property
73 | def start_time(self) -> float:
74 | return self._start_time
75 |
76 | @start_time.setter
77 | def start_time(self, value: float) -> None:
78 | check_pos_float(value, "start_time", with_zero=True)
79 | replace_line_in_file(
80 | join(self.path, "system", "controlDict"),
81 | "timeStart",
82 | f" timeStart {value};"
83 | )
84 | self._start_time = value
85 |
86 | @property
87 | def end_time(self) -> float:
88 | return self._end_time
89 |
90 | @end_time.setter
91 | def end_time(self, value: float) -> None:
92 | check_pos_float(value, "end_time", with_zero=True)
93 | replace_line_in_file(
94 | join(self.path, "system", "controlDict"),
95 | "endTime ",
96 | f"endTime {value};"
97 | )
98 | self._end_time = value
99 |
100 | @property
101 | def control_interval(self) -> Union[float, int]:
102 | return self._control_interval
103 |
104 | @control_interval.setter
105 | def control_interval(self, value: int) -> None:
106 | check_pos_float(value, "control_interval")
107 | replace_line_in_file(
108 | join(self.path, "system", "controlDict"),
109 | "executeInterval",
110 | f" executeInterval {value};",
111 | )
112 | replace_line_in_file(
113 | join(self.path, "system", "controlDict"),
114 | "writeInterval",
115 | f" writeInterval {value};",
116 | )
117 | self._control_interval = value
118 |
119 | @property
120 | def action_bounds(self) -> float:
121 | return self._action_bounds
122 |
123 | @action_bounds.setter
124 | def action_bounds(self, value: float) -> None:
125 | proc = True if self.initialized else False
126 | new = f" absOmegaMax {value:2.4f};"
127 | replace_line_latest(self.path, "U", "absOmegaMax", new, proc)
128 | self._action_bounds = value
129 |
130 | @property
131 | def seed(self) -> int:
132 | return self._seed
133 |
134 | @seed.setter
135 | def seed(self, value: int) -> None:
136 | check_pos_int(value, "seed", with_zero=True)
137 | proc = True if self.initialized else False
138 | new = f" seed {value};"
139 | replace_line_latest(self.path, "U", "seed", new, proc)
140 | self._seed = value
141 |
142 | @property
143 | def policy(self) -> str:
144 | return self._policy
145 |
146 | @policy.setter
147 | def policy(self, value: str) -> None:
148 | proc = True if self.initialized else False
149 | new = f" policy {value};"
150 | replace_line_latest(self.path, "U", "policy", new, proc)
151 | self._policy = value
152 |
153 | @property
154 | def train(self) -> bool:
155 | return self._train
156 |
157 | @train.setter
158 | def train(self, value: bool) -> None:
159 | proc = True if self.initialized else False
160 | value_cpp = "true" if value else "false"
161 | new = f" train {value_cpp};"
162 | replace_line_latest(self.path, "U", "train", new, proc)
163 | self._train = value
164 |
165 | @property
166 | def observations(self) -> dict:
167 | obs = {}
168 | try:
169 | times_folder_forces = glob(join(self.path, "postProcessing", "forces", "*"))
170 | force_path = join(times_folder_forces[0], "coefficient.dat")
171 | forces = _parse_forces(force_path)
172 | tr_path = join(self.path, "trajectory.csv")
173 | tr = _parse_trajectory(tr_path)
174 | times_folder_probes = glob(join(self.path, "postProcessing", "probes", "*"))
175 | probes_path = join(times_folder_probes[0], "p")
176 | probes = _parse_probes(probes_path, self._n_states)
177 | p_names = ["p{:d}".format(i) for i in range(self._n_states)]
178 | obs["states"] = pt.from_numpy(probes[p_names].values)
179 | obs["actions"] = pt.from_numpy(tr["omega_a"].values)
180 | obs["cx_a"] = pt.from_numpy(forces["cx_a"].values)
181 | obs["cy_a"] = pt.from_numpy(forces["cy_a"].values)
182 | obs["rewards"] = self.reward(obs)
183 | obs["alpha"] = pt.from_numpy(tr["alpha_a"].values)
184 | obs["beta"] = pt.from_numpy(tr["beta_a"].values)
185 | except Exception as e:
186 | logger.warning("Could not parse observations: ", e)
187 | finally:
188 | return obs
189 |
190 | def reset(self) -> None:
191 | # if we are not in base case, then there should be a log-file from the solver used (e.g. interFoam / pimpleFoam)
192 | solver_log = glob(join(self.path, "log.*Foam"))
193 | if solver_log:
194 | files = [f"log.{solver_log[0].split('.')[-1]}", "finished.txt", "trajectory.csv"]
195 | else:
196 | # otherwise we are in the base case and have only a log.*Foam.pre, which we don't want to remove
197 | files = ["finished.txt", "trajectory.csv"]
198 | for f in files:
199 | f_path = join(self.path, f)
200 | if isfile(f_path):
201 | remove(f_path)
202 | post = join(self.path, "postProcessing")
203 | if isdir(post):
204 | rmtree(post)
205 | times = get_time_folders(join(self.path, "processor0"))
206 | times = [t for t in times if float(t) > self.start_time]
207 | for p in glob(join(self.path, "processor*")):
208 | for t in times:
209 | rmtree(join(p, t))
210 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deep Reinforcement Learning with OpenFOAM
2 |
3 | ## Overview
4 |
5 | ### Repository structure
6 |
7 | - **docs**: Sphinx documentation sources (work in progress)
8 | - **drlfoam**: Python library for DRL with OpenFOAM
9 | - **examples**: annotated scripts for performing DRL trainings and tests
10 | - **openfoam**: OpenFOAM simulation setups (*test_cases*) and source files for additional OpenFOAM library components, e.g., boundary conditions (*src*)
11 |
12 | For a list of research projects employing drlFoam, refer to the [references](./references.md).
13 |
14 | ### drlFoam package
15 |
16 | - **drlfoam.environment**: wrapper classes for manipulating and parsing OpenFOAM test cases
17 | - **drlfoam.agent**: policy and value networks, agent logic and training
18 | - **drlfoam.execution**: trajectory buffer, local and cluster (Slurm) execution
19 |
20 | ## Installation
21 |
22 | ### Python environment
23 |
24 | A miss-match between the installed Python-frontend of PyTorch and the C++ frontend (libTorch) can lead to unexpected behavior. Therefore, it is recommended to create a virtual environment using [venv](https://docs.python.org/3/library/venv.html) or [conda](https://docs.conda.io/en/latest/miniconda.html). Moreover, the currently used PyTorch version (1.12.1) requires a Python interpreter **>=3.8**. The following listing shows all steps to set up a suitable virtual environment on Ubuntu 20.04 or newer.
25 | ```
26 | # install venv
27 | sudo apt update && sudo apt install python3.8-venv
28 | # if not yet cloned, get drlfoam
29 | git clone https://github.com/OFDataCommittee/drlfoam.git
30 | cd drlfoam
31 | # create a new environment called pydrl
32 | python3 -m venv pydrl
33 | # start the environment and install dependencies
34 | source pydrl/bin/activate
35 | pip install -r requirements.txt
36 | # once the work is done, leave the environment
37 | deactivate
38 | ```
39 |
40 | ### OpenFOAM library components
41 |
42 | Source code and test cases are only compatible with **OpenFOAM-v2206**; [installation instructions](https://develop.openfoam.com/Development/openfoam/-/wikis/precompiled). You can use the pre-compiled binaries. Make sure that the OpenFOAM environment variables are available, e.g., by adding `source /usr/lib/openfoam/openfoam2206/etc/bashrc` to `$HOME/.bashrc`. The additional OpenFOAM library components are compiled as follows:
43 | ```
44 | # at the top-level of this repository
45 | # when executing for the first time, libTorch is downloaded by Allwmake
46 | source setup-env
47 | ./Allwmake
48 | ```
49 | In case you want to re-compile starting from a clean state:
50 | ```
51 | # assuming you are at the repository's top folder
52 | ./Allwclean
53 | ```
54 |
55 | ### Working with Singularity containers
56 |
57 | Instead of installing dependencies manually, you can also employ the provided Singularity container. Singularity simplifies execution on HPC infrastructures, because no dependencies except for Singularity itself and OpenMPI are required. To build the container locally, run:
58 | ```
59 | sudo singularity build of2206-py1.12.1-cpu.sif docker://andreweiner/of_pytorch:of2206-py1.12.1-cpu
60 | ```
61 | By default, the container is expected to be located at the repository's top level. The default location may be changed by adjusting the `DRL_IMAGE` variable in *setup-env*. To build the OpenFOAM library components, provide the `--container` flag:
62 | ```
63 | ./Allwmake --container
64 | ```
65 | Similarly, for cleaning up the build:
66 | ```
67 | ./Allwclean --container
68 | ```
69 |
70 | ## Running a training
71 |
72 | Currently, there are two examples of assembling a DRL training with drlFoam:
73 | 1. the *rotatingCylinder2D* test case
74 | 2. the *rotatingPinball2D* test case
75 |
76 | To perform the training locally, execute the following steps:
77 | ```
78 | # from the top-level of this repository
79 | source pydrl/bin/activate
80 | source setup-env
81 | cd examples
82 | # see config_orig.yml for all available options
83 | # defaults to: training saved in test_training; buffer size 4; 2 runners
84 | # this training requires 4 MPI ranks on average and two loops
85 | # of each runner to fill the buffer
86 | python3 run_training.py
87 | ```
88 | The settings can be adjusted in the `config_orig.yml`, located in the `examples` directory.
89 | To run the training with the Apptainer container, pass the `--container` flag to *setup-env*:
90 | ```
91 | source setup-env --container
92 | python3 run_training.py
93 | ```
94 |
95 | ## Running a training with SLURM
96 |
97 | This section describes how to run a training on a HPC with SLURM. The workflow was tested on TU Braunschweig's [Pheonix cluster](https://www.tu-braunschweig.de/en/it/dienste/21/phoenix) and might need small adjustments for other HPC configurations. The cluster should provide the following modules/packages:
98 | - Apptainer
99 | - Python 3.8
100 | - OpenMPI v4.1 (minor difference might be OK)
101 | - SLURM
102 |
103 | After logging in, the following steps set up all dependencies. These steps have to be executed only once in a new environment:
104 | ```
105 | # load git and clone repository
106 | module load comp/git/latest
107 | git clone https://github.com/OFDataCommittee/drlfoam.git
108 | # copy the Singularity image to the drlfoam folder
109 | cp /path/to/of2206-py1.12.1-cpu.sif drlfoam/
110 | # set up the virtual Python environment
111 | module load python/3.8.2
112 | python3 -m venv pydrl
113 | source pydrl/bin/activate
114 | pip install --upgrade pip
115 | pip install -r requirements.txt
116 | # compile the OpenFOAM library components
117 | module load singularity/latest
118 | ./Allwmake --container
119 | ```
120 | The *examples/run_training.py* scripts support SLURM-based. To run a new training on the cluster, navigate to the *examples* folder and create a new dedicated jobscript, e.g., *training_jobscript*. A suitable jobscript looks as follows:
121 | ```
122 | #SBATCH --partition=standard
123 | #SBATCH --nodes=1
124 | #SBATCH --time=7-00:00:00
125 | #SBATCH --job-name=drl_train
126 | #SBATCH --ntasks-per-node=1
127 |
128 | module load python/3.8.2
129 |
130 | # adjust path if necessary
131 | source ~/drlfoam/pydrl/bin/activate
132 | source ~/drlfoam/setup-env --container
133 |
134 | # start a training with a buffer size of 8 and 8 runners;
135 | # save output to log.test_training
136 | python3 run_training.py &> log.test_training
137 | ```
138 | Submitting, inspecting, and canceling of trainings works as follows:
139 | ```
140 | # start the training
141 | sbatch training_jobscript
142 | # check the SLURM queue
143 | squeue -u $USER
144 | # canceling a job
145 | scancel job_id
146 | ```
147 | To detect potential errors in case of failure, inspect *all* log files:
148 | - the log file of the driver script, e.g., *log.test_training*
149 | - the output of individual simulation jobs, e.g., slurm-######.out
150 | - the standard OpenFOAM log files located in *test_training/copy_#*
151 |
152 | ## Development
153 |
154 | Unittests are implemented with [PyTest](https://docs.pytest.org/en/7.1.x/). Some tests require a [Slurm](https://slurm.schedmd.com/documentation.html) installation on the test machine. Instructions for a minimal Slurm setup on Ubuntu are available [here](https://gist.github.com/ckandoth/2acef6310041244a690e4c08d2610423). If Slurm is not available, related tests are ignored. Some test require additional test data, which can be created with the *create_test_data* script.
155 | ```
156 | # examples for running all or selected tests
157 | # starting from the repository top-level
158 | # run all available tests with additional output (-s)
159 | source setup-env
160 | pytest -s drlfoam
161 | # run all tests in the agent sub-package
162 | pytest -s drlfoam/agent
163 | # run all tests for the ppo_agent.py module
164 | pytest -s drlfoam/agent/tests/test_ppo_agent.py
165 | ```
166 |
167 | ## Contributions
168 |
169 | drlFoam is currently developed and maintained by Andre Weiner (@AndreWeiner). Significant contributions to usability, bug fixes, and tests have been made during the first [OpenFOAM-Machine Learning Hackathon](https://github.com/OFDataCommittee/OFMLHackathon) by (in alphabetical order):
170 |
171 | - Ajay Navilarekal Rajgopal (@ajaynr)
172 | - Darshan Thummar (@darshan315)
173 | - Guilherme Lindner (@guilindner)
174 | - Julian Bissantz (@jbissantz)
175 | - Morgan Kerhouant
176 | - Mosayeb Shams (@mosayebshams)
177 | - Tomislav Marić (@tmaric)
178 |
179 | The foundation of the drlFoam implementation are the student projects by [Darshan Thummar](https://github.com/darshan315/flow_past_cylinder_by_DRL) and [Fabian Gabriel](https://github.com/FabianGabriel/Active_flow_control_past_cylinder_using_DRL).
180 |
181 | ## License
182 |
183 | drlFoam is [GPLv3](https://en.wikipedia.org/wiki/GNU_General_Public_License)-licensed; refer to the [LICENSE](https://github.com/OFDataCommittee/drlfoam/blob/main/LICENSE) file for more information.
--------------------------------------------------------------------------------
/openfoam/src/pinballRotatingWallVelocity/pinballRotatingWallVelocityFvPatchVectorField.H:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------*\
2 | ========= |
3 | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4 | \\ / O peration |
5 | \\ / A nd | www.openfoam.com
6 | \\/ M anipulation |
7 | -------------------------------------------------------------------------------
8 | Copyright (C) 2011-2016 OpenFOAM Foundation
9 | -------------------------------------------------------------------------------
10 | License
11 | This file is part of OpenFOAM.
12 |
13 | OpenFOAM is free software: you can redistribute it and/or modify it
14 | under the terms of the GNU General Public License as published by
15 | the Free Software Foundation, either version 3 of the License, or
16 | (at your option) any later version.
17 |
18 | OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 | for more details.
22 |
23 | You should have received a copy of the GNU General Public License
24 | along with OpenFOAM. If not, see .
25 |
26 | Class
27 | Foam::pinballRotatingWallVelocityFvPatchVectorField
28 |
29 | Group
30 | grpWallBoundaryConditions grpGenericBoundaryConditions
31 |
32 | Description
33 | This boundary condition provides a rotational velocity condition.
34 | The boundary condition was originally implemented by Tom Krogmann
35 | during his student project at TU Braunschweig. For more information
36 | refer to:
37 | GitHub: https://github.com/TomKrogmann/Optimal_Sensor_Placement_for_Active_Flow_Control_in_Deep_Reinforcement_Learning
38 | report: https://zenodo.org/record/7636959#.Y-qTLcbMJkg
39 |
40 | Usage
41 | \table
42 | Property | Description | Required | Default value
43 | origin | origin of rotation in Cartesian coordinates | yes|
44 | axis | axis of rotation | yes |
45 | \endtable
46 |
47 | Example of the boundary condition specification:
48 | \verbatim
49 |
50 | {
51 | type pinballRotatingWallVelocity;
52 | origin_a (1 0 0);
53 | origin_b (0 1 0);
54 | origin_c (0 -1 0);
55 | axis (0 0 1);
56 | policy "policy.pt"
57 | seed 0;
58 | train true;
59 | absOmegaMax 5.0;
60 | }
61 | \endverbatim
62 |
63 | See also
64 | Foam::fixedValueFvPatchField
65 |
66 | SourceFiles
67 | pinballRotatingWallVelocityFvPatchVectorField.C
68 |
69 | \*---------------------------------------------------------------------------*/
70 |
71 | #ifndef pinballRotatingWallVelocityFvPatchVectorField_H
72 | #define pinballRotatingWallVelocityFvPatchVectorField_H
73 |
74 | #include
75 | #include
76 | #include "fixedValueFvPatchFields.H"
77 | #include "probes.H"
78 | #include "timeControl.H"
79 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
80 |
81 | namespace Foam
82 | {
83 |
84 | /*---------------------------------------------------------------------------*\
85 | Class pinballRotatingWallVelocityFvPatchVectorField Declaration
86 | \*---------------------------------------------------------------------------*/
87 |
88 | class pinballRotatingWallVelocityFvPatchVectorField
89 | :
90 | public fixedValueFvPatchVectorField
91 | {
92 | // Private data
93 |
94 | //- origin of cylinder a
95 | vector origin_a_;
96 |
97 | //- origin of cylinder b
98 | vector origin_b_;
99 |
100 | //- origin of cylinder c
101 | vector origin_c_;
102 |
103 | //- axis of the rotation
104 | vector axis_;
105 |
106 | //- training or evaluation mode
107 | bool train_;
108 |
109 | //- name of the PyTorch angular velocity model
110 | word policy_name_;
111 |
112 | //- PyTorch model predicting mean and log(std) of angular velocity
113 | torch::jit::script::Module policy_;
114 |
115 | //- largest allowed absolute value of angular velocity
116 | scalar abs_omega_max_;
117 |
118 | //- seed for random sampling
119 | int seed_;
120 |
121 | //- name of the probes function object dictionary
122 | word probes_name_;
123 |
124 | //- random number generator
125 | std::mt19937 gen_;
126 |
127 | //- next predicted angular velocity of cylinder a
128 | scalar omega_a_;
129 |
130 | //- previously predicted angular velocity of cylinder a
131 | scalar omega_old_a_;
132 |
133 | //- next predicted angular velocity of cylinder b
134 | scalar omega_b_;
135 |
136 | //- previously predicted angular velocity of cylinder b
137 | scalar omega_old_b_;
138 |
139 | //- next predicted angular velocity of cylinder c
140 | scalar omega_c_;
141 |
142 | //- previously predicted angular velocity of cylinder c
143 | scalar omega_old_c_;
144 |
145 | //- last time at which angular velocity was computed
146 | scalar control_time_;
147 |
148 | //- update of next angular velocity value if true
149 | bool update_omega_;
150 |
151 | //- when to start controlling
152 | scalar start_time_;
153 |
154 | //- time increment between control actions
155 | scalar dt_control_;
156 |
157 | //- probes function object
158 | Foam::probes probes_;
159 |
160 | //- timeControls consistent with function objects
161 | Foam::timeControl control_;
162 |
163 | //- lists of face centers, normals, and face IDs
164 | DynamicList centers_a_;
165 | DynamicList centers_b_;
166 | DynamicList centers_c_;
167 | DynamicList normals_a_;
168 | DynamicList normals_b_;
169 | DynamicList normals_c_;
170 | DynamicList