diff --git a/.github/workflows/centos7.yml b/.github/workflows/centos7.yml
index fd0f4ecc27..268f88a70d 100644
--- a/.github/workflows/centos7.yml
+++ b/.github/workflows/centos7.yml
@@ -12,12 +12,17 @@ on:
inputs:
run-tests:
required: true
- type: boolean
+ type: string
+ target_branch:
+ required: true
+ type: string
env:
GITHUB_TOKEN: ${{ github.token }}
IS_RELEASE: ${{ github.event_name == 'workflow_dispatch' }}
IS_PUSH: ${{ github.event_name == 'push' }}
+ REF: ${{ inputs.target_branch =='' && github.ref_name || inputs.target_branch}}
+ ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
jobs:
@@ -33,7 +38,14 @@ jobs:
- name: Checkout
run: |
- git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git -b $GITHUB_REF_NAME .
+ git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git -b ${{ env.REF }} .
+
+ - name: Install gcc 11
+ run: |
+ yum install -y centos-release-scl
+ yum install -y devtoolset-11-gcc*
+
+ - uses: ./.github/workflows/install-cmake-328
- name: Init submodule
run: |
@@ -49,7 +61,7 @@ jobs:
- name: Config OR-Tools URL
run: |
- echo "URL_ORTOOLS=https://github.com/rte-france/or-tools/releases/download/$(cat ortools_tag)/ortools_cxx_centos7_static_sirius.zip" >> $GITHUB_ENV
+ echo "URL_ORTOOLS=https://github.com/rte-france/or-tools-rte/releases/download/$(cat ortools_tag)/ortools_cxx_centos7_static_sirius.zip" >> $GITHUB_ENV
- name: Download OR-Tools
id: ortools
@@ -59,41 +71,43 @@ jobs:
unzip -q ortools.zip
rm ortools.zip
- - name: Install gcc 10
+ - name: Install gh if needed
+ if: ${{ env.IS_RELEASE == 'true' }}
run: |
- yum install -y centos-release-scl
- yum install -y devtoolset-10-gcc*
+ wget https://github.com/cli/cli/releases/download/v2.52.0/gh_2.52.0_linux_amd64.rpm
+ rpm -i gh_2.52.0_linux_amd64.rpm
+ gh --version
- name: Configure
run: |
- source /opt/rh/devtoolset-10/enable
- cmake3 -B _build -S src \
+ source /opt/rh/devtoolset-11/enable
+ cmake -B _build -S src \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DDEPS_INSTALL_DIR=/rte-antares-deps-Release \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON \
-DBUILD_not_system=OFF \
- -DBUILD_TOOLS=OFF \
+ -DBUILD_TOOLS=ON \
-DBUILD_UI=OFF \
-DCMAKE_PREFIX_PATH=${{ env.ORTOOLSDIR }}/install \
- name: Build
run: |
- source /opt/rh/devtoolset-10/enable
+ source /opt/rh/devtoolset-11/enable
source /opt/rh/rh-git227/enable
- cmake3 --build _build --config Release -j$(nproc)
+ cmake --build _build --config Release -j$(nproc)
ccache -s
- name: Installer .rpm creation
run: |
cd _build
- cpack3 -G RPM
+ cpack -G RPM
- name: Solver archive creation
run: |
cd _build
- cmake3 --install . --prefix install
+ cmake --install . --prefix install
pushd .
cd install/bin
tar czf ../../antares-solver_centos7.tar.gz antares-*-solver libsirius_solver.so
@@ -103,25 +117,7 @@ jobs:
- name: .tar.gz creation
run: |
cd _build
- cpack3 -G TGZ
-
- - name: Installer TGZ push
- uses: actions/upload-artifact@v3
- with:
- path: _build/*.tar.gz
-
- - name: Installer RPM push
- uses: actions/upload-artifact@v3
- with:
- path: _build/*.rpm
-
- - name: Install gh
- if: ${{ env.IS_RELEASE == 'true' }}
- run: |
- yum -y install dnf
- dnf -y install 'dnf-command(config-manager)'
- dnf -y config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
- dnf -y install gh
+ cpack -G TGZ
- name: Publish assets
if: ${{ env.IS_RELEASE == 'true' }}
@@ -131,4 +127,3 @@ jobs:
run: |
gh release upload "$tag" _build/*.tar.gz _build/*.rpm
-
\ No newline at end of file
diff --git a/.github/workflows/install-cmake-328/action.yml b/.github/workflows/install-cmake-328/action.yml
new file mode 100644
index 0000000000..9c2beb7876
--- /dev/null
+++ b/.github/workflows/install-cmake-328/action.yml
@@ -0,0 +1,17 @@
+name: "Install cmake 3.28 using devtoolset 10"
+description: "Download and install system wide cmake 3.28"
+
+runs:
+ using: "composite"
+ steps:
+ - name: Build cmake
+ shell: bash
+ run: |
+ source /opt/rh/devtoolset-11/enable
+ yum -y install openssl-devel
+ wget https://github.com/Kitware/CMake/releases/download/v3.28.2/cmake-3.28.2.tar.gz
+ tar -xvf cmake-3.28.2.tar.gz
+ cd cmake-3.28.2
+ ./bootstrap
+ make -j$(nproc)
+ make install
diff --git a/.github/workflows/new_release.yml b/.github/workflows/new_release.yml
index bdb156a776..6cf6da390a 100644
--- a/.github/workflows/new_release.yml
+++ b/.github/workflows/new_release.yml
@@ -3,6 +3,9 @@ name: Create new release
on:
workflow_dispatch:
inputs:
+ target_branch:
+ description: "Target branch or full commit SHA"
+ required: true
release_tag:
description: "Release tag"
required: true
@@ -12,6 +15,11 @@ on:
run-tests:
description: "Run all tests (true/false)"
required: true
+ type: string
+ set_latest:
+ description: "Set the release as latest"
+ required: true
+ type: boolean
jobs:
release:
name: Release pushed tag
@@ -23,12 +31,15 @@ jobs:
tag: ${{ github.event.inputs.release_tag }}
title: ${{ github.event.inputs.release_name }}
tests: ${{ github.event.inputs.run-tests }}
+ target_branch: ${{ github.event.inputs.target_branch }}
+ latest: ${{ github.event.inputs.set_latest }}
run: |
gh release create "$tag" \
--repo="$GITHUB_REPOSITORY" \
--title="$title" \
- --notes="Run tests: $tests"
-
+ --notes="Run tests: $tests"\
+ --target="$target_branch" \
+ --latest=$latest
ubuntu:
@@ -36,32 +47,36 @@ jobs:
needs: release
uses: ./.github/workflows/ubuntu.yml
with:
- run-tests: ${{ fromJSON(inputs.run-tests) }}
+ run-tests: ${{ inputs.run-tests }}
+ target_branch: ${{ inputs.target_branch }}
windows:
name: Release - Windows
needs: release
uses: ./.github/workflows/windows-vcpkg.yml
with:
- run-tests: ${{ fromJSON(inputs.run-tests) }}
+ run-tests: ${{ inputs.run-tests }}
+ target_branch: ${{ inputs.target_branch }}
centos7:
name: Release - centos7
needs: release
uses: ./.github/workflows/centos7.yml
with:
- run-tests: ${{ fromJSON(inputs.run-tests) }}
+ run-tests: ${{ inputs.run-tests }}
+ target_branch: ${{ inputs.target_branch }}
oracle8:
name: Release - oracle8
needs: release
uses: ./.github/workflows/oracle8.yml
with:
- run-tests: ${{ fromJSON(inputs.run-tests) }}
+ run-tests: ${{ inputs.run-tests }}
+ target_branch: ${{ inputs.target_branch }}
user_guide:
name: User Guide
needs: release
uses: ./.github/workflows/build-userguide.yml
with:
- run-tests: ${{ fromJSON(inputs.run-tests) }}
+ run-tests: ${{ inputs.run-tests }}
diff --git a/.github/workflows/oracle8.yml b/.github/workflows/oracle8.yml
index 10e94ddb92..ac971bb998 100644
--- a/.github/workflows/oracle8.yml
+++ b/.github/workflows/oracle8.yml
@@ -6,25 +6,32 @@ on:
push:
branches:
- develop
+ - dependabot/*
+
schedule:
- cron: '21 2 * * *'
workflow_call:
inputs:
run-tests:
required: true
- type: boolean
+ type: string
+ target_branch:
+ required: true
+ type: string
env:
GITHUB_TOKEN: ${{ github.token }}
IS_RELEASE: ${{ github.event_name == 'workflow_dispatch' }}
IS_PUSH: ${{ github.event_name == 'push' }}
+ REF: ${{ inputs.target_branch =='' && github.ref_name || inputs.target_branch}}
+
jobs:
build:
name: Build
env:
- ORTOOLSDIR: ${{ github.workspace }}/or-tools
+ ORTOOLS_DIR: ${{ github.workspace }}/or-tools
runs-on: ubuntu-latest
container: 'oraclelinux:8'
@@ -38,12 +45,24 @@ jobs:
- name: Install libraries
run: |
- dnf install -y epel-release git cmake wget rpm-build redhat-lsb-core
- dnf install -y unzip libuuid-devel boost-test boost-devel gcc-toolset-10-toolchain zlib-devel
+ dnf install -y epel-release git cmake wget rpm-build redhat-lsb-core
+ dnf install -y unzip libuuid-devel boost-test boost-devel gcc-toolset-11 zlib-devel
- name: Checkout
run: |
- git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git -b $GITHUB_REF_NAME .
+ git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git -b ${{ env.REF }} .
+
+ - name: Config OR-Tools URL
+ run: |
+ echo "ORTOOLS_URL=https://github.com/rte-france/or-tools-rte/releases/download/$(cat ortools_tag)/ortools_cxx_oraclelinux-8_static_sirius.zip" >> $GITHUB_ENV
+
+ - name: Download & extract OR-Tools
+ run: |
+ mkdir -p ${{env.ORTOOLS_DIR}}
+ cd ${{env.ORTOOLS_DIR}}
+ wget ${{env.ORTOOLS_URL}} -O ortools.zip
+ unzip ortools.zip
+ rm ortools.zip
- name: Init submodule
run: |
@@ -54,18 +73,26 @@ jobs:
run: |
pip3 install -r src/tests/examples/requirements.txt
+ - name: Install gh if needed
+ if: ${{ env.IS_RELEASE == 'true' }}
+ run: |
+ dnf -y install 'dnf-command(config-manager)'
+ dnf -y config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
+ dnf -y install gh
+
- name: Configure
run: |
- source /opt/rh/gcc-toolset-10/enable
+ source /opt/rh/gcc-toolset-11/enable
cmake -B _build -S src \
-DCMAKE_BUILD_TYPE=Release \
- -DBUILD_TESTING=ON \
- -DBUILD_TOOLS=OFF \
+ -DBUILD_TESTING=OFF \
+ -DBUILD_TOOLS=ON \
-DBUILD_UI=OFF \
+ -DCMAKE_PREFIX_PATH=${{ env.ORTOOLS_DIR }}/install
- name: Build
run: |
- source /opt/rh/gcc-toolset-10/enable
+ source /opt/rh/gcc-toolset-11/enable
cmake --build _build --config Release -j$(nproc)
- name: Run unit and end-to-end tests
@@ -95,27 +122,21 @@ jobs:
cpack -G TGZ
- name: Installer TGZ push
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: oracle-targz
path: _build/*.tar.gz
- name: Installer RPM push
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: oracle-rpm
path: _build/*.rpm
- - name: Install gh
- if: ${{ env.IS_RELEASE == 'true' }}
- run: |
- dnf -y install 'dnf-command(config-manager)'
- dnf -y config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
- dnf -y install gh
-
- name: Publish assets
if: ${{ env.IS_RELEASE == 'true' }}
env:
GITHUB_TOKEN: ${{ github.token }}
tag: ${{ github.event.inputs.release_tag }}
run: |
- gh release upload "$tag" _build/*.tar.gz _build/*.rpm
-
+ gh release upload "$tag" _build/*.tar.gz _build/*.rpm
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index 7978abc23f..589a64cda9 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -28,7 +28,7 @@ jobs:
- name: Config OR-Tools URL
run: |
- echo "ORTOOLS_URL=https://github.com/rte-france/or-tools/releases/download/$(cat ortools_tag)/ortools_cxx_ubuntu-20.04_static_sirius.zip" >> $GITHUB_ENV
+ echo "ORTOOLS_URL=https://github.com/rte-france/or-tools-rte/releases/download/$(cat ortools_tag)/ortools_cxx_ubuntu-20.04_static_sirius.zip" >> $GITHUB_ENV
- name: Install sonar-scanner and build-wrapper
uses: SonarSource/sonarcloud-github-c-cpp@v2
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index f85572cdbc..8b29e7cfb3 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -10,19 +10,28 @@ on:
- issue-*
- release/*
- doc/*
+ - dependabot/*
schedule:
- cron: '21 2 * * *'
workflow_call:
inputs:
run-tests:
required: true
- type: boolean
+ type: string
+ target_branch:
+ required: true
+ type: string
env:
GITHUB_TOKEN: ${{ github.token }}
IS_RELEASE: ${{ github.event_name == 'workflow_dispatch' }}
RUN_SIMPLE_TESTS: ${{ github.event_name == 'push' || inputs.run-tests == 'true' }}
RUN_EXTENDED_TESTS: ${{ github.event_name == 'schedule' || inputs.run-tests == 'true' }}
+ REF: ${{ inputs.target_branch =='' && github.ref || inputs.target_branch}}
+ VCPKG_ROOT: ${{ github.workspace }}/vcpkg
+ vcpkgPackages: yaml-cpp antlr4
+ triplet: x64-linux
+ WX_CONFIG: /usr/bin/wx-config
jobs:
@@ -34,15 +43,39 @@ jobs:
runs-on: ubuntu-20.04
if: "!contains(github.event.head_commit.message, '[skip ci]')"
-
+
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ env.REF }}
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ env.os }}
+
+ - name : Init VCPKG submodule
+ run: |
+ git submodule update --init vcpkg
+
+ # Restore both vcpkg and its artifacts from the GitHub cache service.
+ - name: Restore vcpkg and its artifacts.
+ uses: actions/cache@v4
+ with:
+ # The first path is the location of vcpkg (it contains the vcpkg executable and data files).
+ # The other paths starting with '!' are exclusions: they contain termporary files generated during the build of the installed packages.
+ path: |
+ ${{ env.VCPKG_ROOT }}
+ !${{ env.VCPKG_ROOT }}/buildtrees
+ !${{ env.VCPKG_ROOT }}/packages
+ !${{ env.VCPKG_ROOT }}/downloads
+ # The key is composed in a way that it gets properly invalidated: this must happen whenever vcpkg's Git commit id changes, or the list of packages changes. In this case a cache miss must happen and a new entry with a new key with be pushed to GitHub the cache service.
+ # The key includes: hash of the vcpkg.json file, the hash of the vcpkg Git commit id, and the used vcpkg's triplet. The vcpkg's commit id would suffice, but computing an hash out it does not harm.
+ # Note: given a key, the cache content is immutable. If a cache entry has been created improperly, in order the recreate the right content the key must be changed as well, and it must be brand new (i.e. not existing already).
+ key: |
+ ${{ hashFiles( 'vcpkg_manifest/vcpkg.json' ) }}-${{ hashFiles( '.git/modules/vcpkg/HEAD' )}}-${{ env.triplet }}
+
- name: Install libraries
run: |
sudo apt-get update
@@ -50,6 +83,19 @@ jobs:
sudo apt-get install libboost-test-dev
sudo apt-get install g++-10 gcc-10
+ - name: export wxWidgets script
+ shell: bash
+ run: |
+ export WX_CONFIG=${{env.WX_CONFIG}}
+
+ - name : Install deps with VCPKG
+ run: |
+ cd vcpkg
+ ./bootstrap-vcpkg.sh
+ vcpkg install ${{env.vcpkgPackages}} --triplet ${{env.triplet}}
+ rm -rf buildtrees packages downloads
+ shell: bash
+
- name: Read antares-deps version
id: antares-deps-version
uses: notiz-dev/github-action-json-property@release
@@ -60,7 +106,7 @@ jobs:
- name: Config OR-Tools URL
run: |
- echo "ORTOOLS_URL=https://github.com/rte-france/or-tools/releases/download/$(cat ortools_tag)/ortools_cxx_ubuntu-20.04_static_sirius.zip" >> $GITHUB_ENV
+ echo "ORTOOLS_URL=https://github.com/rte-france/or-tools-rte/releases/download/$(cat ortools_tag)/ortools_cxx_ubuntu-20.04_static_sirius.zip" >> $GITHUB_ENV
- name: Download pre-compiled librairies
uses: ./.github/workflows/download-extract-precompiled-libraries-tgz
@@ -72,7 +118,7 @@ jobs:
- name: Set up Python
id: setup-python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.12'
@@ -85,11 +131,15 @@ jobs:
run: |
git submodule update --init src/antares-deps
git submodule update --init --remote --recursive src/tests/resources/Antares_Simulator_Tests
+
+
- name: Configure
run: |
cmake -B _build -S src \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
+ -DVCPKG_ROOT="${{env.VCPKG_ROOT}}" \
+ -DVCPKG_TARGET_TRIPLET=${{ env.triplet }} \
-DCMAKE_C_COMPILER=/usr/bin/gcc-10 \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER=/usr/bin/g++-10 \
@@ -138,7 +188,7 @@ jobs:
- name: Upload logs for failed tests
if: ${{ failure() }}
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: test-log
path: ${{ github.workspace }}/_build/Testing/Temporary/LastTest.log
@@ -192,14 +242,6 @@ jobs:
batch-name: valid-mps
os: ${{ env.os }}
- - name: Run tests for adequacy patch (CSR)
- if: ${{ env.RUN_SIMPLE_TESTS == 'true' }}
- uses: ./.github/workflows/run-tests
- with:
- simtest-tag: ${{steps.simtest-version.outputs.prop}}
- batch-name: adequacy-patch-CSR
- os: ${{ env.os }}
-
- name: Run parallel tests
if: ${{ env.RUN_EXTENDED_TESTS == 'true' }}
uses: ./.github/workflows/run-tests
@@ -262,20 +304,22 @@ jobs:
rm -rf install
- name: Installer archive upload push
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: targz
path: _build/*.tar.gz
- name: Installer deb upload push
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: deb
path: _build/*.deb
-
-
+
+
- name: Publish assets
if: ${{ env.IS_RELEASE == 'true' }}
env:
GITHUB_TOKEN: ${{ github.token }}
tag: ${{ github.event.inputs.release_tag }}
run: |
- gh release upload "$tag" _build/*.tar.gz _build/*.deb
+ gh release upload "$tag" _build/*.tar.gz _build/*.deb
diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml
index 5c0b58fff0..610337d7bc 100644
--- a/.github/workflows/windows-release.yml
+++ b/.github/workflows/windows-release.yml
@@ -26,7 +26,7 @@ jobs:
- name: Config OR-Tools URL
run: |
- echo "ORTOOLS_URL=https://github.com/rte-france/or-tools/releases/download/$(cat ortools_tag)/ortools_cxx_windows-latest_static_sirius.zip" >> $GITHUB_ENV
+ echo "ORTOOLS_URL=https://github.com/rte-france/or-tools-rte/releases/download/$(cat ortools_tag)/ortools_cxx_windows-latest_static_sirius.zip" >> $GITHUB_ENV
shell: bash
- name: Pre-requisites
@@ -135,13 +135,6 @@ jobs:
path: 'simtest.json'
prop_path: 'version'
- - name: Run tests for adequacy patch (CSR)
- uses: ./.github/workflows/run-tests
- with:
- simtest-tag: ${{steps.simtest-version.outputs.prop}}
- batch-name: adequacy-patch-CSR
- os: ${{ matrix.test-platform }}
-
- name: Run medium-tests
uses: ./.github/workflows/run-tests
with:
diff --git a/.github/workflows/windows-vcpkg.yml b/.github/workflows/windows-vcpkg.yml
index 0378a8f074..e95b495723 100644
--- a/.github/workflows/windows-vcpkg.yml
+++ b/.github/workflows/windows-vcpkg.yml
@@ -10,19 +10,24 @@ on:
- issue-*
- release/*
- doc/*
+ - dependabot/*
schedule:
- cron: '21 2 * * *'
workflow_call:
inputs:
run-tests:
required: true
- type: boolean
+ type: string
+ target_branch:
+ required: true
+ type: string
env:
GITHUB_TOKEN: ${{ github.token }}
IS_RELEASE: ${{ github.event_name == 'workflow_dispatch' }}
RUN_SIMPLE_TESTS: ${{ github.event_name == 'push' || inputs.run-tests == 'true' }}
RUN_EXTENDED_TESTS: ${{ github.event_name == 'schedule' || inputs.run-tests == 'true' }}
+ REF: ${{ inputs.target_branch =='' && github.ref || inputs.target_branch}}
jobs:
@@ -32,20 +37,22 @@ jobs:
# Indicates the location of the vcpkg as a Git submodule of the project repository.
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
ORTOOLS_DIR: ${{ github.workspace }}/or-tools
- RUN_EXTENDED_TESTS: ${{ github.event_name == 'schedule'}}
os: windows-latest
test-platform: windows-2022
- vcpkgPackages: wxwidgets boost-test
+ vcpkgPackages: wxwidgets boost-test yaml-cpp antlr4
triplet: x64-windows
runs-on: windows-latest
steps:
- - uses: actions/checkout@v3
+
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ env.REF }}
- name: Config OR-Tools URL
run: |
- echo "ORTOOLS_URL=https://github.com/rte-france/or-tools/releases/download/$(cat ortools_tag)/ortools_cxx_windows-latest_static_sirius.zip" >> $GITHUB_ENV
+ echo "ORTOOLS_URL=https://github.com/rte-france/or-tools-rte/releases/download/$(cat ortools_tag)/ortools_cxx_windows-latest_static_sirius.zip" >> $GITHUB_ENV
shell: bash
- name: Pre-requisites
@@ -77,10 +84,10 @@ jobs:
# Restore both vcpkg and its artifacts from the GitHub cache service.
- name: Restore vcpkg and its artifacts.
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
# The first path is the location of vcpkg (it contains the vcpkg executable and data files).
- # The other paths starting with '!' are exclusions: they contain termporary files generated during the build of the installed packages.
+ # The other paths starting with '!' are exclusions: they contain temporary files generated during the build of the installed packages.
path: |
${{ env.VCPKG_ROOT }}
!${{ env.VCPKG_ROOT }}/buildtrees
@@ -116,7 +123,7 @@ jobs:
ortools-dir: ${{env.ORTOOLS_DIR}}
- name: Setup Python 3.12
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
id: setup-python
with:
architecture: 'x64'
@@ -146,12 +153,13 @@ jobs:
-DBUILD_TOOLS=ON \
-DBUILD_not_system=OFF \
-DPython3_EXECUTABLE="${{ env.Python3_ROOT_DIR }}/python.exe" \
- -DCMAKE_VS_GLOBALS="CLToolExe=cl.exe;CLToolPath=${GITHUB_WORKSPACE}/ccache;TrackFileAccess=false;UseMultiToolTask=true;DebugInformationFormat=OldStyle"
+ -DCMAKE_VS_GLOBALS="CLToolExe=cl.exe;CLToolPath=${GITHUB_WORKSPACE}/ccache;TrackFileAccess=false;UseMultiToolTask=true;DebugInformationFormat=OldStyle" \
+ -DCMAKE_EXE_LINKER_FLAGS="/STACK:33554432"
- name: Build
shell: bash
run: |
- cmake --build _build --config Release -j2
+ cmake --build _build --config Release -j$(nproc)
# simtest
- name: Read simtest version
id: simtest-version
@@ -183,20 +191,11 @@ jobs:
- name: Upload build on failure
if: ${{ failure() }}
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: MPS-diff
path: ${{ github.workspace }}/src/tests/mps
-
- - name: Run tests for adequacy patch (CSR)
- if: ${{ env.RUN_SIMPLE_TESTS == 'true' }}
- uses: ./.github/workflows/run-tests
- with:
- simtest-tag: ${{steps.simtest-version.outputs.prop}}
- batch-name: adequacy-patch-CSR
- os: ${{ env.test-platform }}
-
- name: Run tests about infinity on BCs RHS
if: ${{ env.RUN_SIMPLE_TESTS == 'true' }}
uses: ./.github/workflows/run-tests
@@ -304,7 +303,7 @@ jobs:
- name: Upload NSIS log on failure
if: ${{ failure() }}
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: NSISError.log
path: _build/_CPack_Packages/win64/NSIS/NSISOutput.log
@@ -315,8 +314,9 @@ jobs:
cpack -G ZIP
- name: Installer upload
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
+ name: installer
path: _build/${{env.NSIS_NAME}}
- name: Publish assets
@@ -325,5 +325,5 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
tag: ${{ github.event.inputs.release_tag }}
run: |
- gh release upload "$tag" _build/*.zip
+ gh release upload "$tag" _build/*.zip _build/*.exe
shell: bash
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 4f80b0eef7..e4bfbe02e7 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,10 +1,57 @@
Antares Changelog
=================
-8.9.0 (Unreleased yet)
+8.8.10 (09/2024)
--------------------
-## New Features
-* Solver logs can be enabled either by the command-line option (--solver-logs) or in the generaldata.ini by setting solver-logs = true under the optimization section [(#1717)](https://github.com/AntaresSimulatorTeam/Antares_Simulator/pull/1717)
+## Bugfix (adequacy patch)
+* Force enable-first-step=false [ANT-2218] (#2419)
+* Adequacy patch CSR - revamp output variables [ANT-1932] (#2421)
+* Place CSR after hydro remix [ANT-2070] (#2407)
+
+## Bugfix (other)
+* Use OR-Tools v9.11-rte1.1 [ANT-2069] (#2418)
+
+8.8.9 (09/2024)
+--------------------
+* Revert "Fix bug hydro heuristic with mingen (ANT-1825) (#2258)"
+
+8.8.8 (09/2024)
+--------------------
+## Bugfix
+* Timeseries generation stored in input (#2180)
+* Fix bug hydro heuristic with mingen (ANT-1825) (#2258)
+
+8.8.7 (07/2024)
+--------------------
+## Batchrun tool improvement
+* Add OR-Tools solver option for batchrun tool #1981
+* Add missing parameter to command built by batchrun for OR-Tools #1984
+## Bugfix
+* Adequacy Patch regression [ANT-1845] #2235
+
+8.8.6 (07/2024)
+--------------------
+## Bugfix
+* BC marginal cost #2121
+
+8.8.5 (05/2024)
+--------------------
+## Bugfix
+* [UI] Fix opening a study from the file browser
+* Fix crash occurring when duplicate thermal clusters are present in a study (same name)
+* Fix formula for "PROFIT BY PLANT"
+
+8.8.4 (03/2024)
+--------------------
+## Bugfix
+* Adequacy patch CSR - fix DTG MRG (#1982)
+* Fix ts numbers for no gen clusters (#1969)
+* Remove unitcount limit for time series generation (#1960)
+
+8.8.3
+--------------------
+## Bugfix
+* Fix an issue where depending on the platform the output archive could contain several entries of the same area and inrco files
8.8.2
--------------------
diff --git a/ortools_tag b/ortools_tag
index fdaa0174f8..4121b49d4c 100644
--- a/ortools_tag
+++ b/ortools_tag
@@ -1 +1 @@
-v9.8-rte1.0
\ No newline at end of file
+v9.11-rte1.1
\ No newline at end of file
diff --git a/simtest.json b/simtest.json
index 8fbede7352..1193cc61ae 100644
--- a/simtest.json
+++ b/simtest.json
@@ -1,3 +1,3 @@
{
- "version": "v8.8.1"
+ "version": "v8.8.7"
}
diff --git a/sonar-project.properties b/sonar-project.properties
index 1a30847665..703a8f0696 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -1,7 +1,28 @@
+#
+# Copyright 2007-2024, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of Antares-Simulator,
+# Adequacy and Performance assessment for interconnected energy networks.
+#
+# Antares_Simulator is free software: you can redistribute it and/or modify
+# it under the terms of the Mozilla Public Licence 2.0 as published by
+# the Mozilla Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Antares_Simulator is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Mozilla Public Licence 2.0 for more details.
+#
+# You should have received a copy of the Mozilla Public Licence 2.0
+# along with Antares_Simulator. If not, see .
+#
+
sonar.projectName=Antares_Simulator
sonar.projectKey=AntaresSimulatorTeam_Antares_Simulator
sonar.organization=antaressimulatorteam
-sonar.projectVersion=8.8.0
+sonar.projectVersion=8.8.3
# =====================================================
# Properties that will be shared amongst all modules
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9a2b334d3d..47617b923c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.14) # FetchContent_MakeAvailable
# Version
set(ANTARES_VERSION_HI 8)
set(ANTARES_VERSION_LO 8)
-set(ANTARES_VERSION_REVISION 2)
+set(ANTARES_VERSION_REVISION 10)
# Beta release
set(ANTARES_BETA 0)
diff --git a/src/ext/yuni/src/yuni/io/filename-manipulation.cpp b/src/ext/yuni/src/yuni/io/filename-manipulation.cpp
index e65caeced1..2a1665fe8b 100644
--- a/src/ext/yuni/src/yuni/io/filename-manipulation.cpp
+++ b/src/ext/yuni/src/yuni/io/filename-manipulation.cpp
@@ -300,7 +300,7 @@ void parentPath(String& out, const AnyString& path, bool systemDependant)
}
template
-static inline void parentPath(StringT& out, const AnyString& path, bool systemDependant)
+static inline void ExtractFileNameImpl(StringT& out, const AnyString& path, bool systemDependant)
{
AnyString::size_type pos = (systemDependant)
? path.find_last_of(IO::Constant::Separator)
@@ -313,12 +313,12 @@ static inline void parentPath(StringT& out, const AnyString& path, bool systemDe
void ExtractFileName(String& out, const AnyString& path, bool systemDependant)
{
- parentPath(out, path, systemDependant);
+ ExtractFileNameImpl(out, path, systemDependant);
}
void ExtractFileName(Clob& out, const AnyString& path, bool systemDependant)
{
- parentPath(out, path, systemDependant);
+ ExtractFileNameImpl(out, path, systemDependant);
}
template
diff --git a/src/libs/antares/InfoCollection/StudyInfoCollector.cpp b/src/libs/antares/InfoCollection/StudyInfoCollector.cpp
index ba626a59ed..415b1709be 100644
--- a/src/libs/antares/InfoCollection/StudyInfoCollector.cpp
+++ b/src/libs/antares/InfoCollection/StudyInfoCollector.cpp
@@ -19,6 +19,7 @@ void StudyInfoCollector::toFileContent(FileContent& file_content)
performedYearsCountToFileContent(file_content);
enabledThermalClustersCountToFileContent(file_content);
enabledBindingConstraintsCountToFileContent(file_content);
+ enabledMaintenanceGroupCountToFileContent(file_content);
unitCommitmentModeToFileContent(file_content);
maxNbYearsInParallelToFileContent(file_content);
solverVersionToFileContent(file_content);
@@ -67,6 +68,13 @@ void StudyInfoCollector::enabledThermalClustersCountToFileContent(FileContent& f
file_content.addItemToSection("study", "enabled thermal clusters", nbEnabledThermalClusters);
}
+void StudyInfoCollector::enabledMaintenanceGroupCountToFileContent(FileContent& file_content)
+{
+ auto activeMntGroups = study_.maintenanceGroups.activeMaintenanceGroups();
+ auto nbEnabledMNT = activeMntGroups.size();
+ file_content.addItemToSection("study", "enabled Maintenance Group-s", nbEnabledMNT);
+}
+
void StudyInfoCollector::enabledBindingConstraintsCountToFileContent(FileContent& file_content)
{
auto activeContraints = study_.bindingConstraints.activeContraints();
diff --git a/src/libs/antares/InfoCollection/include/antares/infoCollection/StudyInfoCollector.h b/src/libs/antares/InfoCollection/include/antares/infoCollection/StudyInfoCollector.h
index bb3b1f1df8..13137ffb95 100644
--- a/src/libs/antares/InfoCollection/include/antares/infoCollection/StudyInfoCollector.h
+++ b/src/libs/antares/InfoCollection/include/antares/infoCollection/StudyInfoCollector.h
@@ -24,6 +24,7 @@ class StudyInfoCollector
void performedYearsCountToFileContent(FileContent& file_content);
void enabledThermalClustersCountToFileContent(FileContent& file_content);
void enabledBindingConstraintsCountToFileContent(FileContent& file_content);
+ void enabledMaintenanceGroupCountToFileContent(FileContent& file_content);
void unitCommitmentModeToFileContent(FileContent& file_content);
void maxNbYearsInParallelToFileContent(FileContent& file_content);
void solverVersionToFileContent(FileContent& file_content);
diff --git a/src/libs/antares/checks/antares/checks/checkLoadedInputData.h b/src/libs/antares/checks/antares/checks/checkLoadedInputData.h
index ad036cafc0..781ab7d56e 100644
--- a/src/libs/antares/checks/antares/checks/checkLoadedInputData.h
+++ b/src/libs/antares/checks/antares/checks/checkLoadedInputData.h
@@ -46,4 +46,7 @@ void checkMinStablePower(bool tsGenThermal, const Antares::Data::AreaList& areas
void checkFuelCostColumnNumber(const Antares::Data::AreaList& areas);
void checkCO2CostColumnNumber(const Antares::Data::AreaList& areas);
+void checkMaintenancePlanningSettings(const Antares::Data::Parameters* parameters);
+void checkMaintenancePlanningTsNum(const Antares::Data::Parameters* parameters);
+
} // namespace Antares::Check
diff --git a/src/libs/antares/checks/checkLoadedInputData.cpp b/src/libs/antares/checks/checkLoadedInputData.cpp
index d585ffdaa1..59733f6b87 100644
--- a/src/libs/antares/checks/checkLoadedInputData.cpp
+++ b/src/libs/antares/checks/checkLoadedInputData.cpp
@@ -200,4 +200,36 @@ void checkCO2CostColumnNumber(const Antares::Data::AreaList& areas)
&Antares::Data::EconomicInputData::co2cost);
}
+void checkMaintenancePlanningSettings(const Antares::Data::Parameters* parameters)
+{
+ const auto timeSeriesToGenerate = parameters->timeSeriesToGenerate;
+ bool aggregatedMode = parameters->renewableGeneration.isAggregated();
+
+ bool activeThermalTSGenAndMntPlanning
+ = (parameters->maintenancePlanning.isOptimized()
+ && (timeSeriesToGenerate & Antares::Data::timeSeriesThermal));
+
+ bool activeOtherTSGen
+ = ((timeSeriesToGenerate & Antares::Data::timeSeriesLoad)
+ || (timeSeriesToGenerate & Antares::Data::timeSeriesHydro)
+ || ((timeSeriesToGenerate & Antares::Data::timeSeriesWind) && aggregatedMode)
+ || ((timeSeriesToGenerate & Antares::Data::timeSeriesSolar) && aggregatedMode));
+
+ if (activeThermalTSGenAndMntPlanning && activeOtherTSGen)
+ throw Error::IncompatibleMaintenancePlanningUsage();
+}
+
+void checkMaintenancePlanningTsNum(const Antares::Data::Parameters* parameters)
+{
+ bool activeThermalTSGenAndMntPlanning
+ = (parameters->maintenancePlanning.isOptimized()
+ && (parameters->timeSeriesToGenerate & Antares::Data::timeSeriesThermal));
+
+ if (activeThermalTSGenAndMntPlanning
+ && parameters->maintenancePlanning.getScenarioLength()
+ * parameters->maintenancePlanning.getScenarioNumber()
+ != parameters->nbTimeSeriesThermal)
+ throw Error::IncompatibleMaintenancePlanningTsNum();
+}
+
} // namespace Antares::Check
diff --git a/src/libs/antares/exception/LoadingError.cpp b/src/libs/antares/exception/LoadingError.cpp
index 0508103496..48835ae97a 100644
--- a/src/libs/antares/exception/LoadingError.cpp
+++ b/src/libs/antares/exception/LoadingError.cpp
@@ -172,5 +172,17 @@ IncompatibleFuelCostColumns::IncompatibleFuelCostColumns() :
{
}
+IncompatibleMaintenancePlanningUsage::IncompatibleMaintenancePlanningUsage() :
+ LoadingError("When generating thermal time series using Maintenance Planning, Stochastic "
+ "ts-generator for load, hydro and renewables must be turned off")
+{
+}
+
+IncompatibleMaintenancePlanningTsNum::IncompatibleMaintenancePlanningTsNum() :
+ LoadingError("When generating thermal time series using Maintenance Planning, scenario length "
+ "* scenario number must be equal to number of thermal ts in Stochastic TS")
+{
+}
+
} // namespace Error
} // namespace Antares
diff --git a/src/libs/antares/exception/antares/exception/LoadingError.hpp b/src/libs/antares/exception/antares/exception/LoadingError.hpp
index 093375718d..cc58c84af7 100644
--- a/src/libs/antares/exception/antares/exception/LoadingError.hpp
+++ b/src/libs/antares/exception/antares/exception/LoadingError.hpp
@@ -216,5 +216,17 @@ class IncompatibleFuelCostColumns : public LoadingError
IncompatibleFuelCostColumns();
};
+class IncompatibleMaintenancePlanningUsage : public LoadingError
+{
+public:
+ IncompatibleMaintenancePlanningUsage();
+};
+
+class IncompatibleMaintenancePlanningTsNum : public LoadingError
+{
+public:
+ IncompatibleMaintenancePlanningTsNum();
+};
+
} // namespace Error
} // namespace Antares
diff --git a/src/libs/antares/series/include/antares/series/series.h b/src/libs/antares/series/include/antares/series/series.h
index 41823f29ff..beaab89ad9 100644
--- a/src/libs/antares/series/include/antares/series/series.h
+++ b/src/libs/antares/series/include/antares/series/series.h
@@ -88,7 +88,7 @@ class TimeSeries
void markAsModified() const;
uint64_t memoryUsage() const;
- TS timeSeries;
+ TS timeSeries; // TMP.INFO CR27: TS-s will be written here!
numbers& timeseriesNumbers;
static const std::vector emptyColumn; ///< used in getColumn if timeSeries empty
diff --git a/src/libs/antares/study/CMakeLists.txt b/src/libs/antares/study/CMakeLists.txt
index d4259be54c..75a3c34abc 100644
--- a/src/libs/antares/study/CMakeLists.txt
+++ b/src/libs/antares/study/CMakeLists.txt
@@ -180,6 +180,20 @@ set(SRC_STUDY_BINDING_CONSTRAINT
source_group("study\\constraint" FILES ${SRC_STUDY_BINDING_CONSTRAINT})
+set(SRC_STUDY_MAINTENANCE_PLANNING
+ maintenance_planning/MaintenanceGroup.h
+ maintenance_planning/MaintenanceGroup.hxx
+ maintenance_planning/MaintenanceGroup.cpp
+ maintenance_planning/MaintenanceGroupSaver.h
+ maintenance_planning/MaintenanceGroupSaver.cpp
+ maintenance_planning/MaintenanceGroupLoader.h
+ maintenance_planning/MaintenanceGroupLoader.cpp
+ maintenance_planning/MaintenanceGroupRepository.h
+ maintenance_planning/MaintenanceGroupRepository.cpp
+ maintenance_planning/MaintenanceGroupRepository.hxx
+)
+
+source_group("study\\maintenance" FILES ${SRC_STUDY_MAINTENANCE_PLANNING})
set(SRC_XCAST
xcast.h
@@ -273,6 +287,7 @@ set(SRC_ALL
${SRC_STUDY_PARAMETERS}
${SRC_STUDY_FINDER}
${SRC_STUDY_BINDING_CONSTRAINT}
+ ${SRC_STUDY_MAINTENANCE_PLANNING}
${SRC_STUDY_PART_WIND}
${SRC_STUDY_PART_HYDRO}
${SRC_STUDY_PART_COMMON}
diff --git a/src/libs/antares/study/area/area.h b/src/libs/antares/study/area/area.h
index d2f60d4a0c..03be2177d7 100644
--- a/src/libs/antares/study/area/area.h
+++ b/src/libs/antares/study/area/area.h
@@ -627,6 +627,11 @@ class AreaList final : public Yuni::NonCopyable
*/
ThermalCluster* findClusterFromINIKey(const AnyString& key);
+ /*!
+ ** \brief Try to find the area from a given INI key (.'weights')
+ */
+ Area* findAreaFromINIKey(const AnyString& key);
+
/*!
** \brief Get the total number of interconnections between all areas
*/
diff --git a/src/libs/antares/study/area/list.cpp b/src/libs/antares/study/area/list.cpp
index 5942afb550..135ee317ac 100644
--- a/src/libs/antares/study/area/list.cpp
+++ b/src/libs/antares/study/area/list.cpp
@@ -675,6 +675,9 @@ bool AreaList::saveToFolder(const AnyString& folder) const
buffer.clear() << folder << SEP << "input" << SEP << "bindingconstraints";
ret = IO::Directory::Create(buffer) && ret;
+ buffer.clear() << folder << SEP << "input" << SEP << "maintenanceplanning";
+ ret = IO::Directory::Create(buffer) && ret;
+
buffer.clear() << folder << SEP << "input" << SEP << "links";
ret = IO::Directory::Create(buffer) && ret;
@@ -936,6 +939,7 @@ static bool AreaListLoadFromFolderSingleArea(Study& study,
buffer.clear() << study.folderInput << SEP << "thermal" << SEP << "series";
ret = area.thermal.list.loadDataSeriesFromFolder(study, options, buffer) && ret;
ret = area.thermal.list.loadEconomicCosts(study, buffer) && ret;
+ ret = area.thermal.list.generateRandomDaysSinceLastMaintenance(study) && ret;
// In adequacy mode, all thermal clusters must be in 'mustrun' mode
if (study.usedByTheSolver && study.parameters.mode == SimulationMode::Adequacy)
@@ -1538,6 +1542,18 @@ ThermalCluster* AreaList::findClusterFromINIKey(const AnyString& key)
}
+Area* AreaList::findAreaFromINIKey(const AnyString& key)
+{
+ if (key.empty())
+ return nullptr;
+ auto offset = key.find('.');
+ if (offset == AreaName::npos || (0 == offset) || (offset == key.size() - 1))
+ return nullptr;
+ AreaName parentName(key.c_str(), offset);
+ Area* parentArea = findFromName(parentName);
+ return (parentArea != nullptr) ? parentArea : nullptr;
+}
+
void AreaList::updateNameIDSet() const
{
nameidSet.clear();
diff --git a/src/libs/antares/study/cleaner/cleaner-v20.cpp b/src/libs/antares/study/cleaner/cleaner-v20.cpp
index 5f8e6636e1..950e48dca8 100644
--- a/src/libs/antares/study/cleaner/cleaner-v20.cpp
+++ b/src/libs/antares/study/cleaner/cleaner-v20.cpp
@@ -252,6 +252,7 @@ bool listOfFilesAnDirectoriesToKeep(StudyCleaningInfos* infos)
e.add("settings/constraintbuilder.ini");
e.add("settings/scenariobuilder.dat");
e.add("input/bindingconstraints/bindingconstraints.ini");
+ e.add("input/maintenanceplanning/maintenancegroups.ini");
e.add("input/hydro/hydro.ini");
e.add("input/areas/list.txt");
e.add("input/areas/sets.ini");
@@ -303,6 +304,7 @@ bool listOfFilesAnDirectoriesToKeep(StudyCleaningInfos* infos)
p.add("settings/resources");
p.add("settings/simulations");
p.add("input/bindingconstraints");
+ p.add("input/maintenanceplanning");
// Getting all areas
auto* arealist = new AreaList(*study);
diff --git a/src/libs/antares/study/fwd.cpp b/src/libs/antares/study/fwd.cpp
index bfba0dc762..195c5fbae6 100644
--- a/src/libs/antares/study/fwd.cpp
+++ b/src/libs/antares/study/fwd.cpp
@@ -345,6 +345,20 @@ const char* RenewableGenerationModellingToCString(RenewableGenerationModelling r
return "";
}
+const char* MaintenancePlanningModellingToCString(MaintenancePlanningModelling mtcPlanning)
+{
+ switch (mtcPlanning)
+ {
+ case mpRandomized:
+ return "randomized";
+ case mpOptimized:
+ return "optimized";
+ case mpUnknown:
+ return "";
+ }
+ return "";
+}
+
std::string mpsExportStatusToString(const mpsExportStatus& mps_export_status)
{
switch (mps_export_status)
diff --git a/src/libs/antares/study/fwd.h b/src/libs/antares/study/fwd.h
index 839cb4d04e..34ee507b30 100644
--- a/src/libs/antares/study/fwd.h
+++ b/src/libs/antares/study/fwd.h
@@ -525,6 +525,26 @@ const char* RenewableGenerationModellingToCString(RenewableGenerationModelling r
*/
RenewableGenerationModelling StringToRenewableGenerationModelling(const AnyString& text);
+/*
+ * Maintenance Planning
+ */
+enum MaintenancePlanningModelling
+{
+ mpRandomized = 0, // Default
+ mpOptimized, // Using Maintenance Planning algorithm
+ mpUnknown,
+};
+
+/*!
+** \brief Convert a Maintenance-Planning into a text
+*/
+const char* MaintenancePlanningModellingToCString(MaintenancePlanningModelling mtcPlanning);
+
+/*!
+** \brief Convert a text into a MaintenancePlanningModelling
+*/
+MaintenancePlanningModelling StringToMaintenancePlanningModelling(const AnyString& text);
+
// ------------------------
// MPS export status
// ------------------------
diff --git a/src/libs/antares/study/load.cpp b/src/libs/antares/study/load.cpp
index b93af9dcea..8f6e95feb1 100644
--- a/src/libs/antares/study/load.cpp
+++ b/src/libs/antares/study/load.cpp
@@ -116,11 +116,11 @@ void Study::parameterFiller(const StudyLoadOptions& options)
{
// We have time-series to import
StudyVersion studyVersion;
- if (parameters.exportTimeSeriesInInput && studyVersion.isStudyLatestVersion(folder.c_str()))
+ if (parameters.exportTimeSeriesInInput && !studyVersion.isStudyLatestVersion(folder.c_str()))
{
logs.info() << "Stochastic TS stored in input parametrized."
" Disabling Store in input because study is not at latest version"
- "Prevents writing data in unsupported format at the study version";
+ " This Prevents writing data in unsupported format at the study version";
parameters.exportTimeSeriesInInput = 0;
}
}
@@ -205,6 +205,8 @@ bool Study::internalLoadFromFolder(const String& path, const StudyLoadOptions& o
ret = internalLoadCorrelationMatrices(options) && ret;
// Binding constraints
ret = internalLoadBindingConstraints(options) && ret;
+ // Maintenance Group-s
+ ret = internalLoadMaintenanceGroup(options) && ret;
// Sets of areas & links
ret = internalLoadSets() && ret;
@@ -268,6 +270,14 @@ bool Study::internalLoadBindingConstraints(const StudyLoadOptions& options)
return (!r && options.loadOnlyNeeded) ? false : r;
}
+bool Study::internalLoadMaintenanceGroup(const StudyLoadOptions& options)
+{
+ // All checks are performed in 'loadFromFolder'
+ buffer.clear() << folderInput << SEP << "maintenanceplanning";
+ bool r = maintenanceGroups.loadFromFolder(*this, options, buffer);
+ return (!r && options.loadOnlyNeeded) ? false : r;
+}
+
class SetHandlerAreas
{
public:
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroup.cpp b/src/libs/antares/study/maintenance_planning/MaintenanceGroup.cpp
new file mode 100644
index 0000000000..3d7b29f152
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroup.cpp
@@ -0,0 +1,176 @@
+#include "MaintenanceGroup.h"
+#include
+#include
+#include
+#include
+#include "../study.h"
+
+using namespace Yuni;
+using namespace Antares;
+
+#define SEP IO::Separator
+
+namespace Antares::Data
+{
+
+const char* MaintenanceGroup::ResidualLoadDefinitionTypeToCString(
+ MaintenanceGroup::ResidualLoadDefinitionType type)
+{
+ static const char* const names[typeMax + 1] = {"", "weights", "timeserie", ""};
+ assert((uint)type < (uint)(typeMax + 1));
+ return names[type];
+}
+
+MaintenanceGroup::ResidualLoadDefinitionType MaintenanceGroup::StringToResidualLoadDefinitionType(
+ const AnyString& text)
+{
+ ShortString16 l(text);
+ l.toLower();
+
+ if (l == "weights")
+ return typeWeights;
+ if (l == "timeserie")
+ return typeTimeserie;
+ return typeUnknown;
+}
+
+MaintenanceGroup::~MaintenanceGroup()
+{
+#ifndef NDEBUG
+ name_ = "";
+ ID_ = "";
+#endif
+}
+
+void MaintenanceGroup::name(const std::string& newName)
+{
+ name_ = std::move(newName);
+ ID_.clear();
+ Antares::TransformNameIntoID(name_, ID_);
+}
+
+void MaintenanceGroup::loadWeight(const Area* area, double w)
+{
+ if (area)
+ weights_[area].load = w;
+}
+
+void MaintenanceGroup::renewableWeight(const Area* area, double w)
+{
+ if (area)
+ weights_[area].renewable = w;
+}
+
+void MaintenanceGroup::rorWeight(const Area* area, double w)
+{
+ if (area)
+ weights_[area].ror = w;
+}
+
+void MaintenanceGroup::removeAllWeights()
+{
+ weights_.clear();
+}
+
+void MaintenanceGroup::resetToDefaultValues()
+{
+ enabled_ = true;
+ removeAllWeights();
+}
+
+void MaintenanceGroup::clear()
+{
+ // Name / ID
+ this->name_.clear();
+ this->ID_.clear();
+ this->type_ = typeUnknown;
+ this->enabled_ = true;
+}
+
+bool MaintenanceGroup::contains(const MaintenanceGroup* mnt) const
+{
+ return (this == mnt);
+}
+
+bool MaintenanceGroup::contains(const Area* area) const
+{
+ const auto i = weights_.find(area);
+ return (i != weights_.end());
+}
+
+uint64_t MaintenanceGroup::memoryUsage() const
+{
+ return sizeof(MaintenanceGroup)
+ // Estimation
+ + weights_.size() * (sizeof(double) * 3 + 3 * sizeof(void*) * 3);
+}
+
+void MaintenanceGroup::enabled(bool v)
+{
+ enabled_ = v;
+}
+
+void MaintenanceGroup::setResidualLoadDefinitionType(MaintenanceGroup::ResidualLoadDefinitionType t)
+{
+ type_ = t;
+}
+
+// TODO CR27: not implemented for now - used only for UI - maybe to added later
+bool MaintenanceGroup::hasAllWeightedClustersOnLayer(size_t layerID)
+{
+ return true;
+}
+// TODO CR27: not implemented for now - used only for UI - maybe to added later
+void MaintenanceGroup::copyWeights()
+{
+}
+
+double MaintenanceGroup::loadWeight(const Area* area) const
+{
+ auto i = weights_.find(area);
+ return (i != weights_.end()) ? i->second.load : 0.;
+}
+
+double MaintenanceGroup::renewableWeight(const Area* area) const
+{
+ auto i = weights_.find(area);
+ return (i != weights_.end()) ? i->second.renewable : 0.;
+}
+
+double MaintenanceGroup::rorWeight(const Area* area) const
+{
+ auto i = weights_.find(area);
+ return (i != weights_.end()) ? i->second.ror : 0.;
+}
+
+void MaintenanceGroup::setUsedResidualLoadTS(std::array ts)
+{
+ usedResidualLoadTS_ = ts;
+}
+
+std::array MaintenanceGroup::getUsedResidualLoadTS() const
+{
+ return usedResidualLoadTS_;
+}
+
+void MaintenanceGroup::clearAndReset(const MaintenanceGroupName& name,
+ MaintenanceGroup::ResidualLoadDefinitionType newType)
+{
+ // Name / ID
+ name_ = std::move(name);
+ ID_.clear();
+ TransformNameIntoID(name_, ID_);
+ // New type
+ type_ = newType;
+ // Resetting the weights
+ removeAllWeights();
+}
+
+void MaintenanceGroup::copyFrom(MaintenanceGroup const* original)
+{
+ clearAndReset(original->name(), original->type());
+ weights_ = original->weights_;
+ enabled_ = original->enabled_;
+}
+
+} // namespace Antares::Data
\ No newline at end of file
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroup.h b/src/libs/antares/study/maintenance_planning/MaintenanceGroup.h
new file mode 100644
index 0000000000..4d50940b89
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroup.h
@@ -0,0 +1,280 @@
+/*
+** Copyright 2007-2023 RTE
+** Authors: Antares_Simulator Team
+**
+** This file is part of Antares_Simulator.
+**
+** Antares_Simulator is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** There are special exceptions to the terms and conditions of the
+** license as they are applied to this software. View the full text of
+** the exceptions in file COPYING.txt in the directory of this software
+** distribution
+**
+** Antares_Simulator is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with Antares_Simulator. If not, see .
+**
+** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions
+*/
+#pragma once
+
+#include
+#include
+#include
+#include "../fwd.h"
+#include "../binding_constraint/EnvForLoading.h"
+#include
+#include
+#include "antares/study/filter.h"
+
+#include
+#include
+#include
+#include
+#include
+#include "antares/study/area/area.h"
+#include
+
+namespace Antares::Data
+{
+// Forward declaration
+struct CompareMaintenanceGroupName;
+class MaintenanceGroup : public Yuni::NonCopyable
+{
+ friend class MaintenanceGroupLoader;
+ friend class MaintenanceGroupSaver;
+
+public:
+ enum ResidualLoadDefinitionType
+ {
+ //! Unknown status
+ typeUnknown = 0,
+ //! residual Load timeserie is defined using weights
+ typeWeights,
+ //! residual Load timeserie is given directly as an input
+ typeTimeserie,
+ //! The maximum number of types
+ typeMax
+ };
+
+ struct Weights final
+ {
+ double load = 1.0;
+ double renewable = 1.0;
+ double ror = 1.0;
+ };
+
+ using MaintenanceGroupName = std::string;
+ //! Map of load, renewable or ror weight-s
+ using weightMap = std::map;
+ //! Iterator
+ using iterator = weightMap::iterator;
+ //! Const iterator
+ using const_iterator = weightMap::const_iterator;
+
+ /*!
+ ** \brief Convert a Residual Load Definition Type into a mere C-String
+ */
+ static const char* ResidualLoadDefinitionTypeToCString(ResidualLoadDefinitionType t);
+
+ /*!
+ ** \brief Convert a string into its corresponding Residual Load Definition Type
+ */
+ static ResidualLoadDefinitionType StringToResidualLoadDefinitionType(const AnyString& text);
+
+ //! \name Constructor & Destructor
+ //@{
+ /*!
+ ** \brief Destructor
+ */
+ ~MaintenanceGroup();
+ //@}
+
+ //! \name / ID
+ //@{
+ /*!
+ ** \brief Get the name of the Maintenance Group
+ */
+ const MaintenanceGroupName& name() const; // using here different string definition
+ /*!
+ ** \brief Set the name of the Maintenance Group
+ **
+ ** The ID will be changed in the same time
+ */
+ void name(const std::string& newname); // using here different string definition
+
+ /*!
+ ** \brief Get the ID of the binding constraint
+ */
+ const MaintenanceGroupName& id() const;
+ //@}
+
+ //! \name iterator
+ //@{
+ iterator begin();
+ const_iterator begin() const;
+
+ iterator end();
+ const_iterator end() const;
+ //@}
+
+ /*!
+ ** \brief resets all to default data
+ */
+ void resetToDefaultValues();
+
+ /*!
+ ** \brief returns if Maintenance Group is skipped for generating ts and if it is active
+ */
+ bool skipped() const;
+ bool isActive() const;
+
+ // used only for UI - maybe to added later
+ bool hasAllWeightedClustersOnLayer(size_t layerID);
+
+ //! \name per Area
+ //@{
+ /*!
+ ** \brief Get the load, renewable or ror weight of a given area
+ **
+ ** \return The load, renewable or ror weight of the thermal area. 0. if not found
+ */
+ double loadWeight(const Area* area) const;
+ double renewableWeight(const Area* area) const;
+ double rorWeight(const Area* area) const;
+
+ /*!
+ ** \brief Set the load, renewable or ror weight of a given area
+ */
+ void loadWeight(const Area* area, double w);
+ void renewableWeight(const Area* area, double w);
+ void rorWeight(const Area* area, double w);
+
+ /*!
+ ** \brief Remove all weights for load, renewable and ror
+ */
+ void removeAllWeights();
+
+ // used only in UI
+ /*!
+ ** \brief Copy all weights from another Maintenance Group
+ */
+ void copyWeights();
+
+ /*!
+ ** \brief Get how many areas the Maintenance Group contains
+ */
+ uint areaCount() const;
+ //@}
+
+ //! \name Type of the Maintenance Group
+ //@{
+ /*!
+ ** \brief Get the ResidualLoadDefinitionType of the Maintenance Group
+ */
+ ResidualLoadDefinitionType type() const;
+
+ /*!
+ ** \brief Set the ResidualLoadDefinitionType of the Maintenance Group
+ */
+ void setResidualLoadDefinitionType(ResidualLoadDefinitionType t);
+ //@}
+
+ //! \name UsedResidualLoadTS
+ //@{
+ /*!
+ ** \brief Get the UsedResidualLoadTS_ of the Maintenance Group
+ */
+ void setUsedResidualLoadTS(std::array ts);
+
+ /*!
+ ** \brief Set the UsedResidualLoadTS_ of the Maintenance Group
+ */
+ std::array getUsedResidualLoadTS() const;
+ //@}
+
+ //! \name Enabled / Disabled
+ //@{
+ //! Get if the Maintenance Group is enabled
+ bool enabled() const;
+ //! Enabled / Disabled the Maintenance Group
+ void enabled(bool v);
+ //@}
+
+ //! \name Reset // this is only used in UI and for BC in Kirchhoff generator, so we do not
+ //! really need it here
+ //@{
+ /*!
+ ** \brief Clear all values and reset the Maintenance Group to its new type
+ **
+ ** \param name Name of the Maintenance Group
+ ** \param newType Its new ResidualLoadDefinitionType
+ */
+ void clearAndReset(const MaintenanceGroupName& name, ResidualLoadDefinitionType newType);
+ //@}
+
+ //! \name Memory Usage
+ //@{
+ /*!
+ ** \brief Get the memory usage
+ */
+ uint64_t memoryUsage() const;
+ //@}
+
+ /*!
+ ** \brief Get if the Maintenance Group contains a given area
+ */
+ bool contains(const Area* area) const;
+
+ /*!
+ ** \brief Get if the given Maintenance Group is identical
+ */
+ bool contains(const MaintenanceGroup* mnt) const;
+
+private:
+
+ //! Raw name
+ MaintenanceGroupName name_;
+ //! Raw ID
+ MaintenanceGroupName ID_;
+
+ //! Weights for load, renewable and ror
+ weightMap weights_;
+ //! Type of the Maintenance Group
+ ResidualLoadDefinitionType type_ = typeWeights;
+ //! Enabled / Disabled
+ bool enabled_ = true;
+ //! 8760 x 1 - Matrix of residual loads per h provided by the user
+ // TODO CR27: for phase-II -> setters, getters, reset, markAsModified, resetToDefaultValues, forceReload, memoryUsage
+ Matrix<> userProvidedResidualLoadTS_;
+ //! array of residual loads per h
+ //! if typeTimeserie -> equal to userProvidedResidualLoadTS_,
+ //! if typeWeights -> calculated using per area weights
+ std::array usedResidualLoadTS_;
+
+ void clear();
+ void copyFrom(MaintenanceGroup const* original);
+
+}; // class MaintenanceGroup
+
+// class MntGrpList
+struct CompareMaintenanceGroupName final
+{
+ bool operator()(const std::shared_ptr& s1,
+ const std::shared_ptr& s2) const
+ {
+ return s1->name() < s2->name();
+ }
+};
+
+} // namespace Antares::Data
+
+#include "MaintenanceGroup.hxx"
\ No newline at end of file
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroup.hxx b/src/libs/antares/study/maintenance_planning/MaintenanceGroup.hxx
new file mode 100644
index 0000000000..89ebfd8b88
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroup.hxx
@@ -0,0 +1,89 @@
+/*
+** Copyright 2007-2023 RTE
+** Authors: Antares_Simulator Team
+**
+** This file is part of Antares_Simulator.
+**
+** Antares_Simulator is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** There are special exceptions to the terms and conditions of the
+** license as they are applied to this software. View the full text of
+** the exceptions in file COPYING.txt in the directory of this software
+** distribution
+**
+** Antares_Simulator is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with Antares_Simulator. If not, see .
+**
+** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions
+*/
+#pragma once
+
+#include
+#include "MaintenanceGroup.h"
+
+namespace Antares::Data
+{
+inline const MaintenanceGroup::MaintenanceGroupName& MaintenanceGroup::name() const
+{
+ return name_;
+}
+
+inline const MaintenanceGroup::MaintenanceGroupName& MaintenanceGroup::id() const
+{
+ return ID_;
+}
+
+inline uint MaintenanceGroup::areaCount() const
+{
+ return (uint)weights_.size();
+}
+
+inline bool MaintenanceGroup::enabled() const
+{
+ return enabled_;
+}
+
+inline MaintenanceGroup::ResidualLoadDefinitionType MaintenanceGroup::type() const
+{
+ return type_;
+}
+
+inline bool MaintenanceGroup::skipped() const
+{
+ return areaCount() == 0;
+}
+
+inline bool MaintenanceGroup::isActive() const
+{
+ return enabled() && !skipped();
+}
+
+inline MaintenanceGroup::iterator MaintenanceGroup::begin()
+{
+ return weights_.begin();
+}
+
+inline MaintenanceGroup::iterator MaintenanceGroup::end()
+{
+ return weights_.end();
+}
+
+inline MaintenanceGroup::const_iterator MaintenanceGroup::begin() const
+{
+ return weights_.begin();
+}
+
+inline MaintenanceGroup::const_iterator MaintenanceGroup::end() const
+{
+ return weights_.end();
+}
+
+} // namespace Antares::Data
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroupLoader.cpp b/src/libs/antares/study/maintenance_planning/MaintenanceGroupLoader.cpp
new file mode 100644
index 0000000000..b5b48d3901
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroupLoader.cpp
@@ -0,0 +1,158 @@
+//
+// Created by milos on 1/11/23.
+//
+
+#include "MaintenanceGroupLoader.h"
+#include
+#include
+#include
+#include
+#include "MaintenanceGroup.h"
+#include "yuni/core/string/string.h"
+#include "antares/study/version.h"
+
+namespace Antares::Data
+{
+using namespace Yuni;
+
+std::vector> MaintenanceGroupLoader::load(EnvForLoading env)
+{
+ auto mnt = std::make_shared();
+ mnt->clear();
+
+ // Foreach property in the section...
+ for (const IniFile::Property* p = env.section->firstProperty; p; p = p->next)
+ {
+ if (p->key.empty())
+ continue;
+
+ if (p->key == "name")
+ {
+ mnt->name_ = p->value;
+ continue;
+ }
+ if (p->key == "id")
+ {
+ mnt->ID_ = toLower(p->value);
+ continue;
+ }
+ if (p->key == "enabled")
+ {
+ mnt->enabled_ = p->value.to();
+ continue;
+ }
+ if (p->key == "residual-load-definition")
+ {
+ mnt->type_ = MaintenanceGroup::StringToResidualLoadDefinitionType(p->value);
+ continue;
+ }
+
+ // Check if key is valid
+ std::string setKey = p->key.to();
+ if (!((setKey.find(".weight-load") != std::string::npos)
+ || (setKey.find(".weight-renewable") != std::string::npos)
+ || (setKey.find(".weight-ror") != std::string::npos)))
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name << "]: `" << p->key
+ << "`: invalid key";
+ continue;
+ }
+
+ // Separate the key
+ const Area* area = env.areaList.findAreaFromINIKey(p->key);
+ if (!area)
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name << "]: `" << p->key
+ << "`: area not found";
+ continue;
+ }
+
+ // Initialize the value
+ double val = 1;
+ if (bool ret = CheckValue(env, p, val); !ret)
+ continue;
+
+ // check is it load, renewable or ror.
+ auto offset = setKey.find("."); // It must be found because we already checked the validity of the key
+ std::string weightName(setKey.c_str() + offset + 1, setKey.size() - (offset + 1));
+ if (weightName == "weight-load")
+ mnt->loadWeight(area, val);
+ else if (weightName == "weight-renewable")
+ mnt->renewableWeight(area, val);
+ else if (weightName == "weight-ror")
+ mnt->rorWeight(area, val);
+ else
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name << "]: `" << p->key
+ << "`: invalid key";
+ continue;
+ }
+ }
+
+ // Checking for validity
+ if (mnt->name_.empty())
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name
+ << "]: Invalid maintenance group name";
+ return {};
+ }
+ if (mnt->ID_.empty())
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name
+ << "]: Invalid maintenance group id";
+ return {};
+ }
+ if (mnt->type_ == mnt->typeUnknown)
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name
+ << "]: Invalid Residual Load Definition Type";
+ return {};
+ }
+
+ return {mnt};
+}
+
+bool MaintenanceGroupLoader::CheckValue(const EnvForLoading& env,
+ const IniFile::Property* p,
+ double& w)
+{
+ // Convert to a double and report an error if conversion fails
+ auto stringValue = p->value.to();
+ char* endptr;
+ strtod(stringValue.c_str(), &endptr);
+ if (!(*endptr == '\0'))
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name << "]: `" << p->key
+ << "`: weight cannot be converted to number";
+ return false;
+ }
+
+ // check if number is between 0 and 1.0
+ double val = p->value.to();
+ if (!(val >= 0.0 && val <= 1.0))
+ {
+ logs.error() << env.iniFilename << ": in [" << env.section->name << "]: `" << p->key
+ << "`: weight is not in the [0, 1] range";
+ return false;
+ }
+
+ // Output the parsed numbers
+ w = val;
+
+ return true;
+}
+
+std::string MaintenanceGroupLoader::toLower(const std::string& str)
+{
+ std::string result = str; // Create a copy of the input string
+
+ // Use std::transform to convert each character to lowercase
+ std::transform(result.begin(),
+ result.end(),
+ result.begin(),
+ [](unsigned char c) { return std::tolower(c); });
+
+ return result;
+}
+
+} // namespace Antares::Data
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroupLoader.h b/src/libs/antares/study/maintenance_planning/MaintenanceGroupLoader.h
new file mode 100644
index 0000000000..55cc20d3a7
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroupLoader.h
@@ -0,0 +1,42 @@
+//
+// Created by milos on 1/11/23.
+//
+
+#pragma once
+
+#include
+#include "antares/study/area/area.h"
+#include "../binding_constraint/EnvForLoading.h"
+#include "MaintenanceGroup.h"
+
+namespace Antares::Data
+{
+
+class MaintenanceGroup;
+class MaintenanceGroupLoader
+{
+public:
+ std::vector> load(EnvForLoading env);
+
+private:
+ static bool CheckValue(const EnvForLoading& env, const IniFile::Property* p, double& w);
+
+ std::string toLower(const std::string& str);
+};
+
+} // namespace Antares::Data
+
+
+/*
+TODO CR27:
+ for II phase:
+ * check if same area is inside more than 1 Maintenance Group - and throw error
+ * what happens when area name is changed - UI code -- tested area name will be changed in INI file once study is saved - OK
+ * what happens if the area is deleted - UI code - tested all mnt groups containing area name will be removed from INI file once study is saved
+ - OK?! or not OK! - maybe it is better to remove this area from mnt group?
+ This will actually happen if you try to load the mnt groups from ini file and the area from INI file do not exist in the model.
+ That area,with all weights, will be removed from INI file. Mnt Group-s will not be removed - just missing area will be removed from the mnt group.
+
+
+*/
+
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.cpp b/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.cpp
new file mode 100644
index 0000000000..fb281918a9
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.cpp
@@ -0,0 +1,323 @@
+//
+// Created by milos on 1/11/23.
+//
+
+#include "MaintenanceGroupRepository.h"
+#include
+#include
+#include
+#include
+#include "MaintenanceGroup.h"
+#include
+#include "MaintenanceGroupLoader.h"
+#include "MaintenanceGroupSaver.h"
+
+void Data::MaintenanceGroupRepository::clear()
+{
+ maintenanceGroups_.clear();
+ activeMaintenanceGroups_.reset();
+}
+
+namespace Antares::Data
+{
+std::shared_ptr MaintenanceGroupRepository::find(const AnyString& id)
+{
+ for (auto const& i : maintenanceGroups_)
+ {
+ if (i->id() == id)
+ return i;
+ }
+ return nullptr;
+}
+
+std::shared_ptr MaintenanceGroupRepository::find(
+ const AnyString& id) const
+{
+ for (const auto& i : maintenanceGroups_)
+ {
+ if (i->id() == id)
+ return i;
+ }
+ return nullptr;
+}
+
+MaintenanceGroup* MaintenanceGroupRepository::findByName(const AnyString& name)
+{
+ for (auto const& i : maintenanceGroups_)
+ {
+ if (i->name() == name)
+ return i.get();
+ }
+ return nullptr;
+}
+
+const MaintenanceGroup* MaintenanceGroupRepository::findByName(const AnyString& name) const
+{
+ for (const auto& i : maintenanceGroups_)
+ {
+ if (i->name() == name)
+ return i.get();
+ }
+ return nullptr;
+}
+
+void MaintenanceGroupRepository::removeMaintenanceGroupsWhoseNameConstains(const AnyString& filter)
+{
+ NameContainsMnt pred(filter);
+ maintenanceGroups_.erase(
+ std::remove_if(maintenanceGroups_.begin(), maintenanceGroups_.end(), pred),
+ maintenanceGroups_.end());
+}
+
+bool compareMaintenanceGroups(const std::shared_ptr& s1,
+ const std::shared_ptr& s2)
+{
+ if (s1->name() != s2->name())
+ {
+ return s1->name() < s2->name();
+ }
+ else
+ {
+ return s1->type() < s2->type();
+ }
+}
+
+std::shared_ptr MaintenanceGroupRepository::add(const AnyString& name)
+{
+ auto mnt = std::make_shared();
+ mnt->name(name);
+ maintenanceGroups_.push_back(mnt);
+ std::sort(maintenanceGroups_.begin(), maintenanceGroups_.end(), compareMaintenanceGroups);
+ return mnt;
+}
+
+std::vector> MaintenanceGroupRepository::LoadMaintenanceGroup(
+ EnvForLoading env)
+{
+ MaintenanceGroupLoader loader;
+ return loader.load(std::move(env));
+}
+
+bool MaintenanceGroupRepository::saveToFolder(const AnyString& folder) const
+{
+ MaintenanceGroupSaver::EnvForSaving env;
+ env.folder = folder;
+ return internalSaveToFolder(env);
+}
+
+bool MaintenanceGroupRepository::rename(MaintenanceGroup* mnt, const AnyString& newname)
+{
+ // Copy of the name
+ MaintenanceGroup::MaintenanceGroupName name;
+ name = newname;
+ if (name == mnt->name())
+ return true;
+ MaintenanceGroup::MaintenanceGroupName id;
+ Antares::TransformNameIntoID(name, id);
+ if (std::any_of(maintenanceGroups_.begin(),
+ maintenanceGroups_.end(),
+ [&id](auto mntGroup) { return mntGroup->id() == id; }))
+ {
+ return false;
+ }
+ mnt->name(name);
+ return true;
+}
+
+bool MaintenanceGroupRepository::loadFromFolder(Study& study,
+ const StudyLoadOptions& options,
+ const AnyString& folder)
+{
+ // do not load if below 870
+ if (study.header.version < 870)
+ return true;
+
+ // Log entries
+ logs.info(); // space for beauty
+ logs.info() << "Loading maintenance groups...";
+
+ // Cleaning
+ clear();
+
+ if (study.usedByTheSolver)
+ {
+ if (!study.parameters.maintenancePlanning.isOptimized())
+ {
+ logs.info()
+ << " The maintenance groups shall be ignored due to the advanced parameters";
+ return true;
+ }
+ }
+
+ EnvForLoading env(study.areas, study.header.version);
+ env.folder = folder;
+
+ env.iniFilename << env.folder << Yuni::IO::Separator << "maintenancegroups.ini";
+ IniFile ini;
+ if (!ini.open(env.iniFilename))
+ {
+ return false;
+ }
+
+ // For each section
+ if (ini.firstSection)
+ {
+ for (env.section = ini.firstSection; env.section; env.section = env.section->next)
+ {
+ if (env.section->firstProperty)
+ {
+ auto new_mnt = LoadMaintenanceGroup(env);
+ std::copy(new_mnt.begin(), new_mnt.end(), std::back_inserter(maintenanceGroups_));
+ }
+ }
+ }
+
+ // Logs
+ if (maintenanceGroups_.empty())
+ logs.info() << "No maintenance group found";
+ else
+ {
+ std::sort(maintenanceGroups_.begin(), maintenanceGroups_.end(), compareMaintenanceGroups);
+
+ if (maintenanceGroups_.size() == 1)
+ logs.info() << "1 maintenance group found";
+ else
+ logs.info() << maintenanceGroups_.size() << " maintenance groups found";
+ }
+
+ return true;
+}
+
+bool MaintenanceGroupRepository::internalSaveToFolder(
+ MaintenanceGroupSaver::EnvForSaving& env) const
+{
+ if (maintenanceGroups_.empty())
+ {
+ logs.info() << "No maintenance group to export.";
+ if (!Yuni::IO::Directory::Create(env.folder))
+ return false;
+ // stripping the file
+ env.folder << Yuni::IO::Separator << "maintenancegroups.ini";
+ return Yuni::IO::File::CreateEmptyFile(env.folder);
+ }
+
+ if (maintenanceGroups_.size() == 1)
+ logs.info() << "Exporting 1 maintenance group...";
+ else
+ logs.info() << "Exporting " << maintenanceGroups_.size() << " maintenance groups...";
+
+ if (!Yuni::IO::Directory::Create(env.folder))
+ return false;
+
+ IniFile ini;
+ bool ret = true;
+ uint index = 0;
+ auto end = maintenanceGroups_.end();
+ Yuni::ShortString64 text;
+
+ for (auto i = maintenanceGroups_.begin(); i != end; ++i, ++index)
+ {
+ text = index;
+ env.section = ini.addSection(text);
+ ret = Antares::Data::MaintenanceGroupSaver::saveToEnv(env, i->get()) && ret;
+ }
+
+ env.folder << Yuni::IO::Separator << "maintenancegroups.ini";
+ return ini.save(env.folder) && ret;
+}
+
+void MaintenanceGroupRepository::reverseWeightSign(const AreaLink* lnk)
+{
+}
+
+uint64_t MaintenanceGroupRepository::memoryUsage() const
+{
+ uint64_t m = sizeof(MaintenanceGroupRepository);
+ for (const auto& i : maintenanceGroups_)
+ m += i->memoryUsage();
+ return m;
+}
+
+namespace // anonymous
+{
+template
+class RemovePredicate final
+{
+public:
+ explicit RemovePredicate(const T* u) : pItem(u)
+ {
+ }
+
+ bool operator()(const std::shared_ptr& mnt) const
+ {
+ assert(mnt);
+ if (mnt->contains(pItem))
+ {
+ logs.info() << "destroying the maintenance group " << mnt->name();
+ return true;
+ }
+ return false;
+ }
+
+private:
+ const T* pItem;
+};
+
+} // anonymous namespace
+
+void MaintenanceGroupRepository::remove(const Area* area)
+{
+ RemovePredicate predicate(area);
+ auto e = std::remove_if(maintenanceGroups_.begin(), maintenanceGroups_.end(), predicate);
+ maintenanceGroups_.erase(e, maintenanceGroups_.end());
+ activeMaintenanceGroups_.reset();
+}
+
+void MaintenanceGroupRepository::remove(const MaintenanceGroup* mnt)
+{
+ RemovePredicate predicate(mnt);
+ auto e = std::remove_if(maintenanceGroups_.begin(), maintenanceGroups_.end(), predicate);
+ maintenanceGroups_.erase(e, maintenanceGroups_.end());
+ activeMaintenanceGroups_.reset();
+}
+
+MaintenanceGroupRepository::iterator MaintenanceGroupRepository::begin()
+{
+ return maintenanceGroups_.begin();
+}
+
+MaintenanceGroupRepository::const_iterator MaintenanceGroupRepository::begin() const
+{
+ return maintenanceGroups_.begin();
+}
+
+MaintenanceGroupRepository::iterator MaintenanceGroupRepository::end()
+{
+ return maintenanceGroups_.end();
+}
+
+MaintenanceGroupRepository::const_iterator MaintenanceGroupRepository::end() const
+{
+ return maintenanceGroups_.end();
+}
+
+std::vector> MaintenanceGroupRepository::activeMaintenanceGroups()
+ const
+{
+ if (activeMaintenanceGroups_)
+ {
+ return activeMaintenanceGroups_.value();
+ }
+ else
+ {
+ std::vector> out;
+ std::copy_if(maintenanceGroups_.begin(),
+ maintenanceGroups_.end(),
+ std::back_inserter(out),
+ [](const auto& mnt) { return mnt->isActive(); });
+ activeMaintenanceGroups_ = std::move(out);
+ return activeMaintenanceGroups_.value();
+ }
+}
+
+} // namespace Antares::Data
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.h b/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.h
new file mode 100644
index 0000000000..4bdaf14dfd
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.h
@@ -0,0 +1,166 @@
+//
+// Created by milos on 1/11/23.
+//
+
+#pragma once
+
+#include
+#include
+#include "MaintenanceGroup.h"
+#include "MaintenanceGroupSaver.h"
+
+namespace Antares::Data
+{
+class MaintenanceGroupRepository final : public Yuni::NonCopyable
+{
+public:
+ //! Vector of Maintenance Group-s
+ using Vector = std::vector>;
+ //! Ordered Set of Maintenance Group-s
+ using Set = std::set, CompareMaintenanceGroupName>;
+
+ using iterator = Vector::iterator;
+ using const_iterator = Vector::const_iterator;
+
+ //! \name Constructor && Destructor
+ //@{
+ /*!
+ ** \brief Default constructor
+ */
+ MaintenanceGroupRepository() = default;
+ /*!
+ ** \brief Destructor
+ */
+ ~MaintenanceGroupRepository() = default;
+ //@}
+
+ /*!
+ ** \brief Delete all Maintenance Group-s
+ */
+ void clear();
+
+ //! \name Iterating through all Maintenance Group-s
+ //@{
+ /*!
+ ** \brief Iterate through all Maintenance Group-s
+ */
+ template
+ void each(const PredicateT& predicate);
+ /*!
+ ** \brief Iterate through all Maintenance Group-s (const)
+ */
+ template
+ void each(const PredicateT& predicate) const;
+
+ iterator begin();
+ [[nodiscard]] const_iterator begin() const;
+
+ iterator end();
+ [[nodiscard]] const_iterator end() const;
+
+ [[nodiscard]] bool empty() const;
+ //@}
+
+ /*!
+ ** \brief Add a new Maintenance Group
+ */
+ std::shared_ptr add(const AnyString& name);
+
+ /*!
+ ** Try to find a Maintenance Group from its id
+ */
+ std::shared_ptr find(const AnyString& id);
+
+ /*!
+ ** \brief Try to find a Maintenance Group from its id (const)
+ */
+ [[nodiscard]] std::shared_ptr find(const AnyString& id) const;
+
+ /*!
+ ** \brief Try to find a Maintenance Group from its name
+ */
+ [[nodiscard]] Data::MaintenanceGroup* findByName(const AnyString& name);
+
+ /*!
+ ** \brief Try to find a Maintenance Group from its name (const)
+ */
+ [[nodiscard]] const Data::MaintenanceGroup* findByName(const AnyString& name) const;
+
+ /*!
+ ** \brief Load all Maintenance Group from a folder
+ */
+ [[nodiscard]] bool loadFromFolder(Data::Study& s,
+ const Data::StudyLoadOptions& options,
+ const AnyString& folder);
+
+ /*!
+ ** \brief Save all Maintenance Group into a folder
+ */
+ [[nodiscard]] bool saveToFolder(const AnyString& folder) const;
+
+ // TODO CR27: do we need this at all
+ /*!
+ ** \brief Reverse the sign of the weight for a given Maintenance Group
+ **
+ ** This method is used when reverting an Maintenance Group
+ */
+ void reverseWeightSign(const Data::AreaLink* lnk);
+
+ //! Get the number of Maintenance Group
+ [[nodiscard]] uint size() const;
+
+ /*!
+ ** \brief Remove a Maintenance Group
+ */
+ void remove(const Data::MaintenanceGroup* bc);
+ /*!
+ ** \brief Remove any Maintenance Group linked with a given area
+ */
+ void remove(const Data::Area* area);
+
+ /*!
+ ** \brief Remove any Maintenance Group whose name contains the string in argument
+ */
+ void removeMaintenanceGroupsWhoseNameConstains(const AnyString& filter);
+
+ /*!
+ ** \brief Rename a Maintenance Group
+ */
+ bool rename(Data::MaintenanceGroup* bc, const AnyString& newname);
+
+ /*!
+ ** \brief Get the memory usage
+ */
+ [[nodiscard]] uint64_t memoryUsage() const;
+
+ static std::vector> LoadMaintenanceGroup(EnvForLoading env);
+
+ [[nodiscard]] std::vector> activeMaintenanceGroups() const;
+
+private:
+
+ // private methods
+ bool internalSaveToFolder(Data::MaintenanceGroupSaver::EnvForSaving& env) const;
+
+ //! All Maintenance Group-s
+ Data::MaintenanceGroupRepository::Vector maintenanceGroups_;
+ mutable std::optional>> activeMaintenanceGroups_;
+};
+
+struct NameContainsMnt final
+{
+public:
+ explicit NameContainsMnt(AnyString filter) : pFilter(std::move(filter))
+ {
+ }
+ bool operator()(const std::shared_ptr& s) const
+ {
+ return ((s->name()).find(pFilter) != std::string::npos);
+ }
+
+private:
+ AnyString pFilter;
+};
+} // namespace Antares::Data
+
+#include "MaintenanceGroupRepository.hxx"
\ No newline at end of file
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.hxx b/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.hxx
new file mode 100644
index 0000000000..6f4a384967
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroupRepository.hxx
@@ -0,0 +1,38 @@
+//
+// Created by milos on 1/11/23.
+//
+
+#pragma once
+
+#include
+#include
+
+namespace Antares::Data
+{
+
+inline uint MaintenanceGroupRepository::size() const
+{
+ return (uint)maintenanceGroups_.size();
+}
+
+inline bool MaintenanceGroupRepository::empty() const
+{
+ return maintenanceGroups_.empty();
+}
+
+template
+inline void MaintenanceGroupRepository::each(const PredicateT& predicate)
+{
+ uint count = (uint)maintenanceGroups_.size();
+ for (uint i = 0; i != count; ++i)
+ predicate(*(maintenanceGroups_[i]));
+}
+
+template
+inline void MaintenanceGroupRepository::each(const PredicateT& predicate) const
+{
+ uint count = (uint)maintenanceGroups_.size();
+ for (uint i = 0; i != count; ++i)
+ predicate(*(maintenanceGroups_[i].get()));
+}
+} // namespace Antares::Data
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroupSaver.cpp b/src/libs/antares/study/maintenance_planning/MaintenanceGroupSaver.cpp
new file mode 100644
index 0000000000..1afc1a4506
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroupSaver.cpp
@@ -0,0 +1,50 @@
+//
+// Created by milos on 1/11/23.
+//
+#include "MaintenanceGroupSaver.h"
+#include "MaintenanceGroup.h"
+#include
+#include
+#include "antares/study/area/area.h"
+#include "antares/study/fwd.h"
+
+namespace Antares::Data
+{
+
+using namespace Yuni;
+bool MaintenanceGroupSaver::saveToEnv(EnvForSaving& env, const MaintenanceGroup* maintenanceGroup)
+{
+ env.section->add("name", maintenanceGroup->name_);
+ env.section->add("id", maintenanceGroup->ID_);
+ env.section->add("enabled", maintenanceGroup->enabled_);
+ env.section->add(
+ "residual-load-definition",
+ MaintenanceGroup::ResidualLoadDefinitionTypeToCString(maintenanceGroup->type_));
+
+ if (!maintenanceGroup->weights_.empty())
+ {
+ auto end = maintenanceGroup->weights_.end();
+ for (auto i = maintenanceGroup->weights_.begin(); i != end; ++i)
+ {
+ // asserts
+ assert(i->first and "Invalid area");
+ String value;
+ const Area& area = *(i->first);
+
+ // add weight-load
+ env.key.clear() << area.name << ".weight-load";
+ value.clear() << i->second.load;
+ env.section->add(env.key, value);
+ // add weight-renewable
+ env.key.clear() << area.name << ".weight-renewable";
+ value.clear() << i->second.renewable;
+ env.section->add(env.key, value);
+ // add weight-ror
+ env.key.clear() << area.name << ".weight-ror";
+ value.clear() << i->second.ror;
+ env.section->add(env.key, value);
+ }
+ }
+ return true;
+}
+} // namespace Antares::Data
\ No newline at end of file
diff --git a/src/libs/antares/study/maintenance_planning/MaintenanceGroupSaver.h b/src/libs/antares/study/maintenance_planning/MaintenanceGroupSaver.h
new file mode 100644
index 0000000000..a027b2ca15
--- /dev/null
+++ b/src/libs/antares/study/maintenance_planning/MaintenanceGroupSaver.h
@@ -0,0 +1,35 @@
+//
+// Created by milos on 1/11/23.
+//
+
+#pragma once
+
+#include
+#include "antares/study/fwd.h"
+#include "MaintenanceGroup.h"
+
+namespace Antares::Data
+{
+class MaintenanceGroupSaver
+{
+public:
+ class EnvForSaving final
+ {
+ public:
+ EnvForSaving() = default;
+
+ //! Current section
+ IniFile::Section* section = nullptr;
+ Yuni::Clob folder;
+ Yuni::CString<(ant_k_area_name_max_length + 8), false> key;
+ };
+
+ /*!
+ ** \brief Save the Maintenance Group into a folder and an INI file
+ **
+ ** \param env All information needed to perform the task
+ ** \return True if the operation succeeded, false otherwise
+ */
+ static bool saveToEnv(EnvForSaving& env, const MaintenanceGroup* maintenanceGroup);
+};
+} // namespace Antares::Data
\ No newline at end of file
diff --git a/src/libs/antares/study/parameters.cpp b/src/libs/antares/study/parameters.cpp
index 0a3be6df4f..1a801beaaa 100644
--- a/src/libs/antares/study/parameters.cpp
+++ b/src/libs/antares/study/parameters.cpp
@@ -102,6 +102,28 @@ static bool ConvertStringToRenewableGenerationModelling(const AnyString& text,
return false;
}
+static bool ConvertStringToMaintenancePlanningModelling(const AnyString& text, MaintenancePlanningModelling& out)
+{
+ CString<24, false> s = text;
+ s.trim();
+ s.toLower();
+ if (s == "randomized")
+ {
+ out = mpRandomized;
+ return true;
+ }
+ if (s == "optimized")
+ {
+ out = mpOptimized;
+ return true;
+ }
+
+ logs.warning() << "parameters: invalid maintenance planning. Got '" << text << "'";
+ out = mpUnknown;
+
+ return false;
+}
+
static bool ConvertCStrToResultFormat(const AnyString& text, ResultFormat& out)
{
CString<24, false> s = text;
@@ -302,6 +324,8 @@ void Parameters::reset()
unitCommitment.ucMode = ucHeuristicFast;
nbCores.ncMode = ncAvg;
renewableGeneration.rgModelling = rgAggregated;
+ // maintenance Planning
+ maintenancePlanning.clear();
// Misc
improveUnitsStartup = false;
@@ -639,6 +663,22 @@ static bool SGDIntLoadFamily_AdqPatch(Parameters& d,
return d.adqPatchParams.updateFromKeyValue(key, value);
}
+static bool SGDIntLoadFamily_MaintenancePlanning(Parameters& d,
+ const String& key,
+ const String& value,
+ const String&)
+{
+ // Maintenance planning modelling
+ if (key == "maintenance-planning-modelling")
+ return ConvertStringToMaintenancePlanningModelling(value,
+ d.maintenancePlanning.mpModelling);
+ if (key == "scenario-number")
+ return value.to(d.maintenancePlanning.scenarioNumber);
+ if (key == "scenario-length")
+ return value.to(d.maintenancePlanning.scenarioLength);
+ return false;
+}
+
static bool SGDIntLoadFamily_OtherPreferences(Parameters& d,
const String& key,
const String& value,
@@ -970,6 +1010,7 @@ bool Parameters::loadFromINI(const IniFile& ini, uint version, const StudyLoadOp
{"output", &SGDIntLoadFamily_Output},
{"optimization", &SGDIntLoadFamily_Optimization},
{"adequacy patch", &SGDIntLoadFamily_AdqPatch},
+ {"maintenance planning", &SGDIntLoadFamily_MaintenancePlanning},
{"other preferences", &SGDIntLoadFamily_OtherPreferences},
{"advanced parameters", &SGDIntLoadFamily_AdvancedParameters},
{"playlist", &SGDIntLoadFamily_Playlist},
@@ -1585,6 +1626,15 @@ void Parameters::saveToINI(IniFile& ini) const
// Adequacy patch
adqPatchParams.saveToINI(ini);
+ // MaintenancePlanning
+ {
+ auto* section = ini.addSection("maintenance planning");
+ section->add("maintenance-planning-modelling",
+ MaintenancePlanningModellingToCString(maintenancePlanning()));
+ section->add("scenario-number", maintenancePlanning.scenarioNumber);
+ section->add("scenario-length", maintenancePlanning.scenarioLength);
+ }
+
// Other preferences
{
auto* section = ini.addSection("other preferences");
@@ -1780,6 +1830,58 @@ bool Parameters::RenewableGeneration::isClusters() const
return rgModelling == Antares::Data::rgClusters;
}
+MaintenancePlanningModelling Parameters::MaintenancePlanning::operator()() const
+{
+ return mpModelling;
+}
+
+void Parameters::MaintenancePlanning::toRandomized()
+{
+ mpModelling = Antares::Data::mpRandomized;
+}
+
+void Parameters::MaintenancePlanning::toOptimized()
+{
+ mpModelling = Antares::Data::mpOptimized;
+}
+
+bool Parameters::MaintenancePlanning::isRandomized() const
+{
+ return mpModelling == Antares::Data::mpRandomized;
+}
+
+bool Parameters::MaintenancePlanning::isOptimized() const
+{
+ return mpModelling == Antares::Data::mpOptimized;
+}
+
+void Parameters::MaintenancePlanning::setScenarioNumber(uint v)
+{
+ scenarioNumber = v;
+}
+
+void Parameters::MaintenancePlanning::setScenarioLength(uint v)
+{
+ scenarioLength = v;
+}
+
+uint Parameters::MaintenancePlanning::getScenarioNumber() const
+{
+ return scenarioNumber;
+}
+
+uint Parameters::MaintenancePlanning::getScenarioLength() const
+{
+ return scenarioLength;
+}
+
+void Parameters::MaintenancePlanning::clear()
+{
+ toRandomized();
+ setScenarioNumber(0);
+ setScenarioLength(0);
+}
+
// Some variables rely on dual values & marginal costs
void Parameters::UCMode::addExcludedVariables(std::vector& out) const
{
diff --git a/src/libs/antares/study/parameters.h b/src/libs/antares/study/parameters.h
index ba951766de..0e9cc8c5b4 100644
--- a/src/libs/antares/study/parameters.h
+++ b/src/libs/antares/study/parameters.h
@@ -299,7 +299,7 @@ class Parameters final
** This value is a mask bits for timeSeries.
** \see TimeSeries
*/
- uint timeSeriesToArchive;
+ uint timeSeriesToArchive; //TMP.INFO CR27: archives = load, thermal etc... - adds category only if store in ouput is yes and stochastic TS status is yes
//@}
//! \name Pre-Processor
@@ -457,6 +457,26 @@ class Parameters final
RenewableGeneration renewableGeneration;
+ struct MaintenancePlanning
+ {
+ //! Maintenance Planning mode
+ MaintenancePlanningModelling mpModelling;
+ MaintenancePlanningModelling operator()() const;
+ uint scenarioNumber = 0;
+ uint scenarioLength = 0;
+ void toRandomized();
+ void toOptimized();
+ bool isRandomized() const;
+ bool isOptimized() const;
+ void setScenarioNumber(uint v);
+ void setScenarioLength(uint v);
+ uint getScenarioNumber() const;
+ uint getScenarioLength() const;
+ void clear();
+ };
+
+ MaintenancePlanning maintenancePlanning;
+
struct
{
//! Initial reservoir levels
diff --git a/src/libs/antares/study/parameters/adq-patch-params.cpp b/src/libs/antares/study/parameters/adq-patch-params.cpp
index 2e012ff37d..eedef3aaef 100644
--- a/src/libs/antares/study/parameters/adq-patch-params.cpp
+++ b/src/libs/antares/study/parameters/adq-patch-params.cpp
@@ -25,7 +25,15 @@ bool LocalMatching::updateFromKeyValue(const Yuni::String& key, const Yuni::Stri
if (key == "set-to-null-ntc-between-physical-out-for-first-step")
return value.to(setToZeroOutsideOutsideLinks);
if (key == "enable-first-step")
- return value.to(enabled);
+ {
+ if (value == "true")
+ {
+ logs.info() << "Property enable-first-step has been disabled, it is known to cause "
+ "errors and inconsistent results";
+ }
+ return true;
+ }
+
return false;
}
@@ -140,15 +148,9 @@ void AdqPatchParams::addExcludedVariables(std::vector& out) const
{
out.emplace_back("DENS");
out.emplace_back("LMR VIOL.");
- out.emplace_back("SPIL. ENRG. CSR");
+ out.emplace_back("UNSP. ENRG CSR");
out.emplace_back("DTG MRG CSR");
}
-
- // If the adequacy patch is enabled, but the LMR is disabled, the DENS variable shouldn't exist
- if (enabled && !localMatching.enabled)
- {
- out.emplace_back("DENS");
- }
}
@@ -176,13 +178,13 @@ bool AdqPatchParams::checkAdqPatchParams(const SimulationMode simulationMode,
checkAdqPatchSimulationModeEconomyOnly(simulationMode);
checkAdqPatchContainsAdqPatchArea(areas);
checkAdqPatchIncludeHurdleCost(includeHurdleCostParameters);
- checkAdqPatchDisabledLocalMatching();
return true;
}
// Adequacy Patch can only be used with Economy Study/Simulation Mode.
-void AdqPatchParams::checkAdqPatchSimulationModeEconomyOnly(const SimulationMode simulationMode) const
+void AdqPatchParams::checkAdqPatchSimulationModeEconomyOnly(
+ const SimulationMode simulationMode) const
{
if (simulationMode != SimulationMode::Economy)
throw Error::IncompatibleSimulationModeForAdqPatch();
@@ -205,11 +207,4 @@ void AdqPatchParams::checkAdqPatchIncludeHurdleCost(const bool includeHurdleCost
if (curtailmentSharing.includeHurdleCost && !includeHurdleCostParameters)
throw Error::IncompatibleHurdleCostCSR();
}
-
-void AdqPatchParams::checkAdqPatchDisabledLocalMatching() const
-{
- if (!localMatching.enabled && curtailmentSharing.priceTakingOrder == AdqPatchPTO::isDens)
- throw Error::AdqPatchDisabledLMR();
-}
-
-} // Antares::Data::AdequacyPatch
+} // namespace Antares::Data::AdequacyPatch
diff --git a/src/libs/antares/study/parameters/adq-patch-params.h b/src/libs/antares/study/parameters/adq-patch-params.h
index 8362d66765..47ec5599c2 100644
--- a/src/libs/antares/study/parameters/adq-patch-params.h
+++ b/src/libs/antares/study/parameters/adq-patch-params.h
@@ -9,7 +9,6 @@
namespace Antares::Data::AdequacyPatch
{
-
//! A default threshold value for initiate curtailment sharing rule
const double defaultThresholdToRunCurtailmentSharing = 0.0;
//! A default threshold value for display local matching rule violations
@@ -58,10 +57,9 @@ enum class AdqPatchPTO
}; // enum AdqPatchPTO
-
struct LocalMatching
{
- bool enabled = true;
+ const bool enabled = false;
//! Transmission capacities from physical areas outside adequacy patch (area type 1) to
//! physical areas inside adequacy patch (area type 2). NTC is set to null (if true)
//! only in the first step of adequacy patch local matching rule.
@@ -71,12 +69,11 @@ struct LocalMatching
//! rule.
bool setToZeroOutsideOutsideLinks = true;
/*!
- ** \brief Reset to default values related to local matching
- */
+ ** \brief Reset to default values related to local matching
+ */
void reset();
bool updateFromKeyValue(const Yuni::String& key, const Yuni::String& value);
void addProperties(IniFile::Section* section) const;
-
};
class CurtailmentSharing
@@ -104,10 +101,8 @@ class CurtailmentSharing
void resetThresholds();
};
-
struct AdqPatchParams
{
-
bool enabled;
LocalMatching localMatching;
CurtailmentSharing curtailmentSharing;
@@ -120,11 +115,9 @@ struct AdqPatchParams
const AreaList& areas,
const bool includeHurdleCostParameters) const;
-
void checkAdqPatchSimulationModeEconomyOnly(const SimulationMode simulationMode) const;
void checkAdqPatchContainsAdqPatchArea(const Antares::Data::AreaList& areas) const;
void checkAdqPatchIncludeHurdleCost(const bool includeHurdleCost) const;
- void checkAdqPatchDisabledLocalMatching() const;
};
} // namespace Antares::Data::AdequacyPatch
diff --git a/src/libs/antares/study/parts/thermal/cluster.cpp b/src/libs/antares/study/parts/thermal/cluster.cpp
index 72548e03fe..c7366728b3 100644
--- a/src/libs/antares/study/parts/thermal/cluster.cpp
+++ b/src/libs/antares/study/parts/thermal/cluster.cpp
@@ -212,6 +212,13 @@ void Data::ThermalCluster::copyFrom(const ThermalCluster& cluster)
modulation = cluster.modulation;
cluster.modulation.unloadFromMemory();
+ // Maintenance Planning additional parameters
+ optimizeMaintenance = cluster.optimizeMaintenance;
+ interPoPeriod = cluster.interPoPeriod;
+ poWindows = cluster.poWindows;
+ originalRandomlyGeneratedDaysSinceLastMaintenance
+ = cluster.originalRandomlyGeneratedDaysSinceLastMaintenance;
+
// Making sure that the data related to the prepro and timeseries are present
// prepro
if (not prepro)
@@ -476,6 +483,12 @@ void Data::ThermalCluster::reset()
modulation.fill(1.);
modulation.fillColumn(thermalMinGenModulation, 0.);
+ // Maintenance Planning additional parameters
+ optimizeMaintenance = true;
+ interPoPeriod = 365;
+ poWindows = 0;
+ originalRandomlyGeneratedDaysSinceLastMaintenance.clear();
+
// prepro
// warning: the variables `prepro` and `series` __must__ not be destroyed
// since the interface may still have a pointer to them.
@@ -588,6 +601,24 @@ bool Data::ThermalCluster::integrityCheck()
ret = MatrixTestForPositiveValues(buffer.c_str(), &modulation) && ret;
}
+ // Maintenance Planning additional parameters
+ if (interPoPeriod < 0 || interPoPeriod > 365)
+ {
+ interPoPeriod = 365;
+ logs.error() << "Thermal cluster: " << parentArea->name << '/' << pName
+ << ": The Inter PO period (days) must be within the range [0,365] (set to "
+ << interPoPeriod << ')';
+ ret = false;
+ }
+ if (poWindows < 0 || poWindows > 365)
+ {
+ poWindows = 0;
+ logs.error() << "Thermal cluster: " << parentArea->name << '/' << pName
+ << ": The PO windows (+/- days) must be within the range [0,365] (set to "
+ << poWindows << ')';
+ ret = false;
+ }
+
// la valeur minStablePower should not be modified
/*
if (minStablePower > nominalCapacity)
diff --git a/src/libs/antares/study/parts/thermal/cluster.h b/src/libs/antares/study/parts/thermal/cluster.h
index a75ce10a00..bcddb6bb5f 100644
--- a/src/libs/antares/study/parts/thermal/cluster.h
+++ b/src/libs/antares/study/parts/thermal/cluster.h
@@ -294,6 +294,20 @@ class ThermalCluster final : public Cluster, public std::enable_shared_from_this
//! Law (ts-generator)
ThermalLaw plannedLaw;
+ // Maintenance Planning additional parameters
+
+ //! Optimize maintenance yes/no
+ bool optimizeMaintenance = true;
+ //! Inter PO period (days) // Average duration between maintenance events
+ int interPoPeriod = 365;
+ //! PO windows (+/- days) // Acceptable maintenance window (+/- N days, 0 as default value)
+ int poWindows = 0;
+ // TODO CR27: keep this here for now
+ // maybe create something like PreproThermal for Maintenance - and move everything there!!!
+ // days since last maintenance - random int number between 0-interPoPeriod
+ // defined per each unit of the cluster
+ std::vector originalRandomlyGeneratedDaysSinceLastMaintenance;
+
//! \name Costs
// Marginal (€/MWh) MA
// Spread (€/MWh) SP
diff --git a/src/libs/antares/study/parts/thermal/cluster_list.cpp b/src/libs/antares/study/parts/thermal/cluster_list.cpp
index 3867bfeca4..fd1c882836 100644
--- a/src/libs/antares/study/parts/thermal/cluster_list.cpp
+++ b/src/libs/antares/study/parts/thermal/cluster_list.cpp
@@ -232,6 +232,14 @@ static bool ThermalClusterLoadFromProperty(ThermalCluster& cluster, const IniFil
if (p->key == "variableomcost")
return p->value.to(cluster.variableomcost);
+ // Maintenance Planning additional parameters
+ if (p->key == "optimize-maintenance")
+ return p->value.to(cluster.optimizeMaintenance);
+ if (p->key == "inter-po-period")
+ return p->value.to(cluster.interPoPeriod);
+ if (p->key == "po-windows")
+ return p->value.to(cluster.poWindows);
+
//pollutant
if (auto it = Pollutant::namesToEnum.find(p->key.c_str()); it != Pollutant::namesToEnum.end())
return p->value.to (cluster.emissions.factors[it->second]);
@@ -378,6 +386,13 @@ bool ThermalClusterList::saveToFolder(const AnyString& folder) const
if (!Math::Zero(c.variableomcost))
s->add("variableomcost", Math::Round(c.variableomcost,3));
+ // Maintenance Planning additional parameters
+ if (!c.optimizeMaintenance)
+ s->add("optimize-maintenance", c.optimizeMaintenance);
+ if (c.interPoPeriod != 365)
+ s->add("inter-po-period", c.interPoPeriod);
+ if (!Math::Zero(c.poWindows))
+ s->add("po-windows", c.poWindows);
//pollutant factor
for (auto const& [key, val] : Pollutant::namesToEnum)
@@ -486,5 +501,29 @@ bool ThermalClusterList::loadEconomicCosts(Study& study, const AnyString& folder
});
}
+bool ThermalClusterList::generateRandomDaysSinceLastMaintenance(Study& study)
+{
+ if (empty() || !study.parameters.maintenancePlanning.isOptimized())
+ return true;
+
+ bool ret = true;
+ MersenneTwister random[seedMax];
+
+ for (auto& c : clusters)
+ {
+ assert(c->parentArea and "cluster: invalid parent area");
+
+ if (!c->optimizeMaintenance)
+ continue;
+
+ for (uint unitIndex = 0; unitIndex < c->unitCount; ++unitIndex)
+ {
+ c->originalRandomlyGeneratedDaysSinceLastMaintenance.push_back(
+ (uint32_t)(floor(random[seedTsGenThermal].next() * c->interPoPeriod)));
+ }
+ }
+ return ret;
+}
+
} // namespace Data
} // namespace Antares
diff --git a/src/libs/antares/study/parts/thermal/cluster_list.h b/src/libs/antares/study/parts/thermal/cluster_list.h
index b23164bfb0..4ce05cd17e 100644
--- a/src/libs/antares/study/parts/thermal/cluster_list.h
+++ b/src/libs/antares/study/parts/thermal/cluster_list.h
@@ -83,6 +83,8 @@ class ThermalClusterList : public ClusterList
bool loadEconomicCosts(Study& s, const AnyString& folder);
+ bool generateRandomDaysSinceLastMaintenance(Study& s);
+
bool savePreproToFolder(const AnyString& folder) const;
bool saveEconomicCosts(const AnyString& folder) const;
diff --git a/src/libs/antares/study/runtime/runtime.cpp b/src/libs/antares/study/runtime/runtime.cpp
index 0f112e9f53..c875e9bdc2 100644
--- a/src/libs/antares/study/runtime/runtime.cpp
+++ b/src/libs/antares/study/runtime/runtime.cpp
@@ -335,6 +335,7 @@ bool StudyRuntimeInfos::loadFromStudy(Study& study)
logs.info() << " thermal clusters (must-run): " << thermalPlantTotalCountMustRun;
logs.info() << " short-term storages: " << shortTermStorageCount;
logs.info() << " binding constraints: " << study.bindingConstraints.activeContraints().size();
+ logs.info() << " maintenance groups: " << study.maintenanceGroups.activeMaintenanceGroups().size();
logs.info() << " geographic trimming:" << (gd.geographicTrimming ? "true" : "false");
logs.info() << " memory : " << ((study.memoryUsage()) / 1024 / 1024) << "Mo";
logs.info();
diff --git a/src/libs/antares/study/save.cpp b/src/libs/antares/study/save.cpp
index f8dea51ebd..52f6539d92 100644
--- a/src/libs/antares/study/save.cpp
+++ b/src/libs/antares/study/save.cpp
@@ -225,6 +225,10 @@ bool Study::saveToFolder(const AnyString& newfolder)
buffer.clear() << folder << SEP << "input" << SEP << "bindingconstraints";
ret = bindingConstraints.saveToFolder(buffer) and ret;
+ // Maintenance Group-s
+ buffer.clear() << folder << SEP << "input" << SEP << "maintenanceplanning";
+ ret = maintenanceGroups.saveToFolder(buffer) and ret;
+
// Correlation matrices
logs.info() << "Exporting the correlation matrices";
diff --git a/src/libs/antares/study/scenario-builder/ThermalTSNumberData.cpp b/src/libs/antares/study/scenario-builder/ThermalTSNumberData.cpp
index b67ede4039..7de24c0f0e 100644
--- a/src/libs/antares/study/scenario-builder/ThermalTSNumberData.cpp
+++ b/src/libs/antares/study/scenario-builder/ThermalTSNumberData.cpp
@@ -83,8 +83,6 @@ bool thermalTSNumberData::apply(Study& study)
// WARNING: We may have some thermal clusters with the `mustrun` option
auto clusterCount = (uint)area.thermal.clusterCount();
- const uint tsGenCountThermal = get_tsGenCount(study);
-
for (uint clusterIndex = 0; clusterIndex != clusterCount; ++clusterIndex)
{
auto& cluster = *(area.thermal.clusters[clusterIndex]);
@@ -92,9 +90,13 @@ bool thermalTSNumberData::apply(Study& study)
assert(clusterIndex < pTSNumberRules.width);
const auto& col = pTSNumberRules[clusterIndex];
+ uint tsGenCount = cluster.tsGenBehavior == LocalTSGenerationBehavior::forceNoGen ?
+ cluster.series.timeSeries.width : get_tsGenCount(study);
+
logprefix.clear() << "Thermal: area '" << area.name << "', cluster: '" << cluster.name()
<< "': ";
- ret = ApplyToMatrix(errors, logprefix, cluster.series, col, tsGenCountThermal) && ret;
+ ret = ApplyToMatrix(errors, logprefix, cluster.series, col, tsGenCount) && ret;
+
}
return ret;
}
diff --git a/src/libs/antares/study/study.cpp b/src/libs/antares/study/study.cpp
index cd8e8b28fc..3d4cfa8c8c 100644
--- a/src/libs/antares/study/study.cpp
+++ b/src/libs/antares/study/study.cpp
@@ -125,6 +125,9 @@ void Study::clear()
bindingConstraintsGroups.clear();
areas.clear();
+ // maintenance Group-s
+ maintenanceGroups.clear();
+
// no folder
ClearAndShrink(header.caption);
ClearAndShrink(header.author);
@@ -149,6 +152,8 @@ void Study::createAsNew()
// ... At study creation, renewable cluster is the default mode for RES (Renewable Energy
// Source)
parameters.renewableGeneration.rgModelling = Antares::Data::rgClusters;
+ // default mode for maintenancePlanning is Randomized
+ parameters.maintenancePlanning.clear();
parameters.yearsFilter = std::vector(1, true);
@@ -161,6 +166,9 @@ void Study::createAsNew()
// Binding constraints
bindingConstraints.clear();
+ // maintenance Group-s
+ maintenanceGroups.clear();
+
// Areas
areas.clear();
@@ -206,6 +214,8 @@ uint64_t Study::memoryUsage() const
+ areas.memoryUsage()
// Binding constraints
+ bindingConstraints.memoryUsage()
+ // maintenance Group-s
+ + maintenanceGroups.memoryUsage()
// Correlations matrices
+ preproLoadCorrelation.memoryUsage() + preproSolarCorrelation.memoryUsage()
+ preproHydroCorrelation.memoryUsage() + preproWindCorrelation.memoryUsage()
@@ -845,6 +855,8 @@ bool Study::areaDelete(Area* area)
// Remove a single area
// Remove all binding constraints attached to the area
bindingConstraints.remove(area);
+ // Remove all Maintenance Group-s attached to the area
+ maintenanceGroups.remove(area);
// Delete the area from the list
areas.remove(area->id);
@@ -896,6 +908,8 @@ void Study::areaDelete(Area::Vector& arealist)
// Remove all binding constraints attached to the area
bindingConstraints.remove(*i);
+ // Remove all Maintenance Group-s attached to the area
+ maintenanceGroups.remove(*i);
// Delete the area from the list
areas.remove(area.id);
}
@@ -1133,6 +1147,8 @@ void Study::destroyAllThermalTSGeneratorData()
});
}
+// TODO CR27: see if we need this one for mant groups - only used in UI - so II phase
+
void Study::ensureDataAreLoadedForAllBindingConstraints()
{
for(const auto& constraint: bindingConstraints)
@@ -1499,6 +1515,30 @@ bool Study::checkForFilenameLimits(bool output, const String& chfolder) const
}
}
}
+
+ if (not maintenanceGroups.empty())
+ {
+ auto end = maintenanceGroups.end();
+ for (auto i = maintenanceGroups.begin(); i != end; ++i)
+ {
+ // The current constraint
+ auto& mntGroup = *(*i);
+
+ filename.clear();
+ filename << studyfolder << "input" << SEP << "maintenanceplanning" << SEP;
+ filename << mntGroup.id() << ".ini";
+
+ if (filename.size() >= limit)
+ {
+ logs.error()
+ << "OS Maximum path length limitation obtained with the maintenance group '"
+ << mntGroup.name() << "' (got " << filename.size() << " characters)";
+ logs.error() << "You may experience problems while accessing to this file: "
+ << filename;
+ return false;
+ }
+ }
+ }
}
return true;
}
diff --git a/src/libs/antares/study/study.h b/src/libs/antares/study/study.h
index e377b11ebd..51552229e7 100644
--- a/src/libs/antares/study/study.h
+++ b/src/libs/antares/study/study.h
@@ -53,6 +53,9 @@
#include "antares/study/binding_constraint/BindingConstraintsRepository.h"
#include "antares/study/binding_constraint/BindingConstraintGroupRepository.h"
+#include "maintenance_planning/MaintenanceGroup.h"
+#include "antares/study/maintenance_planning/MaintenanceGroupRepository.h"
+
#include
namespace Antares::Data
@@ -583,6 +586,12 @@ class Study: public Yuni::NonCopyable, public LayerData
BindingConstraintGroupRepository bindingConstraintsGroups;
//@}
+ //! \name Maintenance Group-s
+ //@{
+ //! Maintenance Group-s
+ MaintenanceGroupRepository maintenanceGroups;
+ //@}
+
//! \name Correlation matrices used by the prepro
//@{
//! Correlation matrix for the load time series generated by the prepro
@@ -682,6 +691,8 @@ class Study: public Yuni::NonCopyable, public LayerData
bool internalLoadCorrelationMatrices(const StudyLoadOptions& options);
//! Load all binding constraints
virtual bool internalLoadBindingConstraints(const StudyLoadOptions& options);
+ //! Load all maintenance groups
+ virtual bool internalLoadMaintenanceGroup(const StudyLoadOptions& options);
//! Load all set of areas and links
bool internalLoadSets();
//@}
diff --git a/src/libs/antares/study/ui-runtimeinfos.cpp b/src/libs/antares/study/ui-runtimeinfos.cpp
index 0ba362f828..8d0f50b97f 100644
--- a/src/libs/antares/study/ui-runtimeinfos.cpp
+++ b/src/libs/antares/study/ui-runtimeinfos.cpp
@@ -108,6 +108,9 @@ void UIRuntimeInfo::reload()
}
}
+//TODO CR27: make reload maintenanceGroups? it is used mostly in UI, but also have couple of uses in solver - leave it for II phase
+
+
void UIRuntimeInfo::reloadBindingConstraints()
{
orderedConstraint.clear();
diff --git a/src/libs/antares/writer/antares/writer/i_writer.h b/src/libs/antares/writer/antares/writer/i_writer.h
index c288fc68cf..c7589645ae 100644
--- a/src/libs/antares/writer/antares/writer/i_writer.h
+++ b/src/libs/antares/writer/antares/writer/i_writer.h
@@ -18,7 +18,7 @@ class IOError : public std::runtime_error
using std::runtime_error::runtime_error;
};
-class IResultWriter
+class IResultWriter // TMP.INFO CR27: this is your TS generator writer
{
public:
using Ptr = std::shared_ptr;
diff --git a/src/solver/application/application.cpp b/src/solver/application/application.cpp
index e61c278b4c..26cf2e52e7 100644
--- a/src/solver/application/application.cpp
+++ b/src/solver/application/application.cpp
@@ -159,6 +159,9 @@ void Application::prepare(int argc, char* argv[])
checkFuelCostColumnNumber(pStudy->areas);
checkCO2CostColumnNumber(pStudy->areas);
+ checkMaintenancePlanningSettings(pParameters);
+ checkMaintenancePlanningTsNum(pParameters);
+
// Start the progress meter
pStudy->initializeProgressMeter(pSettings.tsGeneratorsOnly);
if (pSettings.noOutput)
diff --git a/src/solver/optimisation/CMakeLists.txt b/src/solver/optimisation/CMakeLists.txt
index 4ca7205048..f552b874f2 100644
--- a/src/solver/optimisation/CMakeLists.txt
+++ b/src/solver/optimisation/CMakeLists.txt
@@ -50,6 +50,8 @@ set(RTESOLVER_OPT
adequacy_patch_csr/hourly_csr_problem.h
adequacy_patch_csr/adq_patch_post_process_list.h
adequacy_patch_csr/adq_patch_post_process_list.cpp
+ adequacy_patch_csr/post_processing.cpp
+ adequacy_patch_csr/post_processing.h
adequacy_patch_local_matching/adq_patch_local_matching.h
adequacy_patch_local_matching/adq_patch_local_matching.cpp
adequacy_patch_csr/adq_patch_curtailment_sharing.h
diff --git a/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp b/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp
index 9bf0665d4b..838288b4f8 100644
--- a/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp
+++ b/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp
@@ -1,57 +1,35 @@
#include "adq_patch_post_process_list.h"
#include "../post_process_commands.h"
-
namespace Antares::Solver::Simulation
{
-
AdqPatchPostProcessList::AdqPatchPostProcessList(const AdqPatchParams& adqPatchParams,
PROBLEME_HEBDO* problemeHebdo,
uint thread_number,
AreaList& areas,
SheddingPolicy sheddingPolicy,
SimplexOptimization splxOptimization,
- Calendar& calendar)
- : interfacePostProcessList(problemeHebdo, thread_number)
+ Calendar& calendar) :
+ interfacePostProcessList(problemeHebdo, thread_number)
{
- post_process_list.push_back(std::make_unique(
- problemeHebdo_,
- thread_number_,
- areas));
+ post_process_list.push_back(
+ std::make_unique(problemeHebdo_, thread_number_, areas));
// Here a post process particular to adq patch
- post_process_list.push_back(std::make_unique(
- adqPatchParams,
- problemeHebdo_,
- areas,
- thread_number_));
- post_process_list.push_back(std::make_unique(
- problemeHebdo_,
- areas,
- false,
- false));
+ post_process_list.push_back(
+ std::make_unique(problemeHebdo_, areas, false, false));
post_process_list.push_back(std::make_unique(
- problemeHebdo_,
- areas,
- sheddingPolicy,
- splxOptimization,
- thread_number));
+ problemeHebdo_, areas, sheddingPolicy, splxOptimization, thread_number));
+ post_process_list.push_back(std::make_unique(
+ adqPatchParams, problemeHebdo_, areas, thread_number_));
// Here a post process particular to adq patch
post_process_list.push_back(std::make_unique(
- problemeHebdo_,
- areas,
- thread_number));
- post_process_list.push_back(std::make_unique(
- problemeHebdo_,
- areas,
- true,
- false));
- post_process_list.push_back(std::make_unique(
- problemeHebdo_,
- areas,
- calendar));
- post_process_list.push_back(std::make_unique(
- problemeHebdo_,
- areas));
+ adqPatchParams, problemeHebdo_, areas, thread_number));
+ post_process_list.push_back(
+ std::make_unique(problemeHebdo_, areas, true, false));
+ post_process_list.push_back(
+ std::make_unique(problemeHebdo_, areas, calendar));
+ post_process_list.push_back(
+ std::make_unique(problemeHebdo_, areas));
}
-} // namespace Antares::Solver::Simulation
\ No newline at end of file
+} // namespace Antares::Solver::Simulation
diff --git a/src/solver/optimisation/adequacy_patch_csr/post_processing.cpp b/src/solver/optimisation/adequacy_patch_csr/post_processing.cpp
new file mode 100644
index 0000000000..725ec9c364
--- /dev/null
+++ b/src/solver/optimisation/adequacy_patch_csr/post_processing.cpp
@@ -0,0 +1,62 @@
+/*
+** Copyright 2007-2024, RTE (https://www.rte-france.com)
+** See AUTHORS.txt
+** SPDX-License-Identifier: MPL-2.0
+** This file is part of Antares-Simulator,
+** Adequacy and Performance assessment for interconnected energy networks.
+**
+** Antares_Simulator is free software: you can redistribute it and/or modify
+** it under the terms of the Mozilla Public Licence 2.0 as published by
+** the Mozilla Foundation, either version 2 of the License, or
+** (at your option) any later version.
+**
+** Antares_Simulator is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** Mozilla Public Licence 2.0 for more details.
+**
+** You should have received a copy of the Mozilla Public Licence 2.0
+** along with Antares_Simulator. If not, see .
+*/
+#include "post_processing.h"
+
+#include
+
+namespace Antares::Data::AdequacyPatch
+{
+double recomputeDTG_MRG(bool triggered, double dtgMrg, double ens)
+{
+ if (triggered)
+ {
+ return std::max(0.0, dtgMrg - ens);
+ }
+ else
+ {
+ return dtgMrg;
+ }
+}
+
+double recomputeENS_MRG(bool triggered, double dtgMrg, double ens)
+{
+ if (triggered)
+ {
+ return std::max(0.0, ens - dtgMrg);
+ }
+ else
+ {
+ return ens;
+ }
+}
+
+double recomputeMRGPrice(double ensCsr, double originalCost, double unsuppliedEnergyCost)
+{
+ if (ensCsr > 0.5)
+ {
+ return -unsuppliedEnergyCost;
+ }
+ else
+ {
+ return originalCost;
+ }
+}
+} // namespace Antares::Data::AdequacyPatch
diff --git a/src/solver/optimisation/adequacy_patch_csr/post_processing.h b/src/solver/optimisation/adequacy_patch_csr/post_processing.h
new file mode 100644
index 0000000000..3ae5ee2d91
--- /dev/null
+++ b/src/solver/optimisation/adequacy_patch_csr/post_processing.h
@@ -0,0 +1,29 @@
+/*
+** Copyright 2007-2024, RTE (https://www.rte-france.com)
+** See AUTHORS.txt
+** SPDX-License-Identifier: MPL-2.0
+** This file is part of Antares-Simulator,
+** Adequacy and Performance assessment for interconnected energy networks.
+**
+** Antares_Simulator is free software: you can redistribute it and/or modify
+** it under the terms of the Mozilla Public Licence 2.0 as published by
+** the Mozilla Foundation, either version 2 of the License, or
+** (at your option) any later version.
+**
+** Antares_Simulator is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** Mozilla Public Licence 2.0 for more details.
+**
+** You should have received a copy of the Mozilla Public Licence 2.0
+** along with Antares_Simulator. If not, see .
+*/
+
+#pragma once
+
+namespace Antares::Data::AdequacyPatch
+{
+double recomputeDTG_MRG(bool triggered, double dtgMrg, double ens);
+double recomputeENS_MRG(bool triggered, double dtgMrg, double ens);
+double recomputeMRGPrice(double ensCsr, double originalCost, double unsuppliedEnergyCost);
+} // namespace Antares::Data::AdequacyPatch
diff --git a/src/solver/optimisation/adequacy_patch_csr/set_variable_boundaries.cpp b/src/solver/optimisation/adequacy_patch_csr/set_variable_boundaries.cpp
index 0bb5d91925..7cb72b7b0d 100644
--- a/src/solver/optimisation/adequacy_patch_csr/set_variable_boundaries.cpp
+++ b/src/solver/optimisation/adequacy_patch_csr/set_variable_boundaries.cpp
@@ -93,8 +93,7 @@ void HourlyCSRProblem::setBoundsOnSpilledEnergy()
.ValeursHorairesDeDefaillanceNegative[triggeredHour];
double* AdresseDuResultat = &(problemeHebdo_->ResultatsHoraires[area]
- .ValeursHorairesSpilledEnergyAfterCSR[triggeredHour]);
-
+ .ValeursHorairesDeDefaillanceNegative[triggeredHour]);
problemeAResoudre_.AdresseOuPlacerLaValeurDesVariablesOptimisees[var]
= AdresseDuResultat;
diff --git a/src/solver/optimisation/opt_optimisation_lineaire.cpp b/src/solver/optimisation/opt_optimisation_lineaire.cpp
index 0a80822a17..d6fd877dda 100644
--- a/src/solver/optimisation/opt_optimisation_lineaire.cpp
+++ b/src/solver/optimisation/opt_optimisation_lineaire.cpp
@@ -163,7 +163,10 @@ bool OPT_OptimisationLineaire(const OptimizationOptions& options,
ConstraintBuilder builder(builder_data);
LinearProblemMatrix linearProblemMatrix(problemeHebdo, builder);
linearProblemMatrix.Run();
- OPT_ExportStructures(problemeHebdo, writer);
+ if (problemeHebdo->ExportStructure && problemeHebdo->firstWeekOfSimulation)
+ {
+ OPT_ExportStructures(problemeHebdo, writer);
+ }
bool ret = runWeeklyOptimization(
options, problemeHebdo, adqPatchParams, writer, PREMIERE_OPTIMISATION);
diff --git a/src/solver/optimisation/post_process_commands.cpp b/src/solver/optimisation/post_process_commands.cpp
index d1a367e03c..31e70626d3 100644
--- a/src/solver/optimisation/post_process_commands.cpp
+++ b/src/solver/optimisation/post_process_commands.cpp
@@ -1,8 +1,29 @@
+/*
+** Copyright 2007-2024, RTE (https://www.rte-france.com)
+** See AUTHORS.txt
+** SPDX-License-Identifier: MPL-2.0
+** This file is part of Antares-Simulator,
+** Adequacy and Performance assessment for interconnected energy networks.
+**
+** Antares_Simulator is free software: you can redistribute it and/or modify
+** it under the terms of the Mozilla Public Licence 2.0 as published by
+** the Mozilla Foundation, either version 2 of the License, or
+** (at your option) any later version.
+**
+** Antares_Simulator is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** Mozilla Public Licence 2.0 for more details.
+**
+** You should have received a copy of the Mozilla Public Licence 2.0
+** along with Antares_Simulator. If not, see .
+*/
#include "post_process_commands.h"
#include "../simulation/common-eco-adq.h"
#include "../simulation/adequacy_patch_runtime_data.h"
#include "adequacy_patch_local_matching/adequacy_patch_weekly_optimization.h"
+#include "adequacy_patch_csr/post_processing.h"
#include "adequacy_patch_csr/adq_patch_curtailment_sharing.h"
namespace Antares::Solver::Simulation
@@ -97,10 +118,14 @@ void RemixHydroPostProcessCmd::execute(const optRuntimeData& opt_runtime_data)
using namespace Antares::Data::AdequacyPatch;
DTGmarginForAdqPatchPostProcessCmd::DTGmarginForAdqPatchPostProcessCmd(
+ const AdqPatchParams& adqPatchParams,
PROBLEME_HEBDO* problemeHebdo,
AreaList& areas,
unsigned int thread_number) :
- basePostProcessCommand(problemeHebdo), area_list_(areas), thread_number_(thread_number)
+ basePostProcessCommand(problemeHebdo),
+ adqPatchParams_(adqPatchParams),
+ area_list_(areas),
+ thread_number_(thread_number)
{
}
@@ -117,25 +142,23 @@ void DTGmarginForAdqPatchPostProcessCmd::execute(const optRuntimeData&)
for (uint hour = 0; hour < nbHoursInWeek; hour++)
{
- // define access to the required variables
+ auto& hourlyResults = problemeHebdo_->ResultatsHoraires[Area];
const auto& scratchpad = area_list_[Area]->scratchpad[thread_number_];
- double dtgMrg = scratchpad.dispatchableGenerationMargin[hour];
+ const double dtgMrg = scratchpad.dispatchableGenerationMargin[hour];
+ const double ens = hourlyResults.ValeursHorairesDeDefaillancePositive[hour];
+ const bool triggered = problemeHebdo_->adequacyPatchRuntimeData
+ ->wasCSRTriggeredAtAreaHour(Area, hour);
+ hourlyResults.ValeursHorairesDtgMrgCsr[hour] = recomputeDTG_MRG(triggered, dtgMrg, ens);
+ hourlyResults.ValeursHorairesDeDefaillancePositiveCSR[hour] = recomputeENS_MRG(
+ triggered,
+ dtgMrg,
+ ens);
- auto& hourlyResults = problemeHebdo_->ResultatsHoraires[Area];
- double& dtgMrgCsr = hourlyResults.ValeursHorairesDtgMrgCsr[hour];
- double& ens = hourlyResults.ValeursHorairesDeDefaillancePositive[hour];
- double& mrgCost = hourlyResults.CoutsMarginauxHoraires[hour];
- // calculate DTG MRG CSR and adjust ENS if neccessary
- if (problemeHebdo_->adequacyPatchRuntimeData->wasCSRTriggeredAtAreaHour(Area, hour))
- {
- dtgMrgCsr = std::max(0.0, dtgMrg - ens);
- ens = std::max(0.0, ens - dtgMrg);
- // set MRG PRICE to value of unsupplied energy cost, if LOLD=1.0 (ENS>0.5)
- if (ens > 0.5)
- mrgCost = -area_list_[Area]->thermal.unsuppliedEnergyCost;
- }
- else
- dtgMrgCsr = dtgMrg;
+ const double unsuppliedEnergyCost = area_list_[Area]->thermal.unsuppliedEnergyCost;
+ hourlyResults.CoutsMarginauxHoraires[hour] = recomputeMRGPrice(
+ hourlyResults.ValeursHorairesDtgMrgCsr[hour],
+ hourlyResults.CoutsMarginauxHoraires[hour],
+ unsuppliedEnergyCost);
}
}
}
@@ -177,14 +200,15 @@ void HydroLevelsFinalUpdatePostProcessCmd::execute(const optRuntimeData&)
// --------------------------------------
// Curtailment sharing for adq patch
// --------------------------------------
-CurtailmentSharingPostProcessCmd::CurtailmentSharingPostProcessCmd(const AdqPatchParams& adqPatchParams,
- PROBLEME_HEBDO* problemeHebdo,
- AreaList& areas,
- unsigned int thread_number) :
- basePostProcessCommand(problemeHebdo),
- area_list_(areas),
- adqPatchParams_(adqPatchParams),
- thread_number_(thread_number)
+CurtailmentSharingPostProcessCmd::CurtailmentSharingPostProcessCmd(
+ const AdqPatchParams& adqPatchParams,
+ PROBLEME_HEBDO* problemeHebdo,
+ AreaList& areas,
+ unsigned int thread_number) :
+ basePostProcessCommand(problemeHebdo),
+ area_list_(areas),
+ adqPatchParams_(adqPatchParams),
+ thread_number_(thread_number)
{
}
@@ -217,11 +241,11 @@ double CurtailmentSharingPostProcessCmd::calculateDensNewAndTotalLmrViolation()
{
for (uint hour = 0; hour < nbHoursInWeek; hour++)
{
- const auto [netPositionInit, densNew, totalNodeBalance]
- = calculateAreaFlowBalance(problemeHebdo_,
- adqPatchParams_.localMatching.setToZeroOutsideInsideLinks,
- Area,
- hour);
+ const auto [netPositionInit, densNew, totalNodeBalance] = calculateAreaFlowBalance(
+ problemeHebdo_,
+ adqPatchParams_.localMatching.setToZeroOutsideInsideLinks,
+ Area,
+ hour);
// adjust densNew according to the new specification/request by ELIA
/* DENS_new (node A) = max [ 0; ENS_init (node A) + net_position_init (node A)
+ ? flows (node 1 -> node A) - DTG.MRG(node A)] */
@@ -229,18 +253,14 @@ double CurtailmentSharingPostProcessCmd::calculateDensNewAndTotalLmrViolation()
double dtgMrg = scratchpad.dispatchableGenerationMargin[hour];
// write down densNew values for all the hours
problemeHebdo_->ResultatsHoraires[Area].ValeursHorairesDENS[hour]
- = std::max(0.0, densNew - dtgMrg);
- ;
- // copy spilled Energy values into spilled Energy values after CSR
- problemeHebdo_->ResultatsHoraires[Area].ValeursHorairesSpilledEnergyAfterCSR[hour]
- = problemeHebdo_->ResultatsHoraires[Area]
- .ValeursHorairesDeDefaillanceNegative[hour];
+ = std::max(0.0, densNew);
// check LMR violations
totalLmrViolation += LmrViolationAreaHour(
- problemeHebdo_,
- totalNodeBalance,
- adqPatchParams_.curtailmentSharing.thresholdDisplayViolations,
- Area, hour);
+ problemeHebdo_,
+ totalNodeBalance,
+ adqPatchParams_.curtailmentSharing.thresholdDisplayViolations,
+ Area,
+ hour);
}
}
}
diff --git a/src/solver/optimisation/post_process_commands.h b/src/solver/optimisation/post_process_commands.h
index d04daf02ce..4a58237bb2 100644
--- a/src/solver/optimisation/post_process_commands.h
+++ b/src/solver/optimisation/post_process_commands.h
@@ -51,14 +51,18 @@ class RemixHydroPostProcessCmd : public basePostProcessCommand
class DTGmarginForAdqPatchPostProcessCmd : public basePostProcessCommand
{
+ using AdqPatchParams = Antares::Data::AdequacyPatch::AdqPatchParams;
+
public:
- DTGmarginForAdqPatchPostProcessCmd(PROBLEME_HEBDO* problemeHebdo,
+ DTGmarginForAdqPatchPostProcessCmd(const AdqPatchParams& adqPatchParams,
+ PROBLEME_HEBDO* problemeHebdo,
AreaList& areas,
unsigned int thread_number);
void execute(const optRuntimeData& opt_runtime_data) override;
private:
+ const AdqPatchParams& adqPatchParams_;
const AreaList& area_list_;
unsigned int thread_number_ = 0;
};
@@ -91,6 +95,7 @@ class HydroLevelsFinalUpdatePostProcessCmd : public basePostProcessCommand
class CurtailmentSharingPostProcessCmd : public basePostProcessCommand
{
using AdqPatchParams = Antares::Data::AdequacyPatch::AdqPatchParams;
+
public:
CurtailmentSharingPostProcessCmd(const AdqPatchParams& adqPatchParams,
PROBLEME_HEBDO* problemeHebdo,
diff --git a/src/solver/simulation/sim_alloc_probleme_hebdo.cpp b/src/solver/simulation/sim_alloc_probleme_hebdo.cpp
index 80c5ad5edb..5ee3b74e92 100644
--- a/src/solver/simulation/sim_alloc_probleme_hebdo.cpp
+++ b/src/solver/simulation/sim_alloc_probleme_hebdo.cpp
@@ -415,12 +415,12 @@ void SIM_AllocateAreas(PROBLEME_HEBDO& problem,
problem.ResultatsHoraires[k].ValeursHorairesDeDefaillancePositive
.assign(NombreDePasDeTemps, 0.);
+ problem.ResultatsHoraires[k].ValeursHorairesDeDefaillancePositiveCSR
+ .assign(NombreDePasDeTemps, 0.);
problem.ResultatsHoraires[k].ValeursHorairesDENS
.assign(NombreDePasDeTemps, 0.); // adq patch
problem.ResultatsHoraires[k].ValeursHorairesLmrViolations
.assign(NombreDePasDeTemps, 0); // adq patch
- problem.ResultatsHoraires[k].ValeursHorairesSpilledEnergyAfterCSR
- .assign(NombreDePasDeTemps, 0.); // adq patch
problem.ResultatsHoraires[k].ValeursHorairesDtgMrgCsr
.assign(NombreDePasDeTemps, 0.); // adq patch
problem.ResultatsHoraires[k].ValeursHorairesDeDefaillancePositiveUp
diff --git a/src/solver/simulation/sim_calcul_economique.cpp b/src/solver/simulation/sim_calcul_economique.cpp
index 23315d4ef4..929c7d051d 100644
--- a/src/solver/simulation/sim_calcul_economique.cpp
+++ b/src/solver/simulation/sim_calcul_economique.cpp
@@ -788,7 +788,9 @@ void SIM_RenseignementProblemeHebdo(const Study& study,
if (problem.CaracteristiquesHydrauliques[k].NiveauInitialReservoir
< weekTarget_tmp)
+ {
marginGen = problem.CaracteristiquesHydrauliques[k].NiveauInitialReservoir;
+ }
}
if (not problem.CaracteristiquesHydrauliques[k].TurbinageEntreBornes)
diff --git a/src/solver/simulation/sim_structure_probleme_economique.h b/src/solver/simulation/sim_structure_probleme_economique.h
index a357f4f34c..7feea19585 100644
--- a/src/solver/simulation/sim_structure_probleme_economique.h
+++ b/src/solver/simulation/sim_structure_probleme_economique.h
@@ -429,9 +429,9 @@ struct PRODUCTION_THERMIQUE_OPTIMALE
struct RESULTATS_HORAIRES
{
std::vector ValeursHorairesDeDefaillancePositive;
- std::vector ValeursHorairesDENS; // adq patch domestic unsupplied energy
- std::vector ValeursHorairesLmrViolations; // adq patch lmr violations
- std::vector ValeursHorairesSpilledEnergyAfterCSR; // adq patch spillage after CSR
+ std::vector ValeursHorairesDeDefaillancePositiveCSR;
+ std::vector ValeursHorairesDENS; // adq patch domestic unsupplied energy
+ std::vector ValeursHorairesLmrViolations; // adq patch lmr violations
std::vector ValeursHorairesDtgMrgCsr; // adq patch DTG MRG after CSR
std::vector ValeursHorairesDeDefaillancePositiveUp;
std::vector ValeursHorairesDeDefaillancePositiveDown;
diff --git a/src/solver/simulation/solver.hxx b/src/solver/simulation/solver.hxx
index 6cb1425246..e8470e4f2f 100644
--- a/src/solver/simulation/solver.hxx
+++ b/src/solver/simulation/solver.hxx
@@ -40,6 +40,7 @@
#include "../ts-generator/generator.h"
#include "opt_time_writer.h"
#include "../hydro/management.h" // Added for use of randomReservoirLevel(...)
+#include "../ts-generator/optimized-thermal-generator/support/pre-scenario-builder.h"
#include
#include
@@ -321,6 +322,9 @@ void ISimulation::run()
// in general data of the study.
logs.info() << " Only the preprocessors are enabled.";
+ // we want to know TS numbers if we want to use Maintenance planning
+ ApplyScenarioBuilderDueToMaintenancePlanning(study);
+
regenerateTimeSeries(0);
// Destroy the TS Generators if any
@@ -366,7 +370,7 @@ void ISimulation::run()
uint finalYear = 1 + study.runtime->rangeLimits.year[Data::rangeEnd];
{
Benchmarking::Timer timer;
- loopThroughYears(0, finalYear, state);
+ loopThroughYears(0, finalYear, state); // TMP.INFO CR27: inside this method is TS-generator
timer.stop();
pDurationCollector.addDuration("mc_years", timer.get_duration());
}
@@ -445,7 +449,7 @@ void ISimulation::writeResults(bool synthesis, uint year, uint numSpace)
}
template
-void ISimulation::regenerateTimeSeries(uint year)
+void ISimulation::regenerateTimeSeries(uint year) // TMP.INFO CR27: Generate TS-s. Input year - is MC year.
{
// A preprocessor can be launched for several reasons:
// * The option "Preprocessor" is checked in the interface _and_ year == 0
@@ -485,10 +489,10 @@ void ISimulation::regenerateTimeSeries(uint year)
pDurationCollector.addDuration("tsgen_hydro", timer.get_duration());
}
// Thermal
- const bool refreshTSonCurrentYear = (year % pData.refreshIntervalThermal == 0);
+ const bool refreshTSonCurrentYear = (year % pData.refreshIntervalThermal == 0); // TMP.INFO CR27: we are going to generate new set of TS-s only if MC-year is dividable by "refresh span"
{
Benchmarking::Timer timer;
- GenerateThermalTimeSeries(
+ GenerateThermalTimeSeries( // TMP.INFO CR27: call the thermal ts- gen here!
study, year, pData.haveToRefreshTSThermal, refreshTSonCurrentYear, pResultWriter);
timer.stop();
pDurationCollector.addDuration("tsgen_thermal", timer.get_duration());
@@ -956,13 +960,13 @@ void ISimulation::loopThroughYears(uint firstYear,
// Loop over sets of parallel years
std::vector::iterator set_it;
- for (set_it = setsOfParallelYears.begin(); set_it != setsOfParallelYears.end(); ++set_it)
+ for (set_it = setsOfParallelYears.begin(); set_it != setsOfParallelYears.end(); ++set_it) // TMP.INFO CR27: we are in parallel work here already
{
// 1 - We may want to regenerate the time-series this year.
// This is the case when the preprocessors are enabled from the
// interface and/or the refresh is enabled.
if (set_it->regenerateTS)
- regenerateTimeSeries(set_it->yearForTSgeneration);
+ regenerateTimeSeries(set_it->yearForTSgeneration); // TMP.INFO CR27: The TS-gen is going to be called ONLY for those years where at least one type [load,wind,hydro...] needs re-generation!!
computeRandomNumbers(randomForParallelYears, set_it->yearsIndices, set_it->isYearPerformed,
randomHydroGenerator);
diff --git a/src/solver/ts-generator/CMakeLists.txt b/src/solver/ts-generator/CMakeLists.txt
index 7b8f80ae2c..aad015a7f7 100644
--- a/src/solver/ts-generator/CMakeLists.txt
+++ b/src/solver/ts-generator/CMakeLists.txt
@@ -1,5 +1,44 @@
project(ts-generator)
+#
+# Randomized-thermal-ts-generator
+#
+set(SRC_RANDOMIZEDGENERATORS
+ randomized-thermal-generator/RandomizedGenerator.h
+ randomized-thermal-generator/RandomizedGenerator.cpp
+)
+source_group("ts-generator\\Randomized-thermal-generator" FILES ${SRC_RANDOMIZEDGENERATORS})
+
+#
+# Optimized-thermal-ts-generator
+#
+set(SRC_OPTIMIZEDGENERATORS
+ optimized-thermal-generator/support/SupportStructures.h
+ optimized-thermal-generator/support/SupportStructures.cpp
+ optimized-thermal-generator/support/SupportFunctions.h
+ optimized-thermal-generator/support/SupportFunctions.cpp
+ optimized-thermal-generator/support/pre-scenario-builder.h
+ optimized-thermal-generator/support/pre-scenario-builder.cpp
+ optimized-thermal-generator/main/OptimizedGenerator.h
+ optimized-thermal-generator/main/OptimizedGenerator.cpp
+ optimized-thermal-generator/parameters/OptimizationParameters.h
+ optimized-thermal-generator/parameters/CalculateParameters.cpp
+ optimized-thermal-generator/parameters/CalculateParametersPerCluster.cpp
+ optimized-thermal-generator/parameters/Getters.cpp
+ optimized-thermal-generator/optimization/CreateVariables.cpp
+ optimized-thermal-generator/optimization/SetVariableBounds.cpp
+ optimized-thermal-generator/optimization/SetProblemConstraints.cpp
+ optimized-thermal-generator/optimization/SetProblemCost.cpp
+ optimized-thermal-generator/optimization/ResetProblem.cpp
+ optimized-thermal-generator/optimization/SolveProbem.cpp
+ optimized-thermal-generator/optimization/PrintProblemVarRes.cpp
+ optimized-thermal-generator/postoptimization/PostTimeStepOptimization.cpp
+ optimized-thermal-generator/postoptimization/PostScenarioOptimization.cpp
+ optimized-thermal-generator/postoptimization/WriteResults.cpp
+
+)
+source_group("ts-generator\\Optimized-thermal-generator" FILES ${SRC_OPTIMIZEDGENERATORS})
+
#
# Time-Series Generators
#
@@ -39,6 +78,8 @@ source_group("ts-generator\\XCast" FILES ${SRC_XCAST})
# --- Library VARIABLES ---
#
add_library(antares-solver-ts-generator
+ ${SRC_RANDOMIZEDGENERATORS}
+ ${SRC_OPTIMIZEDGENERATORS}
${SRC_GENERATORS}
${SRC_XCAST})
@@ -48,5 +89,6 @@ target_link_libraries(antares-solver-ts-generator
array
benchmarking
Antares::study
+ utils #ortools-utils, not Antares::utils
Antares::misc
)
diff --git a/src/solver/ts-generator/generator.h b/src/solver/ts-generator/generator.h
index ebdd8e3bb8..056be959c3 100644
--- a/src/solver/ts-generator/generator.h
+++ b/src/solver/ts-generator/generator.h
@@ -44,14 +44,25 @@ void ResizeGeneratedTimeSeries(Data::AreaList& areas, Data::Parameters& params);
** \brief Regenerate the time-series
*/
template
-bool GenerateTimeSeries(Data::Study& study, uint year, IResultWriter& writer);
+bool GenerateTimeSeries(Data::Study& study, uint year, IResultWriter& writer); // TMP.INFO CR27: one TS gen for the rest
-bool GenerateThermalTimeSeries(Data::Study& study,
+bool GenerateThermalTimeSeries(Data::Study& study, // TMP.INFO CR27: One TS gen for thermal
uint year,
bool globalThermalTSgeneration,
bool refresh,
IResultWriter& writer);
+bool GenerateRandomizedThermalTimeSeries(Data::Study& study,
+ uint year,
+ bool globalThermalTSgeneration,
+ bool refresh,
+ IResultWriter& writer);
+
+bool GenerateOptimizedThermalTimeSeries(Data::Study& study,
+ uint year,
+ bool globalThermalTSgeneration,
+ IResultWriter& writer);
+
/*!
** \brief Destroy all TS Generators
*/
diff --git a/src/solver/ts-generator/optimized-thermal-generator/main/OptimizedGenerator.cpp b/src/solver/ts-generator/optimized-thermal-generator/main/OptimizedGenerator.cpp
new file mode 100644
index 0000000000..16f8d5afcb
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/main/OptimizedGenerator.cpp
@@ -0,0 +1,72 @@
+//
+// Created by milos on 10/11/23.
+//
+
+#include "OptimizedGenerator.h"
+
+#include
+#include
+
+namespace Antares::TSGenerator
+{
+// optimization problem - methods
+void OptimizedThermalGenerator::GenerateOptimizedThermalTimeSeries()
+{
+ allocateWhereToWriteTs();
+ par.setMaintenanceGroupParameters();
+ if (!par.checkMaintenanceGroupParameters())
+ return;
+
+ // loop through all scenarios
+ for (std::size_t scenarioIndex = 0; scenarioIndex < scenarioNumber_; ++scenarioIndex)
+ {
+ OptProblemSettings optSett;
+ optSett.firstDay = 0;
+ optSett.lastDay = optSett.firstDay + par.timeHorizon_;
+ optSett.scenario = scenarioIndex;
+
+ // loop till the end of scenario length
+ while (optSett.firstDay < scenarioLength_ * DAYS_PER_YEAR)
+ {
+ // check if the optimization was successful and exit loop otherwise
+ if (!runOptimizationProblem(optSett))
+ break;
+
+ // Update the time values for the next iteration
+ optSett.firstDay += par.timeStep_;
+ optSett.lastDay = optSett.firstDay + par.timeHorizon_;
+ optSett.isFirstStep = false;
+ }
+ par.postScenarioOptimization(optSett);
+ ++progression_;
+ }
+ writeTsResults();
+}
+
+void OptimizedThermalGenerator::allocateWhereToWriteTs()
+{
+ // loop per areas inside maintenance group
+ for (auto& entryWeightMap : maintenanceGroup_)
+ {
+ auto& area = *(entryWeightMap.first);
+ // loop per thermal clusters inside the area
+ for (auto& clusterEntry : area.thermal.list.mapping)
+ {
+ auto& cluster = *(clusterEntry.second);
+
+ // check if cluster exist, do we generate + optimizeMaintenance
+ // create start end variables only for these clusters
+ bool genTS = checkClusterExist(cluster)
+ && cluster.doWeGenerateTS(globalThermalTSgeneration_)
+ && cluster.optimizeMaintenance;
+ if (!genTS)
+ continue;
+
+ // allocate space
+ cluster.series.timeSeries.reset(nbThermalTimeseries, 8760);
+ }
+ }
+ return;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/main/OptimizedGenerator.h b/src/solver/ts-generator/optimized-thermal-generator/main/OptimizedGenerator.h
new file mode 100644
index 0000000000..ab99820b73
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/main/OptimizedGenerator.h
@@ -0,0 +1,180 @@
+//
+// Created by milos on 10/11/23.
+//
+
+#pragma once
+
+#include "ortools/linear_solver/linear_solver.h"
+#include "../../randomized-thermal-generator/RandomizedGenerator.h"
+#include "../../../../libs/antares/study/maintenance_planning/MaintenanceGroup.h"
+#include "../support/SupportStructures.h"
+#include "../support/SupportFunctions.h"
+#include "../parameters/OptimizationParameters.h"
+#include
+
+// static const std::string mntPlSolverName = "cbc";
+static const int minNumberOfMaintenances = 2;
+static const double solverDelta = 10e-4;
+
+using namespace operations_research;
+
+namespace Antares::TSGenerator
+{
+
+class OptimizedThermalGenerator : public GeneratorTempData
+{
+ using OptimizationResults = std::vector;
+
+private:
+ /* ===================OPTIMIZATION=================== */
+
+ // functions to build problem variables
+ void buildProblemVariables(const OptProblemSettings& optSett);
+ void buildEnsAndSpillageVariables(const OptProblemSettings& optSett);
+ void buildUnitPowerOutputVariables(const OptProblemSettings& optSett);
+ void buildUnitPowerOutputVariables(const OptProblemSettings& optSett,
+ const Data::ThermalCluster& cluster);
+ void buildUnitPowerOutputVariables(const OptProblemSettings& optSett,
+ const Data::ThermalCluster& cluster,
+ int unit);
+ void buildStartEndMntVariables(const OptProblemSettings& optSett,
+ const Data::ThermalCluster& cluster,
+ int unit,
+ Unit& unitRef);
+ void buildStartVariables(const OptProblemSettings& optSett,
+ const Data ::ThermalCluster& cluster,
+ int unit,
+ Unit& unitRef,
+ int mnt);
+ void buildEndVariables(const OptProblemSettings& optSett,
+ const Data ::ThermalCluster& cluster,
+ int unit,
+ Unit& unitRef,
+ int mnt);
+
+ // functions to fix bounds of some variables
+ void fixBounds();
+ void fixBounds(const Unit& unit);
+ void fixBounds(const Unit& unit, int averageMaintenanceDuration);
+ void fixBoundsFirstMnt(const Unit& unit);
+ void fixBoundsStartSecondMnt(const Unit& unit, int mnt);
+ void fixBoundsMntEnd(const Unit& unit, int mnt, int averageMaintenanceDuration);
+
+ // functions to build problem constraints
+ void buildProblemConstraints(const OptProblemSettings& optSett);
+ void setLoadBalanceConstraints(const OptProblemSettings& optSett);
+ void setLoadBalanceConstraints(const OptProblemSettings& optSett, int& day);
+ void insertEnsVars(MPConstraint* ct, int day);
+ void insertSpillVars(MPConstraint* ct, int day);
+ void insertPowerVars(MPConstraint* ct, int day);
+ void insertPowerVars(MPConstraint* ct, int day, const Unit& unit);
+ void setStartEndMntLogicConstraints(const OptProblemSettings& optSett);
+ void setStartEndMntLogicConstraints(const OptProblemSettings& optSett, const Unit& unit);
+ void setEndOfMaintenanceEventBasedOnAverageDurationOfMaintenanceEvent(
+ const OptProblemSettings& optSett,
+ const Unit& unit,
+ int mnt);
+ void setUpFollowingMaintenanceBasedOnAverageDurationBetweenMaintenanceEvents(
+ const OptProblemSettings& optSett,
+ const Unit& unit,
+ int mnt);
+ void setOnceStartIsSetToOneItWillBeOneUntilEndOfOptimizationTimeHorizon(
+ const OptProblemSettings& optSett,
+ const Unit& unit,
+ int mnt);
+ void setNextMaintenanceCanNotStartBeforePreviousMaintenance(const OptProblemSettings& optSett,
+ const Unit& cluster,
+ int mnt);
+ void setMaxUnitOutputConstraints(const OptProblemSettings& optSett);
+ void setMaxUnitOutputConstraints(const OptProblemSettings& optSett, int& day);
+ void setMaxUnitOutputConstraints(const OptProblemSettings& optSett, int day, const Unit& unit);
+ void insertStartSum(MPConstraint* ct, int day, const Unit& unit, double maxPower);
+ void insertEndSum(MPConstraint* ct, int day, const Unit& cluster, double maxPower);
+
+ // functions to set problem objective function
+ void setProblemCost(const OptProblemSettings& optSett);
+ void setProblemEnsCost(MPObjective* objective);
+ void setProblemSpillCost(MPObjective* objective);
+ void setProblemPowerCost(const OptProblemSettings& optSett, MPObjective* objective);
+ void setProblemPowerCost(const OptProblemSettings& optSett,
+ MPObjective* objective,
+ const Unit& unit);
+
+ // solve problem and check if optimal solution found
+ bool solveProblem(OptProblemSettings& optSett);
+
+ // reset problem and variable structure
+ void resetProblem();
+
+ /* ===================END-OPTIMIZATION=================== */
+
+ /* ===================MAIN=================== */
+
+ // Functions called in main method:
+ void allocateWhereToWriteTs();
+ bool runOptimizationProblem(OptProblemSettings& optSett);
+ void writeTsResults();
+
+ /* ===================END-MAIN=================== */
+
+ /* ===================CLASS-VARIABLES=================== */
+
+ // variables
+ Data::MaintenanceGroup& maintenanceGroup_;
+ bool globalThermalTSgeneration_;
+ int scenarioLength_;
+ int scenarioNumber_;
+
+ OptimizationParameters par;
+ OptimizationVariables vars;
+ OptimizationResults scenarioResults;
+
+ // MPSolver instance
+ MPSolver solver;
+ double solverInfinity;
+ Solver::Progression::Task& progression_;
+ /* ===================END-CLASS-VARIABLES=================== */
+
+public:
+ explicit OptimizedThermalGenerator(Data::Study& study,
+ Data::MaintenanceGroup& maintenanceGroup,
+ uint year,
+ bool globalThermalTSgeneration,
+ Solver::Progression::Task& progr,
+ Solver::IResultWriter& writer) :
+ GeneratorTempData(study, progr, writer),
+ maintenanceGroup_(maintenanceGroup),
+ progression_(progr),
+ par(study, maintenanceGroup, globalThermalTSgeneration, vars, scenarioResults, progr, writer),
+ solver(MPSolver("MaintenancePlanning", MPSolver::CBC_MIXED_INTEGER_PROGRAMMING))
+ {
+ currentYear = year;
+ globalThermalTSgeneration_ = globalThermalTSgeneration;
+ scenarioLength_ = study.parameters.maintenancePlanning.getScenarioLength();
+ scenarioNumber_ = study.parameters.maintenancePlanning.getScenarioNumber();
+ nbThermalTimeseries = scenarioLength_ * scenarioNumber_;
+ par.scenarioLength_ = scenarioLength_;
+
+ // Solver Settings
+ // MP solver parameters / TODD CR27: do we change this -
+ // I would keep it on default values for the time being
+
+ // Access solver parameters
+ MPSolverParameters params;
+ // Set parameter values
+ // params.SetIntegerParam(MPSolverParameters::SCALING, 0);
+ // params.SetIntegerParam(MPSolverParameters::PRESOLVE, 0);
+
+ // set solver infinity
+ solverInfinity = solver.infinity();
+ }
+
+ ~OptimizedThermalGenerator() = default;
+
+ // Main functions - loop per scenarios and
+ // through the scenario length step by step
+ // (moving window)
+ void GenerateOptimizedThermalTimeSeries();
+};
+
+} // namespace Antares::TSGenerator
\ No newline at end of file
diff --git a/src/solver/ts-generator/optimized-thermal-generator/optimization/CreateVariables.cpp b/src/solver/ts-generator/optimized-thermal-generator/optimization/CreateVariables.cpp
new file mode 100644
index 0000000000..3b603bf67f
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/optimization/CreateVariables.cpp
@@ -0,0 +1,156 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+void OptimizedThermalGenerator::buildProblemVariables(const OptProblemSettings& optSett)
+{
+ buildEnsAndSpillageVariables(optSett);
+ buildUnitPowerOutputVariables(optSett);
+}
+
+// create VARIABLES per day - ENS[t], Spill[t]
+void OptimizedThermalGenerator::buildEnsAndSpillageVariables(const OptProblemSettings& optSett)
+{
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ // add ENS[t] variables
+ vars.ens.push_back(solver.MakeNumVar(
+ 0.0, solverInfinity, "ENS_[" + std::to_string(day + optSett.firstDay) + "]"));
+ }
+
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ // add Spillage[t] variables
+ vars.spill.push_back(solver.MakeNumVar(
+ 0.0, solverInfinity, "Spill_[" + std::to_string(day + optSett.firstDay) + "]"));
+ }
+ return;
+}
+
+// create VARIABLES per day and per cluster-unit - P[u][t]
+
+void OptimizedThermalGenerator::buildUnitPowerOutputVariables(const OptProblemSettings& optSett)
+{
+ // loop per thermal clusters inside the area
+ for (const auto& clusterEntry : par.clusterData)
+ {
+ const auto& cluster = *(clusterEntry.first);
+ buildUnitPowerOutputVariables(optSett, cluster);
+ }
+}
+
+void OptimizedThermalGenerator::buildUnitPowerOutputVariables(const OptProblemSettings& optSett,
+ const Data::ThermalCluster& cluster)
+{
+ // loop per units inside the cluster
+ for (int unit = 0; unit < cluster.unitCount; ++unit)
+ {
+ buildUnitPowerOutputVariables(optSett, cluster, unit);
+ }
+}
+
+void OptimizedThermalGenerator::buildUnitPowerOutputVariables(const OptProblemSettings& optSett,
+ const Data::ThermalCluster& cluster,
+ int unit)
+{
+ // add new Unit
+ vars.clusterUnits.push_back(Unit());
+
+ // but we do not know the total unit count
+ // so always retrieve the last one
+ auto& unitRef = vars.clusterUnits.back();
+
+ // fill in data for the Unit
+ unitRef.parentCluster = &cluster;
+ unitRef.index = unit; // local count inside the cluster
+ unitRef.createStartEndVariables = true;
+
+ // if we are in the first step
+ // lets add Unit, with inputs, to the scenarioResults
+ if (optSett.isFirstStep)
+ scenarioResults.push_back(vars.clusterUnits.back());
+
+ // loop per day
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ // add P[u][t] variables
+ unitRef.P.push_back(solver.MakeNumVar(0.0,
+ solverInfinity,
+ "P_[" + cluster.getFullName().to() + "."
+ + std::to_string(unit) + "]["
+ + std::to_string(day + optSett.firstDay) + "]"));
+ }
+
+ // check if: do we generate + optimizeMaintenance
+ // create start end variables only for these clusters
+ if (!(cluster.doWeGenerateTS(globalThermalTSgeneration_) && cluster.optimizeMaintenance))
+ {
+ unitRef.createStartEndVariables = false;
+ if (optSett.isFirstStep)
+ scenarioResults.back().createStartEndVariables = false;
+ return;
+ }
+
+ buildStartEndMntVariables(optSett, cluster, unit, unitRef);
+
+ return;
+}
+
+void OptimizedThermalGenerator::buildStartEndMntVariables(const OptProblemSettings& optSett,
+ const Data ::ThermalCluster& cluster,
+ int unit,
+ Unit& unitRef)
+{
+ int totalMntNumber = par.getNumberOfMaintenances(cluster, unit);
+ // loop per maintenances per unit
+ for (int mnt = 0; mnt < totalMntNumber; ++mnt)
+ {
+ unitRef.maintenances.push_back(Maintenances());
+ buildStartVariables(optSett, cluster, unit, unitRef, mnt);
+ buildEndVariables(optSett, cluster, unit, unitRef, mnt);
+ }
+}
+
+void OptimizedThermalGenerator::buildStartVariables(const OptProblemSettings& optSett,
+ const Data ::ThermalCluster& cluster,
+ int unit,
+ Unit& unitRef,
+ int mnt)
+{
+ // loop per day
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ // add start[u][m][t] variables
+ unitRef.maintenances.back().start.push_back(solver.MakeIntVar(
+ 0.0,
+ 1.0,
+ "S_[" + cluster.getFullName().to() + "." + std::to_string(unit) + "]["
+ + std::to_string(mnt) + "][" + std::to_string(day + optSett.firstDay) + "]"));
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::buildEndVariables(const OptProblemSettings& optSett,
+ const Data ::ThermalCluster& cluster,
+ int unit,
+ Unit& unitRef,
+ int mnt)
+{
+ // loop per day
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ // add end[u][m][t] variables
+ unitRef.maintenances.back().end.push_back(solver.MakeIntVar(
+ 0.0,
+ 1.0,
+ "E_[" + cluster.getFullName().to() + "." + std::to_string(unit) + "]["
+ + std::to_string(mnt) + "][" + std::to_string(day + optSett.firstDay) + "]"));
+ }
+ return;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/optimization/PrintProblemVarRes.cpp b/src/solver/ts-generator/optimized-thermal-generator/optimization/PrintProblemVarRes.cpp
new file mode 100644
index 0000000000..a99cb4b32b
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/optimization/PrintProblemVarRes.cpp
@@ -0,0 +1,215 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include
+#include
+#include
+#include
+#include
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+void OptimizationParameters::printProblemVarAndResults(OptProblemSettings& optSett, MPSolver& solver)
+{
+ printAllVariables(solver);
+ printConstraints(solver);
+ printObjectiveFunction(solver.MutableObjective(), solver);
+ printResults(optSett, solver);
+}
+
+void OptimizationParameters::printAllVariables(MPSolver& solver)
+{
+ std::vector> dataToPrint;
+
+ for (MPVariable* const variable : solver.variables())
+ {
+ std::vector RED;
+ std::cout << "Variable: " << variable->name() << ", "
+ << "Lower bound: " << variable->lb() << ", "
+ << "Upper bound: " << variable->ub() << std::endl;
+ RED.push_back(variable->name());
+ RED.push_back(std::to_string(variable->lb()));
+ RED.push_back(std::to_string(variable->ub()));
+ dataToPrint.push_back(RED);
+ }
+
+ std::cout << "total number of variables is: " << solver.NumVariables() << std::endl;
+ printColumnToFile(
+ dataToPrint, "/home/milos/Documents/RTEi/01-Antares/04-TestModels/REFACTOR-CR27-Vars.csv");
+ return;
+}
+
+void OptimizationParameters::printConstraints(MPSolver& solver)
+{
+ const int num_constraints = solver.NumConstraints();
+ std::vector> dataToPrint;
+ for (int i = 0; i < num_constraints; ++i)
+ {
+ std::vector RED;
+ const MPConstraint* const constraint = solver.constraint(i);
+ std::cout << "**** Constraint " << i + 1 << " ****" << std::endl;
+ std::cout << "Name: " << constraint->name() << std::endl;
+ std::cout << "Lower Bound: " << constraint->lb() << std::endl;
+ std::cout << "Upper Bound: " << constraint->ub() << std::endl;
+
+ RED.push_back(constraint->name());
+ RED.push_back(std::to_string(constraint->lb()));
+ RED.push_back(std::to_string(constraint->ub()));
+
+ std::vector RED_SORT;
+ for (const auto& term : constraint->terms())
+ {
+ const MPVariable* const var = term.first;
+ const double coefficient = term.second;
+ std::cout << var->name() << ": " << coefficient << std::endl;
+ RED_SORT.push_back(var->name() + "->" + std::to_string(coefficient));
+ }
+
+ // sort sub vector
+ std::sort(RED_SORT.begin(), RED_SORT.end());
+ // insert it in main vector
+ RED.insert(RED.end(), RED_SORT.begin(), RED_SORT.end());
+ // end of row
+ dataToPrint.push_back(RED);
+ std::cout << "------------------------" << std::endl;
+ }
+
+ printColumnToFile(
+ dataToPrint,
+ "/home/milos/Documents/RTEi/01-Antares/04-TestModels/REFACTOR-CR27-Constraints.csv");
+}
+
+void OptimizationParameters::printObjectiveFunction(MPObjective* objective, MPSolver& solver)
+{
+ std::vector> dataToPrint;
+ for (MPVariable* variable : solver.variables())
+ {
+ std::cout << variable->name() << ": " << objective->GetCoefficient(variable) << std::endl;
+ std::vector RED;
+ RED.push_back(variable->name());
+ RED.push_back(std::to_string(objective->GetCoefficient(variable)));
+ dataToPrint.push_back(RED);
+ }
+ std::cout << std::endl;
+ printColumnToFile(
+ dataToPrint,
+ "/home/milos/Documents/RTEi/01-Antares/04-TestModels/REFACTOR-CR27-Objective.csv");
+ return;
+}
+
+void OptimizationParameters::printResults(OptProblemSettings& optSett, MPSolver& solver)
+{
+ std::vector> dataToPrint;
+
+ // loop per day
+ for (auto day = 0; day != vars_.ens.size(); ++day)
+ {
+ // row vector
+ std::vector RED;
+
+ // ens and spill
+ RED.push_back(vars_.ens[day]->solution_value());
+ RED.push_back(vars_.spill[day]->solution_value());
+
+ // powers + start/end
+ for (const auto& unit : vars_.clusterUnits)
+ {
+ // powers
+ if (unit.P[day] != nullptr)
+ {
+ RED.push_back(unit.P[day]->solution_value());
+ }
+ // start and end
+ for (uint mnt = 0; mnt < unit.maintenances.size(); ++mnt)
+ {
+ RED.push_back(unit.maintenances[mnt].start[day]->solution_value());
+ RED.push_back(unit.maintenances[mnt].end[day]->solution_value());
+ }
+ }
+
+ dataToPrint.push_back(RED);
+ }
+
+ printf("Optimal objective value = %.2f\n", solver.MutableObjective()->Value());
+
+ std::string fileName = "/home/milos/Documents/RTEi/01-Antares/04-TestModels/"
+ + std::to_string(optSett.firstDay) + "-Results.csv";
+
+ printColumnToFile(dataToPrint, fileName);
+
+ return;
+}
+
+void OptimizationParameters::printMaintenances(OptProblemSettings& optSett)
+{
+ std::vector> dataToPrint;
+ for (auto& unit : scenarioResults_)
+ {
+ std::vector RED;
+ RED.push_back(9999);
+ RED.push_back(9999);
+ dataToPrint.push_back(RED);
+ for (auto& mnt : unit.maintenanceResults)
+ {
+ RED.clear();
+ RED.push_back(mnt.first);
+ RED.push_back(mnt.second);
+ dataToPrint.push_back(RED);
+ }
+ }
+
+ std::string fileName = "/home/milos/Documents/RTEi/01-Antares/04-TestModels/"
+ + std::to_string(optSett.firstDay) + "-Maintenances.csv";
+
+ printColumnToFile(dataToPrint, fileName);
+}
+
+void OptimizationParameters::printAvailability(OptProblemSettings& optSett)
+{
+ std::vector> dataToPrint;
+
+ for (int day = 0; day != scenarioLength_ * 365; ++day)
+ {
+ std::vector RED;
+ for (auto& cluster : clusterData)
+ {
+ RED.push_back(cluster.second.dynamicResults.availableDailyPower[day]);
+ }
+
+ dataToPrint.push_back(RED);
+ }
+
+ std::string fileName = "/home/milos/Documents/RTEi/01-Antares/04-TestModels/"
+ + std::to_string(optSett.firstDay) + "-Availability.csv";
+
+ printColumnToFile(dataToPrint, fileName);
+}
+
+// Define the auxiliary function outside the class
+template
+void printColumnToFile(const std::vector>& data, const std::string& filename)
+{
+ std::ofstream outFile(filename);
+ if (outFile.is_open())
+ {
+ for (size_t row = 0; row < data.size(); ++row)
+ {
+ for (size_t col = 0; col < data[row].size(); ++col)
+ {
+ outFile << data[row][col];
+ outFile << ",";
+ }
+ outFile << std::endl;
+ }
+ outFile.close();
+ }
+ else
+ {
+ std::cerr << "Unable to open file: " << filename << std::endl;
+ }
+}
+
+} // namespace Antares::TSGenerator
\ No newline at end of file
diff --git a/src/solver/ts-generator/optimized-thermal-generator/optimization/ResetProblem.cpp b/src/solver/ts-generator/optimized-thermal-generator/optimization/ResetProblem.cpp
new file mode 100644
index 0000000000..6880ee114a
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/optimization/ResetProblem.cpp
@@ -0,0 +1,19 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+void OptimizedThermalGenerator::resetProblem()
+{
+ // Clear the solver to reset it for the new problem
+ solver.Clear();
+ vars.ens.clear();
+ vars.spill.clear();
+ vars.clusterUnits.clear();
+ return;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/optimization/SetProblemConstraints.cpp b/src/solver/ts-generator/optimized-thermal-generator/optimization/SetProblemConstraints.cpp
new file mode 100644
index 0000000000..148cdeb0e1
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/optimization/SetProblemConstraints.cpp
@@ -0,0 +1,248 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+void OptimizedThermalGenerator::buildProblemConstraints(const OptProblemSettings& optSett)
+{
+ setLoadBalanceConstraints(optSett);
+ setStartEndMntLogicConstraints(optSett);
+ setMaxUnitOutputConstraints(optSett);
+}
+
+// load balance CONSTRAINTS - constraint-per-each-day[t] - we have sum through [u] inside of it
+void OptimizedThermalGenerator::setLoadBalanceConstraints(const OptProblemSettings& optSett)
+{
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ setLoadBalanceConstraints(optSett, day);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::setLoadBalanceConstraints(const OptProblemSettings& optSett,
+ int& day)
+{
+ int optimizationDay = day + optSett.firstDay;
+ std::string ctName = "LoadBalanceConstraint[" + std::to_string(optimizationDay) + "]";
+ double residualLoad = par.getResidualLoad(optimizationDay);
+ MPConstraint* ct = solver.MakeRowConstraint(residualLoad, residualLoad, ctName);
+
+ insertPowerVars(ct, day);
+ insertEnsVars(ct, day);
+ insertSpillVars(ct, day);
+ return;
+}
+
+void OptimizedThermalGenerator::insertEnsVars(MPConstraint* ct, int day)
+{
+ ct->SetCoefficient(vars.ens[day], 1.0);
+}
+
+void OptimizedThermalGenerator::insertSpillVars(MPConstraint* ct, int day)
+{
+ ct->SetCoefficient(vars.spill[day], -1.0);
+}
+
+void OptimizedThermalGenerator::insertPowerVars(MPConstraint* ct, int day)
+{
+ for (const auto& unit : vars.clusterUnits)
+ {
+ insertPowerVars(ct, day, unit);
+ }
+}
+
+void OptimizedThermalGenerator::insertPowerVars(MPConstraint* ct, int day, const Unit& unit)
+{
+ ct->SetCoefficient(unit.P[day], 1.0);
+}
+
+// CONSTRAINTS per days, per units and per maintenance - constraint-per-each-day+unit+mnt[u][m][t]
+void OptimizedThermalGenerator::setStartEndMntLogicConstraints(const OptProblemSettings& optSett)
+{
+ for (const auto& unit : vars.clusterUnits)
+ {
+ setStartEndMntLogicConstraints(optSett, unit);
+ }
+}
+
+void OptimizedThermalGenerator::setStartEndMntLogicConstraints(const OptProblemSettings& optSett,
+ const Unit& unit)
+{
+ if (!unit.createStartEndVariables)
+ return;
+
+ // loop per maintenances per unit
+ for (int mnt = 0; mnt < unit.maintenances.size(); ++mnt)
+ {
+ assert(unit.maintenances[mnt].start.size() == par.timeHorizon_);
+ assert(unit.maintenances[mnt].end.size() == par.timeHorizon_);
+ setEndOfMaintenanceEventBasedOnAverageDurationOfMaintenanceEvent(optSett, unit, mnt);
+ setOnceStartIsSetToOneItWillBeOneUntilEndOfOptimizationTimeHorizon(optSett, unit, mnt);
+ }
+
+ // loop per maintenances per unit - except last one
+ for (int mnt = 0; mnt < unit.maintenances.size() - 1; ++mnt)
+ {
+ setUpFollowingMaintenanceBasedOnAverageDurationBetweenMaintenanceEvents(optSett, unit, mnt);
+ setNextMaintenanceCanNotStartBeforePreviousMaintenance(optSett, unit, mnt);
+ }
+}
+
+void OptimizedThermalGenerator::setEndOfMaintenanceEventBasedOnAverageDurationOfMaintenanceEvent(
+ const OptProblemSettings& optSett,
+ const Unit& unit,
+ int mnt)
+{
+ const auto& cluster = *(unit.parentCluster);
+ int averageMaintenanceDuration = par.getAverageMaintenanceDuration(cluster);
+ for (int day = 0; day < par.timeHorizon_ - averageMaintenanceDuration; ++day)
+ {
+ std::string ctName = "E[u][q][t+Mu] = S[u][q][t] -> ["
+ + cluster.getFullName().to() + "."
+ + std::to_string(unit.index) + "][" + std::to_string(mnt) + "]["
+ + std::to_string(day + optSett.firstDay) + "]";
+ MPConstraint* ct = solver.MakeRowConstraint(0.0, 0.0, ctName);
+
+ ct->SetCoefficient(unit.maintenances[mnt].end[day + averageMaintenanceDuration], 1.0);
+ ct->SetCoefficient(unit.maintenances[mnt].start[day], -1.0);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::
+ setUpFollowingMaintenanceBasedOnAverageDurationBetweenMaintenanceEvents(
+ const OptProblemSettings& optSett,
+ const Unit& unit,
+ int mnt)
+{
+ const auto& cluster = *(unit.parentCluster);
+ int averageDurationBetweenMaintenances = par.getAverageDurationBetweenMaintenances(cluster);
+ for (int day = 0; day < par.timeHorizon_ - averageDurationBetweenMaintenances; ++day)
+ {
+ std::string ctName = "S[u][q+1][t+Tu] = E[u][q][t] -> ["
+ + cluster.getFullName().to() + "."
+ + std::to_string(unit.index) + "][" + std::to_string(mnt) + "]["
+ + std::to_string(day + optSett.firstDay) + "]";
+ MPConstraint* ct = solver.MakeRowConstraint(0.0, 0.0, ctName);
+
+ ct->SetCoefficient(
+ unit.maintenances[mnt + 1].start[day + averageDurationBetweenMaintenances], 1.0);
+ ct->SetCoefficient(unit.maintenances[mnt].end[day], -1.0);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::setOnceStartIsSetToOneItWillBeOneUntilEndOfOptimizationTimeHorizon(
+ const OptProblemSettings& optSett,
+ const Unit& unit,
+ int mnt)
+{
+ const auto& cluster = *(unit.parentCluster);
+ for (int day = 0; day < par.timeHorizon_ - 1; ++day)
+ {
+ std::string ctName = "S[u][q][t+1] >= S[u][q][t] -> ["
+ + cluster.getFullName().to() + "."
+ + std::to_string(unit.index) + "][" + std::to_string(mnt) + "]["
+ + std::to_string(day + optSett.firstDay) + "]";
+ MPConstraint* ct = solver.MakeRowConstraint(0.0 - solverDelta, solverInfinity, ctName);
+
+ ct->SetCoefficient(unit.maintenances[mnt].start[day + 1], 1.0);
+ ct->SetCoefficient(unit.maintenances[mnt].start[day], -1.0);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::setNextMaintenanceCanNotStartBeforePreviousMaintenance(
+ const OptProblemSettings& optSett,
+ const Unit& unit,
+ int mnt)
+{
+ const auto& cluster = *(unit.parentCluster);
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ std::string ctName = "S[u][q][t] >= S[u][q+1][t] -> ["
+ + cluster.getFullName().to() + "."
+ + std::to_string(unit.index) + "][" + std::to_string(mnt) + "]["
+ + std::to_string(day + optSett.firstDay) + "]";
+ MPConstraint* ct = solver.MakeRowConstraint(0.0 - solverDelta, solverInfinity, ctName);
+
+ ct->SetCoefficient(unit.maintenances[mnt].start[day], 1.0);
+ ct->SetCoefficient(unit.maintenances[mnt + 1].start[day], -1.0);
+ }
+ return;
+}
+
+// Maximum outputs of the units
+// CONSTRAINTS per days and per units - constraint-per-each-day+unit[t][u][m-sum per m]
+void OptimizedThermalGenerator::setMaxUnitOutputConstraints(const OptProblemSettings& optSett)
+{
+ for (int day = 0; day < par.timeHorizon_; ++day)
+ {
+ setMaxUnitOutputConstraints(optSett, day);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::setMaxUnitOutputConstraints(const OptProblemSettings& optSett,
+ int& day)
+{
+ for (const auto& unit : vars.clusterUnits)
+ {
+ setMaxUnitOutputConstraints(optSett, day, unit);
+ }
+}
+
+void OptimizedThermalGenerator::setMaxUnitOutputConstraints(const OptProblemSettings& optSett,
+ int day,
+ const Unit& unit)
+{
+ const auto& cluster = *(unit.parentCluster);
+ int optimizationDay = day + optSett.firstDay;
+ double maxPower = par.getPowerOutput(cluster, optimizationDay);
+
+ std::string ctName = "MaxPowerOutputConstraint[" + cluster.getFullName().to() + "."
+ + std::to_string(unit.index) + "][" + std::to_string(optimizationDay)
+ + "]";
+ MPConstraint* ct = solver.MakeRowConstraint(0.0 - solverDelta, maxPower + solverDelta, ctName);
+
+ insertPowerVars(ct, day, unit); // re-using this method on purpose!
+
+ // we add sum[per-q](s[u][q][t]-e[u][q][t])
+ // only if we have defined variables start and end for the units
+ // if not we ebd up with: 0 <= P <= Pmax
+ // so here we just check if the cluster is involved in maintenance planing
+ if (!unit.createStartEndVariables)
+ return;
+
+ insertStartSum(ct, day, unit, maxPower);
+ insertEndSum(ct, day, unit, maxPower);
+}
+
+void OptimizedThermalGenerator::insertStartSum(MPConstraint* ct,
+ int day,
+ const Unit& unit,
+ double maxPower)
+{
+ for (int mnt = 0; mnt < unit.maintenances.size(); ++mnt)
+ {
+ ct->SetCoefficient(unit.maintenances[mnt].start[day], maxPower);
+ }
+}
+
+void OptimizedThermalGenerator::insertEndSum(MPConstraint* ct,
+ int day,
+ const Unit& unit,
+ double maxPower)
+{
+ // loop per maintenances per unit
+ for (int mnt = 0; mnt < unit.maintenances.size(); ++mnt)
+ {
+ ct->SetCoefficient(unit.maintenances[mnt].end[day], -maxPower);
+ }
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/optimization/SetProblemCost.cpp b/src/solver/ts-generator/optimized-thermal-generator/optimization/SetProblemCost.cpp
new file mode 100644
index 0000000000..c97a75a8e8
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/optimization/SetProblemCost.cpp
@@ -0,0 +1,69 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+// create OBJECTIVE FUNCTION - sum through [t] and sum through [u]
+// sum[days]{ EnsCost*Ens[day] + SpillCost[day] + sum[units][ avgCost*P[t][u] ] }
+void OptimizedThermalGenerator::setProblemCost(const OptProblemSettings& optSett)
+{
+ MPObjective* objective = solver.MutableObjective();
+
+ setProblemEnsCost(objective);
+ setProblemSpillCost(objective);
+ setProblemPowerCost(optSett, objective);
+
+ objective->SetMinimization();
+
+ return;
+}
+
+void OptimizedThermalGenerator::setProblemEnsCost(MPObjective* objective)
+{
+ // loop per day
+ for (const auto& ens : vars.ens)
+ {
+ objective->SetCoefficient(ens, par.ensCost_);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::setProblemSpillCost(MPObjective* objective)
+{
+ // loop per day
+ for (const auto& spill : vars.spill)
+ {
+ objective->SetCoefficient(spill, par.spillCost_);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::setProblemPowerCost(const OptProblemSettings& optSett,
+ MPObjective* objective)
+{
+ // loop per units
+ for (const auto& unit : vars.clusterUnits)
+ {
+ setProblemPowerCost(optSett, objective, unit);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::setProblemPowerCost(const OptProblemSettings& optSett,
+ MPObjective* objective,
+ const Unit& unit)
+{
+ // loop per day
+ for (int day = 0; day < unit.P.size(); ++day)
+ {
+ double unitPowerCost = par.getPowerCost(*(unit.parentCluster), day + optSett.firstDay);
+ objective->SetCoefficient(unit.P[day], unitPowerCost);
+ }
+
+ return;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/optimization/SetVariableBounds.cpp b/src/solver/ts-generator/optimized-thermal-generator/optimization/SetVariableBounds.cpp
new file mode 100644
index 0000000000..3064a544cc
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/optimization/SetVariableBounds.cpp
@@ -0,0 +1,96 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+// TODO CR27: see if to make this bound or constraint -
+// it is definitely easier to do set it as a fix bound -
+// but the solver might go crazy - as for adq.patch
+
+// this will fix some start & end variable bounds to 0 or 1
+void OptimizedThermalGenerator::fixBounds()
+{
+ // loop per units
+ for (const auto& unit : vars.clusterUnits)
+ {
+ fixBounds(unit);
+ }
+ return;
+}
+
+void OptimizedThermalGenerator::fixBounds(const Unit& unit)
+{
+ if (!unit.createStartEndVariables)
+ return;
+
+ int averageMaintenanceDuration = par.getAverageMaintenanceDuration(*(unit.parentCluster));
+
+ fixBoundsFirstMnt(unit);
+ fixBounds(unit, averageMaintenanceDuration);
+}
+
+// Bounds for the start of the first maintenance
+// first maintenance must start between tauLower and tauUpper
+// start[u][0][tauLower-1] = 0
+// start[u][0][tauUpper] = 1
+void OptimizedThermalGenerator::fixBoundsFirstMnt(const Unit& unit)
+{
+ int earliestStartOfFirstMaintenance
+ = par.calculateUnitEarliestStartOfFirstMaintenance(*(unit.parentCluster), unit.index);
+ int latestStartOfFirstMaintenance
+ = par.calculateUnitLatestStartOfFirstMaintenance(*(unit.parentCluster), unit.index);
+
+ //
+ // We assume here that vector "maintenance" has member [0]
+ // meaning: for each unit we assume we have at least one maintenance
+ // this assumption is ok - since method calculateNumberOfMaintenances()
+ // will never return number bellow 2
+
+ if (earliestStartOfFirstMaintenance >= 1)
+ {
+ // start[u][0][tauLower-1] = 0
+ unit.maintenances[0].start[earliestStartOfFirstMaintenance - 1]->SetBounds(0.0, 0.0);
+ }
+
+ // start[u][0][tauUpper] = 1
+ unit.maintenances[0].start[latestStartOfFirstMaintenance]->SetBounds(1.0, 1.0);
+
+ return;
+}
+
+void OptimizedThermalGenerator::fixBounds(const Unit& unit, int averageMaintenanceDuration)
+{
+ // loop per maintenances of unit
+ for (int mnt = 0; mnt < unit.maintenances.size(); ++mnt)
+ {
+ fixBoundsStartSecondMnt(unit, mnt);
+ fixBoundsMntEnd(unit, mnt, averageMaintenanceDuration);
+ }
+
+ return;
+}
+
+// Ensure that units with max average duration between maintenances start their second maintenance
+// start[u][q][T] = 1
+void OptimizedThermalGenerator::fixBoundsStartSecondMnt(const Unit& unit, int mnt)
+{
+ unit.maintenances[mnt].start[par.timeHorizon_ - 1]->SetBounds(1.0, 1.0);
+}
+
+// End of the maintenance can't happen before average maintenance duration
+// end[u][q][T = [0, average_maintenance_duration_per_unit]] = 0
+void OptimizedThermalGenerator::fixBoundsMntEnd(const Unit& unit,
+ int mnt,
+ int averageMaintenanceDuration)
+{
+ for (int day = 0; day < averageMaintenanceDuration; ++day)
+ {
+ unit.maintenances[mnt].end[day]->SetBounds(0.0, 0.0);
+ }
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/optimization/SolveProbem.cpp b/src/solver/ts-generator/optimized-thermal-generator/optimization/SolveProbem.cpp
new file mode 100644
index 0000000000..31dea2844a
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/optimization/SolveProbem.cpp
@@ -0,0 +1,42 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+// call all methods
+bool OptimizedThermalGenerator::runOptimizationProblem(OptProblemSettings& optSett)
+{
+ resetProblem();
+ buildProblemVariables(optSett);
+ fixBounds();
+ buildProblemConstraints(optSett);
+ setProblemCost(optSett);
+ if (!solveProblem(optSett))
+ return false;
+ par.postTimeStepOptimization(optSett);
+ return par.checkTimeHorizon(optSett);
+}
+
+// retrieve and check the results if optimization was successful
+bool OptimizedThermalGenerator::solveProblem(OptProblemSettings& optSett)
+{
+ // Solve the problem
+ const MPSolver::ResultStatus result_status = solver.Solve();
+
+ if (result_status != MPSolver::OPTIMAL)
+ {
+ // If not optimal, print that optimization failed
+ optSett.solved = false;
+ logs.warning() << "Maintenance group: " << maintenanceGroup_.name()
+ << ". Scenario Num: " << optSett.scenario
+ << ". Optimization failed in step: " << optSett.firstDay << ".Day - "
+ << optSett.lastDay << ".Day. This scenario wont have generated timeseries";
+ return false;
+ }
+ return true;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/parameters/CalculateParameters.cpp b/src/solver/ts-generator/optimized-thermal-generator/parameters/CalculateParameters.cpp
new file mode 100644
index 0000000000..cd88b7fdcd
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/parameters/CalculateParameters.cpp
@@ -0,0 +1,270 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+// methods are ordered in the line of execution order
+void OptimizationParameters::allocateClusterData()
+{
+ // loop all areas in maintenance group
+ for (const auto& entryWeightMap : maintenanceGroup_)
+ {
+ const auto& area = *(entryWeightMap.first);
+ // loop all thermal clusters inside the area
+ for (const auto& clusterEntry : area.thermal.list.mapping)
+ {
+ const auto& cluster = *(clusterEntry.second);
+ if (!checkClusterExist(cluster))
+ continue;
+
+ // create struct
+ clusterData[&cluster] = ClusterData();
+ clusterData[&cluster].maintenanceEnabled
+ = (cluster.doWeGenerateTS(globalThermalTSgeneration_) && cluster.optimizeMaintenance);
+ clusterData[&cluster].staticInputs = StaticInputs();
+ clusterData[&cluster].dynamicInputs = DynamicInputs();
+ clusterData[&cluster].dynamicResults = DynamicResults();
+ }
+ }
+}
+
+void OptimizationParameters::calculateNonDependantClusterData()
+{
+ for (auto& clusterEntry : clusterData)
+ {
+ auto& data = clusterEntry.second;
+ const auto& cluster = *(clusterEntry.first);
+
+ // static Inputs
+ // this inputs wont be changed during optimization problem
+ // and are important to calculate immediately
+ // cause their values are used for calculating other parameters
+ data.staticInputs.maxPower = calculateMaxUnitOutput(cluster);
+ data.staticInputs.avgCost = calculateAvrUnitDailyCost(cluster);
+
+ if (!data.maintenanceEnabled)
+ continue;
+
+ data.staticInputs.averageMaintenanceDuration = calculateAverageMaintenanceDuration(cluster);
+ // static inputs for random generator
+ prepareIndispoFromLaw(cluster.plannedLaw,
+ cluster.plannedVolatility,
+ data.staticInputs.AP,
+ data.staticInputs.BP,
+ cluster.prepro->data[Data::PreproThermal::poDuration]);
+
+ // dynamic input
+ // this input will be updated during optimization steps
+ // and then re-sett
+ // important to initialize it immediately
+ // cause it is used for later parameter calculation
+ data.dynamicInputs.daysSinceLastMaintenance
+ = cluster.originalRandomlyGeneratedDaysSinceLastMaintenance;
+ }
+}
+
+void OptimizationParameters::calculateResidualLoad()
+{
+ // create reference value arrays
+ std::array refValueLoad = {};
+ std::array refValueRor = {};
+ std::array refValueRenewable = {};
+ std::array refValue = {};
+
+ // for phase II
+ if (maintenanceGroup_.type() == Data::MaintenanceGroup::typeTimeserie)
+ {
+ // read user defined ts - userProvidedResidualLoadTS_ with getter - phase-II
+ return;
+ }
+
+ // loop through the elements of weightMap weights_
+ for (const auto& entryWeightMap : maintenanceGroup_)
+ {
+ const auto& area = *(entryWeightMap.first);
+ const auto weights = (entryWeightMap.second);
+
+ auto tmpLoad = calculateAverageLoadTs(area);
+ auto tmpRor = calculateAverageRorTs(area);
+ auto tmpRenewable = calculateAverageRenewableTs(modelingType_, area);
+
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ refValueLoad[row] += tmpLoad[row] * weights.load;
+ refValueRor[row] += tmpRor[row] * weights.ror;
+ refValueRenewable[row] += tmpRenewable[row] * weights.renewable;
+ }
+ }
+ // calculate reference value
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ refValue[row] = refValueLoad[row] - refValueRor[row] - refValueRenewable[row];
+ // set ResidualLoadTS
+ maintenanceGroup_.setUsedResidualLoadTS(refValue);
+}
+
+std::pair OptimizationParameters::calculateMaintenanceGroupENSandSpillageCost()
+{
+ std::vector ensVector = {};
+ std::vector spillageVector = {};
+ for (const auto& entryWeightMap : maintenanceGroup_)
+ {
+ const auto& area = *(entryWeightMap.first);
+ ensVector.push_back(area.thermal.unsuppliedEnergyCost);
+ spillageVector.push_back(area.thermal.spilledEnergyCost);
+ }
+
+ // Using std::minmax_element to find min and max
+ auto ens = std::min_element(ensVector.begin(), ensVector.end());
+ auto spill = std::min_element(spillageVector.begin(), spillageVector.end());
+
+ // Check if the vector is not empty
+ if (ens != ensVector.end() && spill != spillageVector.end())
+ {
+ return std::make_pair(*ens, *spill);
+ }
+
+ return std::make_pair(0, 0);
+}
+
+int OptimizationParameters::calculateTimeStep()
+{
+ std::vector averageDurationBetweenMaintenances = {};
+ for (const auto& clusterEntry : clusterData)
+ {
+ const auto& cluster = *(clusterEntry.first);
+ if (cluster.doWeGenerateTS(globalThermalTSgeneration_) && cluster.optimizeMaintenance)
+ {
+ averageDurationBetweenMaintenances.push_back(cluster.interPoPeriod);
+ }
+ }
+
+ auto minIter = std::min_element(averageDurationBetweenMaintenances.begin(),
+ averageDurationBetweenMaintenances.end());
+
+ // Check if the vector is not empty
+ if (minIter != averageDurationBetweenMaintenances.end())
+ {
+ return *minIter;
+ }
+
+ return 0;
+}
+
+int OptimizationParameters::calculateTimeHorizon()
+{
+ std::vector timeHorizonVector = {};
+ for (const auto& clusterEntry : clusterData)
+ {
+ const auto& cluster = *(clusterEntry.first);
+ if (!(cluster.doWeGenerateTS(globalThermalTSgeneration_) && cluster.optimizeMaintenance))
+ continue;
+ for (int unit = 0; unit < cluster.unitCount; ++unit)
+ {
+ int value = 2 * getAverageDurationBetweenMaintenances(cluster)
+ + getAverageMaintenanceDuration(cluster)
+ - std::min(getAverageDurationBetweenMaintenances(cluster),
+ getDaysSinceLastMaintenance(cluster, unit))
+ + 1;
+ timeHorizonVector.push_back(value);
+ }
+ }
+
+ auto maxIter = std::max_element(timeHorizonVector.begin(), timeHorizonVector.end());
+
+ // Check if the vector is not empty
+ if (maxIter != timeHorizonVector.end())
+ {
+ return *maxIter;
+ }
+
+ return 0;
+}
+
+void OptimizationParameters::calculateDependantClusterData()
+{
+ for (auto& clusterEntry : clusterData)
+ {
+ auto& data = clusterEntry.second;
+ const auto& cluster = *(clusterEntry.first);
+
+ if (!data.maintenanceEnabled)
+ continue;
+ /*
+ calculateNumberOfMaintenances uses:
+ averageMaintenanceDuration
+ DaysSinceLastMaintenance
+ timeHorizon
+ averageDurationBetweenMaintenances - cluster.interPoPeriod - this is static always available
+ so all these parameters need to be calculated before calling this method
+ */
+
+ // static input - wont be changed - used to reset dynamicInputs.numberOfMaintenances
+ // after each scenario
+ data.staticInputs.numberOfMaintenancesFirstStep = calculateNumberOfMaintenances(cluster);
+ // dynamic inputs
+ data.dynamicInputs.numberOfMaintenances = data.staticInputs.numberOfMaintenancesFirstStep;
+ }
+ return;
+}
+
+void OptimizationParameters::setMaintenanceGroupParameters()
+{
+ // it is crucial that we allocateClusterData
+ // and calculate NonDependantClusterData
+ // since later methods expect this to be filled
+ // and values available
+ allocateClusterData();
+ calculateNonDependantClusterData();
+ //
+ calculateResidualLoad();
+ residualLoadDailyValues_ = calculateDailySums(maintenanceGroup_.getUsedResidualLoadTS());
+ std::tie(ensCost_, spillCost_) = calculateMaintenanceGroupENSandSpillageCost();
+ timeStep_ = calculateTimeStep();
+ timeHorizon_ = calculateTimeHorizon();
+ timeHorizonFirstStep_ = timeHorizon_;
+ // calculateDependantClusterData
+ // uses timeHorizon_ so it is important we calculate timeHorizon_ first
+ calculateDependantClusterData();
+}
+
+bool OptimizationParameters::checkMaintenanceGroupParameters()
+{
+ if (timeStep_ <= 0)
+ {
+ logs.warning() << "Maintenance group: " << maintenanceGroup_.name()
+ << ": The timeseries generation will be skiped: timeStep = 0. It is possible "
+ "that the maintenance group has no clusters designated for maintenance "
+ "planning, or at least one cluster has interPoPeriod = 0";
+ return false;
+ }
+ if (timeHorizon_ <= 0)
+ {
+ logs.warning() << "Maintenance group: " << maintenanceGroup_.name()
+ << ": The timeseries generation will be skiped: timeHorizon <= 0";
+ return false;
+ }
+ // add some more check here if necessary!
+ return true;
+}
+
+bool OptimizationParameters::checkTimeHorizon(OptProblemSettings& optSett)
+{
+ if (timeHorizon_ <= 0)
+ {
+ logs.warning() << "Maintenance group: " << maintenanceGroup_.name()
+ << ". Scenario Num: " << optSett.scenario
+ << ". Optimization stopped in step: " << optSett.firstDay << ".Day - "
+ << optSett.lastDay
+ << ".Day. TimeHorizon <= 0. This scenario wont have generated timeseries";
+ optSett.solved = false;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/parameters/CalculateParametersPerCluster.cpp b/src/solver/ts-generator/optimized-thermal-generator/parameters/CalculateParametersPerCluster.cpp
new file mode 100644
index 0000000000..cef1a807d8
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/parameters/CalculateParametersPerCluster.cpp
@@ -0,0 +1,71 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+int OptimizationParameters::calculateUnitEarliestStartOfFirstMaintenance(
+ const Data::ThermalCluster& cluster,
+ uint unitIndex)
+{
+ // earliest start of the first maintenance of unit u (beginning of the window, can be negative):
+ // let it return negative value - if it returns negative value we wont implement constraint:
+ // s[u][0][tauLower-1] = 0
+
+ return std::min(getAverageDurationBetweenMaintenances(cluster)
+ - getDaysSinceLastMaintenance(cluster, unitIndex) - cluster.poWindows,
+ timeHorizon_ - 1);
+}
+
+int OptimizationParameters::calculateUnitLatestStartOfFirstMaintenance(
+ const Data::ThermalCluster& cluster,
+ uint unitIndex)
+{
+ // latest start of the first maintenance of unit u, must be positive -
+ // FIRST STEP ONLY!
+
+ // cannot be negative: FIRST STEP ONLY
+ // cluster.interPoPeriod -
+ // cluster.originalRandomlyGeneratedDaysSinceLastMaintenance[unitIndex] - is always positive
+ // or zero
+ // cluster.poWindows is positive or zero
+ // however we will make sure it does not surpass timeHorizon_ - 1 value
+ // AFTER FIRST STEP it can go to negative value - so we will floor it to zero
+
+ return std::min(
+ std::max(0,
+ getAverageDurationBetweenMaintenances(cluster)
+ - getDaysSinceLastMaintenance(cluster, unitIndex) + cluster.poWindows),
+ timeHorizon_ - 1);
+}
+
+std::vector OptimizationParameters::calculateNumberOfMaintenances(
+ const Data::ThermalCluster& cluster)
+{
+ // getAverageMaintenanceDuration must be at least 1
+ // so we do not need to check if div / 0
+
+ std::vector numberOfMaintenances;
+ numberOfMaintenances.resize(cluster.unitCount);
+
+ if (!(cluster.doWeGenerateTS(globalThermalTSgeneration_) && cluster.optimizeMaintenance))
+ return numberOfMaintenances;
+
+ for (int unit = 0; unit != cluster.unitCount; ++unit)
+ {
+ int div = (timeHorizon_
+ + std::min(getAverageDurationBetweenMaintenances(cluster) - 1,
+ getDaysSinceLastMaintenance(cluster, unit))
+ - getAverageDurationBetweenMaintenances(cluster))
+ / (getAverageDurationBetweenMaintenances(cluster)
+ + getAverageMaintenanceDuration(cluster));
+ numberOfMaintenances[unit] = std::max(1 + div, minNumberOfMaintenances);
+ }
+
+ return numberOfMaintenances;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/parameters/Getters.cpp b/src/solver/ts-generator/optimized-thermal-generator/parameters/Getters.cpp
new file mode 100644
index 0000000000..99ccd2b0af
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/parameters/Getters.cpp
@@ -0,0 +1,74 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+// Getters
+double OptimizationParameters::getPowerCost(const Data::ThermalCluster& cluster,
+ int optimizationDay)
+{
+ /*
+ ** Unit cost can be directly set,
+ ** Or calculated using Fuel Cost, Co2 cost, Fuel Eff and V&O Cost.
+
+ ** In second case we also need information which year it is (to choose proper TS number, and
+ also what hour it is)
+ ** we need price per day (so averaging the hourly values)
+ ** this is NOT calculated prior to the simulation - so if we only want to run ts-gen, we cannot
+ get this info just yet
+ ** using:
+ ** getMarginalCost(uint serieIndex, uint hourInTheYear) or
+ ** getMarketBidCost(uint hourInTheYear, uint year)
+ ** TODO CR27: maybe for phase-II
+ ** for now just disable this option but take into account the thermalModulationCost!!
+ */
+
+ if (cluster.costgeneration == Data::useCostTimeseries)
+ {
+ logs.warning()
+ << "Cluster: " << cluster.getFullName()
+ << " has Cost generation set to: Use cost timeseries. Option not suported yet. "
+ "Cost set to zero.";
+ return 0.;
+ }
+
+ return clusterData[&cluster].staticInputs.avgCost[dayOfTheYear(optimizationDay)];
+}
+
+double OptimizationParameters::getPowerOutput(const Data::ThermalCluster& cluster,
+ int optimizationDay)
+{
+ return clusterData[&cluster].staticInputs.maxPower[dayOfTheYear(optimizationDay)];
+}
+
+double OptimizationParameters::getResidualLoad(int optimizationDay)
+{
+ return residualLoadDailyValues_[dayOfTheYear(optimizationDay)];
+}
+
+int OptimizationParameters::getNumberOfMaintenances(const Data::ThermalCluster& cluster, int unit)
+{
+ return clusterData[&cluster].dynamicInputs.numberOfMaintenances[unit];
+}
+
+int OptimizationParameters::getAverageMaintenanceDuration(const Data::ThermalCluster& cluster)
+{
+ return clusterData[&cluster].staticInputs.averageMaintenanceDuration;
+}
+
+int OptimizationParameters::getAverageDurationBetweenMaintenances(
+ const Data::ThermalCluster& cluster)
+{
+ return cluster.interPoPeriod;
+}
+
+int OptimizationParameters::getDaysSinceLastMaintenance(const Data::ThermalCluster& cluster,
+ int unit)
+{
+ return clusterData[&cluster].dynamicInputs.daysSinceLastMaintenance[unit];
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/parameters/OptimizationParameters.h b/src/solver/ts-generator/optimized-thermal-generator/parameters/OptimizationParameters.h
new file mode 100644
index 0000000000..257f43508d
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/parameters/OptimizationParameters.h
@@ -0,0 +1,172 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#pragma once
+
+#include "../../randomized-thermal-generator/RandomizedGenerator.h"
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+// it is better to immediately calculate and populate structure
+// that will store information about clusters
+// so inside optimization problem we just retrieve these data with getters
+// not re-calculate them over and over again
+
+// this structure stores cluster input data (optimization parameters)
+// that stays the same during optimization - static inputs
+// that are updated after each optimization - dynamic inputs
+// the structure also temporary stores scenario results
+// output/results for one scenario
+
+struct StaticInputs
+{
+ // static input data
+ // calculated once before all loops
+ std::array maxPower;
+ std::array avgCost;
+ std::vector numberOfMaintenancesFirstStep;
+ int averageMaintenanceDuration;
+
+ // for random generator
+ double AP[366];
+ double BP[366];
+};
+
+struct DynamicInputs
+{
+ // dynamic input data
+ // re-calculated after each optimization time-step
+ std::vector daysSinceLastMaintenance;
+ std::vector numberOfMaintenances;
+};
+
+struct DynamicResults
+{
+ // scenario results
+ // temporary store scenario results
+ // before writing them to output
+ // reset after each scenario
+ std::vector availableDailyPower;
+};
+
+struct ClusterData
+{
+ bool maintenanceEnabled;
+ StaticInputs staticInputs;
+ DynamicInputs dynamicInputs;
+ DynamicResults dynamicResults;
+};
+
+class OptimizationParameters : public GeneratorTempData
+{
+ using OptimizationResults = std::vector;
+
+public:
+ explicit OptimizationParameters(Data::Study& study,
+ Data::MaintenanceGroup& maintenanceGroup,
+ bool globalThermalTSgeneration,
+ OptimizationVariables& vars,
+ OptimizationResults& scenarioResults,
+ Solver::Progression::Task& progr,
+ Solver::IResultWriter& writer) :
+ GeneratorTempData(study, progr, writer),
+ maintenanceGroup_(maintenanceGroup),
+ globalThermalTSgeneration_(globalThermalTSgeneration),
+ modelingType_(study.parameters.renewableGeneration),
+ vars_(vars),
+ scenarioResults_(scenarioResults)
+ {
+ }
+
+private:
+ Data::MaintenanceGroup& maintenanceGroup_;
+ const Data::Parameters::RenewableGeneration& modelingType_;
+ const bool globalThermalTSgeneration_;
+ const OptimizationVariables& vars_;
+ OptimizationResults& scenarioResults_;
+
+public:
+ int scenarioLength_;
+ int timeHorizon_;
+ int timeHorizonFirstStep_;
+ int timeStep_;
+ double ensCost_;
+ double spillCost_;
+ std::array residualLoadDailyValues_;
+
+ std::map clusterData;
+
+ /* ===================CALCULATE-PARAMETERS-MAIN=================== */
+
+ void allocateClusterData();
+ void calculateNonDependantClusterData();
+ void calculateResidualLoad();
+ std::pair calculateMaintenanceGroupENSandSpillageCost();
+ int calculateTimeStep();
+ int calculateTimeHorizon();
+ void calculateDependantClusterData();
+ void setMaintenanceGroupParameters();
+ bool checkMaintenanceGroupParameters();
+
+ /* ===================CALCULATE-PARAMETERS-PER CLUSTER/UNIT=================== */
+
+ int calculateUnitEarliestStartOfFirstMaintenance(const Data::ThermalCluster& cluster,
+ uint unitIndex);
+ int calculateUnitLatestStartOfFirstMaintenance(const Data::ThermalCluster& cluster,
+ uint unitIndex);
+ std::vector calculateNumberOfMaintenances(const Data::ThermalCluster& cluster);
+
+ /* ===================GETTERS=================== */
+
+ double getPowerCost(const Data::ThermalCluster& cluster, int optimizationDay);
+ double getPowerOutput(const Data::ThermalCluster& cluster, int optimizationDay);
+ double getResidualLoad(int optimizationDay);
+ int getAverageMaintenanceDuration(const Data::ThermalCluster& cluster);
+ int getAverageDurationBetweenMaintenances(const Data::ThermalCluster& cluster);
+ int getNumberOfMaintenances(const Data::ThermalCluster& cluster, int unit);
+ int getDaysSinceLastMaintenance(const Data::ThermalCluster& cluster, int unit);
+
+ /* ===================POST-OPTIMIZATION=================== */
+
+ /* ===================AFTER-EACH-TIME-STEP=================== */
+
+ void postTimeStepOptimization(OptProblemSettings& optSett);
+ void appendTimeStepResults(const OptProblemSettings& optSett);
+ void reCalculateDaysSinceLastMnt(const OptProblemSettings& optSett);
+ void reCalculateDaysSinceLastMnt(const OptProblemSettings& optSett, const Unit& unit);
+ int reCalculateDaysSinceLastMnt(const OptProblemSettings& optSett,
+ const Unit& unit,
+ bool maintenanceHappened,
+ int lastMaintenanceStart,
+ int lastMaintenanceDuration);
+ void reCalculateTimeHorizon();
+ void reCalculateNumberOfMaintenances();
+ bool checkTimeHorizon(OptProblemSettings& optSett);
+
+ /* ===================AFTER-EACH-SCENARIO=================== */
+
+ void postScenarioOptimization(OptProblemSettings& optSett);
+ void calculateScenarioResults();
+ void saveScenarioResults(const OptProblemSettings& optSett);
+ void saveScenarioResults(int fromCol, int toCol, Data::ThermalCluster& cluster);
+ void resetResultStorage();
+ void reSetDaysSinceLastMnt();
+ void reSetTimeHorizon();
+ void reSetNumberOfMaintenances();
+
+ /* ===================END-POST-OPTIMIZATION=================== */
+
+ /* ===================PRINT-DEBUG-ONLY-TO-BE-REMOVED=================== */
+ void printAllVariables(MPSolver& solver);
+ void printObjectiveFunction(MPObjective* objective, MPSolver& solver);
+ void printConstraints(MPSolver& solver);
+ void printResults(OptProblemSettings& optSett, MPSolver& solver);
+ void printProblemVarAndResults(OptProblemSettings& optSett, MPSolver& solver);
+ void printMaintenances(OptProblemSettings& optSett);
+ void printAvailability(OptProblemSettings& optSett);
+};
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/postoptimization/PostScenarioOptimization.cpp b/src/solver/ts-generator/optimized-thermal-generator/postoptimization/PostScenarioOptimization.cpp
new file mode 100644
index 0000000000..83cfbd1275
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/postoptimization/PostScenarioOptimization.cpp
@@ -0,0 +1,177 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+void OptimizationParameters::postScenarioOptimization(OptProblemSettings& optSett)
+{
+ // do not save if optimization failed at some step
+ if (optSett.solved)
+ {
+ calculateScenarioResults();
+ saveScenarioResults(optSett);
+ }
+
+ resetResultStorage();
+ reSetDaysSinceLastMnt();
+ reSetTimeHorizon();
+ reSetNumberOfMaintenances();
+
+ return;
+}
+
+void OptimizationParameters::calculateScenarioResults()
+{
+ // for each unit we have now scenarioResults_
+ // which contains std::pairs of all [maintenanceStart, maintenanceDuration]
+ // lets transfer that into vectors of UNIT availability
+
+ // loop per units
+ for (auto& unit : scenarioResults_)
+ {
+ unit.calculateAvailableDailyPower(scenarioLength_);
+ }
+
+ // now lets get CLUSTER availability by summing up UNIT availability
+
+ // fill in with zeros
+ for (auto& cluster : clusterData)
+ {
+ cluster.second.dynamicResults.availableDailyPower.resize(scenarioLength_ * DAYS_PER_YEAR);
+ }
+
+ // add one by one unit availability
+ for (auto& unit : scenarioResults_)
+ {
+ auto& availableDailyPower
+ = clusterData[unit.parentCluster].dynamicResults.availableDailyPower;
+
+ std::transform(availableDailyPower.begin(),
+ availableDailyPower.end(),
+ unit.availableDailyPower.begin(),
+ availableDailyPower.begin(),
+ std::plus());
+ }
+
+ // do not convert to hourly values yet
+ // why waste memory - convert to hourly just before writing to ts
+ // and then delete - local variable
+
+ return;
+}
+
+void OptimizationParameters::saveScenarioResults(const OptProblemSettings& optSett)
+{
+ // loop through all clusters and write results
+ // for one scenario into designated columns
+
+ int colSaveFrom = optSett.scenario * scenarioLength_;
+ int colSaveTo = colSaveFrom + scenarioLength_;
+
+ // using on purpose this double loop
+ // because looping through clusterData we cannot change cluster
+ // const Data::ThermalCluster*
+ for (auto& entryWeightMap : maintenanceGroup_)
+ {
+ auto& area = *(entryWeightMap.first);
+ for (auto& clusterEntry : area.thermal.list.mapping)
+ {
+ auto& cluster = *(clusterEntry.second);
+ bool genTS = checkClusterExist(cluster)
+ && cluster.doWeGenerateTS(globalThermalTSgeneration_)
+ && cluster.optimizeMaintenance;
+ if (!genTS)
+ continue;
+
+ // write results
+ saveScenarioResults(colSaveFrom, colSaveTo, cluster);
+ }
+ }
+ return;
+}
+
+void OptimizationParameters::saveScenarioResults(int fromCol,
+ int toCol,
+ Data::ThermalCluster& cluster)
+{
+ // daily results are in clusterData.availableDailyPower
+ // convert to hourly values and store in cluster ts
+ // we assume that vector availableDailyPower has:
+ // scenarioLength_ * DAYS_PER_YEAR element
+ // that we need to store inside columns from-to
+
+ auto& availability = clusterData[&cluster].dynamicResults.availableDailyPower;
+ assert((toCol - fromCol) * DAYS_PER_YEAR == availability.size());
+
+ int vctCol = 0;
+ for (int col = fromCol; col < toCol; ++col)
+ {
+ for (int row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ cluster.series.timeSeries[col][row] = availability[vctCol * 365 + (int)(row / 24)];
+ }
+ vctCol++;
+ }
+}
+
+void OptimizationParameters::resetResultStorage()
+{
+ // clear units result structure
+ scenarioResults_.clear();
+ // clear cluster result structure
+ // do not clear whole clusterData
+ // we store input data here as well
+
+ for (auto& cluster : clusterData)
+ {
+ cluster.second.dynamicResults.availableDailyPower.clear();
+ }
+
+ return;
+}
+
+void OptimizationParameters::reSetDaysSinceLastMnt()
+{
+ // we are back in first step, but not first scenario
+ // we have messed up our values
+ // we need to reset
+
+ for (auto& cluster : clusterData)
+ {
+ cluster.second.dynamicInputs.daysSinceLastMaintenance
+ = cluster.first->originalRandomlyGeneratedDaysSinceLastMaintenance;
+ }
+
+ return;
+}
+
+void OptimizationParameters::reSetTimeHorizon()
+{
+ // we are back in first step, but not first scenario
+ // we have messed up our values
+ // we need to reset
+
+ timeHorizon_ = timeHorizonFirstStep_;
+ return;
+}
+
+void OptimizationParameters::reSetNumberOfMaintenances()
+{
+ // we are back in first step, but not first scenario
+ // we have messed up our values
+ // we need to reset
+
+ for (auto& cluster : clusterData)
+ {
+ cluster.second.dynamicInputs.numberOfMaintenances
+ = cluster.second.staticInputs.numberOfMaintenancesFirstStep;
+ }
+
+ return;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/postoptimization/PostTimeStepOptimization.cpp b/src/solver/ts-generator/optimized-thermal-generator/postoptimization/PostTimeStepOptimization.cpp
new file mode 100644
index 0000000000..ef15b50dd5
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/postoptimization/PostTimeStepOptimization.cpp
@@ -0,0 +1,154 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+void OptimizationParameters::postTimeStepOptimization(OptProblemSettings& optSett)
+{
+ appendTimeStepResults(optSett);
+ reCalculateDaysSinceLastMnt(optSett);
+ reCalculateTimeHorizon();
+ reCalculateNumberOfMaintenances();
+ return;
+}
+
+// save/append optimization results form range 0-timeStep
+void OptimizationParameters::appendTimeStepResults(const OptProblemSettings& optSett)
+{
+ // we have vectors of start (zeros and ones)
+ // lets convert that into maintenance start day vector
+ // and then randomly generate maintenance duration
+ // and create std::pairs - of start_day + mnt_duration
+
+ // loop per units
+ for (std::size_t unitIndexTotal = 0; unitIndexTotal < vars_.clusterUnits.size();
+ ++unitIndexTotal)
+ {
+ // Unit-unitIndexTotal - is index in a vector of all the units (area * cluster * units)
+ // not to be confused by Unit-index - index in cluster
+
+ // variables structure: vars and result structure: scenarioResults
+ // are created at the same time in the same loop
+ // so the order of the units should be the same !!!
+ // so lets avoid creating some search/find method
+ // that will find the Unit in scenarioResults according to its parentCluster and index
+ // and just loop
+ // assert parentCluster and index
+
+ const auto& readResultUnit = vars_.clusterUnits[unitIndexTotal];
+ auto& storeResultUnit = scenarioResults_[unitIndexTotal];
+
+ assert(readResultUnit.parentCluster == storeResultUnit.parentCluster
+ && "Read and Store Units do not point to the same parent cluster.");
+ assert(readResultUnit.index == storeResultUnit.index
+ && "Read and Store Units do not point to the same unit index.");
+
+ auto& cluster = *(readResultUnit.parentCluster);
+
+ // if createStartEndVariables for the readResultUnit is false
+ // maintenances.size() is going to be zero - so in a way there is our check
+ if (readResultUnit.maintenances.empty())
+ continue;
+
+ // NO need to loop through maintenances
+ // only one maintenance can happen in the timeStep_
+ // TODO CR27: in phase-II we may change this and looping will be necessary
+ {
+ int localMaintenanceStart = readResultUnit.maintenances[0].startDay(timeStep_);
+ if (localMaintenanceStart == -1)
+ continue;
+
+ int globalMaintenanceStart = localMaintenanceStart + optSett.firstDay;
+ int dayInTheYearStart = dayOfTheYear(globalMaintenanceStart);
+
+ int PODOfTheDay
+ = (int)cluster.prepro->data[Data::PreproThermal::poDuration][dayInTheYearStart];
+ double app = clusterData[&cluster].staticInputs.AP[dayInTheYearStart];
+ double bpp = clusterData[&cluster].staticInputs.BP[dayInTheYearStart];
+ int maintenanceDuration = durationGenerator(
+ cluster.plannedLaw, PODOfTheDay, cluster.plannedVolatility, app, bpp);
+
+ storeResultUnit.maintenanceResults.push_back(
+ std::make_pair(globalMaintenanceStart, maintenanceDuration));
+ }
+ }
+
+ return;
+}
+
+// re-calculate parameters
+
+void OptimizationParameters::reCalculateDaysSinceLastMnt(const OptProblemSettings& optSett)
+{
+ // re-calculate days since last maintenance inputs if necessary
+ for (const auto& unit : scenarioResults_)
+ {
+ reCalculateDaysSinceLastMnt(optSett, unit);
+ }
+}
+
+void OptimizationParameters::reCalculateDaysSinceLastMnt(const OptProblemSettings& optSett,
+ const Unit& unit)
+{
+ if (!unit.createStartEndVariables)
+ return;
+
+ auto& daysSinceLastMaintenance
+ = clusterData[unit.parentCluster].dynamicInputs.daysSinceLastMaintenance[unit.index];
+ bool maintenanceHappened = false;
+
+ if (unit.maintenanceResults.empty())
+ {
+ daysSinceLastMaintenance
+ = reCalculateDaysSinceLastMnt(optSett, unit, maintenanceHappened, 0, 0);
+ return;
+ }
+
+ maintenanceHappened = true;
+ int lastMaintenanceStart = unit.maintenanceResults.back().first;
+ int lastMaintenanceDuration = unit.maintenanceResults.back().second;
+
+ daysSinceLastMaintenance = reCalculateDaysSinceLastMnt(
+ optSett, unit, maintenanceHappened, lastMaintenanceStart, lastMaintenanceDuration);
+ return;
+}
+
+int OptimizationParameters::reCalculateDaysSinceLastMnt(const OptProblemSettings& optSett,
+ const Unit& unit,
+ bool maintenanceHappened,
+ int lastMaintenanceStart,
+ int lastMaintenanceDuration)
+{
+ int nextOptimizationFirstDay = optSett.firstDay + timeStep_;
+ if (maintenanceHappened)
+ return std::max(
+ 0, nextOptimizationFirstDay - (lastMaintenanceStart + lastMaintenanceDuration));
+ // we let this go into negative value
+ // it will only move the maintenance in the next optimization
+ // further away from start
+ // TODO CR27: no we don't! It broke the solver - we keep std::max for now!
+ else
+ return nextOptimizationFirstDay
+ + unit.parentCluster->originalRandomlyGeneratedDaysSinceLastMaintenance[unit.index];
+}
+
+void OptimizationParameters::reCalculateTimeHorizon()
+{
+ timeHorizon_ = calculateTimeHorizon();
+}
+
+void OptimizationParameters::reCalculateNumberOfMaintenances()
+{
+ // re-calculate days since last maintenance inputs if necessary
+ for (auto& cluster : clusterData)
+ {
+ cluster.second.dynamicInputs.numberOfMaintenances
+ = calculateNumberOfMaintenances(*(cluster.first));
+ }
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/postoptimization/WriteResults.cpp b/src/solver/ts-generator/optimized-thermal-generator/postoptimization/WriteResults.cpp
new file mode 100644
index 0000000000..19735fb40e
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/postoptimization/WriteResults.cpp
@@ -0,0 +1,45 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+// this method is called at the very end
+// after all time-steps and scenarios
+void OptimizedThermalGenerator::writeTsResults()
+{
+ // we need to loop through all the clusters
+ // and write the results
+ // it would be much easier to loop using clusterData
+ // inside of it we already excluded all the non-important clusters
+ // however it is const Data::ThermalCluster*
+ // so we cannot modify cluster values
+
+ for (auto& entryWeightMap : maintenanceGroup_)
+ {
+ auto& area = *(entryWeightMap.first);
+
+ for (auto& clusterEntry : area.thermal.list.mapping)
+ {
+ auto& cluster = *(clusterEntry.second);
+
+ if (!(checkClusterExist(cluster) && cluster.doWeGenerateTS(globalThermalTSgeneration_)
+ && cluster.optimizeMaintenance))
+ continue;
+
+ if (derated)
+ cluster.series.timeSeries.averageTimeseries();
+
+ if (archive)
+ writeResultsToDisk(area, cluster);
+
+ cluster.calculationOfSpinning();
+ }
+ }
+ return;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/support/SupportFunctions.cpp b/src/solver/ts-generator/optimized-thermal-generator/support/SupportFunctions.cpp
new file mode 100644
index 0000000000..eadb9a5dee
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/support/SupportFunctions.cpp
@@ -0,0 +1,199 @@
+//
+// Created by milos on 14/11/23.
+//
+#include
+#include
+#include
+#include "../main/OptimizedGenerator.h"
+#include "../support/SupportFunctions.h"
+
+namespace Antares::TSGenerator
+{
+
+// calculate Average time-series functions
+std::array calculateAverageLoadTs(const Data::Area& area)
+{
+ // we assume ready-make TS - (pre-check exist for this!)
+ const auto tsValues = area.load.series.timeSeries;
+ const auto tsNumbers = area.load.series.timeseriesNumbers;
+ return calculateAverageTs(tsValues, tsNumbers);
+}
+
+std::array calculateAverageRorTs(const Data::Area& area)
+{
+ const auto tsValues = area.hydro.series->ror.timeSeries;
+ auto tsNumbers = area.hydro.series->ror.timeseriesNumbers;
+ return calculateAverageTs(tsValues, tsNumbers);
+}
+
+std::array calculateAverageRenewableTsAggregated(const Data::Area& area)
+{
+ std::array averageTsSolar
+ = calculateAverageTs(area.solar.series.timeSeries, area.solar.series.timeseriesNumbers);
+ std::array averageTsWind
+ = calculateAverageTs(area.wind.series.timeSeries, area.wind.series.timeseriesNumbers);
+
+ std::array averageTsRenewable = {};
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ averageTsRenewable[row] = (averageTsSolar[row] + averageTsWind[row]);
+ }
+ return averageTsRenewable;
+}
+
+std::array calculateAverageRenewableTsClusters(const Data::Area& area)
+{
+ std::array averageTsRenewable = {};
+ for (const auto& entryCluster : area.renewable.list)
+ {
+ auto& cluster = *entryCluster;
+ // this is not even necessary - because area.renewable.clusters returns list of only
+ // ENABLED clusters but let's keep it for now
+ if (!cluster.enabled)
+ continue;
+ auto tmpArrayPerCluster
+ = calculateAverageTs(cluster.series.timeSeries, cluster.series.timeseriesNumbers);
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ if (cluster.tsMode == Data::RenewableCluster::productionFactor)
+ averageTsRenewable[row]
+ += tmpArrayPerCluster[row] * cluster.unitCount * cluster.nominalCapacity;
+ else
+ averageTsRenewable[row] += tmpArrayPerCluster[row];
+ }
+ }
+ return averageTsRenewable;
+}
+
+std::array calculateAverageRenewableTs(
+ const Data::Parameters::RenewableGeneration modelingType,
+ const Data::Area& area)
+{
+ if (modelingType.isAggregated())
+ {
+ return calculateAverageRenewableTsAggregated(area);
+ }
+ else // clusters it is
+ {
+ return calculateAverageRenewableTsClusters(area);
+ }
+}
+
+// support functions - for parameter calculations
+std::array calculateDailySums(
+ const std::array& hourlyValues)
+{
+ std::array dailyValues;
+ auto hours_iter = hourlyValues.begin();
+
+ for (double& day_sum : dailyValues)
+ {
+ day_sum = std::accumulate(hours_iter, hours_iter + 24, 0.0);
+ hours_iter += 24;
+ }
+
+ return dailyValues;
+}
+
+std::array calculateAverageTs(const Matrix& tsValue,
+ const Matrix& tsNumbers)
+{
+ // define array
+ std::array averageTs = {};
+ // calculate sum
+ for (std::size_t year = 0; year < tsNumbers.height; ++year)
+ {
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ averageTs[row] += tsValue[tsNumbers[0][year]][row];
+ }
+ }
+ // calculate mean
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ averageTs[row] = averageTs[row] / tsNumbers.height;
+ }
+ // return
+ return averageTs;
+}
+
+bool checkClusterExist(const Data::ThermalCluster& cluster)
+{
+ if (!cluster.prepro)
+ {
+ logs.error() << "Cluster: " << cluster.getFullName()
+ << ": The timeseries will not be regenerated. All data "
+ "related to the ts-generator for "
+ << "'thermal' have been released.";
+ return false;
+ }
+
+ if (0 == cluster.unitCount || 0 == cluster.nominalCapacity)
+ {
+ return false;
+ }
+ return true;
+}
+
+int dayOfTheYear(int optimizationDay)
+{
+ return optimizationDay % DAYS_PER_YEAR;
+}
+
+// calculate parameters methods - per cluster
+int calculateAverageMaintenanceDuration(const Data::ThermalCluster& cluster)
+{
+ double sum = 0.0;
+ for (std::size_t row = 0; row < DAYS_PER_YEAR; ++row)
+ {
+ sum += cluster.prepro->data[Data::PreproThermal::poDuration][row];
+ }
+ // poDuration in Antares cannot be below 1.0
+ // so it is redundant to check here if return value is above 1.0
+ // that is why I did not use std::max()
+ return sum / static_cast(DAYS_PER_YEAR);
+}
+
+std::array calculateMaxUnitOutput(const Data::ThermalCluster& cluster)
+{
+ std::array maxOutputDailyValues = {};
+ std::array maxOutputHourlyValues = {};
+
+ // transfer to array
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ maxOutputHourlyValues[row]
+ = cluster.modulation[Data::ThermalModulation::thermalModulationCapacity][row];
+ }
+
+ maxOutputDailyValues = calculateDailySums(maxOutputHourlyValues);
+ // multiply by per unit power (nominal capacity)
+ for (double& num : maxOutputDailyValues)
+ {
+ num *= cluster.nominalCapacity;
+ }
+ return maxOutputDailyValues;
+}
+
+std::array calculateAvrUnitDailyCost(const Data::ThermalCluster& cluster)
+{
+ std::array avrCostDailyValues = {};
+ std::array costHourlyValues = {};
+
+ // transfer to array
+ for (std::size_t row = 0; row < HOURS_PER_YEAR; ++row)
+ {
+ costHourlyValues[row]
+ = cluster.modulation[Data::ThermalModulation::thermalModulationMarketBid][row];
+ }
+
+ avrCostDailyValues = calculateDailySums(costHourlyValues);
+ // multiply by per unit/cluster market bid cost + average this on 24 hours
+ for (double& num : avrCostDailyValues)
+ {
+ num *= cluster.marketBidCost / 24.0;
+ }
+ return avrCostDailyValues;
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/support/SupportFunctions.h b/src/solver/ts-generator/optimized-thermal-generator/support/SupportFunctions.h
new file mode 100644
index 0000000000..ccf618e335
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/support/SupportFunctions.h
@@ -0,0 +1,39 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#pragma once
+
+#include "../main/OptimizedGenerator.h"
+
+namespace Antares::TSGenerator
+{
+
+// support functions
+std::array calculateDailySums(
+ const std::array& hourlyValues);
+std::array calculateAverageTs(const Matrix& tsValue,
+ const Matrix& tsNumbers);
+bool checkClusterExist(const Data::ThermalCluster& cluster);
+int dayOfTheYear(int optimizationDay);
+
+// calculate Average time-series functions
+std::array calculateAverageLoadTs(const Data::Area& area);
+std::array calculateAverageRorTs(const Data::Area& area);
+std::array calculateAverageRenewableTs(
+ const Data::Parameters::RenewableGeneration modelingType,
+ const Data::Area& area);
+std::array calculateAverageRenewableTsAggregated(const Data::Area& area);
+std::array calculateAverageRenewableTsClusters(const Data::Area& area);
+
+// calculate parameters functions - per cluster
+int calculateAverageMaintenanceDuration(const Data::ThermalCluster& cluster);
+std::array calculateMaxUnitOutput(const Data::ThermalCluster& cluster);
+std::array calculateAvrUnitDailyCost(const Data::ThermalCluster& cluster);
+
+// Declare the auxiliary function outside the class
+// Debug & Test purpose - to be removed
+template
+void printColumnToFile(const std::vector>& data, const std::string& filename);
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/support/SupportStructures.cpp b/src/solver/ts-generator/optimized-thermal-generator/support/SupportStructures.cpp
new file mode 100644
index 0000000000..e49e11760f
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/support/SupportStructures.cpp
@@ -0,0 +1,57 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#include "SupportStructures.h"
+
+namespace Antares::TSGenerator
+{
+// post-time step optimization - functions
+int Maintenances::startDay(int limit) const
+{
+ // this functions finds first 1 in a vector and returns its index
+ // if no 1, return -1
+ // because 0 is a valid output - maintenance starts on first day
+
+ for (int day = 0; day < start.size(); ++day)
+ {
+ int value = static_cast(start[day]->solution_value());
+ if (value == 1)
+ {
+ if (day < limit)
+ {
+ return day;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ return -1;
+}
+
+void Unit::calculateAvailableDailyPower(int tsCount)
+{
+ int totalDays = tsCount * DAYS_PER_YEAR;
+ double maxPower = parentCluster->nominalCapacity;
+ availableDailyPower.resize(totalDays);
+ std::fill(availableDailyPower.begin(), availableDailyPower.end(), maxPower);
+
+ for (const auto& maintenance : maintenanceResults)
+ {
+ int start = maintenance.first;
+ int duration = maintenance.second;
+
+ int end = start + duration;
+ if (end > totalDays)
+ end = totalDays;
+
+ for (int day = start; day < end; ++day)
+ {
+ availableDailyPower[day] = 0.0;
+ }
+ }
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/support/SupportStructures.h b/src/solver/ts-generator/optimized-thermal-generator/support/SupportStructures.h
new file mode 100644
index 0000000000..7f9674f38a
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/support/SupportStructures.h
@@ -0,0 +1,84 @@
+//
+// Created by milos on 14/11/23.
+//
+
+#pragma once
+
+#include "ortools/linear_solver/linear_solver.h"
+#include "../../../libs/antares/study/area/area.h"
+
+using namespace operations_research;
+
+namespace Antares::TSGenerator
+{
+
+// this class stores data about optimization problem settings
+class OptProblemSettings final
+{
+public:
+ OptProblemSettings() = default;
+
+ int firstDay;
+ int lastDay;
+ int scenario;
+
+ bool solved = true;
+ bool isFirstStep = true;
+};
+
+struct Maintenances
+{
+ // number of elements in the vector is number of days in optimization problem
+ std::vector start; // pointer to s[u][m][t] variables
+ std::vector end; // pointer to e[u][m][t] variables
+
+ // methods
+ int startDay(int limit) const;
+};
+
+struct Unit
+{
+ // inputs
+ const Data::ThermalCluster* parentCluster;
+ int index;
+ bool createStartEndVariables;
+
+ // solver variables
+
+ // number of elements in the vector is number of days in optimization problem
+ std::vector P; // pointers to P[u][t] variables
+
+ // number of elements in the vector is total maintenances of unit
+ std::vector maintenances;
+
+ // results
+ // we will store results in the same structure
+ // however we need separate instance of this struct
+ // since the object that stores optimization variables is cleared after each optimization
+ // and the results need to be saved/appended for all optimizations (time steps)
+
+ // number of elements in the vector is TOTAL number of maintenances
+ // all the maintenances after all the time steps - not just one optimization
+ // first element of the pair is start of the maintenance
+ // second element of the pair is randomly drawn maintenance duration
+ std::vector> maintenanceResults;
+ // after all the maintenances are determined
+ // we calculate UNIT availability - we do this once after all optimizations
+ std::vector availableDailyPower;
+
+ // methods
+ void calculateAvailableDailyPower(int tsCount);
+};
+
+// this structure stores the data about optimization problem variables and results
+struct OptimizationVariables
+{
+ // number of elements in the vector is number units (areas*cluster*units)
+ std::vector clusterUnits;
+
+ // number of elements in the vector is number of days in optimization problem
+ std::vector ens; // pointers to Ens[t] variables
+ std::vector spill; // pointers to Spill[t] variables
+};
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/optimized-thermal-generator/support/pre-scenario-builder.cpp b/src/solver/ts-generator/optimized-thermal-generator/support/pre-scenario-builder.cpp
new file mode 100644
index 0000000000..f1f1cdef4d
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/support/pre-scenario-builder.cpp
@@ -0,0 +1,31 @@
+/*
+** created by milos 13/11/2023
+*/
+
+#include
+#include "pre-scenario-builder.h"
+#include "../../../simulation/apply-scenario.h"
+#include "../../../simulation/timeseries-numbers.h"
+
+namespace Antares::Solver
+{
+void ApplyScenarioBuilderDueToMaintenancePlanning(Data::Study& study)
+{
+ if (!study.parameters.maintenancePlanning.isOptimized())
+ return;
+
+ if (!(study.parameters.timeSeriesToGenerate & Antares::Data::timeSeriesThermal))
+ return;
+
+ study.resizeAllTimeseriesNumbers(1 + study.runtime->rangeLimits.year[Data::rangeEnd]);
+
+ if (!TimeSeriesNumbers::Generate(study))
+ {
+ throw FatalError("An unrecoverable error has occurred. Can not continue.");
+ }
+
+ if (study.parameters.useCustomScenario)
+ ApplyCustomScenario(study);
+}
+
+} // namespace Antares::Solver
diff --git a/src/solver/ts-generator/optimized-thermal-generator/support/pre-scenario-builder.h b/src/solver/ts-generator/optimized-thermal-generator/support/pre-scenario-builder.h
new file mode 100644
index 0000000000..388c49b0d6
--- /dev/null
+++ b/src/solver/ts-generator/optimized-thermal-generator/support/pre-scenario-builder.h
@@ -0,0 +1,11 @@
+/*
+** created by milos 13/11/2023
+*/
+#pragma once
+
+#include
+
+namespace Antares::Solver
+{
+void ApplyScenarioBuilderDueToMaintenancePlanning(Data::Study& study);
+} // namespace Antares::Solver
diff --git a/src/solver/ts-generator/randomized-thermal-generator/RandomizedGenerator.cpp b/src/solver/ts-generator/randomized-thermal-generator/RandomizedGenerator.cpp
new file mode 100644
index 0000000000..fadbb1c3f4
--- /dev/null
+++ b/src/solver/ts-generator/randomized-thermal-generator/RandomizedGenerator.cpp
@@ -0,0 +1,505 @@
+// //
+// // Created by milos on 10/11/23.
+// //
+
+#include "../../simulation/sim_extern_variables_globales.h"
+#include "RandomizedGenerator.h"
+
+using namespace Yuni;
+
+namespace Antares::TSGenerator
+{
+
+GeneratorTempData::GeneratorTempData(Data::Study& study,
+ Solver::Progression::Task& progr,
+ Solver::IResultWriter& writer) :
+ study(study),
+ rndgenerator(study.runtime->random[Data::seedTsGenThermal]),
+ pProgression(progr),
+ pWriter(writer)
+{
+ auto& parameters = study.parameters;
+
+ archive = (0 != (parameters.timeSeriesToArchive & Data::timeSeriesThermal));
+
+ nbThermalTimeseries = parameters.nbTimeSeriesThermal;
+
+ derated = parameters.derated;
+
+ FPOW.resize(DAYS_PER_YEAR);
+ PPOW.resize(DAYS_PER_YEAR);
+}
+
+void GeneratorTempData::writeResultsToDisk(const Data::Area& area,
+ const Data::ThermalCluster& cluster)
+{
+ if (not study.parameters.noOutput)
+ {
+ pTempFilename.reserve(study.folderOutput.size() + 256);
+
+ pTempFilename.clear() << "ts-generator" << SEP << "thermal" << SEP << "mc-" << currentYear
+ << SEP << area.id << SEP << cluster.id() << ".txt";
+
+ enum
+ {
+ precision = 0
+ };
+
+ std::string buffer;
+ cluster.series.timeSeries.saveToBuffer(buffer, precision);
+
+ pWriter.addEntryFromBuffer(pTempFilename.c_str(), buffer);
+ }
+
+ ++pProgression;
+}
+
+template
+void GeneratorTempData::prepareIndispoFromLaw(Data::ThermalLaw law,
+ double volatility,
+ double A[],
+ double B[],
+ const T& duration)
+{
+ switch (law)
+ {
+ case Data::thermalLawUniform:
+ {
+ for (uint d = 0; d < daysPerYear; ++d)
+ {
+ double D = (double)duration[d];
+ double xtemp = volatility * (D - 1.);
+ A[d] = D - xtemp;
+ B[d] = 2. * xtemp + 1.;
+ }
+ break;
+ }
+ case Data::thermalLawGeometric:
+ {
+ for (uint d = 0; d < daysPerYear; ++d)
+ {
+ double D = (double)duration[d];
+ double xtemp = volatility * volatility * D * (D - 1.);
+ if (xtemp != 0)
+ {
+ B[d] = 4. * xtemp + 1.;
+ B[d] = sqrt(B[d]) - 1.;
+ B[d] /= 2. * xtemp;
+ A[d] = D - 1. / B[d];
+ B[d] = log(1. - B[d]);
+ B[d] = 1. / B[d];
+ }
+ else
+ {
+ B[d] = 1.;
+ A[d] = 1.;
+ }
+ }
+ break;
+ }
+ }
+}
+
+int GeneratorTempData::durationGenerator(Data::ThermalLaw law,
+ int expec,
+ double volat,
+ double a,
+ double b)
+{
+ if (volat == 0 or expec == 1)
+ return expec;
+
+ double rndnumber = rndgenerator.next();
+ switch (law)
+ {
+ case Data::thermalLawUniform:
+ {
+ return (int(a + rndnumber * b));
+ }
+ case Data::thermalLawGeometric:
+ {
+ int resultat = (1 + int(a + (b)*log(rndnumber)));
+ enum
+ {
+ limit = Log_size / 2 - 1
+ };
+ assert(limit == 1999);
+ return (resultat <= limit) ? resultat : limit;
+ }
+ }
+ assert(false and "return is missing");
+ return 0;
+}
+
+void GeneratorTempData::operator()(Data::Area& area, Data::ThermalCluster& cluster)
+{
+ if (not cluster.prepro)
+ {
+ logs.error()
+ << "Cluster: " << area.name << '/' << cluster.name()
+ << ": The timeseries will not be regenerated. All data related to the ts-generator for "
+ << "'thermal' have been released.";
+ return;
+ }
+
+ assert(cluster.prepro);
+
+ if (0 == cluster.unitCount or 0 == cluster.nominalCapacity)
+ {
+ if (archive)
+ writeResultsToDisk(area, cluster);
+ return;
+ }
+
+ const auto& preproData = *(cluster.prepro);
+
+ int AUN = (int)cluster.unitCount;
+
+ auto& FOD = preproData.data[Data::PreproThermal::foDuration];
+
+ auto& POD = preproData.data[Data::PreproThermal::poDuration];
+
+ auto& FOR = preproData.data[Data::PreproThermal::foRate];
+
+ auto& POR = preproData.data[Data::PreproThermal::poRate];
+
+ auto& NPOmin = preproData.data[Data::PreproThermal::npoMin];
+
+ auto& NPOmax = preproData.data[Data::PreproThermal::npoMax];
+
+ double f_volatility = cluster.forcedVolatility;
+
+ double p_volatility = cluster.plannedVolatility;
+
+ auto f_law = cluster.forcedLaw;
+
+ auto p_law = cluster.plannedLaw;
+
+ int FODOfTheDay;
+ int PODOfTheDay;
+
+ int FOD_reel = 0;
+ int POD_reel = 0;
+
+ for (uint d = 0; d < daysPerYear; ++d)
+ {
+ FPOW[d].resize(cluster.unitCount + 1);
+ PPOW[d].resize(cluster.unitCount + 1);
+
+ PODOfTheDay = (int)POD[d];
+ FODOfTheDay = (int)FOD[d];
+
+ lf[d] = FOR[d] / (FOR[d] + (FODOfTheDay) * (1. - FOR[d]));
+ lp[d] = POR[d] / (POR[d] + (PODOfTheDay) * (1. - POR[d]));
+
+ if (0. < lf[d] and lf[d] < lp[d])
+ lf[d] *= (1. - lp[d]) / (1. - lf[d]);
+
+ if (0. < lp[d] and lp[d] < lf[d])
+ lp[d] *= (1. - lf[d]) / (1. - lp[d]);
+
+ double a = 0.;
+ double b = 0.;
+
+ if (lf[d] <= FAILURE_RATE_EQ_1)
+ {
+ a = 1. - lf[d];
+ ff[d] = lf[d] / a;
+ }
+
+ if (lp[d] <= FAILURE_RATE_EQ_1)
+ {
+ b = 1. - lp[d];
+ pp[d] = lp[d] / b;
+ }
+
+ for (uint k = 0; k != cluster.unitCount + 1; ++k)
+ {
+ FPOW[d][k] = pow(a, (double)k);
+ PPOW[d][k] = pow(b, (double)k);
+ }
+ }
+
+ prepareIndispoFromLaw(f_law, f_volatility, af, bf, FOD);
+ prepareIndispoFromLaw(p_law, p_volatility, ap, bp, POD);
+
+ (void)::memset(AVP, 0, sizeof(AVP));
+ (void)::memset(LOG, 0, sizeof(LOG));
+ (void)::memset(LOGP, 0, sizeof(LOGP));
+
+ int MXO = 0;
+
+ int PPO = 0;
+
+ int PFO = 0;
+
+ int NOW = 0;
+
+ int FUT = 0;
+
+ int NPO_cur = 0;
+
+ int stock = 0;
+
+ double A = 0;
+ double cumul = 0;
+ double last = 0;
+
+ auto& modulation = cluster.modulation[Data::thermalModulationCapacity];
+
+ double* dstSeries = nullptr;
+
+ const uint tsCount = nbThermalTimeseries + 2;
+ for (uint tsIndex = 0; tsIndex != tsCount; ++tsIndex)
+ {
+ uint hour = 0;
+
+ if (tsIndex > 1)
+ dstSeries = cluster.series.timeSeries[tsIndex - 2];
+
+ for (uint dayInTheYear = 0; dayInTheYear < daysPerYear; ++dayInTheYear)
+ {
+ assert(dayInTheYear < 366);
+ assert(not(lf[dayInTheYear] < 0.));
+ assert(not(lp[dayInTheYear] < 0.));
+
+ PODOfTheDay = (int)POD[dayInTheYear];
+ FODOfTheDay = (int)FOD[dayInTheYear];
+
+ assert(NOW < Log_size);
+ NPO_cur -= LOGP[NOW];
+ LOGP[NOW] = 0;
+ AUN += LOG[NOW];
+ LOG[NOW] = 0;
+
+ if (NPO_cur > NPOmax[dayInTheYear])
+ {
+ int cible_retour = NPO_cur - (int)NPOmax[dayInTheYear];
+
+ int cumul_retour = 0;
+
+ for (int index = 1; index < Log_size; ++index)
+ {
+ if (cumul_retour == cible_retour)
+ break;
+
+ if (LOGP[(NOW + index) % Log_size] + cumul_retour >= cible_retour)
+ {
+ LOGP[(NOW + index) % Log_size] -= (cible_retour - cumul_retour);
+
+ LOG[(NOW + index) % Log_size] -= (cible_retour - cumul_retour);
+
+ cumul_retour = cible_retour;
+ }
+ else
+ {
+ if (LOGP[(NOW + index) % Log_size] > 0)
+ {
+ cumul_retour += LOGP[(NOW + index) % Log_size];
+
+ LOG[(NOW + index) % Log_size] -= LOGP[(NOW + index) % Log_size];
+
+ LOGP[(NOW + index) % Log_size] = 0;
+ }
+ }
+ }
+
+ AUN += cible_retour;
+ NPO_cur = (int)NPOmax[dayInTheYear];
+ }
+
+ int FOC = 0;
+
+ int POC = 0;
+
+ if (lf[dayInTheYear] > 0. and lf[dayInTheYear] <= FAILURE_RATE_EQ_1)
+ {
+ A = rndgenerator.next();
+ last = FPOW[dayInTheYear][AUN];
+
+ if (A > last)
+ {
+ cumul = last;
+ for (int d = 1; d < AUN + 1; ++d)
+ {
+ last = ((last * ff[dayInTheYear]) * ((double)(AUN + 1. - d))) / (double)d;
+ cumul += last;
+ FOC = d;
+ if (A <= cumul)
+ break;
+ }
+ }
+ }
+ else
+ {
+ FOC = (lf[dayInTheYear] > 0.) ? AUN : 0;
+ }
+
+ if (lp[dayInTheYear] > 0. and lp[dayInTheYear] <= FAILURE_RATE_EQ_1)
+ {
+ int AUN_app = AUN;
+ if (stock >= 0 and stock <= AUN)
+ AUN_app -= stock;
+ if (stock > AUN)
+ AUN_app = 0;
+
+ last = PPOW[dayInTheYear][AUN_app];
+ A = rndgenerator.next();
+
+ if (A > last)
+ {
+ cumul = last;
+ for (int d = 1; d < AUN_app + 1; ++d)
+ {
+ last
+ = ((last * pp[dayInTheYear]) * ((double)(AUN_app + 1. - d))) / (double)d;
+ cumul += last;
+ POC = d;
+ if (A <= cumul)
+ break;
+ }
+ }
+ }
+ else
+ {
+ POC = (lp[dayInTheYear] > 0.) ? AUN : 0;
+ }
+
+ int candidat = POC + stock;
+ if (0 <= candidat and candidat <= AUN)
+ {
+ POC = candidat;
+
+ stock = 0;
+ }
+ if (candidat > AUN)
+ {
+ stock = candidat - AUN;
+
+ POC = AUN;
+ }
+ if (candidat < 0)
+ {
+ stock = candidat;
+
+ POC = 0;
+ }
+
+ if (POC + NPO_cur > NPOmax[dayInTheYear])
+ {
+ stock += POC + NPO_cur - (int)NPOmax[dayInTheYear];
+
+ POC = (int)NPOmax[dayInTheYear] - NPO_cur;
+
+ NPO_cur = (int)NPOmax[dayInTheYear];
+ }
+ else
+ {
+ if (POC + NPO_cur < NPOmin[dayInTheYear])
+ {
+ if (NPOmin[dayInTheYear] - NPO_cur > AUN)
+ {
+ stock -= (AUN - POC);
+
+ POC = AUN;
+ NPO_cur += POC;
+ }
+ else
+ {
+ stock -= (int)NPOmin[dayInTheYear] - (POC + NPO_cur);
+
+ POC = (int)NPOmin[dayInTheYear] - NPO_cur;
+
+ NPO_cur = (int)NPOmin[dayInTheYear];
+ }
+ }
+ else
+ {
+ NPO_cur += POC;
+ }
+ }
+
+ if (cluster.unitCount == 1)
+ {
+ if (POC == 1 and FOC == 1)
+ {
+ PPO = 0;
+ PFO = 0;
+ MXO = 1;
+ }
+ else
+ {
+ MXO = 0;
+ PFO = FOC;
+ PPO = POC;
+ }
+ }
+ else
+ {
+ if (AUN != 0)
+ {
+ MXO = POC * FOC / AUN;
+ PPO = POC - MXO;
+ PFO = FOC - MXO;
+ }
+ else
+ {
+ MXO = 0;
+ PPO = 0;
+ PFO = 0;
+ }
+ }
+
+ AUN = AUN - (PPO + PFO + MXO);
+
+ if (PFO != 0 or MXO != 0)
+ FOD_reel = durationGenerator(
+ f_law, FODOfTheDay, f_volatility, af[dayInTheYear], bf[dayInTheYear]);
+ if (PPO != 0 or MXO != 0)
+ POD_reel = durationGenerator(
+ p_law, PODOfTheDay, p_volatility, ap[dayInTheYear], bp[dayInTheYear]);
+
+ assert(FUT < Log_size);
+ if (PFO != 0)
+ {
+ FUT = (NOW + FOD_reel) % Log_size;
+ LOG[FUT] += PFO;
+ }
+ if (PPO != 0)
+ {
+ FUT = (NOW + POD_reel) % Log_size;
+ LOG[FUT] += PPO;
+ LOGP[FUT] = LOGP[FUT] + PPO;
+ }
+ if (MXO != 0)
+ {
+ FUT = (NOW + POD_reel + FOD_reel) % Log_size;
+ LOG[FUT] += MXO;
+ LOGP[FUT] = LOGP[FUT] + MXO;
+ }
+ NOW = (NOW + 1) % Log_size;
+
+ AVP[dayInTheYear] = AUN * cluster.nominalCapacity;
+
+ if (tsIndex > 1)
+ {
+ double AVPDayInTheYear = AVP[dayInTheYear];
+ for (uint h = 0; h != 24; ++h)
+ {
+ dstSeries[hour] = Math::Round(AVPDayInTheYear * modulation[hour]);
+ ++hour;
+ }
+ }
+ }
+ }
+
+ if (derated)
+ cluster.series.timeSeries.averageTimeseries();
+
+ if (archive)
+ writeResultsToDisk(area, cluster);
+
+ cluster.calculationOfSpinning();
+}
+
+} // namespace Antares::TSGenerator
diff --git a/src/solver/ts-generator/randomized-thermal-generator/RandomizedGenerator.h b/src/solver/ts-generator/randomized-thermal-generator/RandomizedGenerator.h
new file mode 100644
index 0000000000..1f61d0284a
--- /dev/null
+++ b/src/solver/ts-generator/randomized-thermal-generator/RandomizedGenerator.h
@@ -0,0 +1,83 @@
+//
+// Created by milos on 10/11/23.
+//
+#pragma once
+
+#include "../../simulation/sim_extern_variables_globales.h"
+
+using namespace Yuni;
+
+#define SEP IO::Separator
+
+#define FAILURE_RATE_EQ_1 0.999
+
+namespace Antares::TSGenerator
+{
+
+class GeneratorTempData
+{
+ friend class OptimizationParameters;
+ friend class OptimizedThermalGenerator;
+
+public:
+ GeneratorTempData(Data::Study& study, Solver::Progression::Task& progr, Solver::IResultWriter& writer);
+
+ void prepareOutputFoldersForAllAreas(uint year);
+
+ void operator()(Data::Area& area, Data::ThermalCluster& cluster);
+
+public:
+ Data::Study& study;
+
+ bool archive;
+
+ uint currentYear;
+
+ uint nbThermalTimeseries;
+
+ bool derated;
+
+private:
+ void writeResultsToDisk(const Data::Area& area, const Data::ThermalCluster& cluster);
+
+ int durationGenerator(Data::ThermalLaw law, int expec, double volat, double a, double b);
+
+ template
+ void prepareIndispoFromLaw(Data::ThermalLaw law,
+ double volatility,
+ double A[],
+ double B[],
+ const T& duration);
+
+private:
+ const uint nbHoursPerYear = HOURS_PER_YEAR;
+ const uint daysPerYear = DAYS_PER_YEAR;
+
+ MersenneTwister& rndgenerator;
+
+ double AVP[366];
+ enum
+ {
+ Log_size = 4000
+ };
+ int LOG[Log_size];
+ int LOGP[Log_size];
+
+ double lf[366];
+ double lp[366];
+ double ff[366];
+ double pp[366];
+ double af[366];
+ double ap[366];
+ double bf[366];
+ double bp[366];
+
+ std::vector> FPOW;
+ std::vector> PPOW;
+
+ String pTempFilename;
+ Solver::Progression::Task& pProgression;
+ Solver::IResultWriter& pWriter;
+};
+
+} // namespace Antares::TSGenerator
\ No newline at end of file
diff --git a/src/solver/ts-generator/thermal.cpp b/src/solver/ts-generator/thermal.cpp
index 5c3f75b137..6bb2520467 100644
--- a/src/solver/ts-generator/thermal.cpp
+++ b/src/solver/ts-generator/thermal.cpp
@@ -25,592 +25,21 @@
** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions
*/
-#include