Source code for flow360.component.simulation.models.material

"""Material classes for the simulation framework."""

from typing import Literal, Optional, Union

import pydantic as pd
from numpy import sqrt

import flow360.component.simulation.units as u
from flow360.component.simulation.framework.base_model import Flow360BaseModel
from flow360.component.simulation.unit_system import (
    AbsoluteTemperatureType,
    DensityType,
    PressureType,
    SpecificHeatCapacityType,
    ThermalConductivityType,
    VelocityType,
    ViscosityType,
)


class MaterialBase(Flow360BaseModel):
    """
    Basic properties required to define a material.
    For example: young's modulus, viscosity as an expression of temperature, etc.
    """

    type: str = pd.Field()
    name: str = pd.Field()


[docs] class Sutherland(Flow360BaseModel): """ Represents Sutherland's law for calculating dynamic viscosity. This class implements Sutherland's formula to compute the dynamic viscosity of a gas as a function of temperature. Example ------- >>> fl.Sutherland( ... reference_viscosity=1.70138e-5 * fl.u.Pa * fl.u.s, ... reference_temperature=300.0 * fl.u.K, ... effective_temperature=110.4 * fl.u.K, ... ) ==== """ # pylint: disable=no-member reference_viscosity: ViscosityType.NonNegative = pd.Field( description="The reference dynamic viscosity at the reference temperature." ) reference_temperature: AbsoluteTemperatureType = pd.Field( description="The reference temperature associated with the reference viscosity." ) effective_temperature: AbsoluteTemperatureType = pd.Field( description="The effective temperature constant used in Sutherland's formula." )
[docs] @pd.validate_call def get_dynamic_viscosity( self, temperature: AbsoluteTemperatureType ) -> ViscosityType.NonNegative: """ Calculates the dynamic viscosity at a given temperature using Sutherland's law. Parameters ---------- temperature : AbsoluteTemperatureType The temperature at which to calculate the dynamic viscosity. Returns ------- ViscosityType.NonNegative The calculated dynamic viscosity at the specified temperature. """ return self.reference_viscosity * float( pow(temperature / self.reference_temperature, 1.5) * (self.reference_temperature + self.effective_temperature) / (temperature + self.effective_temperature) )
# pylint: disable=no-member, missing-function-docstring
[docs] class Air(MaterialBase): """ Represents the material properties for air. This sets specific material properties for air, including dynamic viscosity, specific heat ratio, gas constant, and Prandtl number. Example ------- >>> fl.Air( ... dynamic_viscosity=1.063e-05 * fl.u.Pa * fl.u.s ... ) ==== """ type: Literal["air"] = pd.Field("air", frozen=True) name: str = pd.Field("air") dynamic_viscosity: Union[Sutherland, ViscosityType.NonNegative] = pd.Field( Sutherland( reference_viscosity=1.716e-5 * u.Pa * u.s, reference_temperature=273.15 * u.K, # pylint: disable=fixme # TODO: validation error for effective_temperature not equal 110.4 K effective_temperature=110.4 * u.K, ), description=( "The dynamic viscosity model or value for air. Defaults to a `Sutherland` " "model with standard atmospheric conditions." ), ) @property def specific_heat_ratio(self) -> pd.PositiveFloat: """ Returns the specific heat ratio (gamma) for air. Returns ------- pd.PositiveFloat The specific heat ratio, typically 1.4 for air. """ return 1.4 @property def gas_constant(self) -> SpecificHeatCapacityType.Positive: """ Returns the specific gas constant for air. Returns ------- SpecificHeatCapacityType.Positive The specific gas constant for air. """ return 287.0529 * u.m**2 / u.s**2 / u.K @property def prandtl_number(self) -> pd.PositiveFloat: """ Returns the Prandtl number for air. Returns ------- pd.PositiveFloat The Prandtl number, typically around 0.72 for air. """ return 0.72
[docs] @pd.validate_call def get_pressure( self, density: DensityType.Positive, temperature: AbsoluteTemperatureType ) -> PressureType.Positive: """ Calculates the pressure of air using the ideal gas law. Parameters ---------- density : DensityType.Positive The density of the air. temperature : AbsoluteTemperatureType The temperature of the air. Returns ------- PressureType.Positive The calculated pressure. """ temperature = temperature.to("K") return density * self.gas_constant * temperature
[docs] @pd.validate_call def get_speed_of_sound(self, temperature: AbsoluteTemperatureType) -> VelocityType.Positive: """ Calculates the speed of sound in air at a given temperature. Parameters ---------- temperature : AbsoluteTemperatureType The temperature at which to calculate the speed of sound. Returns ------- VelocityType.Positive The speed of sound at the specified temperature. """ temperature = temperature.to("K") return sqrt(self.specific_heat_ratio * self.gas_constant * temperature)
[docs] @pd.validate_call def get_dynamic_viscosity( self, temperature: AbsoluteTemperatureType ) -> ViscosityType.NonNegative: """ Calculates the dynamic viscosity of air at a given temperature. Parameters ---------- temperature : AbsoluteTemperatureType The temperature at which to calculate the dynamic viscosity. Returns ------- ViscosityType.NonNegative The dynamic viscosity at the specified temperature. """ if temperature.units is u.degC or temperature.units is u.degF: temperature = temperature.to("K") if isinstance(self.dynamic_viscosity, Sutherland): return self.dynamic_viscosity.get_dynamic_viscosity(temperature) return self.dynamic_viscosity
[docs] class SolidMaterial(MaterialBase): """ Represents the solid material properties for heat transfer volume. Example ------- >>> fl.SolidMaterial( ... name="aluminum", ... thermal_conductivity=235 * fl.u.kg / fl.u.s**3 * fl.u.m / fl.u.K, ... density=2710 * fl.u.kg / fl.u.m**3, ... specific_heat_capacity=903 * fl.u.m**2 / fl.u.s**2 / fl.u.K, ... ) ==== """ type: Literal["solid"] = pd.Field("solid", frozen=True) name: str = pd.Field(frozen=True, description="Name of the solid material.") thermal_conductivity: ThermalConductivityType.Positive = pd.Field( frozen=True, description="Thermal conductivity of the material." ) density: Optional[DensityType.Positive] = pd.Field( None, frozen=True, description="Density of the material." ) specific_heat_capacity: Optional[SpecificHeatCapacityType.Positive] = pd.Field( None, frozen=True, description="Specific heat capacity of the material." )
aluminum = SolidMaterial( name="aluminum", thermal_conductivity=235 * u.kg / u.s**3 * u.m / u.K, density=2710 * u.kg / u.m**3, specific_heat_capacity=903 * u.m**2 / u.s**2 / u.K, )
[docs] class Water(MaterialBase): """ Water material used for :class:`LiquidOperatingCondition` Example ------- >>> fl.Water( ... name="Water", ... density=1000 * fl.u.kg / fl.u.m**3, ... dynamic_viscosity=0.001002 * fl.u.kg / fl.u.m / fl.u.s, ... ) ==== """ type: Literal["water"] = pd.Field("water", frozen=True) name: str = pd.Field(frozen=True, description="Custom name of the water with given property.") density: Optional[DensityType.Positive] = pd.Field( 1000 * u.kg / u.m**3, frozen=True, description="Density of the water." ) dynamic_viscosity: ViscosityType.NonNegative = pd.Field( 0.001002 * u.kg / u.m / u.s, frozen=True, description="Dynamic viscosity of the water." )
SolidMaterialTypes = SolidMaterial FluidMaterialTypes = Union[Air, Water]