-
Notifications
You must be signed in to change notification settings - Fork 1
Code: basics
Use of the _prefix for methods that are not intended to be used outside of the body of a 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:
-
_make_boundariesprovides a definition and naming of the boundaries of the mesh in a pandas DataFrame.
@abstractmethod
def _make_boundaries(self) -> pd.DataFrame:
pass-
_make_bcsprovides a description of the boundary conditions on the boundaries defined above, in a dictionary.
@abstractmethod
def _make_bcs(self) -> dict[str, Any]:
passFor the two aforementioned examples, these methods are reimplemented in the classes CylinderFlowSolver and CavityFlowSolver that inherit from FlowSolver.
You also need to be able to provide a mesh that is consistent with your definition of boundaries
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.
-
Sensoris an abstract class that provides a methodeval(self, up: dolfin.Function) -> float. ClassesSensorPoint(point probe) andSensorIntegral(integration on a subdomain) are examples of subclasses that implement theevalmethod. - Likewise,
Actuatoris an abstract class that encapsulates adolfin.Expressionamong 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],
)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.
Mesh as xdmf.
User is responsible for mesh coherence with respect to boundary conditions.
Timeline of function calls at __init__ for example.
- good practice is to do Picard iterations, then Newton
_default_steady_state_initial_guess(self) -> dolfin.UserExpression_default_initial_perturbation(self, xloc: float = 0.0, yloc: float = 0.0, radius: float = 1.0) -> dolfin.Functionuser_data: dict
Their contribution does not go into the same function:
- FORCE is seamless
- BC goes somewhere in _make_bcs() and should be handled by the user. An example can be found in
examples/cylinder/cylinderflowsolver.pyin_make_bcs(), as follows:
def _make_bcs(self):
...
bcu_actuation_up = dolfin.DirichletBC(
self.W.sub(0),
self.params_control.actuator_list[0].expression,
self.get_subdomain["actuator_up"],
)
bcu_actuation_lo = dolfin.DirichletBC(
self.W.sub(0),
self.params_control.actuator_list[1].expression,
self.get_subdomain["actuator_lo"],
)
...
return BoundaryConditions(bcu=bcu, bcp=[])User is responsible for parallel execution when implementing new sensors.
control.StateSpace encapsulating a current state x and potentially a file
Wrapper around control.StateSpace.forced_response
Powered by GitHub