Skip to content
William Jussiau edited this page Feb 21, 2025 · 35 revisions

Welcome to the FlowControl wiki! Learn more about How It Works and How To Use.

🌀 FlowControl

The FlowControl toolbox is an open-source toolbox addressing the simulation and control of 2D incompressible flows. It aims at providing a user-friendly way to simulate flows with actuators and sensors, and a possibility to readily define new use-cases.

Animated GIF featuring the stabilization of the flow past a cylinder at Re=100. The self-sustained, periodic oscillations of the flow (known as vortex shedding) gradually disappear as the controller actuates the flow. The feedback controller uses a sensor in the wake and actuates the flow on the poles of the cylinder. More details are given below.

Introduction

The primary goal of the toolbox is the design and implementation of feedback control algorithms, but it may be used for a variety of other topics such as model reduction or identification, actuator and sensor placement study...

The toolbox is shipped with two benchmarks for flow control and allows for easy implementation of new cases.

Animated GIF featuring the stabilization of the flow over an open cavity at Re=7500. The self-sustained, quasi-periodic oscillations of the flow gradually disappear as the controller actuates the flow. The feedback controller uses a wall stress sensor on the wall after the cavity, and actuates the flow with a volume force upstream of the cavity. More details are given below.

The core of the toolbox is in Python and relies on FEniCS 2019.1.0 as a backend.

FEniCS Project banner, featuring a flame meshed with colorful elements and the text fenics project next to it.

What the toolbox offers

Simulation

  • By default, the toolbox integrates in time the Incompressible Navier-Stokes equations. For a 2D flow defined by its velocity ${v}({x}, t) = [v_1({x}, t), v_2({x}, t)]$ and pressure $p({x}, t)$ inside a domain ${x} = [x_1, x_2] \in\Omega$, the equations read as follows:
