Source code for armi.matProps.function

# Copyright 2026 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Generic class for a function to be defined in a YAML."""


[docs] class Function: """ An base class for computing material Properties. The word "function" here is used in the mathematical sense, to describe a generic mathematical curve. The various Function types are read in from YAML, and interpreted at run time. The sub-classes of Function have specific requirements on the YAML format. """ # This is the list of all nodes that are associated with functions in the YAML input file. Any node named something # not in this list is assumed to be an independent variable for the function. This list needs to remain updated if # any child class adds a new YAML node. FUNCTION_NODES = { "type", # All equations have this to define the child class type "tabulated data", # Optional for all equations, required for table functions "equation", # Used by SymbolicFunction for the equation definition "functions", # Used by PiecewiseFunction to define the child functions "reference temperature", # Optional for all equations } def __init__(self, mat, prop): """ Constructor for base Function class. Parameters ---------- mat: Material Material object with which this Function is associated prop: Property Property that is represented by this Function """ self.material = mat """A pointer back to the parent Material for this Function.""" self.property = prop """The Property this Function represents.""" self.independentVars: dict = {} # Keys are the independent variables, values are a tuple of the min/max bounds self.tableData = None """A TableFunction containing verification data for this specific function. Note that for actual TableFunction instances, the tableData property is NULL.""" self._referenceTemperature: float = -274.0 """Reference temperature. Initialized be less than absolute zero in degrees Celsius""" self._references = [] """Reference data"""
[docs] def clear(self): self.tableData = None
[docs] @staticmethod def isTable(): """Returns True if a subclass of TableFunction, otherwise False.""" return False
[docs] def getReferenceTemperature(self): """ Returns the reference temperature, in Celcius, if it is defined. Returns ------- float Reference temperature, in Celcius """ # If this statement below is true, either the reference temperature was not provided in the material YAML file # or was a non-physical value. if self._referenceTemperature < -273.15: raise ValueError("Reference temperature is undefined or set to less than absolute zero.") return self._referenceTemperature
[docs] def getIndependentVariables(self): """ Returns the independent variables that are required for this function. Returns ------- list list of independent variable strings """ return list(self.independentVars.keys())
[docs] def getMinBound(self, var) -> float: """ Returns the minimum bound for the requested variable. Returns ------- float Minimum valid value """ return self.independentVars[var][0]
[docs] def getMaxBound(self, var) -> float: """ Returns the minimum bound for the requested variable. Returns ------- float Maximum valid value """ return self.independentVars[var][1]
@property def references(self) -> list: return self._references
[docs] def calc(self, point: dict = None, **kwargs): """ Calculate the quantity of a specific Property. The user must provide a "point" dictionary, or kwargs, but not both or neither. Parameters ---------- point: dict dictionary of independent variable/value pairs kwargs: dictionary of independent variable/value pairs, same purpose but to allow a nicer API. Returns ------- float property evaluation """ # This method should take in one dictionary or a set of kwargs, but not both if point is not None and kwargs: raise ValueError("Please provide either a single dictionary or a set of kwargs, but not both.") elif point is None and not kwargs: raise ValueError("Please provide at least one input to this method.") # select the inputs provided if point: data = point else: data = kwargs # input sanity checking if not self.independentVars.keys() <= data.keys(): raise KeyError( f"Specified point {data} does contain the correct independent variables: {self.independentVars}" ) elif not self.inRange(data): raise ValueError(f"Requested calculation point, {data} is not in the valid range of the function") return self._calcSpecific(data)
[docs] def inRange(self, point: dict) -> bool: """ Determine if a point is within range of the function. Parameters ---------- point: dict dictionary of independent variable/value pairs Returns ------- bool True if the point is in the valid range, False otherwise. """ for var, bounds in self.independentVars.items(): if point[var] < bounds[0] or point[var] > bounds[1]: return False return True
def __repr__(self): """Provides string representation of Function object.""" return f"<{self.__class__.__name__}>" @staticmethod def _factory(mat, node, prop): """ Parsing a property node and using that information to construct a Function object. This method is responsible for searching for the assigning the Function object to the appropriate child class instance. Parameters ---------- mat: Material Material object which is associated with the returned Function object node: dict YAML object representing root level node of material yaml file being parsed prop: Property Property object that is being populated on the Material Returns ------- Function Function pointer parsed from the specified property. """ from armi.matProps.piecewiseFunction import PiecewiseFunction from armi.matProps.symbolicFunction import SymbolicFunction from armi.matProps.tableFunction1D import TableFunction1D from armi.matProps.tableFunction2D import TableFunction2D funTypes = { "symbolic": SymbolicFunction, "table": TableFunction1D, "two dimensional table": TableFunction2D, "piecewise": PiecewiseFunction, } funcNode = node["function"] funcType = str(funcNode["type"]) func = funTypes[funcType](mat, prop) func._parse(node) return func def _setBounds(self, node: dict, var: str): """ Validate and set the min and max bounds for a variable. Parameters ---------- node: dict dictionary that contains min and max values. var: str name of the variable """ if "min" not in node or "max" not in node: raise KeyError( f"The independent variable node, {var}, is not formatted correctly: {node}. If this node is not " "intended to be an independent variable, please ensure that the Function.FUNCTION_NODES set is updated " "properly." ) minVal = float(node["min"]) maxVal = float(node["max"]) if maxVal < minVal: raise ValueError(f"Maximum bound {maxVal} cannot be less than the minimum bound {minVal}") self.independentVars[var] = (minVal, maxVal) def _parse(self, node): """ Method used to parse property node and fill in appropriate Function data members. Parameters ---------- node YAML containing object to be parsed """ from armi.matProps.reference import Reference from armi.matProps.tableFunction1D import TableFunction1D from armi.matProps.tableFunction2D import TableFunction2D funcNode = node["function"] refTempNode = funcNode.get("reference temperature", None) if refTempNode is not None: self._referenceTemperature = float(refTempNode) funcType = str(funcNode["type"]) references = node.get("references", []) for ref in references: self._references.append(Reference._factory(ref)) tabulatedNode = node.get("tabulated data", None) if tabulatedNode: if funcType == "two dimensional table": self.tableData = TableFunction2D(self.material, self.property) else: self.tableData = TableFunction1D(self.material, self.property) if self.isTable(): self._parseSpecific(node) self.tableData = self else: self.tableData._parseSpecific(node) elif self.isTable(): raise KeyError("Missing node `tabulated data`") for var in funcNode: if var not in self.FUNCTION_NODES: self._setBounds(funcNode[var], var) if not self.isTable(): self._parseSpecific(node) def _parseSpecific(self, node): """ Abstract method that is used to parse information specific to Function child classes. Parameters ---------- node YAML containing object information to parse and fill in Function """ raise NotImplementedError() def _calcSpecific(self, point: dict) -> float: """ Private method that contains the analytic expression used to return a property value. Parameters ---------- point : dict dictionary of independent variable/value pairs Returns ------- float property evaluation at specified independent variable point """ raise NotImplementedError()