From f90a5610d85f39b9194e8ff46164fdc6306ab9ec Mon Sep 17 00:00:00 2001 From: swryan Date: Mon, 24 Feb 2025 08:41:30 -0500 Subject: [PATCH 1/2] publish_workflow --- .github/workflows/publish-image.yml | 64 ++++++++++++++++++++++++++ Dockerfile | 70 +++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 .github/workflows/publish-image.yml create mode 100644 Dockerfile diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml new file mode 100644 index 000000000..9b025049d --- /dev/null +++ b/.github/workflows/publish-image.yml @@ -0,0 +1,64 @@ +name: Publish Image + +on: + release: + types: [published] + + # Run the workflow manually from the Actions tab + workflow_dispatch: + +env: + # Will use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + defaults: + run: + shell: bash -l {0} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-tags: true + + - name: Get tags + run: | + git fetch --unshallow --tags + git describe --tags > tag.txt + echo "TAG=$(cat tag.txt)" >> $GITHUB_ENV + + # Login against the registry + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ env.TAG }} + + # Build and push image with Buildx + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + push: true + context: ${{ inputs.image_name }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..8e81ea9c7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +# Define image with OpenMDAO and Dymos + +FROM ubuntu:24.04 + +SHELL ["/bin/bash", "-c"] + +# Install updates +RUN apt-get update -y && apt-get -y install wget git g++ gfortran make libblas-dev liblapack-dev + +RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb ;\ + apt-get install -y ./google-chrome-stable_current_amd64.deb + +# Create user +ENV USER=omdao +RUN adduser --shell /bin/bash --disabled-password ${USER} +RUN adduser ${USER} sudo +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + +USER ${USER} +WORKDIR /home/${USER} + +# Install Miniforge +RUN wget -q -O Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" ;\ + bash Miniforge3.sh -b ;\ + rm Miniforge3.sh ;\ + export PATH=$HOME/miniforge3/bin:$PATH ;\ + conda init bash + +# Create conda environment +RUN source $HOME/miniforge3/etc/profile.d/conda.sh ;\ + # + # Create conda environment + # + conda create -n mdaowork python=3.12 'numpy<2' scipy cython swig -y ;\ + conda activate mdaowork ;\ + conda install matplotlib graphviz -y ;\ + conda install mpi4py petsc4py=3.20 -y ;\ + python -m pip install pyparsing psutil objgraph plotly pyxdsm pydot ;\ + # + # Install pyoptsparse + # + python -m pip install git+https://github.com/openmdao/build_pyoptsparse ;\ + build_pyoptsparse -v ;\ + # + # Install OpenMDAO + # + git clone https://github.com/OpenMDAO/OpenMDAO.git ;\ + python -m pip install -e 'OpenMDAO[all]' ;\ + # + # Install Dymos + # + git clone https://github.com/OpenMDAO/dymos.git ;\ + python -m pip install -e 'dymos[all]' + +# Modify .bashrc +RUN echo "## Always activate mdaowork environment on startup ##" >> ~/.bashrc ;\ + echo "conda activate mdaowork" >> ~/.bashrc ;\ + echo "" >> ~/.bashrc ;\ + echo "## OpenMPI settings" >> ~/.bashrc ;\ + echo "export OMPI_MCA_rmaps_base_oversubscribe=1" >> ~/.bashrc ;\ + echo "export OMPI_MCA_btl=^openib" >> ~/.bashrc ;\ + echo "" >> ~/.bashrc ;\ + echo "## Required for some newer MPI / libfabric instances" >> ~/.bashrc ;\ + echo "export RDMAV_FORK_SAFE=true" >> ~/.bashrc ;\ + echo "" >> ~/.bashrc + +# Set up a work directory that can be shared with the host operating system +WORKDIR /home/${USER}/work + +ENTRYPOINT ["tail", "-f", "/dev/null"] From 97c8b3fc1a42974ae81b51909f1e97b5ad6b73fe Mon Sep 17 00:00:00 2001 From: Rob Falck Date: Mon, 24 Feb 2025 13:24:08 -0500 Subject: [PATCH 2/2] Fixed a test that was using an incomplete ODE definition that declared partials but didn't bother populating them. Removed this ODE declaration in favor of the full one from elsewhere in the examples. --- .../test/test_add_boundary_constraint.py | 46 +++---------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/dymos/phase/test/test_add_boundary_constraint.py b/dymos/phase/test/test_add_boundary_constraint.py index 4e2b003e2..8df237608 100644 --- a/dymos/phase/test/test_add_boundary_constraint.py +++ b/dymos/phase/test/test_add_boundary_constraint.py @@ -8,50 +8,12 @@ from dymos.utils.misc import om_version import dymos as dm - - -class BrachistochroneVectorStatesODE(om.ExplicitComponent): - def initialize(self): - self.options.declare('num_nodes', types=int) - - def setup(self): - nn = self.options['num_nodes'] - - self.add_input('v', val=np.zeros(nn), desc='velocity', units='m/s') - self.add_input('g', val=9.80665 * np.ones(nn), desc='grav. acceleration', units='m/s/s') - self.add_input('theta', val=np.zeros(nn), desc='angle of wire', units='rad') - - self.add_output('pos_dot', val=np.zeros((nn, 2)), desc='velocity components', units='m/s') - self.add_output('vdot', val=np.zeros(nn), desc='acceleration magnitude', units='m/s**2') - self.add_output('check', val=np.zeros(nn), desc='check solution: v/sin(theta) = constant', units='m/s') - - # Setup partials - arange = np.arange(self.options['num_nodes']) - - self.declare_partials(of='vdot', wrt='g', rows=arange, cols=arange) - self.declare_partials(of='vdot', wrt='theta', rows=arange, cols=arange) - - self.declare_coloring(wrt='*', method='cs', show_sparsity=False) - - def compute(self, inputs, outputs): - theta = inputs['theta'] - cos_theta = np.cos(theta) - sin_theta = np.sin(theta) - g = inputs['g'] - v = inputs['v'] - - outputs['vdot'] = g * cos_theta - outputs['pos_dot'][:, 0] = v * sin_theta - outputs['pos_dot'][:, 1] = -v * cos_theta - - outputs['check'] = v / sin_theta +from dymos.examples.brachistochrone.brachistochrone_vector_states_ode import BrachistochroneVectorStatesODE @use_tempdirs class TestAddBoundaryConstraint(unittest.TestCase): - @unittest.skipIf(om_version()[0] >= (3, 36, 1) and om_version()[0] < (3, 37, 0), - reason='Test is broken in OpenMDAO 3.37.0 interim development.') @require_pyoptsparse(optimizer='SLSQP') def test_simple_no_exception(self): p = om.Problem(model=om.Group()) @@ -91,7 +53,7 @@ def test_simple_no_exception(self): continuity=True, rate_continuity=True, units='deg', lower=0.01, upper=179.9) - phase.add_parameter('g', units='m/s**2', val=9.80665, opt=False) + phase.add_parameter('g', units='m/s**2', val=9, opt=False) # Minimize time at the end of the phase phase.add_objective('time', loc='final', scaler=10) @@ -251,3 +213,7 @@ def test_duplicate_constraint(self): expected = 'Cannot add new final boundary constraint for variable `pos` and indices None. One already exists.' self.assertEqual(expected, str(e.exception)) + + +if __name__ == '__main__': + unittest.main()