Code Monkey home page Code Monkey logo

dymos's Introduction

GitHub Actions Test Badge Coveralls Badge PyPI version PyPI Monthly Downloads

OpenMDAO is an open-source high-performance computing platform for systems analysis and multidisciplinary optimization, written in Python. It enables you to decompose your models, making them easier to build and maintain, while still solving them in a tightly coupled manner with efficient parallel numerical methods.

The OpenMDAO project is primarily focused on supporting gradient-based optimization with analytic derivatives to allow you to explore large design spaces with hundreds or thousands of design variables, but the framework also has a number of parallel computing features that can work with gradient-free optimization, mixed-integer nonlinear programming, and traditional design space exploration.

If you are using OpenMDAO, please cite us!

Documentation

Documentation for the latest development version can be found here.

Documentation for all released versions can be found here.

Important Notice

While the API is relatively stable, OpenMDAO remains in active development. There will be periodic changes to the API. User's are encouraged to pin their version of OpenMDAO to a recent release and update periodically.

Install OpenMDAO

You have two options for installing OpenMDAO, (1) from the Python Package Index (PyPI), and (2) from the GitHub repository.

OpenMDAO includes several optional sets of dependencies including: test for installing the developer tools (e.g., testing, coverage), docs for building the documentation and visualization for some extra visualization tools. Specifying all will include all of the optional dependencies.

Install from PyPI

This is the easiest way to install OpenMDAO. To install only the runtime dependencies:

pip install openmdao

To install all the optional dependencies:

pip install openmdao[all]

Install from a Cloned Repository

This allows you to install OpenMDAO from a local copy of the source code.

git clone http://github.com/OpenMDAO/OpenMDAO
cd OpenMDAO
pip install .

If you would like to make changes to OpenMDAO it is recommended you install it in editable mode (i.e., development mode) by adding the -e flag when calling pip, this way any changes you make to the source code will be included when you import OpenMDAO in Python. You will also want to install the packages necessary for running OpenMDAO's tests and documentation generator. You can install everything needed for development by running:

pip install -e OpenMDAO[all]

OpenMDAO Versions

OpenMDAO 3.x.y represents the current, supported version. It requires Python 3.8 or later and is maintained here. To upgrade to the latest release, run:

pip install --upgrade openmdao

OpenMDAO 2.10.x was the last version to support Python 2.x and is no longer supported. To install this older release, run:

pip install "openmdao<3"

OpenMDAO 1.7.4 was an earlier version of OpenMDAO and is also no longer supported. The code repository is now named OpenMDAO1, and has moved here. To install it, run:

pip install "openmdao<2"

The legacy OpenMDAO v0.x (versions 0.13.0 and older) of the OpenMDAO-Framework are here.

Test OpenMDAO

Users are encouraged to run the unit tests to ensure OpenMDAO is performing correctly. In order to do so, you must install the testing dependencies.

  1. Install OpenMDAO and its testing dependencies:

    pip install openmdao[test]

    Alternatively, you can clone the repository, as explained here, and install the development dependencies as described here.

  2. Run tests:

    testflo openmdao -n 1

  3. If everything works correctly, you should see a message stating that there were zero failures. If the tests produce failures, you are encouraged to report them as an issue. If so, please make sure you include your system spec, and include the error message.

    If tests fail, please include your system information, you can obtain that by running the following commands in python and copying the results produced by the last line.

     import platform, sys
    
     info = platform.uname()
     (info.system, info.version), (info.machine, info.processor), sys.version
    

    Which should produce a result similar to:

     (('Windows', '10.0.17134'),
      ('AMD64', 'Intel64 Family 6 Model 94 Stepping 3, GenuineIntel'),
      '3.6.6 | packaged by conda-forge | (default, Jul 26 2018, 11:48:23) ...')
    

Build the Documentation for OpenMDAO

Documentation for the latest version can always be found here, but if you would like to build a local copy you can find instructions to do so here.

dymos's People

Contributors

ale5000 avatar andrewellis55 avatar bbahiam avatar dkilkenny avatar dpsanders avatar ehariton avatar hschilling avatar hwangjt avatar joel-martin avatar johnjasa avatar justinsgray avatar kanekosh avatar kaushikponnapalli avatar kenneth-t-moore avatar m-derra avatar mjfwest avatar naylor-b avatar nsteffen avatar pgkirsch avatar robfalck avatar swryan avatar tadkollar avatar wright avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dymos's Issues

