class Layout(Structure): style = String() title = String() x_label = String() x_ticks = List() y_label = String() y_ticks = List() xlim = Tuple() ylim = Tuple()
class FillRegion(Structure): color_index = Integer() start = UnsignedFloat() end = UnsignedFloat() y_max = UnsignedFloat() text = String()
class ComponentSystem(metaclass=StructMeta): name = String() def __init__(self, name=None, n_comp=None): self.name = name self._components = [] if n_comp is not None: for comp in range(n_comp): self.add_component() @property def components(self): return self._components @property def n_components(self): return len(self.components) @property def n_comp(self): return sum([comp.n_species for comp in self.components]) def add_component(self, *args, **kwargs): component = Component(*args, **kwargs) self._components.append(component) @property def labels(self): labels = [] index = 0 for comp in self.components: for label in comp.labels: if label is None: labels.append(str(index)) else: labels.append(label) index += 1 return labels @property def charges(self): charges = [] for comp in self.components: charges += comp.charges return charges
class Component(Structure): name = String() species = List() charges = DependentlySizedList(dep='n_species') molecular_weight = DependentlySizedList(dep='n_species') @property def n_species(self): if self.species is None: return 1 else: return len(self.species) @property def labels(self): if self.species is None: return [self.name] else: return self.species
class OptimizationProblem(metaclass=StructMeta): """Class for configuring optimization problems Defines lists, dictionaries and variables for creating an OptimizationProblem. If no name is set it tries to set the name by the evaluation_object name. An excepted AttributeError is ignored. Attributes ---------- name : str Name of the optimization problem evaluation_object : obj Object containing parameters to be optimized. evaluator : obj Object used to evaluate evaluation_object. Returns performance. variables : list List of optimization variables objectives: list of callables Functions that return value of objective function for performance. nonlinear_constraints: list of callables Functions that return value of nonlinear constraints for performance. linear_constraints : list List of all linear constraints of an OptimizationProblem. linear_equality_constraints : list List with all linear equality constrains of an OptimizationProblem. eval_dict : dict Database for storing evaluated individuals """ name = String() def __init__(self, evaluation_object, evaluator=None, name=None, save_log=False): if evaluator is not None: self.evaluator = evaluator else: self._evaluator = None self.evaluation_object = evaluation_object if name is None: try: name = evaluation_object.name + '_optimization' except AttributeError: msg = '__init__() missing 1 required positional argument: \'name\'' raise TypeError(msg) self.name = name if save_log: self.logger = log.get_logger(self.name, log_directory=self.name) else: self.logger = log.get_logger(self.name) self._variables = [] self._objectives = [] self._nonlinear_constraints = [] self._linear_constraints = [] self._linear_equality_constraints = [] self._x0 = None @property def evaluation_object(self): """obj : Object that contains all parameters that are optimized. Returns ------- evaluation_object : obj Object to be evaluated during optimization. See also -------- OptimizatonVariable Evaluator evaluate Performance objectives nonlinear_constraints """ return self._evaluation_object @evaluation_object.setter def evaluation_object(self, evaluation_object): self._evaluation_object = evaluation_object @property def evaluator(self): """Object that performs evaluation object during optimization. The evaluator has to implement an evaluate method that returns a Performance object when called with the evaluation_object. The performance Object is required for calculating the objective function and nonlinear constraint function. Parameters ---------- evaluator : obj Object to be evaluated during optimization. Raises ------ CADETProcessError If evaluation object does not implement evaluation method. See also -------- evaluation_object evaluate Performance objectives nonlinear_constraints """ return self._evaluator @evaluator.setter def evaluator(self, evaluator): try: getattr(evaluator, 'evaluate') except TypeError: raise TypeError('Evaluator has to implement evaluate method') self._evaluator = evaluator @property def variables(self): """list: List of all optimization variables. """ return self._variables @property def variables_dict(self): """Returns a dictionary with all events in a process. Returns ------- events_dict : dict Dictionary with all events and durations, indexed by Event.name. """ return {var.name: var for var in self._variables} @property def n_variables(self): """int: Number of optimization variables. """ return len(self.variables) def add_variable(self, parameter_path, name=None, lb=-math.inf, ub=math.inf, component_index=None): """Add optimization variable to the OptimizationProblem. The function encapsulates the creation of OoptimizationVariable objects in order to prevent invalid OptimizationVariables. Parameters ---------- attr : str Attribute of the variable to be added. name : str Name of the variable to be added, default value is None. lb : float Lower bound of the variable value. ub : float Upper bound of the variable value. component_index : int Index for compnent specific variables Raises ------ CADETProcessError If the Variable already exists in the dictionary. See also -------- evaluation_object OptimizationVariable remove_variable """ if name is None: name = parameter_path if name in self.variables_dict: raise CADETProcessError("Variable already exists") var = OptimizationVariable(name, self.evaluation_object, parameter_path, lb=lb, ub=ub, component_index=component_index) self._variables.append(var) super().__setattr__(name, var) def remove_variable(self, var_name): """Removes variables from the OptimizationProblem. Parameters ---------- var_name : str Name of the variable to be removed from list of variables. Raises ------ CADETProcessError If required variable does not exist. See also -------- add_variable """ try: var = self.variables_dict[var_name] except KeyError: raise CADETProcessError("Variable does not exist") self._variables.remove(var) self.__dict__.pop(var_name) def set_variables(self, x, make_copy=False): """Sets the values from the x-vector to the OptimizationVariables. Parameters ---------- x : array_like Value of the optimization variables make_copy : Bool If True, a copy of the evaluation_object attribute is made on which the values are set. Otherwise, the values are set on the attribute. Returns ------- evaluation_object : object Returns copy of evaluation object if make_copy is True, else returns the attribute evaluation_object with the values set. Raises ------ ValueError If value of variable exceeds bounds See also -------- OptimizationVariable evaluate """ if len(x) != self.n_variables: raise CADETProcessError('Expected {} variables'.format( self.n_variables)) if make_copy: evaluation_object = copy.deepcopy(self.evaluation_object) else: evaluation_object = self.evaluation_object for variable, value in zip(self.variables, x): if value < variable.lb: raise ValueError("Exceeds lower bound") if value > variable.ub: raise ValueError("Exceeds upper bound") if variable.component_index is not None: value_list = get_nested_value(evaluation_object.parameters, variable.parameter_path) value_list[variable.component_index] = value parameters = generate_nested_dict(variable.parameter_path, value_list) else: parameters = generate_nested_dict(variable.parameter_path, value) evaluation_object.parameters = parameters return evaluation_object def evaluate(self, x, make_copy=False, cache=None, force=False): """Evaluates the evaluation object at x. For performance reasons, the function first checks if the function has already been evaluated at x and returns the results. If force is True, the lookup is ignored and the function is evaluated regularly. Parameters ---------- x : array_like Value of the optimization variables force : bool If True, reevaluation of previously evaluated values is enforced. Returns ------- performance : Performance Performance object from fractionation. """ x = np.array(x) # Try to get cached results if cache is not None and not force: try: performance = cache[tuple(x.tolist())] return performance except KeyError: pass # Get evaluation_object evaluation_object = self.set_variables(x, make_copy) # Set bad results if constraints are not met if not self.check_linear_constraints(x): self.logger.warn( f'Linear constraints not met at {x}. Returning bad performance.\ cycle time: {evaluation_object.process_meta.cycle_time}') performance = get_bad_performance(evaluation_object.n_comp) # Pass evaluation_object to evaluator and evaluate elif self.evaluator is not None: try: performance = self.evaluator.evaluate(evaluation_object) except CADETProcessError: self.logger.warn( 'Evaluation failed. Returning bad performance') performance = get_bad_performance(evaluation_object.n_comp) else: performance = evaluation_object.performance if cache is not None: cache[tuple(x.tolist())] = performance self.logger.info('{} evaluated at x={} yielded {}'.format( self.evaluation_object.name, tuple(x.tolist()), performance.to_dict())) return performance def evaluate_population(self, population, n_cores=0): manager = multiprocess.Manager() cache = manager.dict() eval_fun = lambda ind: self.evaluate(ind, make_copy=True, cache=cache) if n_cores == 1: for ind in population: try: eval_fun(ind) except CADETProcessError: print(ind) else: if n_cores == 0: n_cores = None with pathos.multiprocessing.ProcessPool(ncpus=n_cores) as pool: pool.map(eval_fun, population) return cache @property def objectives(self): return self._objectives @property def n_objectives(self): return len(self.objectives) def add_objective(self, objective_fun): """Add objective function to optimization problem. Parameters ---------- objective_fun : function Objective function. Funtion should take a Performance object as argument and return a scalar value. Raises ------ TypeError If objective_fun is not callable. """ if not callable(objective_fun): raise TypeError("Expected callable object") self._objectives.append(objective_fun) def evaluate_objectives(self, x, *args, **kwargs): """Function that evaluates at x and computes objective function. This function is usually presented to the optimization solver. The actual evaluation of the evaluation object is peformed by the evaluate function which also logs the results. Parameters ---------- x : array_like Value of the optimization variables. Returns ------- f : list Values of the objective functions at point x. See Also -------- optimization.SolverBase add_objective evaluate evaluate_nonlinear_constraints """ performance = self.evaluate(x, *args, **kwargs) f = np.array([obj(performance) for obj in self.objectives]) return f def objective_gradient(self, x, dx=0.1): """Calculate the gradient of the objective functions at point x. Gradient is approximated using finite differences. Parameters ---------- x : ndarray Value of the optimization variables. dx : float Increment to x to use for determining the function gradient. Returns ------- grad: list The partial derivatives of the objective functions at point x. See Also -------- OptimizationProblem objectives """ dx = [dx] * len(x) grad = [optimize.approx_fprime(x, obj, dx) for obj in self.objectives] return grad @property def nonlinear_constraints(self): return self._nonlinear_constraints @property def n_nonlinear_constraints(self): return len(self.nonlinear_constraints) def add_nonlinear_constraint(self, nonlinear_constraint_fun): """Add nonlinear constraint function to optimization problem. Parameters ---------- nonlinear_constraint_fun: function Nonlinear constraint function. Funtion should take a Performance object as argument and return a scalar value or an array. Raises ------ TypeError If nonlinear_constraint_fun is not callable. """ if not callable(nonlinear_constraint_fun): raise TypeError("Expected callable object") self._nonlinear_constraints.append(nonlinear_constraint_fun) def evaluate_nonlinear_constraints(self, x, *args, **kwargs): """Function that evaluates at x and computes nonlinear constraitns. This function is usually presented to the optimization solver. The actual evaluation of the evaluation object is peformed by the evaluate function which also logs the results. Parameters ---------- x : array_like Value of the optimization variables. Returns ------- c : list Value(s) of the constraint functions at point x. See also -------- nonlinear_constraints evaluate evaluate_objectives """ performance = self.evaluate(x, *args, **kwargs) c = np.array( [constr(performance) for constr in self.nonlinear_constraints]) return c def check_nonlinear_constraints(self, x): """Checks if all nonlinear constraints are kept. Parameters ---------- x : array_like Value of the optimization variables. Returns ------- flag : bool True if all nonlinear constraints are smaller or equal to zero, False otherwise. """ c = self.evaluate_nonlinear_constraints(x) for constr in c: if np.any(constr > 0): return False return True def nonlinear_constraint_jacobian(self, x, dx=1e-3): """Return the jacobian matrix of the nonlinear constraints at point x. Parameters ---------- x : array_like Value of the optimization variables dx : float Increment to x to use for determining the function gradient. Returns ------- jacobian: list Value of the partial derivatives at point x. See also -------- nonlinear_constraint_fun approximate_jac """ jacobian = [ approximate_jac(x, constr, dx) for constr in self.nonlinear_constraints ] return jacobian @property def lower_bounds(self): """list : List of the lower bounds of all OptimizationVariables. See also -------- upper_bounds """ return [var.lb for var in self.variables] @property def upper_bounds(self): """list : List of the upper bounds of all OptimizationVariables. See also -------- upper_bounds """ return [var.ub for var in self.variables] def check_bounds(self, x): """Checks if all bound constraints are kept. Parameters ---------- x : array_like Value of the optimization variables First sets a local variable named flag to True. Then checks if the values of the list x are exceeding the lower and upper bounds and sets the value of the flag to False. For this the list x is converted into an np.array. Returns ------- flag : Bool Returns True, if the values of the list x are in the defined bounds. Returns False if the values of the list x are violating the bound constraints. """ flag = True if np.any(np.less(x, self.lower_bounds)): flag = False if np.any(np.greater(x, self.upper_bounds)): flag = False return flag @property def linear_constraints(self): """list : linear inequality constraints of OptimizationProblem See Also -------- add_linear_constraint remove_linear_constraint linear_equality_constraints """ return self._linear_constraints @property def n_linear_constraints(self): """int: number of linear inequality constraints """ return len(self.linear_constraints) def add_linear_constraint(self, opt_vars, factors, b=0): """Add linear inequality constraints. Parameters ----------- opt_vars : list of strings Names of the OptimizationVariable to be added. factors : list of integers Factors for OptimizationVariables. b : float, optional Constraint of inequality constraint; default set to zero. Raises ------- CADETProcessError If optimization variables do not exist. If length of factors does not match length of optimization variables. See also --------- linear_constraints remove_linear_constraint linear_equality_constraints """ if not all(var in self.variables_dict for var in opt_vars): raise CADETProcessError('Variable not in variables') if len(factors) != len(opt_vars): raise CADETProcessError('Factors length does not match variables') lincon = dict() lincon['opt_vars'] = opt_vars lincon['factors'] = factors lincon['b'] = b self._linear_constraints.append(lincon) def remove_linear_constraint(self, index): """Removes linear inequality constraint. Parameters ---------- index : int Index of the linear inequality constraint to be removed. See also -------- add_linear_equality_constraint linear_equality_constraint """ del (self._linear_constraints[index]) @property def A(self): """np.ndarray: Matrix form of linear inequality constraints. See Also -------- b add_linear_constraint remove_linear_constraint """ A = np.zeros((len(self.linear_constraints), len(self.variables))) for lincon_index, lincon in enumerate(self.linear_constraints): for var_index, var in enumerate(lincon['opt_vars']): index = self.variables.index(self.variables_dict[var]) A[lincon_index, index] = lincon['factors'][var_index] return A @property def b(self): """list: Vector form of linear constraints. See Also -------- A add_linear_constraint remove_linear_constraint """ b = [lincon['b'] for lincon in self.linear_constraints] return np.array(b) def evaluate_linear_constraints(self, x): """Calculate value of linear inequality constraints at point x. Parameters ---------- x : array_like Value of the optimization variables. Returns ------- constraints: np.array Value of the linear constraints at point x See Also -------- A b linear_constraints """ x = np.array(x) return self.A.dot(x) - self.b def check_linear_constraints(self, x): """Check if linear inequality constraints are met at point x. Parameters ---------- x : array_like Value of the optimization variables. Returns ------- flag : bool Returns True if linear inequality constraints are met. False otherwise. See also: --------- linear_constraints evaluate_linear_constraints A b """ flag = True if np.any(self.evaluate_linear_constraints(x) > 0): flag = False return flag @property def linear_equality_constraints(self): """list: linear equality constraints of OptimizationProblem See Also -------- add_linear_equality_constraint remove_linear_equality_constraint linear_constraints """ return self._linear_equality_constraints @property def n_linear_equality_constraints(self): """int: number of linear equality constraints """ return len(self.linear_constraints) def add_linear_equality_constraint(self, opt_vars, factors, beq=0): """Add linear equality constraints. Parameters ----------- opt_vars : list of strings Names of the OptimizationVariable to be added. factors : list of integers Factors for OptimizationVariables. b_eq : float, optional Constraint of equality constraint; default set to zero. Raises ------- CADETProcessError If optimization variables do not exist. If length of factors does not match length of optimization variables. See also -------- linear_equality_constraints remove_linear_equality_constraint linear_constraints """ if not all(var in self.variables for var in opt_vars): return CADETProcessError('Variables not in variables') if len(factors) != len(opt_vars): return CADETProcessError('Factors length does not match variables') lineqcon = dict() lineqcon['opt_vars'] = opt_vars lineqcon['factors'] = factors lineqcon['beq'] = beq self._linear_equality_constraints.append(lineqcon) def remove_linear_equality_constraint(self, index): """Removes at given index the added linear equality conctraint. Parameters ---------- index : int Index of the linear equality constraint to be removed. See also -------- add_linear_equality_constraint linear_equality_constraint """ del (self._linear_equality_constraints[index]) @property def Aeq(self): """np.ndarray: Matrix form of linear equality constraints. See Also -------- beq add_linear_equality_constraint remove_linear_equality_constraint """ Aeq = np.zeros( (len(self.linear_equality_constraints), len(self.variables))) for lineqcon_index, lineqcon in enumerate( self.linear_equality_constraints): for var_index, var in enumerate(lineqcon.opt_vars): index = self.variables.index(var) Aeq[lineqcon_index, index] = lineqcon.factors[var_index] return Aeq @property def beq(self): """list: Vector form of linear equality constraints. See Also -------- Aeq add_linear_equality_constraint remove_linear_equality_constraint """ beq = np.zeros((len(self.linear_equality_constraints), )) beq = [lineqcon.beq for lineqcon in self.linear_equality_constraints] return beq def evaluate_linear_equality_constraints(self, x): """Calculate value of linear equality constraints at point x. Parameters ---------- x : array_like Value of the optimization variables. Returns ------- constraints: np.array Value of the linear euqlity constraints at point x See Also -------- Aeq beq linear_equality_constraints """ x = np.array(x) return self.Aeq.dot(x) - self.beq def check_linear_equality_constraints(self, x): """Check if linear equality constraints are met at point x. Parameters ---------- x : array_like Value of the optimization variables. Returns ------- flag : bool Returns True if linear equality constraints are met. False otherwise. """ flag = True if np.any(self.evaluate_linear_equality_constraints(x) != 0): flag = False return flag @property def x0(self): """Initival values for optimization. Parameters ---------- x0 : array_like Initival values for optimization. Raises ------ CADETProcessError If the initial value does not match length of optimization variables """ return self._x0 @x0.setter def x0(self, x0): if not len(x0) == len(self.variables): raise CADETProcessError( "Starting value must be given for all variables") self._x0 = x0 def create_initial_values(self, n_samples=1, method='random', seed=None): """Create initial value within parameter space. Uses hopsy (Highly Optimized toolbox for Polytope Sampling) to retrieve uniformly distributed samples from the parameter space. Parameters ---------- n_samples : int Number of initial values to be drawn method : str, optional chebyshev: Return center of the minimal-radius ball enclosing the entire set . random: Any random valid point in the parameter space. seed : int, optional Seed to initialize random numbers. Only used if method == 'random' Returns ------- init : ndarray Initial values for starting the optimization. """ model = hopsy.UniformModel() problem = hopsy.Problem(self.A, self.b, model) problem = hopsy.add_box_constraints(problem, self.lower_bounds, self.upper_bounds) with warnings.catch_warnings(): warnings.simplefilter("ignore") starting_points = [hopsy.compute_chebyshev_center(problem)] run = hopsy.Run(problem, starting_points=starting_points) if seed is None: seed = random.randint(0, 255) run.random_seed = seed run.sample(n_samples) states = np.array(run.data.states[0]) if n_samples == 1: if method == 'chebyshev': states = hopsy.compute_chebyshev_center(problem) elif method == 'random': states = states[0] else: raise CADETProcessError("Unexpected method.") return states @property def parameters(self): parameters = Dict() parameters.variables = { opt.name: opt.parameters for opt in self.variables } parameters.linear_constraints = self.linear_constraints return parameters def __str__(self): return self.name
class ReactionBaseClass(metaclass=StructMeta): """Abstract base class for parameters of binding models. Attributes ---------- n_comp : UnsignedInteger number of components. parameters : dict dict with parameter values. name : String name of the binding model. """ name = String() n_comp = UnsignedInteger() _parameter_names = [] def __init__(self, component_system, name=None): self.component_system = component_system self.name = name self._parameters = { param: getattr(self, param) for param in self._parameter_names } @property def model(self): return self.__class__.__name__ @property def component_system(self): return self._component_system @component_system.setter def component_system(self, component_system): if not isinstance(component_system, ComponentSystem): raise TypeError('Expected ComponentSystem') self._component_system = component_system @property def n_comp(self): return self.component_system.n_comp @property def parameters(self): """dict: Dictionary with parameter values. """ return {param: getattr(self, param) for param in self._parameter_names} @parameters.setter def parameters(self, parameters): for param, value in parameters.items(): if param not in self._parameter_names: raise CADETProcessError('Not a valid parameter') setattr(self, param, value) def __repr__(self): return '{}(n_comp={}, name=\'{}\')'.format(self.__class__.__name__, self.n_comp, self.name) def __str__(self): return self.name
class SimulationResults(metaclass=StructMeta): """Class for storing simulation results including the solver configuration Attributes ---------- solver_name : str Name of the solver used to simulate the process solver_parameters : dict Dictionary with parameters used by the solver exit_flag : int Information about the solver termination. exit_message : str Additional information about the solver status time_elapsed : float Execution time of simulation. process_name : str Name of the simulated proces process_config : dict Configuration of the simulated process process_meta : dict Meta information of the process. solution : dict Time signals for all cycles of all Unit Operations. system_state : dict Final state and state_derivative of the system. chromatograms : List of chromatogram Solution of the final cycle of the chromatogram_sinks. n_cycles : int Number of cycles that were simulated. Notes ----- Ideally, the final state for each unit operation should be saved. However, CADET does currently provide this functionality. """ solver_name = String() solver_parameters = Dict() exit_flag = UnsignedInteger() exit_message = String() time_elapsed = UnsignedFloat() process_name = String() process_config = Dict() solution_cycles = Dict() system_state = Dict() chromatograms = List() def __init__(self, solver_name, solver_parameters, exit_flag, exit_message, time_elapsed, process_name, process_config, process_meta, solution_cycles, system_state, chromatograms): self.solver_name = solver_name self.solver_parameters = solver_parameters self.exit_flag = exit_flag self.exit_message = exit_message self.time_elapsed = time_elapsed self.process_name = process_name self.process_config = process_config self.process_meta = process_meta self.solution_cycles = solution_cycles self.system_state = system_state self.chromatograms = chromatograms def update(self, new_results): if self.process_name != new_results.process_name: raise CADETProcessError('Process does not match') self.exit_flag = new_results.exit_flag self.exit_message = new_results.exit_message self.time_elapsed += new_results.time_elapsed self.system_state = new_results.system_state self.chromatograms = new_results.chromatograms for unit, solutions in self.solution_cycles.items(): for sol in solutions: self.solution_cycles[unit][sol].append( new_results.solution[unit][sol]) @property def solution(self): """Construct complete solution from individual cyles. """ cycle_time = self.process_config['parameters']['cycle_time'] time_complete = self.time_cycle for i in range(1, self.n_cycles): time_complete = np.hstack( (time_complete, self.time_cycle[1:] + i * cycle_time)) solution = addict.Dict() for unit, solutions in self.solution_cycles.items(): for sol, cycles in solutions.items(): solution[unit][sol] = copy.deepcopy(cycles[0]) solution_complete = cycles[0].solution for i in range(1, self.n_cycles): solution_complete = np.vstack( (solution_complete, cycles[i].solution[1:])) solution[unit][sol].time = time_complete solution[unit][sol].solution = solution_complete return solution @property def n_cycles(self): return len( self.solution_cycles[self._first_unit][self._first_solution]) @property def _first_unit(self): return next(iter(self.solution_cycles)) @property def _first_solution(self): return next(iter(self.solution_cycles[self._first_unit])) @property def time_cycle(self): """np.array: Solution times vector """ return \ self.solution_cycles[self._first_unit][self._first_solution][0].time def save(self, case_dir, unit=None, start=0, end=None): path = os.path.join(settings.project_directory, case_dir) if unit is None: units = self.solution.keys() else: units = self.solution[unit] for unit in units: self.solution[unit][-1].plot(save_path=path + '/' + unit + '_last.png', start=start, end=end) for unit in units: self.solution_complete[unit].plot(save_path=path + '/' + unit + '_complete.png', start=start, end=end) for unit in units: self.solution[unit][-1].plot( save_path=path + '/' + unit + '_overlay.png', overlay=[cyc.signal for cyc in self.solution[unit][0:-1]], start=start, end=end)
class UnitBaseClass(metaclass=StructMeta): """Base class for all UnitOperation classes. A UnitOperation object stores model parameters and states of a unit. Every unit operation can be assotiated with a binding behavior and a reaction model. UnitOperations can be connected in a FlowSheet. Attributes ---------- n_comp : UnsignedInteger Number of components in a system. parameters : list list of parameter names. name : String name of the unit_operation. binding_model : BindingBaseClass binding behavior of the unit. Defaults to NoBinding. See also -------- FlowSheet CADETProcess.binding CADETProcess.reaction """ name = String() _parameter_names = [] _section_dependent_parameters = [] _polynomial_parameters = [] _initial_state = [] supports_bulk_reaction = False supports_particle_reaction = False def __init__(self, component_system, name): self.name = name self.component_system = component_system self._binding_model = NoBinding() self._bulk_reaction_model = NoReaction() self._particle_reaction_model = NoReaction() self._discretization = NoDiscretization() self._parameters = { param: getattr(self, param) for param in self._parameter_names } @property def model(self): return self.__class__.__name__ @property def component_system(self): return self._component_system @component_system.setter def component_system(self, component_system): if not isinstance(component_system, ComponentSystem): raise TypeError('Expected ComponentSystem') self._component_system = component_system @property def n_comp(self): return self.component_system.n_comp @property def parameters(self): """dict: Dictionary with parameter values. """ parameters = self._parameters if not isinstance(self.binding_model, NoBinding): parameters['binding_model'] = self.binding_model.parameters if not isinstance(self.bulk_reaction_model, NoReaction): parameters[ 'bulk_reaction_model'] = self.bulk_reaction_model.parameters if not isinstance(self.particle_reaction_model, NoReaction): parameters['particle_reaction_model'] = \ self.particle_reaction_model.parameters if not isinstance(self.discretization, NoDiscretization): parameters['discretization'] = \ self.discretization.parameters return parameters @parameters.setter def parameters(self, parameters): try: self.binding_model.parameters = parameters.pop('binding_model') except KeyError: pass try: self.bulk_reaction_model.parameters = parameters.pop( 'bulk_reaction_model') except KeyError: pass try: self.particle_reaction_model.parameters = parameters.pop( 'particle_reaction_model') except KeyError: pass try: self.discretization.parameters = parameters.pop('discretization') except KeyError: pass for param, value in parameters.items(): if param not in self._parameters: raise CADETProcessError('Not a valid parameter') if value is not None: setattr(self, param, value) @property def section_dependent_parameters(self): parameters = { key: value for key, value in self.parameters.items() if key in self._section_dependent_parameters } return parameters @property def polynomial_parameters(self): parameters = { key: value for key, value in self.parameters.items() if key in self._polynomial_parameters } return parameters @property def initial_state(self): """dict: Dictionary with initial states. """ initial_state = {st: getattr(self, st) for st in self._initial_state} return initial_state @initial_state.setter def initial_state(self, initial_state): for st, value in initial_state.items(): if st not in self._initial_state: raise CADETProcessError('Not a valid parameter') if value is not None: setattr(self, st, value) @property def binding_model(self): """binding_model: BindingModel of the unit_operation. Raises ------ TypeError If binding_model object is not an instance of BindingBaseClass. CADETProcessError If number of components do not match. """ return self._binding_model @binding_model.setter def binding_model(self, binding_model): if not isinstance(binding_model, BindingBaseClass): raise TypeError('Expected BindingBaseClass') if binding_model.component_system is not self.component_system: raise CADETProcessError('Component systems do not match.') self._binding_model = binding_model @property def _n_bound_states(self): return self.binding_model.n_states @property def bulk_reaction_model(self): """bulk_reaction_model: Reaction model in the bulk phase Raises ------ TypeError If bulk_reaction_model object is not an instance of ReactionBaseClass. CADETProcessError If unit does not support bulk reaction model. If number of components do not match. """ return self._bulk_reaction_model @bulk_reaction_model.setter def bulk_reaction_model(self, bulk_reaction_model): if not isinstance(bulk_reaction_model, ReactionBaseClass): raise TypeError('Expected ReactionBaseClass') if not self.supports_bulk_reaction: raise CADETProcessError('Unit does not support bulk reactions.') if bulk_reaction_model.component_system is not self.component_system \ and not isinstance(bulk_reaction_model, NoReaction): raise CADETProcessError('Component systems do not match.') self._bulk_reaction_model = bulk_reaction_model @property def particle_reaction_model(self): """particle_liquid_reaction_model: Reaction model in the particle liquid phase Raises ------ TypeError If particle_reaction_model object is not an instance of ReactionBaseClass. CADETProcessError If unit does not support particle reaction model. If number of components do not match. """ return self._particle_reaction_model @particle_reaction_model.setter def particle_reaction_model(self, particle_reaction_model): if not isinstance(particle_reaction_model, ReactionBaseClass): raise TypeError('Expected ReactionBaseClass') if not self.supports_bulk_reaction: raise CADETProcessError( 'Unit does not support particle reactions.') if particle_reaction_model.component_system is not self.component_system \ and not isinstance(particle_reaction_model, NoReaction): raise CADETProcessError('Component systems do not match.') self._particle_reaction_model = particle_reaction_model @property def discretization(self): """discretization: Discretization Parameters """ return self._discretization def __repr__(self): """String-depiction of the object, can be changed into an object by calling the method eval. Returns ------- class.name(parameters with values) : str Information about the class's name of an object and its parameters like number of components and object name, depicted as a string. """ return '{}(n_comp={}, name=\'{}\')'.format(self.__class__.__name__, self.n_comp, self.name) def __str__(self): """Returns the information von __repr__ as a string object. Returns ------- name : String Information about the class's name of an object and its paremeters like number of components and object name """ return self.name
class FlowSheet(metaclass=StructMeta): """Class to design process flow sheet. In this class, UnitOperation models are added and connected in a flow sheet. Attributes ---------- n_comp : UnsignedInteger Number of components of the units in the flow sheet. name : String Name of the FlowSheet. units : list UnitOperations in the FlowSheet. connections : dict Connections of UnitOperations. output_states : dict Split ratios of outgoing streams of UnitOperations. """ name = String() n_comp = UnsignedInteger() def __init__(self, component_system, name=None): self.component_system = component_system self.name = name self._units = [] self._feed_sources = [] self._eluent_sources = [] self._chromatogram_sinks = [] self._connections = Dict() self._output_states = Dict() self._flow_rates = Dict() self._parameters = Dict() self._section_dependent_parameters = Dict() self._polynomial_parameters = Dict() @property def component_system(self): return self._component_system @component_system.setter def component_system(self, component_system): if not isinstance(component_system, ComponentSystem): raise TypeError('Expected ComponentSystem') self._component_system = component_system @property def n_comp(self): return self.component_system.n_comp def _unit_name_decorator(func): def wrapper(self, unit, *args, **kwargs): """Enable calling functions with unit object or unit name. """ if isinstance(unit, str): try: unit = self.units_dict[unit] except KeyError: raise CADETProcessError('Not a valid unit') return func(self, unit, *args, **kwargs) return wrapper def update_parameters(self): for unit in self.units: self._parameters[unit.name] = unit.parameters self._section_dependent_parameters[unit.name] = \ unit.section_dependent_parameters self._polynomial_parameters[unit.name] = unit.polynomial_parameters self._parameters['output_states'] = { unit.name: self.output_states[unit] for unit in self.units } self._section_dependent_parameters['output_states'] = { unit.name: self.output_states[unit] for unit in self.units } def update_parameters_decorator(func): def wrapper(self, *args, **kwargs): """Update parameters dict to save time. """ results = func(self, *args, **kwargs) self.update_parameters() return results return wrapper @property def units(self): """list: list of all unit_operations in the flow sheet. """ return self._units @property def units_dict(self): """dict: Unit names and objects. """ return {unit.name: unit for unit in self.units} @property def unit_names(self): """list: Unit names """ return [unit.name for unit in self.units] @property def number_of_units(self): """int: Number of unit operations in the FlowSheet. """ return len(self._units) @_unit_name_decorator def get_unit_index(self, unit): """Return the unit index of the unit. Parameters ---------- unit : UnitBaseClass UnitBaseClass object of which the index is to be returned. Raises ------ CADETProcessError If unit does not exist in the current flow sheet. Returns ------- unit_index : int Returns the unit index of the unit_operation. """ if unit not in self.units: raise CADETProcessError('Unit not in flow sheet') return self.units.index(unit) @property def sources(self): """list: All UnitOperations implementing the SourceMixin interface. """ return [unit for unit in self._units if isinstance(unit, SourceMixin)] @property def sinks(self): """list: All UnitOperations implementing the SinkMixin interface. """ return [unit for unit in self._units if isinstance(unit, SinkMixin)] @property def units_with_binding(self): """list: UnitOperations with binding models. """ return [unit for unit in self._units if not isinstance(unit.binding_model, NoBinding)] @update_parameters_decorator def add_unit( self, unit, feed_source=False, eluent_source=False, chromatogram_sink=False ): """Add unit to the flow sheet. Parameters ---------- unit : UnitBaseClass UnitBaseClass object to be added to the flow sheet. feed_source : bool If True, add unit to feed sources. eluent_source : bool If True, add unit to eluent sources. chromatogram_sink : bool If True, add unit to chromatogram sinks. Raises ------ TypeError If unit is no instance of UnitBaseClass. CADETProcessError If unit already exists in flow sheet. If n_comp does not match with FlowSheet. See Also -------- remove_unit """ if not isinstance(unit, UnitBaseClass): raise TypeError('Expected UnitOperation') if unit in self._units: raise CADETProcessError('Unit already part of System') if unit.component_system is not self.component_system: raise CADETProcessError('Component systems do not match.') self._units.append(unit) self._connections[unit] = Dict({ 'origins': [], 'destinations': [], }) self._output_states[unit] = [] self._flow_rates[unit] = [] super().__setattr__(unit.name, unit) if feed_source: self.add_feed_source(unit) if eluent_source: self.add_eluent_source(unit) if chromatogram_sink: self.add_chromatogram_sink(unit) @update_parameters_decorator def remove_unit(self, unit): """Remove unit from flow sheet. Removes unit from the list. Tries to remove units which are twice located as desinations. For this the origins and destinations are deleted for the unit. Raises a CADETProcessError if an ValueError is excepted. If the unit is specified as feed_source, eluent_source or chromatogram_sink, the corresponding attributes are deleted. Parameters ---------- unit : UnitBaseClass UnitBaseClass object to be removed to the flow sheet. Raises ------ CADETProcessError If unit does not exist in the flow sheet. See Also -------- add_unit feed_source eluent_source chromatogram_sink """ if unit not in self.units: raise CADETProcessError('Unit not in flow sheet') if unit is self.feed_sources: self.remove_feed_source(unit) if unit is self.eluent_sources: self.remove_eluent_source(unit) if unit is self.chromatogram_sinks: self.remove_chromatogram_sink(unit) origins = self.connections[unit].origins.copy() for origin in origins: self.remove_connection(origin, unit) destinations = self.connections[unit].destinations.copy() for destination in destinations: self.remove_connection(unit, destination) self._units.remove(unit) self._connections.pop(unit) self._output_states.pop(unit) self.__dict__.pop(unit.name) @property def connections(self): """dict: In- and outgoing connections for each unit. See Also -------- add_connection remove_connection """ return self._connections @update_parameters_decorator def add_connection(self, origin, destination): """Add connection between units 'origin' and 'destination'. Parameters ---------- origin : UnitBaseClass UnitBaseClass from which the connection originates. destination : UnitBaseClass UnitBaseClass where the connection terminates. Raises ------ CADETProcessError If origin OR destination do not exist in the current flow sheet. If connection already exists in the current flow sheet. See Also -------- connections remove_connection output_state """ if origin not in self._units: raise CADETProcessError('Origin not in flow sheet') if destination not in self._units: raise CADETProcessError('Destination not in flow sheet') if destination in self.connections[origin].destinations: raise CADETProcessError('Connection already exists') self._connections[origin].destinations.append(destination) self._connections[destination].origins.append(origin) self.set_output_state(origin, 0) @update_parameters_decorator def remove_connection(self, origin, destination): """Remove connection between units 'origin' and 'destination'. Parameters ---------- origin : UnitBaseClass UnitBaseClass from which the connection originates. destination : UnitBaseClass UnitBaseClass where the connection terminates. Raises ------ CADETProcessError If origin OR destination do not exist in the current flow sheet. If connection does not exists in the current flow sheet. See Also -------- connections add_connection """ if origin not in self._units: raise CADETProcessError('Origin not in flow sheet') if destination not in self._units: raise CADETProcessError('Destination not in flow sheet') try: self._connections[origin].destinations.remove(destination) self._connections[destination].origins.remove(origin) except KeyError: raise CADETProcessError('Connection does not exist.') @property def output_states(self): return self._output_states @_unit_name_decorator @update_parameters_decorator def set_output_state(self, unit, state): """Set split ratio of outgoing streams for UnitOperation. Parameters ---------- unit : UnitBaseClass UnitOperation of flowsheet. state : int or list of floats new output state of the unit. Raises ------ CADETProcessError If unit not in flowSheet If state is integer and the state >= the state_length. If the length of the states is unequal the state_length. If the sum of the states is not equal to 1. """ if unit not in self._units: raise CADETProcessError('Unit not in flow sheet') state_length = len(self.connections[unit].destinations) if state_length == 0: output_state = [] if isinstance(state, (int, np.int64)): if state >= state_length: raise CADETProcessError('Index exceeds destinations') output_state = [0] * state_length output_state[state] = 1 else: if len(state) != state_length: raise CADETProcessError( 'Expected length {}.'.format(state_length)) elif sum(state) != 1: raise CADETProcessError('Sum of fractions must be 1') output_state = state self._output_states[unit] = output_state def get_flow_rates(self, state=None): """Calculate flow rate for all connections.unit operation flow rates. If an additional state is passed, it will b Parameters ---------- state : Dict, optional Output states Returns ------- flow_rates : Dict Volumetric flow rate of each unit operation. """ flow_rates = { unit.name: unit.flow_rate for unit in self.sources } output_states = self.output_states if state is not None: for param, value in state.items(): param = param.split('.') unit_name = param[1] param_name = param[-1] if param_name == 'flow_rate': flow_rates[unit_name] = value[0] elif unit_name == 'output_states': unit = self.units_dict[param_name] output_states[unit] = list(value.ravel()) def list_factory(): return [0,0,0,0] total_flow_rates = {unit.name: list_factory() for unit in self.units} destination_flow_rates = { unit.name: defaultdict(list_factory) for unit in self.units } for i in range(4): solution = self.solve_flow_rates(flow_rates, output_states, i) if solution is not None: for unit_index, unit in enumerate(self.units): total_flow_rates[unit.name][i] = \ float(solution['Q_total_{}'.format(unit_index)]) for destination in self.connections[unit].destinations: destination_index = self.get_unit_index(destination) destination_flow_rates[unit.name][destination.name][i] = \ float(solution['Q_{}_{}'.format(unit_index, destination_index)]) flow_rates = Dict() for unit in self.units: flow_rates[unit.name].total = np.array(total_flow_rates[unit.name]) for destination, flow_rate in destination_flow_rates[unit.name].items(): flow_rates[unit.name].destinations[destination] = np.array(flow_rate) return flow_rates def solve_flow_rates(self, source_flow_rates, output_states, coeff=0): """Solve flow rates of system using sympy. Because a simple 'push' algorithm cannot be used when closed loops are present in a FlowSheet (e.g. SMBs), sympy is used to set up and solve the system of equations. Parameters ---------- source_flow_rates: dict Flow rates of Source UnitOperations. output_states: dict Output states of all UnitOperations. coeff: int Polynomial coefficient of flow rates to be solved. Returns ------- solution : dict Solution of the flow rates in the system Note ---- Since dynamic flow rates can be described as cubic polynomials, the flow rates are solved individually for all coefficients. """ coeffs = np.array( [source_flow_rates[unit.name][coeff] for unit in self.sources] ) if not np.any(coeffs): return None # Setup lists for symbols unit_total_flow_symbols = sym.symbols( 'Q_total_0:{}'.format(self.number_of_units) ) unit_inflow_symbols = [] unit_outflow_symbols = [] unit_total_flow_eq = [] unit_outflow_eq = [] # Setup symbolic equations for unit_index, unit in enumerate(self.units): if isinstance(unit, SourceMixin): unit_total_flow_eq.append( sym.Add( unit_total_flow_symbols[unit_index], - float(source_flow_rates[unit.name][coeff]) ) ) else: unit_i_inflow_symbols = [] for origin in self.connections[unit].origins: origin_index = self.get_unit_index(origin) unit_i_inflow_symbols.append( sym.symbols('Q_{}_{}'.format(origin_index, unit_index)) ) unit_i_total_flow_eq = sym.Add( *unit_i_inflow_symbols, -unit_total_flow_symbols[unit_index] ) unit_inflow_symbols += unit_i_inflow_symbols unit_total_flow_eq.append(unit_i_total_flow_eq) if not isinstance(unit, Sink): output_state = output_states[unit] unit_i_outflow_symbols = [] for destination in self.connections[unit].destinations: destination_index = self.get_unit_index(destination) unit_i_outflow_symbols.append( sym.symbols('Q_{}_{}'.format(unit_index, destination_index)) ) unit_i_outflow_eq = [ sym.Add( unit_i_outflow_symbols[dest], -unit_total_flow_symbols[unit_index]*output_state[dest] ) for dest in range(len(self.connections[unit].destinations)) ] unit_outflow_symbols += unit_i_outflow_symbols unit_outflow_eq += unit_i_outflow_eq # Solve system of equations solution = sym.solve( unit_total_flow_eq + unit_outflow_eq, (*unit_total_flow_symbols, *unit_inflow_symbols, *unit_outflow_symbols) ) solution = {str(key): value for key, value in solution.items()} return solution @property def feed_sources(self): """list: List of sources considered for calculating recovery yield. """ return self._feed_sources @_unit_name_decorator def add_feed_source(self, feed_source): """Add source to list of units to be considered for recovery. Parameters ---------- feed_source : SourceMixin Unit to be added to list of feed sources Raises ------ CADETProcessError If unit is not in a source object If unit is already marked as feed source """ if feed_source not in self.sources: raise CADETProcessError('Expected Source') if feed_source in self._feed_sources: raise CADETProcessError( '{} is already eluent source'.format(feed_source) ) self._feed_sources.append(feed_source) @_unit_name_decorator def remove_feed_source(self, feed_source): """Remove source from list of units to be considered for recovery. Parameters ---------- feed_source : SourceMixin Unit to be removed from list of feed sources. """ if feed_source not in self._feed_sources: raise CADETProcessError('Unit \'{}\' is not a feed source.'.format( feed_source)) self._feed_sources.remove(feed_source) @property def eluent_sources(self): """list: List of sources to be considered for eluent consumption. """ return self._eluent_sources @_unit_name_decorator def add_eluent_source(self, eluent_source): """Add source to list of units to be considered for eluent consumption. Parameters ---------- eluent_source : SourceMixin Unit to be added to list of eluent sources. Raises ------ CADETProcessError If unit is not in a source object If unit is already marked as eluent source """ if eluent_source not in self.sources: raise CADETProcessError('Expected Source') if eluent_source in self._eluent_sources: raise CADETProcessError('{} is already eluent source'.format( eluent_source)) self._eluent_sources.append(eluent_source) @_unit_name_decorator def remove_eluent_source(self, eluent_source): """Remove source from list of units to be considered eluent consumption. Parameters ---------- eluent_source : SourceMixin Unit to be added to list of eluent sources. Raises ------ CADETProcessError If unit is not in eluent sources """ if eluent_source not in self._eluent_sources: raise CADETProcessError('Unit \'{}\' is not an eluent source.'.format( eluent_source)) self._eluent_sources.remove(eluent_source) @property def chromatogram_sinks(self): """list: List of sinks to be considered for fractionation. """ return self._chromatogram_sinks @_unit_name_decorator def add_chromatogram_sink(self, chromatogram_sink): """Add sink to list of units to be considered for fractionation. Parameters ---------- chromatogram_sink : SinkMixin Unit to be added to list of chromatogram sinks. Raises ------ CADETProcessError If unit is not a sink object. If unit is already marked as chromatogram sink. """ if chromatogram_sink not in self.sinks: raise CADETProcessError('Expected Sink') if chromatogram_sink in self._chromatogram_sinks: raise CADETProcessError( '{} is already chomatogram sink'.format(chromatogram_sink) ) self._chromatogram_sinks.append(chromatogram_sink) @_unit_name_decorator def remove_chromatogram_sink(self, chromatogram_sink): """Remove sink from list of units to be considered for fractionation. Parameters ---------- chromatogram_sink : SinkMixin Unit to be added to list of chromatogram sinks. Raises ------ CADETProcessError If unit is not a chromatogram sink. """ if chromatogram_sink not in self._chromatogram_sinks: raise CADETProcessError( 'Unit \'{}\' is not a chromatogram sink.'.format(chromatogram_sink) ) self._chromatogram_sinks.remove(chromatogram_sink) @property def parameters(self): return self._parameters @parameters.setter def parameters(self, parameters): try: output_states = parameters.pop('output_states') for unit, state in output_states.items(): unit = self.units_dict[unit] self.set_output_state(unit, state) except KeyError: pass for unit, params in parameters.items(): if unit not in self.units_dict: raise CADETProcessError('Not a valid unit') self.units_dict[unit].parameters = params self.update_parameters() @property def section_dependent_parameters(self): return self._section_dependent_parameters @property def polynomial_parameters(self): return self._polynomial_parameters @property def initial_state(self): initial_state = {unit.name: unit.initial_state for unit in self.units} return initial_state @initial_state.setter def initial_state(self, initial_state): for unit, st in initial_state.items(): if unit not in self.units_dict: raise CADETProcessError('Not a valid unit') self.units_dict[unit].initial_state = st def __getitem__(self, unit_name): """Make FlowSheet substriptable s.t. units can be used as keys. Parameters ---------- unit_name : str Name of the unit. Returns ------- unit : UnitBaseClass UnitOperation of flowsheet. Raises ------ KeyError If unit not in flowSheet """ try: return self.units_dict[unit_name] except KeyError: raise KeyError('Not a valid unit') def __contains__(self, item): """Check if an item is part of units. Parameters ---------- item : UnitBaseClass item to be searched Returns ------- Bool : True if item is in units, otherwise False. Note ---- maybe deficient in documentation. """ if (item in self._units) or (item in self.unit_names): return True else: return False
class TimeSignal(metaclass=StructMeta): """Class for storing concentration profiles after simulation. Attributes ---------- time : NdArray NdArray object for the time of a chromatogram. signal : NdArray NdArray of the concentration of a chromatogram. cycle_time : float Maximum value of time vector. """ time = NdArray() signal = NdArray() cycle_time = Float() name = String() def __init__(self, time, signal, name=''): self.time = time self.signal = signal self.name = name self.cycle_time = float(max(self.time)) @plotting.save_fig def plot(self, start=0, end=None): """Plots the whole time_signal for each component. Parameters ---------- start : float start time for plotting end : float end time for plotting See also -------- plotlib plot_purity """ x = self.time / 60 y = self.signal fig, ax = plotting.setup_figure() ax.plot(x,y) layout = plotting.Layout() layout.x_label = '$time~/~min$' layout.y_label = '$c~/~mol \cdot L^{-1}$' layout.xlim = (start, end) layout.ylim = (0, 1.1*np.max(y)) plotting.set_layout(fig, ax, layout) return ax @property def local_purity(self): """Returns the local purity of the signal. Creates an array with the size of the signal with zero values. An array with the sum of the signal is created. Every value for signal_sum under a defined value is set to NaN. The other values are set for each component. Errorstatehandling of floating point error by the division: ignores the divide and invalid. Returns a NaN, for zero and infinity with large finite numbers. Returns ------- local_purity : NdArray Returns the local purity for each component as an array. """ purity = np.zeros(self.signal.shape) signal_sum = self.signal.sum(1) signal_sum[signal_sum < 1e-6] = np.nan for comp in range(self.n_comp): signal = self.signal[:,comp] with np.errstate(divide='ignore', invalid='ignore'): purity[:,comp] = np.divide(signal, signal_sum) purity = np.nan_to_num(purity) return np.nan_to_num(purity) @plotting.save_fig def plot_purity(self, start=0, end=None): """Plots the local purity for each component of the concentration profile. Parameters ---------- start : float start time for plotting end : float ent time for plotting See also -------- plotlib plot """ x = self.time / 60 y = self.local_purity * 100 fig, ax = plotting.setup_figure() ax.plot(x,y) layout = plotting.Layout() layout.x_label = '$time~/~min$' layout.y_label = '$c~/~mol \cdot L^{-1}$' layout.xlim = (start, end) layout.ylim = (0, 1.1*np.max(y)) plotting.set_layout(fig, ax, layout) return ax @property def n_comp(self): """Number of components of the signal Returns -------- n_comp : int Returns the number of the components for the signal. """ return self.signal.shape[1] def __str__(self): return self.__class__.__name__
class SecondaryAxis(Structure): component_indices = List() y_label = String() transform = Callable()
class Annotation(Structure): text = String() xy = Tuple() xytext = Tuple() arrowstyle = '-|>'
class Tick(Structure): location: Tuple() label: String()
class OptimizationResults(metaclass=StructMeta): """Class for storing optimization results including the solver configuration Attributes ---------- optimization_problem : OptimizationProblem Optimization problem evaluation_object : obj Evaluation object in optimized state. solver_name : str Name of the solver used to simulate the process solver_parameters : dict Dictionary with parameters used by the solver exit_flag : int Information about the solver termination. exit_message : str Additional information about the solver status time_elapsed : float Execution time of simulation. x : list Values of optimization variables at optimum. f : np.ndarray Value of objective function at x. c : np.ndarray Values of constraint function at x """ x0 = List() solver_name = String() solver_parameters = Dict() exit_flag = UnsignedInteger() exit_message = String() time_elapsed = UnsignedFloat() x = List() f = NdArray() c = NdArray() performance = Dict() def __init__( self, optimization_problem, evaluation_object, solver_name, solver_parameters, exit_flag, exit_message, time_elapsed, x, f, c, performance, frac=None, history=None ): self.optimization_problem = optimization_problem self.evaluation_object = evaluation_object self.solver_name = solver_name self.solver_parameters = solver_parameters self.exit_flag = exit_flag self.exit_message = exit_message self.time_elapsed = time_elapsed self.x = x self.f = f if c is not None: self.c = c self.performance = performance self.frac = frac self.history = history def to_dict(self): return { 'optimization_problem': self.optimization_problem.name, 'optimization_problem_parameters': self.optimization_problem.parameters, 'evaluation_object_parameters': self.evaluation_object.parameters, 'x0': self.optimization_problem.x0, 'solver_name': self.solver_name, 'solver_parameters': self.solver_parameters, 'exit_flag': self.exit_flag, 'exit_message': self.exit_message, 'time_elapsed': self.time_elapsed, 'x': self.x, 'f': self.f.tolist(), 'c': self.c.tolist(), 'performance': self.performance, 'git': { 'chromapy_branch': str(settings.repo.active_branch), 'chromapy_commit': settings.repo.head.object.hexsha } } def save(self, directory): path = os.path.join(settings.project_directory, directory, 'results.json') with open(path, 'w') as f: json.dump(self.to_dict(), f, indent=4) def plot_solution(self): pass