From c7c5b2796e1bd7fbdb7a549efd5fff8bfb48ddcb Mon Sep 17 00:00:00 2001 From: Simon Exner <43469235+0815Creeper@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:10:59 +0100 Subject: [PATCH] added working export example --- .github/workflows/Example.yml | 55 ++- .github/workflows/Formatter.yml | 16 + .../jupyter-src/Export-BouncingBall.ipynb | 385 ++++++++++++++++++ 3 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/Formatter.yml create mode 100644 examples/jupyter-src/Export-BouncingBall.ipynb diff --git a/.github/workflows/Example.yml b/.github/workflows/Example.yml index 2d0f15d..b2d08b6 100644 --- a/.github/workflows/Example.yml +++ b/.github/workflows/Example.yml @@ -19,7 +19,7 @@ jobs: fail-fast: false matrix: os: [windows-latest] # ubuntu-latest - file-name: [] + file-name: [Export-BouncingBall] julia-version: ['1.10'] # 1.9 julia-arch: [x64] experimental: [false] # true @@ -89,8 +89,57 @@ jobs: git commit -m "${{ env.CI_COMMIT_MESSAGE }}" git push origin examples || (git reset --soft HEAD~1 && (exit 1)) + pluto: + runs-on: ubuntu-latest + steps: + - name: "Check out repository" + uses: actions/checkout@v4 + + - name: "Set up Julia" + uses: julia-actions/setup-julia@v2 + with: + version: '1.10' + + - name: "Execute pluto notebooks" + if: ${{ hashFiles('examples/pluto-src/') != '' }} + run: julia -e 'using Pkg; Pkg.add("PlutoSliderServer"); using PlutoSliderServer; PlutoSliderServer.export_directory("examples/pluto-src")' + + - name: "auto-commit (retry on merge)" + if: success() && github.event_name != 'pull_request' && github.ref_name == 'main' + uses: nick-fields/retry@v3 + env: + CI_COMMIT_MESSAGE: pluto-examples[${{ github.ref_name }}] + CI_COMMIT_AUTHOR: github-actions[bot] + EXAMPLES_PATH: examples/pluto-src + # Fetch all and clear the stash list. Include all files from the examples folder to the stash and switch the branch. + # Reset the branch and remove all current files in the examples folder. + # Checkout the last stash to restore the new notebooks and apply the stash index to restore all other new files in the folder. + with: + timeout_minutes: 999 + max_attempts: 10 + warning_on_retry: false + shell: bash + command: | + git fetch --all + git stash clear + git stash --include-untracked -- ${{ env.EXAMPLES_PATH }} + git switch examples + git reset --hard origin/examples + rm -r ${{ env.EXAMPLES_PATH }}/* + git checkout stash -f -- ${{ env.EXAMPLES_PATH }} + git stash apply --index + git stash drop + git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}" + git config --global user.email "${{ env.CI_COMMIT_AUTHOR }}@users.noreply.github.com" + git config --global core.autocrlf false + git pull + git reset + git add ${{ env.EXAMPLES_PATH }} + git commit -m "${{ env.CI_COMMIT_MESSAGE }}" + git push origin examples || (git reset --soft HEAD~1 && (exit 1)) + call-docu: - needs: [jupyter] + needs: [jupyter, pluto] if: github.event_name != 'pull_request' && github.ref_name == 'main' runs-on: ubuntu-latest steps: @@ -99,5 +148,5 @@ jobs: uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.FMI_DOC_TRIGGER_PAT }} - repository: 'ThummeTo/FMIExport.jl' + repository: 'ThummeTo/FMI.jl' event-type: trigger-docu diff --git a/.github/workflows/Formatter.yml b/.github/workflows/Formatter.yml new file mode 100644 index 0000000..1465935 --- /dev/null +++ b/.github/workflows/Formatter.yml @@ -0,0 +1,16 @@ +name: Format suggestions +on: + pull_request: + # this argument is not required if you don't use the `suggestion-label` input + types: [ opened, reopened, synchronize, labeled, unlabeled ] + workflow_dispatch: + +jobs: + code-style: + runs-on: ubuntu-latest + steps: + - uses: julia-actions/julia-format@v3 + with: + version: '1' # Set `version` to '1.0.54' if you need to use JuliaFormatter.jl v1.0.54 (default: '1') + suggestion-label: 'format-suggest' # leave this unset or empty to show suggestions for all PRs + diff --git a/examples/jupyter-src/Export-BouncingBall.ipynb b/examples/jupyter-src/Export-BouncingBall.ipynb new file mode 100644 index 0000000..421c5ca --- /dev/null +++ b/examples/jupyter-src/Export-BouncingBall.ipynb @@ -0,0 +1,385 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "db4395fe-0874-4976-a0de-206787f79e77", + "metadata": {}, + "source": [ + "# Create a Bouncing Ball FMU\n", + "\n", + "Tutorial by Johannes Stoljar, Tobias Thummerer, Simon Exner | Last edit: October 29 2024\n", + "\n", + "## License" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b5524a2-a822-431b-a3d0-d9b417b64c87", + "metadata": {}, + "outputs": [], + "source": [ + "# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher, Johannes Stoljar\n", + "# Licensed under the MIT license.\n", + "# See LICENSE (https://github.com/thummeto/FMIExport.jl/blob/main/LICENSE) file in the project root for details." + ] + }, + { + "cell_type": "markdown", + "id": "684f4ee9-af54-4781-a49d-3f5d0d89c4fe", + "metadata": {}, + "source": [ + "## Motivation\n", + "\n", + "This Julia Package FMIExport.jl is motivated by the export of simulation models in Julia. Here the FMI specification is implemented. FMI (Functional Mock-up Interface) is a free standard ([fmi-standard.org](https://fmi-standard.org)) that defines a container and an interface to exchange dynamic models using a combination of XML files, binaries and C code zipped into a single file. The user is able to create own FMUs (Functional Mock-up Units)." + ] + }, + { + "cell_type": "markdown", + "id": "be2ac71e-a3b0-4bc3-91d9-a4f0ec2a4377", + "metadata": {}, + "source": [ + "## Target group\n", + "\n", + "The example is primarily intended for users who work in the field of simulations. The example wants to show how simple it is to export FMUs in Julia." + ] + }, + { + "cell_type": "markdown", + "id": "7e21f7c8-d9d7-4259-9729-a69c6605b26d", + "metadata": {}, + "source": [ + "## Introduction to the example\n", + "\n", + "This example shows how to export a FMU from julia-code. It uses the BouncingBall FMU, that can be found on the main branch of FMIExport in [examples/FMI2/BouncingBall](https://github.com/ThummeTo/FMIExport.jl/tree/main/examples/FMI2/BouncingBall). This notebook will show you how to export it." + ] + }, + { + "cell_type": "markdown", + "id": "18e579c8-31c9-4753-8079-4bb1f082439b", + "metadata": {}, + "source": [ + "## Installation prerequisites\n", + "\n", + "| | Description | Command | Alternative |\n", + "|:----|:----------------------------------|:--------------------------|:-----------------------------------------------|\n", + "| 1. | Enter Package Manager via | ] | |\n", + "| 2. | Install FMIExport via | add FMIExport | add \"https://github.com/ThummeTo/FMIExport.jl\" |\n", + "| 3. | Install FMIBuild via | add FMIBuild | add \"https://github.com/ThummeTo/FMIBuild.jl\" |" + ] + }, + { + "cell_type": "markdown", + "id": "c354487b-99e1-4428-ad6f-cc535008df78", + "metadata": {}, + "source": [ + "## REPL-commands or build-script\n", + "\n", + "The way to do this usually will be the REPL, but if you plan on exporting FMUs in an automated way, you may want to use a jl script containing the following commands.\n", + "To run this example, the previously installed packages must be included." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1acea4a0-3817-4559-a47f-d880cc78d3e4", + "metadata": {}, + "outputs": [], + "source": [ + "using FMIExport\n", + "using FMIBuild: saveFMU" + ] + }, + { + "cell_type": "markdown", + "id": "a42e20da-526a-4d13-aeb4-6c758fc38003", + "metadata": {}, + "source": [ + "next we have to define where to put the generated files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "edf9d24c-ee04-4371-9d62-70c6b6dfefa1", + "metadata": {}, + "outputs": [], + "source": [ + "tmpDir = mktempdir(; prefix=\"fmibuildjl_test_\", cleanup=false) \n", + "@info \"Saving example files at: $(tmpDir)\"\n", + "fmu_save_path = joinpath(tmpDir, \"BouncingBall.fmu\") " + ] + }, + { + "cell_type": "markdown", + "id": "8fb29e5a-0384-499c-807b-302a49193c95", + "metadata": {}, + "source": [ + "Remember, that we use the FMU-source stored at [examples/FMI2/BouncingBall](https://github.com/ThummeTo/FMIExport.jl/tree/main/examples/FMI2/BouncingBall). If you execute this notebook locally, make shure to ajust the fmu_source_path to where your FMU-Package resides. **It is important, that an absolute path is provided!** For this notebook to work in the automated bulid pipeline, this absolute path is obtained by the following instructions. If you run this example locally, you can provide the path manually, just make shure you use the correct directory seperator or just use just use julias `joinpath` function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb90aa47-5203-4480-bc5c-fa79ed240a6f", + "metadata": {}, + "outputs": [], + "source": [ + "working_dir = pwd() # current working directory\n", + "println(string(\"pwd() returns: \", working_dir))\n", + "\n", + "package_dir = split(working_dir, joinpath(\"examples\", \"jupyter-src\"))[1] # remove everything after and including \"examples\\jupyter-src\"\n", + "println(string(\"package_dir is \", package_dir))\n", + "\n", + "fmu_source_package = joinpath(package_dir, \"examples\", \"FMI2\", \"BouncingBall\") # add correct relative path\n", + "println(string(\"fmu_source_package is \", fmu_source_package))\n", + "\n", + "fmu_source_path = joinpath(fmu_source_package, \"src\", \"BouncingBall.jl\") # add correct relative path\n", + "println(string(\"fmu_source_path is \", fmu_source_path))" + ] + }, + { + "cell_type": "markdown", + "id": "625f5898-c269-4146-8284-c1bd0e005819", + "metadata": {}, + "source": [ + "TODO The following codecell contains *workardound* code that will be obsolete with the next release" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d86f0783-05fd-4cb3-9be6-5997ab9cfbdd", + "metadata": {}, + "outputs": [], + "source": [ + "using FMIExport.FMIBase.FMICore: fmi2True, fmi2False \n", + "\n", + "EPS = 1e-6\n", + "\n", + "FMU_FCT_INIT = function()\n", + " m = 1.0 # ball mass\n", + " r = 0.0 # ball radius\n", + " d = 0.7 # ball collision damping\n", + " v_min = 1e-1 # ball minimum velocity\n", + " g = 9.81 # gravity constant \n", + " sticking = fmi2False\n", + "\n", + " s = 1.0 # ball position\n", + " v = 0.0 # ball velocity\n", + " a = 0.0 # ball acceleration\n", + "\n", + " t = 0.0 \n", + " x_c = [s, v] \n", + " ẋ_c = [v, a]\n", + " x_d = [sticking]\n", + " u = []\n", + " p = [m, r, d, v_min, g]\n", + "\n", + " return (t, x_c, ẋ_c, x_d, u, p)\n", + "end\n", + "\n", + "FMU_FCT_EVALUATE = function(t, x_c, ẋ_c, x_d, u, p, eventMode)\n", + " m, r, d, v_min, g = p\n", + " s, v = x_c\n", + " sticking = x_d[1]\n", + " _, a = ẋ_c\n", + "\n", + " if sticking == fmi2True\n", + " a = 0.0\n", + " elseif sticking == fmi2False\n", + " if eventMode\n", + " if s < r && v < 0.0\n", + " s = r + EPS # so that indicator is not triggered again\n", + " v = -v*d \n", + " \n", + " # stop bouncing to prevent high frequency bouncing (and maybe tunneling the floor)\n", + " if abs(v) < v_min\n", + " sticking = fmi2True\n", + " v = 0.0\n", + " end\n", + " end\n", + " else\n", + " # no specials in continuos time mode\n", + " end\n", + "\n", + " a = (m * -g) / m # the system's physical equation (a little longer than necessary)\n", + " else\n", + " @error \"Unknown value for `sticking` == $(sticking).\"\n", + " return (x_c, ẋ_c, x_d, p)\n", + " end\n", + "\n", + " x_c = [s, v]\n", + " ẋ_c = [v, a]\n", + " x_d = [sticking]\n", + " p = [m, r, d, v_min, g]\n", + "\n", + " return (x_c, ẋ_c, x_d, p) # evaluation can't change discrete state!\n", + "end\n", + "\n", + "FMU_FCT_OUTPUT = function(t, x_c, ẋ_c, x_d, u, p)\n", + " m, r, d, v_min, g = p\n", + " s, v = x_c\n", + " _, a = ẋ_c\n", + " sticking = x_d[1]\n", + "\n", + " y = [s]\n", + "\n", + " return y\n", + "end\n", + "\n", + "FMU_FCT_EVENT = function(t, x_c, ẋ_c, x_d, u, p)\n", + " m, r, d, v_min, g = p\n", + " s, v = x_c\n", + " _, a = ẋ_c\n", + " sticking = x_d[1]\n", + " \n", + " if sticking == fmi2True\n", + " z1 = 1.0 # event 1: ball stay-on-ground\n", + " else\n", + " z1 = (s-r) # event 1: ball hits ground \n", + " end\n", + "\n", + " z = [z1]\n", + "\n", + " return z\n", + "end\n", + "FMIBUILD_CONSTRUCTOR = function(resPath=\"\")\n", + " fmu = fmi2CreateSimple(initializationFct=FMU_FCT_INIT,\n", + " evaluationFct=FMU_FCT_EVALUATE,\n", + " outputFct=FMU_FCT_OUTPUT,\n", + " eventFct=FMU_FCT_EVENT)\n", + "\n", + " fmu.modelDescription.modelName = \"BouncingBall\"\n", + "\n", + " # modes \n", + " fmi2ModelDescriptionAddModelExchange(fmu.modelDescription, \"BouncingBall\")\n", + "\n", + " # states [2]\n", + " fmi2AddStateAndDerivative(fmu, \"ball.s\"; stateDescr=\"Absolute position of ball center of mass\", derivativeDescr=\"Absolute velocity of ball center of mass\")\n", + " fmi2AddStateAndDerivative(fmu, \"ball.v\"; stateDescr=\"Absolute velocity of ball center of mass\", derivativeDescr=\"Absolute acceleration of ball center of mass\")\n", + "\n", + " # discrete state [1]\n", + " fmi2AddIntegerDiscreteState(fmu, \"sticking\"; description=\"Indicator (boolean) if the mass is sticking on the ground, as soon as abs(v) < v_min\")\n", + "\n", + " # outputs [1]\n", + " fmi2AddRealOutput(fmu, \"ball.s_out\"; description=\"Absolute position of ball center of mass\")\n", + "\n", + " # parameters [5]\n", + " fmi2AddRealParameter(fmu, \"m\"; description=\"Mass of ball\")\n", + " fmi2AddRealParameter(fmu, \"r\"; description=\"Radius of ball\")\n", + " fmi2AddRealParameter(fmu, \"d\"; description=\"Collision damping constant (velocity fraction after hitting the ground)\")\n", + " fmi2AddRealParameter(fmu, \"v_min\"; description=\"Minimal ball velocity to enter on-ground-state\")\n", + " fmi2AddRealParameter(fmu, \"g\"; description=\"Gravity constant\")\n", + "\n", + " fmi2AddEventIndicator(fmu)\n", + "\n", + " return fmu\n", + "end\n", + "fmu = FMIBUILD_CONSTRUCTOR()" + ] + }, + { + "cell_type": "markdown", + "id": "95a4ce82-9320-4776-b382-d9fe650d8d83", + "metadata": {}, + "source": [ + "TODO? It is questionable if this is the job of the library or the user... Currently it is not implemented and therefor the job of the user\n", + "\n", + "We need to make shure the fmu_source_package is instantiated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e04c11a-2afa-4d91-8bfc-1595aefa1aa6", + "metadata": {}, + "outputs": [], + "source": [ + "using Pkg\n", + "notebook_env = Base.active_project(); # save current enviroment to return to it after we are done\n", + "Pkg.activate(fmu_source_package); # activate the FMUs enviroment\n", + "\n", + "# make shure to use the same FMI source as in the enviroment of this example (\"notebook_env\"). \n", + "# As this example is automattically built using the local FMIExport package and not the one from the Juila registry, we need to add it using \"develop\". \n", + "Pkg.develop(PackageSpec(path=package_dir)); # If you added FMIExport using \"add FMIExport\", you have to remove this line and use instantiate instead.\n", + "# Pkg.instantiate(); # instantiate the FMUs enviroment only if develop was not previously called\n", + "\n", + "Pkg.activate(notebook_env); # return to the original notebooks enviroment" + ] + }, + { + "cell_type": "markdown", + "id": "c3c6ffee-d62f-40a1-826f-bccb5caffc36", + "metadata": {}, + "source": [ + "That is all the preperation, that was necessary. Now we can export the FMU. \n", + "\n", + "TODO The following codecell contains *workardound* code that will need to be modified with the next release" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec7fe879-5ad4-4053-9676-eaf933b13804", + "metadata": {}, + "outputs": [], + "source": [ + "saveFMU(fmu, fmu_save_path, fmu_source_path; debug=false, compress=false) # feel free to set debug true, disabled for documentation building\n", + "#saveFMU(fmu_save_path, fmu_source_path; debug=false, compress=false) this meight be the format after the next release" + ] + }, + { + "cell_type": "markdown", + "id": "a511bfeb-df91-4aaf-85cf-5bb6b78a1b3f", + "metadata": {}, + "source": [ + "Now we will grab the generated FMU and move it to a path, where it will be included in this documentation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a4261b9-b5aa-4932-a5a5-6af70f5033a6", + "metadata": {}, + "outputs": [], + "source": [ + "mkpath(\"Export-BouncingBall_files\")\n", + "cp(fmu_save_path, joinpath(\"Export-BouncingBall_files\", \"BouncingBall.fmu\"))" + ] + }, + { + "cell_type": "markdown", + "id": "b4cee9f8-ec5c-433c-8f59-c1911583a305", + "metadata": {}, + "source": [ + "The FMU files generated by this example can be downloaded [here](https://github.com/ThummeTo/FMIExport.jl/tree/examples/examples/jupyter-src/Export-BouncingBall_files)" + ] + }, + { + "cell_type": "markdown", + "id": "1f982a2c-f7e6-45b9-a7d0-2e315ee0404e", + "metadata": {}, + "source": [ + "One current limitation of Julia-FMUs is, that they can not be imported back into Julia, as it is currently not allowed having two Julia-sys-images existing at the same time within the same process. (The Julia FMU comes bundeled with its own image) \n", + "\n", + "TODO Therefore we will test our generated FMU in Python unsing FMPy." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.10.6", + "language": "julia", + "name": "julia-1.10" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.10.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}