AnalyticPhase

Define an AnalyticPhase for which the solution to the ODE is known analytically. Rather than taking an ODE, take a system that provides a solution for any time.

This would be useful to have on some of the project's we're currently working, so I'm bumping the priority up.

For the initial cut, we need a new Phase called AnalyticPhase that inherits fromPhase and overrides some of its methods.

Handling states

In an analytic phase, states are really just outputs of the form f(x0, t, p).
That is analytic phase will, at least initially, fundamentally just solve the IVP analytically, providing states just as any other output.
We'll maintain an official list of states for the sake of keeping things consistent with other phases in dymos when it comes to linking them together and stuff like that.

We will override the set_state_options method so that it doesn't have a rate_src, but instead has val_src - the output in the ODE that provides the value of the state at a given time.

Handling controls

Initially there will be no support for controls in AnalyticPhase.
Attempting to add a control or polynomial control will raise an exception.
I'm not certain that there's any example where an ODE with dynamic controls has an analytic solution.

TImeseries outputs

Timeseries outputs will still be provided in the form phasename.timeseries.* in order to be consistent with other phases, to support constraints and linkages.

Transcriptions

I don't think it makes sense that AnalyticPhases have transcriptions. In this case we're not really converting an infinite-dimensional optimal control problem to a discrete optimization problem.

Still, the mechanisms in dymos dictate that the transcription helps to setup/configure the phase, so we'll have an AnalyticTranscription that will just automatically be associated with the AnalyticPhase.

The next question is, where do the nodes go?
For this initial cut I think it makes sense that AnalyticPhase has a num_nodes option and provides output nodes by using the formula np.linspace(t_initial, t_initial + t_duration, num_nodes).

Constraints

We should still support path and boundary constraints as we do with the other Phase/transcription types.
From a control viewpoint, this phase is similar to ExplicitShooting, in that it fundamentally is governed by values of the initial states, parameters, initial time and duration.

Possible example cases

  1. Propagating a 2-body orbit using f and g series. Example here

  2. Propagate some simple ODE, such as the one here

Documentation Update

SSTO Linear Tangent docs need to be updated - currently don't contain the correct code.
Multi-phase cannonball contains a math display error that needs to be corrected.

Trajectory Objects

Trajectory objects are Groups which serve to organize a set of one or more phases.

Each Trajectory group consists of two primary subsystems:

  • phases
    A ParallelGroup within the Trajectory that can hold each phase, and potentially evaluate them in parallel
  • linkages
    A PhaseLinkageComp that manages all of the linkage constraints between phases

Trajectory objects as currently envisioned have the following primary methods:

  • add_phase(phase_name, phase_object, **kwargs)

    Phases are added to a trajectory with an add_phase method. Functionally, it's very similar to the add_subsystem method of ParallelGroup. Phases are cached in an internal dictionary _phases, and then actually added to the phases subgroup during setup.

  • link_phase(phases, vars=['*'], locs=('++', '--'))
    Trajectory objects provide a link_phases method that provides an ordering of phases in the trajectory. The goal of link_phases is to make common phase linkages (sequential) trivial, and complicated phase linkages (parallel, multiple branches, etc) tractable.

    Argument phases provides a series of two or more phases that follow one another in sequence. Argument vars provides the name of the time, state, controls, or control rates to be linked. Providing the value '*' in the list here will imply that time and all common state variables between the phases are to be linked. Providing a tuple-pair as one of the elements in vars allows variables of different names to be connected across phases.

    Finally locs provides the location of the connection for each pair of phases in the given sequence, using the --, -+, +-, ++ convention for states and controls.

    Ultimately link_phases should probably provide the same functionality as OTIS's global constraints, where any variables can be connected between two phases.

  • simulate(times, parallel=True)
    Simulate each phase in the trajectory, returning the results of each simulation in a dictionary. Use python's multiprocessing module to simulate each phase simultaneously when possible, to improve performance.

  • get_values(var, phases=None, nodes='all', flat=False)
    Return the values of a given variable at the given nodes through the given sequence of phases in the trajectory. Omitting phases returns the variable in all phases. If not present, return nan for each node in the phase. Might have to think about how we treat variables which may have different shapes in different phases, since plotting a 3-vector from various phases, and then just a 1-vector of nans from a phase where the variable is not present. If flat, rather than returning a dictionary, return a continuous array across phases.

