"""
pyStrut_problem
"""
# =============================================================================
# Imports
# =============================================================================
from ..utils import Error, SolverHistory
[docs]
class StructProblem:
    """
    The main purpose of this class is to represent all relevant
    information for a structural analysis. This will include
    information defining the loading condition as well as various
    other pieces of information.
    Parameters
    ----------
    name : str
        Name of this structural problem
    loadFile : str
        Filename of the (static) external load file. Should be
        generated from either ADflow or Tripan.
    Examples
    --------
    >>> sp = StructProblem('lc0', loadFile='loads.txt')
    """
    def __init__(self, name, loadFile=None, loadFactor=None, evalFuncs=None):
        # Always have to have the name
        self.name = name
        self.loadFile = loadFile
        # Set defaults for loadFactor and evalFuncs if not supplied
        if loadFactor is None:
            self.loadFactor = 1.0
        else:
            self.loadFactor = loadFactor
        if evalFuncs is None:
            self.evalFuncs = set()
        else:
            self.evalFuncs = set(evalFuncs)
        # we cast the set to a sorted list, so that each proc can loop over in the same order
        self.evalFuncs = sorted(self.evalFuncs)
        # When a solver calls its evalFunctions() it must write the
        # unique name it gives to funcNames.
        self.funcNames = {}
        self.possibleFunctions = set()
        # Storage of DVs (non as of yet)
        self.DVs = {}
        self.DVNames = {}
        # Solver History
        self.history = SolverHistory()
[docs]
    def addDV(self, key, value=None, lower=None, upper=None, scale=1.0, name=None):
        """
        No design variable functions yet.
        Parameters
        ----------
        key : str
            Name of variable to add. See above for possible ones
        value : float. Default is None
            Initial value for variable. If not given, current value
            of the attribute will be used.
        lower : float. Default is None
            Optimization lower bound. Default is unbonded.
        upper : float. Default is None
            Optimization upper bound. Default is unbounded.
        scale : float. Default is 1.0
            Set scaling parameter for the optimization to use.
        name : str. Default is None
            Overwrite the default auto-generated name of this variable.
        """
        # First check if we are allowed to add the DV:
        if key not in self.possibleDVs:
            raise Error(f"The DV '{key}' could not be added. The list of possible DVs are: {self.possibleDVs!r}.")
        if name is None:
            dvName = f"{key}_{self.name}"
        else:
            dvName = name
        if value is None:
            raise Error("Value must be given for keyword 'value'.")
        self.DVs[dvName] = structDV(key, value, lower, upper, scale, offset)  # noqa
        self.DVNames[key] = dvName 
[docs]
    def setDesignVars(self, x):
        """
        Set the variables in the x-dict for this object.
        Parameters
        ----------
        x : dict
            Dictionary of variables which may or may not contain the
            design variable names this object needs
        """
        for key in self.DVNames:
            dvName = self.DVNames[key]
            if dvName in x:
                setattr(self, key, x[dvName] + self.DVs[dvName].offset) 
[docs]
    def addVariablesPyOpt(self, optProb):
        """
        Add the current set of variables to the optProb object.
        Parameters
        ----------
        optProb : pyOpt_optimization class
            Optimization problem definition to which variables are added
        """
        for key in self.DVs:
            dv = self.DVs[key]
            optProb.addVar(key, "c", value=dv.value, lower=dv.lower, upper=dv.upper, scale=dv.scale) 
    def __getitem__(self, key):
        return self.funcNames[key]
[docs]
    def evalFunctions(self, funcs, evalFuncs, ignoreMissing=False):
        """
        No current functions
        Parameters
        ----------
        funcs : dict
            Dictionary into which the functions are save
        evalFuncs : iterable object containing strings
            The functions that the user wants evaluated
        """
        if set(evalFuncs) <= self.possibleFunctions:
            # All the functions are ok:
            for f in evalFuncs:
                # Save the key into funcNames
                key = f"{self.name}_{f}"
                self.funcNames[f] = key
                funcs[key] = getattr(self, f)
        else:
            if not ignoreMissing:
                raise Error(
                    "One of the functions in 'evalFuncs' was not "
                    "valid. The valid list of functions is: %s." % (repr(self.possibleFunctions))
                ) 
[docs]
    def evalFunctionsSens(self, funcsSens, evalFuncs, ignoreMissing=False):
        """
        Evaluate the sensitivity of the desired functions
        Parameters
        ----------
        funcsSens : dict
            Dictionary into which the function sensitivities are saved
        evalFuncs : iterable object containing strings
            The functions that the user wants evaluated
        """
        # Make sure all the functions have been evaluated.
        tmp = {}
        self.evalFunctions(tmp, evalFuncs, ignoreMissing)
        # Check that all functions are ok:
        if set(evalFuncs) <= self.possibleFunctions:
            for f in evalFuncs:
                funcsSens[self.funcNames[f]] = self._getDVSens(f)
        else:
            if not ignoreMissing:
                raise Error(
                    "One of the functions in 'evalFunctionsSens' was "
                    "not valid. The valid list of functions is: %s." % (repr(self.possibleFunctions))
                ) 
 
class structDV:
    """
    A container storing information regarding an 'structral problem' variable.
    """
    def __init__(self, key, value, lower, upper, scale, offset):
        self.key = key
        self.value = value
        self.lower = lower
        self.upper = upper
        self.scale = scale
        self.offset = offset