def __init__(self, **kwargs): self.grid_data = None self.options = om.OptionsDictionary() self.options.declare('num_segments', types=int, desc='Number of segments') self.options.declare('segment_ends', default=None, types=(Sequence, np.ndarray), allow_none=True, desc='Locations of segment ends or None for equally ' 'spaced segments') self.options.declare('order', default=3, types=(int, Sequence, np.ndarray), desc='Order of the state transcription') self.options.declare('compressed', default=True, types=bool, desc='Use compressed transcription, meaning state and control values' 'at segment boundaries are not duplicated on input. This ' 'implicitly enforces value continuity between segments but in ' 'some cases may make the problem more difficult to solve.') self._declare_options() self.initialize() self.options.update(kwargs) self.init_grid() # Where to query var info. self._rhs_source = None
def add_linkage(self, name, vars, shape=(1, ), equals=None, lower=None, upper=None, units=None, scaler=None, adder=None, ref0=None, ref=None, linear=False): """ Add a linkage constraint to be managed by this component. .. math :: C_n = y_{n1} - y_{n0} where :math:`y_1` is the value of the variable at the beginning or end of phase 1, and :math:`y_0` is the value of the variable at the beginning or end of phase 0. The location of the source of the constraint can be set by the user based on connected indices. The name of each linkage constraint will be LNK_var where LNK is the name of the linkage and var are the vars in that linkage. Parameters ---------- name : str. The name of one or more linkage constraints to be added. vars : str or iterable The name of one or more linked variables to be added. shape : tuple or dict The shape of the constraint being formed. Must be compliant with the shape of the variable. If given as a dict, it should be keyed with variables in var, and the associated value being the corresponding units. units : str, dict, or None The units of the linkage constraint. If given as a string, the units will apply to each variable in vars. If given as a dict, it should be keyed with variables in var, and the associated value being the corresponding units. Default is None. lower : float or ndarray The minimum allowable difference of y_1 - y_0, enforced by the optimizer. upper : float or ndarray The minimum allowable difference of y_1 - y_0, enforced by the optimizer. equals : float or ndarray The prescribed difference of y_1 - y_0, enforced bt the optimizer. scaler : float, ndarray, or None The scalar applied to this constraint by the driver. adder : float, ndarray, or None The adder applied to this constraint by the driver. ref0 : float, ndarray, or None The zero-reference value of this constraint, used for scaling by the driver. ref : float, ndarray, or None The one-reference value of this constraint, used for scaling by the driver. linear : bool If True, this constraint will be treated as a linear constraint by the optimizer. This should only be done if the *total derivative* of the constraint is linear. That is, the affected variables in each phase are design variables or linear functions of design variables. Default is False. """ if equals is None and lower is None and upper is None: equals = 0.0 if isinstance(vars, str): _vars = (vars, ) else: _vars = vars if isinstance(units, str) or units is None: _units = {} for var in _vars: _units[var] = units else: _units = units if isinstance(shape, tuple): _shapes = {} for var in _vars: _shapes[var] = shape else: _shapes = shape for var in _vars: lnk = om.OptionsDictionary() lnk.declare('name', types=(str, )) lnk.declare('equals', types=(float, np.ndarray), allow_none=True) lnk.declare('lower', types=(float, np.ndarray), allow_none=True) lnk.declare('upper', types=(float, np.ndarray), allow_none=True) lnk.declare('units', types=str, allow_none=True) lnk.declare('scaler', types=(float, np.ndarray), allow_none=True) lnk.declare('adder', types=(float, np.ndarray), allow_none=True) lnk.declare('ref0', types=(float, np.ndarray), allow_none=True) lnk.declare('ref', types=(float, np.ndarray), allow_none=True) lnk.declare('linear', types=bool) lnk.declare('shape', types=tuple) lnk.declare('cond0_name', types=str) lnk.declare('cond1_name', types=str) lnk['name'] = '{0}_{1}'.format(name, var) lnk['equals'] = equals lnk['lower'] = lower lnk['upper'] = upper lnk['scaler'] = scaler lnk['adder'] = adder lnk['ref0'] = ref0 lnk['ref'] = ref # This nonsense shouldn't be required, but the default value for shape is None, and # can't be _undefined due to limitations in the options dictionary. shape = _shapes.get(var, (1, )) if shape is None: shape = (1, ) lnk['shape'] = shape lnk['linear'] = linear lnk['units'] = _units.get(var, None) lnk['cond0_name'] = '{0}:lhs'.format(lnk['name']) lnk['cond1_name'] = '{0}:rhs'.format(lnk['name']) self.options['linkages'].append(lnk) self.add_input(name=lnk['cond0_name'], shape=lnk['shape'], val=np.zeros(lnk['shape']), units=lnk['units']) self.add_input(name=lnk['cond1_name'], shape=lnk['shape'], val=np.zeros(lnk['shape']), units=lnk['units']) self.add_output(name=lnk['name'], shape=lnk['shape'], val=np.zeros(lnk['shape']), units=lnk['units']) self.add_constraint(name=lnk['name'], equals=lnk['equals'], lower=lnk['lower'], upper=lnk['upper'], ref=lnk['ref'], ref0=lnk['ref0'], scaler=lnk['scaler'], adder=lnk['adder'], linear=lnk['linear']) shape = lnk['shape'] ar = np.arange(np.prod(shape)) self.declare_partials(of=lnk['name'], wrt=lnk['cond1_name'], rows=ar, cols=ar, val=1.0) self.declare_partials(of=lnk['name'], wrt=lnk['cond0_name'], rows=ar, cols=ar, val=-1.0)