opt_duration=False causes errors

For instance, in the double integrator example, setting opt_duration=False causes the following.

NameError: Output 't_duration' does not exist for connection in 'phase0' from 't_duration' to 'continuity_comp.t_duration'.

Fix some issues with RungeKuttaPhase

Move direction out of phase options and put on state options as time_direction.
Add 'solve_subsystems': True to the default continuity solver options.
Add a test case that uses phase (optimizer constrained) linkages.

implement set_values for GaussLobatto and RadauPS phases

set_values is the complement to get_values. Given a continuous time-series of data, interpolate as necessary and insert into the appropriate state/control values.

set_values should remove the need for the user directly calling phase.interpolate, and therefore has to support a similar option set (xs, ys, interpolation kind). Signature should be

phase.set_values(name, values, units=None, interp=None, interp_xs=None)

name is the dymos variable name, where internally we check to see if it is a time, state, control, or ODE variable.

values are the values to be assigned.

units are the units in which we are assigning the variable. Internally the appropriate unit conversion will occur.

interp is the type of interpolation to perform on the given values. Default should be None, which will raise an error if the given values aren't of the correct length.

interp_xs are the time/independent variable values to use, equivalent to the xs argument to phase.interpolate. If interp is None or 'linear' then xs are not necessary, otherwise an error should be raised in its absence.

Add time_phase variable that provides the current phase elapsed time

Sometimes it is necessary for ODE to know the current elapsed time since a phase started (i.e. an event).

  1. Add a new argument for time_phase targets to set_time_options
  2. Add time_phase output to time_comp to be connected to any time_phase targets given
  3. Allow time_phase to be used in the same contexts as time (boundary constraints, objectives, etc).

default pointer-heritage phases to compressed=True

Compressed transcription seems to be giving a substantial performance advantage over uncompressed. It might even be worth looking into testing if pyoptsparse is ordering linear constraints in the SNOPT-recommended way.

Connection issues when connecting static, shaped input parameters

import numpy as np

from openmdao.api import ExplicitComponent, Group, Problem
from dymos import declare_time, declare_state, declare_parameter, Phase, ODEOptions


n_traj = 4


class MyComp(ExplicitComponent):

    def initialize(self):
        self.options.declare('n_traj', types=int)
        self.options.declare('num_nodes', types=int)

    def setup(self):
        nn = self.options['num_nodes']
        n_traj = self.options['n_traj']
        self.add_input('time', val=np.zeros(nn))
        self.add_input('alpha', shape=np.zeros((n_traj, 2)).shape)

        self.add_output('y', val=np.zeros(nn))

    def compute(self, inputs, outputs):
        pass

    def compute_partials(self, inputs, partials):
        pass

class MyODE(Group):
    ode_options = ODEOptions()
    ode_options.declare_time(units='s', targets = ['comp.time'])
    ode_options.declare_state(name='F', rate_source='comp.y')
    ode_options.declare_parameter(name='alpha', shape=(4,2), targets = 'comp.alpha')

    def initialize(self):   
        self.options.declare('num_nodes', types=int)
        self.options.declare('n_traj', default=2, types=int)

    def setup(self):
        nn = self.options['num_nodes']
        n_traj = self.options['n_traj']

        self.add_subsystem(name='comp',
                           subsys=MyComp(num_nodes=nn,
                                         n_traj=n_traj))

p = Problem(model=Group())

phase = Phase(transcription='gauss-lobatto',
              ode_class=MyODE,
              num_segments=25,
              transcription_order=3,
              compressed=True)

p.model.add_subsystem('phase0', phase)

phase.add_input_parameter('alpha', val=np.ones((n_traj, 2)), units='m')

p.setup()

Results in error:

ValueError: The source indices [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] do not specify a valid shape for the connection 'phase0.input_params.input_parameters:alpha_out' to 'phase0.rhs_disc.comp.alpha'. The target shape is (2, 2) but indices are (50,).

Allow the rate source for a state to be connected to a dynamic control or another state.

