From 7b89fd54cd9d6a3759764d5c161ae663dcb0bd73 Mon Sep 17 00:00:00 2001 From: Baptiste MAZURIE Date: Fri, 2 Oct 2026 18:42:34 +0200 Subject: [PATCH 1/2] switch from gitlab to github for links and CI --- .documentation/_config.yml | 2 +- .../ISSUE_TEMPLATE/default.md | 8 +- .../ISSUE_TEMPLATE/enhancement.md | 6 + .github/workflows/documentation.yml | 96 +++++++++ .github/workflows/openmodelica-tests.yml | 147 +++++++++++++ .gitignore | 5 + .gitlab-ci.yml | 123 +---------- .test/config_ref_openmodelica.json | 27 +++ .test/report.py | 198 ++++++++++++++++++ ContributionsWorkflow.md | 57 +++-- README.md | 11 +- 11 files changed, 523 insertions(+), 157 deletions(-) rename .gitlab/issue_templates/Default.md => .github/ISSUE_TEMPLATE/default.md (85%) rename .gitlab/issue_templates/Enhancement.md => .github/ISSUE_TEMPLATE/enhancement.md (83%) create mode 100644 .github/workflows/documentation.yml create mode 100644 .github/workflows/openmodelica-tests.yml create mode 100644 .test/config_ref_openmodelica.json create mode 100644 .test/report.py diff --git a/.documentation/_config.yml b/.documentation/_config.yml index 26c0be8c..80c6588e 100644 --- a/.documentation/_config.yml +++ b/.documentation/_config.yml @@ -22,7 +22,7 @@ bibtex_bibfiles: # Information about where the book exists on the web repository: - url: https://gitlab.pam-retd.fr/thermosysproandco/ThermoSysPro # Online location of your book + url: https://github.com/EDF-Lab/ThermoSysPro # Online location of your book path_to_book: .documentation # Optional path to your book, relative to the repository root branch: master # Which branch of the repository should be used when creating links (optional) diff --git a/.gitlab/issue_templates/Default.md b/.github/ISSUE_TEMPLATE/default.md similarity index 85% rename from .gitlab/issue_templates/Default.md rename to .github/ISSUE_TEMPLATE/default.md index 7a27f89e..f0dae965 100644 --- a/.gitlab/issue_templates/Default.md +++ b/.github/ISSUE_TEMPLATE/default.md @@ -1,4 +1,10 @@ - +--- +name: Issue +about: Report a bug or ask a question +labels: [] +--- + + # Issue diff --git a/.gitlab/issue_templates/Enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md similarity index 83% rename from .gitlab/issue_templates/Enhancement.md rename to .github/ISSUE_TEMPLATE/enhancement.md index 87b41815..2fba6e2d 100644 --- a/.gitlab/issue_templates/Enhancement.md +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -1,3 +1,9 @@ +--- +name: Change request +about: Propose a new feature or an improvement +labels: [enhancement] +--- + # Change Request ## Description diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..da03b06d --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,96 @@ +# Generate Markdown from the Modelica annotations of ThermoSysPro +# (.documentation/mo_to_book.py), build the Jupyter Book, and deploy it to +# GitHub Pages. +# +# - Push to master, or manual run on master: the book is built and deployed. +# - Pull request: the book is built and, TEMPORARILY (while this workflow is +# being developed), also deployed, replacing the master documentation +# until the next deployment. Pull requests from forks are not deployed +# (no write access). +# TODO: once the workflow is validated, deploy master only: change the two +# DEPLOY conditions below, and restrict again the deployment branches of +# the github-pages environment to master. +# +# Required repository settings: +# - Settings > Pages > Build and deployment > Source: GitHub Actions. +# - TODO temporary, while pull requests are deployed: Settings > Environments +# > github-pages > Deployment branches and tags: allow all branches (by +# default only the default branch can deploy, and pull requests are +# rejected). +# - variable OPENMODELICA_IMAGE (optional): OpenModelica Docker image. + +name: Documentation + +on: + push: + branches: [master] + pull_request: + branches: [master, develop] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ${{ vars.OPENMODELICA_IMAGE || 'openmodelica/openmodelica:v1.26.7-ompython' }} + steps: + # python3-venv is used to avoid installing Python packages as root. + - name: Install system dependencies + run: | + apt-get update + apt-get install -y --no-install-recommends git ca-certificates python3-venv + + - name: Checkout ThermoSysPro + uses: actions/checkout@v4 + + # Docker jobs run as root. OpenModelica refuses to start its ZMQ server + # as root, and omc4py needs that server, so the documentation is + # generated as ciuser. + - name: Install Python dependencies + run: | + useradd --create-home --shell /bin/bash ciuser + chown -R ciuser:ciuser "$GITHUB_WORKSPACE" + su ciuser -c "python3 -m venv ~/venv && . ~/venv/bin/activate && python -m pip install --upgrade pip && python -m pip install -r '$GITHUB_WORKSPACE/.documentation/requirements.txt'" + + # Fail early, with a clear message, if the library cannot be loaded. + - name: Check that OpenModelica loads ThermoSysPro + run: | + omc --version + su ciuser -c ". ~/venv/bin/activate && cd '$GITHUB_WORKSPACE' && python3 -c 'import omc4py; omc = omc4py.open_session(); print(omc.getVersion()); assert omc.loadFile(\"ThermoSysPro/package.mo\"); print(\"Loaded ThermoSysPro with\", len(omc.getClassNames(\"ThermoSysPro\")), \"top-level classes\"); omc.close()'" + + - name: Build the book + run: su ciuser -c ". ~/venv/bin/activate && cd '$GITHUB_WORKSPACE/.documentation' && python3 mo_to_book.py && jupyter-book build ." + + # DEPLOY condition. TODO: temporary, pull requests are deployed while this + # workflow is developed. Once validated, deploy master only: + # if: github.ref == 'refs/heads/master' + - name: Upload GitHub Pages artifact + if: github.event.pull_request.head.repo.full_name == github.repository || github.ref == 'refs/heads/master' + uses: actions/upload-pages-artifact@v3 + with: + path: .documentation/_build/html + + deploy: + needs: build + # DEPLOY condition. TODO: temporary, pull requests are deployed while this + # workflow is developed. Once validated, deploy master only: + # if: github.ref == 'refs/heads/master' + if: github.event.pull_request.head.repo.full_name == github.repository || github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/openmodelica-tests.yml b/.github/workflows/openmodelica-tests.yml new file mode 100644 index 00000000..8e2664c0 --- /dev/null +++ b/.github/workflows/openmodelica-tests.yml @@ -0,0 +1,147 @@ +# Test with OpenModelica the Examples models of ThermoSysPro, using +# ModelicaTests (https://gitlab.pam-retd.fr/thermosysproandco/modelicatests), +# and compare them with a reference version of ThermoSysPro: +# - .test/config_new_openmodelica.json tests the checked out version, +# - .test/config_ref_openmodelica.json tests the reference, cloned by +# ModelicaTests from this repository. +# The reference is the target branch of the pull request (develop, or master +# for hotfix/misc branches). On a push to develop or a manual run, the reference +# is master. On a push to master, the checked out version is tested alone. +# If the reference cannot be tested, the checked out version is tested alone. +# The run summary, written by .test/report.py, shows the results table of the +# ModelicaTests HTML report for the tested version, and the comparison with the +# reference (fixed, regression, results changed). +# +# Required repository settings (Settings > Secrets and variables > Actions): +# - secret MODELICATESTS_TOKEN: GitLab token with read_repository access to +# the ModelicaTests project. +# - variable OPENMODELICA_IMAGE (optional): OpenModelica Docker image, which +# must provide the Modelica Standard Library 4.0.0. +# +# The Dymola tests run on an internal GitLab runner (see .gitlab-ci.yml). +# +# Failing models do not fail the workflow: they are reported in the run +# summary, and in the uploaded HTML/CSV reports and OpenModelica logs. The +# workflow only fails on infrastructure errors (library or MSL not loadable, +# ModelicaTests crash). + +name: OpenModelica tests + +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-openmodelica: + # Pull requests from forks have no access to secrets, so the private + # ModelicaTests repository cannot be cloned: skip the job instead of failing. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + container: + image: ${{ vars.OPENMODELICA_IMAGE || 'openmodelica/openmodelica:v1.26.7-ompython' }} + env: + REF_BRANCH: ${{ github.base_ref || 'master' }} + # Empty when the checked out version is master itself: nothing to compare. + RUN_REF: ${{ (github.event_name == 'pull_request' || github.ref_name != 'master') && 'true' || '' }} + steps: + # git is needed to clone ModelicaTests; python3-venv is used to avoid + # installing Python packages as root. + - name: Install system dependencies + run: | + apt-get update + apt-get install -y --no-install-recommends git ca-certificates python3-venv + + # Checked out at the workspace root, as in the GitLab pipeline: the + # library is then found at ../../ThermoSysPro/package.mo from + # modelicatests/tests. + - name: Checkout ThermoSysPro + uses: actions/checkout@v4 + + - name: Clone ModelicaTests + env: + MODELICATESTS_TOKEN: ${{ secrets.MODELICATESTS_TOKEN }} + run: git clone --depth 1 "https://oauth2:${MODELICATESTS_TOKEN}@gitlab.pam-retd.fr/thermosysproandco/modelicatests.git" modelicatests + + # Copy ThermoSysPro test configuration files into the ModelicaTests test + # directory, where the runner expects them, and point the reference to + # the reference branch of this repository. + - name: Configure ModelicaTests + env: + REF_URL: ${{ github.server_url }}/${{ github.repository }}.git + run: | + cp .test/*.json modelicatests/tests/ + cat > modelicatests/tests/local_config.json <<'JSON' + { + "dymola": { + "egg": "", + "exe": "" + }, + "openmodelica": { + "exe": "/usr/bin/omc" + } + } + JSON + python3 - <<'PY' + import json, os + file = "modelicatests/tests/config_ref_openmodelica.json" + with open(file) as f: + config = json.load(f) + config["libs"]["ThermoSysPro"]["path"].update(url=os.environ["REF_URL"], target=os.environ["REF_BRANCH"]) + with open(file, "w") as f: + json.dump(config, f, indent=4) + PY + if [ -n "$RUN_REF" ]; then echo "Reference: $REF_URL ($REF_BRANCH)"; else echo "No reference: master tested alone"; fi + + # Docker jobs run as root. OpenModelica refuses to start its ZMQ server + # as root, and omc4py needs that server, so the tests run as ciuser. + # If the run with the reference fails, its outputs are removed and the + # checked out version is tested alone (report.py then skips the comparison). + - name: Run ModelicaTests + run: | + useradd --create-home --shell /bin/bash ciuser + chown -R ciuser:ciuser "$GITHUB_WORKSPACE" + su ciuser -c "cd '$GITHUB_WORKSPACE/modelicatests' && python3 -m venv .venv && . .venv/bin/activate && python -m pip install --upgrade pip && python -m pip install ." + cd modelicatests/tests + if [ -z "$RUN_REF" ]; then + su ciuser -c ". ../.venv/bin/activate && python3 run_cli.py config_new_openmodelica.json --ncpus 4 --timeout 120" + else + su ciuser -c ". ../.venv/bin/activate && python3 run_cli.py config_ref_openmodelica.json config_new_openmodelica.json --ncpus 4 --timeout 120" || { + echo "::warning::ModelicaTests failed with the reference ($REF_BRANCH), see the log above: testing the checked out version alone." + rm -rf config_ref_openmodelica.csv config_ref_openmodelica.html config_ref_openmodelica-* TMP_* + su ciuser -c ". ../.venv/bin/activate && python3 run_cli.py config_new_openmodelica.json --ncpus 4 --timeout 120" + } + fi + + # Written to a file first: ciuser cannot write to $GITHUB_STEP_SUMMARY. + - name: Write results summary + if: always() + run: | + su ciuser -c "cd '$GITHUB_WORKSPACE/modelicatests/tests' && . ../.venv/bin/activate && python3 '$GITHUB_WORKSPACE/.test/report.py' config_ref_openmodelica.csv config_new_openmodelica.csv > report.md" + cat modelicatests/tests/report.md >> "$GITHUB_STEP_SUMMARY" + + - name: Upload reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: modelicatests-report + retention-days: 7 + # TMP_*// holds the OpenModelica check, build and simulation + # logs of each model, needed to understand a failure. + path: | + modelicatests/tests/*.csv + modelicatests/tests/*.html + modelicatests/tests/*-junit.xml + modelicatests/tests/report.md + modelicatests/tests/TMP_*/*/*.log + modelicatests/tests/TMP_*/*/*.txt + if-no-files-found: warn diff --git a/.gitignore b/.gitignore index e5321807..7fd26f33 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,8 @@ status* *.c buildlog.txt *.bak-mo + +# Files generated by .documentation/mo_to_book.py and jupyter-book +.documentation/_build/ +.documentation/_toc.yml +.documentation/modelica_docs/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f9f7b314..bd6fbd01 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,9 @@ -# This pipeline has three objectives: -# - Test ThermoSysPro with OpenModelica on the OS repository using GitLab -# runners directly. -# - Test ThermoSysPro with Dymola on an internal VM (PAM) through a mirrored -# internal project, so the runner can access the Dymola licence server. -# - Deploy the ThermoSysPro documentation. +# This pipeline tests ThermoSysPro with Dymola on an internal VM (PAM), through +# the internal mirror project ThermoSysPro_mirror of the GitHub repository, so +# that the runner can access the Dymola licence server. +# +# The OpenModelica tests and the documentation (GitHub Pages) run with GitHub +# Actions: see .github/workflows/. # # The objectives of the test section are: # - Test ThermoSysPro examples within the library @@ -15,66 +15,6 @@ stages: # List of stages for jobs, and their order of execution - test - - build - - deploy - -test-job-openmodelica: - stage: test - # OpenModelica image configured in the GitLab CI/CD variable - # openmodelica_image. - image: "$openmodelica_image" - before_script: - # git is needed to clone ModelicaTests; python3-venv is used to avoid - # installing Python packages as root in the Docker job. - - apt-get update - - apt-get install -y --no-install-recommends git ca-certificates python3-venv - # GitLab Docker jobs run as root by default. OpenModelica/omc4py should be - # run as a regular user, so the tests are launched through ciuser. - - useradd --create-home --shell /bin/bash ciuser - script: - - echo "$CI_PROJECT_NAME" - - echo "$CI_PROJECT_PATH" - - echo "$CI_PROJECT_URL" - - which omc - # Clone ModelicaTests with a token stored as the ThermoSysPro CI/CD - # variable modelicaTests_token. - - git clone https://token:$modelicaTests_token@gitlab.pam-retd.fr/thermosysproandco/modelicatests.git - - cd modelicatests - # Copy ThermoSysPro test configuration files into the ModelicaTests test - # directory, where the runner expects them. - - cp ../.test/*.json tests/ - - | - cat > tests/local_config.json <<'JSON' - { - "dymola": { - "egg": "", - "exe": "" - }, - "openmodelica": { - "exe": "/usr/bin/omc" - } - } - JSON - # Give ciuser ownership of the checkout so OpenModelica and ModelicaTests - # can write simulation outputs, logs and HTML reports. - - chown -R ciuser:ciuser "$CI_PROJECT_DIR" - - | - su ciuser -c "cd '$CI_PROJECT_DIR/modelicatests' && python3 -m venv .venv && . .venv/bin/activate && python -m pip install --upgrade pip && python -m pip install ." - - | - su ciuser -c "cd '$CI_PROJECT_DIR/modelicatests/tests' && . ../.venv/bin/activate && python3 run_cli.py config_new_openmodelica.json --ncpus 7 --timeout 120" - rules: - - if: '$CI_PROJECT_NAME == "ThermoSysPro" && ($CI_COMMIT_BRANCH == "master" || $CI_MERGE_REQUEST_ID)' - artifacts: - when: always - reports: - junit: modelicatests/tests/*-junit.xml - annotations: modelicatests/tests/annotations.json - paths: - - modelicatests/tests/*.csv - - modelicatests/tests/*.html - - modelicatests/tests/*-junit.xml - - modelicatests/tests/annotations.json - test-job-dymola-internal: stage: test @@ -105,54 +45,3 @@ test-job-dymola-internal: - modelicatests*/tests/*.html - modelicatests*/tests/*-junit.xml - modelicatests*/tests/annotations.json - - -# Smoke-test that the Docker image contains enough Python and OpenModelica -# tooling to read the Modelica annotations used to generate the book. -documentation-environment: - stage: test - image: "$openmodelica_image" - script: - # GitLab Docker jobs run as root by default. OpenModelica refuses to start - # its ZMQ server as root, and omc4py needs that server, so use ciuser. - - useradd --create-home --shell /bin/bash ciuser - - chown -R ciuser:ciuser "$CI_PROJECT_DIR" - - omc --version - - python3 --version - - python3 -m pip install -r .documentation/requirements.txt - - python3 -c "import yaml, tqdm; print('Python documentation dependencies are available')" - - su ciuser -c "test -f ThermoSysPro/package.mo" - - su ciuser -c "python3 -c 'import omc4py; omc = omc4py.open_session(); print(omc.getVersion()); assert omc.loadFile(\"ThermoSysPro/package.mo\"); classes = omc.getClassNames(\"ThermoSysPro\"); print(\"Loaded ThermoSysPro with\", len(classes), \"top-level classes\"); omc.close()'" - rules: - - if: '$CI_PROJECT_NAME == "ThermoSysPro" && ($CI_COMMIT_BRANCH == "master" || $CI_MERGE_REQUEST_ID)' - -# Generate Markdown from the Modelica annotations, then build the Jupyter Book. -jupyter-build: - stage: build - image: "$openmodelica_image" - script: - # The documentation generator opens an omc4py session, so it must also run - # as a non-root user for the same OpenModelica ZMQ security reason. - - useradd --create-home --shell /bin/bash ciuser - - chown -R ciuser:ciuser "$CI_PROJECT_DIR" - - python3 -m pip install -r .documentation/requirements.txt - - su ciuser -c "cd .documentation && python3 mo_to_book.py" - - su ciuser -c "cd .documentation && jupyter-book clean ." - - su ciuser -c "cd .documentation && jupyter-book build ." - rules: - - if: '$CI_PROJECT_NAME == "ThermoSysPro" && ($CI_COMMIT_BRANCH == "master" || $CI_MERGE_REQUEST_ID)' - artifacts: - paths: - - .documentation/_build/ - -# GitLab Pages serves files from a top-level public/ directory. -pages: - stage: deploy - image: busybox:latest - script: - - mv .documentation/_build/html public - artifacts: - paths: - - public - rules: - - if: '$CI_PROJECT_NAME == "ThermoSysPro" && $CI_COMMIT_BRANCH == "master"' diff --git a/.test/config_ref_openmodelica.json b/.test/config_ref_openmodelica.json new file mode 100644 index 00000000..1311ea1c --- /dev/null +++ b/.test/config_ref_openmodelica.json @@ -0,0 +1,27 @@ +{ + "software": "OpenModelica", + "dymola": { + "advanced_flags":["Advanced.LogNonLinearIterationVariables=true","Advanced.GenerateBlockTimers=true"] + }, + "openmodelica": { + "modelica_version": "4.0.0", + "compiler_flags":{ + "--matchingAlgorithm":"PFPlusExt", + "-d":"initialization,iterationVars,stateselection,backenddaeinfo,stateselection,aliasConflicts,bltdump" + }, + "simflags":{ + "-jacobian":"coloredNumerical" + } + }, + "libs": { + "ThermoSysPro": { + "path": { + "url" : "https://github.com/EDF-Lab/ThermoSysPro.git", + "target" : "master", + "package_path" : "ThermoSysPro/package.mo" + }, + "models_filter": "(?.html), with the same columns and legend. +2. Comparison with the reference configuration: status of each model on both + versions and, for models simulated in both, the relative differences between + the final values of the saved variables, computed as in the ModelicaTests + HTML comparison (-.html). The 10 largest differences are listed + for each model whose results changed. Skipped if the reference CSV report + does not exist (reference not tested). + +Column order and relative differences come from ModelicaTests' own +post-processing, so this report matches its HTML files. + +Usage, from the ModelicaTests tests directory (TMP_0 = reference, TMP_1 = tested): + python report.py config_ref_openmodelica.csv config_new_openmodelica.csv > report.md +""" + +import argparse +import math +import os + +import DyMat +import numpy as np +import pandas as pd +from ModelicaTests.postprocessing import compute_relative_diff, sort_columns + +PHASES = ["check", "translate", "simulate"] + +# Plain-text version of the ModelicaTests column names (MathJax is not rendered in the run summary) +LABELS = { + "number_variable": "N_var", + "number_equation": "N_eq", + "translation_time": "T_translation [s]", + "warnings": "warnings", + "initialisation_time": "T_init [s]", + "simulation_time": "T_sim [s]", + "failed_time": "T_failed [s]", +} + +LEGEND = """*Key to abbreviated columns:* +N_var: number of variables · N_eq: number of equations (🟠 when different) · +T_translation: translation time · T_init: initialization time · +T_sim: simulation time · T_failed: simulation failure time +""" + + +def to_bool(value): + """Convert True/False strings and numpy booleans to Python booleans.""" + if isinstance(value, str): + return {"True": True, "False": False}.get(value, value) + return bool(value) if isinstance(value, np.bool_) else value + + +def read_results(csv_file): + """Read a ModelicaTests CSV report.""" + df = pd.read_csv(csv_file, sep=";", header=[0, 1], index_col=0) + df.index.name = None + return df + + +def format_cell(value, field): + """Format a cell as in the ModelicaTests HTML report (colors replaced by symbols).""" + value = to_bool(value) + if value is None or (isinstance(value, float) and math.isnan(value)): + return "–" + if isinstance(value, bool) or field == "success": + return {True: "✅", False: "❌", "TimedOut": "⏱️ TimedOut"}.get(value, str(value)) + if field in ("number_variable", "number_equation"): + return str(int(value)) + if isinstance(value, float): + return f"{value:.3f}" + return str(value) + + +def results_table(df, name): + """Markdown version of the ModelicaTests HTML table of one configuration.""" + columns = sort_columns(df.columns) + header = ["Model"] + [phase if field == "success" else LABELS.get(field, field) for phase, field in columns] + lines = [f"## Results of simulation tests for configuration {name}\n", + f"**{sum(to_bool(v) is True for v in df[('simulate', 'success')])}/{len(df)} models simulated.**\n", + "| " + " | ".join(header) + " |", + "|" + "---|" * len(header)] + for model, row in df.iterrows(): + n_var, n_eq = row.get(("check", "number_variable")), row.get(("check", "number_equation")) + mismatch = pd.notna(n_var) and pd.notna(n_eq) and n_var != n_eq + cells = [] + for phase, field in columns: + cell = format_cell(row[(phase, field)], field) + if mismatch and field in ("number_variable", "number_equation"): + cell = "🟠 " + cell + cells.append(cell) + lines.append(f"| `{model}` | " + " | ".join(cells) + " |") + return "\n".join(lines) + "\n\n" + LEGEND + + +def status(row): + """'OK', or the first failing phase of a model.""" + for phase in PHASES: + value = to_bool(row.get((phase, "success"))) + if value is not True: + return "timeout" if value == "TimedOut" else f"{phase} failed" + return "OK" + + +def final_values(mat_file): + """Return (final time, final values) of a result file, without internal variables.""" + res = DyMat.DyMatFile(mat_file) + names = [n for n in res.names() if not n.startswith(("_", "$"))] + values = pd.Series({n: float(res.data(n)[-1]) for n in names}, dtype=float) + end_time = float(res.abscissa(names[0])[0][-1]) if names else math.nan + return end_time, values + + +def diff_symbol(diff): + """Color scale of the ModelicaTests HTML comparison: green <= 0.1, red >= 0.3.""" + return "🟢" if diff <= 0.1 else "🔴" if diff >= 0.3 else "🟠" + + +def compare(ref_mat, new_mat, rtol, names): + """Return (change description or None, markdown details) for a model simulated in both.""" + (ref_end, ref), (new_end, new) = final_values(ref_mat), final_values(new_mat) + common = ref.index.intersection(new.index) + if common.empty: + return "❔ no saved variable to compare", "" + diff = pd.Series(compute_relative_diff(ref[common], new[common])).sort_values(ascending=False) + if not math.isclose(ref_end, new_end, rel_tol=1e-9): + change = f"🔶 end time changed: {ref_end:g} → {new_end:g} s" + elif diff.iloc[0] > rtol: + change = f"🔶 results changed: `{diff.index[0]}` {diff.iloc[0]:.2e}" + else: + return None, "" + rows = [f"| `{var}` | {ref[var]:.6g} | {new[var]:.6g} | {diff_symbol(d)} {d:.4f} |" for var, d in diff.iloc[:10].items()] + table = "\n".join([f"| Variable | {names[0]} | {names[1]} | Relative diff |", "|---|---|---|---|"] + rows) + return change, table + + +def comparison(ref_df, new_df, names, simu_dir, rtol): + """Markdown comparison of the tested configuration with the reference.""" + rows, details = [], [] # rows: (sort key, model, reference status, tested status, change) + for model in sorted(ref_df.index.union(new_df.index)): + r = status(ref_df.loc[model]) if model in ref_df.index else "absent" + n = status(new_df.loc[model]) if model in new_df.index else "absent" + if r == "absent": + change, key = "🆕 new model", 3 + elif n == "absent": + change, key = "🗑️ removed", 4 + elif r != "OK" and n == "OK": + change, key = "✅ fixed", 1 + elif r == "OK" and n != "OK": + change, key = "❌ regression", 0 + elif r != "OK": + change, key = ("⚠️ still failing" if r == n else "⚠️ fails differently"), 2 + else: + mats = [os.path.join(f"{simu_dir}_{i}", model, f"{model}.mat") for i in (0, 1)] + try: + change, table = compare(*mats, rtol, names) + except Exception as error: # missing or unreadable result file + change, table = f"❔ results not compared ({type(error).__name__})", "" + key = 5 if change else 6 + change = change or "= unchanged" + if table: + details.append(f"
{model}: 10 largest relative differences\n\n{table}\n\n
") + rows.append((key, model, r, n, change)) + + counts = {} + for *_, change in rows: + label = change.split(":")[0].split(" (")[0] + counts[label] = counts.get(label, 0) + 1 + lines = [f"## Comparison of simulation test results for configurations {names[0]} - {names[1]}\n", + " · ".join(f"{label}: {count}" for label, count in counts.items()) + "\n", + f"Results are compared on the final values of all saved variables. Relative difference as in " + f"ModelicaTests: |{names[0]} - {names[1]}| / max(|{names[0]}|, |{names[1]}|, 0.01); " + f"results are reported as changed above {rtol:g}.\n", + f"| Model | {names[0]} | {names[1]} | Change |", + "|---|---|---|---|"] + lines += [f"| `{model}` | {r} | {n} | {change} |" for _, model, r, n, change in sorted(rows)] + return "\n".join(lines + [""] + details) + "\n" + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("ref_csv", help="ModelicaTests CSV report of the reference configuration") + parser.add_argument("new_csv", help="ModelicaTests CSV report of the tested configuration") + parser.add_argument("--simu-dir", default="TMP", help="Simulation directory prefix used by ModelicaTests") + parser.add_argument("--rtol", type=float, default=1e-4, help="Relative difference above which results are reported as changed") + args = parser.parse_args() + + names = [os.path.basename(f).removesuffix(".csv") for f in (args.ref_csv, args.new_csv)] + print(results_table(read_results(args.new_csv), names[1])) + if os.path.exists(args.ref_csv): + print(comparison(read_results(args.ref_csv), read_results(args.new_csv), names, args.simu_dir, args.rtol)) + else: + print(f"## Comparison with {names[0]}\n\n⚠️ Not available: the reference was not tested (see the job log).") + + +if __name__ == "__main__": + main() diff --git a/ContributionsWorkflow.md b/ContributionsWorkflow.md index 091bdb86..a31f0548 100644 --- a/ContributionsWorkflow.md +++ b/ContributionsWorkflow.md @@ -1,18 +1,15 @@ # Contribution and Development Guidelines ## First Things First: open an issue -To report a bug, request a new feature, propose a development or for any question, please open an [issue](https://gitlab.pam-retd.fr/thermosysproandco/ThermoSysPro/-/issues). +To report a bug, request a new feature, propose a development or for any question, please open an [issue](https://github.com/EDF-Lab/ThermoSysPro/issues/new/choose) on the GitHub repository. -If you do not have an account on our GitLab platform you can: -- [Preferred option] [Mail us](mailto:contact-thermosyspro@edf.fr) to have your account created. -- [Valid alternative] Open a issue on the [GitHub repository](https://github.com/ThermoSysPro/ThermoSysPro/issues). -- [Last resort] [Mail us](mailto:contact-thermosyspro@edf.fr) your issue so that we can open it on GitLab on your behalf. +If you do not have a GitHub account, you can [mail us](mailto:contact-thermosyspro@edf.fr) your issue so that we can open it on your behalf. ## Development Committee Regular monthly meeting are run by the EDF R&D team responsible for the development of ThermoSysPro. This Committee has the following objectives: - Review new issues, discuss the potential solution, assign them to a developer. -- Approve developments and merge requests. +- Approve developments and pull requests. - Discuss the development roadmap (library main orientations for the future). Partners or issue reporter may be invited to participate to this meeting. @@ -20,9 +17,9 @@ Partners or issue reporter may be invited to participate to this meeting. ## Code Development ### Repository structure -The [`master`](https://gitlab.pam-retd.fr/thermosysproandco/ThermoSysPro/-/tree/master?ref_type=heads) branch contains only official releases. This is the *default* branch of the repository. A *tag* is associated to each release. No development is made directly in this branch (which is *protected*). +The [`master`](https://github.com/EDF-Lab/ThermoSysPro/tree/master) branch contains only official releases. This is the *default* branch of the repository. A *tag* is associated to each release. No development is made directly in this branch (which is *protected*). -The [`develop`](https://gitlab.pam-retd.fr/thermosysproandco/ThermoSysPro/-/tree/develop?ref_type=heads) branch is the receptacle of all developments. This branch is also *protected*, meaning that developments can only be done through `feature`/`fix`/... branches and associated *merge requests*; these branches originate from the `develop` branch and merge later into it. The `develop` branch merges into `master` to originate new official releases. +The [`develop`](https://github.com/EDF-Lab/ThermoSysPro/tree/develop) branch is the receptacle of all developments. This branch is also *protected*, meaning that developments can only be done through `feature`/`fix`/... branches and associated *pull requests*; these branches originate from the `develop` branch and merge later into it. The `develop` branch merges into `master` to originate new official releases. ```mermaid %%{init: {'gitGraph': {'showCommitLabel': false, 'mainBranchName':'master'}} }%% @@ -112,17 +109,17 @@ Here follows a synthetic view of the workflow for contribution: 1. Reporter/Developer: create a new `feature` branch from `develop`. *Hint: the branch can be created from the issue itself (to link the future developments to the issue)*. - It is recommended to directly create a Merge Request for that branch using the appropriate tag depending on the advances: - - `WIP`: Work In Progress. + It is recommended to directly create a *draft* Pull Request for that branch, and to use the appropriate label depending on the advances: + - `WIP`: Work In Progress (draft pull request). - `reviewToMerge`: to be reviewed or ongoing review. - `readyToMerge`: waiting the final approval from the DevCom. 1. Reporter/Developer: realize the necessary developments in as many *atomic* commits as needed. *Hint: cite the issue `#ID` in commit messages to link commits and issue*. -1. Reporter/Developer: create a merge request. +1. Reporter/Developer: create a pull request (or mark the draft pull request as *ready for review*). - → The merge request will be reviewed by the Development Committee in their periodic meeting. + → The pull request will be reviewed by the Development Committee in their periodic meeting. -1. If necessary, comments are added to the merge requests and some additional developments/iterations are requested. -1. DevCom action: The merge request is accepted and the `feature` branch is merged in the `develop` branch. +1. If necessary, comments are added to the pull requests and some additional developments/iterations are requested. +1. DevCom action: The pull request is accepted and the `feature` branch is merged in the `develop` branch. ```mermaid @@ -131,27 +128,21 @@ issue[Open issue] -.-> DC1([DevCom Issue Review]) DC2([DevCom Merge Review]) issue --> branch[feature branch] branch --> Developments -Developments --> mergeR[Merge Request] +Developments --> mergeR[Pull Request] DC2 -.-> Merge[Merge to develop] mergeR --> Merge ``` -### Non-Regression Tests [Work in progress] - - -Every commit pushed to the remote generates the following actions (*CI/CD pipeline*): - -- Run non-regression tests: - - Check, for a set of test models: - - the translation, - - the simulation. - - Verification of results compared to reference calculation. - - Report generation and its storage. -- Mail a notification to: - - the committer, - - the *Dev Committee* members, - - the person in charge of a test model in case of issues for that model. - -The *test models* are the models of the `Example` package of ThermoSysPro and an additional library of *private* models. Please [mail](mailto:contact-thermosyspro@edf.fr) us if you want to include some of your model in the test suite. - \ No newline at end of file +### Non-Regression Tests + +Every pull request targeting `develop` or `master`, and every push to these branches, runs the following actions (*CI/CD pipeline*): + +- Non-regression tests with OpenModelica ([GitHub Actions](https://github.com/EDF-Lab/ThermoSysPro/actions/workflows/openmodelica-tests.yml)): + - Check, translation and simulation of the models of the `Examples` package of ThermoSysPro. + - Comparison with the target branch of the pull request (`master` for a push to `develop`): fixed models, regressions, changed results. + - Report in the run summary, and HTML/CSV reports and OpenModelica logs as artifacts of the run. +- Non-regression tests with Dymola, on an internal EDF runner (GitLab mirror of the repository). +- Generation of the [documentation](https://edf-lab.github.io/ThermoSysPro/) from the Modelica annotations ([GitHub Actions](https://github.com/EDF-Lab/ThermoSysPro/actions/workflows/documentation.yml)), published on GitHub Pages from `master`. + +The *test models* are the models of the `Examples` package of ThermoSysPro and an additional library of *private* models. Please [mail](mailto:contact-thermosyspro@edf.fr) us if you want to include some of your model in the test suite. diff --git a/README.md b/README.md index f7ac7af5..18039733 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ ThermoSysPro has been historically designed for the modeling and simulation of t ## Current release and Compatibilities -Current release of ThermoSysPro is ![GitLab Release](https://img.shields.io/gitlab/v/release/thermosysproandco%2FThermoSysPro?gitlab_url=https%3A%2F%2Fgitlab.pam-retd.fr). \ -It is developed on Dymola. OpenModelica can also be used as the simulation environment. +Current release of ThermoSysPro is [![GitHub Release](https://img.shields.io/github/v/release/EDF-Lab/ThermoSysPro)](https://github.com/EDF-Lab/ThermoSysPro/releases). \ +It is developed on Dymola. OpenModelica can also be used as the simulation environment. \ +[![OpenModelica tests](https://github.com/EDF-Lab/ThermoSysPro/actions/workflows/openmodelica-tests.yml/badge.svg?branch=develop)](https://github.com/EDF-Lab/ThermoSysPro/actions/workflows/openmodelica-tests.yml) [![Documentation](https://github.com/EDF-Lab/ThermoSysPro/actions/workflows/documentation.yml/badge.svg)](https://github.com/EDF-Lab/ThermoSysPro/actions/workflows/documentation.yml) ## Future releases @@ -22,14 +23,14 @@ The development roadmap outlines the planned features and improvements for the T ## Documentation, book and website -The library documentation is available [online](https://thermosyspro.gitlab.io/documentation). It includes material from the book [Modeling and Simulation of Thermal Power Plants with ThermoSysPro](https://www.springer.com/gp/book/9783030051044), which provides an in-depth description of the physics involved in the library.\ +The library documentation is available [online](https://edf-lab.github.io/ThermoSysPro/). It includes material from the book [Modeling and Simulation of Thermal Power Plants with ThermoSysPro](https://www.springer.com/gp/book/9783030051044), which provides an in-depth description of the physics involved in the library.\ More information can be found on [ThermoSysPro website](https://thermosyspro.com). ## How to contribute -Contributions from the community are welcome! You can ask questions, report bugs or request a new feature by opening an issue [here](https://gitlab.pam-retd.fr/thermosysproandco/ThermoSysPro/-/issues/new) [preferred option] or on [GitHub](https://github.com/ThermoSysPro/ThermoSysPro/issues/new) (see our [Contribution Guidelines](https://gitlab.pam-retd.fr/thermosysproandco/ThermoSysPro/-/blob/master/ContributionsWorkflow.md?ref_type=heads#first-things-first-open-an-issue) if needed). +Contributions from the community are welcome! You can ask questions, report bugs or request a new feature by opening an [issue](https://github.com/EDF-Lab/ThermoSysPro/issues/new/choose) (see our [Contribution Guidelines](ContributionsWorkflow.md#first-things-first-open-an-issue) if needed). -More in-depth contributions are also welcome. Please refer to [ThermoSysPro workflow](https://gitlab.pam-retd.fr/thermosysproandco/ThermoSysPro/-/blob/master/ContributionsWorkflow.md?ref_type=heads) for detailed procedures on how to contribute. +More in-depth contributions are also welcome. Please refer to [ThermoSysPro workflow](ContributionsWorkflow.md) for detailed procedures on how to contribute. ## Citation and References From a9b1b9d934f1d1eec3b488c11718b24cd97e95bb Mon Sep 17 00:00:00 2001 From: Baptiste MAZURIE Date: Mon, 12 Oct 2026 18:53:57 +0200 Subject: [PATCH 2/2] documentation deployed only on master --- .github/workflows/documentation.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index da03b06d..ca508196 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -68,9 +68,6 @@ jobs: - name: Build the book run: su ciuser -c ". ~/venv/bin/activate && cd '$GITHUB_WORKSPACE/.documentation' && python3 mo_to_book.py && jupyter-book build ." - # DEPLOY condition. TODO: temporary, pull requests are deployed while this - # workflow is developed. Once validated, deploy master only: - # if: github.ref == 'refs/heads/master' - name: Upload GitHub Pages artifact if: github.event.pull_request.head.repo.full_name == github.repository || github.ref == 'refs/heads/master' uses: actions/upload-pages-artifact@v3 @@ -79,10 +76,8 @@ jobs: deploy: needs: build - # DEPLOY condition. TODO: temporary, pull requests are deployed while this - # workflow is developed. Once validated, deploy master only: - # if: github.ref == 'refs/heads/master' - if: github.event.pull_request.head.repo.full_name == github.repository || github.ref == 'refs/heads/master' + # DEPLOY condition. Once validated, deploy master only: + if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest permissions: pages: write