"""Time stepping setting for simulation"""
from typing import Literal, Optional, Union
import pydantic as pd
from flow360.component.simulation.framework.base_model import Flow360BaseModel
from flow360.component.simulation.unit_system import TimeType
from flow360.component.simulation.user_code.core.types import ValueOrExpression
def _apply_default_to_none(original, default):
for field_name, value in original.model_dump().items():
if value is None:
setattr(original, field_name, default.model_dump()[field_name])
return original
[docs]
class RampCFL(Flow360BaseModel):
"""
:class:`RampCFL` class for the Ramp CFL setting of time stepping.
Example
-------
>>> fl.RampCFL(initial=1, final=200, ramp_steps=200)
====
"""
type: Literal["ramp"] = pd.Field("ramp", frozen=True)
initial: Optional[pd.PositiveFloat] = pd.Field(
None, description="Initial CFL for solving pseudo time step."
)
final: Optional[pd.PositiveFloat] = pd.Field(
None, description="Final CFL for solving pseudo time step."
)
ramp_steps: Optional[pd.PositiveInt] = pd.Field(
None,
description="Number of pseudo steps before reaching :py:attr:`RampCFL.final` within 1 physical step.",
)
[docs]
@classmethod
def default_unsteady(cls):
"""
returns default unsteady Ramp CFL settings
"""
return cls(initial=1, final=1e6, ramp_steps=30)
[docs]
@classmethod
def default_steady(cls):
"""
returns default steady Ramp CFL settings
"""
return cls(initial=5, final=200, ramp_steps=40)
[docs]
class AdaptiveCFL(Flow360BaseModel):
"""
:class:`AdaptiveCFL` class for Adaptive CFL setting of time stepping.
Example
-------
- Set up Adaptive CFL with convergence limiting factor:
>>> fl.AdaptiveCFL(convergence_limiting_factor=0.5)
- Set up Adaptive CFL with max relative change:
>>> fl.AdaptiveCFL(
... min=1,
... max=100000,
... max_relative_change=50
... )
====
"""
type: Literal["adaptive"] = pd.Field("adaptive", frozen=True)
min: pd.PositiveFloat = pd.Field(
default=0.1, description="The minimum allowable value for Adaptive CFL."
)
max: Optional[pd.PositiveFloat] = pd.Field(
None, description="The maximum allowable value for Adaptive CFL."
)
max_relative_change: Optional[pd.PositiveFloat] = pd.Field(
None,
description="The maximum allowable relative change of CFL (%) at each pseudo step. "
+ "In unsteady simulations, the value of :py:attr:`AdaptiveCFL.max_relative_change` "
+ "is updated automatically depending on how well the solver converges in each physical step.",
)
convergence_limiting_factor: Optional[pd.PositiveFloat] = pd.Field(
None,
description="This factor specifies the level of conservativeness when using Adaptive CFL. "
+ "Smaller values correspond to a more conservative limitation on the value of CFL.",
)
[docs]
@classmethod
def default_unsteady(cls):
"""
returns default unsteady Adaptive CFL settings
"""
return cls(max=1e6, convergence_limiting_factor=1.0, max_relative_change=50)
[docs]
@classmethod
def default_steady(cls):
"""
returns default steady Adaptive CFL settings
"""
return cls(max=1e4, convergence_limiting_factor=0.25, max_relative_change=1)
[docs]
class Steady(Flow360BaseModel):
"""
:class:`Steady` class for specifying steady simulation.
Example
-------
>>> fl.Steady(
... CFL=fl.RampCFL(initial=1, final=200, ramp_steps=200),
... max_steps=6000,
... )
====
"""
type_name: Literal["Steady"] = pd.Field("Steady", frozen=True)
max_steps: int = pd.Field(2000, gt=0, le=100000, description="Maximum number of pseudo steps.")
# pylint: disable=duplicate-code
CFL: Union[RampCFL, AdaptiveCFL] = pd.Field(
default=AdaptiveCFL.default_steady(), description="CFL settings."
)
@pd.model_validator(mode="before")
@classmethod
def set_default_cfl(cls, values):
"""
Populate CFL's None fields with default
"""
if "CFL" not in values:
return values # will be handled by default value
cfl_input = values["CFL"]
if isinstance(cfl_input, AdaptiveCFL):
cfl_input = _apply_default_to_none(cfl_input, AdaptiveCFL.default_steady())
elif isinstance(cfl_input, RampCFL):
cfl_input = _apply_default_to_none(cfl_input, RampCFL.default_steady())
return values
[docs]
class Unsteady(Flow360BaseModel):
"""
:class:`Unsteady` class for specifying unsteady simulation.
Example
-------
>>> fl.Unsteady(
... CFL=fl.AdaptiveCFL(
... convergence_limiting_factor=0.5
... ),
... step_size=0.01 * fl.u.s,
... steps=120,
... max_pseudo_steps=35,
... )
====
"""
type_name: Literal["Unsteady"] = pd.Field("Unsteady", frozen=True)
max_pseudo_steps: int = pd.Field(
20, gt=0, le=100000, description="Maximum pseudo steps within one physical step."
)
steps: pd.PositiveInt = pd.Field(description="Number of physical steps.")
# pylint: disable=no-member
step_size: ValueOrExpression[TimeType.Positive] = pd.Field(
description="Time step size in physical step marching,"
)
# pylint: disable=duplicate-code
CFL: Union[RampCFL, AdaptiveCFL] = pd.Field(
default=AdaptiveCFL.default_unsteady(),
description="CFL settings within each physical step.",
)
order_of_accuracy: Literal[1, 2] = pd.Field(2, description="Temporal order of accuracy.")
@pd.model_validator(mode="before")
@classmethod
def set_default_cfl(cls, values):
"""
Populate CFL's None fields with default
"""
if "CFL" not in values:
return values # will be handled by default value
cfl_input = values["CFL"]
if isinstance(cfl_input, AdaptiveCFL):
cfl_input = _apply_default_to_none(cfl_input, AdaptiveCFL.default_unsteady())
elif isinstance(cfl_input, RampCFL):
cfl_input = _apply_default_to_none(cfl_input, RampCFL.default_unsteady())
return values