Sometimes it's convenient to define the rate of a variable as a control, and then integrate it to keep track of the control value. Currently, doing so requires a 'pass-through' component in the ODE that just echoes the control value as an output.

This change would allow the user to declare_state with a rate_source of None in the ODE, which could then be overridden with a rate_source option to set_state_options.

For some problems, like the double integrator, the problem could potentially be solved without an ODE at all.

Trajectories support design parameters

This enhancement allows trajectories to own design parameters. The design parameters can then be connected to each phase within the trajectory.

Syntax:
trajectory.add_design_parameter(name, val, units=None, targets=None, opt=True, input=False, lower, upper, adder, scaler, ref0, ref)

All options are the same for the phase add_design_parameter method except for targets. If given, targets is a dictionary that maps each phase name to the ODE parameter name to which it is being connected. This allows a single design parameter at the trajectory level to be connected to variables which may have different names in different phases. If targets is None, assume that the given name is also the name of the target parameter in each phase.

Additional state options needed for RungeKuttaPhase and solved pseudospectral phases

Options fixed_initial and fixed_final are not sufficient to determine the direction of integration of each variable. Change the current state option direction (used only by RungeKuttaPhase at the moment) to time_direction.

Options connected_initial and connected_final specify that either the initial state value or final state value is to be connected to another output.

If options fixed_initial or fixed_final are True, then the optimizer owns those values and the initial/final values are not design variables. If they are fixed then they cannot be connected. fixed_final cannot be used in forward shooting (time_direction = 1) and option fixed_initial cannot be used with backward shooting time_direction = -1

If options fixed_initial or fixed_final are False, and the respective connect_initial or connect_final is False, then the initial/final value is governed by the optimizer as a design variable.

There are various combinations of these options that are invalid that need to be checked.

User-defined timeseries interpolation outputs.

Allow the user to specify phase-tau points at which the timeseries outputs should be provided. The timeseries output will then use interpolation to provide results at the desired points.

Phase design parameters support external connection via the `input=True` argument

This change makes phase design parameters consistent with times in that they support external connections via the input=True argument to add_design_parameter.

As with time, once a design parameter is set to be input from an external source, the user is playing by big kid rules. Options on the design parameter dealing with optimization are invalid.

simulate updates

