# coding=utf-8
"""Butterfly recipes."""
import os
from copy import deepcopy
from .turbulenceProperties import TurbulenceProperties
from .transportProperties import TransportProperties
from .fvSolution import FvSolution, ResidualControl, RelaxationFactors
from .fvSchemes import FvSchemes
from .g import G
# 0 folder objects
from .U import U
from .k import K
from .p import P
from .nut import Nut
from .epsilon import Epsilon
from .T import T
from .alphat import Alphat
from .p_rgh import P_rgh
class _Recipe(object):
"""Base class for recipes.
Attributes:
commands: A list of OpenFOAM commands. You can pass arguments for each
command with each command as a string. e.g. ('blockMesh',
'snappyHexMesh -constant')
turbulenceProperties: Turbulence properties.
fvSolution: Optional input for fvSolution to overwrite default fvSolution.
fvSchemes: Optional input for fvSchemes to overwrite default fvSchemes.
quantities: A collection of strings for quantities in this solution.
('U', 'p', 'k', 'epsilon')
residualControl: A dictionary of values for residualControl of quantities.
relaxationFactors: A list of values for relaxationFactors of quantities.
"""
__foamfilescollection = {'g': G, 'U': U, 'k': K, 'p': P, 'nut': Nut,
'epsilon': Epsilon, 'T': T, 'alphat': Alphat,
'p_rgh': P_rgh,
'transportProperties': TransportProperties}
__globalConvergence = 10 ** -4
def __init__(self, commands, turbulenceProperties, fvSolution=None,
fvSchemes=None, quantities=None, residualControl=None,
relaxationFactors=None):
"""Initiate recipe."""
self.commands = commands
self.turbulenceProperties = turbulenceProperties
self.fvSolution = fvSolution
self.fvSchemes = fvSchemes
self.quantities = quantities
self.residualControl = residualControl
self.relaxationFactors = relaxationFactors
@property
def isRecipe(self):
"""Return True for recipe."""
return True
@property
def commands(self):
"""List of openfoam commands for this recipe."""
return self.__commands
@commands.setter
def commands(self, cmds):
"""List of openfoam commands for this recipe."""
if isinstance(cmds, str):
cmds = (cmds,)
assert isinstance(cmds, (tuple, list)), \
'Invalid input for commands: {}. Should be a list or a tuple.' \
.format(cmds)
self.__commands = cmds
@property
def turbulenceProperties(self):
"""Get the OpenFOAM case."""
return self.__turbulenceProperties
@turbulenceProperties.setter
def turbulenceProperties(self, tp):
"""Update turbulence file for the case."""
if not tp:
tp = TurbulenceProperties.RAS()
assert hasattr(tp, 'isTurbulenceProperties'), \
'Expected turbulencePropertise not {}.'.format(type(tp))
self.__turbulenceProperties = tp
@property
def fvSolution(self):
"""Get or set fvSolution."""
return self.__fvSolution
@fvSolution.setter
def fvSolution(self, fvsln):
if fvsln:
assert hasattr(fvsln, 'isFvSolution'), \
'fvSolution should be from type FvSolution not {}.' \
.format(type(fvsln))
self.__fvSolution = fvsln
@property
def fvSchemes(self):
"""Get fvSchemes."""
return self.__fvSchemes
@fvSchemes.setter
def fvSchemes(self, fvschm):
if fvschm:
assert hasattr(fvschm, 'isFvSchemes'), \
'fvSolution should be from type FvSchemes not {}.' \
.format(type(fvschm))
self.__fvSchemes = fvschm
@property
def quantities(self):
"""List of quantities for the recipe."""
return self.__quantities
@quantities.setter
def quantities(self, q):
"""List of quantities for the recipe."""
if not q:
q = ('p', 'U', 'k', 'epsilon')
else:
self.__quantities = q
@property
def residualControl(self):
"""A dictionary of residuals as quantity: residualValue."""
return self.__residualControl
@residualControl.setter
def residualControl(self, res):
if res is None:
res = dict.fromkeys(self.quantities, self.__globalConvergence)
# check the input to be dictionary and have values for all the input
# otherwise use default value
if isinstance(res, dict):
self.__residualControl = ResidualControl(res)
elif isinstance(res, ResidualControl):
self.__residualControl = res
else:
raise TypeError(
'residualControl should be a dictionary not a {}.'.format(type(res))
)
# update fvSolution
self.fvSolution.residualControl = self.residualControl
@property
def relaxationFactors(self):
"""A dictionary of residuals as quantity: residualValue."""
return self.__relaxationFactors
@relaxationFactors.setter
def relaxationFactors(self, relax_fact):
if relax_fact is None:
relax_fact = {}
# check the input to be dictionary and have values for all the input
# otherwise use default value
if isinstance(relax_fact, dict):
self.__relaxationFactors = RelaxationFactors(relax_fact)
elif isinstance(relax_fact, RelaxationFactors):
self.__relaxationFactors = relax_fact
else:
raise TypeError(
'relaxationFactors should be a dictionary not a {}.'
.format(type(relax_fact))
)
# update fvSolution
self.fvSolution.relaxationFactors = self.relaxationFactors
def prepare_case(self, case, overwrite=False, remove=False):
"""Prepare a case for this recipe.
This method double checks files under Zero folder for each quantities.
It creates, overwrites or removes the files if needed. Solution class
calls this method on initialization.
Args:
case: A Butterfly case.
overwrite: Set to True to overwrite current files.
remove: Set to True to remove extra files in the folder.
"""
print('Preparing {} for {} study...'.format(case, self.__class__.__name__))
if self.fvSchemes and case.fvSchemes != self.fvSchemes:
case.fvSchemes = self.fvSchemes
case.fvSchemes.save(case.project_dir)
if self.fvSolution and case.fvSolution != self.fvSolution:
case.fvSolution = self.fvSolution
case.fvSolution.save(case.project_dir)
if self.turbulenceProperties and \
case.turbulenceProperties != self.turbulenceProperties:
case.turbulenceProperties = self.turbulenceProperties
case.turbulenceProperties.save(case.project_dir)
if case.decomposeParDict:
case.decomposeParDict.save(case.project_dir)
if hasattr(case, 'probes'):
case.probes.save(case.project_dir)
if hasattr(case, 'ABLConditions'):
case.ABLConditions.save(case.project_dir, overwrite=overwrite)
if hasattr(case, 'initialConditions'):
case.initialConditions.save(case.project_dir, overwrite=overwrite)
# check neccasary files.
for q in (self.quantities + ('transportProperties', 'g')):
if not hasattr(case, q):
# try to create the quantity
assert q in self.__foamfilescollection, \
'Failed to find {0} method in {1}.' \
'{2} needs {0} foamfile to execute.'.format(
q, case, self.__class__.__name__)
case.__dict__[q] = \
self.__foamfilescollection[q].from_bf_geometries(case.geometries)
case.__dict__[q].save(case.project_dir, overwrite=overwrite)
# remove extra files.
if remove:
for f in os.listdir(case.zero_folder):
if f in self.quantities:
continue
if f.endswith('Conditions'):
# do not remove ABLConditions and initialConditions
continue
p = os.path.join(case.zero_folder, f)
if not os.path.isfile(p):
continue
try:
os.remove(p)
except Exception as e:
raise IOError('Unable to remove {}:\n{}'.format(p, e))
def duplicate(self):
"""Return a copy of this object."""
return deepcopy(self)
def ToString(self):
"""Overwrite .NET ToString method."""
return self.__repr__()
def __repr__(self):
return '{} Recipe'.format(self.__class__.__name__)
class _SingleCommandRecipe(_Recipe):
"""Recipe for recipe's with a single OpenFOAM command (e.g. simpleFoam).
Attributes:
commands: An OpenFOAM command (e.g. 'simpleFoam')
turbulenceProperties: Turbulence properties.
fvSolution: Optional input for fvSolution to overwrite default fvSolution.
fvSchemes: Optional input for fvSchemes to overwrite default fvSchemes.
quantities: A collection of strings for quantities in this solution. A
separate file will be created in 0 folder for each quantity.
residualControl: A dictionary of values for residualControl of residuals.
relaxationFactors: A list of values for relaxationFactors of residuals.
residual_fields: List of quantities that should be watched during solution
run ('Ux', 'Uy', 'Uz', 'p', 'k', 'epsilon').
"""
def __init__(self, command, turbulenceProperties, fvSolution=None,
fvSchemes=None, quantities=None, residualControl=None,
relaxationFactors=None, residual_fields=None):
"""Initiate recipe."""
_Recipe.__init__(self, (command,), turbulenceProperties, fvSolution,
fvSchemes, quantities, residualControl,
relaxationFactors)
self.__command = command
self.__residual_fields = residual_fields or ('p')
@property
def application(self):
"""OpenFOAM application."""
return self.__command
def prepare_case(self, case, overwrite=False, remove=False):
"""Prepare a case for this recipe.
This method sets up the application in control dict and double checks
files under Zero folder for each quantities. It creates, overwrites or
removes the files if needed. Solution class calls this method on
initialization.
Args:
case: A Butterfly case.
overwrite: Set to True to overwrite current files.
remove: Set to True to remove extra files in the folder.
"""
# update controlDict
if case.controlDict.application != self.application:
case.controlDict.application = self.application
case.controlDict.save(case.project_dir)
super(_SingleCommandRecipe, self).prepare_case(case, overwrite, remove)
@property
def residual_fields(self):
"""List of values for residual plot."""
return self.__residual_fields
@property
def log_file(self):
"""Return log file name."""
return '%s.log' % self.application
@property
def err_file(self):
"""Return error file name."""
return '%s.err' % self.application
[docs]class SteadyIncompressible(_SingleCommandRecipe):
"""Recipe for Steady Incompressible flows.
This recipe excutes simpleFoam for the input case.
Attributes:
turbulenceProperties: Turbulence properties.
fvSolution: Optional input for fvSolution to overwrite default fvSolution.
fvSchemes: Optional input for fvSchemes to overwrite default fvSchemes.
residualControl: A dictionary of values for residualControl of quantities.
relaxationFactors: A list of values for relaxationFactors of quantities.
"""
__command = 'simpleFoam'
# foam files in zero folder
__quantities = ('epsilon', 'k', 'nut', 'U', 'p')
# Values for residual plot.
__residual_fields = ('Ux', 'Uy', 'Uz', 'p', 'k', 'epsilon')
def __init__(self, turbulenceProperties=None, fvSolution=None,
fvSchemes=None, residualControl=None, relaxationFactors=None):
"""Initiate recipe."""
turbulenceProperties = turbulenceProperties or TurbulenceProperties.RAS()
# add inputs here, and initiate the class.
fvSolution = fvSolution or FvSolution.from_recipe(0)
fvSchemes = fvSchemes or FvSchemes.from_recipe(0)
quantities = self.__quantities
_SingleCommandRecipe.__init__(
self, self.__command, turbulenceProperties, fvSolution, fvSchemes,
quantities, residualControl, relaxationFactors, self.__residual_fields)
[docs]class HeatTransfer(_SingleCommandRecipe):
"""Recipe for heat transfer.
This recipe excutes buoyantBoussinesqSimpleFoam for the input case.
Attributes:
turbulenceProperties: Turbulence properties.
fvSolution: Optional input for fvSolution to overwrite default fvSolution.
fvSchemes: Optional input for fvSchemes to overwrite default fvSchemes.
residualControl: A dictionary of values for residualControl of quantities.
relaxationFactors: A list of values for relaxationFactors of quantities.
"""
__command = 'buoyantBoussinesqSimpleFoam'
# foam files in zero folder
__quantities = ('alphat', 'epsilon', 'k', 'nut', 'p_rgh', 'T', 'U')
# values for residual plot.
__residual_fields = ('Ux', 'Uy', 'Uz', 'p_rgh', 'T', 'k', 'epsilon')
def __init__(self, turbulenceProperties=None, fvSolution=None,
fvSchemes=None, residualControl=None, relaxationFactors=None,
TRef=None):
"""Initiate recipe."""
turbulenceProperties = turbulenceProperties or TurbulenceProperties.RAS()
# add inputs here, and initiate the class.
fvSolution = fvSolution or FvSolution.from_recipe(1)
fvSchemes = fvSchemes or FvSchemes.from_recipe(1)
self.temperature = TRef or 300
quantities = self.__quantities
_SingleCommandRecipe.__init__(
self, self.__command, turbulenceProperties, fvSolution, fvSchemes,
quantities, residualControl, relaxationFactors, self.__residual_fields)
[docs] def prepare_case(self, case, overwrite=False, remove=False):
"""Prepare a case for this recipe.
This method sets up the application in control dict and double checks
files under Zero folder for each quantities. It creates, overwrites or
removes the files if needed. Solution class calls this method on
initialization.
Args:
case: A Butterfly case.
overwrite: Set to True to overwrite current files.
remove: Set to True to remove extra files in the folder.
"""
# update pRefPoint to center locationInMesh for snappyHexMeshDict
self.fvSolution.values['SIMPLE']['pRefPoint'] = \
str(case.snappyHexMeshDict.locationInMesh).replace(',', ' ')
# updated TRef if it is provided
case.T.update_values({'internalField': 'uniform %f' % self.temperature})
super(HeatTransfer, self).prepare_case(case, overwrite, remove)