$$\left\{ \begin{aligned} & \frac{\partial {v}}{\partial t} + ({v} \cdot \nabla){v} = -\nabla p + \frac{1}{Re}\nabla^2 {v} \\\ & \nabla \cdot {v} = 0 \end{aligned} \right.$$
  • The only numerical parameter of the non-dimensional equations, the Reynolds number defined as $Re = \frac{UL}{\nu}$, balances convective and viscous terms.

Actuation and sensing

The toolbox allows the user to define actuators and sensors for forcing the flow. It also provides utility for controller design and implementation. See the examples given below.

Two benchmarks

Two classic oscillator flows used for flow control are shipped with the current code.

Use-case Description Feedback configuration
Cylinder Flow past a cylinder at Re=100 SISO
Cavity Flow over an open cavity at Re=7500 SISO


Flow past a cylinder at Re=100

Illustration

Stationary solution of the flow past a cylinder at Re=100. The base flow is symetric with respect to the axis x_2=0 and features a long recirculation bubble after the cylinder obstacle.

Stationary solution

Snapshot of the attractor of the flow past a cylinder at Re=100. The vortex shedding exhibits symmetry around the x_2=0 axis and vortices are shed periodically.

Periodic attractor (stable limit cycle)

Feedback configuration

The default feedback configuration (same as in Jussiau, W., Leclercq, C., Demourant, F., & Apkarian, P. (2022). Learning linear feedback controllers for suppressing the vortex-shedding flow past a cylinder. IEEE Control Systems Letters, 6, 3212-3217.) is as follows:

  • Cross-stream velocity measurement $y(t)=v_2({x_s}, t)$ in the wake at ${x_s} = [3, 0]$,

  • Boundary actuation at the poles of the cylinder, acting on the cross-stream velocity $v_2$. The velocity profile on the actuated boundary reads:

$${v_{act}}({x}, t) = - \dfrac{(x_1-l)(x_1+l)}{l^2} u(t)$$

where $u(t)$ is the control input, $l = \frac{1}{2} D \sin \left( \frac{\delta}{2} \right)$, $\delta=10\degree$ are tunable actuator parameters.

Actuator profile at the top pole of the cylinder. The spatial parabolic profile amplitude is modified by the control input u(t).

Description of the feedback configuration of the cylinder. The cross-stream velocity sensor (black dot) in the wake (red wave) can be fed to the controller K (in blue) and conveyed back to the actuators at the poles of the cylinder (in green).



Flow over an open cavity at Re=7500

Illustration

Stationary solution of the flow over an open cavity at Re=7500

Stationary solution

Snapshot of the attractor of the flow over an open cavity at Re=7500

Quasi-periodic attractor

Contrary to the cylinder, the attractor on the cavity at Re=7500 is quasi-periodic (featuring two incommensurable frequencies in its frequency spectrum):

Snapshot of the attractor of the flow over an open cavity at Re=7500

Feedback configuration

The default feedback configuration (same as in Leclercq et al. (2019). Linear iterative method for closed-loop control of quasiperiodic flows. Journal of Fluid Mechanics, 868, 26-65.) is as follows:

  • Actuation is produced near the upstream edge of the cavity by a volume force $f({x}, t)=B({x}) u(t)$ in the momentum equation, acting on the cross-stream velocity, with:

$$B({x})=\left[ 0, \eta \exp\left( \frac{\left(x_1 - x_1^0\right)^2 + \left(x_2 - x_2^0\right)^2}{2\sigma_0^2} \right), 0 \right]^T$$

By default, the center of the actuator is $(x_1^0, x_2^0) = (-0.1, 0.02)$, just before the cavity and slightly above the wall. The amplitude $\eta\approx 8.25$ is chosen such that $\int_\Omega B({x})^T B({x}) d\Omega = 1$. The spatial extent of the actuation is set by $\sigma_0 = 0.0849$, making the force reach $50%$ of its peak value at a distance $0.1$ from its center.

  • The measurement is made through wall friction on the bottom wall just downstream of the cavity:

$$y(t) = \int_{x_1=1}^{1.1} \left. \frac{\partial v_1(x, t)}{\partial x_2} \right\rvert_{x_2=0} dx_1$$

Visual description of the feedback configuration of the cavity. The wall friction is measured on the bottom wall downstream of the cavity, then fed to a controller K, which produces an input signal u(t) modifying the amplitude of a volumic force before the cavity.

Numerical details

  • For discretization in space, the Finite Element Method is used, using default continuous Galerkin elements of order 2 (for each component of the velocity) and 1 (for the scalar pressure).

  • For the time integration, a linear multistep semi-implicit method is used (the nonlinear term is extrapolated with a second-order Adams–Bashforth scheme, while the viscous term is treated implicitly).

  • The equations are implemented using a perturbation formulation:

    • the field $v(x,t)$ is decomposed as $v(x,t) = V(x) + v'(x, t)$,
    • $V(x)$ is computed first,
    • then, we can compute the time evolution of $v'(x,t)$.
  • To some extent, the toolbox aims at making the equations, numerical integration schemes and solvers replaceable by user-defined ones.



Examples of use of the toolbox

The following articles were based on previous versions of the code:



Installation 🛠️

conda

The conda environment required to run the code can be extracted from the file environment.yml. Additional path tweaking may be required for all FEniCS (dolfin module) and custom modules to be found.

Docker 🐳

[coming soon]

Code overview

Define a new use-case: inherit FlowSolver abstract class

The simulation revolves around the abstract class FlowSolver that implements core features such as loading mesh, defining function spaces, trial/test functions, variational formulations, numerical schemes and solvers, handling the time-stepping and exporting information. The class is abstract as it does not implement a simulation case per se, but only provides utility for doing so. It features two abstract methods, that are redefined for each use-case:

  1. _make_boundaries provides a definition and naming of the boundaries of the mesh in a pandas DataFrame.
    @abstractmethod
    def _make_boundaries(self) -> pd.DataFrame:
        pass
  1. _make_bcs provides a description of the boundary conditions on the boundaries defined above, in a dictionary.
    @abstractmethod
    def _make_bcs(self) -> dict[str, Any]:
        pass

For the two aforementioned examples, these methods are reimplemented in the classes CylinderFlowSolver and CavityFlowSolver that inherit from FlowSolver.

Attach Sensors and Actuators to an instance of a FlowSolver subclass

In order to perform sensing and actuation (in order to close the loop), dedicated classes Sensor and Actuator are proposed. They are not aimed at being instantiated, but rather inherited.

  • Sensor is an abstract class that provides a method eval(self, up: dolfin.Function) -> float. Classes SensorPoint (point probe) and SensorIntegral (integration on a subdomain) are examples of subclasses that implement the eval method.
  • Likewise, Actuator is an abstract class that encapsulates a dolfin.Expression among other elements, and embeds it in the variational formulations.

The sensors and actuators are attached to a FlowSolver object as a list, embedded in the ParamControl object. The call to Sensors and Actuators is made automatically by FlowSolver. By attaching several sensors or actuators, it is possible to use Multiple-Input, Multiple-Output controllers in the loop.

In the example below (for the cylinder use-case), we are creating an actuator acting on boundary conditions and three point probes at different locations. They are gathered in a ParamControl object, which is passed as an argument when creating a CylinderFlowSolver.

# Actuator
actuator_bc = ActuatorBCParabolicV(angular_size_deg=10)
# Sensors
sensor_feedback = SensorPoint(sensor_type=SENSOR_TYPE.V, position=np.array([3, 0]))
sensor_perf_1 = SensorPoint(sensor_type=SENSOR_TYPE.V, position=np.array([3.1, 1]))
sensor_perf_2 = SensorPoint(sensor_type=SENSOR_TYPE.V, position=np.array([3.1, -1.3]))
# Gather actuators and sensors in ParamControl object
params_control = ParamControl(
    sensor_list=[sensor_feedback, sensor_perf_1, sensor_perf_2],
    actuator_list=[actuator_bc],
)

Running a closed-loop simulation

Once a use-case has been defined by implementing the corresponding class, the basic feedback syntax has the following philosophy:

# Instantiate and initialize FlowSolver object
fs = CylinderFlowSolver(...)
fs.compute_steady_state(...)
fs.initialize_time_stepping(...)

# Instantiate Controller (e.g. load from .mat file)
Kss = Controller.from_file(...)

# Time loop
y_meas = fs.y_meas
for _ in range(fs.params_time.num_steps):
    u_ctrl = Kss.step(y=-y_meas[0], dt=fs.params_time.dt)
    y_meas = fs.step(u_ctrl=u_ctrl)

See examples for a more detailed description.

Meshing tools

No meshing tools are shipped with this code, but gmsh (and its Python API) are suggested for generating meshes. The mesh should be exported to xdmf format, which can be reached thanks to meshio.

Visualization

Paraview is suggested for visualizations, whether it be for csv timeseries or fields saved as xdmf.

Additional uses of the toolbox

The toolbox provides additional utility related to flow control:

Roadmap

The current roadmap is as follows:

  • Complete the documentation 📖,
  • Refactor and release additional control-related tools,
  • Update the project to FEniCSx,
  • Sort and check all utility functions,
  • General form for operator computation,
  • Docker/venv/pip.

Contact

📫 william.jussiau@gmail.com

Also, I highly recommend FEniCS documentation, FEniCS forum (and potentially the BitBucket repository) for problems regarding FEniCS 2019.1.0 itself.


This README has been optimized for accessibility based on GitHub's blogpost "Tips for Making your GitHub Profile Page Accessible".

Clone this wiki locally