phase.simulate should take times as a number or an iterable.

  • If a number, return that many evenly distributed points across the phase (np.linspace(t_initial, t_initial+t_duration, N).
  • If an iterable, require it to be a monotonic increasing or decreasing sequence of times within the phase at which output is requested>
  • It should be a warning to request an extrapolation of time

Test

Encompasses all work performed leading up to the 1.0.0 major release.

timeseries outputs across a trajectory

Now that the post-processing get_values methods have been replaced by timeseries outputs, a trajectory should have a timeseries output so users can access timeseries across the entire trajectory.

This isn't completely trivial since a state/control variable may not exists in all phases across a trajectory, or more change from being a state in one phase to a control or design parameter in another.

Custom Phase-Wide Control Classes

Add custom control profiles at the phase level.

phase.add_control(..., control_class=X)

The control class will declare it's on design variables, promoted to 'controls:' in the phase.
The control class will provide control values at the control discretization nodes, which will then be connected through the standard control interpolation functions.

timecomp refactor

Use the same time comp in GLM, Gauss-Lobatto, and Radau-PS phases

Replace get_values functionality with a component that provides timeseries outputs

Part 1:

Phases get a new component timeseries_output_comp which aggregates all output variables.

By default, the following timeseries outputs are provided:

  • time
  • time_phase
  • states
  • controls
  • design parameters
  • input parameters
  • constraints
  • objective

User may add additional timeseries outputs using
phase.add_timeseries_output(ode_rel_path)

Outputs are accessible via path_to_phase.outputs:output_name

Part 2:

Remove the complexity of PhaseSimulationResults and TrajSimulationResults by having simulate only record the specified timeseries outputs.

add_input_parameter dimension mismatch

When using add_input_parameter to pass static information from outside of Dymos into a phase, there is a shape mismatch error.

from openmdao.api import Group, ExecComp, Problem
from openmdao.api import DirectSolver, pyOptSparseDriver

from dymos import declare_time, declare_state, declare_parameter
from dymos import Phase
from dymos.utils.lgl import lgl
from dymos.models.eom import FlightPathEOM2D
         
import numpy as np
       
@declare_time(units='s')
@declare_state('v', rate_source='eom.v_dot', units='m/s')
@declare_state('h', rate_source='eom.h_dot', units='m')
@declare_parameter('m', targets='sum.m', units='kg', shape=(2, 2))

class TrajectoryODE(Group):
    def initialize(self):
        self.options.declare('num_nodes', types=int)    
    
    def setup(self):
        nn = self.options['num_nodes']
        
        self.add_subsystem('sum', ExecComp('m_tot = sum(m)', 
                                           m={'value':np.zeros(nn)}, 
                                           m_tot={'value':np.zeros(nn)}))
        
        self.add_subsystem('eom', FlightPathEOM2D(num_nodes=nn))
        
        self.connect('sum.m_tot','eom.m')
        
def BallDrop(optimizer='SLSQP', num_segments=1, transcription_order=5):
    
    p = Problem(model=Group())
    
    p.driver = pyOptSparseDriver()
    p.driver.options['optimizer'] = optimizer
    p.driver.options['dynamic_simul_derivs'] = True

    seg_ends, _ = lgl(num_segments + 1)

    phase = Phase(transcription='radau-ps',
                  ode_class=TrajectoryODE,
                  num_segments=num_segments, transcription_order=transcription_order,
                  segment_ends=seg_ends,
                  )    
    
    p.model.add_subsystem('phase0', phase)
    
    phase.set_time_options(initial_bounds=(0.0, 100.0), duration_bounds=(0.,100.))
    
    phase.set_state_options('h', fix_initial=True, fix_final=True, lower=0.0, units='m')
    phase.set_state_options('v', fix_initial=True, fix_final=False, units='m/s')    

    phase.add_input_parameter('m', val=[[1,1],[2,2]], units='kg')
    
    p.model.linear_solver = DirectSolver()
    
    p.setup(check=True, force_alloc_complex=True)

    p['phase0.t_initial'] = 0.0
    p['phase0.t_duration'] = 100.0

    p['phase0.states:h'] = phase.interpolate(ys=[20, 0], nodes='state_input')
    p['phase0.states:v'] = phase.interpolate(ys=[0, -5], nodes='state_input')

    
    p.run_model()

    print('Time:')
    print(p.get_val('phase0.timeseries.time'))
    print('')
    print('Height:')
    print(p.get_val('phase0.timeseries.states:h'))
    print('')
    print('Velocity:')
    print(p.get_val('phase0.timeseries.states:v'))

if __name__ == '__main__':
    p = BallDrop()
Traceback (most recent call last):
  File "mre_target_shape.py", line 84, in <module>
    p = BallDrop()
  File "mre_target_shape.py", line 62, in BallDrop
    p.setup(check=True, force_alloc_complex=True)
  File "/mnt/c/OMDAO/OpenMDAO.git/openmdao/core/problem.py", line 755, in setup
    derivatives)
  File "/mnt/c/OMDAO/OpenMDAO.git/openmdao/core/system.py", line 697, in _setup
    self._setup_connections(recurse=recurse)
  File "/mnt/c/OMDAO/OpenMDAO.git/openmdao/core/group.py", line 913, in _setup_connections
    subsys._setup_connections(recurse)
  File "/mnt/c/OMDAO/OpenMDAO.git/openmdao/core/group.py", line 1084, in _setup_connections
    len(out_shape), source_dimensions))
ValueError: The source indices [[[0 1]  [2 3]] [[0 1]  [2 3]] [[0 1]  [2 3]] [[0 1]  [2 3]] [[0 1]  [2 3]] [[0 1]  [2 3]]] do not specify a valid shape for the connection 'phase0.input_params.input_parameters:m_out' to 'phase0.rhs_all.sum.m'. The source has 3 dimensions but the indices expect 2.

Add explicit phases

Adds explicit phases which will initially use fixed-step RK4 integration across each segment of a phase.

GLM organization refactor

put GLM phase alongside gauss-lobatto and radau-ps phases in the file structure.
get rid of the ozone subfolder

Replace **kwargs with actual argument list where possible.

This will make it easier for the user to find default values for arguments.
It will change Dymos such that calling set_state_options on the same state will override previous values.

  • set_state_options
  • set_time_options
  • add_path_constraint
  • add_boundary_constraint

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.