From 193ec517e4cd5e83218d21c11ec7bef298914afd Mon Sep 17 00:00:00 2001 From: johnjasa Date: Tue, 7 May 2024 17:40:31 -0500 Subject: [PATCH 01/17] WIP: combining controls --- ...doc_brachistochrone_polynomial_controls.py | 11 +- dymos/phase/options.py | 8 + dymos/phase/phase.py | 30 +- dymos/transcriptions/common/control_group.py | 398 +++++++++++++----- .../explicit_shooting/explicit_shooting.py | 2 +- .../transcriptions/pseudospectral/birkhoff.py | 2 +- .../pseudospectral/gauss_lobatto.py | 35 +- .../pseudospectral/pseudospectral_base.py | 15 +- dymos/transcriptions/solve_ivp/solve_ivp.py | 4 +- dymos/transcriptions/transcription_base.py | 2 + dymos/utils/introspection.py | 36 +- 11 files changed, 394 insertions(+), 149 deletions(-) diff --git a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py index e54d2fc93..975067dd1 100644 --- a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py +++ b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py @@ -1141,7 +1141,8 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + # phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', control_type='polynomial', order=1, units='deg', lower=0.01, upper=179.9, continuity=False, rate_continuity=False, rate2_continuity=False) phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1162,6 +1163,11 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + # p.final_setup() + + # p.model.list_inputs(print_arrays=True) + # p.model.list_outputs(print_arrays=True) + # Solve for the optimal trajectory p.run_driver() @@ -1576,4 +1582,5 @@ def test_brachistochrone_polynomial_control_birkhoff(self): if __name__ == '__main__': # pragma: no cover - unittest.main() + z = TestBrachistochronePolynomialControlRate2PathConstrained() + z.test_brachistochrone_polynomial_control_gauss_lobatto() \ No newline at end of file diff --git a/dymos/phase/options.py b/dymos/phase/options.py index d6e723ae0..3702478e6 100644 --- a/dymos/phase/options.py +++ b/dymos/phase/options.py @@ -20,6 +20,14 @@ def __init__(self, read_only=False): self.declare(name='name', types=str, desc='The name of ODE system parameter to be controlled.') + + self.declare(name='control_type', types=str, default='full', + desc='The type of control variable. Options are `full` or `polynomial`.') + + self.declare(name='order', types=(int,), default=None, allow_none=True, + desc='A integer that provides the interpolation order when the control is ' + 'to assume a single polynomial basis across the entire phase, or None ' + 'to use the default control behavior.') self.declare(name='units', default=_unspecified, allow_none=True, desc='The units in which the control variable is defined.') diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index 8684f6211..1b3cd6437 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -527,7 +527,7 @@ def check_parameter(self, name): elif name in self.polynomial_control_options: raise ValueError(f'{name} has already been added as a polynomial control.') - def add_control(self, name, units=_unspecified, desc=_unspecified, opt=_unspecified, + def add_control(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, opt=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, rate_targets=_unspecified, rate2_targets=_unspecified, val=_unspecified, shape=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, @@ -544,6 +544,10 @@ def add_control(self, name, units=_unspecified, desc=_unspecified, opt=_unspecif name : str The name assigned to the control variable. If the ODE has been decorated with parameters, this should be the name of a control in the system. + control_type : str + The type of control variable. Valid options include 'full' and 'polynomial'. + order : int + The order of the polynomial control variable. This option is invalid if control_type is 'full'. units : str or None The units with which the control parameter in this phase will be defined. It must be compatible with the units of the targets to which the control is connected. @@ -627,7 +631,7 @@ def add_control(self, name, units=_unspecified, desc=_unspecified, opt=_unspecif self.control_options[name] = ControlOptionsDictionary() self.control_options[name]['name'] = name - self.set_control_options(name, units=units, desc=desc, opt=opt, fix_initial=fix_initial, + self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, fix_final=fix_final, targets=targets, rate_targets=rate_targets, rate2_targets=rate2_targets, val=val, shape=shape, lower=lower, upper=upper, scaler=scaler, adder=adder, ref0=ref0, ref=ref, @@ -639,7 +643,7 @@ def add_control(self, name, units=_unspecified, desc=_unspecified, opt=_unspecif rate2_continuity_scaler=rate2_continuity_scaler, rate2_continuity_ref=rate2_continuity_ref) - def set_control_options(self, name, units=_unspecified, desc=_unspecified, opt=_unspecified, + def set_control_options(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, opt=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, rate_targets=_unspecified, rate2_targets=_unspecified, val=_unspecified, shape=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, @@ -656,6 +660,10 @@ def set_control_options(self, name, units=_unspecified, desc=_unspecified, opt=_ name : str The name assigned to the control variable. If the ODE has been decorated with parameters, this should be the name of a control in the system. + control_type : str + The type of control variable. Valid options include 'full' and 'polynomial'. + order : int + The order of the polynomial control variable. This option is invalid if control_type is 'full'. units : str or None The units with which the control parameter in this phase will be defined. It must be compatible with the units of the targets to which the control is connected. @@ -734,6 +742,12 @@ def set_control_options(self, name, units=_unspecified, desc=_unspecified, opt=_ ----- rate and rate2 continuity are not enforced for input controls. """ + if control_type is not _unspecified: + self.control_options[name]['control_type'] = control_type + + if order is not _unspecified: + self.control_options[name]['order'] = order + if units is not _unspecified: self.control_options[name]['units'] = units @@ -881,6 +895,11 @@ def add_polynomial_control(self, name, order, desc=_unspecified, val=_unspecifie shape : Sequence of int The shape of the control variable at each point in time. """ + om.issue_warning(f'{self.pathname}: The method `add_polynomial_control` is ' + 'deprecated and will be removed in Dymos 2.1. Please use ' + '`add_control` with the appropriate options to define a polynomial control.', + category=om.OMDeprecationWarning) + self.check_parameter(name) if name not in self.polynomial_control_options: @@ -2305,7 +2324,10 @@ def interp(self, name=None, ys=None, xs=None, nodes=None, kind='linear', axis=0) else: node_locations = gd.node_ptau[gd.subset_node_indices['state_input']] elif name in self.control_options: - node_locations = gd.node_ptau[gd.subset_node_indices['control_input']] + if self.control_options[name]['control_type'] == 'polynomial': + node_locations, _ = lgl(self.control_options[name]['order'] + 1) + else: + node_locations = gd.node_ptau[gd.subset_node_indices['control_input']] elif name in self.polynomial_control_options: node_locations, _ = lgl(self.polynomial_control_options[name]['order'] + 1) else: diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index 1c8daee3f..18577ec47 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -6,6 +6,7 @@ from ..grid_data import GridData from ...utils.misc import get_rate_units, CoerceDesvar, reshape_val +from ...utils.lgl import lgl from ...utils.lagrange import lagrange_matrices from ...utils.indexing import get_desvar_indices from ...utils.constants import INF_BOUND @@ -67,6 +68,7 @@ def initialize(self): self._output_val_names = {} self._output_rate_names = {} self._output_rate2_names = {} + self._matrices = {} def setup(self): """ @@ -83,6 +85,8 @@ def setup(self): def _configure_controls(self): gd = self.options['grid_data'] + num_nodes = gd.subset_num_nodes['all'] + eval_nodes = gd.node_ptau ogd = self.options['output_grid_data'] or gd control_options = self.options['control_options'] num_output_nodes = ogd.num_nodes @@ -90,68 +94,146 @@ def _configure_controls(self): time_units = self.options['time_units'] for name, options in control_options.items(): - self._input_names[name] = f'controls:{name}' - self._output_val_names[name] = f'control_values:{name}' - self._output_rate_names[name] = f'control_rates:{name}_rate' - self._output_rate2_names[name] = f'control_rates:{name}_rate2' - shape = options['shape'] - input_shape = (num_control_input_nodes,) + shape - output_shape = (num_output_nodes,) + shape - - units = options['units'] - rate_units = get_rate_units(units, time_units) - rate2_units = get_rate_units(units, time_units, deriv=2) - - self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) - - self.add_output(self._output_val_names[name], shape=output_shape, units=units) - - self.add_output(self._output_rate_names[name], shape=output_shape, units=rate_units) - - self.add_output(self._output_rate2_names[name], shape=output_shape, - units=rate2_units) - - size = np.prod(shape) - self.sizes[name] = size - sp_eye = sp.eye(size, format='csr') - - # The partial of interpolated value wrt the control input values is linear - # and can be computed as the kronecker product of the interpolation matrix (L) - # and eye(size). - J_val = sp.kron(self.L, sp_eye, format='csr') - rs, cs, data = sp.find(J_val) - self.declare_partials(of=self._output_val_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs, val=data) - - # The partials of the output rate and second derivative wrt dt_dstau - rs = np.arange(num_output_nodes * size, dtype=int) - cs = np.repeat(np.arange(num_output_nodes, dtype=int), size) - - self.declare_partials(of=self._output_rate_names[name], - wrt='dt_dstau', - rows=rs, cols=cs) - - self.declare_partials(of=self._output_rate2_names[name], - wrt='dt_dstau', - rows=rs, cols=cs) - - # The partials of the rates and second derivatives are nonlinear but the sparsity - # pattern is obtained from the kronecker product of the 1st and 2nd differentiation - # matrices (D and D2) and eye(size). - self.rate_jacs[name] = sp.kron(self.D, sp_eye, format='csr') - rs, cs = self.rate_jacs[name].nonzero() - - self.declare_partials(of=self._output_rate_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs) - - self.rate2_jacs[name] = sp.kron(self.D2, sp_eye, format='csr') - rs, cs = self.rate2_jacs[name].nonzero() - - self.declare_partials(of=self._output_rate2_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs) + if control_options[name]['control_type'] == 'polynomial': + disc_nodes, _ = lgl(options['order'] + 1) + num_control_input_nodes = len(disc_nodes) + shape = options['shape'] + size = np.prod(shape) + units = options['units'] + rate_units = get_rate_units(units, self.options['time_units'], deriv=1) + rate2_units = get_rate_units(units, self.options['time_units'], deriv=2) + + input_shape = (num_control_input_nodes,) + shape + output_shape = (num_nodes,) + shape + + L_de, D_de = lagrange_matrices(disc_nodes, eval_nodes) + _, D_dd = lagrange_matrices(disc_nodes, disc_nodes) + D2_de = np.dot(D_de, D_dd) + + self._matrices[name] = L_de, D_de, D2_de + + self._input_names[name] = f'polynomial_controls:{name}' + self._output_val_names[name] = f'polynomial_control_values:{name}' + self._output_rate_names[name] = f'polynomial_control_rates:{name}_rate' + self._output_rate2_names[name] = f'polynomial_control_rates:{name}_rate2' + + self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) + self.add_output(self._output_val_names[name], shape=output_shape, units=units) + self.add_output(self._output_rate_names[name], shape=output_shape, units=rate_units) + self.add_output(self._output_rate2_names[name], shape=output_shape, units=rate2_units) + + self.val_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) + self.rate_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) + self.rate2_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) + + for i in range(size): + self.val_jacs[name][:, i, :, i] = L_de + self.rate_jacs[name][:, i, :, i] = D_de + self.rate2_jacs[name][:, i, :, i] = D2_de + + self.val_jacs[name] = self.val_jacs[name].reshape((num_nodes * size, + num_control_input_nodes * size), + order='C') + self.rate_jacs[name] = self.rate_jacs[name].reshape((num_nodes * size, + num_control_input_nodes * size), + order='C') + self.rate2_jacs[name] = self.rate2_jacs[name].reshape((num_nodes * size, + num_control_input_nodes * size), + order='C') + self.val_jac_rows[name], self.val_jac_cols[name] = \ + np.where(self.val_jacs[name] != 0) + self.rate_jac_rows[name], self.rate_jac_cols[name] = \ + np.where(self.rate_jacs[name] != 0) + self.rate2_jac_rows[name], self.rate2_jac_cols[name] = \ + np.where(self.rate2_jacs[name] != 0) + + self.sizes[name] = size + + rs, cs = self.val_jac_rows[name], self.val_jac_cols[name] + self.declare_partials(of=self._output_val_names[name], + wrt=self._input_names[name], + rows=rs, cols=cs, val=self.val_jacs[name][rs, cs]) + + rs = np.concatenate([np.arange(0, num_nodes * size, size, dtype=int) + i + for i in range(size)]) + + self.declare_partials(of=self._output_rate_names[name], + wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) + + self.declare_partials(of=self._output_rate_names[name], + wrt=self._input_names[name], + rows=self.rate_jac_rows[name], cols=self.rate_jac_cols[name]) + + self.declare_partials(of=self._output_rate2_names[name], + wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) + + self.declare_partials(of=self._output_rate2_names[name], + wrt=self._input_names[name], + rows=self.rate2_jac_rows[name], cols=self.rate2_jac_cols[name]) + + else: + self._input_names[name] = f'controls:{name}' + self._output_val_names[name] = f'control_values:{name}' + self._output_rate_names[name] = f'control_rates:{name}_rate' + self._output_rate2_names[name] = f'control_rates:{name}_rate2' + shape = options['shape'] + input_shape = (num_control_input_nodes,) + shape + output_shape = (num_output_nodes,) + shape + + units = options['units'] + rate_units = get_rate_units(units, time_units) + rate2_units = get_rate_units(units, time_units, deriv=2) + + self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) + + self.add_output(self._output_val_names[name], shape=output_shape, units=units) + + self.add_output(self._output_rate_names[name], shape=output_shape, units=rate_units) + + self.add_output(self._output_rate2_names[name], shape=output_shape, + units=rate2_units) + + size = np.prod(shape) + self.sizes[name] = size + sp_eye = sp.eye(size, format='csr') + + # The partial of interpolated value wrt the control input values is linear + # and can be computed as the kronecker product of the interpolation matrix (L) + # and eye(size). + J_val = sp.kron(self.L, sp_eye, format='csr') + rs, cs, data = sp.find(J_val) + self.declare_partials(of=self._output_val_names[name], + wrt=self._input_names[name], + rows=rs, cols=cs, val=data) + + # The partials of the output rate and second derivative wrt dt_dstau + rs = np.arange(num_output_nodes * size, dtype=int) + cs = np.repeat(np.arange(num_output_nodes, dtype=int), size) + + self.declare_partials(of=self._output_rate_names[name], + wrt='dt_dstau', + rows=rs, cols=cs) + + self.declare_partials(of=self._output_rate2_names[name], + wrt='dt_dstau', + rows=rs, cols=cs) + + # The partials of the rates and second derivatives are nonlinear but the sparsity + # pattern is obtained from the kronecker product of the 1st and 2nd differentiation + # matrices (D and D2) and eye(size). + self.rate_jacs[name] = sp.kron(self.D, sp_eye, format='csr') + rs, cs = self.rate_jacs[name].nonzero() + + self.declare_partials(of=self._output_rate_names[name], + wrt=self._input_names[name], + rows=rs, cols=cs) + + self.rate2_jacs[name] = sp.kron(self.D2, sp_eye, format='csr') + rs, cs = self.rate2_jacs[name].nonzero() + + self.declare_partials(of=self._output_rate2_names[name], + wrt=self._input_names[name], + rows=rs, cols=cs) def configure_io(self): """ @@ -165,10 +247,21 @@ def configure_io(self): self.add_input('dt_dstau', shape=output_num_nodes, units=time_units) + self.val_jacs = {} self.rate_jacs = {} self.rate2_jacs = {} + self.val_jac_rows = {} + self.val_jac_cols = {} + self.rate_jac_rows = {} + self.rate_jac_cols = {} + self.rate2_jac_rows = {} + self.rate2_jac_cols = {} self.sizes = {} + self.add_input('t_duration', val=1.0, units=self.options['time_units'], + desc='duration of the phase to which this interpolated control group ' + 'belongs') + num_disc_nodes = gd.subset_num_nodes['control_disc'] num_input_nodes = gd.subset_num_nodes['control_input'] @@ -233,27 +326,41 @@ def compute(self, inputs, outputs): control_options = self.options['control_options'] num_control_input_nodes = self.options['grid_data'].subset_num_nodes['control_input'] num_output_nodes = ogd.num_nodes + dt_dptau = 0.5 * inputs['t_duration'] for name, options in control_options.items(): - size = np.prod(options['shape']) + if control_options[name]['control_type'] == 'polynomial': + L_de, D_de, D2_de = self._matrices[name] - u_flat = np.reshape(inputs[self._input_names[name]], - newshape=(num_control_input_nodes, size)) + u = inputs[self._input_names[name]] - a = self.D.dot(u_flat) - b = self.D2.dot(u_flat) + a = np.tensordot(D_de, u, axes=(1, 0)).T + b = np.tensordot(D2_de, u, axes=(1, 0)).T - val = np.reshape(self.L.dot(u_flat), (num_output_nodes,) + options['shape']) + # divide each "row" by dt_dptau or dt_dptau**2 + outputs[self._output_val_names[name]] = np.tensordot(L_de, u, axes=(1, 0)) + outputs[self._output_rate_names[name]] = (a / dt_dptau).T + outputs[self._output_rate2_names[name]] = (b / dt_dptau ** 2).T + else: + size = np.prod(options['shape']) - rate = a / inputs['dt_dstau'][:, np.newaxis] - rate = np.reshape(rate, (num_output_nodes,) + options['shape']) + u_flat = np.reshape(inputs[self._input_names[name]], + newshape=(num_control_input_nodes, size)) - rate2 = b / inputs['dt_dstau'][:, np.newaxis] ** 2 - rate2 = np.reshape(rate2, (num_output_nodes,) + options['shape']) + a = self.D.dot(u_flat) + b = self.D2.dot(u_flat) - outputs[self._output_val_names[name]] = val - outputs[self._output_rate_names[name]] = rate - outputs[self._output_rate2_names[name]] = rate2 + val = np.reshape(self.L.dot(u_flat), (num_output_nodes,) + options['shape']) + + rate = a / inputs['dt_dstau'][:, np.newaxis] + rate = np.reshape(rate, (num_output_nodes,) + options['shape']) + + rate2 = b / inputs['dt_dstau'][:, np.newaxis] ** 2 + rate2 = np.reshape(rate2, (num_output_nodes,) + options['shape']) + + outputs[self._output_val_names[name]] = val + outputs[self._output_rate_names[name]] = rate + outputs[self._output_rate2_names[name]] = rate2 def compute_partials(self, inputs, partials): """ @@ -268,30 +375,64 @@ def compute_partials(self, inputs, partials): """ control_options = self.options['control_options'] num_input_nodes = self.options['grid_data'].subset_num_nodes['control_input'] + nn = self.options['grid_data'].num_nodes dstau_dt = np.reciprocal(inputs['dt_dstau']) dstau_dt2 = (dstau_dt ** 2)[:, np.newaxis] dstau_dt3 = (dstau_dt ** 3)[:, np.newaxis] + t_duration = inputs['t_duration'] + for name in control_options: - control_name = self._input_names[name] + if control_options[name]['control_type'] == 'polynomial': + options = control_options[name] + control_name = self._input_names[name] + num_input_nodes = options['order'] + 1 + L_de, D_de, D2_de = self._matrices[name] + + size = self.sizes[name] + rate_name = self._output_rate_names[name] + rate2_name = self._output_rate2_names[name] + + # Unroll matrix-shaped controls into an array at each node + u_d = np.reshape(inputs[control_name], (num_input_nodes, size)) + + t_duration_tile = np.tile(t_duration, size * nn) - size = self.sizes[name] - rate_name = self._output_rate_names[name] - rate2_name = self._output_rate2_names[name] + partials[rate_name, 't_duration'] = \ + 0.5 * (-np.dot(D_de, u_d).ravel(order='F') / (0.5 * t_duration_tile) ** 2) - # Unroll shaped controls into an array at each node - u_flat = np.reshape(inputs[control_name], (num_input_nodes, size)) + partials[rate2_name, 't_duration'] = \ + -1.0 * (np.dot(D2_de, u_d).ravel(order='F') / (0.5 * t_duration_tile) ** 3) - partials[rate_name, 'dt_dstau'] = (-self.D.dot(u_flat) * dstau_dt2).ravel() - partials[rate2_name, 'dt_dstau'] = (-2.0 * self.D2.dot(u_flat) * dstau_dt3).ravel() + t_duration_x_size = np.repeat(t_duration, size * nn)[:, np.newaxis] - dstau_dt_x_size = np.repeat(dstau_dt, size)[:, np.newaxis] - dstau_dt2_x_size = np.repeat(dstau_dt2, size)[:, np.newaxis] + r_nz, c_nz = self.rate_jac_rows[name], self.rate_jac_cols[name] + partials[rate_name, control_name] = \ + (self.rate_jacs[name] / (0.5 * t_duration_x_size))[r_nz, c_nz] - partials[rate_name, control_name] = self.rate_jacs[name].multiply(dstau_dt_x_size).data + r_nz, c_nz = self.rate2_jac_rows[name], self.rate2_jac_cols[name] + partials[rate2_name, control_name] = \ + (self.rate2_jacs[name] / (0.5 * t_duration_x_size) ** 2)[r_nz, c_nz] + else: + control_name = self._input_names[name] - partials[rate2_name, control_name] = self.rate2_jacs[name].multiply(dstau_dt2_x_size).data + size = self.sizes[name] + rate_name = self._output_rate_names[name] + rate2_name = self._output_rate2_names[name] + + # Unroll shaped controls into an array at each node + u_flat = np.reshape(inputs[control_name], (num_input_nodes, size)) + + partials[rate_name, 'dt_dstau'] = (-self.D.dot(u_flat) * dstau_dt2).ravel() + partials[rate2_name, 'dt_dstau'] = (-2.0 * self.D2.dot(u_flat) * dstau_dt3).ravel() + + dstau_dt_x_size = np.repeat(dstau_dt, size)[:, np.newaxis] + dstau_dt2_x_size = np.repeat(dstau_dt2, size)[:, np.newaxis] + + partials[rate_name, control_name] = self.rate_jacs[name].multiply(dstau_dt_x_size).data + + partials[rate2_name, control_name] = self.rate2_jacs[name].multiply(dstau_dt2_x_size).data class ControlGroup(om.Group): @@ -326,7 +467,7 @@ def setup(self): if len(control_options) < 1: return - + self.add_subsystem( 'control_interp_comp', subsys=ControlInterpComp(time_units=time_units, grid_data=gd, output_grid_data=ogd, @@ -345,35 +486,62 @@ def configure_io(self): self.control_interp_comp.configure_io() for name, options in control_options.items(): - dvname = f'controls:{name}' - shape = options['shape'] - size = np.prod(shape) - if options['opt']: - desvar_indices = get_desvar_indices(size, num_input_nodes, - options['fix_initial'], options['fix_final']) - - if len(desvar_indices) > 0: - coerce_desvar_option = CoerceDesvar(num_input_nodes, desvar_indices, - options=options) - - lb = np.zeros_like(desvar_indices, dtype=float) - lb[:] = -INF_BOUND if coerce_desvar_option('lower') is None else \ - coerce_desvar_option('lower') - - ub = np.zeros_like(desvar_indices, dtype=float) - ub[:] = INF_BOUND if coerce_desvar_option('upper') is None else \ - coerce_desvar_option('upper') - - self.add_design_var(name=dvname, + if options['control_type'] == 'polynomial': + num_input_nodes = options['order'] + 1 + shape = options['shape'] + default_val = reshape_val(options['val'], shape, num_input_nodes) + if options['opt']: + + desvar_indices = np.arange(num_input_nodes, dtype=int) + if options['fix_initial']: + desvar_indices = desvar_indices[1:] + if options['fix_final']: + desvar_indices = desvar_indices[:-1] + + lb = -INF_BOUND if options['lower'] is None else options['lower'] + ub = INF_BOUND if options['upper'] is None else options['upper'] + + self.add_design_var(f'polynomial_controls:{name}', lower=lb, upper=ub, - scaler=coerce_desvar_option('scaler'), - adder=coerce_desvar_option('adder'), - ref0=coerce_desvar_option('ref0'), - ref=coerce_desvar_option('ref'), + ref=options['ref'], + ref0=options['ref0'], + adder=options['adder'], + scaler=options['scaler'], indices=desvar_indices, flat_indices=True) - default_val = reshape_val(options['val'], shape, num_input_nodes) - - self.set_input_defaults(name=dvname, val=default_val, units=options['units']) + self.set_input_defaults(name=f'polynomial_controls:{name}', val=default_val, units=options['units']) + else: + dvname = f'controls:{name}' + shape = options['shape'] + size = np.prod(shape) + if options['opt']: + desvar_indices = get_desvar_indices(size, num_input_nodes, + options['fix_initial'], options['fix_final']) + + if len(desvar_indices) > 0: + coerce_desvar_option = CoerceDesvar(num_input_nodes, desvar_indices, + options=options) + + lb = np.zeros_like(desvar_indices, dtype=float) + lb[:] = -INF_BOUND if coerce_desvar_option('lower') is None else \ + coerce_desvar_option('lower') + + ub = np.zeros_like(desvar_indices, dtype=float) + ub[:] = INF_BOUND if coerce_desvar_option('upper') is None else \ + coerce_desvar_option('upper') + + self.add_design_var(name=dvname, + lower=lb, + upper=ub, + scaler=coerce_desvar_option('scaler'), + adder=coerce_desvar_option('adder'), + ref0=coerce_desvar_option('ref0'), + ref=coerce_desvar_option('ref'), + indices=desvar_indices, + flat_indices=True) + + default_val = reshape_val(options['val'], shape, num_input_nodes) + + self.set_input_defaults(name=dvname, val=default_val, units=options['units']) diff --git a/dymos/transcriptions/explicit_shooting/explicit_shooting.py b/dymos/transcriptions/explicit_shooting/explicit_shooting.py index 8bcf7b77c..728aeeac3 100644 --- a/dymos/transcriptions/explicit_shooting/explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/explicit_shooting.py @@ -362,7 +362,7 @@ def setup_controls(self, phase): phase.add_subsystem('control_group', subsys=control_group, - promotes=['dt_dstau', 'controls:*', 'control_values:*', 'control_rates:*']) + promotes=['dt_dstau', '*controls:*', '*control_values:*', '*control_rates:*']) control_prefix = 'controls:' if phase.timeseries_options['use_prefix'] else '' control_rate_prefix = 'control_rates:' if phase.timeseries_options['use_prefix'] else '' diff --git a/dymos/transcriptions/pseudospectral/birkhoff.py b/dymos/transcriptions/pseudospectral/birkhoff.py index 265203cea..7ec6a5175 100644 --- a/dymos/transcriptions/pseudospectral/birkhoff.py +++ b/dymos/transcriptions/pseudospectral/birkhoff.py @@ -174,7 +174,7 @@ def configure_controls(self, phase): if phase.control_options: phase.control_group.configure_io() phase.promotes('control_group', - any=['controls:*', 'control_values:*', 'control_rates:*']) + any=['*controls:*', '*control_values:*', '*control_rates:*']) phase.connect('dt_dstau', 'control_group.dt_dstau') diff --git a/dymos/transcriptions/pseudospectral/gauss_lobatto.py b/dymos/transcriptions/pseudospectral/gauss_lobatto.py index 93e635eaf..483a7af7d 100644 --- a/dymos/transcriptions/pseudospectral/gauss_lobatto.py +++ b/dymos/transcriptions/pseudospectral/gauss_lobatto.py @@ -150,32 +150,37 @@ def configure_controls(self, phase): disc_src_idxs = (disc_src_idxs,) col_src_idxs = (col_src_idxs,) + if options['control_type'] == 'polynomial': + control_str = 'polynomial_control' + else: + control_str = 'control' + if options['targets']: - phase.connect(f'control_values:{name}', + phase.connect(f'{control_str}_values:{name}', [f'rhs_disc.{t}' for t in options['targets']], src_indices=disc_src_idxs, flat_src_indices=True) - phase.connect(f'control_values:{name}', + phase.connect(f'{control_str}_values:{name}', [f'rhs_col.{t}' for t in options['targets']], src_indices=col_src_idxs, flat_src_indices=True) # Rate targets if options['rate_targets']: - phase.connect(f'control_rates:{name}_rate', + phase.connect(f'{control_str}_rates:{name}_rate', [f'rhs_disc.{t}' for t in options['rate_targets']], src_indices=disc_src_idxs, flat_src_indices=True) - phase.connect(f'control_rates:{name}_rate', + phase.connect(f'{control_str}_rates:{name}_rate', [f'rhs_col.{t}' for t in options['rate_targets']], src_indices=col_src_idxs, flat_src_indices=True) # Second time derivative targets must be specified explicitly if options['rate2_targets']: - phase.connect(f'control_rates:{name}_rate2', + phase.connect(f'{control_str}_rates:{name}_rate2', [f'rhs_disc.{t}' for t in options['rate2_targets']], src_indices=disc_src_idxs, flat_src_indices=True) - phase.connect(f'control_rates:{name}_rate2', + phase.connect(f'{control_str}_rates:{name}_rate2', [f'rhs_col.{t}' for t in options['rate2_targets']], src_indices=col_src_idxs, flat_src_indices=True) @@ -571,18 +576,28 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: path = f'polynomial_control_values:{var}' - src_units = phase.polynomial_control_options[var]['units'] - src_shape = phase.polynomial_control_options[var]['shape'] + if var in phase.control_options: + src_units = phase.control_options[var]['units'] + src_shape = phase.control_options[var]['shape'] + else: + src_units = phase.polynomial_control_options[var]['units'] + src_shape = phase.polynomial_control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] path = f'polynomial_control_rates:{control_name}_rate' - control = phase.polynomial_control_options[control_name] + if control_name in phase.control_options: + control = phase.control_options[control_name] + else: + control = phase.polynomial_control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] path = f'polynomial_control_rates:{control_name}_rate2' - control = phase.polynomial_control_options[control_name] + if control_name in phase.control_options: + control = phase.control_options[control_name] + else: + control = phase.polynomial_control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] elif var_type == 'parameter': diff --git a/dymos/transcriptions/pseudospectral/pseudospectral_base.py b/dymos/transcriptions/pseudospectral/pseudospectral_base.py index 411440065..333b9b55e 100644 --- a/dymos/transcriptions/pseudospectral/pseudospectral_base.py +++ b/dymos/transcriptions/pseudospectral/pseudospectral_base.py @@ -121,7 +121,7 @@ def configure_controls(self, phase): if phase.control_options: phase.control_group.configure_io() phase.promotes('control_group', - any=['controls:*', 'control_values:*', 'control_rates:*']) + any=['*controls:*', '*control_values:*', '*control_rates:*']) phase.connect('dt_dstau', 'control_group.dt_dstau') @@ -416,8 +416,6 @@ def configure_defects(self, phase): phase.connect('t_duration_val', 'continuity_comp.t_duration') for name, options in phase.control_options.items(): - control_src_name = f'control_values:{name}' - # The sub-indices of control_disc indices that are segment ends segment_end_idxs = grid_data.subset_node_indices['segment_ends'] src_idxs = get_src_indices_by_row(segment_end_idxs, options['shape'], flat=True) @@ -425,18 +423,23 @@ def configure_defects(self, phase): # enclose indices in tuple to ensure shaping of indices works src_idxs = (src_idxs,) + if options['control_type'] == 'polynomial': + control_str = 'polynomial_control' + else: + control_str = 'control' + if options['continuity']: - phase.connect(control_src_name, + phase.connect(f'{control_str}_values:{name}', f'continuity_comp.controls:{name}', src_indices=src_idxs, flat_src_indices=True) if options['rate_continuity']: - phase.connect(f'control_rates:{name}_rate', + phase.connect(f'{control_str}_rates:{name}_rate', f'continuity_comp.control_rates:{name}_rate', src_indices=src_idxs, flat_src_indices=True) if options['rate2_continuity']: - phase.connect(f'control_rates:{name}_rate2', + phase.connect(f'{control_str}_rates:{name}_rate2', f'continuity_comp.control_rates:{name}_rate2', src_indices=src_idxs, flat_src_indices=True) diff --git a/dymos/transcriptions/solve_ivp/solve_ivp.py b/dymos/transcriptions/solve_ivp/solve_ivp.py index a8d478399..04e484ebb 100644 --- a/dymos/transcriptions/solve_ivp/solve_ivp.py +++ b/dymos/transcriptions/solve_ivp/solve_ivp.py @@ -309,8 +309,8 @@ def setup_controls(self, phase): phase.add_subsystem('control_group', subsys=control_group, - promotes=['controls:*', 'control_values:*', 'control_values_all:*', - 'control_rates:*']) + promotes=['*controls:*', '*control_values:*', '*control_values_all:*', + '*control_rates:*']) def configure_controls(self, phase): """ diff --git a/dymos/transcriptions/transcription_base.py b/dymos/transcriptions/transcription_base.py index adb8f5f87..0c4a9d139 100644 --- a/dymos/transcriptions/transcription_base.py +++ b/dymos/transcriptions/transcription_base.py @@ -158,6 +158,8 @@ def setup_controls(self, phase): phase.add_subsystem('control_group', subsys=control_group) + + phase.connect('t_duration_val', 'control_group.t_duration') def configure_controls(self, phase): """ diff --git a/dymos/utils/introspection.py b/dymos/utils/introspection.py index 775fa29bc..8854efd37 100644 --- a/dymos/utils/introspection.py +++ b/dymos/utils/introspection.py @@ -62,9 +62,15 @@ def classify_var(var, time_options, state_options, parameter_options, control_op return 'state' elif var in control_options: if control_options[var]['opt']: - return 'indep_control' + if control_options[var]['control_type'] == 'polynomial': + return 'indep_polynomial_control' + else: + return 'indep_control' else: - return 'input_control' + if control_options[var]['control_type'] == 'polynomial': + return 'input_polynomial_control' + else: + return 'input_control' elif var in polynomial_control_options: if polynomial_control_options[var]['opt']: return 'indep_polynomial_control' @@ -74,12 +80,18 @@ def classify_var(var, time_options, state_options, parameter_options, control_op return 'parameter' elif var.endswith('_rate'): if var[:-5] in control_options: - return 'control_rate' + if control_options[var[:-5]]['control_type'] == 'polynomial': + return 'polynomial_control_rate' + else: + return 'control_rate' elif var[:-5] in polynomial_control_options: return 'polynomial_control_rate' elif var.endswith('_rate2'): if var[:-6] in control_options: - return 'control_rate2' + if control_options[var[:-6]]['control_type'] == 'polynomial': + return 'polynomial_control_rate2' + else: + return 'control_rate2' elif var[:-6] in polynomial_control_options: return 'polynomial_control_rate2' elif timeseries_options is not None: @@ -272,8 +284,12 @@ def _configure_constraint_introspection(phase): elif var_type == 'polynomial_control_rate': prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' control_name = var[:-5] - control_shape = phase.polynomial_control_options[control_name]['shape'] - control_units = phase.polynomial_control_options[control_name]['units'] + if control_name in phase.polynomial_control_options: + control_shape = phase.polynomial_control_options[control_name]['shape'] + control_units = phase.polynomial_control_options[control_name]['units'] + else: + control_shape = phase.control_options[control_name]['shape'] + control_units = phase.control_options[control_name]['units'] con['shape'] = control_shape con['units'] = get_rate_units(control_units, time_units, deriv=1) \ if con['units'] is None else con['units'] @@ -285,8 +301,12 @@ def _configure_constraint_introspection(phase): elif var_type == 'polynomial_control_rate2': prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' control_name = var[:-6] - control_shape = phase.polynomial_control_options[control_name]['shape'] - control_units = phase.polynomial_control_options[control_name]['units'] + if control_name in phase.polynomial_control_options: + control_shape = phase.polynomial_control_options[control_name]['shape'] + control_units = phase.polynomial_control_options[control_name]['units'] + else: + control_shape = phase.control_options[control_name]['shape'] + control_units = phase.control_options[control_name]['units'] con['shape'] = control_shape con['units'] = get_rate_units(control_units, time_units, deriv=2) \ if con['units'] is None else con['units'] From 8aad8e3486227256bee37bba385e0ceb2da7c7cd Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 8 May 2024 11:32:30 -0400 Subject: [PATCH 02/17] Removing more of the polynomial controls --- ...doc_brachistochrone_polynomial_controls.py | 4 +- dymos/phase/options.py | 87 ------ dymos/phase/phase.py | 266 +----------------- dymos/phase/simulation_phase.py | 12 +- .../explicit_shooting/explicit_shooting.py | 124 ++------ .../explicit_shooting/ode_evaluation_group.py | 3 +- dymos/transcriptions/transcription_base.py | 51 +--- dymos/utils/introspection.py | 59 +--- 8 files changed, 53 insertions(+), 553 deletions(-) diff --git a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py index 975067dd1..86bf2de2b 100644 --- a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py +++ b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py @@ -1141,8 +1141,8 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) - # phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) - phase.add_control('theta', control_type='polynomial', order=1, units='deg', lower=0.01, upper=179.9, continuity=False, rate_continuity=False, rate2_continuity=False) + phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + # phase.add_control('theta', control_type='polynomial', order=1, units='deg', lower=0.01, upper=179.9, continuity=False, rate_continuity=False, rate2_continuity=False) phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) diff --git a/dymos/phase/options.py b/dymos/phase/options.py index 3702478e6..2fe9a8d18 100644 --- a/dymos/phase/options.py +++ b/dymos/phase/options.py @@ -133,93 +133,6 @@ def __init__(self, read_only=False): 'This option is invalid if opt=False.') -class PolynomialControlOptionsDictionary(om.OptionsDictionary): - """ - An OptionsDictionary specific to controls. - - Parameters - ---------- - read_only : bool - If True, setting (via __setitem__ or update) is not permitted. - """ - def __init__(self, read_only=False): - super(PolynomialControlOptionsDictionary, self).__init__(read_only) - - self.declare(name='name', types=str, - desc='The name of ODE system parameter to be controlled.') - - self.declare(name='units', default=_unspecified, - allow_none=True, desc='The units in which the control variable is defined.') - - self.declare(name='desc', types=str, default='', - desc='The description of the control variable.') - - self.declare(name='opt', default=True, types=bool, - desc='If True, the control value will be a design variable ' - 'for the optimization problem. If False, allow the ' - 'control to be connected externally.') - - self.declare(name='fix_initial', types=bool, default=False, - desc='If True, the initial value of this control is fixed and not a ' - 'design variable. This option is invalid if opt=False.') - - self.declare(name='fix_final', types=bool, default=False, - desc='If True, the final value of this control is fixed and not a ' - 'design variable. This option is invalid if opt=False.') - - self.declare(name='targets', allow_none=True, default=_unspecified, - desc='Targets in the ODE to which the state is connected') - - self.declare(name='rate_targets', allow_none=True, default=_unspecified, - desc='The targets in the ODE to which the polynomial control rate is connected') - - self.declare(name='rate2_targets', allow_none=True, default=_unspecified, - desc='The targets in the ODE to which the polynomial control 2nd derivative ' - 'is connected') - - self.declare(name='val', types=(Iterable, np.ndarray, Number), default=np.zeros(1), - desc='The default value of the control variable at the ' - 'control discretization nodes.') - - self.declare(name='shape', types=Iterable, allow_none=True, default=None, - desc='The shape of the control variable at each point in time.') - - self.declare(name='lower', types=(Iterable, Number), default=None, - allow_none=True, - desc='The lower bound of the control variable at the nodes. This ' - 'option is invalid if opt=False.') - - self.declare(name='upper', types=(Iterable, Number), default=None, - allow_none=True, - desc='The upper bound of the control variable at the nodes. This ' - 'option is invalid if opt=False.') - - self.declare(name='scaler', types=(Iterable, Number), default=None, - allow_none=True, - desc='The scaler of the control variable at the nodes. This ' - 'option is invalid if opt=False.') - - self.declare(name='adder', types=(Iterable, Number), default=None, - allow_none=True, - desc='The adder of the control variable at the nodes. This' - 'option is invalid if opt=False.') - - self.declare(name='ref0', types=(Iterable, Number), default=None, - allow_none=True, - desc='The zero-reference value of the control variable at the nodes. This ' - 'option is invalid if opt=False.') - - self.declare(name='ref', types=(Iterable, Number), default=None, - allow_none=True, - desc='The unit-reference value of the control variable at the nodes. This ' - 'option is invalid if opt=False.') - - self.declare(name='order', types=(int,), default=None, allow_none=True, - desc='A integer that provides the interpolation order when the control is ' - 'to assume a single polynomial basis across the entire phase, or None ' - 'to use the default control behavior.') - - def check_valid_shape(name, value): """ Raise an exception if the value specified for a shape is invalid. diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index 1b3cd6437..e6f87053b 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -19,7 +19,7 @@ from .options import ControlOptionsDictionary, ParameterOptionsDictionary, \ StateOptionsDictionary, TimeOptionsDictionary, ConstraintOptionsDictionary, \ - PolynomialControlOptionsDictionary, GridRefinementOptionsDictionary, SimulateOptionsDictionary, \ + GridRefinementOptionsDictionary, SimulateOptionsDictionary, \ TimeseriesOutputOptionsDictionary, PhaseTimeseriesOptionsDictionary from ..transcriptions.transcription_base import TranscriptionBase @@ -72,7 +72,6 @@ def __init__(self, from_phase=None, **kwargs): # self.time_options = TimeOptionsDictionary() # self.state_options = {} # self.control_options = {} - # self.polynomial_control_options = {} # self.parameter_options = {} self.refine_options = GridRefinementOptionsDictionary() self.simulate_options = SimulateOptionsDictionary() @@ -144,9 +143,6 @@ def duplicate(self, transcription=None, boundary_constraints=False, path_constra for control_name, control_options in self.control_options.items(): p.control_options[control_name] = deepcopy(control_options) - for pc_name, pc_options in self.polynomial_control_options.items(): - p.polynomial_control_options[pc_name] = deepcopy(pc_options) - p.time_options['fix_initial'] = fix_initial_time _fis = [] if fix_initial_states is None else fix_initial_states @@ -201,8 +197,6 @@ def initialize(self): desc='Options for each parameter in this phase.') self.options.declare('control_options', types=dict, default={}, desc='Options for each control in this phase.') - self.options.declare('polynomial_control_options', types=dict, default={}, - desc='Options for each polynomial control in this phase.') @property def time_options(self): @@ -224,10 +218,6 @@ def parameter_options(self): def control_options(self): return self.options['control_options'] - @property - def polynomial_control_options(self): - return self.options['polynomial_control_options'] - def add_state(self, name, units=_unspecified, shape=_unspecified, rate_source=_unspecified, targets=_unspecified, val=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, @@ -524,8 +514,6 @@ def check_parameter(self, name): raise ValueError(f'{name} has already been added as a control.') elif name in self.parameter_options: raise ValueError(f'{name} has already been added as a parameter.') - elif name in self.polynomial_control_options: - raise ValueError(f'{name} has already been added as a polynomial control.') def add_control(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, opt=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, @@ -902,132 +890,18 @@ def add_polynomial_control(self, name, order, desc=_unspecified, val=_unspecifie self.check_parameter(name) - if name not in self.polynomial_control_options: - self.polynomial_control_options[name] = PolynomialControlOptionsDictionary() - self.polynomial_control_options[name]['name'] = name - self.polynomial_control_options[name]['order'] = order - - self.set_polynomial_control_options(name, order, desc, val, units, opt, - fix_initial, fix_final, lower, upper, - scaler, adder, ref0, ref, - targets, rate_targets, rate2_targets, shape) - - def set_polynomial_control_options(self, name, order=_unspecified, desc=_unspecified, val=_unspecified, - units=_unspecified, opt=_unspecified, fix_initial=_unspecified, - fix_final=_unspecified, lower=_unspecified, upper=_unspecified, - scaler=_unspecified, adder=_unspecified, ref0=_unspecified, - ref=_unspecified, targets=_unspecified, rate_targets=_unspecified, - rate2_targets=_unspecified, shape=_unspecified): - """ - Set options on an existing polynomial control variable in the phase. - - Parameters - ---------- - name : str - Name of the controllable parameter in the ODE. - order : int - The order of the interpolating polynomial used to represent the control value in - phase tau space. - desc : str - A description of the polynomial control. - val : float or ndarray - Default value of the control at all nodes. If val scalar and the control - is dynamic it will be broadcast. - units : str or None or 0 - Units in which the control variable is defined. If 0, use the units declared - for the parameter in the ODE. - opt : bool - If True (default) the value(s) of this control will be design variables in - the optimization problem, in the path 'phase_name.indep_controls.controls:control_name'. - If False, the values of this control will exist as input controls:{name}. - fix_initial : bool - If True, the given initial value of the polynomial control is not a design variable and - will not be changed during the optimization. - fix_final : bool - If True, the given final value of the polynomial control is not a design variable and - will not be changed during the optimization. - lower : float or ndarray - The lower bound of the control at the nodes of the phase. - upper : float or ndarray - The upper bound of the control at the nodes of the phase. - scaler : float or ndarray - The scaler of the control value at the nodes of the phase. - adder : float or ndarray - The adder of the control value at the nodes of the phase. - ref0 : float or ndarray - The zero-reference value of the control at the nodes of the phase. - ref : float or ndarray - The unit-reference value of the control at the nodes of the phase. - targets : Sequence of str or None - Targets in the ODE to which this polynomial control is connected. - rate_targets : None or str - The name of the parameter in the ODE to which the first time-derivative - of the control value is connected. - rate2_targets : None or str - The name of the parameter in the ODE to which the second time-derivative - of the control value is connected. - shape : Sequence of int - The shape of the control variable at each point in time. - """ - if order is not _unspecified: - self.polynomial_control_options[name]['order'] = order - - if units is not _unspecified: - self.polynomial_control_options[name]['units'] = units - - if opt is not _unspecified: - self.polynomial_control_options[name]['opt'] = opt - - if desc is not _unspecified: - self.polynomial_control_options[name]['desc'] = desc - - if targets is not _unspecified: - if isinstance(targets, str): - self.polynomial_control_options[name]['targets'] = (targets,) - else: - self.polynomial_control_options[name]['targets'] = targets - - if rate_targets is not _unspecified: - if isinstance(rate_targets, str): - self.polynomial_control_options[name]['rate_targets'] = (rate_targets,) - else: - self.polynomial_control_options[name]['rate_targets'] = rate_targets - - if rate2_targets is not _unspecified: - if isinstance(rate2_targets, str): - self.polynomial_control_options[name]['rate2_targets'] = (rate2_targets,) - else: - self.polynomial_control_options[name]['rate2_targets'] = rate2_targets - - if val is not _unspecified: - self.polynomial_control_options[name]['val'] = val - - if shape is not _unspecified: - self.polynomial_control_options[name]['shape'] = shape - - if fix_initial is not _unspecified: - self.polynomial_control_options[name]['fix_initial'] = fix_initial - - if fix_final is not _unspecified: - self.polynomial_control_options[name]['fix_final'] = fix_final - - if lower is not _unspecified: - self.polynomial_control_options[name]['lower'] = lower - - if upper is not _unspecified: - self.polynomial_control_options[name]['upper'] = upper - - if scaler is not _unspecified: - self.polynomial_control_options[name]['scaler'] = scaler - - if adder is not _unspecified: - self.polynomial_control_options[name]['adder'] = adder + if name not in self.control_options: + self.control_options[name] = ControlOptionsDictionary() + self.control_options[name]['name'] = name + self.control_options[name]['order'] = order - if ref0 is not _unspecified: - self.polynomial_control_options[name]['ref0'] = ref0 + control_type = 'polynomial' - if ref is not _unspecified: - self.polynomial_control_options[name]['ref'] = ref + self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, + fix_final=fix_final, targets=targets, rate_targets=rate_targets, + rate2_targets=rate2_targets, val=val, shape=shape, lower=lower, + upper=upper, scaler=scaler, adder=adder, ref0=ref0, ref=ref, + continuity=False, rate_continuity=False, rate2_continuity=False) def add_parameter(self, name, val=_unspecified, units=_unspecified, opt=False, desc=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, @@ -1960,7 +1834,6 @@ def classify_var(self, var): return classify_var(var, time_options=self.time_options, state_options=self.state_options, parameter_options=self.parameter_options, control_options=self.control_options, - polynomial_control_options=self.polynomial_control_options, timeseries_options=self._timeseries) def _check_ode(self): @@ -2002,9 +1875,6 @@ def setup(self): if self.control_options: transcription.setup_controls(self) - if self.polynomial_control_options: - transcription.setup_polynomial_controls(self) - if self.parameter_options: transcription.setup_parameters(self) @@ -2035,10 +1905,6 @@ def configure(self): configure_controls_introspection(self.control_options, ode, time_units=self.time_options['units']) - if self.polynomial_control_options: - configure_controls_introspection(self.polynomial_control_options, ode, - time_units=self.time_options['units']) - if self.parameter_options: try: configure_parameters_introspection(self.parameter_options, ode) @@ -2049,7 +1915,6 @@ def configure(self): transcription.configure_states_introspection(self) transcription.configure_time(self) transcription.configure_controls(self) - transcription.configure_polynomial_controls(self) transcription.configure_parameters(self) transcription.configure_states(self) @@ -2149,26 +2014,6 @@ def _check_control_options(self): self.control_options[name]['rate_continuity'] = False self.control_options[name]['rate2_continuity'] = False - def _check_polynomial_control_options(self): - """ - Check that polynomial control options are valid and issue warnings if invalid options are provided. - - Warns - ----- - RuntimeWarning - RuntimeWarning is issued in the case of one or more invalid time options. - """ - for name, options in self.control_options.items(): - if not options['opt']: - invalid_options = [] - for opt in 'lower', 'upper', 'scaler', 'adder', 'ref', 'ref0': - if options[opt] is not None: - invalid_options.append(opt) - if invalid_options: - warnings.warn(f"Invalid options for non-optimal polynoimal control '{name}' in " - f"phase '{self.name}': {', '.join(invalid_options)}", - RuntimeWarning) - def _check_parameter_options(self): """ Check that parameter options are valid and issue warnings if invalid @@ -2328,8 +2173,6 @@ def interp(self, name=None, ys=None, xs=None, nodes=None, kind='linear', axis=0) node_locations, _ = lgl(self.control_options[name]['order'] + 1) else: node_locations = gd.node_ptau[gd.subset_node_indices['control_input']] - elif name in self.polynomial_control_options: - node_locations, _ = lgl(self.polynomial_control_options[name]['order'] + 1) else: raise ValueError('Could not find a state, control, or polynomial control named ' f'{name} to be interpolated.\nPlease explicitly specified the ' @@ -2460,10 +2303,6 @@ def get_simulation_phase(self, times_per_seg=_unspecified, method=_unspecified, sim_phase.control_options[control_name] = deepcopy(control_options) sim_phase.control_options[control_name]['opt'] = False - for pc_name, pc_options in self.polynomial_control_options.items(): - sim_phase.polynomial_control_options[pc_name] = deepcopy(pc_options) - sim_phase.polynomial_control_options[pc_name]['opt'] = False - sim_phase._timeseries = {ts_name: ts_options for ts_name, ts_options in self._timeseries.items() if ts_name == 'timeseries'} @@ -2527,12 +2366,6 @@ def initialize_values_from_phase(self, prob, from_phase, phase_path='', skip_par ip = ip_dict[f'control_group.control_interp_comp.controls:{name}'] prob[f'{self_path}controls:{name}'][...] = ip['val'] - # Assign polynomial control values - for name, options in phs.polynomial_control_options.items(): - ip = ip_dict[f'polynomial_control_group.interp_comp.' - f'polynomial_controls:{name}'] - prob[f'{self_path}polynomial_controls:{name}'][...] = ip['val'] - # Assign parameter values for name in phs.parameter_options: units = phs.parameter_options[name]['units'] @@ -2775,55 +2608,6 @@ def is_control_rate_fixed(self, name, loc): control_name = name[:-6] return self.is_control_fixed(control_name, loc) - def is_polynomial_control_fixed(self, name, loc): - """ - Test if the polynomial control of the given name is guaranteed to be fixed at the initial or final time. - - Parameters - ---------- - name : str - The name of the polynomial control to be tested. - loc : str - The location of time to be tested: either 'initial' or 'final'. - - Returns - ------- - bool - True if the state of the given name is guaranteed to be fixed at the given location. - """ - if loc == 'initial': - res = self.polynomial_control_options[name]['fix_initial'] - elif loc == 'final': - res = self.polynomial_control_options[name]['fix_final'] - else: - raise ValueError(f'Unknown value for argument "loc": must be either "initial" or ' - f'"final" but got {loc}') - return res - - def is_polynomial_control_rate_fixed(self, name, loc): - """ - Test if the polynomial control rate of the given name is guaranteed to be fixed at the initial or final time. - - Parameters - ---------- - name : str - The name of the control to be tested. - loc : str - The location of time to be tested: either 'initial' or 'final'. - - Returns - ------- - bool - True if the state of the given name is guaranteed to be fixed at the given location. - """ - if name.endswith('_rate') and self.polynomial_control_options is not None and \ - name[:-5] in self.polynomial_control_options: - control_name = name[:-5] - elif name.endswith('_rate2') and self.polynomial_control_options is not None and \ - name[:-6] in self.options['polynomial_control_options']: - control_name = name[:-6] - return self.is_polynomial_control_fixed(control_name, loc) - def _indices_in_constraints(self, name, loc): """ Returns a set of the C-order flattened indices involving constraint of the given name at the given loc. @@ -3027,34 +2811,6 @@ def load_case(self, case): f" different final value this will overwrite the user-specified value" issue_warning(warning_message) - # Set the output polynomial control outputs from the previous solution as the value - for pc_name, options in self.polynomial_control_options.items(): - if f'{prev_timeseries_prom_path}.polynomial_controls:{pc_name}' in prev_vars_prom2abs: - prev_pc_path = f'{prev_timeseries_prom_path}.polynomial_controls:{pc_name}' - elif f'{prev_timeseries_prom_path}.{pc_name}' in prev_vars_prom2abs: - prev_pc_path = f'{prev_timeseries_prom_path}.{pc_name}' - else: - issue_warning(f'Unable to find polynomial control {pc_name} in timeseries data from case being ' - f'loaded.', om.OpenMDAOWarning) - continue - - prev_pc_val = prev_vars[prev_pc_path]['val'] - prev_pc_units = prev_vars[prev_pc_path]['units'] - interp_vals = self.interp(name=pc_name, - xs=prev_time_val, - ys=prev_pc_val[unique_idxs], - kind='slinear') - if options['lower'] is not None or options['upper'] is not None: - interp_vals = interp_vals.clip(options['lower'], options['upper']) - self.set_val(f'polynomial_controls:{pc_name}', - interp_vals, - units=prev_pc_units) - if options['fix_final']: - warning_message = f"{phase_name}.polynomial_controls:{pc_name} specifies 'fix_final=True'. " \ - f"If the given restart file has a" \ - f" different final value this will overwrite the user-specified value" - issue_warning(warning_message) - # Set the timeseries parameter outputs from the previous solution as the parameter value for param_name in self.parameter_options: if f'{prev_phase_prom_path}.parameter_vals:{param_name}' in prev_vars: diff --git a/dymos/phase/simulation_phase.py b/dymos/phase/simulation_phase.py index e60fd1c66..b4e899516 100644 --- a/dymos/phase/simulation_phase.py +++ b/dymos/phase/simulation_phase.py @@ -89,12 +89,12 @@ def set_vals_from_phase(self, from_phase): self.set_val(f'parameters:{name}', val, units=options['units']) for name, options in self.control_options.items(): - val = from_phase.get_val(f'controls:{name}', units=options['units'], from_src=False) - self.set_val(f'controls:{name}', val, units=options['units']) - - for name, options in self.polynomial_control_options.items(): - val = from_phase.get_val(f'polynomial_controls:{name}', units=options['units'], from_src=False) - self.set_val(f'polynomial_controls:{name}', val, units=options['units']) + if options['control_type'] == 'polynomial': + control_str = 'polynomial_controls' + else: + control_str = 'controls' + val = from_phase.get_val(f'{control_str}:{name}', units=options['units'], from_src=False) + self.set_val(f'{control_str}:{name}', val, units=options['units']) def add_boundary_constraint(self, name, loc, constraint_name=None, units=None, shape=None, indices=None, lower=None, upper=None, equals=None, diff --git a/dymos/transcriptions/explicit_shooting/explicit_shooting.py b/dymos/transcriptions/explicit_shooting/explicit_shooting.py index 728aeeac3..adcf9fcee 100644 --- a/dymos/transcriptions/explicit_shooting/explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/explicit_shooting.py @@ -303,7 +303,6 @@ def setup_ode(self, phase): state_options=phase.state_options, parameter_options=phase.parameter_options, control_options=phase.control_options, - polynomial_control_options=phase.polynomial_control_options, method=self.options['method'], atol=self.options['atol'], rtol=self.options['rtol'], @@ -423,116 +422,27 @@ def configure_controls(self, phase): # Control targets are detected automatically targets = get_targets(ode_inputs, control_name, options['targets']) - if targets: - phase.connect(f'control_values:{control_name}', - [f'ode.{t}' for t in targets]) - - # Rate targets - rate_targets = get_targets(ode_inputs, control_name, options['rate_targets']) - - if rate_targets: - phase.connect(f'control_rates:{control_name}_rate', - [f'ode.{t}' for t in rate_targets]) - - # Second time derivative targets must be specified explicitly - rate2_targets = get_targets(ode_inputs, control_name, options['rate2_targets']) - - if rate2_targets: - phase.connect(f'control_rates:{control_name}_rate2', - [f'ode.{t}' for t in targets]) - - def setup_polynomial_controls(self, phase): - """ - Adds the polynomial control group to the model if any polynomial controls are present. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - if phase.polynomial_control_options: - sys = PolynomialControlGroup(grid_data=self._output_grid_data, - polynomial_control_options=phase.polynomial_control_options, - time_units=phase.time_options['units']) - phase.add_subsystem('polynomial_control_group', subsys=sys, - promotes_inputs=['*'], promotes_outputs=['*']) - - control_prefix = 'polynomial_controls:' if phase.timeseries_options['use_prefix'] else '' - control_rate_prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' - - for name, options in phase.polynomial_control_options.items(): - - for ts_name, ts_options in phase._timeseries.items(): - if f'{control_prefix}{name}' not in ts_options['outputs']: - phase.add_timeseries_output(name, output_name=f'{control_prefix}{name}', - timeseries=ts_name) - if f'{control_rate_prefix}{name}_rate' not in ts_options['outputs'] \ - and phase.timeseries_options['include_control_rates']: - phase.add_timeseries_output(f'{name}_rate', output_name=f'{control_rate_prefix}{name}_rate', - timeseries=ts_name) - if f'{control_rate_prefix}{name}_rate2' not in ts_options['outputs'] \ - and phase.timeseries_options['include_control_rates']: - phase.add_timeseries_output(f'{name}_rate2', output_name=f'{control_rate_prefix}{name}_rate2', - timeseries=ts_name) - - def configure_polynomial_controls(self, phase): - """ - Configure the inputs/outputs for the polynomial controls. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - if not phase.polynomial_control_options: - return - - integrator_comp = phase._get_subsystem('integrator') - integrator_comp._configure_polynomial_controls() - - polynomial_control_group = phase._get_subsystem('polynomial_control_group') - polynomial_control_group.configure_io() - - ode = phase._get_subsystem('ode') - ode_inputs = get_promoted_vars(ode, 'input') - - # Add the appropriate design parameters - for control_name, options in phase.polynomial_control_options.items(): - - phase.promotes('integrator', inputs=[f'polynomial_controls:{control_name}']) - - if options['opt']: - ncin = options['order'] + 1 - coerce_desvar_option = CoerceDesvar(num_input_nodes=ncin, options=options) - - phase.add_design_var(name=f'polynomial_controls:{control_name}', - lower=coerce_desvar_option('lower'), - upper=coerce_desvar_option('upper'), - scaler=coerce_desvar_option('scaler'), - adder=coerce_desvar_option('adder'), - ref0=coerce_desvar_option('ref0'), - ref=coerce_desvar_option('ref'), - indices=coerce_desvar_option.desvar_indices) - - # Control targets are detected automatically - targets = get_targets(ode_inputs, control_name, options['targets']) + if options['control_type'] == 'polynomial': + control_str = 'polynomial_control' + else: + control_str = 'control' if targets: - phase.connect(f'polynomial_control_values:{control_name}', + phase.connect(f'{control_str}_values:{control_name}', [f'ode.{t}' for t in targets]) # Rate targets rate_targets = get_targets(ode_inputs, control_name, options['rate_targets']) if rate_targets: - phase.connect(f'polynomial_control_rates:{control_name}_rate', + phase.connect(f'{control_str}_rates:{control_name}_rate', [f'ode.{t}' for t in rate_targets]) # Second time derivative targets must be specified explicitly rate2_targets = get_targets(ode_inputs, control_name, options['rate2_targets']) if rate2_targets: - phase.connect(f'polynomial_control_rates:{control_name}_rate2', + phase.connect(f'{control_str}_rates:{control_name}_rate2', [f'ode.{t}' for t in targets]) def configure_parameters(self, phase): @@ -784,13 +694,13 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): linear = False obj_path = f'control_values:{var}' elif var_type == 'indep_polynomial_control': - shape = phase.polynomial_control_options[var]['shape'] - units = phase.polynomial_control_options[var]['units'] + shape = phase.control_options[var]['shape'] + units = phase.control_options[var]['units'] linear = True obj_path = f'polynomial_control_values:{var}' elif var_type == 'input_polynomial_control': - shape = phase.polynomial_control_options[var]['shape'] - units = phase.polynomial_control_options[var]['units'] + shape = phase.control_options[var]['shape'] + units = phase.control_options[var]['units'] linear = False obj_path = f'polynomial_control_values:{var}' elif var_type == 'parameter': @@ -809,8 +719,8 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): obj_path = f'control_rates:{var}' elif var_type in ('polynomial_control_rate', 'polynomial_control_rate2'): control_var = var[:-5] - shape = phase.polynomial_control_options[control_var]['shape'] - control_units = phase.polynomial_control_options[control_var]['units'] + shape = phase.control_options[control_var]['shape'] + control_units = phase.control_options[control_var]['units'] d = 2 if var_type == 'polynomial_control_rate2' else 1 control_rate_units = get_rate_units(control_units, time_units, deriv=d) units = control_rate_units @@ -939,18 +849,18 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: path = f'polynomial_control_values:{var}' - src_units = phase.polynomial_control_options[var]['units'] - src_shape = phase.polynomial_control_options[var]['shape'] + src_units = phase.control_options[var]['units'] + src_shape = phase.control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] path = f'polynomial_control_rates:{control_name}_rate' - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] path = f'polynomial_control_rates:{control_name}_rate2' - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] elif var_type == 'parameter': diff --git a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py index f7b491865..924ee71ed 100644 --- a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py +++ b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py @@ -164,8 +164,7 @@ def configure(self): configure_states_discovery(self._state_options, ode) configure_states_introspection(self._state_options, self._time_options, self._control_options, - self._parameter_options, - self._polynomial_control_options, ode) + self._parameter_options, ode) self._configure_states() self.state_rate_collector.configure_io() diff --git a/dymos/transcriptions/transcription_base.py b/dymos/transcriptions/transcription_base.py index 0c4a9d139..f85043ed4 100644 --- a/dymos/transcriptions/transcription_base.py +++ b/dymos/transcriptions/transcription_base.py @@ -187,55 +187,6 @@ def configure_controls(self, phase): phase.add_timeseries_output(f'{name}_rate2', output_name=f'{control_rate_prefix}{name}_rate2', timeseries=ts_name) - def setup_polynomial_controls(self, phase): - """ - Adds the polynomial control group to the model if any polynomial controls are present. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - if phase.polynomial_control_options: - sys = PolynomialControlGroup(grid_data=self.grid_data, - polynomial_control_options=phase.polynomial_control_options, - time_units=phase.time_options['units']) - phase.add_subsystem('polynomial_control_group', subsys=sys, - promotes_inputs=['polynomial_controls:*'], - promotes_outputs=['polynomial_control_values:*', - 'polynomial_control_rates:*']) - - phase.connect('t_duration_val', 'polynomial_control_group.t_duration') - - def configure_polynomial_controls(self, phase): - """ - Configure the inputs/outputs for the polynomial controls. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - if phase.polynomial_control_options: - phase.polynomial_control_group.configure_io() - - prefix = 'polynomial_controls:' if phase.timeseries_options['use_prefix'] else '' - rate_prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' - - for name, options in phase.polynomial_control_options.items(): - for ts_name, ts_options in phase._timeseries.items(): - if f'{prefix}{name}' not in ts_options['outputs']: - phase.add_timeseries_output(name, output_name=f'{prefix}{name}', - timeseries=ts_name) - if f'{rate_prefix}{name}_rate' not in ts_options['outputs'] and \ - (phase.timeseries_options['include_control_rates'] or options['rate_targets']): - phase.add_timeseries_output(f'{name}_rate', output_name=f'{rate_prefix}{name}_rate', - timeseries=ts_name) - if f'{rate_prefix}{name}_rate2' not in ts_options['outputs'] and \ - (phase.timeseries_options['include_control_rates'] or options['rate2_targets']): - phase.add_timeseries_output(f'{name}_rate2', output_name=f'{rate_prefix}{name}_rate2', - timeseries=ts_name) - def setup_parameters(self, phase): """ Sets input defaults for parameters and optionally adds design variables. @@ -311,7 +262,7 @@ def configure_states_introspection(self, phase): ode = self._get_ode(phase) try: configure_states_introspection(phase.state_options, phase.time_options, phase.control_options, - phase.parameter_options, phase.polynomial_control_options, ode) + phase.parameter_options, ode) except (ValueError, RuntimeError) as e: raise RuntimeError(f'Error during configure_states_introspection in phase {phase.pathname}.') from e diff --git a/dymos/utils/introspection.py b/dymos/utils/introspection.py index 8854efd37..61d4c1332 100644 --- a/dymos/utils/introspection.py +++ b/dymos/utils/introspection.py @@ -14,7 +14,7 @@ def classify_var(var, time_options, state_options, parameter_options, control_options, - polynomial_control_options, timeseries_options=None): + timeseries_options=None): """ Classifies a variable of the given name or path. @@ -71,11 +71,6 @@ def classify_var(var, time_options, state_options, parameter_options, control_op return 'input_polynomial_control' else: return 'input_control' - elif var in polynomial_control_options: - if polynomial_control_options[var]['opt']: - return 'indep_polynomial_control' - else: - return 'input_polynomial_control' elif var in parameter_options: return 'parameter' elif var.endswith('_rate'): @@ -84,16 +79,12 @@ def classify_var(var, time_options, state_options, parameter_options, control_op return 'polynomial_control_rate' else: return 'control_rate' - elif var[:-5] in polynomial_control_options: - return 'polynomial_control_rate' elif var.endswith('_rate2'): if var[:-6] in control_options: if control_options[var[:-6]]['control_type'] == 'polynomial': return 'polynomial_control_rate2' else: return 'control_rate2' - elif var[:-6] in polynomial_control_options: - return 'polynomial_control_rate2' elif timeseries_options is not None: for timeseries in timeseries_options: if var in timeseries_options[timeseries]['outputs']: @@ -284,12 +275,8 @@ def _configure_constraint_introspection(phase): elif var_type == 'polynomial_control_rate': prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' control_name = var[:-5] - if control_name in phase.polynomial_control_options: - control_shape = phase.polynomial_control_options[control_name]['shape'] - control_units = phase.polynomial_control_options[control_name]['units'] - else: - control_shape = phase.control_options[control_name]['shape'] - control_units = phase.control_options[control_name]['units'] + control_shape = phase.control_options[control_name]['shape'] + control_units = phase.control_options[control_name]['units'] con['shape'] = control_shape con['units'] = get_rate_units(control_units, time_units, deriv=1) \ if con['units'] is None else con['units'] @@ -301,12 +288,8 @@ def _configure_constraint_introspection(phase): elif var_type == 'polynomial_control_rate2': prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' control_name = var[:-6] - if control_name in phase.polynomial_control_options: - control_shape = phase.polynomial_control_options[control_name]['shape'] - control_units = phase.polynomial_control_options[control_name]['units'] - else: - control_shape = phase.control_options[control_name]['shape'] - control_units = phase.control_options[control_name]['units'] + control_shape = phase.control_options[control_name]['shape'] + control_units = phase.control_options[control_name]['units'] con['shape'] = control_shape con['units'] = get_rate_units(control_units, time_units, deriv=2) \ if con['units'] is None else con['units'] @@ -540,7 +523,7 @@ def configure_time_introspection(time_options, ode): def configure_states_introspection(state_options, time_options, control_options, parameter_options, - polynomial_control_options, ode): + ode): """ Modifies state options in-place, automatically determining 'targets', 'units', and 'shape' if necessary. @@ -594,8 +577,7 @@ def configure_states_introspection(state_options, time_options, control_options, # 3. Attempt rate-source introspection rate_src = options['rate_source'] - rate_src_type = classify_var(rate_src, time_options, state_options, parameter_options, control_options, - polynomial_control_options) + rate_src_type = classify_var(rate_src, time_options, state_options, parameter_options, control_options) if rate_src_type in {'t', 't_phase'}: rate_src_units = time_options['units'] @@ -603,12 +585,9 @@ def configure_states_introspection(state_options, time_options, control_options, elif rate_src_type == 'state': rate_src_units = state_options[rate_src]['units'] rate_src_shape = state_options[rate_src]['shape'] - elif rate_src_type in ['input_control', 'indep_control']: + elif rate_src_type in ['input_control', 'indep_control', 'input_polynomial_control', 'indep_polynomial_control']: rate_src_units = control_options[rate_src]['units'] rate_src_shape = control_options[rate_src]['shape'] - elif rate_src_type in ['input_polynomial_control', 'indep_polynomial_control']: - rate_src_units = polynomial_control_options[rate_src]['units'] - rate_src_shape = polynomial_control_options[rate_src]['shape'] elif rate_src_type == 'parameter': rate_src_units = parameter_options[rate_src]['units'] rate_src_shape = parameter_options[rate_src]['shape'] @@ -624,12 +603,12 @@ def configure_states_introspection(state_options, time_options, control_options, rate_src_shape = control['shape'] elif rate_src_type == 'polynomial_control_rate': control_name = rate_src[:-5] - control = polynomial_control_options[control_name] + control = control_options[control_name] rate_src_units = get_rate_units(control['units'], time_units, deriv=1) rate_src_shape = control['shape'] elif rate_src_type == 'polynomial_control_rate2': control_name = rate_src[:-6] - control = polynomial_control_options[control_name] + control = control_options[control_name] rate_src_units = get_rate_units(control['units'], time_units, deriv=2) rate_src_shape = control['shape'] elif rate_src_type == 'ode': @@ -1087,7 +1066,7 @@ def configure_duration_balance_introspection(phase): options['units'] = param_units if options['units'] is None else options['units'] options['var_path'] = f'parameter_vals:{var}' - elif var_type in ['indep_control', 'input_control']: + elif var_type in ['indep_control', 'input_control', 'indep_polynomial_control', 'input_polynomial_control']: prefix = 'controls:' if dymos_options['use_timeseries_prefix'] else '' control_shape = phase.control_options[var]['shape'] control_units = phase.control_options[var]['units'] @@ -1096,14 +1075,6 @@ def configure_duration_balance_introspection(phase): options['units'] = control_units if options['units'] is None else options['units'] options['var_path'] = f'timeseries.{prefix}{var}' - elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: - prefix = 'polynomial_controls:' if dymos_options['use_timeseries_prefix'] else '' - control_shape = phase.polynomial_control_options[var]['shape'] - control_units = phase.polynomial_control_options[var]['units'] - options['shape'] = control_shape - options['units'] = control_units if options['units'] is None else options['units'] - options['var_path'] = f'timeseries.{prefix}{var}' - elif var_type == 'control_rate': prefix = 'control_rates:' if dymos_options['use_timeseries_prefix'] else '' control_name = var[:-5] @@ -1127,8 +1098,8 @@ def configure_duration_balance_introspection(phase): elif var_type == 'polynomial_control_rate': prefix = 'polynomial_control_rates:' if dymos_options['use_timeseries_prefix'] else '' control_name = var[:-5] - control_shape = phase.polynomial_control_options[control_name]['shape'] - control_units = phase.polynomial_control_options[control_name]['units'] + control_shape = phase.control_options[control_name]['shape'] + control_units = phase.control_options[control_name]['units'] options['shape'] = control_shape options['units'] = get_rate_units(control_units, time_units, deriv=1) \ if options['units'] is None else options['units'] @@ -1137,8 +1108,8 @@ def configure_duration_balance_introspection(phase): elif var_type == 'polynomial_control_rate2': prefix = 'polynomial_control_rates:' if dymos_options['use_timeseries_prefix'] else '' control_name = var[:-6] - control_shape = phase.polynomial_control_options[control_name]['shape'] - control_units = phase.polynomial_control_options[control_name]['units'] + control_shape = phase.control_options[control_name]['shape'] + control_units = phase.control_options[control_name]['units'] options['shape'] = control_shape options['units'] = get_rate_units(control_units, time_units, deriv=2) \ if options['units'] is None else options['units'] From faaa86e156336cf40501c3a29cc7a8b2052749a1 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 8 May 2024 14:36:52 -0400 Subject: [PATCH 03/17] WIP: removing more polynomial controls --- ...doc_brachistochrone_polynomial_controls.py | 4 +- dymos/grid_refinement/error_estimation.py | 30 +- .../test/test_error_estimation.py | 5 - dymos/load_case.py | 14 - dymos/phase/analytic_phase.py | 62 -- dymos/phase/simulation_phase.py | 13 - dymos/phase/test/test_analytic_phase.py | 16 - dymos/trajectory/trajectory.py | 10 +- dymos/transcriptions/analytic/analytic.py | 11 - dymos/transcriptions/common/__init__.py | 1 - .../common/polynomial_control_group.py | 283 -------- .../test/test_polynomial_control_group.py | 687 ------------------ .../barycentric_control_interp_comp.py | 5 +- .../explicit_shooting/explicit_shooting.py | 3 +- .../explicit_shooting/ode_evaluation_group.py | 73 +- .../transcriptions/pseudospectral/birkhoff.py | 51 +- .../pseudospectral/gauss_lobatto.py | 72 +- .../pseudospectral/radau_pseudospectral.py | 39 +- .../solve_ivp/components/__init__.py | 2 - .../solve_ivp_polynomial_control_group.py | 293 -------- dymos/transcriptions/solve_ivp/solve_ivp.py | 51 +- dymos/transcriptions/transcription_base.py | 2 +- 22 files changed, 34 insertions(+), 1693 deletions(-) delete mode 100644 dymos/transcriptions/common/polynomial_control_group.py delete mode 100644 dymos/transcriptions/common/test/test_polynomial_control_group.py delete mode 100644 dymos/transcriptions/solve_ivp/components/solve_ivp_polynomial_control_group.py diff --git a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py index 86bf2de2b..dcd27278f 100644 --- a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py +++ b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py @@ -1142,7 +1142,6 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) - # phase.add_control('theta', control_type='polynomial', order=1, units='deg', lower=0.01, upper=179.9, continuity=False, rate_continuity=False, rate2_continuity=False) phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1582,5 +1581,4 @@ def test_brachistochrone_polynomial_control_birkhoff(self): if __name__ == '__main__': # pragma: no cover - z = TestBrachistochronePolynomialControlRate2PathConstrained() - z.test_brachistochrone_polynomial_control_gauss_lobatto() \ No newline at end of file + unittest.main() \ No newline at end of file diff --git a/dymos/grid_refinement/error_estimation.py b/dymos/grid_refinement/error_estimation.py index d44e5b9eb..82e44cac4 100644 --- a/dymos/grid_refinement/error_estimation.py +++ b/dymos/grid_refinement/error_estimation.py @@ -133,7 +133,6 @@ def eval_ode_on_grid(phase, transcription): time=phase.time_options, states=phase.state_options, controls=phase.control_options, - polynomial_controls=phase.polynomial_control_options, parameters=phase.parameter_options, ode_class=phase.options['ode_class'], ode_init_kwargs=phase.options[ @@ -179,7 +178,7 @@ def eval_ode_on_grid(phase, transcription): for name, options in phase.control_options.items(): targets = get_targets(ode, name, options['targets']) rate_targets = get_targets(ode, f'{name}_rate', options['rate_targets']) - rate2_targets = get_targets(ode, f'{name}_rate12', options['rate2_targets']) + rate2_targets = get_targets(ode, f'{name}_rate2', options['rate2_targets']) u_prev = phase.get_val(f'timeseries.{control_prefix}{name}', units=options['units']) u[name] = np.dot(L, u_prev) @@ -197,27 +196,6 @@ def eval_ode_on_grid(phase, transcription): u_rate2[name] = np.dot(L, u_rate2_prev) p_refine.set_val(f'control_rates:{name}_rate2', u_rate2[name]) - for name, options in phase.polynomial_control_options.items(): - targets = get_targets(ode, name, options['targets']) - rate_targets = get_targets(ode, f'{name}_rate', options['rate_targets']) - rate2_targets = get_targets(ode, f'{name}_rate2', options['rate2_targets']) - - p_prev = phase.get_val(f'timeseries.polynomial_controls:{name}', units=options['units']) - p[name] = np.dot(L, p_prev) - if targets: - p_refine.set_val(f'polynomial_controls:{name}', p[name]) - - if phase.timeseries_options['include_control_rates']: - p_rate_prev = phase.get_val(f'timeseries.polynomial_control_rates:{name}_rate') - p_rate[name] = np.dot(L, p_rate_prev) - if rate_targets: - p_refine.set_val(f'polynomial_control_rates:{name}_rate', p_rate[name]) - - p_rate2_prev = phase.get_val(f'timeseries.polynomial_control_rates:{name}_rate2') - p_rate2[name] = np.dot(L, p_rate2_prev) - if rate2_targets: - p_refine.set_val(f'polynomial_control_rates:{name}_rate2', p_rate2[name]) - # Configure the parameters for name, options in phase.parameter_options.items(): targets = get_targets(ode, name, options['targets']) @@ -257,16 +235,16 @@ def eval_ode_on_grid(phase, transcription): src_units = get_rate_units(u_units, phase.time_options['units'], deriv=2) f[name] = om.convert_units(u_rate2[rate_source], src_units, rate_units) elif rate_source_class in {'input_polynomial_control', 'indep_polynomial_control'}: - src_units = phase.polynomial_control_options[rate_source]['units'] + src_units = phase.control_options[rate_source]['units'] f[name] = om.convert_units(p[rate_source], src_units, rate_units) elif rate_source_class in {'polynomial_control_rate'}: pc_name = rate_source[:-5] - pc_units = phase.polynomial_control_options[pc_name]['units'] + pc_units = phase.control_options[pc_name]['units'] src_units = get_rate_units(pc_units, phase.time_options['units'], deriv=1) f[name] = om.convert_units(p_rate[rate_source], src_units, rate_units) elif rate_source_class in {'polynomial_control_rate2'}: pc_name = rate_source[:-6] - pc_units = phase.polynomial_control_options[pc_name]['units'] + pc_units = phase.control_options[pc_name]['units'] src_units = get_rate_units(pc_units, phase.time_options['units'], deriv=2) f[name] = om.convert_units(p_rate2[rate_source], src_units, rate_units) elif rate_source_class in {'parameter'}: diff --git a/dymos/grid_refinement/test/test_error_estimation.py b/dymos/grid_refinement/test/test_error_estimation.py index b54570bb6..b4fa695c9 100644 --- a/dymos/grid_refinement/test/test_error_estimation.py +++ b/dymos/grid_refinement/test/test_error_estimation.py @@ -110,11 +110,6 @@ def test_compute_state_quadratures(self): print(f'{name} interpolation error', max(np.abs(u[name].ravel() - u_solution.ravel()))) - for name, options in phase.polynomial_control_options.items(): - p_solution = phase.get_val(f'timeseries.polynomial_controls:{name}') - print(f'{name} interpolation error', - max(np.abs(pp[name].ravel() - p_solution.ravel()))) - for name, options in phase.state_options.items(): x_solution = phase.get_val(f'timeseries.states:{name}') f_solution = phase.get_val(f'timeseries.state_rates:{name}') diff --git a/dymos/load_case.py b/dymos/load_case.py index 5674b1edf..da7454de7 100644 --- a/dymos/load_case.py +++ b/dymos/load_case.py @@ -193,20 +193,6 @@ def load_case(problem, previous_solution, deprecation_warning=True): f" different final value this will overwrite the user-specified value" issue_warning(warning_message) - # Set the output polynomial control outputs from the previous solution as the value - for pc_name, options in phase.polynomial_control_options.items(): - pc_path = [s for s in phase_vars if - s.endswith(f'{phase_name}.polynomial_controls:{pc_name}')][0] - prev_pc_path = [s for s in prev_vars if s.endswith(f'{phase_name}.polynomial_controls:{pc_name}')][0] - prev_pc_val = prev_vars[prev_pc_path]['val'] - prev_pc_units = prev_vars[prev_pc_path]['units'] - problem.set_val(pc_path, prev_pc_val, units=prev_pc_units) - if options['fix_final']: - warning_message = f"{phase_name}.polynomial_controls:{pc_name} specifies 'fix_final=True'. " \ - f"If the given restart file has a" \ - f" different final value this will overwrite the user-specified value" - issue_warning(warning_message) - # Set the timeseries parameter outputs from the previous solution as the parameter value for param_name in phase.parameter_options: prev_match = [s for s in prev_vars if s.endswith(f'{phase_name}.parameters:{param_name}')] diff --git a/dymos/phase/analytic_phase.py b/dymos/phase/analytic_phase.py index eb3b416c6..4b8206c45 100644 --- a/dymos/phase/analytic_phase.py +++ b/dymos/phase/analytic_phase.py @@ -335,65 +335,6 @@ def add_polynomial_control(self, name, order, desc=_unspecified, val=_unspecifie """ raise NotImplementedError('AnalyticPhase does not support polynomial controls.') - def set_polynomial_control_options(self, name, order=_unspecified, desc=_unspecified, val=_unspecified, - units=_unspecified, opt=_unspecified, fix_initial=_unspecified, - fix_final=_unspecified, lower=_unspecified, upper=_unspecified, - scaler=_unspecified, adder=_unspecified, ref0=_unspecified, - ref=_unspecified, targets=_unspecified, rate_targets=_unspecified, - rate2_targets=_unspecified, shape=_unspecified): - """ - Set options on an existing polynomial control variable in the phase. - - Parameters - ---------- - name : str - Name of the controllable parameter in the ODE. - order : int - The order of the interpolating polynomial used to represent the control value in - phase tau space. - desc : str - A description of the polynomial control. - val : float or ndarray - Default value of the control at all nodes. If val scalar and the control - is dynamic it will be broadcast. - units : str or None or 0 - Units in which the control variable is defined. If 0, use the units declared - for the parameter in the ODE. - opt : bool - If True (default) the value(s) of this control will be design variables in - the optimization problem, in the path 'phase_name.indep_controls.controls:control_name'. - If False, the values of this control will exist as input controls:{name}. - fix_initial : bool - If True, the given initial value of the polynomial control is not a design variable and - will not be changed during the optimization. - fix_final : bool - If True, the given final value of the polynomial control is not a design variable and - will not be changed during the optimization. - lower : float or ndarray - The lower bound of the control at the nodes of the phase. - upper : float or ndarray - The upper bound of the control at the nodes of the phase. - scaler : float or ndarray - The scaler of the control value at the nodes of the phase. - adder : float or ndarray - The adder of the control value at the nodes of the phase. - ref0 : float or ndarray - The zero-reference value of the control at the nodes of the phase. - ref : float or ndarray - The unit-reference value of the control at the nodes of the phase. - targets : Sequence of str or None - Targets in the ODE to which this polynomial control is connected. - rate_targets : None or str - The name of the parameter in the ODE to which the first time-derivative - of the control value is connected. - rate2_targets : None or str - The name of the parameter in the ODE to which the second time-derivative - of the control value is connected. - shape : Sequence of int - The shape of the control variable at each point in time. - """ - raise NotImplementedError('AnalyticPhase does not support polynomial controls.') - def setup(self): """ Build the model hierarchy for a Dymos AnalyticPhase. @@ -406,9 +347,6 @@ def setup(self): if self.control_options: transcription.setup_controls(self) - if self.polynomial_control_options: - transcription.setup_polynomial_controls(self) - if self.parameter_options: transcription.setup_parameters(self) diff --git a/dymos/phase/simulation_phase.py b/dymos/phase/simulation_phase.py index b4e899516..6dbbc7071 100644 --- a/dymos/phase/simulation_phase.py +++ b/dymos/phase/simulation_phase.py @@ -273,19 +273,6 @@ def _check_control_options(self): """ pass - def _check_polynomial_control_options(self): - """ - Check that polynomial control options are valid and issue warnings if invalid options are provided. - - This check is not performed by SimulationPhase. - - Warns - ----- - RuntimeWarning - RuntimeWarning is issued in the case of one or more invalid time options. - """ - pass - def _check_parameter_options(self): """ Check that parameter options are valid and issue warnings if invalid options are provided. diff --git a/dymos/phase/test/test_analytic_phase.py b/dymos/phase/test/test_analytic_phase.py index e47cf6d47..8be126be9 100644 --- a/dymos/phase/test/test_analytic_phase.py +++ b/dymos/phase/test/test_analytic_phase.py @@ -284,22 +284,6 @@ def test_set_control_options(self): self.assertEqual('AnalyticPhase does not support controls.', str(e.exception)) - def test_add_polynomial_control(self): - phase = dm.AnalyticPhase(ode_class=SimpleIVPSolution, num_nodes=11) - - with self.assertRaises(NotImplementedError) as e: - phase.add_polynomial_control('foo', order=2) - - self.assertEqual('AnalyticPhase does not support polynomial controls.', str(e.exception)) - - def test_set_polynomial_control_options(self): - phase = dm.AnalyticPhase(ode_class=SimpleIVPSolution, num_nodes=11) - - with self.assertRaises(NotImplementedError) as e: - phase.set_polynomial_control_options('foo', lower=0) - - self.assertEqual('AnalyticPhase does not support polynomial controls.', str(e.exception)) - def test_timeseries_expr(self): p = om.Problem() traj = p.model.add_subsystem('traj', dm.Trajectory()) diff --git a/dymos/trajectory/trajectory.py b/dymos/trajectory/trajectory.py index 0f4c12db1..1e9bd07a9 100644 --- a/dymos/trajectory/trajectory.py +++ b/dymos/trajectory/trajectory.py @@ -532,7 +532,6 @@ def _configure_phase_options_dicts(self): phase_options_dicts[phs.name]['time_options'] = phs.time_options phase_options_dicts[phs.name]['state_options'] = phs.state_options phase_options_dicts[phs.name]['control_options'] = phs.control_options - phase_options_dicts[phs.name]['polynomial_control_options'] = phs.polynomial_control_options phase_options_dicts[phs.name]['parameter_options'] = phs.parameter_options all_ranks = self.comm.allgather(phase_options_dicts) @@ -543,7 +542,6 @@ def _configure_phase_options_dicts(self): phs.time_options.update(data[phase_name]['time_options']) phs.state_options.update(data[phase_name]['state_options']) phs.control_options.update(data[phase_name]['control_options']) - phs.polynomial_control_options.update(data[phase_name]['polynomial_control_options']) phs.parameter_options.update(data[phase_name]['parameter_options']) def _update_linkage_options_configure(self, linkage_options): @@ -619,17 +617,17 @@ def _update_linkage_options_configure(self, linkage_options): elif classes[i] in {'indep_polynomial_control', 'input_polynomial_control'}: prefix = 'polynomial_controls:' if use_prefix[i] else '' sources[i] = f'timeseries.{prefix}{vars[i]}' - units[i] = phases[i].polynomial_control_options[vars[i]]['units'] - shapes[i] = phases[i].polynomial_control_options[vars[i]]['shape'] + units[i] = phases[i].control_options[vars[i]]['units'] + shapes[i] = phases[i].control_options[vars[i]]['shape'] elif classes[i] in {'polynomial_control_rate', 'polynomial_control_rate2'}: prefix = 'polynomial_control_rates:' if use_prefix[i] else '' sources[i] = f'timeseries.{prefix}{vars[i]}' control_name = vars[i][:-5] if classes[i] == 'polynomial_control_rate' else vars[i][:-6] - control_units = phases[i].polynomial_control_options[control_name]['units'] + control_units = phases[i].control_options[control_name]['units'] time_units = phases[i].time_options['units'] deriv = 1 if classes[i].endswith('rate') else 2 units[i] = get_rate_units(control_units, time_units, deriv=deriv) - shapes[i] = phases[i].polynomial_control_options[control_name]['shape'] + shapes[i] = phases[i].control_options[control_name]['shape'] elif classes[i] == 'parameter': sources[i] = f'parameter_vals:{vars[i]}' units[i] = phases[i].parameter_options[vars[i]]['units'] diff --git a/dymos/transcriptions/analytic/analytic.py b/dymos/transcriptions/analytic/analytic.py index d29ffd190..5862a83aa 100644 --- a/dymos/transcriptions/analytic/analytic.py +++ b/dymos/transcriptions/analytic/analytic.py @@ -139,17 +139,6 @@ def setup_polynomial_controls(self, phase): """ pass - def configure_polynomial_controls(self, phase): - """ - Configure the inputs/outputs for the polynomial controls. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - pass - def setup_states(self, phase): """ Setup the states for this transcription. diff --git a/dymos/transcriptions/common/__init__.py b/dymos/transcriptions/common/__init__.py index a53b6c83e..273402be4 100644 --- a/dymos/transcriptions/common/__init__.py +++ b/dymos/transcriptions/common/__init__.py @@ -1,7 +1,6 @@ from .continuity_comp import RadauPSContinuityComp, GaussLobattoContinuityComp from .control_group import ControlGroup from .parameter_comp import ParameterComp -from .polynomial_control_group import PolynomialControlGroup from .time_comp import TimeComp from .timeseries_group import TimeseriesOutputGroup from .timeseries_output_comp import TimeseriesOutputComp diff --git a/dymos/transcriptions/common/polynomial_control_group.py b/dymos/transcriptions/common/polynomial_control_group.py deleted file mode 100644 index 3d5be2ba8..000000000 --- a/dymos/transcriptions/common/polynomial_control_group.py +++ /dev/null @@ -1,283 +0,0 @@ -import numpy as np - -import openmdao.api as om - -from ..grid_data import GridData -from ...utils.lgl import lgl -from ...utils.lagrange import lagrange_matrices -from ...utils.misc import get_rate_units, reshape_val -from ...utils.constants import INF_BOUND - -from ..._options import options as dymos_options - - -class LGLPolynomialControlComp(om.ExplicitComponent): - """ - Component which interpolates controls as a single polynomial across the entire phase. - - Parameters - ---------- - **kwargs : dict - Dictionary of optional arguments. - """ - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._no_check_partials = not dymos_options['include_check_partials'] - - def initialize(self): - """ - Declare component options. - """ - self.options.declare('time_units', default=None, allow_none=True, types=str, - desc='Units of time') - self.options.declare('grid_data', types=GridData, desc='Container object for grid info') - self.options.declare('polynomial_control_options', types=dict, - desc='Dictionary of options for the polynomial controls') - - self._matrices = {} - - def configure_io(self): - """ - I/O creation is delayed until configure so we can determine shape and units for the states. - """ - self._input_names = {} - self._output_val_names = {} - self._output_rate_names = {} - self._output_rate2_names = {} - self.val_jacs = {} - self.rate_jacs = {} - self.rate2_jacs = {} - self.val_jac_rows = {} - self.val_jac_cols = {} - self.rate_jac_rows = {} - self.rate_jac_cols = {} - self.rate2_jac_rows = {} - self.rate2_jac_cols = {} - self.sizes = {} - - self.add_input('t_duration', val=1.0, units=self.options['time_units'], - desc='duration of the phase to which this interpolated control group ' - 'belongs') - - gd = self.options['grid_data'] - num_nodes = gd.subset_num_nodes['all'] - eval_nodes = gd.node_ptau - - for name, options in self.options['polynomial_control_options'].items(): - disc_nodes, _ = lgl(options['order'] + 1) - num_control_input_nodes = len(disc_nodes) - shape = options['shape'] - size = np.prod(shape) - units = options['units'] - rate_units = get_rate_units(units, self.options['time_units'], deriv=1) - rate2_units = get_rate_units(units, self.options['time_units'], deriv=2) - - input_shape = (num_control_input_nodes,) + shape - output_shape = (num_nodes,) + shape - - L_de, D_de = lagrange_matrices(disc_nodes, eval_nodes) - _, D_dd = lagrange_matrices(disc_nodes, disc_nodes) - D2_de = np.dot(D_de, D_dd) - - self._matrices[name] = L_de, D_de, D2_de - - self._input_names[name] = f'polynomial_controls:{name}' - self._output_val_names[name] = f'polynomial_control_values:{name}' - self._output_rate_names[name] = f'polynomial_control_rates:{name}_rate' - self._output_rate2_names[name] = f'polynomial_control_rates:{name}_rate2' - - self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) - self.add_output(self._output_val_names[name], shape=output_shape, units=units) - self.add_output(self._output_rate_names[name], shape=output_shape, units=rate_units) - self.add_output(self._output_rate2_names[name], shape=output_shape, units=rate2_units) - - self.val_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) - self.rate_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) - self.rate2_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) - - for i in range(size): - self.val_jacs[name][:, i, :, i] = L_de - self.rate_jacs[name][:, i, :, i] = D_de - self.rate2_jacs[name][:, i, :, i] = D2_de - - self.val_jacs[name] = self.val_jacs[name].reshape((num_nodes * size, - num_control_input_nodes * size), - order='C') - self.rate_jacs[name] = self.rate_jacs[name].reshape((num_nodes * size, - num_control_input_nodes * size), - order='C') - self.rate2_jacs[name] = self.rate2_jacs[name].reshape((num_nodes * size, - num_control_input_nodes * size), - order='C') - self.val_jac_rows[name], self.val_jac_cols[name] = \ - np.where(self.val_jacs[name] != 0) - self.rate_jac_rows[name], self.rate_jac_cols[name] = \ - np.where(self.rate_jacs[name] != 0) - self.rate2_jac_rows[name], self.rate2_jac_cols[name] = \ - np.where(self.rate2_jacs[name] != 0) - - self.sizes[name] = size - - rs, cs = self.val_jac_rows[name], self.val_jac_cols[name] - self.declare_partials(of=self._output_val_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs, val=self.val_jacs[name][rs, cs]) - - rs = np.concatenate([np.arange(0, num_nodes * size, size, dtype=int) + i - for i in range(size)]) - - self.declare_partials(of=self._output_rate_names[name], - wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) - - self.declare_partials(of=self._output_rate_names[name], - wrt=self._input_names[name], - rows=self.rate_jac_rows[name], cols=self.rate_jac_cols[name]) - - self.declare_partials(of=self._output_rate2_names[name], - wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) - - self.declare_partials(of=self._output_rate2_names[name], - wrt=self._input_names[name], - rows=self.rate2_jac_rows[name], cols=self.rate2_jac_cols[name]) - - def compute(self, inputs, outputs): - """ - Interpolate control outputs. - - Parameters - ---------- - inputs : `Vector` - `Vector` containing inputs. - outputs : `Vector` - `Vector` containing outputs. - """ - dt_dptau = 0.5 * inputs['t_duration'] - - for name in self.options['polynomial_control_options']: - L_de, D_de, D2_de = self._matrices[name] - - u = inputs[self._input_names[name]] - - a = np.tensordot(D_de, u, axes=(1, 0)).T - b = np.tensordot(D2_de, u, axes=(1, 0)).T - - # divide each "row" by dt_dptau or dt_dptau**2 - outputs[self._output_val_names[name]] = np.tensordot(L_de, u, axes=(1, 0)) - outputs[self._output_rate_names[name]] = (a / dt_dptau).T - outputs[self._output_rate2_names[name]] = (b / dt_dptau ** 2).T - - def compute_partials(self, inputs, partials): - """ - Compute sub-jacobian parts. The model is assumed to be in an unscaled state. - - Parameters - ---------- - inputs : Vector - Unscaled, dimensional input variables read via inputs[key]. - partials : Jacobian - Subjac components written to partials[output_name, input_name]. - """ - nn = self.options['grid_data'].num_nodes - - t_duration = inputs['t_duration'] - - for name, options in self.options['polynomial_control_options'].items(): - control_name = self._input_names[name] - num_input_nodes = options['order'] + 1 - L_de, D_de, D2_de = self._matrices[name] - - size = self.sizes[name] - rate_name = self._output_rate_names[name] - rate2_name = self._output_rate2_names[name] - - # Unroll matrix-shaped controls into an array at each node - u_d = np.reshape(inputs[control_name], (num_input_nodes, size)) - - t_duration_tile = np.tile(t_duration, size * nn) - - partials[rate_name, 't_duration'] = \ - 0.5 * (-np.dot(D_de, u_d).ravel(order='F') / (0.5 * t_duration_tile) ** 2) - - partials[rate2_name, 't_duration'] = \ - -1.0 * (np.dot(D2_de, u_d).ravel(order='F') / (0.5 * t_duration_tile) ** 3) - - t_duration_x_size = np.repeat(t_duration, size * nn)[:, np.newaxis] - - r_nz, c_nz = self.rate_jac_rows[name], self.rate_jac_cols[name] - partials[rate_name, control_name] = \ - (self.rate_jacs[name] / (0.5 * t_duration_x_size))[r_nz, c_nz] - - r_nz, c_nz = self.rate2_jac_rows[name], self.rate2_jac_cols[name] - partials[rate2_name, control_name] = \ - (self.rate2_jacs[name] / (0.5 * t_duration_x_size) ** 2)[r_nz, c_nz] - - -class PolynomialControlGroup(om.Group): - """ - Group that contains and manages the LGLPolynomialControlComp. - - Parameters - ---------- - **kwargs : dict - Dictionary of optional arguments. - """ - def initialize(self): - """ - Declare group options. - """ - self.options.declare('polynomial_control_options', types=dict, - desc='Dictionary of options for the polynomial controls') - self.options.declare('time_units', default=None, allow_none=True, types=str, - desc='Units of time') - self.options.declare('grid_data', types=GridData, desc='Container object for grid info') - - def setup(self): - """ - Define the structure of the polynomial control group. - """ - opts = self.options - - # Pull out the interpolated controls - self.add_subsystem( - 'interp_comp', - subsys=LGLPolynomialControlComp(time_units=opts['time_units'], - grid_data=opts['grid_data'], - polynomial_control_options=opts['polynomial_control_options']), - promotes_inputs=['*'], - promotes_outputs=['*']) - - def configure_io(self): - """ - I/O creation is delayed until configure so we can determine shape and units for the states. - """ - interp_comp = self._get_subsystem('interp_comp') - interp_comp.configure_io() - - # For any interpolated control with `opt=True`, add an indep var comp output and - # setup the design variable for optimization. - for name, options in self.options['polynomial_control_options'].items(): - num_input_nodes = options['order'] + 1 - shape = options['shape'] - default_val = reshape_val(options['val'], shape, num_input_nodes) - if options['opt']: - - desvar_indices = np.arange(num_input_nodes, dtype=int) - if options['fix_initial']: - desvar_indices = desvar_indices[1:] - if options['fix_final']: - desvar_indices = desvar_indices[:-1] - - lb = -INF_BOUND if options['lower'] is None else options['lower'] - ub = INF_BOUND if options['upper'] is None else options['upper'] - - self.add_design_var(f'polynomial_controls:{name}', - lower=lb, - upper=ub, - ref=options['ref'], - ref0=options['ref0'], - adder=options['adder'], - scaler=options['scaler'], - indices=desvar_indices, - flat_indices=True) - - self.set_input_defaults(name=f'polynomial_controls:{name}', val=default_val, units=options['units']) diff --git a/dymos/transcriptions/common/test/test_polynomial_control_group.py b/dymos/transcriptions/common/test/test_polynomial_control_group.py deleted file mode 100644 index e7ac61aca..000000000 --- a/dymos/transcriptions/common/test/test_polynomial_control_group.py +++ /dev/null @@ -1,687 +0,0 @@ -import unittest - -import numpy as np -from numpy.testing import assert_almost_equal -import openmdao.api as om - -import dymos as dm -from dymos.utils.testing_utils import assert_check_partials -from dymos.transcriptions.common import TimeComp, PolynomialControlGroup -from dymos.transcriptions.grid_data import GridData -from dymos.phase.options import PolynomialControlOptionsDictionary -from dymos.utils.lgl import lgl - -from dymos.utils.misc import CompWrapperConfig, GroupWrapperConfig -TimeComp = CompWrapperConfig(TimeComp) -PolynomialControlGroup = GroupWrapperConfig(PolynomialControlGroup) - - -# Test 1: Let x = t**2, f = 2*t -def f_a(t): - return t ** 2 - - -def f1_a(t): - return 2 * t - - -def f2_a(t): - return 2.0 * np.ones_like(t) - - -# Test 1: Let v = t**3-10*t**2, f = 3*t**2 - 20*t -def f_b(t): - return t ** 3 - 10 * t ** 2 - - -def f1_b(t): - return 3 * t ** 2 - 20 * t - - -def f2_b(t): - return 6 * t - 20 - - -def f_c(t): - return t ** 2 - - -def f1_c(t): - return 2 * t - - -def f2_c(t): - return 2.0 * np.ones_like(t) - - -def f_d(t): - return t ** 3 - - -def f1_d(t): - return 3 * t ** 2 - - -def f2_d(t): - return 6 * t - - -class TestInterpolatedControLGroup(unittest.TestCase): - - def setUp(self): - dm.options['include_check_partials'] = True - - def tearDown(self): - dm.options['include_check_partials'] = False - - def test_polynomial_control_group_scalar_gl(self): - transcription = 'gauss-lobatto' - compressed = True - - segends = np.array([0.0, 3.0, 10.0]) - - gd = GridData(num_segments=2, - transcription_order=5, - segment_ends=segends, - transcription=transcription, - compressed=compressed) - - p = om.Problem(model=om.Group()) - - controls = {'a': PolynomialControlOptionsDictionary(), - 'b': PolynomialControlOptionsDictionary()} - - controls['a']['units'] = 'm' - controls['a']['order'] = 3 - controls['a']['shape'] = (1, ) - controls['a']['opt'] = True - - controls['b']['units'] = 'm' - controls['b']['order'] = 3 - controls['b']['shape'] = (1, ) - controls['b']['opt'] = True - - ivc = om.IndepVarComp() - p.model.add_subsystem('ivc', ivc, promotes_outputs=['*']) - - ivc.add_output('t_initial', val=0.0, units='s') - ivc.add_output('t_duration', val=10.0, units='s') - - p.model.add_subsystem('time_comp', - subsys=TimeComp(num_nodes=gd.num_nodes, node_ptau=gd.node_ptau, - node_dptau_dstau=gd.node_dptau_dstau, units='s'), - promotes_inputs=['t_initial', 't_duration'], - promotes_outputs=['t', 'dt_dstau']) - - polynomial_control_group = PolynomialControlGroup(grid_data=gd, - polynomial_control_options=controls, - time_units='s') - - p.model.add_subsystem('polynomial_control_group', - subsys=polynomial_control_group, - promotes_inputs=['*'], - promotes_outputs=['*']) - - p.setup(force_alloc_complex=True) - - p['t_initial'] = 0.0 - p['t_duration'] = 3.0 - - p.run_model() - - control_nodes_ptau, _ = lgl(controls['a']['order'] + 1) - - t_control_input = p['t_initial'] + 0.5 * (control_nodes_ptau + 1) * p['t_duration'] - t_all = p['t'] - - p['polynomial_controls:a'][:, 0] = f_a(t_control_input) - p['polynomial_controls:b'][:, 0] = f_b(t_control_input) - - p.run_model() - - a_value_expected = f_a(t_all) - b_value_expected = f_b(t_all) - - a_rate_expected = f1_a(t_all) - b_rate_expected = f1_b(t_all) - - a_rate2_expected = f2_a(t_all) - b_rate2_expected = f2_b(t_all) - - assert_almost_equal(p['polynomial_control_values:a'], - np.atleast_2d(a_value_expected).T) - - assert_almost_equal(p['polynomial_control_values:b'], - np.atleast_2d(b_value_expected).T) - - assert_almost_equal(p['polynomial_control_rates:a_rate'], - np.atleast_2d(a_rate_expected).T) - - assert_almost_equal(p['polynomial_control_rates:b_rate'], - np.atleast_2d(b_rate_expected).T) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'], - np.atleast_2d(a_rate2_expected).T) - - assert_almost_equal(p['polynomial_control_rates:b_rate2'], - np.atleast_2d(b_rate2_expected).T) - - np.set_printoptions(linewidth=1024) - cpd = p.check_partials(compact_print=False, out_stream=None, method='cs') - assert_check_partials(cpd) - - def test_polynomial_control_group_scalar_radau(self): - transcription = 'radau-ps' - compressed = False - - segends = np.array([0.0, 3.0, 10.0]) - - gd = GridData(num_segments=2, - transcription_order=5, - segment_ends=segends, - transcription=transcription, - compressed=compressed) - - p = om.Problem(model=om.Group()) - - controls = {'a': PolynomialControlOptionsDictionary(), - 'b': PolynomialControlOptionsDictionary()} - - controls['a']['units'] = 'm' - controls['a']['order'] = 3 - controls['a']['shape'] = (1, ) - controls['a']['opt'] = True - - controls['b']['units'] = 'm' - controls['b']['order'] = 3 - controls['b']['shape'] = (1, ) - controls['b']['opt'] = True - - ivc = om.IndepVarComp() - p.model.add_subsystem('ivc', ivc, promotes_outputs=['*']) - - ivc.add_output('t_initial', val=0.0, units='s') - ivc.add_output('t_duration', val=10.0, units='s') - - p.model.add_subsystem('time_comp', - subsys=TimeComp(num_nodes=gd.num_nodes, node_ptau=gd.node_ptau, - node_dptau_dstau=gd.node_dptau_dstau, units='s'), - promotes_inputs=['t_initial', 't_duration'], - promotes_outputs=['t', 'dt_dstau']) - - polynomial_control_group = PolynomialControlGroup(grid_data=gd, - polynomial_control_options=controls, - time_units='s') - - p.model.add_subsystem('polynomial_control_group', - subsys=polynomial_control_group, - promotes_inputs=['*'], - promotes_outputs=['*']) - - p.setup(force_alloc_complex=True) - - p['t_initial'] = 0.0 - p['t_duration'] = 3.0 - - p.run_model() - - control_nodes_ptau, _ = lgl(controls['a']['order'] + 1) - - t_control_input = p['t_initial'] + 0.5 * (control_nodes_ptau + 1) * p['t_duration'] - t_all = p['t'] - - p['polynomial_controls:a'][:, 0] = f_a(t_control_input) - p['polynomial_controls:b'][:, 0] = f_b(t_control_input) - - p.run_model() - - a_value_expected = f_a(t_all) - b_value_expected = f_b(t_all) - - a_rate_expected = f1_a(t_all) - b_rate_expected = f1_b(t_all) - - a_rate2_expected = f2_a(t_all) - b_rate2_expected = f2_b(t_all) - - assert_almost_equal(p['polynomial_control_values:a'], - np.atleast_2d(a_value_expected).T) - - assert_almost_equal(p['polynomial_control_values:b'], - np.atleast_2d(b_value_expected).T) - - assert_almost_equal(p['polynomial_control_rates:a_rate'], - np.atleast_2d(a_rate_expected).T) - - assert_almost_equal(p['polynomial_control_rates:b_rate'], - np.atleast_2d(b_rate_expected).T) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'], - np.atleast_2d(a_rate2_expected).T) - - assert_almost_equal(p['polynomial_control_rates:b_rate2'], - np.atleast_2d(b_rate2_expected).T) - - np.set_printoptions(linewidth=1024) - cpd = p.check_partials(compact_print=False, out_stream=None, method='cs') - assert_check_partials(cpd) - - def test_polynomial_control_group_vector_gl(self): - transcription = 'gauss-lobatto' - compressed = True - - segends = np.array([0.0, 3.0, 10.0]) - - gd = GridData(num_segments=2, - transcription_order=5, - segment_ends=segends, - transcription=transcription, - compressed=compressed) - - p = om.Problem(model=om.Group()) - - controls = {'a': PolynomialControlOptionsDictionary()} - - controls['a']['units'] = 'm' - controls['a']['order'] = 3 - controls['a']['opt'] = True - controls['a']['shape'] = (3,) - - ivc = om.IndepVarComp() - p.model.add_subsystem('ivc', ivc, promotes_outputs=['*']) - - ivc.add_output('t_initial', val=0.0, units='s') - ivc.add_output('t_duration', val=10.0, units='s') - - p.model.add_subsystem('time_comp', - subsys=TimeComp(num_nodes=gd.num_nodes, node_ptau=gd.node_ptau, - node_dptau_dstau=gd.node_dptau_dstau, units='s'), - promotes_inputs=['t_initial', 't_duration'], - promotes_outputs=['t', 'dt_dstau']) - - polynomial_control_group = PolynomialControlGroup(grid_data=gd, - polynomial_control_options=controls, - time_units='s') - - p.model.add_subsystem('polynomial_control_group', - subsys=polynomial_control_group, - promotes_inputs=['*'], - promotes_outputs=['*']) - - p.setup(force_alloc_complex=True) - - p['t_initial'] = 0.0 - p['t_duration'] = 3.0 - - p.run_model() - - control_nodes_ptau, _ = lgl(controls['a']['order'] + 1) - - t_control_input = p['t_initial'] + 0.5 * (control_nodes_ptau + 1) * p['t_duration'] - t_all = p['t'] - - p['polynomial_controls:a'][:, 0] = f_a(t_control_input) - p['polynomial_controls:a'][:, 1] = f_b(t_control_input) - p['polynomial_controls:a'][:, 2] = f_c(t_control_input) - - p.run_model() - - a0_value_expected = f_a(t_all) - a1_value_expected = f_b(t_all) - a2_value_expected = f_c(t_all) - - a0_rate_expected = f1_a(t_all) - a1_rate_expected = f1_b(t_all) - a2_rate_expected = f1_c(t_all) - - a0_rate2_expected = f2_a(t_all) - a1_rate2_expected = f2_b(t_all) - a2_rate2_expected = f2_c(t_all) - - assert_almost_equal(p['polynomial_control_values:a'][:, 0], - a0_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 1], - a1_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 2], - a2_value_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 0], - a0_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 1], - a1_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 2], - a2_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 0], - a0_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 1], - a1_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 2], - a2_rate2_expected) - - np.set_printoptions(linewidth=1024) - cpd = p.check_partials(method='cs', out_stream=None) - - assert_check_partials(cpd) - - def test_polynomial_control_group_vector_radau(self): - transcription = 'radau-ps' - compressed = True - - segends = np.array([0.0, 3.0, 10.0]) - - gd = GridData(num_segments=2, - transcription_order=5, - segment_ends=segends, - transcription=transcription, - compressed=compressed) - - p = om.Problem(model=om.Group()) - - controls = {'a': PolynomialControlOptionsDictionary()} - - controls['a']['units'] = 'm' - controls['a']['order'] = 3 - controls['a']['opt'] = True - controls['a']['shape'] = (3,) - - ivc = om.IndepVarComp() - p.model.add_subsystem('ivc', ivc, promotes_outputs=['*']) - - ivc.add_output('t_initial', val=0.0, units='s') - ivc.add_output('t_duration', val=10.0, units='s') - - p.model.add_subsystem('time_comp', - subsys=TimeComp(num_nodes=gd.num_nodes, node_ptau=gd.node_ptau, - node_dptau_dstau=gd.node_dptau_dstau, units='s'), - promotes_inputs=['t_initial', 't_duration'], - promotes_outputs=['t', 'dt_dstau']) - - polynomial_control_group = PolynomialControlGroup(grid_data=gd, - polynomial_control_options=controls, - time_units='s') - - p.model.add_subsystem('polynomial_control_group', - subsys=polynomial_control_group, - promotes_inputs=['*'], - promotes_outputs=['*']) - - p.setup(force_alloc_complex=True) - - p['t_initial'] = 0.0 - p['t_duration'] = 3.0 - - p.run_model() - - control_nodes_ptau, _ = lgl(controls['a']['order'] + 1) - - t_control_input = p['t_initial'] + 0.5 * (control_nodes_ptau + 1) * p['t_duration'] - t_all = p['t'] - - p['polynomial_controls:a'][:, 0] = f_a(t_control_input) - p['polynomial_controls:a'][:, 1] = f_b(t_control_input) - p['polynomial_controls:a'][:, 2] = f_c(t_control_input) - - p.run_model() - - a0_value_expected = f_a(t_all) - a1_value_expected = f_b(t_all) - a2_value_expected = f_c(t_all) - - a0_rate_expected = f1_a(t_all) - a1_rate_expected = f1_b(t_all) - a2_rate_expected = f1_c(t_all) - - a0_rate2_expected = f2_a(t_all) - a1_rate2_expected = f2_b(t_all) - a2_rate2_expected = f2_c(t_all) - - assert_almost_equal(p['polynomial_control_values:a'][:, 0], - a0_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 1], - a1_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 2], - a2_value_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 0], - a0_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 1], - a1_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 2], - a2_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 0], - a0_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 1], - a1_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 2], - a2_rate2_expected) - - np.set_printoptions(linewidth=1024) - cpd = p.check_partials(method='cs', out_stream=None) - - assert_check_partials(cpd) - - def test_polynomial_control_group_matrix_gl(self): - transcription = 'gauss-lobatto' - compressed = True - - segends = np.array([0.0, 3.0, 10.0]) - - gd = GridData(num_segments=2, - transcription_order=5, - segment_ends=segends, - transcription=transcription, - compressed=compressed) - - p = om.Problem(model=om.Group()) - - controls = {'a': PolynomialControlOptionsDictionary()} - - controls['a']['units'] = 'm' - controls['a']['order'] = 3 - controls['a']['opt'] = True - controls['a']['shape'] = (3, 1) - - ivc = om.IndepVarComp() - p.model.add_subsystem('ivc', ivc, promotes_outputs=['*']) - - ivc.add_output('t_initial', val=0.0, units='s') - ivc.add_output('t_duration', val=10.0, units='s') - - p.model.add_subsystem('time_comp', - subsys=TimeComp(num_nodes=gd.num_nodes, node_ptau=gd.node_ptau, - node_dptau_dstau=gd.node_dptau_dstau, units='s'), - promotes_inputs=['t_initial', 't_duration'], - promotes_outputs=['t', 'dt_dstau']) - - polynomial_control_group = PolynomialControlGroup(grid_data=gd, - polynomial_control_options=controls, - time_units='s') - - p.model.add_subsystem('polynomial_control_group', - subsys=polynomial_control_group, - promotes_inputs=['*'], - promotes_outputs=['*']) - - p.setup(force_alloc_complex=True) - - p['t_initial'] = 0.0 - p['t_duration'] = 3.0 - - p.run_model() - - control_nodes_ptau, _ = lgl(controls['a']['order'] + 1) - - t_control_input = p['t_initial'] + 0.5 * (control_nodes_ptau + 1) * p['t_duration'] - t_all = p['t'] - - p['polynomial_controls:a'][:, 0, 0] = f_a(t_control_input) - p['polynomial_controls:a'][:, 1, 0] = f_b(t_control_input) - p['polynomial_controls:a'][:, 2, 0] = f_c(t_control_input) - - p.run_model() - - a0_value_expected = f_a(t_all) - a1_value_expected = f_b(t_all) - a2_value_expected = f_c(t_all) - - a0_rate_expected = f1_a(t_all) - a1_rate_expected = f1_b(t_all) - a2_rate_expected = f1_c(t_all) - - a0_rate2_expected = f2_a(t_all) - a1_rate2_expected = f2_b(t_all) - a2_rate2_expected = f2_c(t_all) - - assert_almost_equal(p['polynomial_control_values:a'][:, 0, 0], - a0_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 1, 0], - a1_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 2, 0], - a2_value_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 0, 0], - a0_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 1, 0], - a1_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 2, 0], - a2_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 0, 0], - a0_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 1, 0], - a1_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 2, 0], - a2_rate2_expected) - - np.set_printoptions(linewidth=1024) - cpd = p.check_partials(method='cs', out_stream=None) - - assert_check_partials(cpd) - - def test_polynomial_control_group_matrix_radau(self): - transcription = 'radau-ps' - compressed = True - - segends = np.array([0.0, 3.0, 10.0]) - - gd = GridData(num_segments=2, - transcription_order=5, - segment_ends=segends, - transcription=transcription, - compressed=compressed) - - p = om.Problem(model=om.Group()) - - controls = {'a': PolynomialControlOptionsDictionary()} - - controls['a']['units'] = 'm' - controls['a']['order'] = 3 - controls['a']['opt'] = True - controls['a']['shape'] = (3, 1) - - ivc = om.IndepVarComp() - p.model.add_subsystem('ivc', ivc, promotes_outputs=['*']) - - ivc.add_output('t_initial', val=0.0, units='s') - ivc.add_output('t_duration', val=10.0, units='s') - - p.model.add_subsystem('time_comp', - subsys=TimeComp(num_nodes=gd.num_nodes, node_ptau=gd.node_ptau, - node_dptau_dstau=gd.node_dptau_dstau, units='s'), - promotes_inputs=['t_initial', 't_duration'], - promotes_outputs=['t', 'dt_dstau']) - - polynomial_control_group = PolynomialControlGroup(grid_data=gd, - polynomial_control_options=controls, - time_units='s') - - p.model.add_subsystem('polynomial_control_group', - subsys=polynomial_control_group, - promotes_inputs=['*'], - promotes_outputs=['*']) - - p.setup(force_alloc_complex=True) - - p['t_initial'] = 0.0 - p['t_duration'] = 3.0 - - p.run_model() - - control_nodes_ptau, _ = lgl(controls['a']['order'] + 1) - - t_control_input = p['t_initial'] + 0.5 * (control_nodes_ptau + 1) * p['t_duration'] - t_all = p['t'] - - p['polynomial_controls:a'][:, 0, 0] = f_a(t_control_input) - p['polynomial_controls:a'][:, 1, 0] = f_b(t_control_input) - p['polynomial_controls:a'][:, 2, 0] = f_c(t_control_input) - - p.run_model() - - a0_value_expected = f_a(t_all) - a1_value_expected = f_b(t_all) - a2_value_expected = f_c(t_all) - - a0_rate_expected = f1_a(t_all) - a1_rate_expected = f1_b(t_all) - a2_rate_expected = f1_c(t_all) - - a0_rate2_expected = f2_a(t_all) - a1_rate2_expected = f2_b(t_all) - a2_rate2_expected = f2_c(t_all) - - assert_almost_equal(p['polynomial_control_values:a'][:, 0, 0], - a0_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 1, 0], - a1_value_expected) - - assert_almost_equal(p['polynomial_control_values:a'][:, 2, 0], - a2_value_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 0, 0], - a0_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 1, 0], - a1_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate'][:, 2, 0], - a2_rate_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 0, 0], - a0_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 1, 0], - a1_rate2_expected) - - assert_almost_equal(p['polynomial_control_rates:a_rate2'][:, 2, 0], - a2_rate2_expected) - - np.set_printoptions(linewidth=1024) - cpd = p.check_partials(method='cs', out_stream=None) - - assert_check_partials(cpd) - - -if __name__ == '__main__': # pragma: no cover - unittest.main() diff --git a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py index f9e6dee31..26ca1b5b8 100644 --- a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py @@ -106,8 +106,6 @@ class BarycentricControlInterpComp(om.ExplicitComponent): nodes are layed out. control_options : dict of {str: ControlOptionsDictionary} A mapping that maps the name of each control to a ControlOptionsDictionary of its options. - polynomial_control_options : dict of {str: PolynomialControlOptionsDictionary} - A mapping that maps the name of each polynomial control to an OptionsDictionary of its options. time_units : str The time units pertaining to the control rates. standalone_mode : bool @@ -118,11 +116,10 @@ class BarycentricControlInterpComp(om.ExplicitComponent): **kwargs : dict, optional Keyword arguments passed to ExplicitComponent. """ - def __init__(self, grid_data, control_options=None, polynomial_control_options=None, + def __init__(self, grid_data, control_options=None, time_units=None, standalone_mode=False, compute_derivs=True, **kwargs): self._grid_data = grid_data self._control_options = {} if control_options is None else control_options - self._polynomial_control_options = {} if polynomial_control_options is None else polynomial_control_options self._time_units = time_units self._standalone_mode = standalone_mode self._compute_derivs = compute_derivs diff --git a/dymos/transcriptions/explicit_shooting/explicit_shooting.py b/dymos/transcriptions/explicit_shooting/explicit_shooting.py index adcf9fcee..63600f70a 100644 --- a/dymos/transcriptions/explicit_shooting/explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/explicit_shooting.py @@ -14,8 +14,7 @@ from ...utils.indexing import get_src_indices_by_row from ...utils.introspection import get_promoted_vars, get_source_metadata, get_targets, _get_targets_metadata from ...utils.constants import INF_BOUND -from ..common import TimeComp, TimeseriesOutputGroup, ControlGroup, PolynomialControlGroup, \ - ParameterComp +from ..common import TimeComp, TimeseriesOutputGroup, ControlGroup, ParameterComp class ExplicitShooting(TranscriptionBase): diff --git a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py index 924ee71ed..28231147c 100644 --- a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py +++ b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py @@ -33,8 +33,6 @@ class ODEEvaluationGroup(om.Group): For each parameter, a dictionary of its options, keyed by name. control_options : dict of {str: OptionsDictionary} For each control variable, a dictionary of its options, keyed by name. - polynomial_control_options : dict of {str: OptionsDictionary} - For each polynomial variable, a dictionary of its options, keyed by name. ode_init_kwargs : dict A dictionary of keyword arguments to be passed to the instantiation of the ODE. compute_derivs : bool @@ -50,7 +48,7 @@ class ODEEvaluationGroup(om.Group): """ def __init__(self, ode_class, input_grid_data, time_options, state_options, parameter_options, control_options, - polynomial_control_options, ode_init_kwargs=None, compute_derivs=True, vec_size=1, + ode_init_kwargs=None, compute_derivs=True, vec_size=1, control_interp='vandermonde', **kwargs): super().__init__(**kwargs) @@ -62,7 +60,6 @@ def __init__(self, ode_class, input_grid_data, time_options, state_options, para self._parameter_options = deepcopy(parameter_options) self._time_options = deepcopy(time_options) self._control_options = deepcopy(control_options) - self._polynomial_control_options = deepcopy(polynomial_control_options) self._control_interpolants = {} self._polynomial_control_interpolants = {} @@ -107,16 +104,14 @@ def setup(self): promotes_inputs=[('t', t_name), 't_initial', 't_duration'], promotes_outputs=['stau', 'ptau', 'dstau_dt', ('t_phase', f'{t_name}_phase')]) - if self._control_options or self._polynomial_control_options: + if self._control_options: c_options = self._control_options - pc_options = self._polynomial_control_options # Add control interpolant if self._control_interp == 'barycentric': self._control_comp = self.add_subsystem('control_interp', BarycentricControlInterpComp(grid_data=igd, control_options=c_options, - polynomial_control_options=pc_options, time_units=t_units, compute_derivs=self._compute_derivs), promotes_inputs=['ptau', 'stau', 't_duration', 'dstau_dt']) @@ -124,7 +119,6 @@ def setup(self): self._control_comp = self.add_subsystem('control_interp', VandermondeControlInterpComp(grid_data=igd, control_options=c_options, - polynomial_control_options=pc_options, time_units=t_units, compute_derivs=self._compute_derivs), promotes_inputs=['ptau', 'stau', 't_duration', 'dstau_dt']) @@ -155,11 +149,7 @@ def configure(self): time_units=self._time_options['units']) self._configure_controls() - configure_controls_introspection(self._polynomial_control_options, ode, - time_units=self._time_options['units']) - self._configure_polynomial_controls() - - if self._control_options or self._polynomial_control_options: + if self._control_options: self._get_subsystem('control_interp').configure_io() configure_states_discovery(self._state_options, ode) @@ -319,63 +309,6 @@ def _configure_controls(self): val=np.ones(shape), units=rate2_units) - def _configure_polynomial_controls(self): - configure_controls_introspection(self._polynomial_control_options, self.ode) - - if self._polynomial_control_options: - time_units = self._time_options['units'] - gd = self._input_grid_data - - if gd is None: - raise ValueError('ODEEvaluationGroup was provided with control options but ' - 'a GridData object was not provided.') - - for name, options in self._polynomial_control_options.items(): - shape = options['shape'] - units = options['units'] - rate_units = get_rate_units(units, time_units, deriv=1) - rate2_units = get_rate_units(units, time_units, deriv=2) - targets = options['targets'] - rate_targets = options['rate_targets'] - rate2_targets = options['rate2_targets'] - num_control_input_nodes = options['order'] + 1 - uhat_name = f'polynomial_controls:{name}' - u_name = f'polynomial_control_values:{name}' - u_rate_name = f'polynomial_control_rates:{name}_rate' - u_rate2_name = f'polynomial_control_rates:{name}_rate2' - - self._ivc.add_output(uhat_name, shape=(num_control_input_nodes,) + shape, units=units) - self.add_design_var(uhat_name) - self.add_constraint(u_name) - self.add_constraint(u_rate_name) - self.add_constraint(u_rate2_name) - - self.promotes('control_interp', inputs=[uhat_name], - outputs=[u_name, u_rate_name, u_rate2_name]) - - # Promote targets from the ODE - for tgt in targets: - self.promotes('ode', inputs=[(tgt, u_name)]) - if targets: - self.set_input_defaults(name=u_name, - val=np.ones(shape), - units=options['units']) - # Promote rate targets from the ODE - for tgt in rate_targets: - self.promotes('ode', inputs=[(tgt, u_rate_name)]) - if rate_targets: - self.set_input_defaults(name=u_rate_name, - val=np.ones(shape), - units=rate_units) - - # Promote rate2 targets from the ODE - for tgt in rate2_targets: - self.promotes('ode', inputs=[(tgt, u_rate2_name)]) - if rate2_targets: - self.set_input_defaults(name=u_rate2_name, - val=np.ones(shape), - units=rate2_units) - def _get_rate_source_path(self, state_var): """ Get path of the rate source variable so that we can connect it to the diff --git a/dymos/transcriptions/pseudospectral/birkhoff.py b/dymos/transcriptions/pseudospectral/birkhoff.py index 7ec6a5175..11db73418 100644 --- a/dymos/transcriptions/pseudospectral/birkhoff.py +++ b/dymos/transcriptions/pseudospectral/birkhoff.py @@ -198,37 +198,6 @@ def configure_controls(self, phase): [f'boundary_vals.{t}' for t in options['rate2_targets']], src_indices=om.slicer[[0, -1], ...]) - def configure_polynomial_controls(self, phase): - """ - Configure the inputs/outputs for the polynomial controls. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - super(Birkhoff, self).configure_polynomial_controls(phase) - - for name, options in phase.polynomial_control_options.items(): - if options['targets']: - phase.connect(f'polynomial_control_values:{name}', [f'ode_all.{t}' for t in options['targets']]) - phase.connect(f'polynomial_control_values:{name}', [f'boundary_vals.{t}' for t in options['targets']], - src_indices=om.slicer[[0, -1], ...]) - - if options['rate_targets']: - phase.connect(f'polynomial_control_rates:{name}_rate', - [f'ode_all.{t}' for t in options['rate_targets']]) - phase.connect(f'polynomial_control_rates:{name}_rate', - [f'boundary_vals.{t}' for t in options['rate_targets']], - src_indices=om.slicer[[0, -1], ...]) - - if options['rate2_targets']: - phase.connect(f'polynomial_control_rates:{name}_rate2', - [f'ode_all.{t}' for t in options['rate2_targets']]) - phase.connect(f'polynomial_control_rates:{name}_rate2', - [f'boundary_vals.{t}' for t in options['rate2_targets']], - src_indices=om.slicer[[0, -1], ...]) - def setup_ode(self, phase): """ Setup the ode for this transcription. @@ -567,13 +536,13 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): linear = False constraint_path = f'control_values:{var}' elif var_type == 'indep_polynomial_control': - shape = phase.polynomial_control_options[var]['shape'] - units = phase.polynomial_control_options[var]['units'] + shape = phase.control_options[var]['shape'] + units = phase.control_options[var]['units'] linear = True constraint_path = f'polynomial_control_values:{var}' elif var_type == 'input_polynomial_control': - shape = phase.polynomial_control_options[var]['shape'] - units = phase.polynomial_control_options[var]['units'] + shape = phase.control_options[var]['shape'] + units = phase.control_options[var]['units'] linear = False constraint_path = f'polynomial_control_values:{var}' elif var_type == 'parameter': @@ -592,8 +561,8 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): constraint_path = f'control_rates:{var}' elif var_type in ('polynomial_control_rate', 'polynomial_control_rate2'): control_var = var[:-5] - shape = phase.polynomial_control_options[control_var]['shape'] - control_units = phase.polynomial_control_options[control_var]['units'] + shape = phase.control_options[control_var]['shape'] + control_units = phase.control_options[control_var]['units'] d = 2 if var_type == 'polynomial_control_rate2' else 1 control_rate_units = get_rate_units(control_units, time_units, deriv=d) units = control_rate_units @@ -775,18 +744,18 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: path = f'polynomial_control_values:{var}' - src_units = phase.polynomial_control_options[var]['units'] - src_shape = phase.polynomial_control_options[var]['shape'] + src_units = phase.control_options[var]['units'] + src_shape = phase.control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] path = f'polynomial_control_rates:{control_name}_rate' - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] path = f'polynomial_control_rates:{control_name}_rate2' - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] elif var_type == 'parameter': diff --git a/dymos/transcriptions/pseudospectral/gauss_lobatto.py b/dymos/transcriptions/pseudospectral/gauss_lobatto.py index 483a7af7d..cc69e4489 100644 --- a/dymos/transcriptions/pseudospectral/gauss_lobatto.py +++ b/dymos/transcriptions/pseudospectral/gauss_lobatto.py @@ -184,60 +184,6 @@ def configure_controls(self, phase): [f'rhs_col.{t}' for t in options['rate2_targets']], src_indices=col_src_idxs, flat_src_indices=True) - def configure_polynomial_controls(self, phase): - """ - Configure the inputs/outputs for the polynomial controls. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - super(GaussLobatto, self).configure_polynomial_controls(phase) - ode_inputs = get_promoted_vars(self._get_ode(phase), 'input') - grid_data = self.grid_data - - for name, options in phase.polynomial_control_options.items(): - disc_idxs = grid_data.subset_node_indices['state_disc'] - col_idxs = grid_data.subset_node_indices['col'] - - disc_src_idxs = get_src_indices_by_row(disc_idxs, options['shape']) - col_src_idxs = get_src_indices_by_row(col_idxs, options['shape']) - - if options['shape'] == (1,): - disc_src_idxs = disc_src_idxs.ravel() - col_src_idxs = col_src_idxs.ravel() - - # enclose indices in tuple to ensure shaping of indices works - disc_src_idxs = (disc_src_idxs,) - col_src_idxs = (col_src_idxs,) - - if options['targets']: - phase.connect(f'polynomial_control_values:{name}', - [f'rhs_disc.{t}' for t in options['targets']], - src_indices=disc_src_idxs, flat_src_indices=True) - phase.connect(f'polynomial_control_values:{name}', - [f'rhs_col.{t}' for t in options['targets']], - src_indices=col_src_idxs, flat_src_indices=True) - - if options['rate_targets']: - phase.connect(f'polynomial_control_rates:{name}_rate', - [f'rhs_disc.{t}' for t in options['rate_targets']], - src_indices=disc_src_idxs, flat_src_indices=True) - - phase.connect(f'polynomial_control_rates:{name}_rate', - [f'rhs_col.{t}' for t in options['rate_targets']], - src_indices=col_src_idxs, flat_src_indices=True) - - if options['rate2_targets']: - phase.connect(f'polynomial_control_rates:{name}_rate2', - [f'rhs_disc.{t}' for t in options['rate2_targets']], - src_indices=disc_src_idxs, flat_src_indices=True) - - phase.connect(f'polynomial_control_rates:{name}_rate2', - [f'rhs_col.{t}' for t in options['rate2_targets']], - src_indices=col_src_idxs, flat_src_indices=True) - def setup_ode(self, phase): """ Setup the ode for this transcription. @@ -576,28 +522,18 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: path = f'polynomial_control_values:{var}' - if var in phase.control_options: - src_units = phase.control_options[var]['units'] - src_shape = phase.control_options[var]['shape'] - else: - src_units = phase.polynomial_control_options[var]['units'] - src_shape = phase.polynomial_control_options[var]['shape'] + src_units = phase.control_options[var]['units'] + src_shape = phase.control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] path = f'polynomial_control_rates:{control_name}_rate' - if control_name in phase.control_options: - control = phase.control_options[control_name] - else: - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] path = f'polynomial_control_rates:{control_name}_rate2' - if control_name in phase.control_options: - control = phase.control_options[control_name] - else: - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] elif var_type == 'parameter': diff --git a/dymos/transcriptions/pseudospectral/radau_pseudospectral.py b/dymos/transcriptions/pseudospectral/radau_pseudospectral.py index 21321eede..d9e28312f 100644 --- a/dymos/transcriptions/pseudospectral/radau_pseudospectral.py +++ b/dymos/transcriptions/pseudospectral/radau_pseudospectral.py @@ -113,37 +113,6 @@ def configure_controls(self, phase): phase.connect(f'control_rates:{name}_rate2', [f'rhs_all.{t}' for t in options['rate2_targets']]) - def configure_polynomial_controls(self, phase): - """ - Configure the inputs/outputs for the polynomial controls. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - super(Radau, self).configure_polynomial_controls(phase) - - ode_inputs = get_promoted_vars(self._get_ode(phase), 'input') - - for name, options in phase.polynomial_control_options.items(): - targets = get_targets(ode=ode_inputs, name=name, user_targets=options['targets']) - if targets: - phase.connect(f'polynomial_control_values:{name}', - [f'rhs_all.{t}' for t in targets]) - - targets = get_targets(ode=phase.rhs_all, name=f'{name}_rate', - user_targets=options['rate_targets']) - if targets: - phase.connect(f'polynomial_control_rates:{name}_rate', - [f'rhs_all.{t}' for t in targets]) - - targets = get_targets(ode=phase.rhs_all, name=f'{name}_rate2', - user_targets=options['rate2_targets']) - if targets: - phase.connect(f'polynomial_control_rates:{name}_rate2', - [f'rhs_all.{t}' for t in targets]) - def setup_ode(self, phase): """ Setup the ode for this transcription. @@ -405,18 +374,18 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: path = f'polynomial_control_values:{var}' - src_units = phase.polynomial_control_options[var]['units'] - src_shape = phase.polynomial_control_options[var]['shape'] + src_units = phase.control_options[var]['units'] + src_shape = phase.control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] path = f'polynomial_control_rates:{control_name}_rate' - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] path = f'polynomial_control_rates:{control_name}_rate2' - control = phase.polynomial_control_options[control_name] + control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] elif var_type == 'parameter': diff --git a/dymos/transcriptions/solve_ivp/components/__init__.py b/dymos/transcriptions/solve_ivp/components/__init__.py index 55433a800..19cc475a8 100644 --- a/dymos/transcriptions/solve_ivp/components/__init__.py +++ b/dymos/transcriptions/solve_ivp/components/__init__.py @@ -1,9 +1,7 @@ from .segment_state_mux_comp import SegmentStateMuxComp from .segment_simulation_comp import SegmentSimulationComp -from .solve_ivp_polynomial_control_group import SolveIVPPolynomialControlGroup from .ode_integration_interface import ODEIntegrationInterface from .odeint_control_interpolation_comp import ODEIntControlInterpolationComp from .solve_ivp_control_group import SolveIVPControlGroup -from .solve_ivp_polynomial_control_group import SolveIVPPolynomialControlGroup from .solve_ivp_timeseries_comp import SolveIVPTimeseriesOutputComp from .state_rate_collector_comp import StateRateCollectorComp diff --git a/dymos/transcriptions/solve_ivp/components/solve_ivp_polynomial_control_group.py b/dymos/transcriptions/solve_ivp/components/solve_ivp_polynomial_control_group.py deleted file mode 100644 index fc2311cc8..000000000 --- a/dymos/transcriptions/solve_ivp/components/solve_ivp_polynomial_control_group.py +++ /dev/null @@ -1,293 +0,0 @@ -import numpy as np -import openmdao.api as om - -from ...grid_data import GridData -from ....utils.lgl import lgl -from ....utils.lagrange import lagrange_matrices -from ....utils.misc import get_rate_units - - -class SolveIVPLGLPolynomialControlComp(om.ExplicitComponent): - """ - Component which interpolates controls as a single polynomial across the entire phase. - - Parameters - ---------- - **kwargs : dict - Dictionary of optional arguments. - """ - def initialize(self): - """ - Declare component options. - """ - self.options.declare('time_units', default=None, allow_none=True, types=str, - desc='Units of time') - self.options.declare('grid_data', types=GridData, desc='Container object for grid info') - self.options.declare('polynomial_control_options', types=dict, - desc='Dictionary of options for the polynomial controls') - self.options.declare('output_nodes_per_seg', default=None, types=(int,), allow_none=True, - desc='If None, results are provided at the all nodes within each' - 'segment. If an int (n) then results are provided at n ' - 'equally distributed points in time within each segment.') - - self._matrices = {} - - def configure_io(self): - """ - I/O creation is delayed until configure so we can determine variable shape and units. - """ - output_nodes_per_seg = self.options['output_nodes_per_seg'] - gd = self.options['grid_data'] - num_seg = gd.num_segments - num_nodes = gd.subset_num_nodes['all'] - all_nodes_ptau = gd.node_ptau - - if output_nodes_per_seg is None: - output_nodes_ptau = all_nodes_ptau - else: - output_nodes_ptau = np.empty(0, dtype=float) - for iseg in range(num_seg): - i1, i2 = gd.subset_segment_indices['all'][iseg, :] - ptau1 = all_nodes_ptau[i1] - ptau2 = all_nodes_ptau[i2-1] - output_nodes_ptau = np.concatenate((output_nodes_ptau, - np.linspace(ptau1, ptau2, output_nodes_per_seg))) - - num_output_nodes = len(output_nodes_ptau) - - self._input_names = {} - self._output_val_names = {} - self._output_rate_names = {} - self._output_rate2_names = {} - self.val_jacs = {} - self.rate_jacs = {} - self.rate2_jacs = {} - self.val_jac_rows = {} - self.val_jac_cols = {} - self.rate_jac_rows = {} - self.rate_jac_cols = {} - self.rate2_jac_rows = {} - self.rate2_jac_cols = {} - self.sizes = {} - - self.add_input('t_duration', val=1.0, units=self.options['time_units'], - desc='duration of the phase to which this interpolated control group ' - 'belongs') - - for name, options in self.options['polynomial_control_options'].items(): - disc_nodes, _ = lgl(options['order'] + 1) - num_control_input_nodes = len(disc_nodes) - shape = options['shape'] - size = np.prod(shape) - units = options['units'] - rate_units = get_rate_units(units, self.options['time_units'], deriv=1) - rate2_units = get_rate_units(units, self.options['time_units'], deriv=2) - - input_shape = (num_control_input_nodes,) + shape - output_shape = (num_output_nodes,) + shape - - L_do, D_do = lagrange_matrices(disc_nodes, output_nodes_ptau) - _, D_dd = lagrange_matrices(disc_nodes, disc_nodes) - D2_do = np.dot(D_do, D_dd) - - self._matrices[name] = L_do, D_do, D2_do - - self._input_names[name] = f'polynomial_controls:{name}' - self._output_val_names[name] = f'polynomial_control_values:{name}' - self._output_rate_names[name] = f'polynomial_control_rates:{name}_rate' - self._output_rate2_names[name] = f'polynomial_control_rates:{name}_rate2' - - self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) - self.add_output(self._output_val_names[name], shape=output_shape, units=units) - self.add_output(self._output_rate_names[name], shape=output_shape, units=rate_units) - self.add_output(self._output_rate2_names[name], shape=output_shape, units=rate2_units) - - self.val_jacs[name] = np.zeros((num_output_nodes, size, num_control_input_nodes, size)) - self.rate_jacs[name] = np.zeros((num_output_nodes, size, num_control_input_nodes, size)) - self.rate2_jacs[name] = np.zeros((num_output_nodes, size, num_control_input_nodes, size)) - - for i in range(size): - self.val_jacs[name][:, i, :, i] = L_do - self.rate_jacs[name][:, i, :, i] = D_do - self.rate2_jacs[name][:, i, :, i] = D2_do - - self.val_jacs[name] = self.val_jacs[name].reshape((num_output_nodes * size, - num_control_input_nodes * size), - order='C') - self.rate_jacs[name] = self.rate_jacs[name].reshape((num_output_nodes * size, - num_control_input_nodes * size), - order='C') - self.rate2_jacs[name] = self.rate2_jacs[name].reshape((num_output_nodes * size, - num_control_input_nodes * size), - order='C') - self.val_jac_rows[name], self.val_jac_cols[name] = \ - np.where(self.val_jacs[name] != 0) - self.rate_jac_rows[name], self.rate_jac_cols[name] = \ - np.where(self.rate_jacs[name] != 0) - self.rate2_jac_rows[name], self.rate2_jac_cols[name] = \ - np.where(self.rate2_jacs[name] != 0) - - self.sizes[name] = size - - rs, cs = self.val_jac_rows[name], self.val_jac_cols[name] - self.declare_partials(of=self._output_val_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs, val=self.val_jacs[name][rs, cs]) - - rs = np.concatenate([np.arange(i, num_nodes * size + i, size, dtype=int) - for i in range(size)]) - - self.declare_partials(of=self._output_rate_names[name], - wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) - - self.declare_partials(of=self._output_rate_names[name], - wrt=self._input_names[name], - rows=self.rate_jac_rows[name], cols=self.rate_jac_cols[name]) - - self.declare_partials(of=self._output_rate2_names[name], - wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) - - self.declare_partials(of=self._output_rate2_names[name], - wrt=self._input_names[name], - rows=self.rate2_jac_rows[name], cols=self.rate2_jac_cols[name]) - - def compute(self, inputs, outputs): - """ - Compute component outputs. - - Parameters - ---------- - inputs : `Vector` - `Vector` containing inputs. - outputs : `Vector` - `Vector` containing outputs. - """ - dt_dptau = 0.5 * inputs['t_duration'] - - for name in self.options['polynomial_control_options']: - L_do, D_do, D2_do = self._matrices[name] - - u = inputs[self._input_names[name]] - - a = np.tensordot(D_do, u, axes=(1, 0)).T - b = np.tensordot(D2_do, u, axes=(1, 0)).T - - # divide each "row" of the rates by dt_dptau or dt_dptau**2 - outputs[self._output_val_names[name]] = np.tensordot(L_do, u, axes=(1, 0)) - outputs[self._output_rate_names[name]] = (a / dt_dptau).T - outputs[self._output_rate2_names[name]] = (b / dt_dptau ** 2).T - - def compute_partials(self, inputs, partials): - """ - Compute sub-jacobian parts. The model is assumed to be in an unscaled state. - - Parameters - ---------- - inputs : Vector - Unscaled, dimensional input variables read via inputs[key]. - partials : Jacobian - Subjac components written to partials[output_name, input_name]. - """ - nn = self.options['grid_data'].num_nodes - - for name, options in self.options['polynomial_control_options'].items(): - control_name = self._input_names[name] - num_input_nodes = options['order'] + 1 - L_do, D_do, D2_do = self._matrices[name] - - size = self.sizes[name] - rate_name = self._output_rate_names[name] - rate2_name = self._output_rate2_names[name] - - # Unroll matrix-shaped controls into an array at each node - u_d = np.reshape(inputs[control_name], (num_input_nodes, size)) - - t_duration = inputs['t_duration'] - t_duration_tile = np.tile(t_duration, size * nn) - - partials[rate_name, 't_duration'] = \ - 0.5 * (-np.dot(D_do, u_d).ravel(order='F') / (0.5 * t_duration_tile) ** 2) - - partials[rate2_name, 't_duration'] = \ - -1.0 * (np.dot(D2_do, u_d).ravel(order='F') / (0.5 * t_duration_tile) ** 3) - - t_duration_x_size = np.repeat(t_duration, size * nn)[:, np.newaxis] - - r_nz, c_nz = self.rate_jac_rows[name], self.rate_jac_cols[name] - partials[rate_name, control_name] = \ - (self.rate_jacs[name] / (0.5 * t_duration_x_size))[r_nz, c_nz] - - r_nz, c_nz = self.rate2_jac_rows[name], self.rate2_jac_cols[name] - partials[rate2_name, control_name] = \ - (self.rate2_jacs[name] / (0.5 * t_duration_x_size) ** 2)[r_nz, c_nz] - - -class SolveIVPPolynomialControlGroup(om.Group): - """ - Group containing the SolveIVPLGLPolynomialControlComp. - - Parameters - ---------- - **kwargs : dict - Dictionary of optional arguments. - """ - def initialize(self): - """ - Declare group options. - """ - self.options.declare('polynomial_control_options', types=dict, - desc='Dictionary of options for the polynomial controls') - self.options.declare('time_units', default=None, allow_none=True, types=str, - desc='Units of time') - self.options.declare('grid_data', types=GridData, desc='Container object for grid info') - self.options.declare('output_nodes_per_seg', default=None, types=(int,), allow_none=True, - desc='If None, results are provided at the all nodes within each' - 'segment. If an int (n) then results are provided at n ' - 'equally distributed points in time within each segment.') - - def setup(self): - """ - Build the group hierarchy. - """ - ivc = om.IndepVarComp() - - opts = self.options - pcos = self.options['polynomial_control_options'] - output_nodes_per_seg = self.options['output_nodes_per_seg'] - - # Pull out the interpolated controls - num_opt = 0 - for options in opts['polynomial_control_options'].values(): - if options['order'] < 1: - raise ValueError('Interpolation order must be >= 1 (linear)') - if options['opt']: - num_opt += 1 - - if num_opt > 0: - ivc = self.add_subsystem('control_inputs', subsys=ivc, promotes_outputs=['*']) - - self.add_subsystem( - 'control_comp', - subsys=SolveIVPLGLPolynomialControlComp(time_units=opts['time_units'], - grid_data=opts['grid_data'], - polynomial_control_options=pcos, - output_nodes_per_seg=output_nodes_per_seg), - promotes_inputs=['*'], - promotes_outputs=['*']) - - def configure_io(self): - """ - I/O creation is delayed until configure so we can determine variable shape and units. - """ - ivc = self.control_inputs - self.control_comp.configure_io() - - # For any interpolated control with `opt=True`, add an indep var comp output and - # setup the design variable for optimization. - for name, options in self.options['polynomial_control_options'].items(): - num_input_nodes = options['order'] + 1 - shape = options['shape'] - if options['opt']: - ivc.add_output(f'polynomial_controls:{name}', - val=np.ones((num_input_nodes,) + shape), - units=options['units']) diff --git a/dymos/transcriptions/solve_ivp/solve_ivp.py b/dymos/transcriptions/solve_ivp/solve_ivp.py index 04e484ebb..9304b3d92 100644 --- a/dymos/transcriptions/solve_ivp/solve_ivp.py +++ b/dymos/transcriptions/solve_ivp/solve_ivp.py @@ -7,7 +7,7 @@ from ..transcription_base import TranscriptionBase from .components import SegmentSimulationComp, SegmentStateMuxComp, \ - SolveIVPControlGroup, SolveIVPPolynomialControlGroup, SolveIVPTimeseriesOutputComp + SolveIVPControlGroup, SolveIVPTimeseriesOutputComp from ..common import TimeComp, TimeseriesOutputGroup from ...utils.misc import get_rate_units from ...utils.introspection import get_promoted_vars, get_targets, get_source_metadata @@ -345,55 +345,6 @@ def configure_controls(self, phase): if options['rate2_targets']: phase.connect(f'control_rates:{name}_rate2', [f'ode.{t}' for t in options['rate2_targets']]) - def setup_polynomial_controls(self, phase): - """ - Adds the polynomial control group to the model if any polynomial controls are present. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - if phase.polynomial_control_options: - sys = SolveIVPPolynomialControlGroup(grid_data=self.grid_data, - polynomial_control_options=phase.polynomial_control_options, - time_units=phase.time_options['units'], - output_nodes_per_seg=self.options['output_nodes_per_seg']) - phase.add_subsystem('polynomial_control_group', subsys=sys, - promotes_inputs=['*'], promotes_outputs=['*']) - - def configure_polynomial_controls(self, phase): - """ - Configure the inputs/outputs for the polynomial controls. - - Parameters - ---------- - phase : dymos.Phase - The phase object to which this transcription instance applies. - """ - # In transcription_base, we get the control units/shape from the target, and then call - # configure on the control_group. - super(SolveIVP, self).configure_polynomial_controls(phase) - - # Additional connections. - for name, options in phase.polynomial_control_options.items(): - targets = options['targets'] - - for iseg in range(self.grid_data.num_segments): - phase.connect(src_name=f'polynomial_controls:{name}', - tgt_name=f'segment_{iseg}.polynomial_controls:{name}') - - if options['targets']: - phase.connect(f'polynomial_control_values:{name}', [f'ode.{t}' for t in targets]) - - if options['rate_targets']: - phase.connect(f'polynomial_control_rates:{name}_rate', - [f'ode.{t}' for t in targets]) - - if options['rate2_targets']: - phase.connect(f'polynomial_control_rates:{name}_rate2', - [f'ode.{t}' for t in targets]) - def configure_parameters(self, phase): """ Configure parameter promotion. diff --git a/dymos/transcriptions/transcription_base.py b/dymos/transcriptions/transcription_base.py index f85043ed4..b76844f6c 100644 --- a/dymos/transcriptions/transcription_base.py +++ b/dymos/transcriptions/transcription_base.py @@ -4,7 +4,7 @@ import openmdao.api as om -from .common import ControlGroup, PolynomialControlGroup, ParameterComp +from .common import ControlGroup, ParameterComp from ..utils.constants import INF_BOUND from ..utils.indexing import get_constraint_flat_idxs from ..utils.misc import _none_or_unspecified From ebef4ce30090feb3c99418cfa06e8a92eec72397 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Wed, 8 May 2024 15:52:29 -0500 Subject: [PATCH 04/17] WIP: renaming poly controls throughout --- ...doc_brachistochrone_polynomial_controls.py | 39 ++++---- dymos/grid_refinement/error_estimation.py | 15 --- .../grid_refinement_ode_system.py | 36 ------- dymos/phase/phase.py | 10 +- dymos/trajectory/trajectory.py | 12 +-- dymos/transcriptions/common/control_group.py | 12 +-- .../barycentric_control_interp_comp.py | 8 +- .../explicit_shooting/explicit_shooting.py | 48 +-------- .../explicit_shooting/ode_evaluation_group.py | 6 +- .../explicit_shooting/ode_integration_comp.py | 4 +- .../vandermonde_control_interp_comp.py | 8 +- .../transcriptions/pseudospectral/birkhoff.py | 20 ++-- .../components/birkhoff_iter_group.py | 8 +- .../pseudospectral/gauss_lobatto.py | 31 +++--- .../pseudospectral/pseudospectral_base.py | 30 +----- .../pseudospectral/radau_pseudospectral.py | 14 +-- .../ode_integration_interface_system.py | 22 +---- .../odeint_control_interpolation_comp.py | 12 +-- .../components/segment_simulation_comp.py | 4 +- dymos/transcriptions/solve_ivp/solve_ivp.py | 17 ---- dymos/utils/introspection.py | 99 ++----------------- dymos/visualization/linkage/report.py | 2 +- 22 files changed, 109 insertions(+), 348 deletions(-) diff --git a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py index dcd27278f..df85d1961 100644 --- a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py +++ b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py @@ -55,7 +55,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -142,7 +142,7 @@ def test_brachistochrone_polynomial_control_radau(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -236,7 +236,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -329,7 +329,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -418,7 +418,7 @@ def test_brachistochrone_polynomial_control_radau(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -515,7 +515,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [1, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [1, 100])) # Solve for the optimal trajectory p.run_driver() @@ -606,7 +606,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -693,7 +693,7 @@ def test_brachistochrone_polynomial_control_radau(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -789,7 +789,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -882,7 +882,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -971,7 +971,7 @@ def test_brachistochrone_polynomial_control_radau(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -1067,7 +1067,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -1160,7 +1160,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # p.final_setup() @@ -1254,7 +1254,7 @@ def test_brachistochrone_polynomial_control_radau(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -1350,7 +1350,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -1436,7 +1436,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -1493,7 +1493,7 @@ def test_brachistochrone_polynomial_control_radau(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -1562,7 +1562,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): p.set_val('phase0.states:x', phase.interp('x', [0, 10])) p.set_val('phase0.states:y', phase.interp('y', [10, 5])) p.set_val('phase0.states:v', phase.interp('v', [0, 9.9])) - p.set_val('phase0.polynomial_controls:theta', phase.interp('theta', [5, 100])) + p.set_val('phase0.controls:theta', phase.interp('theta', [5, 100])) # Solve for the optimal trajectory p.run_driver() @@ -1581,4 +1581,5 @@ def test_brachistochrone_polynomial_control_birkhoff(self): if __name__ == '__main__': # pragma: no cover - unittest.main() \ No newline at end of file + z = TestBrachistochronePolynomialControlRate2PathConstrained() + z.test_brachistochrone_polynomial_control_gauss_lobatto() \ No newline at end of file diff --git a/dymos/grid_refinement/error_estimation.py b/dymos/grid_refinement/error_estimation.py index 82e44cac4..abb8644e5 100644 --- a/dymos/grid_refinement/error_estimation.py +++ b/dymos/grid_refinement/error_estimation.py @@ -105,8 +105,6 @@ def eval_ode_on_grid(phase, transcription): A dictionary of the state values from the phase interpolated to the new transcription. dict of (str: ndarray) A dictionary of the control values from the phase interpolated to the new transcription. - dict of (str: ndarray) - A dictionary of the polynomial control values from the phase interpolated to the new transcription. dict of (str: ndarray) A dictionary of the state rates computed in the phase's ODE at the new transcription points. """ @@ -234,19 +232,6 @@ def eval_ode_on_grid(phase, transcription): u_units = phase.control_options[u_name]['units'] src_units = get_rate_units(u_units, phase.time_options['units'], deriv=2) f[name] = om.convert_units(u_rate2[rate_source], src_units, rate_units) - elif rate_source_class in {'input_polynomial_control', 'indep_polynomial_control'}: - src_units = phase.control_options[rate_source]['units'] - f[name] = om.convert_units(p[rate_source], src_units, rate_units) - elif rate_source_class in {'polynomial_control_rate'}: - pc_name = rate_source[:-5] - pc_units = phase.control_options[pc_name]['units'] - src_units = get_rate_units(pc_units, phase.time_options['units'], deriv=1) - f[name] = om.convert_units(p_rate[rate_source], src_units, rate_units) - elif rate_source_class in {'polynomial_control_rate2'}: - pc_name = rate_source[:-6] - pc_units = phase.control_options[pc_name]['units'] - src_units = get_rate_units(pc_units, phase.time_options['units'], deriv=2) - f[name] = om.convert_units(p_rate2[rate_source], src_units, rate_units) elif rate_source_class in {'parameter'}: src_units = phase.parameter_options[rate_source]['units'] shape = phase.parameter_options[rate_source]['shape'] diff --git a/dymos/grid_refinement/grid_refinement_ode_system.py b/dymos/grid_refinement/grid_refinement_ode_system.py index fd86ec729..cd9000cea 100644 --- a/dymos/grid_refinement/grid_refinement_ode_system.py +++ b/dymos/grid_refinement/grid_refinement_ode_system.py @@ -46,10 +46,6 @@ def initialize(self): self.options.declare('controls', default=None, types=dict, allow_none=True, desc='Dictionary of control names/options for the segments parent Phase.') - self.options.declare('polynomial_controls', default=None, types=dict, allow_none=True, - desc='Dictionary of polynomial control names/options for the segments ' - 'parent Phase.') - self.options.declare('parameters', default=None, types=dict, allow_none=True, desc='Dictionary of parameter names/options for the segments ' 'parent Phase.') @@ -155,38 +151,6 @@ def configure(self): val=np.ones(num_nodes), units=rate2_units) - # Configure the polynomial controls - for name, options in self.options['polynomial_controls'].items(): - rate_units = get_rate_units(units=options['units'], - time_units=self.options['time']['units']) - rate2_units = get_rate_units(units=options['units'], - time_units=self.options['time']['units'], - deriv=2) - - targets = get_targets(self.ode, name, options['targets']) - if targets: - for tgt in targets: - self.promotes('ode', inputs=[(tgt, f'polynomial_controls:{name}')]) - self.set_input_defaults(name=f'polynomial_controls:{name}', - val=np.ones(num_nodes), - units=options['units']) - - targets = get_targets(self.ode, f'{name}_rate', options['rate_targets']) - if targets: - for tgt in targets: - self.promotes('ode', inputs=[(tgt, f'polynomial_control_rates:{name}_rate')]) - self.set_input_defaults(name=f'polynomial_control_rates:{name}_rate', - val=np.ones(num_nodes), - units=rate_units) - - targets = get_targets(self.ode, f'{name}_rate2', options['rate2_targets']) - if targets: - for tgt in targets: - self.promotes('ode', inputs=[(tgt, f'polynomial_control_rates:{name}_rate2')]) - self.set_input_defaults(name=f'polynomial_control_rates:{name}_rate2', - val=np.ones(num_nodes), - units=rate2_units) - # Configure the parameters for name, options in self.options['parameters'].items(): static_targets = options['static_targets'] diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index e6f87053b..3b51d6d08 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -1212,7 +1212,7 @@ def add_boundary_constraint(self, name, loc, constraint_name=None, units=None, if var_type.startswith('control_rate'): bc['constraint_name'] = f'control_rates:{constraint_name}' elif var_type.startswith('polynomial_control_rate'): - bc['constraint_name'] = f'polynomial_control_rates:{constraint_name}' + bc['constraint_name'] = f'control_rates:{constraint_name}' if constraint_name not in self._timeseries['timeseries']['outputs']: self.add_timeseries_output(name, output_name=bc['constraint_name'], units=units, shape=shape) @@ -1323,8 +1323,6 @@ def add_path_constraint(self, name, constraint_name=None, units=None, shape=None if self.timeseries_options['use_prefix']: if var_type.startswith('control_rate'): pc['constraint_name'] = f'control_rates:{constraint_name}' - elif var_type.startswith('polynomial_control_rate'): - pc['constraint_name'] = f'polynomial_control_rates:{constraint_name}' if constraint_name not in self._timeseries['timeseries']['outputs']: self.add_timeseries_output(name, output_name=pc['constraint_name'], units=units, shape=shape) @@ -1827,9 +1825,7 @@ def classify_var(self, var): str The classification of the given variable, which is one of 't', 't_phase', 'state', 'control', 'control_rate', - 'control_rate2', 'polynomial_control', - 'polynomial_control_rate', 'polynomial_control_rate2', 'parameter', - or 'ode'. + 'control_rate2', 'parameter', or 'ode'. """ return classify_var(var, time_options=self.time_options, state_options=self.state_options, parameter_options=self.parameter_options, @@ -2670,8 +2666,6 @@ def _is_fixed(self, var_name, var_type, loc): return self.is_state_fixed(var_name, loc) elif var_type in {'input_control', 'indep_control'}: return self.is_control_fixed(var_name, loc) - elif var_type in {'input_polynomial_control', 'indep_polynomial_control'}: - return self.is_polynomial_control_fixed(var_name, loc) elif var_type in {'control_rate', 'control_rate2'}: return self.is_control_rate_fixed(var_name, loc) elif var_type == 'parameter': diff --git a/dymos/trajectory/trajectory.py b/dymos/trajectory/trajectory.py index 1e9bd07a9..323bdaea5 100644 --- a/dymos/trajectory/trajectory.py +++ b/dymos/trajectory/trajectory.py @@ -615,12 +615,12 @@ def _update_linkage_options_configure(self, linkage_options): units[i] = get_rate_units(units[i], phases[i].time_options['units'], deriv=deriv) shapes[i] = phases[i].control_options[control_name]['shape'] elif classes[i] in {'indep_polynomial_control', 'input_polynomial_control'}: - prefix = 'polynomial_controls:' if use_prefix[i] else '' + prefix = 'controls:' if use_prefix[i] else '' sources[i] = f'timeseries.{prefix}{vars[i]}' units[i] = phases[i].control_options[vars[i]]['units'] shapes[i] = phases[i].control_options[vars[i]]['shape'] elif classes[i] in {'polynomial_control_rate', 'polynomial_control_rate2'}: - prefix = 'polynomial_control_rates:' if use_prefix[i] else '' + prefix = 'control_rates:' if use_prefix[i] else '' sources[i] = f'timeseries.{prefix}{vars[i]}' control_name = vars[i][:-5] if classes[i] == 'polynomial_control_rate' else vars[i][:-6] control_units = phases[i].control_options[control_name]['units'] @@ -779,10 +779,10 @@ def _print_on_rank(rank=0, *args, **kwargs): 'indep_control': 'controls:', 'control_rate': 'control_rates:', 'control_rate2': 'control_rates:', - 'input_polynomial_control': 'polynomial_controls:', - 'indep_polynomial_control': 'polynomial_controls:', - 'polynomial_control_rate': 'polynomial_control_rates:', - 'polynomial_control_rate2': 'polynomial_control_rates:', + 'input_polynomial_control': 'controls:', + 'indep_polynomial_control': 'controls:', + 'polynomial_control_rate': 'control_rates:', + 'polynomial_control_rate2': 'control_rates:', 'ode': '' } diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index 18577ec47..a8202cf3e 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -112,10 +112,10 @@ def _configure_controls(self): self._matrices[name] = L_de, D_de, D2_de - self._input_names[name] = f'polynomial_controls:{name}' - self._output_val_names[name] = f'polynomial_control_values:{name}' - self._output_rate_names[name] = f'polynomial_control_rates:{name}_rate' - self._output_rate2_names[name] = f'polynomial_control_rates:{name}_rate2' + self._input_names[name] = f'controls:{name}' + self._output_val_names[name] = f'control_values:{name}' + self._output_rate_names[name] = f'control_rates:{name}_rate' + self._output_rate2_names[name] = f'control_rates:{name}_rate2' self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) self.add_output(self._output_val_names[name], shape=output_shape, units=units) @@ -501,7 +501,7 @@ def configure_io(self): lb = -INF_BOUND if options['lower'] is None else options['lower'] ub = INF_BOUND if options['upper'] is None else options['upper'] - self.add_design_var(f'polynomial_controls:{name}', + self.add_design_var(f'controls:{name}', lower=lb, upper=ub, ref=options['ref'], @@ -511,7 +511,7 @@ def configure_io(self): indices=desvar_indices, flat_indices=True) - self.set_input_defaults(name=f'polynomial_controls:{name}', val=default_val, units=options['units']) + self.set_input_defaults(name=f'controls:{name}', val=default_val, units=options['units']) else: dvname = f'controls:{name}' shape = options['shape'] diff --git a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py index 26ca1b5b8..f562da243 100644 --- a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py @@ -266,10 +266,10 @@ def _configure_polynomial_controls(self): shape = options['shape'] units = options['units'] - input_name = f'polynomial_controls:{pc_name}' - output_name = f'polynomial_control_values:{pc_name}' - rate_name = f'polynomial_control_rates:{pc_name}_rate' - rate2_name = f'polynomial_control_rates:{pc_name}_rate2' + input_name = f'controls:{pc_name}' + output_name = f'control_values:{pc_name}' + rate_name = f'control_rates:{pc_name}_rate' + rate2_name = f'control_rates:{pc_name}_rate2' rate_units = get_rate_units(units, self._time_units) rate2_units = get_rate_units(units, self._time_units, deriv=2) input_shape = (order + 1,) + shape diff --git a/dymos/transcriptions/explicit_shooting/explicit_shooting.py b/dymos/transcriptions/explicit_shooting/explicit_shooting.py index 63600f70a..466280f9d 100644 --- a/dymos/transcriptions/explicit_shooting/explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/explicit_shooting.py @@ -32,7 +32,7 @@ class ExplicitShooting(TranscriptionBase): then the inputs are the initial states ($\bar{x}$), the initial time and duration ($t_0$ and $t_d$), and some set of parameters that impact the ODE ($\theta$). For Dymos, $\theta$ may include the phase parameters, or the node values - that govern the shape of the controls and polynomial controls. + that govern the shape of the controls. Parameters ---------- @@ -421,27 +421,22 @@ def configure_controls(self, phase): # Control targets are detected automatically targets = get_targets(ode_inputs, control_name, options['targets']) - if options['control_type'] == 'polynomial': - control_str = 'polynomial_control' - else: - control_str = 'control' - if targets: - phase.connect(f'{control_str}_values:{control_name}', + phase.connect(f'control_values:{control_name}', [f'ode.{t}' for t in targets]) # Rate targets rate_targets = get_targets(ode_inputs, control_name, options['rate_targets']) if rate_targets: - phase.connect(f'{control_str}_rates:{control_name}_rate', + phase.connect(f'control_rates:{control_name}_rate', [f'ode.{t}' for t in rate_targets]) # Second time derivative targets must be specified explicitly rate2_targets = get_targets(ode_inputs, control_name, options['rate2_targets']) if rate2_targets: - phase.connect(f'{control_str}_rates:{control_name}_rate2', + phase.connect(f'control_rates:{control_name}_rate2', [f'ode.{t}' for t in targets]) def configure_parameters(self, phase): @@ -692,16 +687,6 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): units = phase.control_options[var]['units'] linear = False obj_path = f'control_values:{var}' - elif var_type == 'indep_polynomial_control': - shape = phase.control_options[var]['shape'] - units = phase.control_options[var]['units'] - linear = True - obj_path = f'polynomial_control_values:{var}' - elif var_type == 'input_polynomial_control': - shape = phase.control_options[var]['shape'] - units = phase.control_options[var]['units'] - linear = False - obj_path = f'polynomial_control_values:{var}' elif var_type == 'parameter': shape = phase.parameter_options[var]['shape'] units = phase.parameter_options[var]['units'] @@ -716,15 +701,6 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): units = control_rate_units linear = False obj_path = f'control_rates:{var}' - elif var_type in ('polynomial_control_rate', 'polynomial_control_rate2'): - control_var = var[:-5] - shape = phase.control_options[control_var]['shape'] - control_units = phase.control_options[control_var]['units'] - d = 2 if var_type == 'polynomial_control_rate2' else 1 - control_rate_units = get_rate_units(control_units, time_units, deriv=d) - units = control_rate_units - linear = False - obj_path = f'polynomial_control_rates:{var}' elif var_type == 'timeseries_exec_comp_output': shape = (1,) units = None @@ -846,22 +822,6 @@ def _get_timeseries_var_source(self, var, output_name, phase): path = f'control_rates:{control_name}_rate2' src_units = get_rate_units(phase.control_options[control_name]['units'], time_units, deriv=2) src_shape = phase.control_options[control_name]['shape'] - elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: - path = f'polynomial_control_values:{var}' - src_units = phase.control_options[var]['units'] - src_shape = phase.control_options[var]['shape'] - elif var_type == 'polynomial_control_rate': - control_name = var[:-5] - path = f'polynomial_control_rates:{control_name}_rate' - control = phase.control_options[control_name] - src_units = get_rate_units(control['units'], time_units, deriv=1) - src_shape = control['shape'] - elif var_type == 'polynomial_control_rate2': - control_name = var[:-6] - path = f'polynomial_control_rates:{control_name}_rate2' - control = phase.control_options[control_name] - src_units = get_rate_units(control['units'], time_units, deriv=2) - src_shape = control['shape'] elif var_type == 'parameter': path = f'parameter_vals:{var}' node_idxs = np.zeros(self._output_grid_data.subset_num_nodes['all'], dtype=int) diff --git a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py index 28231147c..59a8f7cc7 100644 --- a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py +++ b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py @@ -339,7 +339,7 @@ def _get_rate_source_path(self, state_var): elif self._control_options is not None and var in self._control_options: rate_path = f'control_values:{var}' elif self._polynomial_control_options is not None and var in self._polynomial_control_options: - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' elif self._parameter_options is not None and var in self._parameter_options: rate_path = f'parameters:{var}' elif var.endswith('_rate') and self._control_options is not None and \ @@ -350,10 +350,10 @@ def _get_rate_source_path(self, state_var): rate_path = f'control_rates:{var}' elif var.endswith('_rate') and self._polynomial_control_options is not None and \ var[:-5] in self._polynomial_control_options: - rate_path = f'polynomial_control_rates:{var}' + rate_path = f'control_rates:{var}' elif var.endswith('_rate2') and self._polynomial_control_options is not None and \ var[:-6] in self._polynomial_control_options: - rate_path = f'polynomial_control_rates:{var}' + rate_path = f'control_rates:{var}' else: rate_path = f'ode.{var}' return rate_path diff --git a/dymos/transcriptions/explicit_shooting/ode_integration_comp.py b/dymos/transcriptions/explicit_shooting/ode_integration_comp.py index 216e84014..bc302c8fe 100644 --- a/dymos/transcriptions/explicit_shooting/ode_integration_comp.py +++ b/dymos/transcriptions/explicit_shooting/ode_integration_comp.py @@ -216,7 +216,7 @@ def _configure_states(self): for control_name_wrt in self.polynomial_control_options: self.declare_partials(of=self._state_output_names[state_name], - wrt=f'polynomial_controls:{control_name_wrt}') + wrt=f'controls:{control_name_wrt}') def _setup_parameters(self): if self._standalone_mode: @@ -298,7 +298,7 @@ def _configure_polynomial_controls(self): control_param_shape = (num_input_nodes,) + options['shape'] control_param_size = np.prod(control_param_shape, dtype=int) - self._polynomial_control_input_names[name] = f'polynomial_controls:{name}' + self._polynomial_control_input_names[name] = f'controls:{name}' self._totals_wrt_names.append(self._polynomial_control_input_names[name]) diff --git a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py index 787fabb17..5af2652d0 100644 --- a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py @@ -157,10 +157,10 @@ def _configure_polynomial_controls(self): order = options['order'] shape = options['shape'] units = options['units'] - input_name = f'polynomial_controls:{pc_name}' - output_name = f'polynomial_control_values:{pc_name}' - rate_name = f'polynomial_control_rates:{pc_name}_rate' - rate2_name = f'polynomial_control_rates:{pc_name}_rate2' + input_name = f'controls:{pc_name}' + output_name = f'control_values:{pc_name}' + rate_name = f'control_rates:{pc_name}_rate' + rate2_name = f'control_rates:{pc_name}_rate2' rate_units = get_rate_units(units, self._time_units) rate2_units = get_rate_units(units, self._time_units, deriv=2) input_shape = (order + 1,) + shape diff --git a/dymos/transcriptions/pseudospectral/birkhoff.py b/dymos/transcriptions/pseudospectral/birkhoff.py index 11db73418..0882118d7 100644 --- a/dymos/transcriptions/pseudospectral/birkhoff.py +++ b/dymos/transcriptions/pseudospectral/birkhoff.py @@ -539,12 +539,12 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): shape = phase.control_options[var]['shape'] units = phase.control_options[var]['units'] linear = True - constraint_path = f'polynomial_control_values:{var}' + constraint_path = f'control_values:{var}' elif var_type == 'input_polynomial_control': shape = phase.control_options[var]['shape'] units = phase.control_options[var]['units'] linear = False - constraint_path = f'polynomial_control_values:{var}' + constraint_path = f'control_values:{var}' elif var_type == 'parameter': shape = phase.parameter_options[var]['shape'] units = phase.parameter_options[var]['units'] @@ -567,7 +567,7 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): control_rate_units = get_rate_units(control_units, time_units, deriv=d) units = control_rate_units linear = False - constraint_path = f'polynomial_control_rates:{var}' + constraint_path = f'control_rates:{var}' elif var_type == 'timeseries_exec_comp_output': shape = (1,) units = None @@ -640,15 +640,15 @@ def _get_rate_source_path(self, state_name, phase): control_name = var[:-6] rate_path = f'control_rates:{control_name}_rate2' elif var_type == 'indep_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' elif var_type == 'input_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' elif var_type == 'polynomial_control_rate': control_name = var[:-5] - rate_path = f'polynomial_control_rates:{control_name}_rate' + rate_path = f'control_rates:{control_name}_rate' elif var_type == 'polynomial_control_rate2': control_name = var[:-6] - rate_path = f'polynomial_control_rates:{control_name}_rate2' + rate_path = f'control_rates:{control_name}_rate2' elif var_type == 'parameter': rate_path = f'parameter_vals:{var}' dynamic = not phase.parameter_options[var]['static_target'] @@ -743,18 +743,18 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_units = get_rate_units(phase.control_options[control_name]['units'], time_units, deriv=2) src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: - path = f'polynomial_control_values:{var}' + path = f'control_values:{var}' src_units = phase.control_options[var]['units'] src_shape = phase.control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] - path = f'polynomial_control_rates:{control_name}_rate' + path = f'control_rates:{control_name}_rate' control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] - path = f'polynomial_control_rates:{control_name}_rate2' + path = f'control_rates:{control_name}_rate2' control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] diff --git a/dymos/transcriptions/pseudospectral/components/birkhoff_iter_group.py b/dymos/transcriptions/pseudospectral/components/birkhoff_iter_group.py index 8bbd9d16a..9c1d07a0e 100644 --- a/dymos/transcriptions/pseudospectral/components/birkhoff_iter_group.py +++ b/dymos/transcriptions/pseudospectral/components/birkhoff_iter_group.py @@ -312,18 +312,18 @@ def _get_rate_source_path(self, state_name, nodes, phase): rate_path = f'control_rates:{control_name}_rate2' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'indep_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'input_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'polynomial_control_rate': control_name = var[:-5] - rate_path = f'polynomial_control_rates:{control_name}_rate' + rate_path = f'control_rates:{control_name}_rate' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] - rate_path = f'polynomial_control_rates:{control_name}_rate2' + rate_path = f'control_rates:{control_name}_rate2' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'parameter': rate_path = f'parameter_vals:{var}' diff --git a/dymos/transcriptions/pseudospectral/gauss_lobatto.py b/dymos/transcriptions/pseudospectral/gauss_lobatto.py index cc69e4489..ece89707c 100644 --- a/dymos/transcriptions/pseudospectral/gauss_lobatto.py +++ b/dymos/transcriptions/pseudospectral/gauss_lobatto.py @@ -150,37 +150,32 @@ def configure_controls(self, phase): disc_src_idxs = (disc_src_idxs,) col_src_idxs = (col_src_idxs,) - if options['control_type'] == 'polynomial': - control_str = 'polynomial_control' - else: - control_str = 'control' - if options['targets']: - phase.connect(f'{control_str}_values:{name}', + phase.connect(f'control_values:{name}', [f'rhs_disc.{t}' for t in options['targets']], src_indices=disc_src_idxs, flat_src_indices=True) - phase.connect(f'{control_str}_values:{name}', + phase.connect(f'control_values:{name}', [f'rhs_col.{t}' for t in options['targets']], src_indices=col_src_idxs, flat_src_indices=True) # Rate targets if options['rate_targets']: - phase.connect(f'{control_str}_rates:{name}_rate', + phase.connect(f'control_rates:{name}_rate', [f'rhs_disc.{t}' for t in options['rate_targets']], src_indices=disc_src_idxs, flat_src_indices=True) - phase.connect(f'{control_str}_rates:{name}_rate', + phase.connect(f'control_rates:{name}_rate', [f'rhs_col.{t}' for t in options['rate_targets']], src_indices=col_src_idxs, flat_src_indices=True) # Second time derivative targets must be specified explicitly if options['rate2_targets']: - phase.connect(f'{control_str}_rates:{name}_rate2', + phase.connect(f'control_rates:{name}_rate2', [f'rhs_disc.{t}' for t in options['rate2_targets']], src_indices=disc_src_idxs, flat_src_indices=True) - phase.connect(f'{control_str}_rates:{name}_rate2', + phase.connect(f'control_rates:{name}_rate2', [f'rhs_col.{t}' for t in options['rate2_targets']], src_indices=col_src_idxs, flat_src_indices=True) @@ -427,18 +422,18 @@ def _get_rate_source_path(self, state_name, nodes, phase): rate_path = f'control_rates:{control_name}_rate2' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'indep_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'input_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'polynomial_control_rate': control_name = var[:-5] - rate_path = f'polynomial_control_rates:{control_name}_rate' + rate_path = f'control_rates:{control_name}_rate' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] - rate_path = f'polynomial_control_rates:{control_name}_rate2' + rate_path = f'control_rates:{control_name}_rate2' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'parameter': rate_path = f'parameter_vals:{var}' @@ -521,18 +516,18 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_units = get_rate_units(phase.control_options[control_name]['units'], time_units, deriv=2) src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: - path = f'polynomial_control_values:{var}' + path = f'control_values:{var}' src_units = phase.control_options[var]['units'] src_shape = phase.control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] - path = f'polynomial_control_rates:{control_name}_rate' + path = f'control_rates:{control_name}_rate' control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] - path = f'polynomial_control_rates:{control_name}_rate2' + path = f'control_rates:{control_name}_rate2' control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] diff --git a/dymos/transcriptions/pseudospectral/pseudospectral_base.py b/dymos/transcriptions/pseudospectral/pseudospectral_base.py index 333b9b55e..039be6499 100644 --- a/dymos/transcriptions/pseudospectral/pseudospectral_base.py +++ b/dymos/transcriptions/pseudospectral/pseudospectral_base.py @@ -423,23 +423,18 @@ def configure_defects(self, phase): # enclose indices in tuple to ensure shaping of indices works src_idxs = (src_idxs,) - if options['control_type'] == 'polynomial': - control_str = 'polynomial_control' - else: - control_str = 'control' - if options['continuity']: - phase.connect(f'{control_str}_values:{name}', + phase.connect(f'control_values:{name}', f'continuity_comp.controls:{name}', src_indices=src_idxs, flat_src_indices=True) if options['rate_continuity']: - phase.connect(f'{control_str}_rates:{name}_rate', + phase.connect(f'control_rates:{name}_rate', f'continuity_comp.control_rates:{name}_rate', src_indices=src_idxs, flat_src_indices=True) if options['rate2_continuity']: - phase.connect(f'{control_str}_rates:{name}_rate2', + phase.connect(f'control_rates:{name}_rate2', f'continuity_comp.control_rates:{name}_rate2', src_indices=src_idxs, flat_src_indices=True) @@ -617,16 +612,6 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): units = phase.control_options[var]['units'] linear = False constraint_path = f'control_values:{var}' - elif var_type == 'indep_polynomial_control': - shape = phase.polynomial_control_options[var]['shape'] - units = phase.polynomial_control_options[var]['units'] - linear = True - constraint_path = f'polynomial_control_values:{var}' - elif var_type == 'input_polynomial_control': - shape = phase.polynomial_control_options[var]['shape'] - units = phase.polynomial_control_options[var]['units'] - linear = False - constraint_path = f'polynomial_control_values:{var}' elif var_type == 'parameter': shape = phase.parameter_options[var]['shape'] units = phase.parameter_options[var]['units'] @@ -641,15 +626,6 @@ def _get_objective_src(self, var, loc, phase, ode_outputs=None): units = control_rate_units linear = False constraint_path = f'control_rates:{var}' - elif var_type in ('polynomial_control_rate', 'polynomial_control_rate2'): - control_var = var[:-5] - shape = phase.polynomial_control_options[control_var]['shape'] - control_units = phase.polynomial_control_options[control_var]['units'] - d = 2 if var_type == 'polynomial_control_rate2' else 1 - control_rate_units = get_rate_units(control_units, time_units, deriv=d) - units = control_rate_units - linear = False - constraint_path = f'polynomial_control_rates:{var}' elif var_type == 'timeseries_exec_comp_output': shape = (1,) units = None diff --git a/dymos/transcriptions/pseudospectral/radau_pseudospectral.py b/dymos/transcriptions/pseudospectral/radau_pseudospectral.py index d9e28312f..e971f70e4 100644 --- a/dymos/transcriptions/pseudospectral/radau_pseudospectral.py +++ b/dymos/transcriptions/pseudospectral/radau_pseudospectral.py @@ -268,18 +268,18 @@ def _get_rate_source_path(self, state_name, nodes, phase): rate_path = f'control_rates:{control_name}_rate2' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'indep_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'input_polynomial_control': - rate_path = f'polynomial_control_values:{var}' + rate_path = f'control_values:{var}' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'polynomial_control_rate': control_name = var[:-5] - rate_path = f'polynomial_control_rates:{control_name}_rate' + rate_path = f'control_rates:{control_name}_rate' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] - rate_path = f'polynomial_control_rates:{control_name}_rate2' + rate_path = f'control_rates:{control_name}_rate2' node_idxs = gd.subset_node_indices[nodes] elif var_type == 'parameter': rate_path = f'parameter_vals:{var}' @@ -373,18 +373,18 @@ def _get_timeseries_var_source(self, var, output_name, phase): src_units = get_rate_units(phase.control_options[control_name]['units'], time_units, deriv=2) src_shape = phase.control_options[control_name]['shape'] elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: - path = f'polynomial_control_values:{var}' + path = f'control_values:{var}' src_units = phase.control_options[var]['units'] src_shape = phase.control_options[var]['shape'] elif var_type == 'polynomial_control_rate': control_name = var[:-5] - path = f'polynomial_control_rates:{control_name}_rate' + path = f'control_rates:{control_name}_rate' control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=1) src_shape = control['shape'] elif var_type == 'polynomial_control_rate2': control_name = var[:-6] - path = f'polynomial_control_rates:{control_name}_rate2' + path = f'control_rates:{control_name}_rate2' control = phase.control_options[control_name] src_units = get_rate_units(control['units'], time_units, deriv=2) src_shape = control['shape'] diff --git a/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py b/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py index 903e25767..1f87c9537 100644 --- a/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py +++ b/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py @@ -129,13 +129,13 @@ def configure(self): if self.options['polynomial_control_options']: for name, options in self.options['polynomial_control_options'].items(): if options['targets']: - self.connect(f'polynomial_controls:{name}', + self.connect(f'controls:{name}', [f'ode.{tgt}' for tgt in options['targets']]) if options['rate_targets']: - self.connect(f'polynomial_control_rates:{name}_rate', + self.connect(f'control_rates:{name}_rate', [f'ode.{tgt}' for tgt in options['rate_targets']]) if options['rate2_targets']: - self.connect(f'polynomial_control_rates:{name}_rate2', + self.connect(f'control_rates:{name}_rate2', [f'ode.{tgt}' for tgt in options['rate2_targets']]) # Parameters @@ -160,8 +160,6 @@ def _get_rate_source_path(self, state_var): rate_path = f'states:{var}' elif self.options['control_options'] is not None and var in self.options['control_options']: rate_path = f'controls:{var}' - elif self.options['polynomial_control_options'] is not None and var in self.options['polynomial_control_options']: - rate_path = f'polynomial_controls:{var}' elif self.options['parameter_options'] is not None and var in self.options['parameter_options']: rate_path = f'parameters:{var}' elif var.endswith('_rate') and self.options['control_options'] is not None and \ @@ -170,12 +168,6 @@ def _get_rate_source_path(self, state_var): elif var.endswith('_rate2') and self.options['control_options'] is not None and \ var[:-6] in self.options['control_options']: rate_path = f'control_rates:{var}' - elif var.endswith('_rate') and self.options['polynomial_control_options'] is not None and \ - var[:-5] in self.options['polynomial_control_options']: - rate_path = f'polynomial_control_rates:{var}' - elif var.endswith('_rate2') and self.options['polynomial_control_options'] is not None and \ - var[:-6] in self.options['polynomial_control_options']: - rate_path = f'polynomial_control_rates:{var}' return rate_path @@ -192,8 +184,6 @@ def set_interpolant(self, name, interp): """ if name in self.options['control_options']: self._interp_comp.options['control_interpolants'][name] = interp - elif name in self.options['polynomial_control_options']: - self._interp_comp.options['polynomial_control_interpolants'][name] = interp else: raise KeyError(f'Unable to set control interpolant of unknown control: {name}') @@ -206,15 +196,13 @@ def setup_interpolant(self, name, x0, xf, f_j): name : str The name of the control or polynomial control. x0 : float - The initial time (or independent variable) of the segment (for controls) or phase (for polynomial controls). + The initial time (or independent variable) of the segment (for controls). xf : float - The final time (or independent variable) of the segment (for controls) or phase (for polynomial controls). + The final time (or independent variable) of the segment (for controls). f_j : float The value of the control at the nodes in the segment or phase. """ if name in self.options['control_options']: self._interp_comp.options['control_interpolants'][name].setup(x0, xf, f_j) - elif name in self.options['polynomial_control_options']: - self._interp_comp.options['polynomial_control_interpolants'][name].setup(x0, xf, f_j) else: raise KeyError(f'Unable to setup control interpolant of unknown control: {name}') diff --git a/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py b/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py index 61cd31d39..efdd61288 100644 --- a/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py +++ b/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py @@ -60,13 +60,13 @@ def setup(self): rate_units = get_rate_units(units, time_units, deriv=1) rate2_units = get_rate_units(units, time_units, deriv=2) - self.add_output('polynomial_controls:{0}'.format(control_name), shape=shape, + self.add_output('controls:{0}'.format(control_name), shape=shape, units=units) - self.add_output('polynomial_control_rates:{0}_rate'.format(control_name), shape=shape, + self.add_output('control_rates:{0}_rate'.format(control_name), shape=shape, units=rate_units) - self.add_output('polynomial_control_rates:{0}_rate2'.format(control_name), shape=shape, + self.add_output('control_rates:{0}_rate2'.format(control_name), shape=shape, units=rate2_units) def compute(self, inputs, outputs): @@ -100,8 +100,8 @@ def compute(self, inputs, outputs): interp = self.options['polynomial_control_interpolants'][name] - outputs['polynomial_controls:{0}'.format(name)] = interp.eval(time) + outputs['controls:{0}'.format(name)] = interp.eval(time) - outputs['polynomial_control_rates:{0}_rate'.format(name)] = interp.eval_deriv(time) + outputs['control_rates:{0}_rate'.format(name)] = interp.eval_deriv(time) - outputs['polynomial_control_rates:{0}_rate2'.format(name)] = interp.eval_deriv(time, der=2) + outputs['control_rates:{0}_rate2'.format(name)] = interp.eval_deriv(time, der=2) diff --git a/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py b/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py index b5e787e2f..8aae98ea3 100644 --- a/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py +++ b/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py @@ -155,7 +155,7 @@ def configure_io(self): if self.options['polynomial_control_options']: for name, options in self.options['polynomial_control_options'].items(): poly_control_disc_ptau, _ = lgl(options['order'] + 1) - self.add_input(name='polynomial_controls:{0}'.format(name), + self.add_input(name='controls:{0}'.format(name), val=np.ones(((options['order'] + 1,) + options['shape'])), units=options['units'], desc='Values of polynomial control {0} at control discretization ' @@ -206,7 +206,7 @@ def compute(self, inputs, outputs): t0_phase = inputs['t_initial'] tf_phase = inputs['t_initial'] + inputs['t_duration'] for name, options in self.options['polynomial_control_options'].items(): - ctrl_vals = inputs[f'polynomial_controls:{name}'] + ctrl_vals = inputs[f'controls:{name}'] self.options['ode_integration_interface'].setup_interpolant(name, x0=t0_phase, xf=tf_phase, diff --git a/dymos/transcriptions/solve_ivp/solve_ivp.py b/dymos/transcriptions/solve_ivp/solve_ivp.py index 9304b3d92..772f12f4b 100644 --- a/dymos/transcriptions/solve_ivp/solve_ivp.py +++ b/dymos/transcriptions/solve_ivp/solve_ivp.py @@ -249,7 +249,6 @@ def setup_ode(self, phase): time_options=phase.time_options, state_options=phase.state_options, control_options=phase.control_options, - polynomial_control_options=phase.polynomial_control_options, parameter_options=phase.parameter_options, output_nodes_per_seg=self.options['output_nodes_per_seg'], reports=self.options['reports']) @@ -591,22 +590,6 @@ def _get_timeseries_var_source(self, var, output_name, phase): path = f'control_rates:{control_name}_rate2' src_units = get_rate_units(phase.control_options[control_name]['units'], time_units, deriv=2) src_shape = phase.control_options[control_name]['shape'] - elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: - path = f'polynomial_control_values:{var}' - src_units = phase.polynomial_control_options[var]['units'] - src_shape = phase.polynomial_control_options[var]['shape'] - elif var_type == 'polynomial_control_rate': - control_name = var[:-5] - path = f'polynomial_control_rates:{control_name}_rate' - control = phase.polynomial_control_options[control_name] - src_units = get_rate_units(control['units'], time_units, deriv=1) - src_shape = control['shape'] - elif var_type == 'polynomial_control_rate2': - control_name = var[:-6] - path = f'polynomial_control_rates:{control_name}_rate2' - control = phase.polynomial_control_options[control_name] - src_units = get_rate_units(control['units'], time_units, deriv=1) - src_shape = control['shape'] elif var_type == 'parameter': path = f'parameter_vals:{var}' num_seg = self.grid_data.num_segments diff --git a/dymos/utils/introspection.py b/dymos/utils/introspection.py index 61d4c1332..46dcd3dc5 100644 --- a/dymos/utils/introspection.py +++ b/dymos/utils/introspection.py @@ -35,8 +35,6 @@ def classify_var(var, time_options, state_options, parameter_options, control_op For each parameter, a dictionary of its options, keyed by name. control_options : dict of {str: OptionsDictionary} For each control variable, a dictionary of its options, keyed by name. - polynomial_control_options : dict of {str: OptionsDictionary} - For each polynomial variable, a dictionary of its options, keyed by name. timeseries_options : {str: OptionsDictionary} For each timeseries, a dictionary of its options, keyed by name. @@ -45,9 +43,7 @@ def classify_var(var, time_options, state_options, parameter_options, control_op str The classification of the given variable, which is one of 't', 't_phase', 'state', 'input_control', 'indep_control', 'control_rate', - 'control_rate2', 'input_polynomial_control', 'indep_polynomial_control', - 'polynomial_control_rate', 'polynomial_control_rate2', 'parameter', - or 'ode'. + 'control_rate2', 'parameter', or 'ode'. """ time_name = time_options['name'] if var == time_name: @@ -62,29 +58,17 @@ def classify_var(var, time_options, state_options, parameter_options, control_op return 'state' elif var in control_options: if control_options[var]['opt']: - if control_options[var]['control_type'] == 'polynomial': - return 'indep_polynomial_control' - else: - return 'indep_control' + return 'indep_control' else: - if control_options[var]['control_type'] == 'polynomial': - return 'input_polynomial_control' - else: - return 'input_control' + return 'input_control' elif var in parameter_options: return 'parameter' elif var.endswith('_rate'): if var[:-5] in control_options: - if control_options[var[:-5]]['control_type'] == 'polynomial': - return 'polynomial_control_rate' - else: - return 'control_rate' + return 'control_rate' elif var.endswith('_rate2'): if var[:-6] in control_options: - if control_options[var[:-6]]['control_type'] == 'polynomial': - return 'polynomial_control_rate2' - else: - return 'control_rate2' + return 'control_rate2' elif timeseries_options is not None: for timeseries in timeseries_options: if var in timeseries_options[timeseries]['outputs']: @@ -235,17 +219,6 @@ def _configure_constraint_introspection(phase): else: con['constraint_path'] = f'timeseries.{prefix}{var}' - elif var_type in ['indep_polynomial_control', 'input_polynomial_control']: - prefix = 'polynomial_controls:' if phase.timeseries_options['use_prefix'] else '' - control_shape = phase.polynomial_control_options[var]['shape'] - control_units = phase.polynomial_control_options[var]['units'] - con['shape'] = control_shape - con['units'] = control_units if con['units'] is None else con['units'] - if birkhoff and constraint_type in ('initial', 'final'): - con['constraint_path'] = f'boundary_vals.{var}' - else: - con['constraint_path'] = f'timeseries.{prefix}{var}' - elif var_type == 'control_rate': prefix = 'control_rates:' if phase.timeseries_options['use_prefix'] else '' control_name = var[:-5] @@ -272,32 +245,6 @@ def _configure_constraint_introspection(phase): else: con['constraint_path'] = f'timeseries.{prefix}{var}' - elif var_type == 'polynomial_control_rate': - prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' - control_name = var[:-5] - control_shape = phase.control_options[control_name]['shape'] - control_units = phase.control_options[control_name]['units'] - con['shape'] = control_shape - con['units'] = get_rate_units(control_units, time_units, deriv=1) \ - if con['units'] is None else con['units'] - if birkhoff and constraint_type in ('initial', 'final'): - con['constraint_path'] = f'boundary_vals.{var}' - else: - con['constraint_path'] = f'timeseries.{prefix}{var}' - - elif var_type == 'polynomial_control_rate2': - prefix = 'polynomial_control_rates:' if phase.timeseries_options['use_prefix'] else '' - control_name = var[:-6] - control_shape = phase.control_options[control_name]['shape'] - control_units = phase.control_options[control_name]['units'] - con['shape'] = control_shape - con['units'] = get_rate_units(control_units, time_units, deriv=2) \ - if con['units'] is None else con['units'] - if birkhoff and constraint_type in ('initial', 'final'): - con['constraint_path'] = f'boundary_vals.{var}' - else: - con['constraint_path'] = f'timeseries.{prefix}{var}' - elif var_type == 'timeseries_exec_comp_output': con['shape'] = (1,) con['units'] = None @@ -545,8 +492,6 @@ def configure_states_introspection(state_options, time_options, control_options, The options for each control. parameter_options : dict of {str: ParameterOptionsDictionary} The options for each parameter. - polynomial_control_options : dict of {str: PolynomialControlOptionsDictionary} - The options for each polynomial control. ode : System The OpenMDAO system which provides the state rates as outputs. """ @@ -585,7 +530,7 @@ def configure_states_introspection(state_options, time_options, control_options, elif rate_src_type == 'state': rate_src_units = state_options[rate_src]['units'] rate_src_shape = state_options[rate_src]['shape'] - elif rate_src_type in ['input_control', 'indep_control', 'input_polynomial_control', 'indep_polynomial_control']: + elif rate_src_type in ['input_control', 'indep_control']: rate_src_units = control_options[rate_src]['units'] rate_src_shape = control_options[rate_src]['shape'] elif rate_src_type == 'parameter': @@ -601,16 +546,6 @@ def configure_states_introspection(state_options, time_options, control_options, control = control_options[control_name] rate_src_units = get_rate_units(control['units'], time_units, deriv=2) rate_src_shape = control['shape'] - elif rate_src_type == 'polynomial_control_rate': - control_name = rate_src[:-5] - control = control_options[control_name] - rate_src_units = get_rate_units(control['units'], time_units, deriv=1) - rate_src_shape = control['shape'] - elif rate_src_type == 'polynomial_control_rate2': - control_name = rate_src[:-6] - control = control_options[control_name] - rate_src_units = get_rate_units(control['units'], time_units, deriv=2) - rate_src_shape = control['shape'] elif rate_src_type == 'ode': meta = get_source_metadata(ode_outputs, src=rate_src, user_units=options['units'], user_shape=options['shape']) rate_src_shape = meta['shape'] @@ -1066,7 +1001,7 @@ def configure_duration_balance_introspection(phase): options['units'] = param_units if options['units'] is None else options['units'] options['var_path'] = f'parameter_vals:{var}' - elif var_type in ['indep_control', 'input_control', 'indep_polynomial_control', 'input_polynomial_control']: + elif var_type in ['indep_control', 'input_control']: prefix = 'controls:' if dymos_options['use_timeseries_prefix'] else '' control_shape = phase.control_options[var]['shape'] control_units = phase.control_options[var]['units'] @@ -1095,26 +1030,6 @@ def configure_duration_balance_introspection(phase): if options['units'] is None else options['units'] options['var_path'] = f'timeseries.{prefix}{var}' - elif var_type == 'polynomial_control_rate': - prefix = 'polynomial_control_rates:' if dymos_options['use_timeseries_prefix'] else '' - control_name = var[:-5] - control_shape = phase.control_options[control_name]['shape'] - control_units = phase.control_options[control_name]['units'] - options['shape'] = control_shape - options['units'] = get_rate_units(control_units, time_units, deriv=1) \ - if options['units'] is None else options['units'] - options['var_path'] = f'timeseries.{prefix}{var}' - - elif var_type == 'polynomial_control_rate2': - prefix = 'polynomial_control_rates:' if dymos_options['use_timeseries_prefix'] else '' - control_name = var[:-6] - control_shape = phase.control_options[control_name]['shape'] - control_units = phase.control_options[control_name]['units'] - options['shape'] = control_shape - options['units'] = get_rate_units(control_units, time_units, deriv=2) \ - if options['units'] is None else options['units'] - options['var_path'] = f'timeseries.{prefix}{var}' - elif var_type == 'timeseries_exec_comp_output': options['shape'] = (1,) options['units'] = None diff --git a/dymos/visualization/linkage/report.py b/dymos/visualization/linkage/report.py index 96b19ee53..191330f7f 100644 --- a/dymos/visualization/linkage/report.py +++ b/dymos/visualization/linkage/report.py @@ -216,7 +216,7 @@ def _trajectory_to_dict(traj): # Polynomial Controls for pc_name in phase.polynomial_control_options: for loc, child in condition_children.items(): - child[pc_name] = _tree_var(pc_name, phase, loc, 'polynomial_controls:') + child[pc_name] = _tree_var(pc_name, phase, loc, 'controls:') # Parameters for param_name, param in phase.parameter_options.items(): From c0dd630b91bc13a4697871dd240ebc08145c3993 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Fri, 14 Jun 2024 12:22:28 -0500 Subject: [PATCH 05/17] WIP; controls combining --- dymos/transcriptions/common/control_group.py | 10 ++++- .../common/timeseries_output_comp.py | 2 + .../explicit_shooting/explicit_shooting.py | 5 ++- .../explicit_shooting/ode_evaluation_group.py | 7 +++- .../explicit_shooting/ode_integration_comp.py | 38 +++---------------- 5 files changed, 25 insertions(+), 37 deletions(-) diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index a8202cf3e..82f124a44 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -94,7 +94,7 @@ def _configure_controls(self): time_units = self.options['time_units'] for name, options in control_options.items(): - if control_options[name]['control_type'] == 'polynomial': + if options['control_type'] == 'polynomial': disc_nodes, _ = lgl(options['order'] + 1) num_control_input_nodes = len(disc_nodes) shape = options['shape'] @@ -117,6 +117,11 @@ def _configure_controls(self): self._output_rate_names[name] = f'control_rates:{name}_rate' self._output_rate2_names[name] = f'control_rates:{name}_rate2' + print(f'input name: {self._input_names[name]}') + # print all shapes + print(f'input shape: {input_shape}') + print(f'output shape: {output_shape}') + self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) self.add_output(self._output_val_names[name], shape=output_shape, units=units) self.add_output(self._output_rate_names[name], shape=output_shape, units=rate_units) @@ -468,6 +473,9 @@ def setup(self): if len(control_options) < 1: return + print('grids') + print(gd) + print(ogd) self.add_subsystem( 'control_interp_comp', subsys=ControlInterpComp(time_units=time_units, grid_data=gd, output_grid_data=ogd, diff --git a/dymos/transcriptions/common/timeseries_output_comp.py b/dymos/transcriptions/common/timeseries_output_comp.py index 15b6503ab..5bede8c68 100644 --- a/dymos/transcriptions/common/timeseries_output_comp.py +++ b/dymos/transcriptions/common/timeseries_output_comp.py @@ -165,6 +165,8 @@ def _add_output_configure(self, name, units, shape, desc='', src=None, rate=Fals input_units = self._units[input_name] else: input_name = f'input_values:{name}' + print(f'Adding input {input_name} for {name}') + print(input_num_nodes) self.add_input(input_name, shape=(input_num_nodes,) + shape, units=units, desc=desc) diff --git a/dymos/transcriptions/explicit_shooting/explicit_shooting.py b/dymos/transcriptions/explicit_shooting/explicit_shooting.py index 38a44d1ef..f9657bb2f 100644 --- a/dymos/transcriptions/explicit_shooting/explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/explicit_shooting.py @@ -401,8 +401,11 @@ def configure_controls(self, phase): ode_inputs = get_promoted_vars(ode, 'input') # Add the appropriate design parameters - ncin = self.options['grid'].subset_num_nodes['control_input'] for control_name, options in phase.control_options.items(): + if options['control_type'] == 'polynomial': + ncin = options['order'] + 1 + else: + ncin = options['grid'].subset_num_nodes['control_input'] phase.promotes('integrator', inputs=[f'controls:{control_name}']) diff --git a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py index a01ae0694..2ac9bec94 100644 --- a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py +++ b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py @@ -260,9 +260,12 @@ def _configure_controls(self): raise ValueError('ODEEvaluationGroup was provided with control options but ' 'a GridData object was not provided.') - num_control_input_nodes = igd.subset_num_nodes['control_input'] - for name, options in self._control_options.items(): + if options['control_type'] == 'polynomial': + num_control_input_nodes = options['order'] + 1 + else: + num_control_input_nodes = igd.subset_num_nodes['control_input'] + shape = options['shape'] units = options['units'] rate_units = get_rate_units(units, time_units, deriv=1) diff --git a/dymos/transcriptions/explicit_shooting/ode_integration_comp.py b/dymos/transcriptions/explicit_shooting/ode_integration_comp.py index a888ce8a1..1d1e5030a 100644 --- a/dymos/transcriptions/explicit_shooting/ode_integration_comp.py +++ b/dymos/transcriptions/explicit_shooting/ode_integration_comp.py @@ -264,7 +264,11 @@ def _configure_controls(self): self._control_input_names = {} for control_name, options in self.control_options.items(): - control_param_shape = (self._num_control_input_nodes,) + options['shape'] + if options['control_type'] == 'polynomial': + num_input_nodes = options['order'] + 1 + control_param_shape = (num_input_nodes,) + options['shape'] + else: + control_param_shape = (self._num_control_input_nodes,) + options['shape'] control_param_size = np.prod(control_param_shape, dtype=int) self._control_input_names[control_name] = f'controls:{control_name}' @@ -277,37 +281,6 @@ def _configure_controls(self): self.u_size += control_param_size - def _setup_polynomial_controls(self): - if self._standalone_mode: - self._configure_polynomial_controls() - - def _configure_polynomial_controls(self): - """ - Components do not have configure methods, but since we rely on configure-time introspection to determine - properties of the states, times, controls, parameters, and timeseries, we need to call this method at - configure time in the parent ExplicitShooting transcription object. - """ - self.up_size = 0 - self._polynomial_control_idxs_in_theta = {} - self._polynomial_control_idxs_in_z = {} - self._polynomial_control_input_names = {} - - for name, options in self.polynomial_control_options.items(): - num_input_nodes = options['order'] + 1 - control_param_shape = (num_input_nodes,) + options['shape'] - control_param_size = np.prod(control_param_shape, dtype=int) - - self._polynomial_control_input_names[name] = f'controls:{name}' - - self._totals_wrt_names.append(self._polynomial_control_input_names[name]) - - self.add_input(self._polynomial_control_input_names[name], - shape=control_param_shape, - units=options['units'], - desc=f'values for control {name} at input nodes') - - self.up_size += control_param_size - def _build_dx_dz_idxs(self): self._partial_dx_dz_idxs = {} @@ -464,7 +437,6 @@ def setup(self): self._setup_time() self._setup_parameters() self._setup_controls() - self._setup_polynomial_controls() self._setup_states() self._setup_storage() From 3ccaa742024c74ace2dce31f9e3746a27f7f324b Mon Sep 17 00:00:00 2001 From: johnjasa Date: Fri, 14 Jun 2024 13:01:00 -0500 Subject: [PATCH 06/17] WIP; debugging --- dymos/transcriptions/common/control_group.py | 5 +++++ dymos/transcriptions/solve_ivp/solve_ivp.py | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index 82f124a44..783541f37 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -476,6 +476,11 @@ def setup(self): print('grids') print(gd) print(ogd) + # loop through both grids and print all attributes + for grid in [gd, ogd]: + for item in dir(grid): + if not item.startswith('__'): + print(f'{item}: {getattr(grid, item)}') self.add_subsystem( 'control_interp_comp', subsys=ControlInterpComp(time_units=time_units, grid_data=gd, output_grid_data=ogd, diff --git a/dymos/transcriptions/solve_ivp/solve_ivp.py b/dymos/transcriptions/solve_ivp/solve_ivp.py index 772f12f4b..f68727e1d 100644 --- a/dymos/transcriptions/solve_ivp/solve_ivp.py +++ b/dymos/transcriptions/solve_ivp/solve_ivp.py @@ -1,6 +1,3 @@ -from fnmatch import filter -import warnings - import numpy as np import openmdao.api as om From 8e19e3a524967c94f75b8822244fb6c441f5a63e Mon Sep 17 00:00:00 2001 From: johnjasa Date: Fri, 14 Jun 2024 15:47:02 -0500 Subject: [PATCH 07/17] Better progress; resolved size issue for control/poly control --- dymos/phase/phase.py | 11 ++++-- dymos/phase/simulation_phase.py | 8 ++--- dymos/transcriptions/common/control_group.py | 34 ++++++------------- .../common/timeseries_output_comp.py | 2 -- .../barycentric_control_interp_comp.py | 2 -- .../explicit_shooting/explicit_shooting.py | 2 +- 6 files changed, 21 insertions(+), 38 deletions(-) diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index 511148f5a..b6ed2cbb7 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -1824,12 +1824,17 @@ def set_control_val(self, name, vals=None, time_vals=None, integer specifying the order of the spline interpolator to use. Default is 'linear'. """ + control_options = self.control_options[name] + if control_options['control_type'] == 'polynomial': + nodes = None + else: + nodes = 'control_input' if np.isscalar(vals): val = vals else: - val = self.interp(name, ys=vals, xs=time_vals, nodes='control_input', kind=interpolation_kind) + val = self.interp(name, ys=vals, xs=time_vals, nodes=nodes, kind=interpolation_kind) if units is None: - units = self.control_options[name]['units'] + units = control_options['units'] self.set_val(f'controls:{name}', val=val, units=units) def set_polynomial_control_val(self, name, vals=None, time_vals=None, @@ -2310,7 +2315,7 @@ def interp(self, name=None, ys=None, xs=None, nodes=None, kind='linear', axis=0) node_locations = gd.node_ptau[gd.subset_node_indices['control_input']] else: raise ValueError('Could not find a state, control, or polynomial control named ' - f'{name} to be interpolated.\nPlease explicitly specified the ' + f'{name} to be interpolated.\nPlease explicitly specify the ' f'node subset onto which this value should be interpolated.') else: node_locations = gd.node_ptau[gd.subset_node_indices[nodes]] diff --git a/dymos/phase/simulation_phase.py b/dymos/phase/simulation_phase.py index 6dbbc7071..5b3b2cbd8 100644 --- a/dymos/phase/simulation_phase.py +++ b/dymos/phase/simulation_phase.py @@ -89,12 +89,8 @@ def set_vals_from_phase(self, from_phase): self.set_val(f'parameters:{name}', val, units=options['units']) for name, options in self.control_options.items(): - if options['control_type'] == 'polynomial': - control_str = 'polynomial_controls' - else: - control_str = 'controls' - val = from_phase.get_val(f'{control_str}:{name}', units=options['units'], from_src=False) - self.set_val(f'{control_str}:{name}', val, units=options['units']) + val = from_phase.get_val(f'controls:{name}', units=options['units'], from_src=False) + self.set_val(f'controls:{name}', val, units=options['units']) def add_boundary_constraint(self, name, loc, constraint_name=None, units=None, shape=None, indices=None, lower=None, upper=None, equals=None, diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index 783541f37..a25894b4b 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -85,9 +85,8 @@ def setup(self): def _configure_controls(self): gd = self.options['grid_data'] - num_nodes = gd.subset_num_nodes['all'] - eval_nodes = gd.node_ptau ogd = self.options['output_grid_data'] or gd + eval_nodes = ogd.node_ptau control_options = self.options['control_options'] num_output_nodes = ogd.num_nodes num_control_input_nodes = gd.subset_num_nodes['control_input'] @@ -104,7 +103,7 @@ def _configure_controls(self): rate2_units = get_rate_units(units, self.options['time_units'], deriv=2) input_shape = (num_control_input_nodes,) + shape - output_shape = (num_nodes,) + shape + output_shape = (num_output_nodes,) + shape L_de, D_de = lagrange_matrices(disc_nodes, eval_nodes) _, D_dd = lagrange_matrices(disc_nodes, disc_nodes) @@ -117,32 +116,27 @@ def _configure_controls(self): self._output_rate_names[name] = f'control_rates:{name}_rate' self._output_rate2_names[name] = f'control_rates:{name}_rate2' - print(f'input name: {self._input_names[name]}') - # print all shapes - print(f'input shape: {input_shape}') - print(f'output shape: {output_shape}') - self.add_input(self._input_names[name], val=np.ones(input_shape), units=units) self.add_output(self._output_val_names[name], shape=output_shape, units=units) self.add_output(self._output_rate_names[name], shape=output_shape, units=rate_units) self.add_output(self._output_rate2_names[name], shape=output_shape, units=rate2_units) - self.val_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) - self.rate_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) - self.rate2_jacs[name] = np.zeros((num_nodes, size, num_control_input_nodes, size)) + self.val_jacs[name] = np.zeros((num_output_nodes, size, num_control_input_nodes, size)) + self.rate_jacs[name] = np.zeros((num_output_nodes, size, num_control_input_nodes, size)) + self.rate2_jacs[name] = np.zeros((num_output_nodes, size, num_control_input_nodes, size)) for i in range(size): self.val_jacs[name][:, i, :, i] = L_de self.rate_jacs[name][:, i, :, i] = D_de self.rate2_jacs[name][:, i, :, i] = D2_de - self.val_jacs[name] = self.val_jacs[name].reshape((num_nodes * size, + self.val_jacs[name] = self.val_jacs[name].reshape((num_output_nodes * size, num_control_input_nodes * size), order='C') - self.rate_jacs[name] = self.rate_jacs[name].reshape((num_nodes * size, + self.rate_jacs[name] = self.rate_jacs[name].reshape((num_output_nodes * size, num_control_input_nodes * size), order='C') - self.rate2_jacs[name] = self.rate2_jacs[name].reshape((num_nodes * size, + self.rate2_jacs[name] = self.rate2_jacs[name].reshape((num_output_nodes * size, num_control_input_nodes * size), order='C') self.val_jac_rows[name], self.val_jac_cols[name] = \ @@ -159,7 +153,7 @@ def _configure_controls(self): wrt=self._input_names[name], rows=rs, cols=cs, val=self.val_jacs[name][rs, cs]) - rs = np.concatenate([np.arange(0, num_nodes * size, size, dtype=int) + i + rs = np.concatenate([np.arange(0, num_output_nodes * size, size, dtype=int) + i for i in range(size)]) self.declare_partials(of=self._output_rate_names[name], @@ -380,7 +374,7 @@ def compute_partials(self, inputs, partials): """ control_options = self.options['control_options'] num_input_nodes = self.options['grid_data'].subset_num_nodes['control_input'] - nn = self.options['grid_data'].num_nodes + nn = self.options['output_grid_data'].num_nodes dstau_dt = np.reciprocal(inputs['dt_dstau']) dstau_dt2 = (dstau_dt ** 2)[:, np.newaxis] @@ -473,14 +467,6 @@ def setup(self): if len(control_options) < 1: return - print('grids') - print(gd) - print(ogd) - # loop through both grids and print all attributes - for grid in [gd, ogd]: - for item in dir(grid): - if not item.startswith('__'): - print(f'{item}: {getattr(grid, item)}') self.add_subsystem( 'control_interp_comp', subsys=ControlInterpComp(time_units=time_units, grid_data=gd, output_grid_data=ogd, diff --git a/dymos/transcriptions/common/timeseries_output_comp.py b/dymos/transcriptions/common/timeseries_output_comp.py index 5bede8c68..15b6503ab 100644 --- a/dymos/transcriptions/common/timeseries_output_comp.py +++ b/dymos/transcriptions/common/timeseries_output_comp.py @@ -165,8 +165,6 @@ def _add_output_configure(self, name, units, shape, desc='', src=None, rate=Fals input_units = self._units[input_name] else: input_name = f'input_values:{name}' - print(f'Adding input {input_name} for {name}') - print(input_num_nodes) self.add_input(input_name, shape=(input_num_nodes,) + shape, units=units, desc=desc) diff --git a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py index 053434f73..535b23eb2 100644 --- a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py @@ -160,7 +160,6 @@ def set_segment_index(self, idx, alloc_complex=False): If True, allocate storage for complex step. """ self.options['segment_index'] = idx - disc_node_idxs = self._disc_node_idxs_by_segment[idx] i1, i2 = self._grid_data.subset_segment_indices['control_disc'][idx, :] indices = self._grid_data.subset_node_indices['control_disc'][i1:i2] @@ -172,7 +171,6 @@ def set_segment_index(self, idx, alloc_complex=False): if options['control_type'] == 'polynomial': order = options['order'] ptaus[control_name] = lgl(order + 1)[0] - print(ptaus) self._compute_barycentric_weights(taus_seg, ptaus) dtype = complex if alloc_complex else float diff --git a/dymos/transcriptions/explicit_shooting/explicit_shooting.py b/dymos/transcriptions/explicit_shooting/explicit_shooting.py index f9657bb2f..d1f63b596 100644 --- a/dymos/transcriptions/explicit_shooting/explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/explicit_shooting.py @@ -405,7 +405,7 @@ def configure_controls(self, phase): if options['control_type'] == 'polynomial': ncin = options['order'] + 1 else: - ncin = options['grid'].subset_num_nodes['control_input'] + ncin = self.options['grid'].subset_num_nodes['control_input'] phase.promotes('integrator', inputs=[f'controls:{control_name}']) From 405189debc6f064d5d61817322082acdf082bb71 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Fri, 14 Jun 2024 16:39:26 -0500 Subject: [PATCH 08/17] WIP; need to get all tests and benchmarks working --- benchmark/benchmark_balanced_field.py | 2 +- .../balanced_field/test/test_balanced_field_length.py | 2 +- dymos/examples/shuttle_reentry/test/test_reentry.py | 2 +- dymos/grid_refinement/test/test_error_estimation.py | 2 +- dymos/phase/test/test_simulate.py | 4 ++-- dymos/phase/test/test_timeseries.py | 2 +- dymos/test/test_check_partials.py | 2 +- dymos/test/test_load_case.py | 8 ++++---- dymos/trajectory/test/test_trajectory.py | 6 +++--- .../explicit_shooting/test/test_explicit_shooting.py | 2 +- .../explicit_shooting/test/test_ode_integration_comp.py | 2 +- dymos/visualization/linkage/report.py | 7 ------- .../visualization/linkage/test/linkage_report_ui_test.py | 2 +- dymos/visualization/linkage/test/test_linkage_report.py | 2 +- 14 files changed, 19 insertions(+), 26 deletions(-) diff --git a/benchmark/benchmark_balanced_field.py b/benchmark/benchmark_balanced_field.py index b1aeb1bf3..0df0b5e7c 100644 --- a/benchmark/benchmark_balanced_field.py +++ b/benchmark/benchmark_balanced_field.py @@ -229,7 +229,7 @@ def _run_balanced_field_length_problem(tx=dm.GaussLobatto, timeseries=True, sim= p.set_val('traj.rotate.t_duration', 5) p.set_val('traj.rotate.states:r', rotate.interp('r', [1750, 1800.0])) p.set_val('traj.rotate.states:v', rotate.interp('v', [80, 85.0])) - p.set_val('traj.rotate.polynomial_controls:alpha', 0.0, units='deg') + p.set_val('traj.rotate.controls:alpha', 0.0, units='deg') p.set_val('traj.climb.t_initial', 75) p.set_val('traj.climb.t_duration', 15) diff --git a/dymos/examples/balanced_field/test/test_balanced_field_length.py b/dymos/examples/balanced_field/test/test_balanced_field_length.py index a53bac10e..f896ac85b 100644 --- a/dymos/examples/balanced_field/test/test_balanced_field_length.py +++ b/dymos/examples/balanced_field/test/test_balanced_field_length.py @@ -456,7 +456,7 @@ def test_default_vals_stick(self): assert_near_equal(p.get_val('traj.rotate.t_initial'), 70) assert_near_equal(p.get_val('traj.rotate.t_duration'), 5) - assert_near_equal(p.get_val('traj.rotate.polynomial_controls:alpha'), np.array([[0, 10]]).T) + assert_near_equal(p.get_val('traj.rotate.controls:alpha'), np.array([[0, 10]]).T) assert_near_equal(p.get_val('traj.climb.controls:alpha'), p.model.traj.phases.climb.interp('', [0.01, 0.01], nodes='control_input')) assert_near_equal(p.get_val('traj.climb.states:gam'), diff --git a/dymos/examples/shuttle_reentry/test/test_reentry.py b/dymos/examples/shuttle_reentry/test/test_reentry.py index 10cfb05e4..c28877112 100644 --- a/dymos/examples/shuttle_reentry/test/test_reentry.py +++ b/dymos/examples/shuttle_reentry/test/test_reentry.py @@ -267,7 +267,7 @@ def test_reentry_mixed_controls(self): p.set_val('traj.phase0.t_duration', 2000, units='s') p.set_val('traj.phase0.controls:alpha', phase0.interp('alpha', [17.4, 17.4]), units='deg') - p.set_val('traj.phase0.polynomial_controls:beta', + p.set_val('traj.phase0.controls:beta', phase0.interp('beta', [-20, 0]), units='deg') dm.run_problem(p, simulate=True) diff --git a/dymos/grid_refinement/test/test_error_estimation.py b/dymos/grid_refinement/test/test_error_estimation.py index b4fa695c9..fb79f02ec 100644 --- a/dymos/grid_refinement/test/test_error_estimation.py +++ b/dymos/grid_refinement/test/test_error_estimation.py @@ -69,7 +69,7 @@ def _run_brachistochrone(self, transcription_class=dm.Radau, control_type='contr if control_type == 'control': p.set_val('traj0.phase0.controls:theta', phase.interp('theta', [90, 90]), units='deg') else: - p.set_val('traj0.phase0.polynomial_controls:theta', + p.set_val('traj0.phase0.controls:theta', phase.interp('theta', [5, 100]), units='deg') p['traj0.phase0.parameters:g'] = g diff --git a/dymos/phase/test/test_simulate.py b/dymos/phase/test/test_simulate.py index d25bf9ec2..c6148764f 100644 --- a/dymos/phase/test/test_simulate.py +++ b/dymos/phase/test/test_simulate.py @@ -79,7 +79,7 @@ def test_shaped_params(self): p.set_val('hop0.main_phase.t_initial', 0.0) p.set_val('hop0.main_phase.t_duration', 10) - p.set_val('hop0.main_phase.polynomial_controls:Thrust', val=-3400, indices=om.slicer[:, 0]) + p.set_val('hop0.main_phase.controls:Thrust', val=-3400, indices=om.slicer[:, 0]) p.set_val('hop0.main_phase.states:impulse', main_phase.interp('impulse', [0, 0])) p.run_driver() @@ -123,7 +123,7 @@ def test_shaped_traj_params(self): p.set_val('hop0.main_phase.t_initial', 0.0) p.set_val('hop0.main_phase.t_duration', 10) - p.set_val('hop0.main_phase.polynomial_controls:Thrust', val=-3400, indices=om.slicer[:, 0]) + p.set_val('hop0.main_phase.controls:Thrust', val=-3400, indices=om.slicer[:, 0]) p.set_val('hop0.main_phase.states:impulse', main_phase.interp('impulse', [0, 0])) p.run_driver() diff --git a/dymos/phase/test/test_timeseries.py b/dymos/phase/test/test_timeseries.py index 235f7ee7b..c358f8d17 100644 --- a/dymos/phase/test/test_timeseries.py +++ b/dymos/phase/test/test_timeseries.py @@ -428,7 +428,7 @@ def make_problem_brachistochrone(transcription, polynomial_control=False): if polynomial_control: phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) - control_name = 'polynomial_controls:theta' + control_name = 'controls:theta' else: phase.add_control('theta', continuity=True, rate_continuity=True, opt=True, units='deg', lower=0.01, upper=179.9, ref=1, ref0=0) diff --git a/dymos/test/test_check_partials.py b/dymos/test/test_check_partials.py index bd9451a17..b1301b46a 100644 --- a/dymos/test/test_check_partials.py +++ b/dymos/test/test_check_partials.py @@ -252,7 +252,7 @@ def balanced_field_partials_radau(self): p.set_val('traj.rotate.t_duration', 5) p.set_val('traj.rotate.states:r', rotate.interp('r', [1750, 1800.0])) p.set_val('traj.rotate.states:v', rotate.interp('v', [80, 85.0])) - p.set_val('traj.rotate.polynomial_controls:alpha', 0.0, units='deg') + p.set_val('traj.rotate.controls:alpha', 0.0, units='deg') p.set_val('traj.climb.t_initial', 75) p.set_val('traj.climb.t_duration', 15) diff --git a/dymos/test/test_load_case.py b/dymos/test/test_load_case.py index f2d84abd3..5eb7d54e2 100644 --- a/dymos/test/test_load_case.py +++ b/dymos/test/test_load_case.py @@ -51,7 +51,7 @@ def setup_problem(trans=dm.GaussLobatto(num_segments=10), polynomial_control=Fal p['phase0.states:v'] = phase.interp('v', [0, 9.9]) if polynomial_control: - p['phase0.polynomial_controls:theta'] = phase.interp('theta', [5, 100.5]) + p['phase0.controls:theta'] = phase.interp('theta', [5, 100.5]) else: p['phase0.controls:theta'] = phase.interp('theta', [5, 100.5]) @@ -117,8 +117,8 @@ def test_load_case_unchanged_grid_polynomial_control(self): # Run the model to ensure we find the same output values as those that we recorded p.run_model() - assert_near_equal(p.get_val('phase0.polynomial_controls:theta'), - case.get_val('phase0.polynomial_controls:theta')) + assert_near_equal(p.get_val('phase0.controls:theta'), + case.get_val('phase0.controls:theta')) def test_load_case_lgl_to_radau(self): import openmdao.api as om @@ -249,7 +249,7 @@ def test_load_case_warn_fix_final_polynomial_control(self): fix_final_state=False, fix_final_control=True) # Load the values from the previous solution - msg = f"phase0.polynomial_controls:theta specifies 'fix_final=True'. If the given restart file has a" \ + msg = f"phase0.controls:theta specifies 'fix_final=True'. If the given restart file has a" \ f" different final value this will overwrite the user-specified value" with assert_warning(UserWarning, msg): diff --git a/dymos/trajectory/test/test_trajectory.py b/dymos/trajectory/test/test_trajectory.py index c65d9080a..4ead93a52 100644 --- a/dymos/trajectory/test/test_trajectory.py +++ b/dymos/trajectory/test/test_trajectory.py @@ -536,7 +536,7 @@ def test_linked_control_to_polynomial_control(self): p.set_val('burn2.states:vt', val=burn2.interp('vt', [1, np.sqrt(1 / 3)])) p.set_val('burn2.states:accel', val=burn2.interp('accel', [0.1, 0])) p.set_val('burn2.states:deltav', val=burn2.interp('deltav', [0.1, 0.2])) - p.set_val('burn2.polynomial_controls:u1', val=burn2.interp('u1', [1, 1])) + p.set_val('burn2.controls:u1', val=burn2.interp('u1', [1, 1])) p.set_val('burn2.parameters:c', val=1.5) p.run_model() @@ -939,7 +939,7 @@ def test_linked_polynomial_control_rate(self): p.set_val('burn2.states:vt', val=burn2.interp('vt', [1, np.sqrt(1 / 3)])) p.set_val('burn2.states:accel', val=burn2.interp('accel', [0.1, 0])) p.set_val('burn2.states:deltav', val=burn2.interp('deltav', [0.1, 0.2])) - p.set_val('burn2.polynomial_controls:u1', val=burn2.interp('u1', [1, 1])) + p.set_val('burn2.controls:u1', val=burn2.interp('u1', [1, 1])) p.set_val('burn2.parameters:c', val=1.5) p.run_model() @@ -1073,7 +1073,7 @@ def test_linked_polynomial_control_rate2(self): p.set_val('burn2.states:vt', val=burn2.interp('vt', [1, np.sqrt(1 / 3)])) p.set_val('burn2.states:accel', val=burn2.interp('accel', [0.1, 0])) p.set_val('burn2.states:deltav', val=burn2.interp('deltav', [0.1, 0.2])) - p.set_val('burn2.polynomial_controls:u1', val=burn2.interp('u1', [1, 1])) + p.set_val('burn2.controls:u1', val=burn2.interp('u1', [1, 1])) p.set_val('burn2.parameters:c', val=1.5) p.run_model() diff --git a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py index 706dfeace..a8c2726d0 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py @@ -359,7 +359,7 @@ def test_brachistochrone_explicit_shooting_path_constraint_polynomial_control(se prob.set_val('traj0.phase0.initial_states:y', 10.0) prob.set_val('traj0.phase0.initial_states:v', 1.0E-6) prob.set_val('traj0.phase0.parameters:g', 9.80665, units='m/s**2') - prob.set_val('traj0.phase0.polynomial_controls:theta', + prob.set_val('traj0.phase0.controls:theta', phase.interp('theta', ys=[0.01, 50]), units='deg') dm.run_problem(prob) diff --git a/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py b/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py index 5c960fed6..9bebbf6d7 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py +++ b/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py @@ -262,7 +262,7 @@ def test_integrate_with_polynomial_controls(self): p.set_val('integrator.t_duration', 1.8016) p.set_val('integrator.parameters:g', 9.80665) - p.set_val('integrator.polynomial_controls:theta', + p.set_val('integrator.controls:theta', np.linspace(0.01, 100.0, poly_control_options['theta']['order']+1), units='deg') diff --git a/dymos/visualization/linkage/report.py b/dymos/visualization/linkage/report.py index 191330f7f..573f1f926 100644 --- a/dymos/visualization/linkage/report.py +++ b/dymos/visualization/linkage/report.py @@ -100,8 +100,6 @@ def _is_fixed(var_name: str, class_name: str, phase, loc: str): fixed = phase.is_state_fixed(var_name, loc) elif class_name in {'input_control', 'indep_control'}: fixed = phase.is_control_fixed(var_name, loc) - elif class_name in {'input_polynomial_control', 'indep_polynomial_control'}: - fixed = phase.is_polynomial_control_fixed(var_name, loc) else: fixed = True @@ -213,11 +211,6 @@ def _trajectory_to_dict(traj): for loc, child in condition_children.items(): child[control_name] = _tree_var(control_name, phase, loc, 'controls:') - # Polynomial Controls - for pc_name in phase.polynomial_control_options: - for loc, child in condition_children.items(): - child[pc_name] = _tree_var(pc_name, phase, loc, 'controls:') - # Parameters for param_name, param in phase.parameter_options.items(): params_children[param_name] = _tree_var(param_name, phase) diff --git a/dymos/visualization/linkage/test/linkage_report_ui_test.py b/dymos/visualization/linkage/test/linkage_report_ui_test.py index 21431488b..1dd2805d3 100644 --- a/dymos/visualization/linkage/test/linkage_report_ui_test.py +++ b/dymos/visualization/linkage/test/linkage_report_ui_test.py @@ -444,7 +444,7 @@ def setUp(self): p.set_val('traj.rotate.t_duration', 5) p.set_val('traj.rotate.states:r', rotate.interp('r', [1750, 1800.0])) p.set_val('traj.rotate.states:v', rotate.interp('v', [80, 85.0])) - p.set_val('traj.rotate.polynomial_controls:alpha', 0.0, units='deg') + p.set_val('traj.rotate.controls:alpha', 0.0, units='deg') p.set_val('traj.climb.t_initial', 30) p.set_val('traj.climb.t_duration', 20) diff --git a/dymos/visualization/linkage/test/test_linkage_report.py b/dymos/visualization/linkage/test/test_linkage_report.py index 467af31cf..4aa85ced8 100644 --- a/dymos/visualization/linkage/test/test_linkage_report.py +++ b/dymos/visualization/linkage/test/test_linkage_report.py @@ -222,7 +222,7 @@ def test_model_data(self): p.set_val('traj.rotate.t_duration', 5) p.set_val('traj.rotate.states:r', rotate.interp('r', [1750, 1800.0])) p.set_val('traj.rotate.states:v', rotate.interp('v', [80, 85.0])) - p.set_val('traj.rotate.polynomial_controls:alpha', 0.0, units='deg') + p.set_val('traj.rotate.controls:alpha', 0.0, units='deg') p.set_val('traj.climb.t_initial', 30) p.set_val('traj.climb.t_duration', 20) From 1eadb4f564a1d38485aa736cffd0db37ee315808 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Fri, 14 Jun 2024 16:53:05 -0500 Subject: [PATCH 09/17] WIP --- dymos/phase/test/test_interp.py | 2 +- dymos/transcriptions/common/control_group.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dymos/phase/test/test_interp.py b/dymos/phase/test/test_interp.py index ba13daf32..ae64520d5 100644 --- a/dymos/phase/test/test_interp.py +++ b/dymos/phase/test/test_interp.py @@ -69,7 +69,7 @@ def test_invalid_var(self): phase.interp('x', [0, 100]) expected = 'Could not find a state, control, or polynomial control named x to be ' \ - 'interpolated.\nPlease explicitly specified the node subset onto which this ' \ + 'interpolated.\nPlease explicitly specify the node subset onto which this ' \ 'value should be interpolated.' self.assertEqual(str(e.exception), expected) diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index a25894b4b..af66b850c 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -93,6 +93,8 @@ def _configure_controls(self): time_units = self.options['time_units'] for name, options in control_options.items(): + if 'control_type' not in options: + options['control_type'] = 'full' if options['control_type'] == 'polynomial': disc_nodes, _ = lgl(options['order'] + 1) num_control_input_nodes = len(disc_nodes) From 956052e6f8579b62c9c8f66b34422bfa7ed377f9 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Mon, 17 Jun 2024 17:12:52 -0500 Subject: [PATCH 10/17] More tests passing for controls rework --- dymos/transcriptions/common/control_group.py | 6 +- .../explicit_shooting/ode_integration_comp.py | 86 ++---- .../test/test_explicit_shooting.py | 3 +- .../test/test_ode_evaluation_group.py | 2 - .../test/test_ode_integration_comp.py | 16 +- .../vandermonde_control_interp_comp.py | 262 +++++++++--------- 6 files changed, 171 insertions(+), 204 deletions(-) diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index af66b850c..aff0432c6 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -89,7 +89,6 @@ def _configure_controls(self): eval_nodes = ogd.node_ptau control_options = self.options['control_options'] num_output_nodes = ogd.num_nodes - num_control_input_nodes = gd.subset_num_nodes['control_input'] time_units = self.options['time_units'] for name, options in control_options.items(): @@ -178,6 +177,7 @@ def _configure_controls(self): self._output_rate_names[name] = f'control_rates:{name}_rate' self._output_rate2_names[name] = f'control_rates:{name}_rate2' shape = options['shape'] + num_control_input_nodes = gd.subset_num_nodes['control_input'] input_shape = (num_control_input_nodes,) + shape output_shape = (num_output_nodes,) + shape @@ -375,8 +375,6 @@ def compute_partials(self, inputs, partials): Subjac components written to partials[output_name, input_name]. """ control_options = self.options['control_options'] - num_input_nodes = self.options['grid_data'].subset_num_nodes['control_input'] - nn = self.options['output_grid_data'].num_nodes dstau_dt = np.reciprocal(inputs['dt_dstau']) dstau_dt2 = (dstau_dt ** 2)[:, np.newaxis] @@ -390,6 +388,7 @@ def compute_partials(self, inputs, partials): control_name = self._input_names[name] num_input_nodes = options['order'] + 1 L_de, D_de, D2_de = self._matrices[name] + nn = self.options['output_grid_data'].num_nodes size = self.sizes[name] rate_name = self._output_rate_names[name] @@ -421,6 +420,7 @@ def compute_partials(self, inputs, partials): size = self.sizes[name] rate_name = self._output_rate_names[name] rate2_name = self._output_rate2_names[name] + num_input_nodes = self.options['grid_data'].subset_num_nodes['control_input'] # Unroll shaped controls into an array at each node u_flat = np.reshape(inputs[control_name], (num_input_nodes, size)) diff --git a/dymos/transcriptions/explicit_shooting/ode_integration_comp.py b/dymos/transcriptions/explicit_shooting/ode_integration_comp.py index 1d1e5030a..7f3984d04 100644 --- a/dymos/transcriptions/explicit_shooting/ode_integration_comp.py +++ b/dymos/transcriptions/explicit_shooting/ode_integration_comp.py @@ -27,8 +27,6 @@ class ODEIntegrationComp(om.ExplicitComponent): For each parameter, a dictionary of its options, keyed by name. control_options : dict of {str: OptionsDictionary} For each control variable, a dictionary of its options, keyed by name. - polynomial_control_options : dict of {str: OptionsDictionary} - For each polynomial variable, a dictionary of its options, keyed by name. output_grid_data : GridData The GridData which defines the nodes at which the outputs of the integration are provided, or None if the input_grid_data is to be used. @@ -47,13 +45,12 @@ class ODEIntegrationComp(om.ExplicitComponent): theta: U+03B8 """ def __init__(self, input_grid_data, time_options, state_options, parameter_options=None, control_options=None, - polynomial_control_options=None, output_grid_data=None, reports=False, standalone_mode=True, **kwargs): + output_grid_data=None, reports=False, standalone_mode=True, **kwargs): super().__init__(**kwargs) self.time_options = time_options self.state_options = state_options self.parameter_options = parameter_options or {} self.control_options = control_options or {} - self.polynomial_control_options = polynomial_control_options or {} self._eval_subprob = None self._input_grid_data = input_grid_data self._output_grid_data = output_grid_data if output_grid_data is not None else input_grid_data @@ -213,10 +210,6 @@ def _configure_states(self): self.declare_partials(of=self._state_output_names[state_name], wrt=f'controls:{control_name_wrt}') - for control_name_wrt in self.polynomial_control_options: - self.declare_partials(of=self._state_output_names[state_name], - wrt=f'controls:{control_name_wrt}') - def _setup_parameters(self): if self._standalone_mode: self._configure_parameters() @@ -312,19 +305,18 @@ def _build_dx_dz_idxs(self): # Column indices wrt the controls for control, control_options in self.control_options.items(): - input_name = self._control_input_names[control] - input_size = np.prod(control_options['shape']) * self._input_grid_data.subset_num_nodes['control_input'] - idxs = np.s_[:, dx_dz_idx: dx_dz_idx + output_state_size * input_size] - self._partial_dx_dz_idxs[output_name, input_name] = idxs - dx_dz_idx += output_state_size * input_size - - # Column indices wrt the polynomial controls - for pc, pc_options in self.polynomial_control_options.items(): - input_name = self._polynomial_control_input_names[pc] - input_size = np.prod(pc_options['shape']) * (pc_options['order'] + 1) - idxs = np.s_[:, dx_dz_idx: dx_dz_idx + output_state_size * input_size] - self._partial_dx_dz_idxs[output_name, input_name] = idxs - dx_dz_idx += output_state_size * input_size + if control_options['control_type'] == 'polynomial': + input_name = self._control_input_names[control] + input_size = np.prod(control_options['shape']) * (control_options['order'] + 1) + idxs = np.s_[:, dx_dz_idx: dx_dz_idx + output_state_size * input_size] + self._partial_dx_dz_idxs[output_name, input_name] = idxs + dx_dz_idx += output_state_size * input_size + else: + input_name = self._control_input_names[control] + input_size = np.prod(control_options['shape']) * self._input_grid_data.subset_num_nodes['control_input'] + idxs = np.s_[:, dx_dz_idx: dx_dz_idx + output_state_size * input_size] + self._partial_dx_dz_idxs[output_name, input_name] = idxs + dx_dz_idx += output_state_size * input_size def _setup_storage(self): if self._standalone_mode: @@ -367,24 +359,23 @@ def _configure_storage(self): start_theta += param_size for control_name, options in self.control_options.items(): - control_param_shape = (len(control_input_node_ptau),) + options['shape'] - control_param_size = np.prod(control_param_shape, dtype=int) - self._control_idxs_in_z[control_name] = np.s_[start_z:start_z + control_param_size] - self._control_idxs_in_theta[control_name] = np.s_[start_theta:start_theta+control_param_size] - z_input_names.extend([self._control_input_names[control_name]] * control_param_size) - start_z += control_param_size - start_theta += control_param_size - - for pc_name, options in self.polynomial_control_options.items(): - num_input_nodes = options['order'] + 1 - control_param_shape = (num_input_nodes,) + options['shape'] - control_param_size = np.prod(control_param_shape, dtype=int) - self._polynomial_control_idxs_in_z[pc_name] = np.s_[start_z:start_z + control_param_size] - self._polynomial_control_idxs_in_theta[pc_name] = np.s_[start_theta:start_theta+control_param_size] - z_input_names.extend([self._polynomial_control_input_names[pc_name]] * control_param_size) - - start_z += control_param_size - start_theta += control_param_size + if options['control_type'] == 'polynomial': + num_input_nodes = options['order'] + 1 + control_param_shape = (num_input_nodes,) + options['shape'] + control_param_size = np.prod(control_param_shape, dtype=int) + self._control_idxs_in_z[control_name] = np.s_[start_z:start_z + control_param_size] + self._control_idxs_in_theta[control_name] = np.s_[start_theta:start_theta+control_param_size] + z_input_names.extend([self._control_input_names[control_name]] * control_param_size) + start_z += control_param_size + start_theta += control_param_size + else: + control_param_shape = (len(control_input_node_ptau),) + options['shape'] + control_param_size = np.prod(control_param_shape, dtype=int) + self._control_idxs_in_z[control_name] = np.s_[start_z:start_z + control_param_size] + self._control_idxs_in_theta[control_name] = np.s_[start_theta:start_theta+control_param_size] + z_input_names.extend([self._control_input_names[control_name]] * control_param_size) + start_z += control_param_size + start_theta += control_param_size # Allocate caches to store integrated quantities so we don't have to integrate the same inputs twice # if partials are requested for the same inputs as a compute. @@ -483,10 +474,6 @@ def _subprob_run_model(self, x, t, theta, linearize=True): input_name = self._control_input_names[name] subprob.set_val(input_name, theta[self._control_idxs_in_theta[name]]) - for name in self.polynomial_control_options: - input_name = self._polynomial_control_input_names[name] - subprob.set_val(input_name, theta[self._polynomial_control_idxs_in_theta[name]]) - # Re-run in case the inputs have changed. subprob.run_model() @@ -576,11 +563,6 @@ def eval_ode(self, x, t, theta, eval_solution=True, eval_derivs=True): px_pu = totals[of_name, self._control_input_names[control_name_wrt]] f_theta[idxs, idxs_wrt] = px_pu.ravel() - for pc_name_wrt in self.polynomial_control_options: - idxs_wrt = self._polynomial_control_idxs_in_theta[pc_name_wrt] - px_ppc = totals[of_name, self._polynomial_control_input_names[pc_name_wrt]] - f_theta[idxs, idxs_wrt] = px_ppc.ravel() - else: f_x = f_t = f_theta = None @@ -738,10 +720,6 @@ def _propagate(self, inputs, propagate_derivs=None, x_out=None, t_out=None, dx_d control_vals = inputs[self._control_input_names[control_name]] theta[self._control_idxs_in_theta[control_name]] = control_vals.ravel() - for pc_name in self.polynomial_control_options: - pc_vals = inputs[self._polynomial_control_input_names[pc_name]] - theta[self._polynomial_control_idxs_in_theta[pc_name]] = pc_vals.ravel() - if _propagate_derivs: if dx_dz_out is None: dx_dz_out = np.zeros((nn, self.x_size * self.z_size)) @@ -861,7 +839,3 @@ def compute_partials(self, inputs, partials): for wrt_control_name in self.control_options: wrt = self._control_input_names[wrt_control_name] partials[of, wrt] = dx_dz[self._partial_dx_dz_idxs[of, wrt]] - - for wrt_pc_name in self.polynomial_control_options: - wrt = self._polynomial_control_input_names[wrt_pc_name] - partials[of, wrt] = dx_dz[self._partial_dx_dz_idxs[of, wrt]] diff --git a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py index a8c2726d0..0c6f1233c 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py @@ -314,6 +314,7 @@ def test_brachistochrone_explicit_shooting_path_constraint_polynomial_control(se for path_rename in (True, False): with self.subTest(f'output_grid = {output_grid_type} compressed = {compressed} ' f'rename_path_const = {path_rename}'): + print(f'output_grid = {output_grid_type} compressed = {compressed} ') prob = om.Problem() input_grid = dm.GaussLobattoGrid(num_segments=3, nodes_per_seg=3, compressed=compressed) @@ -650,4 +651,4 @@ def test_brachistochrone_static_gravity_explicit_shooting(self): # dm.run_problem(p, simulate=False) - assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) + assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) \ No newline at end of file diff --git a/dymos/transcriptions/explicit_shooting/test/test_ode_evaluation_group.py b/dymos/transcriptions/explicit_shooting/test/test_ode_evaluation_group.py index 3f9e65b54..3afdc9444 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_ode_evaluation_group.py +++ b/dymos/transcriptions/explicit_shooting/test/test_ode_evaluation_group.py @@ -31,7 +31,6 @@ def test_eval(self): param_options['p']['targets'] = ['p'] control_options = {} - polynomial_control_options = {} p = om.Problem() @@ -43,7 +42,6 @@ def test_eval(self): state_options=state_options, parameter_options=param_options, control_options=control_options, - polynomial_control_options=polynomial_control_options, ode_init_kwargs=None)) p.setup(force_alloc_complex=True) diff --git a/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py b/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py index 9bebbf6d7..205748460 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py +++ b/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py @@ -232,14 +232,13 @@ def test_integrate_with_polynomial_controls(self): param_options['g']['units'] = 'm/s**2' param_options['g']['targets'] = ['g'] - poly_control_options = {'theta': dm.phase.options.PolynomialControlOptionsDictionary()} - - poly_control_options['theta']['shape'] = (1,) - poly_control_options['theta']['order'] = 2 - poly_control_options['theta']['units'] = 'rad' - poly_control_options['theta']['targets'] = ['theta'] + control_options = {'theta': dm.phase.options.ControlOptionsDictionary()} - control_options = {} + control_options['theta']['control_type'] = 'polynomial' + control_options['theta']['shape'] = (1,) + control_options['theta']['order'] = 2 + control_options['theta']['units'] = 'rad' + control_options['theta']['targets'] = ['theta'] p = om.Problem() @@ -249,7 +248,6 @@ def test_integrate_with_polynomial_controls(self): state_options=state_options, parameter_options=param_options, control_options=control_options, - polynomial_control_options=poly_control_options, input_grid_data=gd, ode_init_kwargs=None)) @@ -263,7 +261,7 @@ def test_integrate_with_polynomial_controls(self): p.set_val('integrator.parameters:g', 9.80665) p.set_val('integrator.controls:theta', - np.linspace(0.01, 100.0, poly_control_options['theta']['order']+1), + np.linspace(0.01, 100.0, control_options['theta']['order']+1), units='deg') p.run_model() diff --git a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py index 5af2652d0..c964d2373 100644 --- a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py @@ -125,66 +125,62 @@ def _configure_controls(self): num_uhat_nodes = gd.subset_num_nodes['control_input'] ar = np.arange(vec_size, dtype=int) for control_name, options in self._control_options.items(): - shape = options['shape'] - units = options['units'] - input_name = f'controls:{control_name}' - output_name = f'control_values:{control_name}' - rate_name = f'control_rates:{control_name}_rate' - rate2_name = f'control_rates:{control_name}_rate2' - rate_units = get_rate_units(units, self._time_units) - rate2_units = get_rate_units(units, self._time_units, deriv=2) - uhat_shape = (num_uhat_nodes,) + shape - output_shape = (vec_size,) + shape - self.add_input(input_name, shape=uhat_shape, units=units) - self.add_output(output_name, shape=output_shape, units=units) - self.add_output(rate_name, shape=output_shape, units=rate_units) - self.add_output(rate2_name, shape=output_shape, units=rate2_units) - self._control_io_names[control_name] = (input_name, output_name, rate_name, rate2_name) - self.declare_partials(of=output_name, wrt=input_name) - self.declare_partials(of=output_name, wrt='stau', rows=ar, cols=ar) - self.declare_partials(of=rate_name, wrt=input_name) - self.declare_partials(of=rate_name, wrt='stau', rows=ar, cols=ar) - self.declare_partials(of=rate_name, wrt='dstau_dt') - self.declare_partials(of=rate2_name, wrt=input_name) - self.declare_partials(of=rate2_name, wrt='stau', rows=ar, cols=ar) - self.declare_partials(of=rate2_name, wrt='dstau_dt') - - def _configure_polynomial_controls(self): - vec_size = self.options['vec_size'] - ar = np.arange(vec_size, dtype=int) - - for pc_name, options in self._polynomial_control_options.items(): - order = options['order'] - shape = options['shape'] - units = options['units'] - input_name = f'controls:{pc_name}' - output_name = f'control_values:{pc_name}' - rate_name = f'control_rates:{pc_name}_rate' - rate2_name = f'control_rates:{pc_name}_rate2' - rate_units = get_rate_units(units, self._time_units) - rate2_units = get_rate_units(units, self._time_units, deriv=2) - input_shape = (order + 1,) + shape - output_shape = (vec_size,) + shape - self.add_input(input_name, shape=input_shape, units=units) - self.add_output(output_name, shape=output_shape, units=units) - self.add_output(rate_name, shape=output_shape, units=rate_units) - self.add_output(rate2_name, shape=output_shape, units=rate2_units) - self._control_io_names[pc_name] = (input_name, output_name, rate_name, rate2_name) - self.declare_partials(of=output_name, wrt=input_name) - self.declare_partials(of=output_name, wrt='ptau', rows=ar, cols=ar) - self.declare_partials(of=rate_name, wrt=input_name) - self.declare_partials(of=rate_name, wrt='ptau', rows=ar, cols=ar) - self.declare_partials(of=rate_name, wrt='t_duration') - self.declare_partials(of=rate2_name, wrt=input_name) - self.declare_partials(of=rate2_name, wrt='ptau', rows=ar, cols=ar) - self.declare_partials(of=rate2_name, wrt='t_duration') - - if order not in self._V_hat: - pc_disc_seg_ptau, _ = lgl(order + 1) - self._V_hat[order] = np.vander(pc_disc_seg_ptau, increasing=True) - self._V_hat_inv[order] = np.linalg.inv(self._V_hat[order]) - if order + 1 not in self._fac: - self._fac[order + 1] = np.arange(order + 1, dtype=int) + if options['control_type'] == 'full': + shape = options['shape'] + units = options['units'] + input_name = f'controls:{control_name}' + output_name = f'control_values:{control_name}' + rate_name = f'control_rates:{control_name}_rate' + rate2_name = f'control_rates:{control_name}_rate2' + rate_units = get_rate_units(units, self._time_units) + rate2_units = get_rate_units(units, self._time_units, deriv=2) + uhat_shape = (num_uhat_nodes,) + shape + output_shape = (vec_size,) + shape + self.add_input(input_name, shape=uhat_shape, units=units) + self.add_output(output_name, shape=output_shape, units=units) + self.add_output(rate_name, shape=output_shape, units=rate_units) + self.add_output(rate2_name, shape=output_shape, units=rate2_units) + self._control_io_names[control_name] = (input_name, output_name, rate_name, rate2_name) + self.declare_partials(of=output_name, wrt=input_name) + self.declare_partials(of=output_name, wrt='stau', rows=ar, cols=ar) + self.declare_partials(of=rate_name, wrt=input_name) + self.declare_partials(of=rate_name, wrt='stau', rows=ar, cols=ar) + self.declare_partials(of=rate_name, wrt='dstau_dt') + self.declare_partials(of=rate2_name, wrt=input_name) + self.declare_partials(of=rate2_name, wrt='stau', rows=ar, cols=ar) + self.declare_partials(of=rate2_name, wrt='dstau_dt') + else: + order = options['order'] + shape = options['shape'] + units = options['units'] + input_name = f'controls:{control_name}' + output_name = f'control_values:{control_name}' + rate_name = f'control_rates:{control_name}_rate' + rate2_name = f'control_rates:{control_name}_rate2' + rate_units = get_rate_units(units, self._time_units) + rate2_units = get_rate_units(units, self._time_units, deriv=2) + input_shape = (order + 1,) + shape + output_shape = (vec_size,) + shape + self.add_input(input_name, shape=input_shape, units=units) + self.add_output(output_name, shape=output_shape, units=units) + self.add_output(rate_name, shape=output_shape, units=rate_units) + self.add_output(rate2_name, shape=output_shape, units=rate2_units) + self._control_io_names[control_name] = (input_name, output_name, rate_name, rate2_name) + self.declare_partials(of=output_name, wrt=input_name) + self.declare_partials(of=output_name, wrt='ptau', rows=ar, cols=ar) + self.declare_partials(of=rate_name, wrt=input_name) + self.declare_partials(of=rate_name, wrt='ptau', rows=ar, cols=ar) + self.declare_partials(of=rate_name, wrt='t_duration') + self.declare_partials(of=rate2_name, wrt=input_name) + self.declare_partials(of=rate2_name, wrt='ptau', rows=ar, cols=ar) + self.declare_partials(of=rate2_name, wrt='t_duration') + + if order not in self._V_hat: + pc_disc_seg_ptau, _ = lgl(order + 1) + self._V_hat[order] = np.vander(pc_disc_seg_ptau, increasing=True) + self._V_hat_inv[order] = np.linalg.inv(self._V_hat[order]) + if order + 1 not in self._fac: + self._fac[order + 1] = np.arange(order + 1, dtype=int) def setup(self): """ @@ -222,7 +218,6 @@ def configure_io(self): self.add_input('ptau', shape=(vec_size,), units=None) self._configure_controls() - self._configure_polynomial_controls() def _dvander(self, v): """ @@ -298,22 +293,22 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): input_node_idxs[0]:input_node_idxs[0] + len(input_node_idxs)] for control_name, options in self._control_options.items(): - input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] - u_hat = np.dot(L_seg, inputs[input_name][input_node_idxs]) - a = np.atleast_2d(self._V_hat_inv[seg_order] @ u_hat) - outputs[output_name] = V_stau @ a - outputs[rate_name] = dstau_dt * (dV_stau @ a) - outputs[rate2_name] = dstau_dt**2 * (dV2_stau @ a) - - for pc_name, options in self._polynomial_control_options.items(): - input_name, output_name, rate_name, rate2_name = self._control_io_names[pc_name] - order = options['order'] - V_ptau = np.vander(ptau, N=order+1, increasing=True) - dV_ptau, dV2_ptau, _ = self._dvander(V_ptau) - a = np.atleast_2d(self._V_hat_inv[order] @ inputs[input_name]) - outputs[output_name] = V_ptau @ a - outputs[rate_name] = dptau_dt * (dV_ptau @ a) - outputs[rate2_name] = dptau_dt**2 * (dV2_ptau @ a) + if options['control_type'] == 'full': + input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] + u_hat = np.dot(L_seg, inputs[input_name][input_node_idxs]) + a = np.atleast_2d(self._V_hat_inv[seg_order] @ u_hat) + outputs[output_name] = V_stau @ a + outputs[rate_name] = dstau_dt * (dV_stau @ a) + outputs[rate2_name] = dstau_dt**2 * (dV2_stau @ a) + else: + input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] + order = options['order'] + V_ptau = np.vander(ptau, N=order+1, increasing=True) + dV_ptau, dV2_ptau, _ = self._dvander(V_ptau) + a = np.atleast_2d(self._V_hat_inv[order] @ inputs[input_name]) + outputs[output_name] = V_ptau @ a + outputs[rate_name] = dptau_dt * (dV_ptau @ a) + outputs[rate2_name] = dptau_dt**2 * (dV2_ptau @ a) def compute_partials(self, inputs, partials, discrete_inputs=None): """ @@ -351,59 +346,60 @@ def compute_partials(self, inputs, partials, discrete_inputs=None): input_node_idxs[0]:input_node_idxs[0] + len(input_node_idxs)] for control_name, options in self._control_options.items(): - input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] - - u_hat = np.dot(L_seg, inputs[input_name][input_node_idxs].real) - a = self._V_hat_inv[seg_order] @ u_hat - - da_duhat = self._V_hat_inv[seg_order] @ L_seg - dV_a = dV_stau @ a - dV2_a = dV2_stau @ a - dV3_a = dV3_stau @ a - - partials[output_name, input_name][...] = 0.0 - partials[output_name, input_name][..., u_idxs] = V_stau @ da_duhat - partials[output_name, 'stau'] = dV_a.ravel() - - pudot_pa = dstau_dt * dV_stau - pa_puhat = self._V_hat_inv[seg_order] - partials[rate_name, input_name][...] = 0.0 - partials[rate_name, input_name][..., u_idxs] = pudot_pa @ pa_puhat - partials[rate_name, 'dstau_dt'][...] = dV_a - partials[rate_name, 'stau'][...] = dV2_a.ravel() - - pu2dot_pa = dstau_dt**2 * dV2_stau - partials[rate2_name, input_name][...] = 0.0 - partials[rate2_name, input_name][..., u_idxs] = pu2dot_pa @ pa_puhat - partials[rate2_name, 'dstau_dt'][...] = 2 * dstau_dt * dV2_a - partials[rate2_name, 'stau'][...] = dV3_a.ravel() - - for pc_name, options in self._polynomial_control_options.items(): - input_name, output_name, rate_name, rate2_name = self._control_io_names[pc_name] - order = options['order'] - - V_ptau = np.vander(ptau, N=order+1, increasing=True) - dV_ptau, dV2_ptau, dV3_ptau = self._dvander(V_ptau) - - u_hat = inputs[input_name].real - a = self._V_hat_inv[order] @ u_hat - - dV_a = dV_ptau @ a - dV2_a = dV2_ptau @ a - dV3_a = dV3_ptau @ a - - da_duhat = self._V_hat_inv[order] - - partials[output_name, input_name][...] = V_ptau @ da_duhat - partials[output_name, 'ptau'][...] = dV_a.ravel() - - pudot_pa = dptau_dt * dV_ptau - pa_puhat = self._V_hat_inv[order] - partials[rate_name, input_name][...] = pudot_pa @ pa_puhat - partials[rate_name, 't_duration'][...] = ddptau_dt_dtduration * dV_a - partials[rate_name, 'ptau'][...] = dptau_dt * dV2_a.ravel() - - pu2dot_pa = dptau_dt ** 2 * dV2_ptau - partials[rate2_name, input_name][...] = pu2dot_pa @ pa_puhat - partials[rate2_name, 't_duration'][...] = 2 * dptau_dt * ddptau_dt_dtduration * dV2_a - partials[rate2_name, 'ptau'][...] = dptau_dt**2 * dV3_a.ravel() + if options['control_type'] == 'full': + input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] + + u_hat = np.dot(L_seg, inputs[input_name][input_node_idxs].real) + a = self._V_hat_inv[seg_order] @ u_hat + + da_duhat = self._V_hat_inv[seg_order] @ L_seg + dV_a = dV_stau @ a + dV2_a = dV2_stau @ a + dV3_a = dV3_stau @ a + + partials[output_name, input_name][...] = 0.0 + partials[output_name, input_name][..., u_idxs] = V_stau @ da_duhat + partials[output_name, 'stau'] = dV_a.ravel() + + pudot_pa = dstau_dt * dV_stau + pa_puhat = self._V_hat_inv[seg_order] + partials[rate_name, input_name][...] = 0.0 + partials[rate_name, input_name][..., u_idxs] = pudot_pa @ pa_puhat + partials[rate_name, 'dstau_dt'][...] = dV_a + partials[rate_name, 'stau'][...] = dV2_a.ravel() + + pu2dot_pa = dstau_dt**2 * dV2_stau + partials[rate2_name, input_name][...] = 0.0 + partials[rate2_name, input_name][..., u_idxs] = pu2dot_pa @ pa_puhat + partials[rate2_name, 'dstau_dt'][...] = 2 * dstau_dt * dV2_a + partials[rate2_name, 'stau'][...] = dV3_a.ravel() + + else: + input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] + order = options['order'] + + V_ptau = np.vander(ptau, N=order+1, increasing=True) + dV_ptau, dV2_ptau, dV3_ptau = self._dvander(V_ptau) + + u_hat = inputs[input_name].real + a = self._V_hat_inv[order] @ u_hat + + dV_a = dV_ptau @ a + dV2_a = dV2_ptau @ a + dV3_a = dV3_ptau @ a + + da_duhat = self._V_hat_inv[order] + + partials[output_name, input_name][...] = V_ptau @ da_duhat + partials[output_name, 'ptau'][...] = dV_a.ravel() + + pudot_pa = dptau_dt * dV_ptau + pa_puhat = self._V_hat_inv[order] + partials[rate_name, input_name][...] = pudot_pa @ pa_puhat + partials[rate_name, 't_duration'][...] = ddptau_dt_dtduration * dV_a + partials[rate_name, 'ptau'][...] = dptau_dt * dV2_a.ravel() + + pu2dot_pa = dptau_dt ** 2 * dV2_ptau + partials[rate2_name, input_name][...] = pu2dot_pa @ pa_puhat + partials[rate2_name, 't_duration'][...] = 2 * dptau_dt * ddptau_dt_dtduration * dV2_a + partials[rate2_name, 'ptau'][...] = dptau_dt**2 * dV3_a.ravel() From aef4d866ecf89d6d23e48c6e643e63a37261680f Mon Sep 17 00:00:00 2001 From: johnjasa Date: Tue, 18 Jun 2024 00:17:28 -0500 Subject: [PATCH 11/17] WIP; fixing tests --- .../barycentric_control_interp_comp.py | 215 ++++++------------ .../vandermonde_control_interp_comp.py | 3 +- .../ode_integration_interface_system.py | 22 +- .../odeint_control_interpolation_comp.py | 29 --- .../components/segment_simulation_comp.py | 70 +++--- 5 files changed, 104 insertions(+), 235 deletions(-) diff --git a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py index 535b23eb2..7bb868929 100644 --- a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py @@ -513,113 +513,99 @@ def _compute_partials_controls(self, inputs, partials, discrete_inputs=None): L_id = self._L_id['controls'] L_seg = L_id[disc_node_idxs[0]:disc_node_idxs[0] + len(disc_node_idxs), input_node_idxs[0]:input_node_idxs[0] + len(input_node_idxs)] + + ptau = inputs['ptau'] + t_duration = inputs['t_duration'] + dptau_dt = 2.0 / t_duration - for control_name in self._control_options: - input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] - - # Translate the input nodes to the discretization nodes. - u_hat = np.dot(L_seg, inputs[input_name][input_node_idxs]) - - # Perform a row_wise multiplication of w_b and u_hat - wbuhat = np.einsum("ij,i...->i...", w_b, u_hat) - - # outputs[output_name] = np.einsum('i...,i...->...', l, wbuhat) - partials[output_name, 'stau'] = dl_dstau.T @ wbuhat + for control_name, options in enumerate(self._control_options): + if options['control_type'] == 'full': + input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] - partials[rate_name, 'stau'] = d2l_dstau2.T @ wbuhat * dstau_dt - partials[rate_name, 'dstau_dt'] = partials[output_name, 'stau'] + # Translate the input nodes to the discretization nodes. + u_hat = np.dot(L_seg, inputs[input_name][input_node_idxs]) - if self._compute_derivs: - partials[rate2_name, 'stau'] = d3l_dstau3.T @ wbuhat * dstau_dt ** 2 - partials[rate2_name, 'dstau_dt'] = 2 * wbuhat.T @ d2l_dstau2 * dstau_dt + # Perform a row_wise multiplication of w_b and u_hat + wbuhat = np.einsum("ij,i...->i...", w_b, u_hat) - # Assign only thos jacobian columns due to the current segment, since - # other segments cannot impact interpolation in this one. - partials[output_name, input_name] = 0.0 - partials[output_name, input_name][..., input_node_idxs] = \ - ((l * w_b.T) @ L_seg) + # outputs[output_name] = np.einsum('i...,i...->...', l, wbuhat) + partials[output_name, 'stau'] = dl_dstau.T @ wbuhat - partials[rate_name, input_name] = 0.0 - partials[rate_name, input_name][..., input_node_idxs] = \ - (dl_dstau * w_b * dstau_dt).T @ L_seg + partials[rate_name, 'stau'] = d2l_dstau2.T @ wbuhat * dstau_dt + partials[rate_name, 'dstau_dt'] = partials[output_name, 'stau'] - if self._compute_derivs: - partials[rate2_name, input_name] = 0.0 - partials[rate2_name, input_name][..., input_node_idxs] = \ - (d2l_dstau2 * w_b * dstau_dt ** 2).T @ L_seg + if self._compute_derivs: + partials[rate2_name, 'stau'] = d3l_dstau3.T @ wbuhat * dstau_dt ** 2 + partials[rate2_name, 'dstau_dt'] = 2 * wbuhat.T @ d2l_dstau2 * dstau_dt - def _compute_partials_polynomial_controls(self, inputs, partials, discrete_inputs=None): - """ - Compute partials of interpolated control values and rates for the collocated controls. + # Assign only thos jacobian columns due to the current segment, since + # other segments cannot impact interpolation in this one. + partials[output_name, input_name] = 0.0 + partials[output_name, input_name][..., input_node_idxs] = \ + ((l * w_b.T) @ L_seg) - Parameters - ---------- - inputs : `Vector` - `Vector` containing inputs. - outputs : `Vector` - `Vector` containing outputs. - discrete_inputs : `Vector` - `Vector` containing discrete_inputs. - discrete_outputs : `Vector` - `Vector` containing discrete_outputs. - """ - ptau = inputs['ptau'] - t_duration = inputs['t_duration'] - dptau_dt = 2.0 / t_duration + partials[rate_name, input_name] = 0.0 + partials[rate_name, input_name][..., input_node_idxs] = \ + (dl_dstau * w_b * dstau_dt).T @ L_seg - for pc_name, pc_options in self._polynomial_control_options.items(): - gd = self._grid_data - n = pc_options['order'] + 1 + if self._compute_derivs: + partials[rate2_name, input_name] = 0.0 + partials[rate2_name, input_name][..., input_node_idxs] = \ + (d2l_dstau2 * w_b * dstau_dt ** 2).T @ L_seg + + else: + gd = self._grid_data + n = options['order'] + 1 - taus_seg = self._taus_seg[pc_name] + taus_seg = self._taus_seg[control_name] - # Retrieve the storage vectors that pertain to the collocated controls - l = self._l[pc_name] - dl_dg = self._dl_dg[pc_name] - d2l_dg2 = self._d2l_dg2[pc_name] - d3l_dg3 = self._d3l_dg3[pc_name] - dl_dstau = self._dl_dtau[pc_name] - d2l_dstau2 = self._d2l_dtau2[pc_name] - d3l_dstau3 = self._d3l_dtau3[pc_name] - w_b = self._w_b[pc_name] + # Retrieve the storage vectors that pertain to the collocated controls + l = self._l[control_name] + dl_dg = self._dl_dg[control_name] + d2l_dg2 = self._d2l_dg2[control_name] + d3l_dg3 = self._d3l_dg3[control_name] + dl_dstau = self._dl_dtau[control_name] + d2l_dstau2 = self._d2l_dtau2[control_name] + d3l_dstau3 = self._d3l_dtau3[control_name] + w_b = self._w_b[control_name] - _compute_dl_dg(ptau, taus_seg, l, dl_dg, d2l_dg2, d3l_dg3) + _compute_dl_dg(ptau, taus_seg, l, dl_dg, d2l_dg2, d3l_dg3) - # Equivalent of multiplying dl_dg @ dg_dtau, where dg_dtau is a column vector of n ones. - dl_dstau[...] = np.sum(dl_dg, axis=-1, keepdims=True) - d2l_dstau2[...] = np.sum(np.sum(d2l_dg2, axis=-1), axis=-1, keepdims=True) + # Equivalent of multiplying dl_dg @ dg_dtau, where dg_dtau is a column vector of n ones. + dl_dstau[...] = np.sum(dl_dg, axis=-1, keepdims=True) + d2l_dstau2[...] = np.sum(np.sum(d2l_dg2, axis=-1), axis=-1, keepdims=True) - input_name, output_name, rate_name, rate2_name = self._control_io_names[pc_name] + input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] - # Translate the input nodes to the discretization nodes. - u_hat = inputs[input_name] + # Translate the input nodes to the discretization nodes. + u_hat = inputs[input_name] - # Perform a row_wise multiplication of w_b and u_hat - wbuhat = np.einsum("ij,i...->i...", w_b, u_hat) + # Perform a row_wise multiplication of w_b and u_hat + wbuhat = np.einsum("ij,i...->i...", w_b, u_hat) - # outputs[output_name] = np.einsum('i...,i...->...', l, wbuhat) + # outputs[output_name] = np.einsum('i...,i...->...', l, wbuhat) - d_dptau_dt_d_t_duration = -2.0 / t_duration ** 2 + d_dptau_dt_d_t_duration = -2.0 / t_duration ** 2 - partials[output_name, 'ptau'] = dl_dstau.T @ wbuhat + partials[output_name, 'ptau'] = dl_dstau.T @ wbuhat - partials[rate_name, 'ptau'] = d2l_dstau2.T @ wbuhat * dptau_dt - partials[rate_name, 't_duration'] = partials[output_name, 'ptau'] * d_dptau_dt_d_t_duration + partials[rate_name, 'ptau'] = d2l_dstau2.T @ wbuhat * dptau_dt + partials[rate_name, 't_duration'] = partials[output_name, 'ptau'] * d_dptau_dt_d_t_duration - # Assign only thos jacobian columns due to the current segment, since - # other segments cannot impact interpolation in this one. - # partials[output_name, input_name] = 0.0 - partials[output_name, input_name][...] = ((l * w_b.T).T).T + # Assign only thos jacobian columns due to the current segment, since + # other segments cannot impact interpolation in this one. + # partials[output_name, input_name] = 0.0 + partials[output_name, input_name][...] = ((l * w_b.T).T).T - # partials[rate_name, input_name] = 0.0 - partials[rate_name, input_name][...] = (dl_dstau * w_b * dptau_dt).T + # partials[rate_name, input_name] = 0.0 + partials[rate_name, input_name][...] = (dl_dstau * w_b * dptau_dt).T - # partials[rate2_name, input_name] = 0.0 - if self._compute_derivs: - d3l_dstau3[...] = np.sum(np.sum(np.sum(d3l_dg3, axis=-1), axis=-1), axis=-1, keepdims=True) - partials[rate2_name, 'ptau'] = d3l_dstau3.T @ wbuhat * dptau_dt ** 2 - partials[rate2_name, 't_duration'] = 2 * wbuhat.T @ d2l_dstau2 * dptau_dt * d_dptau_dt_d_t_duration - partials[rate2_name, input_name][...] = (d2l_dstau2 * w_b * dptau_dt ** 2).T + # partials[rate2_name, input_name] = 0.0 + if self._compute_derivs: + d3l_dstau3[...] = np.sum(np.sum(np.sum(d3l_dg3, axis=-1), axis=-1), axis=-1, keepdims=True) + partials[rate2_name, 'ptau'] = d3l_dstau3.T @ wbuhat * dptau_dt ** 2 + partials[rate2_name, 't_duration'] = 2 * wbuhat.T @ d2l_dstau2 * dptau_dt * d_dptau_dt_d_t_duration + partials[rate2_name, input_name][...] = (d2l_dstau2 * w_b * dptau_dt ** 2).T def compute_partials(self, inputs, partials, discrete_inputs=None): """ @@ -636,64 +622,3 @@ def compute_partials(self, inputs, partials, discrete_inputs=None): """ if self._control_options: self._compute_partials_controls(inputs, partials, discrete_inputs) - - if self._polynomial_control_options: - self._compute_partials_polynomial_controls(inputs, partials, discrete_inputs) - - # for control_name, options in self._control_options.items(): - # input_name, output_name, rate_name, rate2_name = self._control_io_names[control_name] - - # u_hat = np.dot(L_seg, inputs[input_name][input_node_idxs].real) - # a = self._V_hat_inv[seg_order] @ u_hat - - # da_duhat = self._V_hat_inv[seg_order] @ L_seg - # dV_a = dV_stau @ a - # dV2_a = dV2_stau @ a - # dV3_a = dV3_stau @ a - - # partials[output_name, input_name][...] = 0.0 - # partials[output_name, input_name][..., u_idxs] = V_stau @ da_duhat - # partials[output_name, 'stau'] = dV_a.ravel() - - # pudot_pa = dstau_dt * dV_stau - # pa_puhat = self._V_hat_inv[seg_order] - # partials[rate_name, input_name][...] = 0.0 - # partials[rate_name, input_name][..., u_idxs] = pudot_pa @ pa_puhat - # partials[rate_name, 'dstau_dt'][...] = dV_a - # partials[rate_name, 'stau'][...] = dV2_a.ravel() - - # pu2dot_pa = dstau_dt**2 * dV2_stau - # partials[rate2_name, input_name][...] = 0.0 - # partials[rate2_name, input_name][..., u_idxs] = pu2dot_pa @ pa_puhat - # partials[rate2_name, 'dstau_dt'][...] = 2 * dstau_dt * dV2_a - # partials[rate2_name, 'stau'][...] = dV3_a.ravel() - - # for pc_name, options in self._polynomial_control_options.items(): - # input_name, output_name, rate_name, rate2_name = self._control_io_names[pc_name] - # order = options['order'] - - # V_ptau = np.vander(ptau, N=order+1, increasing=True) - # dV_ptau, dV2_ptau, dV3_ptau = self._dvander(V_ptau) - - # u_hat = inputs[input_name].real - # a = self._V_hat_inv[order] @ u_hat - - # dV_a = dV_ptau @ a - # dV2_a = dV2_ptau @ a - # dV3_a = dV3_ptau @ a - - # da_duhat = self._V_hat_inv[order] - - # partials[output_name, input_name][...] = V_ptau @ da_duhat - # partials[output_name, 'ptau'][...] = dV_a.ravel() - - # pudot_pa = dptau_dt * dV_ptau - # pa_puhat = self._V_hat_inv[order] - # partials[rate_name, input_name][...] = pudot_pa @ pa_puhat - # partials[rate_name, 't_duration'][...] = ddptau_dt_dtduration * dV_a - # partials[rate_name, 'ptau'][...] = dptau_dt * dV2_a.ravel() - - # pu2dot_pa = dptau_dt ** 2 * dV2_ptau - # partials[rate2_name, input_name][...] = pu2dot_pa @ pa_puhat - # partials[rate2_name, 't_duration'][...] = 2 * dptau_dt * ddptau_dt_dtduration * dV2_a - # partials[rate2_name, 'ptau'][...] = dptau_dt**2 * dV3_a.ravel() diff --git a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py index c964d2373..3829ecabe 100644 --- a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py @@ -37,11 +37,10 @@ class VandermondeControlInterpComp(om.ExplicitComponent): **kwargs Keyword arguments passed to ExplicitComponent. """ - def __init__(self, grid_data, control_options=None, polynomial_control_options=None, + def __init__(self, grid_data, control_options=None, time_units=None, standalone_mode=False, **kwargs): self._grid_data = grid_data self._control_options = {} if control_options is None else control_options - self._polynomial_control_options = {} if polynomial_control_options is None else polynomial_control_options self._time_units = time_units self._standalone_mode = standalone_mode diff --git a/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py b/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py index 1f87c9537..3cdefe60e 100644 --- a/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py +++ b/dymos/transcriptions/solve_ivp/components/ode_integration_interface_system.py @@ -28,10 +28,6 @@ def initialize(self): self.options.declare('control_options', default=None, types=dict, allow_none=True, desc='Dictionary of control names/options for the segments parent Phase.') - self.options.declare('polynomial_control_options', default=None, types=dict, allow_none=True, - desc='Dictionary of polynomial control names/options for the segments ' - 'parent Phase.') - self.options.declare('parameter_options', default=None, types=dict, allow_none=True, desc='Dictionary of parameter names/options for the segments ' 'parent Phase.') @@ -64,11 +60,10 @@ def setup(self): self.connect('t_duration', [f'ode.{tgt}' for tgt in time_options['t_duration_targets']]) - if self.options['control_options'] or self.options['polynomial_control_options']: + if self.options['control_options']: self._interp_comp = \ ODEIntControlInterpolationComp(time_units=time_units, - control_options=self.options['control_options'], - polynomial_control_options=self.options['polynomial_control_options']) + control_options=self.options['control_options']) self.add_subsystem('indep_controls', self._interp_comp, promotes_outputs=['*']) self.connect('t', ['indep_controls.time']) @@ -125,19 +120,6 @@ def configure(self): self.connect(f'control_rates:{name}_rate2', [f'ode.{tgt}' for tgt in options['rate2_targets']]) - # Polynomial controls - if self.options['polynomial_control_options']: - for name, options in self.options['polynomial_control_options'].items(): - if options['targets']: - self.connect(f'controls:{name}', - [f'ode.{tgt}' for tgt in options['targets']]) - if options['rate_targets']: - self.connect(f'control_rates:{name}_rate', - [f'ode.{tgt}' for tgt in options['rate_targets']]) - if options['rate2_targets']: - self.connect(f'control_rates:{name}_rate2', - [f'ode.{tgt}' for tgt in options['rate2_targets']]) - # Parameters if self.options['parameter_options']: for name, options in self.options['parameter_options'].items(): diff --git a/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py b/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py index efdd61288..475c8c4e1 100644 --- a/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py +++ b/dymos/transcriptions/solve_ivp/components/odeint_control_interpolation_comp.py @@ -23,8 +23,6 @@ def initialize(self): desc='Units of time') self.options.declare('control_options', types=dict, allow_none=True, default=None, desc='Dictionary of options for the dynamic controls') - self.options.declare('polynomial_control_options', types=dict, allow_none=True, - default=None, desc='Dictionary of options for the polynomial controls') self.options.declare('control_interpolants', types=dict, allow_none=True, default={}, desc='Dictionary of interpolants for the dynamic controls', recordable=False) @@ -54,21 +52,6 @@ def setup(self): self.add_output('control_rates:{0}_rate2'.format(control_name), shape=shape, units=rate2_units) - for control_name, options in self.options['polynomial_control_options'].items(): - shape = options['shape'] - units = options['units'] - rate_units = get_rate_units(units, time_units, deriv=1) - rate2_units = get_rate_units(units, time_units, deriv=2) - - self.add_output('controls:{0}'.format(control_name), shape=shape, - units=units) - - self.add_output('control_rates:{0}_rate'.format(control_name), shape=shape, - units=rate_units) - - self.add_output('control_rates:{0}_rate2'.format(control_name), shape=shape, - units=rate2_units) - def compute(self, inputs, outputs): """ Compute component outputs. @@ -93,15 +76,3 @@ def compute(self, inputs, outputs): outputs['control_rates:{0}_rate'.format(name)] = interp.eval_deriv(time) outputs['control_rates:{0}_rate2'.format(name)] = interp.eval_deriv(time, der=2) - - for name in self.options['polynomial_control_options']: - if name not in self.options['polynomial_control_interpolants']: - raise ValueError('No interpolant has been specified for {0}'.format(name)) - - interp = self.options['polynomial_control_interpolants'][name] - - outputs['controls:{0}'.format(name)] = interp.eval(time) - - outputs['control_rates:{0}_rate'.format(name)] = interp.eval_deriv(time) - - outputs['control_rates:{0}_rate2'.format(name)] = interp.eval_deriv(time, der=2) diff --git a/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py b/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py index 8aae98ea3..5bb9f25c6 100644 --- a/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py +++ b/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py @@ -53,10 +53,6 @@ def initialize(self): self.options.declare('control_options', default=None, types=dict, allow_none=True, desc='Dictionary of control names/options for the segments parent Phase.') - self.options.declare('polynomial_control_options', default=None, types=dict, allow_none=True, - desc='Dictionary of polynomial control names/options for the segments ' - 'parent Phase.') - self.options.declare('parameter_options', default=None, types=dict, allow_none=True, desc='Dictionary of parameter names/options for the segments ' 'parent Phase.') @@ -106,7 +102,6 @@ def configure_io(self): time_options=self.options['time_options'], state_options=self.options['state_options'], control_options=self.options['control_options'], - polynomial_control_options=self.options['polynomial_control_options'], parameter_options=self.options['parameter_options'], ode_init_kwargs=self.options['ode_init_kwargs'], reports=self.options['reports']) @@ -144,24 +139,23 @@ def configure_io(self): # Setup the control interpolants if self.options['control_options']: for name, options in self.options['control_options'].items(): - self.add_input(name='controls:{0}'.format(name), - val=np.ones(((ncdsps,) + options['shape'])), - units=options['units'], - desc='Values of control {0} at control discretization ' - 'nodes within the segment.'.format(name)) - interp = LagrangeBarycentricInterpolant(control_disc_seg_stau, options['shape']) - self.options['ode_integration_interface'].set_interpolant(name, interp) - - if self.options['polynomial_control_options']: - for name, options in self.options['polynomial_control_options'].items(): - poly_control_disc_ptau, _ = lgl(options['order'] + 1) - self.add_input(name='controls:{0}'.format(name), - val=np.ones(((options['order'] + 1,) + options['shape'])), - units=options['units'], - desc='Values of polynomial control {0} at control discretization ' - 'nodes within the phase.'.format(name)) - interp = LagrangeBarycentricInterpolant(poly_control_disc_ptau, options['shape']) - self.options['ode_integration_interface'].set_interpolant(name, interp) + if options['control_type'] == 'full': + self.add_input(name='controls:{0}'.format(name), + val=np.ones(((ncdsps,) + options['shape'])), + units=options['units'], + desc='Values of control {0} at control discretization ' + 'nodes within the segment.'.format(name)) + interp = LagrangeBarycentricInterpolant(control_disc_seg_stau, options['shape']) + self.options['ode_integration_interface'].set_interpolant(name, interp) + else: + poly_control_disc_ptau, _ = lgl(options['order'] + 1) + self.add_input(name='controls:{0}'.format(name), + val=np.ones(((options['order'] + 1,) + options['shape'])), + units=options['units'], + desc='Values of polynomial control {0} at control discretization ' + 'nodes within the phase.'.format(name)) + interp = LagrangeBarycentricInterpolant(poly_control_disc_ptau, options['shape']) + self.options['ode_integration_interface'].set_interpolant(name, interp) self.declare_partials(of='*', wrt='*', method='fd') @@ -195,22 +189,20 @@ def compute(self, inputs, outputs): t0_seg = inputs[time_name][0] tf_seg = inputs[time_name][-1] for name, options in self.options['control_options'].items(): - ctrl_vals = inputs[f'controls:{name}'] - self.options['ode_integration_interface'].setup_interpolant(name, - x0=t0_seg, - xf=tf_seg, - f_j=ctrl_vals) - - # Setup the polynomial control interpolants - if self.options['polynomial_control_options']: - t0_phase = inputs['t_initial'] - tf_phase = inputs['t_initial'] + inputs['t_duration'] - for name, options in self.options['polynomial_control_options'].items(): - ctrl_vals = inputs[f'controls:{name}'] - self.options['ode_integration_interface'].setup_interpolant(name, - x0=t0_phase, - xf=tf_phase, - f_j=ctrl_vals) + if options['control_type'] == 'full': + ctrl_vals = inputs[f'controls:{name}'] + self.options['ode_integration_interface'].setup_interpolant(name, + x0=t0_seg, + xf=tf_seg, + f_j=ctrl_vals) + else: + t0_phase = inputs['t_initial'] + tf_phase = inputs['t_initial'] + inputs['t_duration'] + ctrl_vals = inputs[f'controls:{name}'] + self.options['ode_integration_interface'].setup_interpolant(name, + x0=t0_phase, + xf=tf_phase, + f_j=ctrl_vals) # Set the values of t_initial and t_duration iface_prob.set_val('t_initial', From e0c8a304cb0564c3e693fa9becea0d2991adb47e Mon Sep 17 00:00:00 2001 From: johnjasa Date: Tue, 18 Jun 2024 10:46:12 -0500 Subject: [PATCH 12/17] All tests passing --- ...doc_brachistochrone_polynomial_controls.py | 3 +- .../test/test_state_rate_introspection.py | 4 -- dymos/phase/options.py | 4 +- dymos/phase/phase.py | 22 ++++----- dymos/transcriptions/common/control_group.py | 48 +++++++++---------- .../barycentric_control_interp_comp.py | 10 ++-- .../explicit_shooting/ode_evaluation_group.py | 2 +- .../test/test_explicit_shooting.py | 2 +- .../test/test_ode_integration_comp.py | 5 -- .../vandermonde_control_interp_comp.py | 2 - .../components/ode_integration_interface.py | 8 +--- .../components/segment_simulation_comp.py | 12 ++--- dymos/transcriptions/transcription_base.py | 2 +- dymos/utils/introspection.py | 2 +- .../visualization/linkage/test/model_data.dat | 2 +- 15 files changed, 53 insertions(+), 75 deletions(-) diff --git a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py index 1396dd944..b08abda34 100644 --- a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py +++ b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py @@ -1516,5 +1516,4 @@ def test_brachistochrone_polynomial_control_birkhoff(self): if __name__ == '__main__': # pragma: no cover - z = TestBrachistochronePolynomialControlRate2PathConstrained() - z.test_brachistochrone_polynomial_control_gauss_lobatto() \ No newline at end of file + unittest.main() diff --git a/dymos/examples/brachistochrone/test/test_state_rate_introspection.py b/dymos/examples/brachistochrone/test/test_state_rate_introspection.py index ada80db15..8667db7fe 100644 --- a/dymos/examples/brachistochrone/test/test_state_rate_introspection.py +++ b/dymos/examples/brachistochrone/test/test_state_rate_introspection.py @@ -902,7 +902,3 @@ def compute(self, inputs, outputs): expected = 'Error during configure_states_introspection in phase traj0.phases.phase0.' self.assertEqual(str(ctx.exception), expected) - -if __name__ == '__main__': - z = TestIntegratePolynomialControl() - z.test_integrate_polynomial_control_gl() \ No newline at end of file diff --git a/dymos/phase/options.py b/dymos/phase/options.py index 2fe9a8d18..2f3306f40 100644 --- a/dymos/phase/options.py +++ b/dymos/phase/options.py @@ -20,10 +20,10 @@ def __init__(self, read_only=False): self.declare(name='name', types=str, desc='The name of ODE system parameter to be controlled.') - + self.declare(name='control_type', types=str, default='full', desc='The type of control variable. Options are `full` or `polynomial`.') - + self.declare(name='order', types=(int,), default=None, allow_none=True, desc='A integer that provides the interpolation order when the control is ' 'to assume a single polynomial basis across the entire phase, or None ' diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index b6ed2cbb7..eee7a99fc 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -515,8 +515,8 @@ def check_parameter(self, name): elif name in self.parameter_options: raise ValueError(f'{name} has already been added as a parameter.') - def add_control(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, opt=_unspecified, - fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, + def add_control(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, + opt=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, rate_targets=_unspecified, rate2_targets=_unspecified, val=_unspecified, shape=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, adder=_unspecified, ref0=_unspecified, ref=_unspecified, continuity=_unspecified, @@ -619,8 +619,8 @@ def add_control(self, name, control_type=_unspecified, order=_unspecified, units self.control_options[name] = ControlOptionsDictionary() self.control_options[name]['name'] = name - self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, - fix_final=fix_final, targets=targets, rate_targets=rate_targets, + self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, + fix_initial=fix_initial, fix_final=fix_final, targets=targets, rate_targets=rate_targets, rate2_targets=rate2_targets, val=val, shape=shape, lower=lower, upper=upper, scaler=scaler, adder=adder, ref0=ref0, ref=ref, continuity=continuity, continuity_scaler=continuity_scaler, @@ -631,8 +631,8 @@ def add_control(self, name, control_type=_unspecified, order=_unspecified, units rate2_continuity_scaler=rate2_continuity_scaler, rate2_continuity_ref=rate2_continuity_ref) - def set_control_options(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, opt=_unspecified, - fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, + def set_control_options(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, + opt=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, rate_targets=_unspecified, rate2_targets=_unspecified, val=_unspecified, shape=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, adder=_unspecified, ref0=_unspecified, ref=_unspecified, continuity=_unspecified, @@ -884,9 +884,9 @@ def add_polynomial_control(self, name, order, desc=_unspecified, val=_unspecifie The shape of the control variable at each point in time. """ om.issue_warning(f'{self.pathname}: The method `add_polynomial_control` is ' - 'deprecated and will be removed in Dymos 2.1. Please use ' - '`add_control` with the appropriate options to define a polynomial control.', - category=om.OMDeprecationWarning) + 'deprecated and will be removed in Dymos 2.1. Please use ' + '`add_control` with the appropriate options to define a polynomial control.', + category=om.OMDeprecationWarning) self.check_parameter(name) @@ -897,8 +897,8 @@ def add_polynomial_control(self, name, order, desc=_unspecified, val=_unspecifie control_type = 'polynomial' - self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, - fix_final=fix_final, targets=targets, rate_targets=rate_targets, + self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, + fix_initial=fix_initial, fix_final=fix_final, targets=targets, rate_targets=rate_targets, rate2_targets=rate2_targets, val=val, shape=shape, lower=lower, upper=upper, scaler=scaler, adder=adder, ref0=ref0, ref=ref, continuity=False, rate_continuity=False, rate2_continuity=False) diff --git a/dymos/transcriptions/common/control_group.py b/dymos/transcriptions/common/control_group.py index aff0432c6..5f545cdf5 100644 --- a/dymos/transcriptions/common/control_group.py +++ b/dymos/transcriptions/common/control_group.py @@ -132,14 +132,14 @@ def _configure_controls(self): self.rate2_jacs[name][:, i, :, i] = D2_de self.val_jacs[name] = self.val_jacs[name].reshape((num_output_nodes * size, - num_control_input_nodes * size), - order='C') + num_control_input_nodes * size), + order='C') self.rate_jacs[name] = self.rate_jacs[name].reshape((num_output_nodes * size, num_control_input_nodes * size), order='C') self.rate2_jacs[name] = self.rate2_jacs[name].reshape((num_output_nodes * size, - num_control_input_nodes * size), - order='C') + num_control_input_nodes * size), + order='C') self.val_jac_rows[name], self.val_jac_cols[name] = \ np.where(self.val_jacs[name] != 0) self.rate_jac_rows[name], self.rate_jac_cols[name] = \ @@ -151,26 +151,26 @@ def _configure_controls(self): rs, cs = self.val_jac_rows[name], self.val_jac_cols[name] self.declare_partials(of=self._output_val_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs, val=self.val_jacs[name][rs, cs]) + wrt=self._input_names[name], + rows=rs, cols=cs, val=self.val_jacs[name][rs, cs]) rs = np.concatenate([np.arange(0, num_output_nodes * size, size, dtype=int) + i for i in range(size)]) self.declare_partials(of=self._output_rate_names[name], - wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) + wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) self.declare_partials(of=self._output_rate_names[name], - wrt=self._input_names[name], - rows=self.rate_jac_rows[name], cols=self.rate_jac_cols[name]) + wrt=self._input_names[name], + rows=self.rate_jac_rows[name], cols=self.rate_jac_cols[name]) self.declare_partials(of=self._output_rate2_names[name], - wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) + wrt='t_duration', rows=rs, cols=np.zeros_like(rs)) self.declare_partials(of=self._output_rate2_names[name], - wrt=self._input_names[name], - rows=self.rate2_jac_rows[name], cols=self.rate2_jac_cols[name]) - + wrt=self._input_names[name], + rows=self.rate2_jac_rows[name], cols=self.rate2_jac_cols[name]) + else: self._input_names[name] = f'controls:{name}' self._output_val_names[name] = f'control_values:{name}' @@ -204,20 +204,20 @@ def _configure_controls(self): J_val = sp.kron(self.L, sp_eye, format='csr') rs, cs, data = sp.find(J_val) self.declare_partials(of=self._output_val_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs, val=data) + wrt=self._input_names[name], + rows=rs, cols=cs, val=data) # The partials of the output rate and second derivative wrt dt_dstau rs = np.arange(num_output_nodes * size, dtype=int) cs = np.repeat(np.arange(num_output_nodes, dtype=int), size) self.declare_partials(of=self._output_rate_names[name], - wrt='dt_dstau', - rows=rs, cols=cs) + wrt='dt_dstau', + rows=rs, cols=cs) self.declare_partials(of=self._output_rate2_names[name], - wrt='dt_dstau', - rows=rs, cols=cs) + wrt='dt_dstau', + rows=rs, cols=cs) # The partials of the rates and second derivatives are nonlinear but the sparsity # pattern is obtained from the kronecker product of the 1st and 2nd differentiation @@ -226,15 +226,15 @@ def _configure_controls(self): rs, cs = self.rate_jacs[name].nonzero() self.declare_partials(of=self._output_rate_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs) + wrt=self._input_names[name], + rows=rs, cols=cs) self.rate2_jacs[name] = sp.kron(self.D2, sp_eye, format='csr') rs, cs = self.rate2_jacs[name].nonzero() self.declare_partials(of=self._output_rate2_names[name], - wrt=self._input_names[name], - rows=rs, cols=cs) + wrt=self._input_names[name], + rows=rs, cols=cs) def configure_io(self): """ @@ -468,7 +468,7 @@ def setup(self): if len(control_options) < 1: return - + self.add_subsystem( 'control_interp_comp', subsys=ControlInterpComp(time_units=time_units, grid_data=gd, output_grid_data=ogd, diff --git a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py index 7bb868929..d88eeca47 100644 --- a/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/barycentric_control_interp_comp.py @@ -347,7 +347,6 @@ def _compute_controls(self, inputs, outputs, discrete_inputs=None, discrete_outp discrete_outputs : `Vector` `Vector` containing discrete_outputs. """ - gd = self._grid_data seg_idx = self.options['segment_index'] stau = inputs['stau'] dstau_dt = inputs['dstau_dt'] @@ -379,9 +378,7 @@ def _compute_controls(self, inputs, outputs, discrete_inputs=None, discrete_outp L_id = self._L_id['controls'] L_seg = L_id[disc_node_idxs[0]:disc_node_idxs[0] + len(disc_node_idxs), input_node_idxs[0]:input_node_idxs[0] + len(input_node_idxs)] - - - gd = self._grid_data + seg_idx = self.options['segment_index'] ptau = inputs['ptau'] dptau_dt = 2. / inputs['t_duration'] @@ -435,7 +432,6 @@ def _compute_controls(self, inputs, outputs, discrete_inputs=None, discrete_outp outputs[rate_name] = wbuhat.T @ dl_dptau * dptau_dt outputs[rate2_name] = wbuhat.T @ d2l_dptau2 * dptau_dt ** 2 - def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): """ Compute interpolated control values and rates. @@ -513,7 +509,7 @@ def _compute_partials_controls(self, inputs, partials, discrete_inputs=None): L_id = self._L_id['controls'] L_seg = L_id[disc_node_idxs[0]:disc_node_idxs[0] + len(disc_node_idxs), input_node_idxs[0]:input_node_idxs[0] + len(input_node_idxs)] - + ptau = inputs['ptau'] t_duration = inputs['t_duration'] dptau_dt = 2.0 / t_duration @@ -552,7 +548,7 @@ def _compute_partials_controls(self, inputs, partials, discrete_inputs=None): partials[rate2_name, input_name] = 0.0 partials[rate2_name, input_name][..., input_node_idxs] = \ (d2l_dstau2 * w_b * dstau_dt ** 2).T @ L_seg - + else: gd = self._grid_data n = options['order'] + 1 diff --git a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py index 2ac9bec94..aedb0b349 100644 --- a/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py +++ b/dymos/transcriptions/explicit_shooting/ode_evaluation_group.py @@ -265,7 +265,7 @@ def _configure_controls(self): num_control_input_nodes = options['order'] + 1 else: num_control_input_nodes = igd.subset_num_nodes['control_input'] - + shape = options['shape'] units = options['units'] rate_units = get_rate_units(units, time_units, deriv=1) diff --git a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py index 0c6f1233c..cf74ab934 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py @@ -651,4 +651,4 @@ def test_brachistochrone_static_gravity_explicit_shooting(self): # dm.run_problem(p, simulate=False) - assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) \ No newline at end of file + assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) diff --git a/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py b/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py index 205748460..3be221e75 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py +++ b/dymos/transcriptions/explicit_shooting/test/test_ode_integration_comp.py @@ -79,7 +79,6 @@ def test_integrate_scalar_ode(self): param_options['p']['targets'] = ['p'] control_options = {} - polynomial_control_options = {} prob = om.Problem() @@ -89,7 +88,6 @@ def test_integrate_scalar_ode(self): time_options=time_options, state_options=state_options, parameter_options=param_options, control_options=control_options, - polynomial_control_options=polynomial_control_options, ode_class=SimpleODE, ode_init_kwargs=None)) prob.setup() prob.set_val('integrator.states:x', 0.5) @@ -154,8 +152,6 @@ def test_integrate_with_controls(self): control_options['theta']['units'] = 'rad' control_options['theta']['targets'] = ['theta'] - polynomial_control_options = {} - p = om.Problem() p.model.add_subsystem('integrator', @@ -164,7 +160,6 @@ def test_integrate_with_controls(self): state_options=state_options, parameter_options=param_options, control_options=control_options, - polynomial_control_options=polynomial_control_options, input_grid_data=gd, ode_init_kwargs=None)) diff --git a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py index 3829ecabe..3d2e00781 100644 --- a/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py +++ b/dymos/transcriptions/explicit_shooting/vandermonde_control_interp_comp.py @@ -27,8 +27,6 @@ class VandermondeControlInterpComp(om.ExplicitComponent): nodes are layed out. control_options : dict of {str: ControlOptionsDictionary} A mapping that maps the name of each control to a ControlOptionsDictionary of its options. - polynomial_control_options : dict of {str: PolynomialControlOptionsDictionary} - A mapping that maps the name of each polynomial control to an OptionsDictionary of its options. time_units : str The time units pertaining to the control rates. standalone_mode : bool diff --git a/dymos/transcriptions/solve_ivp/components/ode_integration_interface.py b/dymos/transcriptions/solve_ivp/components/ode_integration_interface.py index 2002a422b..fcd2e2061 100644 --- a/dymos/transcriptions/solve_ivp/components/ode_integration_interface.py +++ b/dymos/transcriptions/solve_ivp/components/ode_integration_interface.py @@ -24,8 +24,6 @@ class ODEIntegrationInterface(object): The state options for the phase being simulated. control_options : dict of {str: ControlOptionsDictionary} The control options for the phase being simulated. - polynomial_control_options : dict of {str: PolynomialControlOptionsDictionary} - The polynomial control options for the phase being simulated. parameter_options : dict of {str: ParameterOptionsDictionary} The parameter options for the phase being simulated. ode_init_kwargs : dict @@ -34,7 +32,7 @@ class ODEIntegrationInterface(object): The reports argument to be passed to the subproblems. By default, no subproblem reports are generated. """ def __init__(self, ode_class, time_options, state_options, control_options, - polynomial_control_options, parameter_options, ode_init_kwargs=None, + parameter_options, ode_init_kwargs=None, reports=False): # Get the state vector. This isn't necessarily ordered @@ -42,7 +40,6 @@ def __init__(self, ode_class, time_options, state_options, control_options, self.state_options = OrderedDict() self.time_options = time_options self.control_options = control_options - self.polynomial_control_options = polynomial_control_options self.parameter_options = parameter_options self.control_interpolants = {} self.polynomial_control_interpolants = {} @@ -68,7 +65,6 @@ def __init__(self, ode_class, time_options, state_options, control_options, time_options=time_options, state_options=state_options, control_options=control_options, - polynomial_control_options=polynomial_control_options, parameter_options=parameter_options, ode_init_kwargs=ode_init_kwargs), reports=reports) @@ -140,8 +136,6 @@ def setup_interpolant(self, name, x0, xf, f_j): """ if name in self.prob.model.options['control_options']: self.prob.model.setup_interpolant(name, x0, xf, f_j) - elif name in self.prob.model.options['polynomial_control_options']: - self.prob.model.setup_interpolant(name, x0, xf, f_j) else: raise KeyError(f'Unable to set control interpolant of unknown control: {name}') diff --git a/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py b/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py index 5bb9f25c6..4c05ee9a7 100644 --- a/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py +++ b/dymos/transcriptions/solve_ivp/components/segment_simulation_comp.py @@ -141,18 +141,18 @@ def configure_io(self): for name, options in self.options['control_options'].items(): if options['control_type'] == 'full': self.add_input(name='controls:{0}'.format(name), - val=np.ones(((ncdsps,) + options['shape'])), - units=options['units'], - desc='Values of control {0} at control discretization ' + val=np.ones(((ncdsps,) + options['shape'])), + units=options['units'], + desc='Values of control {0} at control discretization ' 'nodes within the segment.'.format(name)) interp = LagrangeBarycentricInterpolant(control_disc_seg_stau, options['shape']) self.options['ode_integration_interface'].set_interpolant(name, interp) else: poly_control_disc_ptau, _ = lgl(options['order'] + 1) self.add_input(name='controls:{0}'.format(name), - val=np.ones(((options['order'] + 1,) + options['shape'])), - units=options['units'], - desc='Values of polynomial control {0} at control discretization ' + val=np.ones(((options['order'] + 1,) + options['shape'])), + units=options['units'], + desc='Values of polynomial control {0} at control discretization ' 'nodes within the phase.'.format(name)) interp = LagrangeBarycentricInterpolant(poly_control_disc_ptau, options['shape']) self.options['ode_integration_interface'].set_interpolant(name, interp) diff --git a/dymos/transcriptions/transcription_base.py b/dymos/transcriptions/transcription_base.py index 34f489dc1..c019fe4bd 100644 --- a/dymos/transcriptions/transcription_base.py +++ b/dymos/transcriptions/transcription_base.py @@ -158,7 +158,7 @@ def setup_controls(self, phase): phase.add_subsystem('control_group', subsys=control_group) - + phase.connect('t_duration_val', 'control_group.t_duration') def configure_controls(self, phase): diff --git a/dymos/utils/introspection.py b/dymos/utils/introspection.py index 75329bd56..e35246376 100644 --- a/dymos/utils/introspection.py +++ b/dymos/utils/introspection.py @@ -304,7 +304,7 @@ def configure_controls_introspection(control_options, ode, time_units='s'): if any(['dymos.static_target' in meta['tags'] for meta in targets.values()]): raise ValueError(f"Control '{name}' cannot be connected to its targets because one " f"or more targets are tagged with 'dymos.static_target'.") - + # Now check rate targets rate_targets = _get_targets_metadata(ode_inputs, name=f'{name}_rate', user_targets=options['rate_targets']) diff --git a/dymos/visualization/linkage/test/model_data.dat b/dymos/visualization/linkage/test/model_data.dat index 993be8e9a..75423cb43 100644 --- a/dymos/visualization/linkage/test/model_data.dat +++ b/dymos/visualization/linkage/test/model_data.dat @@ -1 +1 @@ -{"tree": {"name": "Trajectory", "type": "root", "children_by_name": {"params": {"name": "params", "type": "phase", "children_by_name": {"m": {"name": "m", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "T_nominal": {"name": "T_nominal", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "T_shutdown": {"name": "T_shutdown", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "mu_r_braking": {"name": "mu_r_braking", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}}}, "br_to_v1": {"name": "br_to_v1", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"alpha": {"name": "alpha", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_nominal": {"name": "T_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}}}, "rto": {"name": "rto", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"alpha": {"name": "alpha", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_shutdown": {"name": "T_shutdown", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_braking": {"name": "mu_r_braking", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}}}}}, "v1_to_vr": {"name": "v1_to_vr", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"alpha": {"name": "alpha", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false, "linked": true, "connected": false}, "m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}}}, "rotate": {"name": "rotate", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "alpha": {"name": "polynomial_controls:alpha", "type": "variable", "class": "indep_polynomial_control", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "alpha": {"name": "polynomial_controls:alpha", "type": "variable", "class": "indep_polynomial_control", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}}}, "climb": {"name": "climb", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "h": {"name": "states:h", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "gam": {"name": "states:gam", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}, "alpha": {"name": "controls:alpha", "type": "variable", "class": "indep_control", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "h": {"name": "states:h", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}, "gam": {"name": "states:gam", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}, "alpha": {"name": "controls:alpha", "type": "variable", "class": "indep_control", "fixed": false, "paramOpt": null}}}}}}}, "connections_list": [{"src": "br_to_v1.final.time", "src_fixed": true, "tgt": "v1_to_vr.initial.time", "tgt_fixed": true}, {"src": "br_to_v1.final.states:r", "src_fixed": false, "tgt": "v1_to_vr.initial.states:r", "tgt_fixed": false}, {"src": "br_to_v1.final.states:v", "src_fixed": false, "tgt": "v1_to_vr.initial.states:v", "tgt_fixed": false}, {"src": "v1_to_vr.final.time", "src_fixed": true, "tgt": "rotate.initial.time", "tgt_fixed": true}, {"src": "v1_to_vr.final.states:r", "src_fixed": false, "tgt": "rotate.initial.states:r", "tgt_fixed": false}, {"src": "v1_to_vr.final.states:v", "src_fixed": false, "tgt": "rotate.initial.states:v", "tgt_fixed": false}, {"src": "v1_to_vr.params.alpha", "src_fixed": true, "tgt": "rotate.initial.polynomial_controls:alpha", "tgt_fixed": false}, {"src": "rotate.final.time", "src_fixed": true, "tgt": "climb.initial.time", "tgt_fixed": true}, {"src": "rotate.final.states:r", "src_fixed": false, "tgt": "climb.initial.states:r", "tgt_fixed": false}, {"src": "rotate.final.states:v", "src_fixed": false, "tgt": "climb.initial.states:v", "tgt_fixed": false}, {"src": "rotate.final.polynomial_controls:alpha", "src_fixed": false, "tgt": "climb.initial.controls:alpha", "tgt_fixed": false}, {"src": "br_to_v1.final.time", "src_fixed": true, "tgt": "rto.initial.time", "tgt_fixed": true}, {"src": "br_to_v1.final.states:r", "src_fixed": false, "tgt": "rto.initial.states:r", "tgt_fixed": false}, {"src": "br_to_v1.final.states:v", "src_fixed": false, "tgt": "rto.initial.states:v", "tgt_fixed": false}, {"src": "rto.final.states:r", "src_fixed": false, "tgt": "climb.final.states:r", "tgt_fixed": false}, {"src": "params.m", "src_fixed": false, "tgt": "br_to_v1.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "rto.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "v1_to_vr.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "rotate.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "climb.params.m", "tgt_fixed": true}, {"src": "params.T_nominal", "src_fixed": false, "tgt": "br_to_v1.params.T_nominal", "tgt_fixed": true}, {"src": "params.T_engine_out", "src_fixed": false, "tgt": "v1_to_vr.params.T_engine_out", "tgt_fixed": true}, {"src": "params.T_engine_out", "src_fixed": false, "tgt": "rotate.params.T_engine_out", "tgt_fixed": true}, {"src": "params.T_engine_out", "src_fixed": false, "tgt": "climb.params.T_engine_out", "tgt_fixed": true}, {"src": "params.T_shutdown", "src_fixed": false, "tgt": "rto.params.T_shutdown", "tgt_fixed": true}, {"src": "params.mu_r_nominal", "src_fixed": false, "tgt": "br_to_v1.params.mu_r_nominal", "tgt_fixed": true}, {"src": "params.mu_r_nominal", "src_fixed": false, "tgt": "v1_to_vr.params.mu_r_nominal", "tgt_fixed": true}, {"src": "params.mu_r_nominal", "src_fixed": false, "tgt": "rotate.params.mu_r_nominal", "tgt_fixed": true}, {"src": "params.mu_r_braking", "src_fixed": false, "tgt": "rto.params.mu_r_braking", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "br_to_v1.params.h_runway", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "rto.params.h_runway", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "v1_to_vr.params.h_runway", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "rotate.params.h_runway", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "br_to_v1.params.rho", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "rto.params.rho", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "v1_to_vr.params.rho", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "rotate.params.rho", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "br_to_v1.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "rto.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "v1_to_vr.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "rotate.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "climb.params.S", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "br_to_v1.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "rto.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "v1_to_vr.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "rotate.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "climb.params.CD0", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "br_to_v1.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "rto.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "v1_to_vr.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "rotate.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "climb.params.AR", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "br_to_v1.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "rto.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "v1_to_vr.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "rotate.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "climb.params.e", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "br_to_v1.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "rto.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "v1_to_vr.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "rotate.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "climb.params.span", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "br_to_v1.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "rto.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "v1_to_vr.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "rotate.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "climb.params.h_w", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "br_to_v1.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "rto.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "v1_to_vr.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "rotate.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "climb.params.CL0", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "br_to_v1.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "rto.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "v1_to_vr.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "rotate.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "climb.params.CL_max", "tgt_fixed": true}]} \ No newline at end of file +{"tree": {"name": "Trajectory", "type": "root", "children_by_name": {"params": {"name": "params", "type": "phase", "children_by_name": {"m": {"name": "m", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "T_nominal": {"name": "T_nominal", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "T_shutdown": {"name": "T_shutdown", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "mu_r_braking": {"name": "mu_r_braking", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": false, "paramOpt": false, "connected": true}}}, "br_to_v1": {"name": "br_to_v1", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"alpha": {"name": "alpha", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_nominal": {"name": "T_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}}}, "rto": {"name": "rto", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"alpha": {"name": "alpha", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_shutdown": {"name": "T_shutdown", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_braking": {"name": "mu_r_braking", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}}}}}, "v1_to_vr": {"name": "v1_to_vr", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"alpha": {"name": "alpha", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false, "linked": true, "connected": false}, "m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}}}, "rotate": {"name": "rotate", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "mu_r_nominal": {"name": "mu_r_nominal", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_runway": {"name": "h_runway", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "rho": {"name": "rho", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "alpha": {"name": "controls:alpha", "type": "variable", "class": "indep_control", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "alpha": {"name": "controls:alpha", "type": "variable", "class": "indep_control", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}}}, "climb": {"name": "climb", "type": "phase", "children_by_name": {"params": {"name": "params", "type": "condition", "children_by_name": {"m": {"name": "m", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "T_engine_out": {"name": "T_engine_out", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "S": {"name": "S", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CD0": {"name": "CD0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "AR": {"name": "AR", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "e": {"name": "e", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "span": {"name": "span", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "h_w": {"name": "h_w", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL0": {"name": "CL0", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}, "CL_max": {"name": "CL_max", "type": "variable", "class": "parameter", "fixed": true, "paramOpt": false}}}, "initial": {"name": "initial", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null, "linked": true, "connected": false}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "h": {"name": "states:h", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "gam": {"name": "states:gam", "type": "variable", "class": "state", "fixed": true, "paramOpt": null}, "alpha": {"name": "controls:alpha", "type": "variable", "class": "indep_control", "fixed": false, "paramOpt": null, "linked": true, "connected": false}}}, "final": {"name": "final", "type": "condition", "children_by_name": {"time": {"name": "time", "type": "variable", "class": "t", "fixed": true, "paramOpt": null}, "r": {"name": "states:r", "type": "variable", "class": "state", "fixed": false, "paramOpt": null, "linked": true, "connected": false}, "h": {"name": "states:h", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}, "v": {"name": "states:v", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}, "gam": {"name": "states:gam", "type": "variable", "class": "state", "fixed": false, "paramOpt": null}, "alpha": {"name": "controls:alpha", "type": "variable", "class": "indep_control", "fixed": false, "paramOpt": null}}}}}}}, "connections_list": [{"src": "br_to_v1.final.time", "src_fixed": true, "tgt": "v1_to_vr.initial.time", "tgt_fixed": true}, {"src": "br_to_v1.final.states:r", "src_fixed": false, "tgt": "v1_to_vr.initial.states:r", "tgt_fixed": false}, {"src": "br_to_v1.final.states:v", "src_fixed": false, "tgt": "v1_to_vr.initial.states:v", "tgt_fixed": false}, {"src": "v1_to_vr.final.time", "src_fixed": true, "tgt": "rotate.initial.time", "tgt_fixed": true}, {"src": "v1_to_vr.final.states:r", "src_fixed": false, "tgt": "rotate.initial.states:r", "tgt_fixed": false}, {"src": "v1_to_vr.final.states:v", "src_fixed": false, "tgt": "rotate.initial.states:v", "tgt_fixed": false}, {"src": "v1_to_vr.params.alpha", "src_fixed": true, "tgt": "rotate.initial.controls:alpha", "tgt_fixed": false}, {"src": "rotate.final.time", "src_fixed": true, "tgt": "climb.initial.time", "tgt_fixed": true}, {"src": "rotate.final.states:r", "src_fixed": false, "tgt": "climb.initial.states:r", "tgt_fixed": false}, {"src": "rotate.final.states:v", "src_fixed": false, "tgt": "climb.initial.states:v", "tgt_fixed": false}, {"src": "rotate.final.controls:alpha", "src_fixed": false, "tgt": "climb.initial.controls:alpha", "tgt_fixed": false}, {"src": "br_to_v1.final.time", "src_fixed": true, "tgt": "rto.initial.time", "tgt_fixed": true}, {"src": "br_to_v1.final.states:r", "src_fixed": false, "tgt": "rto.initial.states:r", "tgt_fixed": false}, {"src": "br_to_v1.final.states:v", "src_fixed": false, "tgt": "rto.initial.states:v", "tgt_fixed": false}, {"src": "rto.final.states:r", "src_fixed": false, "tgt": "climb.final.states:r", "tgt_fixed": false}, {"src": "params.m", "src_fixed": false, "tgt": "br_to_v1.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "rto.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "v1_to_vr.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "rotate.params.m", "tgt_fixed": true}, {"src": "params.m", "src_fixed": false, "tgt": "climb.params.m", "tgt_fixed": true}, {"src": "params.T_nominal", "src_fixed": false, "tgt": "br_to_v1.params.T_nominal", "tgt_fixed": true}, {"src": "params.T_engine_out", "src_fixed": false, "tgt": "v1_to_vr.params.T_engine_out", "tgt_fixed": true}, {"src": "params.T_engine_out", "src_fixed": false, "tgt": "rotate.params.T_engine_out", "tgt_fixed": true}, {"src": "params.T_engine_out", "src_fixed": false, "tgt": "climb.params.T_engine_out", "tgt_fixed": true}, {"src": "params.T_shutdown", "src_fixed": false, "tgt": "rto.params.T_shutdown", "tgt_fixed": true}, {"src": "params.mu_r_nominal", "src_fixed": false, "tgt": "br_to_v1.params.mu_r_nominal", "tgt_fixed": true}, {"src": "params.mu_r_nominal", "src_fixed": false, "tgt": "v1_to_vr.params.mu_r_nominal", "tgt_fixed": true}, {"src": "params.mu_r_nominal", "src_fixed": false, "tgt": "rotate.params.mu_r_nominal", "tgt_fixed": true}, {"src": "params.mu_r_braking", "src_fixed": false, "tgt": "rto.params.mu_r_braking", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "br_to_v1.params.h_runway", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "rto.params.h_runway", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "v1_to_vr.params.h_runway", "tgt_fixed": true}, {"src": "params.h_runway", "src_fixed": false, "tgt": "rotate.params.h_runway", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "br_to_v1.params.rho", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "rto.params.rho", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "v1_to_vr.params.rho", "tgt_fixed": true}, {"src": "params.rho", "src_fixed": false, "tgt": "rotate.params.rho", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "br_to_v1.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "rto.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "v1_to_vr.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "rotate.params.S", "tgt_fixed": true}, {"src": "params.S", "src_fixed": false, "tgt": "climb.params.S", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "br_to_v1.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "rto.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "v1_to_vr.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "rotate.params.CD0", "tgt_fixed": true}, {"src": "params.CD0", "src_fixed": false, "tgt": "climb.params.CD0", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "br_to_v1.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "rto.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "v1_to_vr.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "rotate.params.AR", "tgt_fixed": true}, {"src": "params.AR", "src_fixed": false, "tgt": "climb.params.AR", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "br_to_v1.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "rto.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "v1_to_vr.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "rotate.params.e", "tgt_fixed": true}, {"src": "params.e", "src_fixed": false, "tgt": "climb.params.e", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "br_to_v1.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "rto.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "v1_to_vr.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "rotate.params.span", "tgt_fixed": true}, {"src": "params.span", "src_fixed": false, "tgt": "climb.params.span", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "br_to_v1.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "rto.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "v1_to_vr.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "rotate.params.h_w", "tgt_fixed": true}, {"src": "params.h_w", "src_fixed": false, "tgt": "climb.params.h_w", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "br_to_v1.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "rto.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "v1_to_vr.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "rotate.params.CL0", "tgt_fixed": true}, {"src": "params.CL0", "src_fixed": false, "tgt": "climb.params.CL0", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "br_to_v1.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "rto.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "v1_to_vr.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "rotate.params.CL_max", "tgt_fixed": true}, {"src": "params.CL_max", "src_fixed": false, "tgt": "climb.params.CL_max", "tgt_fixed": true}]} \ No newline at end of file From df9e551e1ba2371a10e407f1a1bd2a8915221342 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Tue, 18 Jun 2024 11:21:21 -0500 Subject: [PATCH 13/17] Updating docs --- docs/dymos_book/api/phase_api.ipynb | 6 ---- .../features/phases/variables.ipynb | 28 ++----------------- dymos/phase/phase.py | 3 ++ 3 files changed, 5 insertions(+), 32 deletions(-) diff --git a/docs/dymos_book/api/phase_api.ipynb b/docs/dymos_book/api/phase_api.ipynb index ab5d38a5d..6ac0cc37d 100644 --- a/docs/dymos_book/api/phase_api.ipynb +++ b/docs/dymos_book/api/phase_api.ipynb @@ -220,12 +220,6 @@ " :noindex:\n", "```\n", "\n", - "## set_polynomial_control_options\n", - "```{eval-rst}\n", - " .. automethod:: dymos.Phase.set_polynomial_control_options\n", - " :noindex:\n", - "```\n", - "\n", "## set_polynomial_control_val\n", "```{eval-rst}\n", " .. automethod:: dymos.Phase.set_polynomial_control_val\n", diff --git a/docs/dymos_book/features/phases/variables.ipynb b/docs/dymos_book/features/phases/variables.ipynb index 0d028650e..c41adc0e1 100644 --- a/docs/dymos_book/features/phases/variables.ipynb +++ b/docs/dymos_book/features/phases/variables.ipynb @@ -245,37 +245,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Polynomial Controls\n", + "### Polynomial Controls\n", "\n", "Sometimes it can be easier to optimize a problem by reducing the freedom in the controls.\n", "For instance, one might want the control to be linearly or quadratically varying throughout a phase, rather than having a different value specified at each node.\n", "In Dymos, this role is filled by the PolynomialControl.\n", "Polynomial controls are specified at some limited number of points throughout a _phase_, and then have their values interpolated to each node in each segment.\n", - "\n", - "### Options for Polynomial Control Variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "om.show_options_table(\"dymos.phase.options.PolynomialControlOptionsDictionary\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Polynomial values are connected to the ODE using the `targets` argument.\n", - "The values of this argument obey the same rules as those for states.\n", - "\n", - "The polynomial control first and second derivatives w.r.t. time may also be connected to the ODE.\n", - "First derivatives of controls in Dymos assume the name `_rate`.\n", - "Second derivatives of controls in Dymos assume the name `_rate2`.\n", - "Control rates are automatically connected if a top-level input of the ODE is named `_rate` or `_rate2`.\n", - "These variables are available in the timeseries output as `timeseries.polynomial_control_rates._rate` and `timeseries.polynomial_control_rates._rate2`, respectively." + "Controls added with `control_type='polynomial'` are added as polynomial controls." ] }, { diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index eee7a99fc..87c3666c2 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -1862,6 +1862,9 @@ def set_polynomial_control_val(self, name, vals=None, time_vals=None, integer specifying the order of the spline interpolator to use. Default is 'linear'. """ + om.issue_warning(f'{self.pathname}: The method `set_polynomial_control_val` is ' + 'deprecated and will be removed in Dymos 2.1.', + category=om.OMDeprecationWarning) if np.isscalar(vals): val = vals else: From fcb54e41f9276604775b52c8e0fb002e0e7c6c0b Mon Sep 17 00:00:00 2001 From: johnjasa Date: Mon, 24 Jun 2024 17:00:41 -0500 Subject: [PATCH 14/17] Addressing PR feedback for controls rework --- benchmark/benchmark_balanced_field.py | 4 +- docs/dymos_book/api/phase_api.ipynb | 6 + .../balanced_field/balanced_field.ipynb | 4 +- .../balanced_field_funccomp.ipynb | 28 ++-- .../ssto_moon_polynomial_controls.ipynb | 30 ++-- .../doc/test_doc_balanced_field_length.py | 4 +- .../test/test_balanced_field_func_comp.py | 4 +- .../test/test_balanced_field_length.py | 6 +- ...st_brachistochrone_control_rate_targets.py | 30 ++-- .../test_brachistochrone_static_gravity.py | 20 +-- ...doc_brachistochrone_polynomial_controls.py | 70 ++++----- .../test/test_state_rate_introspection.py | 12 +- .../shuttle_reentry/test/test_reentry.py | 4 +- .../doc/test_doc_ssto_polynomial_control.py | 6 +- .../test/test_simulate_root_trajectory.py | 6 +- .../test/test_error_estimation.py | 2 +- dymos/phase/phase.py | 143 +++++++++++++++++- dymos/phase/test/test_interp.py | 2 +- dymos/phase/test/test_set_time_options.py | 4 +- dymos/phase/test/test_simulate.py | 8 +- dymos/phase/test/test_timeseries.py | 2 +- dymos/test/test_check_partials.py | 2 +- dymos/test/test_load_case.py | 4 +- dymos/trajectory/test/test_trajectory.py | 6 +- .../test/test_explicit_shooting.py | 4 +- .../linkage/test/linkage_report_ui_test.py | 2 +- .../linkage/test/test_linkage_report.py | 2 +- 27 files changed, 274 insertions(+), 141 deletions(-) diff --git a/benchmark/benchmark_balanced_field.py b/benchmark/benchmark_balanced_field.py index 0df0b5e7c..4b4ff1088 100644 --- a/benchmark/benchmark_balanced_field.py +++ b/benchmark/benchmark_balanced_field.py @@ -66,8 +66,8 @@ def _run_balanced_field_length_problem(tx=dm.GaussLobatto, timeseries=True, sim= rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, - ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, + ref=10, val=[0, 10], control_type='polynomial') if timeseries: rotate.add_timeseries_output('*') diff --git a/docs/dymos_book/api/phase_api.ipynb b/docs/dymos_book/api/phase_api.ipynb index 6ac0cc37d..6ecef50d5 100644 --- a/docs/dymos_book/api/phase_api.ipynb +++ b/docs/dymos_book/api/phase_api.ipynb @@ -226,6 +226,12 @@ " :noindex:\n", "```\n", "\n", + "## set_polynomial_control_options\n", + "```{eval-rst}\n", + " .. automethod:: dymos.Phase.set_polynomial_control_options\n", + " :noindex:\n", + "```\n", + "\n", "## add_parameter\n", "```{eval-rst}\n", " .. automethod:: dymos.Phase.add_parameter\n", diff --git a/docs/dymos_book/examples/balanced_field/balanced_field.ipynb b/docs/dymos_book/examples/balanced_field/balanced_field.ipynb index 8372bd8f9..90c4c3284 100644 --- a/docs/dymos_book/examples/balanced_field/balanced_field.ipynb +++ b/docs/dymos_book/examples/balanced_field/balanced_field.ipynb @@ -513,7 +513,7 @@ "rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n", "rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n", "rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n", - "rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])\n", + "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial')\n", "rotate.add_timeseries_output('*')\n", "\n", "# Fifth Phase: Climb to target speed and altitude at end of runway.\n", @@ -687,7 +687,7 @@ "rotate.set_time_val(initial=35.0, duration=5.0)\n", "rotate.set_state_val('r', [1750, 1800.0])\n", "rotate.set_state_val('v', [80, 85.0])\n", - "rotate.set_polynomial_control_val('alpha', 0.0, units='deg')\n", + "rotate.set_control_val('alpha', 0.0, units='deg')\n", "\n", "climb.set_time_val(initial=30.0, duration=20.0)\n", "climb.set_state_val('r', [5000, 5500.0], units='ft')\n", diff --git a/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb b/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb index 696233bc7..af5557ab6 100644 --- a/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb +++ b/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb @@ -248,19 +248,19 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "active-ipynb", - "remove-input", - "remove-output" - ] - }, - "outputs": [], - "source": [ - "%matplotlib inline" + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "active-ipynb", + "remove-input", + "remove-output" ] + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] }, { "cell_type": "code", @@ -328,7 +328,7 @@ "rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n", "rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n", "rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n", - "rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10])\n", + "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial')\n", "rotate.add_timeseries_output('*')\n", "\n", "# Fifth Phase: Climb to target speed and altitude at end of runway.\n", @@ -437,7 +437,7 @@ "rotate.set_time_val(initial=35.0, duration=5.0)\n", "rotate.set_state_val('r', [1750, 1800.0])\n", "rotate.set_state_val('v', [80, 85.0])\n", - "rotate.set_polynomial_control_val('alpha', 0.0, units='deg')\n", + "rotate.set_control_val('alpha', 0.0, units='deg')\n", "\n", "climb.set_time_val(initial=30.0, duration=20.0)\n", "climb.set_state_val('r', [5000, 5500.0], units='ft')\n", diff --git a/docs/dymos_book/examples/ssto_moon_polynomial_controls/ssto_moon_polynomial_controls.ipynb b/docs/dymos_book/examples/ssto_moon_polynomial_controls/ssto_moon_polynomial_controls.ipynb index 3bda1da63..6c6be7268 100644 --- a/docs/dymos_book/examples/ssto_moon_polynomial_controls/ssto_moon_polynomial_controls.ipynb +++ b/docs/dymos_book/examples/ssto_moon_polynomial_controls/ssto_moon_polynomial_controls.ipynb @@ -80,19 +80,19 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "active-ipynb", - "remove-input", - "remove-output" - ] - }, - "outputs": [], - "source": [ - "%matplotlib inline" + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "active-ipynb", + "remove-input", + "remove-output" ] + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] }, { "cell_type": "code", @@ -286,8 +286,8 @@ "#\n", "# The tangent of theta is modeled as a linear polynomial over the duration of the phase.\n", "#\n", - "phase.add_polynomial_control('tan_theta', order=1, units=None, opt=True,\n", - " targets=['guidance.tan_theta'])\n", + "phase.add_control('tan_theta', order=1, units=None, opt=True,\n", + " targets=['guidance.tan_theta'], control_type='polynomial')\n", "\n", "#\n", "# Parameters values for thrust and specific impulse are design parameters. They are\n", @@ -339,7 +339,7 @@ "phase.set_state_val('vx', [0, 1627.0])\n", "phase.set_state_val('vy', [1.0E-6, 0.0])\n", "phase.set_state_val('m', 50000)\n", - "phase.set_polynomial_control_val('tan_theta', [[0.5 * np.pi], [0.0]])\n", + "phase.set_control_val('tan_theta', [[0.5 * np.pi], [0.0]])\n", "\n", "#\n", "# Solve the problem.\n", diff --git a/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py b/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py index ac5d51027..feed6b438 100644 --- a/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py +++ b/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py @@ -65,7 +65,7 @@ def test_balanced_field_length_for_docs(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. @@ -225,7 +225,7 @@ def test_balanced_field_length_for_docs(self): rotate.set_time_val(initial=70.0, duration=5.0) rotate.set_state_val('r', [1750, 1800.0]) rotate.set_state_val('v', [80, 85.0]) - rotate.set_polynomial_control_val('alpha', 0.0, units='deg') + rotate.set_control_val('alpha', 0.0, units='deg') climb.set_time_val(initial=75.0, duration=15.0) climb.set_state_val('r', [5000, 5500.0], units='ft') diff --git a/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py b/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py index 7e847333f..891e3ad76 100644 --- a/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py +++ b/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py @@ -197,7 +197,7 @@ def _run_problem(self, tx): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. @@ -306,7 +306,7 @@ def _run_problem(self, tx): rotate.set_time_val(initial=35.0, duration=5.0) rotate.set_state_val('r', [1750, 1800.0]) rotate.set_state_val('v', [80, 85.0]) - rotate.set_polynomial_control_val('alpha', 0.0, units='deg') + rotate.set_control_val('alpha', 0.0, units='deg') climb.set_time_val(initial=30.0, duration=20.0) climb.set_state_val('r', [5000, 5500.0], units='ft') diff --git a/dymos/examples/balanced_field/test/test_balanced_field_length.py b/dymos/examples/balanced_field/test/test_balanced_field_length.py index f896ac85b..37bf7e50c 100644 --- a/dymos/examples/balanced_field/test/test_balanced_field_length.py +++ b/dymos/examples/balanced_field/test/test_balanced_field_length.py @@ -59,7 +59,7 @@ def _make_problem(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. @@ -204,7 +204,7 @@ def _make_problem(self): rotate.set_time_val(initial=35.0, duration=5.0) rotate.set_state_val('r', [1750, 1800.0]) rotate.set_state_val('v', [80, 85.0]) - rotate.set_polynomial_control_val('alpha', 0.0, units='deg') + rotate.set_control_val('alpha', 0.0, units='deg') climb.set_time_val(initial=30.0, duration=20.0) climb.set_state_val('r', [5000, 5500.0], units='ft') @@ -321,7 +321,7 @@ def test_default_vals_stick(self): val=rotate.interp(ys=[1750, 1800.0], nodes='state_input')) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0, val=rotate.interp(ys=[80, 85.0], nodes='state_input')) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py b/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py index d5d33c26f..997f127bc 100644 --- a/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py +++ b/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py @@ -530,8 +530,8 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): units='m/s', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - fix_initial=True) + phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, + fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -547,7 +547,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [0, 100]) + phase.set_control_val('theta', [0, 100]) # Solve for the optimal trajectory p.run_driver() @@ -624,8 +624,8 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self): units='m/s', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - fix_initial=True) + phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, + fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -641,7 +641,7 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [0, 100]) + phase.set_control_val('theta', [0, 100]) # Solve for the optimal trajectory p.run_driver() @@ -721,8 +721,8 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): units='m/s', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - rate_targets=['theta_rate'], fix_initial=True) + phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, + rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -738,7 +738,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [0, 100]) + phase.set_control_val('theta', [0, 100]) # Solve for the optimal trajectory p.run_driver() @@ -815,8 +815,8 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self): units='m/s', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_polynomial_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - rate_targets=['theta_rate'], fix_initial=True) + phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, + rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -832,7 +832,7 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [0, 100]) + phase.set_control_val('theta', [0, 100]) # Solve for the optimal trajectory p.run_driver() @@ -912,8 +912,8 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): units='m/s', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_polynomial_control('theta', order=5, units='deg*s**2', lower=0.01, upper=179.9, - rate_targets=None, rate2_targets=['theta_rate'], fix_initial=True) + phase.add_control('theta', order=5, units='deg*s**2', lower=0.01, upper=179.9, + rate_targets=None, rate2_targets=['theta_rate'], fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -929,7 +929,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [0, 10, 40, 60, 80, 100], + phase.set_control_val('theta', [0, 10, 40, 60, 80, 100], time_vals=[0, 0.4, 0.8, 1.2, 1.6, 2.0]) # Solve for the optimal trajectory diff --git a/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py b/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py index 312d6bc63..cf1867398 100644 --- a/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py +++ b/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py @@ -155,7 +155,7 @@ def test_polynomial_control_to_static_target_fails_gl(self): """ Tests that control cannot be connected to target tagged as 'dymos.static_target'. """ p, phase = self._make_problem(dm.GaussLobatto(num_segments=10)) - phase.add_polynomial_control('g', opt=False, order=5) + phase.add_control('g', opt=False, order=5, control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -168,7 +168,7 @@ def test_polynomial_control_to_static_target_fails_radau(self): """ Tests that control cannot be connected to target tagged as 'dymos.static_target'. """ p, phase = self._make_problem(dm.Radau(num_segments=10)) - phase.add_polynomial_control('g', opt=False, order=5) + phase.add_control('g', opt=False, order=5, control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -181,8 +181,8 @@ def test_polynomial_control_rate_to_static_target_fails_gl(self): """ Tests that control cannot be connected to target tagged as 'dymos.static_target'. """ p, phase = self._make_problem(dm.GaussLobatto(num_segments=10)) - phase.add_polynomial_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate_targets=['g']) + phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', + rate_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -195,8 +195,8 @@ def test_polynomial_control_rate_to_static_target_fails_radau(self): """ Tests that control cannot be connected to target tagged as 'dymos.static_target'. """ p, phase = self._make_problem(dm.Radau(num_segments=10)) - phase.add_polynomial_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate_targets=['g']) + phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', + rate_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -209,8 +209,8 @@ def test_polynomial_control_rate2_to_static_target_fails_gl(self): """ Tests that control cannot be connected to target tagged as 'dymos.static_target'. """ p, phase = self._make_problem(dm.GaussLobatto(num_segments=10)) - phase.add_polynomial_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate2_targets=['g']) + phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', + rate2_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -223,8 +223,8 @@ def test_polynomial_control_rate2_to_static_target_fails_radau(self): """ Tests that control cannot be connected to target tagged as 'dymos.static_target'. """ p, phase = self._make_problem(dm.Radau(num_segments=10)) - phase.add_polynomial_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate2_targets=['g']) + phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', + rate2_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() diff --git a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py index b08abda34..166a95c72 100644 --- a/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py +++ b/dymos/examples/brachistochrone/test/test_doc_brachistochrone_polynomial_controls.py @@ -38,7 +38,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -54,7 +54,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -124,7 +124,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -210,7 +210,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -226,7 +226,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -299,7 +299,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -318,7 +318,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -387,7 +387,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -406,7 +406,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -476,7 +476,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -495,7 +495,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -567,7 +567,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -585,7 +585,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -653,7 +653,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -671,7 +671,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -741,7 +741,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -759,7 +759,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -833,7 +833,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -851,7 +851,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -921,7 +921,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -939,7 +939,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -1009,7 +1009,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1027,7 +1027,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -1101,7 +1101,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1119,7 +1119,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -1189,7 +1189,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1207,7 +1207,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -1277,7 +1277,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1295,7 +1295,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -1364,7 +1364,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1380,7 +1380,7 @@ def test_brachistochrone_polynomial_control_gauss_lobatto(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -1420,7 +1420,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1436,7 +1436,7 @@ def test_brachistochrone_polynomial_control_radau(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() @@ -1481,7 +1481,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.add_state('v', fix_initial=True, fix_final=False) - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -1497,7 +1497,7 @@ def test_brachistochrone_polynomial_control_birkhoff(self): phase.set_state_val('x', [0, 10]) phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) - phase.set_polynomial_control_val('theta', [5, 100]) + phase.set_control_val('theta', [5, 100]) # Solve for the optimal trajectory p.run_driver() diff --git a/dymos/examples/brachistochrone/test/test_state_rate_introspection.py b/dymos/examples/brachistochrone/test/test_state_rate_introspection.py index 8667db7fe..d81caafac 100644 --- a/dymos/examples/brachistochrone/test/test_state_rate_introspection.py +++ b/dymos/examples/brachistochrone/test/test_state_rate_introspection.py @@ -401,7 +401,7 @@ def _test_integrate_polynomial_control(self, transcription): targets=['theta']) # Define theta as a control. - phase.add_polynomial_control(name='theta_rate', order=11, units='rad/s', shape=(1,), targets=None) + phase.add_control(name='theta_rate', order=11, units='rad/s', shape=(1,), targets=None, control_type='polynomial') # Minimize final time. phase.add_objective('time', loc='final') @@ -424,7 +424,7 @@ def _test_integrate_polynomial_control(self, transcription): phase.set_state_val('y', [10, 5], units='m') phase.set_state_val('v', [0, 5], units='m/s') phase.set_state_val('int_theta', [0.1, 45], units='deg') - phase.set_polynomial_control_val('theta_rate', 10.0, units='deg/s') + phase.set_control_val('theta_rate', 10.0, units='deg/s') # Run the driver to solve the problem dm.run_problem(p, simulate=True, make_plots=True) @@ -500,7 +500,7 @@ def _test_integrate_polynomial_control_rate(self, transcription): targets=['theta']) # Define theta as a control. - phase.add_polynomial_control(name='theta', order=11, units='rad', shape=(1,), targets=None) + phase.add_control(name='theta', order=11, units='rad', shape=(1,), targets=None, control_type='polynomial') # Force the initial value of the theta polynomial control to equal the initial value of the theta state. traj.add_linkage_constraint(phase_a='phase0', phase_b='phase0', @@ -528,7 +528,7 @@ def _test_integrate_polynomial_control_rate(self, transcription): phase.set_state_val('y', [10, 5], units='m') phase.set_state_val('v', [0, 5], units='m/s') phase.set_state_val('int_theta', [0.1, 45], units='deg') - phase.set_polynomial_control_val('theta', 45.0, units='deg') + phase.set_control_val('theta', 45.0, units='deg') # Run the driver to solve the problem dm.run_problem(p, simulate=True, make_plots=False) @@ -608,7 +608,7 @@ def _test_integrate_polynomial_control_rate2(self, transcription): targets=['theta']) # Define theta as a control. - phase.add_polynomial_control(name='theta', order=11, units='rad', shape=(1,), targets=None) + phase.add_control(name='theta', order=11, units='rad', shape=(1,), targets=None, control_type='polynomial') # Force the initial value of the theta polynomial control to equal the initial value of the theta state. traj.add_linkage_constraint(phase_a='phase0', phase_b='phase0', @@ -646,7 +646,7 @@ def _test_integrate_polynomial_control_rate2(self, transcription): phase.set_state_val('v', [0, 5], units='m/s') phase.set_state_val('int_theta', [0.1, 45], units='deg') phase.set_state_val('int_theta_dot', [0.0, 0.0], units='deg/s') - phase.set_polynomial_control_val('theta', 45.0, units='deg') + phase.set_control_val('theta', 45.0, units='deg') # Run the driver to solve the problem dm.run_problem(p, simulate=True, make_plots=True) diff --git a/dymos/examples/shuttle_reentry/test/test_reentry.py b/dymos/examples/shuttle_reentry/test/test_reentry.py index c28877112..59b2cb871 100644 --- a/dymos/examples/shuttle_reentry/test/test_reentry.py +++ b/dymos/examples/shuttle_reentry/test/test_reentry.py @@ -238,8 +238,8 @@ def test_reentry_mixed_controls(self): rate_source='vdot', targets=['v'], lower=500, ref0=2500, ref=25000) phase0.add_control('alpha', units='rad', opt=True, lower=-np.pi / 2, upper=np.pi / 2) - phase0.add_polynomial_control('beta', order=9, units='rad', opt=True, - lower=-89 * np.pi / 180, upper=1 * np.pi / 180) + phase0.add_control('beta', order=9, units='rad', opt=True, + lower=-89 * np.pi / 180, upper=1 * np.pi / 180, control_type='polynomial') phase0.add_objective('theta', loc='final', ref=-0.01) phase0.add_path_constraint('q', lower=0, upper=70, ref=70) diff --git a/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py b/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py index 1c30937a6..d752a6366 100644 --- a/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py +++ b/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py @@ -201,8 +201,8 @@ def setup(self): # # The tangent of theta is modeled as a linear polynomial over the duration of the phase. # - phase.add_polynomial_control('tan_theta', order=1, units=None, opt=True, - targets=['guidance.tan_theta']) + phase.add_control('tan_theta', order=1, units=None, opt=True, + targets=['guidance.tan_theta'], control_type='polynomial') # # Parameters values for thrust and specific impulse are design parameters. They are @@ -254,7 +254,7 @@ def setup(self): phase.set_state_val('vx', [0, 1627.0]) phase.set_state_val('vy', [1.0E-6, 0.0]) phase.set_state_val('m', 50000) - phase.set_polynomial_control_val('tan_theta', [[0.5 * np.pi], [0.0]]) + phase.set_control_val('tan_theta', [[0.5 * np.pi], [0.0]]) # # Solve the problem. diff --git a/dymos/examples/ssto/test/test_simulate_root_trajectory.py b/dymos/examples/ssto/test/test_simulate_root_trajectory.py index fd6b1588a..2d06a72ff 100644 --- a/dymos/examples/ssto/test/test_simulate_root_trajectory.py +++ b/dymos/examples/ssto/test/test_simulate_root_trajectory.py @@ -206,8 +206,8 @@ def setup(self): # # The tangent of theta is modeled as a linear polynomial over the duration of the phase. # - phase.add_polynomial_control('tan_theta', order=1, units=None, opt=True, - targets=['guidance.tan_theta']) + phase.add_control('tan_theta', order=1, units=None, opt=True, + targets=['guidance.tan_theta'], control_type='polynomial') # # Parameters values for thrust and specific impulse are design parameters. They are @@ -259,7 +259,7 @@ def setup(self): phase.set_state_val('vx', [0, 1627.0]) phase.set_state_val('vy', [1.0E-6, 0.0]) phase.set_state_val('m', 50000) - phase.set_polynomial_control_val('tan_theta', [[0.5 * np.pi], [0.0]]) + phase.set_control_val('tan_theta', [[0.5 * np.pi], [0.0]]) # # Solve the problem. diff --git a/dymos/grid_refinement/test/test_error_estimation.py b/dymos/grid_refinement/test/test_error_estimation.py index fb79f02ec..bbe98c208 100644 --- a/dymos/grid_refinement/test/test_error_estimation.py +++ b/dymos/grid_refinement/test/test_error_estimation.py @@ -49,7 +49,7 @@ def _run_brachistochrone(self, transcription_class=dm.Radau, control_type='contr phase.add_control('theta', continuity=True, rate_continuity=True, units='deg', lower=0.01, upper=179.9) elif control_type == 'polynomial_control': - phase.add_polynomial_control('theta', units='deg', lower=0.01, upper=179.9, order=3) + phase.add_control('theta', units='deg', lower=0.01, upper=179.9, order=3, control_type='polynomial') phase.add_parameter('g', units='m/s**2', val=1.0) diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index 87c3666c2..9c34031e5 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -515,7 +515,7 @@ def check_parameter(self, name): elif name in self.parameter_options: raise ValueError(f'{name} has already been added as a parameter.') - def add_control(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, + def add_control(self, name, order=_unspecified, units=_unspecified, desc=_unspecified, opt=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, rate_targets=_unspecified, rate2_targets=_unspecified, val=_unspecified, shape=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, @@ -523,7 +523,7 @@ def add_control(self, name, control_type=_unspecified, order=_unspecified, units continuity_scaler=_unspecified, continuity_ref=_unspecified, rate_continuity=_unspecified, rate_continuity_scaler=_unspecified, rate_continuity_ref=_unspecified, rate2_continuity=_unspecified, rate2_continuity_scaler=_unspecified, - rate2_continuity_ref=_unspecified): + rate2_continuity_ref=_unspecified, control_type=_unspecified): """ Adds a dynamic control variable to be tied to a parameter in the ODE. @@ -619,7 +619,7 @@ def add_control(self, name, control_type=_unspecified, order=_unspecified, units self.control_options[name] = ControlOptionsDictionary() self.control_options[name]['name'] = name - self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, + self.set_control_options(name, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, fix_final=fix_final, targets=targets, rate_targets=rate_targets, rate2_targets=rate2_targets, val=val, shape=shape, lower=lower, upper=upper, scaler=scaler, adder=adder, ref0=ref0, ref=ref, @@ -629,9 +629,10 @@ def add_control(self, name, control_type=_unspecified, order=_unspecified, units rate_continuity_ref=rate_continuity_ref, rate2_continuity=rate2_continuity, rate2_continuity_scaler=rate2_continuity_scaler, - rate2_continuity_ref=rate2_continuity_ref) + rate2_continuity_ref=rate2_continuity_ref, + control_type=control_type) - def set_control_options(self, name, control_type=_unspecified, order=_unspecified, units=_unspecified, desc=_unspecified, + def set_control_options(self, name, order=_unspecified, units=_unspecified, desc=_unspecified, opt=_unspecified, fix_initial=_unspecified, fix_final=_unspecified, targets=_unspecified, rate_targets=_unspecified, rate2_targets=_unspecified, val=_unspecified, shape=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, @@ -639,7 +640,8 @@ def set_control_options(self, name, control_type=_unspecified, order=_unspecifie continuity_scaler=_unspecified, continuity_ref=_unspecified, rate_continuity=_unspecified, rate_continuity_scaler=_unspecified, rate_continuity_ref=_unspecified, rate2_continuity=_unspecified, - rate2_continuity_scaler=_unspecified, rate2_continuity_ref=_unspecified): + rate2_continuity_scaler=_unspecified, rate2_continuity_ref=_unspecified, + control_type=_unspecified): """ Set options on an existing dynamic control variable in the phase. @@ -897,11 +899,136 @@ def add_polynomial_control(self, name, order, desc=_unspecified, val=_unspecifie control_type = 'polynomial' - self.set_control_options(name, control_type=control_type, order=order, units=units, desc=desc, opt=opt, + self.set_control_options(name, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, fix_final=fix_final, targets=targets, rate_targets=rate_targets, rate2_targets=rate2_targets, val=val, shape=shape, lower=lower, upper=upper, scaler=scaler, adder=adder, ref0=ref0, ref=ref, - continuity=False, rate_continuity=False, rate2_continuity=False) + continuity=False, rate_continuity=False, rate2_continuity=False, + control_type=control_type) + + def set_polynomial_control_options(self, name, order=_unspecified, desc=_unspecified, val=_unspecified, + units=_unspecified, opt=_unspecified, fix_initial=_unspecified, + fix_final=_unspecified, lower=_unspecified, upper=_unspecified, + scaler=_unspecified, adder=_unspecified, ref0=_unspecified, + ref=_unspecified, targets=_unspecified, rate_targets=_unspecified, + rate2_targets=_unspecified, shape=_unspecified): + """ + Set options on an existing polynomial control variable in the phase. + + Parameters + ---------- + name : str + Name of the controllable parameter in the ODE. + order : int + The order of the interpolating polynomial used to represent the control value in + phase tau space. + desc : str + A description of the polynomial control. + val : float or ndarray + Default value of the control at all nodes. If val scalar and the control + is dynamic it will be broadcast. + units : str or None or 0 + Units in which the control variable is defined. If 0, use the units declared + for the parameter in the ODE. + opt : bool + If True (default) the value(s) of this control will be design variables in + the optimization problem, in the path 'phase_name.indep_controls.controls:control_name'. + If False, the values of this control will exist as input controls:{name}. + fix_initial : bool + If True, the given initial value of the polynomial control is not a design variable and + will not be changed during the optimization. + fix_final : bool + If True, the given final value of the polynomial control is not a design variable and + will not be changed during the optimization. + lower : float or ndarray + The lower bound of the control at the nodes of the phase. + upper : float or ndarray + The upper bound of the control at the nodes of the phase. + scaler : float or ndarray + The scaler of the control value at the nodes of the phase. + adder : float or ndarray + The adder of the control value at the nodes of the phase. + ref0 : float or ndarray + The zero-reference value of the control at the nodes of the phase. + ref : float or ndarray + The unit-reference value of the control at the nodes of the phase. + targets : Sequence of str or None + Targets in the ODE to which this polynomial control is connected. + rate_targets : None or str + The name of the parameter in the ODE to which the first time-derivative + of the control value is connected. + rate2_targets : None or str + The name of the parameter in the ODE to which the second time-derivative + of the control value is connected. + shape : Sequence of int + The shape of the control variable at each point in time. + """ + om.issue_warning(f'{self.pathname}: The method `set_polynomial_control_options` is ' + 'deprecated and will be removed in Dymos 2.1. Please use ' + '`set_control_options` with the appropriate options to define a polynomial control.', + category=om.OMDeprecationWarning) + + self.control_options[name]['control_type'] = 'polynomial' + + if order is not _unspecified: + self.control_options[name]['order'] = order + + if units is not _unspecified: + self.control_options[name]['units'] = units + + if opt is not _unspecified: + self.control_options[name]['opt'] = opt + + if desc is not _unspecified: + self.control_options[name]['desc'] = desc + + if targets is not _unspecified: + if isinstance(targets, str): + self.control_options[name]['targets'] = (targets,) + else: + self.control_options[name]['targets'] = targets + + if rate_targets is not _unspecified: + if isinstance(rate_targets, str): + self.control_options[name]['rate_targets'] = (rate_targets,) + else: + self.control_options[name]['rate_targets'] = rate_targets + + if rate2_targets is not _unspecified: + if isinstance(rate2_targets, str): + self.control_options[name]['rate2_targets'] = (rate2_targets,) + else: + self.control_options[name]['rate2_targets'] = rate2_targets + + if val is not _unspecified: + self.control_options[name]['val'] = val + + if shape is not _unspecified: + self.control_options[name]['shape'] = shape + + if fix_initial is not _unspecified: + self.control_options[name]['fix_initial'] = fix_initial + + if fix_final is not _unspecified: + self.control_options[name]['fix_final'] = fix_final + + if lower is not _unspecified: + self.control_options[name]['lower'] = lower + + if upper is not _unspecified: + self.control_options[name]['upper'] = upper + + if scaler is not _unspecified: + self.control_options[name]['scaler'] = scaler + + if adder is not _unspecified: + self.control_options[name]['adder'] = adder + + if ref0 is not _unspecified: + self.control_options[name]['ref0'] = ref0 + + if ref is not _unspecified: + self.control_options[name]['ref'] = ref def add_parameter(self, name, val=_unspecified, units=_unspecified, opt=False, desc=_unspecified, lower=_unspecified, upper=_unspecified, scaler=_unspecified, diff --git a/dymos/phase/test/test_interp.py b/dymos/phase/test/test_interp.py index ae64520d5..9411dbe40 100644 --- a/dymos/phase/test/test_interp.py +++ b/dymos/phase/test/test_interp.py @@ -52,7 +52,7 @@ def test_quadratic_control(self): def test_polynomial_control(self): tx = dm.GaussLobatto(num_segments=8, order=5, compressed=True) phase = dm.Phase(ode_class=BrachistochroneODE, transcription=tx) - phase.add_polynomial_control('u', fix_initial=True, fix_final=True, order=3) + phase.add_control('u', fix_initial=True, fix_final=True, order=3, control_type='polynomial') xs = np.linspace(-10, 10, 100) ys = xs**3 diff --git a/dymos/phase/test/test_set_time_options.py b/dymos/phase/test/test_set_time_options.py index 7a75f98af..0ede124c7 100644 --- a/dymos/phase/test/test_set_time_options.py +++ b/dymos/phase/test/test_set_time_options.py @@ -43,9 +43,9 @@ def test_fixed_time_invalid_options(self): phase.add_state('v', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_polynomial_control('theta', + phase.add_control('theta', order=1, - units='deg', lower=0.01, upper=179.9) + units='deg', lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', val=9.80665, opt=False) diff --git a/dymos/phase/test/test_simulate.py b/dymos/phase/test/test_simulate.py index c6148764f..0c8516496 100644 --- a/dymos/phase/test/test_simulate.py +++ b/dymos/phase/test/test_simulate.py @@ -68,10 +68,10 @@ def test_shaped_params(self): rate_source='Out', solve_segments=False) - main_phase.add_polynomial_control('Thrust', units='N', + main_phase.add_control('Thrust', units='N', targets='Thrust', lower=-3450, upper=-500, - order=5, opt=True) + order=5, opt=True, control_type='polynomial') main_phase.add_objective('impulse', loc='final', ref=-1) @@ -112,10 +112,10 @@ def test_shaped_traj_params(self): rate_source='Out', solve_segments=False) - main_phase.add_polynomial_control('Thrust', units='N', + main_phase.add_control('Thrust', units='N', targets='Thrust', lower=-3450, upper=-500, - order=5, opt=True) + order=5, opt=True, control_type='polynomial') main_phase.add_objective('impulse', loc='final', ref=-1) diff --git a/dymos/phase/test/test_timeseries.py b/dymos/phase/test/test_timeseries.py index c358f8d17..346cd262f 100644 --- a/dymos/phase/test/test_timeseries.py +++ b/dymos/phase/test/test_timeseries.py @@ -427,7 +427,7 @@ def make_problem_brachistochrone(transcription, polynomial_control=False): phase.set_state_options('v', fix_initial=True) if polynomial_control: - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, control_type='polynomial') control_name = 'controls:theta' else: phase.add_control('theta', continuity=True, rate_continuity=True, opt=True, diff --git a/dymos/test/test_check_partials.py b/dymos/test/test_check_partials.py index b1301b46a..2b3799436 100644 --- a/dymos/test/test_check_partials.py +++ b/dymos/test/test_check_partials.py @@ -94,7 +94,7 @@ def balanced_field_partials_radau(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/test/test_load_case.py b/dymos/test/test_load_case.py index 5eb7d54e2..f988fff6a 100644 --- a/dymos/test/test_load_case.py +++ b/dymos/test/test_load_case.py @@ -31,8 +31,8 @@ def setup_problem(trans=dm.GaussLobatto(num_segments=10), polynomial_control=Fal phase.add_control('theta', units='deg', rate_continuity=False, lower=0.01, upper=179.9, fix_final=fix_final_control) else: - phase.add_polynomial_control('theta', order=1, units='deg', lower=0.01, upper=179.9, - fix_final=fix_final_control) + phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, + fix_final=fix_final_control, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) diff --git a/dymos/trajectory/test/test_trajectory.py b/dymos/trajectory/test/test_trajectory.py index 4ead93a52..d46020364 100644 --- a/dymos/trajectory/test/test_trajectory.py +++ b/dymos/trajectory/test/test_trajectory.py @@ -487,7 +487,7 @@ def test_linked_control_to_polynomial_control(self): rate_source='at_dot', units='DU/TU**2') burn2.add_state('deltav', fix_initial=False, fix_final=False, rate_source='deltav_dot', units='DU/TU') - burn2.add_polynomial_control('u1', order=2, units='deg', scaler=0.01, lower=-30, upper=30) + burn2.add_control('u1', order=2, units='deg', scaler=0.01, lower=-30, upper=30, control_type='polynomial') burn2.add_parameter('c', opt=False, val=1.5, units='DU/TU') burn2.add_objective('deltav', loc='final') @@ -884,7 +884,7 @@ def test_linked_polynomial_control_rate(self): rate_source='at_dot', units='DU/TU**2') burn2.add_state('deltav', fix_initial=False, fix_final=False, rate_source='deltav_dot', units='DU/TU') - burn2.add_polynomial_control('u1', units='deg', order=2, scaler=0.01, lower=-30, upper=30) + burn2.add_control('u1', units='deg', order=2, scaler=0.01, lower=-30, upper=30, control_type='polynomial') burn2.add_parameter('c', opt=False, val=1.5, units='DU/TU') burn2.add_objective('deltav', loc='final') @@ -1019,7 +1019,7 @@ def test_linked_polynomial_control_rate2(self): rate_source='at_dot', units='DU/TU**2') burn2.add_state('deltav', fix_initial=False, fix_final=False, rate_source='deltav_dot', units='DU/TU') - burn2.add_polynomial_control('u1', units='deg', order=2, scaler=0.01, lower=-30, upper=30) + burn2.add_control('u1', units='deg', order=2, scaler=0.01, lower=-30, upper=30, control_type='polynomial') burn2.add_parameter('c', opt=False, val=1.5, units='DU/TU') burn2.add_objective('deltav', loc='final') diff --git a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py index cf74ab934..b7269e856 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py @@ -342,8 +342,8 @@ def test_brachistochrone_explicit_shooting_path_constraint_polynomial_control(se phase.set_state_options('v', fix_initial=True) phase.add_parameter('g', val=1.0, units='m/s**2', opt=True, lower=1, upper=9.80665) - phase.add_polynomial_control('theta', order=2, val=45.0, units='deg', opt=True, - lower=1.0E-6, upper=179.9, ref=90.) + phase.add_control('theta', order=2, val=45.0, units='deg', opt=True, + lower=1.0E-6, upper=179.9, ref=90., control_type='polynomial') phase.add_boundary_constraint('x', loc='final', equals=10.0) phase.add_boundary_constraint('y', loc='final', equals=5.0) diff --git a/dymos/visualization/linkage/test/linkage_report_ui_test.py b/dymos/visualization/linkage/test/linkage_report_ui_test.py index 1dd2805d3..b45917e9a 100644 --- a/dymos/visualization/linkage/test/linkage_report_ui_test.py +++ b/dymos/visualization/linkage/test/linkage_report_ui_test.py @@ -294,7 +294,7 @@ def setUp(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/visualization/linkage/test/test_linkage_report.py b/dymos/visualization/linkage/test/test_linkage_report.py index 4aa85ced8..bb9f15a74 100644 --- a/dymos/visualization/linkage/test/test_linkage_report.py +++ b/dymos/visualization/linkage/test/test_linkage_report.py @@ -72,7 +72,7 @@ def test_model_data(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0) - rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. From 35a65e97438c6a1621299a5586d7402d82aef511 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Tue, 25 Jun 2024 10:48:17 -0500 Subject: [PATCH 15/17] Fixing regression --- docs/dymos_book/api/phase_api.ipynb | 8 ++++---- dymos/phase/phase.py | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/dymos_book/api/phase_api.ipynb b/docs/dymos_book/api/phase_api.ipynb index 6ecef50d5..ab5d38a5d 100644 --- a/docs/dymos_book/api/phase_api.ipynb +++ b/docs/dymos_book/api/phase_api.ipynb @@ -220,15 +220,15 @@ " :noindex:\n", "```\n", "\n", - "## set_polynomial_control_val\n", + "## set_polynomial_control_options\n", "```{eval-rst}\n", - " .. automethod:: dymos.Phase.set_polynomial_control_val\n", + " .. automethod:: dymos.Phase.set_polynomial_control_options\n", " :noindex:\n", "```\n", "\n", - "## set_polynomial_control_options\n", + "## set_polynomial_control_val\n", "```{eval-rst}\n", - " .. automethod:: dymos.Phase.set_polynomial_control_options\n", + " .. automethod:: dymos.Phase.set_polynomial_control_val\n", " :noindex:\n", "```\n", "\n", diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index 9c34031e5..4daaa70c6 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -619,6 +619,15 @@ def add_control(self, name, order=_unspecified, units=_unspecified, desc=_unspec self.control_options[name] = ControlOptionsDictionary() self.control_options[name]['name'] = name + # if continuity, rate_continuity, or rate2_continuity are not specified, default to False for cases when control_type is 'polynomial' + if control_type == 'polynomial': + if continuity is _unspecified: + continuity = False + if rate_continuity is _unspecified: + rate_continuity = False + if rate2_continuity is _unspecified: + rate2_continuity = False + self.set_control_options(name, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, fix_final=fix_final, targets=targets, rate_targets=rate_targets, rate2_targets=rate2_targets, val=val, shape=shape, lower=lower, From c0458ad1a73c584480c4f23cf1f18f3bc49b2912 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Tue, 25 Jun 2024 11:25:44 -0500 Subject: [PATCH 16/17] Lint fixes --- .../balanced_field/balanced_field.ipynb | 3 ++- .../balanced_field_funccomp.ipynb | 3 ++- .../doc/test_doc_balanced_field_length.py | 3 ++- .../test/test_balanced_field_func_comp.py | 3 ++- .../test/test_balanced_field_length.py | 6 ++++-- ...test_brachistochrone_control_rate_targets.py | 12 ++++++------ .../test/test_brachistochrone_static_gravity.py | 8 ++++---- .../shuttle_reentry/test/test_reentry.py | 2 +- .../doc/test_doc_ssto_polynomial_control.py | 2 +- .../ssto/test/test_simulate_root_trajectory.py | 2 +- dymos/phase/phase.py | 17 +++++++++-------- dymos/phase/test/test_set_time_options.py | 5 ++--- dymos/phase/test/test_simulate.py | 12 ++++++------ dymos/test/test_check_partials.py | 3 ++- dymos/test/test_load_case.py | 2 +- .../test/test_explicit_shooting.py | 2 +- .../linkage/test/linkage_report_ui_test.py | 3 ++- .../linkage/test/test_linkage_report.py | 3 ++- 18 files changed, 50 insertions(+), 41 deletions(-) diff --git a/docs/dymos_book/examples/balanced_field/balanced_field.ipynb b/docs/dymos_book/examples/balanced_field/balanced_field.ipynb index 90c4c3284..e8e48c08b 100644 --- a/docs/dymos_book/examples/balanced_field/balanced_field.ipynb +++ b/docs/dymos_book/examples/balanced_field/balanced_field.ipynb @@ -513,7 +513,8 @@ "rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n", "rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n", "rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n", - "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial')\n", + "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], +control_type='polynomial')\n", "rotate.add_timeseries_output('*')\n", "\n", "# Fifth Phase: Climb to target speed and altitude at end of runway.\n", diff --git a/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb b/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb index af5557ab6..4b4f71234 100644 --- a/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb +++ b/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb @@ -328,7 +328,8 @@ "rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n", "rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n", "rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n", - "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial')\n", + "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], +control_type='polynomial')\n", "rotate.add_timeseries_output('*')\n", "\n", "# Fifth Phase: Climb to target speed and altitude at end of runway.\n", diff --git a/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py b/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py index feed6b438..595e57a29 100644 --- a/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py +++ b/dymos/examples/balanced_field/doc/test_doc_balanced_field_length.py @@ -65,7 +65,8 @@ def test_balanced_field_length_for_docs(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0) - rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], + control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py b/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py index 891e3ad76..13b2edd6e 100644 --- a/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py +++ b/dymos/examples/balanced_field/test/test_balanced_field_func_comp.py @@ -197,7 +197,8 @@ def _run_problem(self, tx): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0) - rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, + val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/examples/balanced_field/test/test_balanced_field_length.py b/dymos/examples/balanced_field/test/test_balanced_field_length.py index 37bf7e50c..9fd8ca323 100644 --- a/dymos/examples/balanced_field/test/test_balanced_field_length.py +++ b/dymos/examples/balanced_field/test/test_balanced_field_length.py @@ -59,7 +59,8 @@ def _make_problem(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0) - rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, + val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. @@ -321,7 +322,8 @@ def test_default_vals_stick(self): val=rotate.interp(ys=[1750, 1800.0], nodes='state_input')) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0, val=rotate.interp(ys=[80, 85.0], nodes='state_input')) - rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, + val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py b/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py index 997f127bc..13578dea6 100644 --- a/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py +++ b/dymos/examples/brachistochrone/test/test_brachistochrone_control_rate_targets.py @@ -531,7 +531,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): fix_initial=True, fix_final=False, solve_segments=False) phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - fix_initial=True, control_type='polynomial') + fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -625,7 +625,7 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self): fix_initial=True, fix_final=False, solve_segments=False) phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - fix_initial=True, control_type='polynomial') + fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -722,7 +722,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): fix_initial=True, fix_final=False, solve_segments=False) phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial') + rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -816,7 +816,7 @@ def test_brachistochrone_polynomial_control_rate_targets_radau(self): fix_initial=True, fix_final=False, solve_segments=False) phase.add_control('theta', order=3, units='deg*s', lower=0.01, upper=179.9, - rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial') + rate_targets=['theta_rate'], fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -913,7 +913,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): fix_initial=True, fix_final=False, solve_segments=False) phase.add_control('theta', order=5, units='deg*s**2', lower=0.01, upper=179.9, - rate_targets=None, rate2_targets=['theta_rate'], fix_initial=True, control_type='polynomial') + rate_targets=None, rate2_targets=['theta_rate'], fix_initial=True, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) @@ -930,7 +930,7 @@ def test_brachistochrone_polynomial_control_rate_targets_gauss_lobatto(self): phase.set_state_val('y', [10, 5]) phase.set_state_val('v', [0, 9.9]) phase.set_control_val('theta', [0, 10, 40, 60, 80, 100], - time_vals=[0, 0.4, 0.8, 1.2, 1.6, 2.0]) + time_vals=[0, 0.4, 0.8, 1.2, 1.6, 2.0]) # Solve for the optimal trajectory dm.run_problem(p, simulate=True) diff --git a/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py b/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py index cf1867398..5ab0250ff 100644 --- a/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py +++ b/dymos/examples/brachistochrone/test/test_brachistochrone_static_gravity.py @@ -182,7 +182,7 @@ def test_polynomial_control_rate_to_static_target_fails_gl(self): p, phase = self._make_problem(dm.GaussLobatto(num_segments=10)) phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate_targets=['g'], control_type='polynomial') + rate_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -196,7 +196,7 @@ def test_polynomial_control_rate_to_static_target_fails_radau(self): p, phase = self._make_problem(dm.Radau(num_segments=10)) phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate_targets=['g'], control_type='polynomial') + rate_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -210,7 +210,7 @@ def test_polynomial_control_rate2_to_static_target_fails_gl(self): p, phase = self._make_problem(dm.GaussLobatto(num_segments=10)) phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate2_targets=['g'], control_type='polynomial') + rate2_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() @@ -224,7 +224,7 @@ def test_polynomial_control_rate2_to_static_target_fails_radau(self): p, phase = self._make_problem(dm.Radau(num_segments=10)) phase.add_control('foo', opt=False, order=5, shape=(1,), units='m/s', - rate2_targets=['g'], control_type='polynomial') + rate2_targets=['g'], control_type='polynomial') with self.assertRaises(ValueError) as e: p.setup() diff --git a/dymos/examples/shuttle_reentry/test/test_reentry.py b/dymos/examples/shuttle_reentry/test/test_reentry.py index 59b2cb871..b79b0c9e1 100644 --- a/dymos/examples/shuttle_reentry/test/test_reentry.py +++ b/dymos/examples/shuttle_reentry/test/test_reentry.py @@ -239,7 +239,7 @@ def test_reentry_mixed_controls(self): phase0.add_control('alpha', units='rad', opt=True, lower=-np.pi / 2, upper=np.pi / 2) phase0.add_control('beta', order=9, units='rad', opt=True, - lower=-89 * np.pi / 180, upper=1 * np.pi / 180, control_type='polynomial') + lower=-89 * np.pi / 180, upper=1 * np.pi / 180, control_type='polynomial') phase0.add_objective('theta', loc='final', ref=-0.01) phase0.add_path_constraint('q', lower=0, upper=70, ref=70) diff --git a/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py b/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py index d752a6366..d106662a1 100644 --- a/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py +++ b/dymos/examples/ssto/doc/test_doc_ssto_polynomial_control.py @@ -202,7 +202,7 @@ def setup(self): # The tangent of theta is modeled as a linear polynomial over the duration of the phase. # phase.add_control('tan_theta', order=1, units=None, opt=True, - targets=['guidance.tan_theta'], control_type='polynomial') + targets=['guidance.tan_theta'], control_type='polynomial') # # Parameters values for thrust and specific impulse are design parameters. They are diff --git a/dymos/examples/ssto/test/test_simulate_root_trajectory.py b/dymos/examples/ssto/test/test_simulate_root_trajectory.py index 2d06a72ff..759f1e584 100644 --- a/dymos/examples/ssto/test/test_simulate_root_trajectory.py +++ b/dymos/examples/ssto/test/test_simulate_root_trajectory.py @@ -207,7 +207,7 @@ def setup(self): # The tangent of theta is modeled as a linear polynomial over the duration of the phase. # phase.add_control('tan_theta', order=1, units=None, opt=True, - targets=['guidance.tan_theta'], control_type='polynomial') + targets=['guidance.tan_theta'], control_type='polynomial') # # Parameters values for thrust and specific impulse are design parameters. They are diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index 4daaa70c6..8500bae9e 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -532,8 +532,6 @@ def add_control(self, name, order=_unspecified, units=_unspecified, desc=_unspec name : str The name assigned to the control variable. If the ODE has been decorated with parameters, this should be the name of a control in the system. - control_type : str - The type of control variable. Valid options include 'full' and 'polynomial'. order : int The order of the polynomial control variable. This option is invalid if control_type is 'full'. units : str or None @@ -609,6 +607,8 @@ def add_control(self, name, order=_unspecified, units=_unspecified, desc=_unspec rate2_continuity_ref : float or None Reference unit value of the rate2 continuity constraint at segment boundaries, for use in place of rate_continuity_scaler. + control_type : str + The type of control variable. Valid options include 'full' and 'polynomial'. Notes ----- @@ -619,7 +619,8 @@ def add_control(self, name, order=_unspecified, units=_unspecified, desc=_unspec self.control_options[name] = ControlOptionsDictionary() self.control_options[name]['name'] = name - # if continuity, rate_continuity, or rate2_continuity are not specified, default to False for cases when control_type is 'polynomial' + # if continuity, rate_continuity, or rate2_continuity are not specified, + # default to False for cases when control_type is 'polynomial' if control_type == 'polynomial': if continuity is _unspecified: continuity = False @@ -659,8 +660,6 @@ def set_control_options(self, name, order=_unspecified, units=_unspecified, desc name : str The name assigned to the control variable. If the ODE has been decorated with parameters, this should be the name of a control in the system. - control_type : str - The type of control variable. Valid options include 'full' and 'polynomial'. order : int The order of the polynomial control variable. This option is invalid if control_type is 'full'. units : str or None @@ -736,6 +735,8 @@ def set_control_options(self, name, order=_unspecified, units=_unspecified, desc rate2_continuity_ref : float or None Reference unit value of the rate2 continuity constraint at segment boundaries, for use in place of rate_continuity_scaler. + control_type : str + The type of control variable. Valid options include 'full' and 'polynomial'. Notes ----- @@ -973,9 +974,9 @@ def set_polynomial_control_options(self, name, order=_unspecified, desc=_unspeci The shape of the control variable at each point in time. """ om.issue_warning(f'{self.pathname}: The method `set_polynomial_control_options` is ' - 'deprecated and will be removed in Dymos 2.1. Please use ' - '`set_control_options` with the appropriate options to define a polynomial control.', - category=om.OMDeprecationWarning) + 'deprecated and will be removed in Dymos 2.1. Please use ' + '`set_control_options` with the appropriate options to define a polynomial control.', + category=om.OMDeprecationWarning) self.control_options[name]['control_type'] = 'polynomial' diff --git a/dymos/phase/test/test_set_time_options.py b/dymos/phase/test/test_set_time_options.py index 0ede124c7..2be748aaf 100644 --- a/dymos/phase/test/test_set_time_options.py +++ b/dymos/phase/test/test_set_time_options.py @@ -43,9 +43,8 @@ def test_fixed_time_invalid_options(self): phase.add_state('v', fix_initial=True, fix_final=False, solve_segments=False) - phase.add_control('theta', - order=1, - units='deg', lower=0.01, upper=179.9, control_type='polynomial') + phase.add_control('theta', order=1, units='deg', + lower=0.01, upper=179.9, control_type='polynomial') phase.add_parameter('g', units='m/s**2', val=9.80665, opt=False) diff --git a/dymos/phase/test/test_simulate.py b/dymos/phase/test/test_simulate.py index 0c8516496..4a91fd2a7 100644 --- a/dymos/phase/test/test_simulate.py +++ b/dymos/phase/test/test_simulate.py @@ -69,9 +69,9 @@ def test_shaped_params(self): solve_segments=False) main_phase.add_control('Thrust', units='N', - targets='Thrust', - lower=-3450, upper=-500, - order=5, opt=True, control_type='polynomial') + targets='Thrust', + lower=-3450, upper=-500, + order=5, opt=True, control_type='polynomial') main_phase.add_objective('impulse', loc='final', ref=-1) @@ -113,9 +113,9 @@ def test_shaped_traj_params(self): solve_segments=False) main_phase.add_control('Thrust', units='N', - targets='Thrust', - lower=-3450, upper=-500, - order=5, opt=True, control_type='polynomial') + targets='Thrust', + lower=-3450, upper=-500, + order=5, opt=True, control_type='polynomial') main_phase.add_objective('impulse', loc='final', ref=-1) diff --git a/dymos/test/test_check_partials.py b/dymos/test/test_check_partials.py index 2b3799436..aea605252 100644 --- a/dymos/test/test_check_partials.py +++ b/dymos/test/test_check_partials.py @@ -94,7 +94,8 @@ def balanced_field_partials_radau(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0) - rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, + ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/test/test_load_case.py b/dymos/test/test_load_case.py index f988fff6a..7cbab5fe8 100644 --- a/dymos/test/test_load_case.py +++ b/dymos/test/test_load_case.py @@ -32,7 +32,7 @@ def setup_problem(trans=dm.GaussLobatto(num_segments=10), polynomial_control=Fal rate_continuity=False, lower=0.01, upper=179.9, fix_final=fix_final_control) else: phase.add_control('theta', order=1, units='deg', lower=0.01, upper=179.9, - fix_final=fix_final_control, control_type='polynomial') + fix_final=fix_final_control, control_type='polynomial') phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) diff --git a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py index b7269e856..583dd9638 100644 --- a/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py +++ b/dymos/transcriptions/explicit_shooting/test/test_explicit_shooting.py @@ -343,7 +343,7 @@ def test_brachistochrone_explicit_shooting_path_constraint_polynomial_control(se phase.add_parameter('g', val=1.0, units='m/s**2', opt=True, lower=1, upper=9.80665) phase.add_control('theta', order=2, val=45.0, units='deg', opt=True, - lower=1.0E-6, upper=179.9, ref=90., control_type='polynomial') + lower=1.0E-6, upper=179.9, ref=90., control_type='polynomial') phase.add_boundary_constraint('x', loc='final', equals=10.0) phase.add_boundary_constraint('y', loc='final', equals=5.0) diff --git a/dymos/visualization/linkage/test/linkage_report_ui_test.py b/dymos/visualization/linkage/test/linkage_report_ui_test.py index b45917e9a..92ce41ad5 100644 --- a/dymos/visualization/linkage/test/linkage_report_ui_test.py +++ b/dymos/visualization/linkage/test/linkage_report_ui_test.py @@ -294,7 +294,8 @@ def setUp(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0) - rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, + ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. diff --git a/dymos/visualization/linkage/test/test_linkage_report.py b/dymos/visualization/linkage/test/test_linkage_report.py index bb9f15a74..36c5f4c75 100644 --- a/dymos/visualization/linkage/test/test_linkage_report.py +++ b/dymos/visualization/linkage/test/test_linkage_report.py @@ -72,7 +72,8 @@ def test_model_data(self): rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0) - rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], control_type='polynomial') + rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, + ref=10, val=[0, 10], control_type='polynomial') rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. From a1854f5cfe67665de45f7ff5349a7d872a73b778 Mon Sep 17 00:00:00 2001 From: johnjasa Date: Tue, 25 Jun 2024 12:11:14 -0500 Subject: [PATCH 17/17] Fixing jupyterbook docs --- .../examples/balanced_field/balanced_field.ipynb | 4 ++-- .../balanced_field/balanced_field_funccomp.ipynb | 5 +++-- dymos/phase/phase.py | 9 +-------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/dymos_book/examples/balanced_field/balanced_field.ipynb b/docs/dymos_book/examples/balanced_field/balanced_field.ipynb index e8e48c08b..e2908ec4f 100644 --- a/docs/dymos_book/examples/balanced_field/balanced_field.ipynb +++ b/docs/dymos_book/examples/balanced_field/balanced_field.ipynb @@ -513,8 +513,8 @@ "rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n", "rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n", "rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n", - "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], -control_type='polynomial')\n", + "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10,\n", + " ref=10, val=[0, 10], control_type='polynomial')\n", "rotate.add_timeseries_output('*')\n", "\n", "# Fifth Phase: Climb to target speed and altitude at end of runway.\n", diff --git a/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb b/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb index 4b4f71234..126a94621 100644 --- a/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb +++ b/docs/dymos_book/examples/balanced_field/balanced_field_funccomp.ipynb @@ -328,8 +328,9 @@ "rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0)\n", "rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0)\n", "rotate.add_state('v', fix_initial=False, lower=0, ref=100.0, defect_ref=100.0)\n", - "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10], -control_type='polynomial')\n", + "rotate.add_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10,\n", + "ref=10,\n", + " val=[0, 10], control_type='polynomial')\n", "rotate.add_timeseries_output('*')\n", "\n", "# Fifth Phase: Climb to target speed and altitude at end of runway.\n", diff --git a/dymos/phase/phase.py b/dymos/phase/phase.py index 8500bae9e..9bb62f94e 100644 --- a/dymos/phase/phase.py +++ b/dymos/phase/phase.py @@ -619,15 +619,8 @@ def add_control(self, name, order=_unspecified, units=_unspecified, desc=_unspec self.control_options[name] = ControlOptionsDictionary() self.control_options[name]['name'] = name - # if continuity, rate_continuity, or rate2_continuity are not specified, - # default to False for cases when control_type is 'polynomial' if control_type == 'polynomial': - if continuity is _unspecified: - continuity = False - if rate_continuity is _unspecified: - rate_continuity = False - if rate2_continuity is _unspecified: - rate2_continuity = False + continuity = rate_continuity = rate2_continuity = False self.set_control_options(name, order=order, units=units, desc=desc, opt=opt, fix_initial=fix_initial, fix_final=fix_final, targets=targets, rate_targets=rate_targets,