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 - -#include -#include -#include - -#include -#include -#include - -#include "../simulation/simulation.h" -#include "../simulation/sim_structure_donnees.h" -#include "../simulation/sim_structure_probleme_economique.h" #include "../simulation/sim_extern_variables_globales.h" +#include "randomized-thermal-generator/RandomizedGenerator.h" +#include "optimized-thermal-generator/main/OptimizedGenerator.h" -using namespace Yuni; - -#define SEP IO::Separator - -#define FAILURE_RATE_EQ_1 0.999 - -namespace Antares -{ -namespace TSGenerator -{ -namespace -{ -class GeneratorTempData final -{ -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; - - 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: - uint nbThermalTimeseries_; - 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]; - double FPOW[366][102]; - double PPOW[366][102]; - - String pTempFilename; - Solver::Progression::Task& pProgression; - Solver::IResultWriter& pWriter; -}; - -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; -} - -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) +namespace Antares::TSGenerator { - 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) - { - 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(AUN <= 100 and "Thermal Prepro: AUN is out of bounds (>=100)"); - 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 - -bool GenerateThermalTimeSeries(Data::Study& study, +bool GenerateRandomizedThermalTimeSeries(Data::Study& study, uint year, bool globalThermalTSgeneration, bool refreshTSonCurrentYear, Antares::Solver::IResultWriter& writer) { logs.info(); - logs.info() << "Generating the thermal time-series"; + logs.info() << "Generating randomized thermal time-series"; Solver::Progression::Task progression(study, year, Solver::Progression::sectTSGThermal); auto* generator = new GeneratorTempData(study, progression, writer); @@ -637,5 +66,49 @@ bool GenerateThermalTimeSeries(Data::Study& study, return true; } -} // namespace TSGenerator -} // namespace Antares +bool GenerateOptimizedThermalTimeSeries(Data::Study& study, + uint year, + bool globalThermalTSgeneration, + Antares::Solver::IResultWriter& writer) +{ + // optimized planning should only be called once. + // Due to possible thermal refresh span we can end up here more than once - but just ignore it + // even if we set in Scenario playlist that year-1 should be skipped, we will execute this + // properly + if (year != 0) + return true; + + logs.info(); + logs.info() << "Generating optimized thermal time-series"; + Solver::Progression::Task progression(study, year, Solver::Progression::sectTSGThermal); + + const auto& activeMaintenanceGroups = study.maintenanceGroups.activeMaintenanceGroups(); + for (const auto& entryMaintenanceGroup : activeMaintenanceGroups) + { + auto& maintenanceGroup = *(entryMaintenanceGroup.get()); + auto generator = OptimizedThermalGenerator( + study, maintenanceGroup, year, globalThermalTSgeneration, progression, writer); + generator.GenerateOptimizedThermalTimeSeries(); + } + + return true; +} + +bool GenerateThermalTimeSeries(Data::Study& study, + uint year, + bool globalThermalTSgeneration, + bool refreshTSonCurrentYear, + Antares::Solver::IResultWriter& writer) +{ + if (study.parameters.maintenancePlanning.isOptimized()) + { + return GenerateOptimizedThermalTimeSeries(study, year, globalThermalTSgeneration, writer); + } + else + { + return GenerateRandomizedThermalTimeSeries( + study, year, globalThermalTSgeneration, refreshTSonCurrentYear, writer); + } +} + +} // namespace Antares::TSGenerator diff --git a/src/solver/variable/CMakeLists.txt b/src/solver/variable/CMakeLists.txt index df21955781..fea122e23c 100644 --- a/src/solver/variable/CMakeLists.txt +++ b/src/solver/variable/CMakeLists.txt @@ -109,9 +109,9 @@ set(SRC_VARIABLE_ECONOMY economy/STStorageLevelsByCluster.h economy/STStorageCashFlowByCluster.h economy/unsupliedEnergy.h + economy/unsupliedEnergyCsr.h economy/domesticUnsuppliedEnergy.h economy/localMatchingRuleViolations.h - economy/spilledEnergyAfterCSR.h economy/dtgMarginAfterCsr.h economy/spilledEnergy.h economy/dispatchableGeneration.h @@ -128,6 +128,7 @@ set(SRC_VARIABLE_ECONOMY economy/avail-dispatchable-generation.h economy/dispatchable-generation-margin.h + # Links economy/links/flowLinear.h economy/links/flowLinearAbs.h diff --git a/src/solver/variable/economy/all.h b/src/solver/variable/economy/all.h index b961c8dbf7..3527fcec6d 100644 --- a/src/solver/variable/economy/all.h +++ b/src/solver/variable/economy/all.h @@ -63,7 +63,6 @@ #include "unsupliedEnergy.h" #include "domesticUnsuppliedEnergy.h" #include "localMatchingRuleViolations.h" -#include "spilledEnergyAfterCSR.h" #include "dtgMarginAfterCsr.h" #include "spilledEnergy.h" @@ -73,6 +72,7 @@ #include "avail-dispatchable-generation.h" #include "dispatchable-generation-margin.h" +#include "unsupliedEnergyCsr.h" // By thermal plant #include "productionByDispatchablePlant.h" @@ -161,22 +161,24 @@ typedef // Prices >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> VariablesPerArea; /*! @@ -240,8 +242,6 @@ typedef // Prices LMRViolations, Common::SpatialAggregate< SpilledEnergy, - Common::SpatialAggregate< - SpilledEnergyAfterCSR, // LOLD Common::SpatialAggregate< LOLD, @@ -271,7 +271,7 @@ typedef // Prices // - // refs: // #55 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> VariablesPerSetOfAreas; typedef BindingConstMarginCost< // Marginal cost for a binding constraint diff --git a/src/solver/variable/economy/bindingConstraints/bindingConstraintsMarginalCost.h b/src/solver/variable/economy/bindingConstraints/bindingConstraintsMarginalCost.h index c120753a7a..19e76ec58c 100644 --- a/src/solver/variable/economy/bindingConstraints/bindingConstraintsMarginalCost.h +++ b/src/solver/variable/economy/bindingConstraints/bindingConstraintsMarginalCost.h @@ -191,28 +191,27 @@ class BindingConstMarginCost void yearEnd(unsigned int year, unsigned int numSpace) { - if (!isInitialized()) - return; - + if (isInitialized()) + { // Compute statistics for the current year depending on // the BC type (hourly, daily, weekly) using namespace Data; switch (associatedBC_->type()) { - case BindingConstraint::typeHourly: - pValuesForTheCurrentYear[numSpace].computeAveragesForCurrentYearFromHourlyResults(); - break; - case BindingConstraint::typeDaily: - pValuesForTheCurrentYear[numSpace].computeAveragesForCurrentYearFromDailyResults(); - break; - case BindingConstraint::typeWeekly: - pValuesForTheCurrentYear[numSpace].computeAveragesForCurrentYearFromWeeklyResults(); - break; - case BindingConstraint::typeUnknown: - case BindingConstraint::typeMax: - break; + case BindingConstraint::typeHourly: + pValuesForTheCurrentYear[numSpace].computeAveragesForCurrentYearFromHourlyResults(); + break; + case BindingConstraint::typeDaily: + pValuesForTheCurrentYear[numSpace].computeAveragesForCurrentYearFromDailyResults(); + break; + case BindingConstraint::typeWeekly: + pValuesForTheCurrentYear[numSpace].computeAveragesForCurrentYearFromWeeklyResults(); + break; + case BindingConstraint::typeUnknown: + case BindingConstraint::typeMax: + break; + } } - // Next variable NextType::yearEnd(year, numSpace); } @@ -233,53 +232,54 @@ class BindingConstMarginCost void weekBegin(State& state) { - if (!isInitialized()) - return; - - auto numSpace = state.numSpace; - // For daily binding constraints, getting daily marginal price - using namespace Data; - switch (associatedBC_->type()) - { - case BindingConstraint::typeHourly: - case BindingConstraint::typeUnknown: - case BindingConstraint::typeMax: - return; - - case BindingConstraint::typeDaily: + if (isInitialized()) { - int dayInTheYear = state.weekInTheYear * 7; - for (int dayInTheWeek = 0; dayInTheWeek < 7; dayInTheWeek++) + auto numSpace = state.numSpace; + // For daily binding constraints, getting daily marginal price + using namespace Data; + switch (associatedBC_->type()) { - pValuesForTheCurrentYear[numSpace].day[dayInTheYear] - -= state.problemeHebdo - ->ResultatsContraintesCouplantes[bindConstraintGlobalIndex_] - .variablesDuales[dayInTheWeek]; + case BindingConstraint::typeHourly: + case BindingConstraint::typeUnknown: + case BindingConstraint::typeMax: + break; - dayInTheYear++; + case BindingConstraint::typeDaily: + { + int dayInTheYear = state.weekInTheYear * 7; + for (int dayInTheWeek = 0; dayInTheWeek < 7; dayInTheWeek++) + { + pValuesForTheCurrentYear[numSpace].day[dayInTheYear] + -= state.problemeHebdo + ->ResultatsContraintesCouplantes[bindConstraintGlobalIndex_] + .variablesDuales[dayInTheWeek]; + + dayInTheYear++; + } + break; } - break; - } - - // For weekly binding constraints, getting weekly marginal price - case BindingConstraint::typeWeekly: - { - uint weekInTheYear = state.weekInTheYear; - double weeklyValue - = -state.problemeHebdo->ResultatsContraintesCouplantes[bindConstraintGlobalIndex_] - .variablesDuales[0]; - pValuesForTheCurrentYear[numSpace].week[weekInTheYear] = weeklyValue; - - int dayInTheYear = state.weekInTheYear * 7; - for (int dayInTheWeek = 0; dayInTheWeek < 7; dayInTheWeek++) + // For weekly binding constraints, getting weekly marginal price + case BindingConstraint::typeWeekly: { - pValuesForTheCurrentYear[numSpace].day[dayInTheYear] = weeklyValue; - dayInTheYear++; + uint weekInTheYear = state.weekInTheYear; + double weeklyValue + = -state.problemeHebdo->ResultatsContraintesCouplantes[bindConstraintGlobalIndex_] + .variablesDuales[0]; + + pValuesForTheCurrentYear[numSpace].week[weekInTheYear] = weeklyValue; + + int dayInTheYear = state.weekInTheYear * 7; + for (int dayInTheWeek = 0; dayInTheWeek < 7; dayInTheWeek++) + { + pValuesForTheCurrentYear[numSpace].day[dayInTheYear] = weeklyValue; + dayInTheYear++; + } + break; + } } - break; - } } + NextType::weekBegin(state); } void hourBegin(unsigned int hourInTheYear) @@ -290,17 +290,16 @@ class BindingConstMarginCost void hourEnd(State& state, unsigned int hourInTheYear) { - if (!isInitialized()) - return; - - auto numSpace = state.numSpace; - if (associatedBC_->type() == Data::BindingConstraint::typeHourly) + if (isInitialized()) { - pValuesForTheCurrentYear[numSpace][hourInTheYear] - -= state.problemeHebdo->ResultatsContraintesCouplantes[bindConstraintGlobalIndex_] - .variablesDuales[state.hourInTheWeek]; + auto numSpace = state.numSpace; + if (associatedBC_->type() == Data::BindingConstraint::typeHourly) + { + pValuesForTheCurrentYear[numSpace][hourInTheYear] + -= state.problemeHebdo->ResultatsContraintesCouplantes[bindConstraintGlobalIndex_] + .variablesDuales[state.hourInTheWeek]; + } } - NextType::hourEnd(state, hourInTheYear); } @@ -340,20 +339,21 @@ class BindingConstMarginCost { // Building syntheses results // ------------------------------ - if (!(precision & associatedBC_->yearByYearFilter())) - return; - - // And only if we match the current data level _and_ precision level - if ((dataLevel & VCardType::categoryDataLevel) && (fileLevel & VCardType::categoryFileLevel) - && (precision & VCardType::precision)) + if (precision & associatedBC_->yearByYearFilter()) { - results.isPrinted = AncestorType::isPrinted; - results.isCurrentVarNA[0] = isCurrentOutputNonApplicable(precision); - results.variableCaption = getBindConstraintCaption(); + // And only if we match the current data level _and_ precision level + if ((dataLevel & VCardType::categoryDataLevel) && (fileLevel & VCardType::categoryFileLevel) + && (precision & VCardType::precision)) + { + results.isPrinted = AncestorType::isPrinted; + results.isCurrentVarNA[0] = isCurrentOutputNonApplicable(precision); + results.variableCaption = getBindConstraintCaption(); - VariableAccessorType::template BuildSurveyReport( - results, AncestorType::pResults, dataLevel, fileLevel, precision, false); + VariableAccessorType::template BuildSurveyReport( + results, AncestorType::pResults, dataLevel, fileLevel, precision, false); + } } + NextType::buildSurveyReport(results, dataLevel, fileLevel, precision); } private: diff --git a/src/solver/variable/economy/profitByPlant.h b/src/solver/variable/economy/profitByPlant.h index 02a471fab8..373237978e 100644 --- a/src/solver/variable/economy/profitByPlant.h +++ b/src/solver/variable/economy/profitByPlant.h @@ -277,12 +277,11 @@ class ProfitByPlant : public Variable::IVariable, NextT, VC auto* cluster = state.area->thermal.clusters[clusterIndex]; double hourlyClusterProduction = thermal[area->index].thermalClustersProductions[clusterIndex]; - double pMin = thermal[area->index].PMinOfClusters[clusterIndex]; uint tsIndex = cluster->series.timeseriesNumbers[0][state.year]; // Thermal cluster profit pValuesForTheCurrentYear[numSpace][cluster->areaWideIndex].hour[hourInTheYear] - = (hourlyClusterProduction - pMin) + = std::max((hourlyClusterProduction - cluster->PthetaInf[hourInTheYear]), 0.) * (-areaMarginalCosts[hourInTheWeek] - cluster->getMarginalCost(tsIndex, hourInTheYear)); } diff --git a/src/solver/variable/economy/spilledEnergyAfterCSR.h b/src/solver/variable/economy/unsupliedEnergyCsr.h similarity index 72% rename from src/solver/variable/economy/spilledEnergyAfterCSR.h rename to src/solver/variable/economy/unsupliedEnergyCsr.h index c36ae1aee5..979977d972 100644 --- a/src/solver/variable/economy/spilledEnergyAfterCSR.h +++ b/src/solver/variable/economy/unsupliedEnergyCsr.h @@ -1,53 +1,53 @@ /* -** Copyright 2007-2023 RTE -** Authors: Antares_Simulator Team -** -** This file is part of Antares_Simulator. +** 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 GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or +** 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. ** -** 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 . +** Mozilla Public Licence 2.0 for more details. ** -** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . */ -#ifndef __SOLVER_VARIABLE_ECONOMY_SpilledEnergyAfterCSR_H__ -#define __SOLVER_VARIABLE_ECONOMY_SpilledEnergyAfterCSR_H__ +#pragma once #include "../variable.h" -namespace Antares::Solver::Variable::Economy +namespace Antares +{ +namespace Solver +{ +namespace Variable { -struct VCardSpilledEnergyAfterCSR +namespace Economy +{ +struct VCardUnsupliedEnergyCSR { //! Caption static std::string Caption() { - return "SPIL. ENRG. CSR"; + return "UNSP. ENRG CSR"; } + //! Unit static std::string Unit() { return "MWh"; } + //! The short description of the variable static std::string Description() { - return "Spilled Energy After CSR Optimization (generation that cannot be satisfied) " - "after CSR optimization"; + return "Unsuplied Energy after CSR (demand that cannot be satisfied)"; } //! The expecte results @@ -59,8 +59,7 @@ struct VCardSpilledEnergyAfterCSR ResultsType; //! The VCard to look for for calculating spatial aggregates - typedef VCardSpilledEnergyAfterCSR VCardForSpatialAggregate; - + typedef VCardUnsupliedEnergyCSR VCardForSpatialAggregate; enum { //! Data Level @@ -82,9 +81,8 @@ struct VCardSpilledEnergyAfterCSR //! Intermediate values hasIntermediateValues = 1, //! Can this variable be non applicable (0 : no, 1 : yes) - isPossiblyNonApplicable = 0, + isPossiblyNonApplicable = 0 }; - typedef IntermediateValues IntermediateValuesBaseType; typedef IntermediateValues* IntermediateValuesType; @@ -92,21 +90,17 @@ struct VCardSpilledEnergyAfterCSR }; // class VCard -/*! -** \brief C02 Average value of the overrall SpilledEnergyAfterCSR emissions expected from all -** the thermal dispatchable clusters -*/ template -class SpilledEnergyAfterCSR - : public Variable::IVariable, NextT, VCardSpilledEnergyAfterCSR> +class UnsupliedEnergyCSR + : public Variable::IVariable, NextT, VCardUnsupliedEnergyCSR> { public: //! Type of the next static variable typedef NextT NextType; //! VCard - typedef VCardSpilledEnergyAfterCSR VCardType; + typedef VCardUnsupliedEnergyCSR VCardType; //! Ancestor - typedef Variable::IVariable, NextT, VCardType> AncestorType; + typedef Variable::IVariable, NextT, VCardType> AncestorType; //! List of expected results typedef typename VCardType::ResultsType ResultsType; @@ -124,15 +118,16 @@ class SpilledEnergyAfterCSR { enum { - count - = ((VCardType::categoryDataLevel & CDataLevel && VCardType::categoryFileLevel & CFile) - ? (NextType::template Statistics::count - + VCardType::columnCount * ResultsType::count) - : NextType::template Statistics::count), + count = ((VCardType::categoryDataLevel & CDataLevel + && VCardType::categoryFileLevel & CFile) + ? (NextType::template Statistics::count + + VCardType::columnCount * ResultsType::count) + : NextType::template Statistics::count), }; }; - ~SpilledEnergyAfterCSR() +public: + ~UnsupliedEnergyCSR() { delete[] pValuesForTheCurrentYear; } @@ -146,7 +141,9 @@ class SpilledEnergyAfterCSR pValuesForTheCurrentYear = new VCardType::IntermediateValuesBaseType[pNbYearsParallel]; for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + { pValuesForTheCurrentYear[numSpace].initializeFromStudy(study); + } // Next NextType::initializeFromStudy(study); @@ -173,7 +170,9 @@ class SpilledEnergyAfterCSR void simulationBegin() { for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + { pValuesForTheCurrentYear[numSpace].reset(); + } // Next NextType::simulationBegin(); } @@ -192,10 +191,10 @@ class SpilledEnergyAfterCSR NextType::yearBegin(year, numSpace); } - void yearEndBuild(State& state, unsigned int year) + void yearEndBuild(State& state, unsigned int year, unsigned int numSpace) { // Next variable - NextType::yearEndBuild(state, year); + NextType::yearEndBuild(state, year, numSpace); } void yearEnd(unsigned int year, unsigned int numSpace) @@ -229,10 +228,8 @@ class SpilledEnergyAfterCSR void hourForEachArea(State& state, unsigned int numSpace) { - // Total SpilledEnergyAfterCSR emissions pValuesForTheCurrentYear[numSpace][state.hourInTheYear] - = state.hourlyResults->ValeursHorairesSpilledEnergyAfterCSR[state.hourInTheWeek]; - + = state.hourlyResults->ValeursHorairesDeDefaillancePositiveCSR[state.hourInTheWeek]; // Next variable NextType::hourForEachArea(state, numSpace); } @@ -257,8 +254,8 @@ class SpilledEnergyAfterCSR // Write the data for the current year results.variableCaption = VCardType::Caption(); results.variableUnit = VCardType::Unit(); - pValuesForTheCurrentYear[numSpace].template buildAnnualSurveyReport( - results, fileLevel, precision); + pValuesForTheCurrentYear[numSpace] + .template buildAnnualSurveyReport(results, fileLevel, precision); } } @@ -267,8 +264,9 @@ class SpilledEnergyAfterCSR typename VCardType::IntermediateValuesType pValuesForTheCurrentYear; unsigned int pNbYearsParallel; -}; // class SpilledEnergyAfterCSR - -} // namespace Antares::Solver::Variable::Economy +}; // class UnsupliedEnergyCSR -#endif // __SOLVER_VARIABLE_ECONOMY_SpilledEnergyAfterCSR_H__ +} // namespace Economy +} // namespace Variable +} // namespace Solver +} // namespace Antares diff --git a/src/tests/end-to-end/binding_constraints/test_binding_constraints.cpp b/src/tests/end-to-end/binding_constraints/test_binding_constraints.cpp index 16c3a9046d..083c8f863a 100644 --- a/src/tests/end-to-end/binding_constraints/test_binding_constraints.cpp +++ b/src/tests/end-to-end/binding_constraints/test_binding_constraints.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test-end-to-end tests_binding_constraints -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN #include #include diff --git a/src/tests/end-to-end/simple_study/simple-study.cpp b/src/tests/end-to-end/simple_study/simple-study.cpp index a36c3fe6ab..e422c05d9d 100644 --- a/src/tests/end-to-end/simple_study/simple-study.cpp +++ b/src/tests/end-to-end/simple_study/simple-study.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test-end-to-end tests -#define BOOST_TEST_DYN_LINK + #include #include diff --git a/src/tests/src/libs/antares/array/tests-matrix-load.cpp b/src/tests/src/libs/antares/array/tests-matrix-load.cpp index 31a209c67b..dd3a43f915 100644 --- a/src/tests/src/libs/antares/array/tests-matrix-load.cpp +++ b/src/tests/src/libs/antares/array/tests-matrix-load.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test-lib-antares-matrix tests -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/array/tests-matrix-save.cpp b/src/tests/src/libs/antares/array/tests-matrix-save.cpp index b01ac3ef17..e672962651 100644 --- a/src/tests/src/libs/antares/array/tests-matrix-save.cpp +++ b/src/tests/src/libs/antares/array/tests-matrix-save.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test-lib-antares-matrix tests -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/concurrency/test_concurrency.cpp b/src/tests/src/libs/antares/concurrency/test_concurrency.cpp index 1ab2119889..72ca263cff 100644 --- a/src/tests/src/libs/antares/concurrency/test_concurrency.cpp +++ b/src/tests/src/libs/antares/concurrency/test_concurrency.cpp @@ -25,7 +25,7 @@ ** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions */ #define BOOST_TEST_MODULE test-concurrency tests -#define BOOST_TEST_DYN_LINK + #include #include diff --git a/src/tests/src/libs/antares/study/area/test-save-area-optimization-ini.cpp b/src/tests/src/libs/antares/study/area/test-save-area-optimization-ini.cpp index 0e3f4163d4..4119915bc9 100644 --- a/src/tests/src/libs/antares/study/area/test-save-area-optimization-ini.cpp +++ b/src/tests/src/libs/antares/study/area/test-save-area-optimization-ini.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test save area optimization.ini -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/study/area/test-save-link-properties.cpp b/src/tests/src/libs/antares/study/area/test-save-link-properties.cpp index 584b0d2861..b774013a10 100644 --- a/src/tests/src/libs/antares/study/area/test-save-link-properties.cpp +++ b/src/tests/src/libs/antares/study/area/test-save-link-properties.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test save link properties.ini -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/study/constraint/test_constraint.cpp b/src/tests/src/libs/antares/study/constraint/test_constraint.cpp index a671d7857c..adc80328de 100644 --- a/src/tests/src/libs/antares/study/constraint/test_constraint.cpp +++ b/src/tests/src/libs/antares/study/constraint/test_constraint.cpp @@ -4,7 +4,7 @@ #define WIN32_LEAN_AND_MEAN #define BOOST_TEST_MODULE binding_constraints -#define BOOST_TEST_DYN_LINK + #include diff --git a/src/tests/src/libs/antares/study/constraint/test_group.cpp b/src/tests/src/libs/antares/study/constraint/test_group.cpp index c53f3c855d..2f732eaa5d 100644 --- a/src/tests/src/libs/antares/study/constraint/test_group.cpp +++ b/src/tests/src/libs/antares/study/constraint/test_group.cpp @@ -2,7 +2,7 @@ // Created by marechaljas on 28/06/23. // #define BOOST_TEST_MODULE binding_constraints_groups -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN #include #include diff --git a/src/tests/src/libs/antares/study/output-folder/study.cpp b/src/tests/src/libs/antares/study/output-folder/study.cpp index f4df8e3e00..281dba2eee 100644 --- a/src/tests/src/libs/antares/study/output-folder/study.cpp +++ b/src/tests/src/libs/antares/study/output-folder/study.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE output folder -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp index 36935b8e36..4fef1b9660 100644 --- a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp +++ b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test read scenario-builder.dat -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN @@ -409,4 +409,25 @@ BOOST_FIXTURE_TEST_CASE(binding_constraints_group_groupTest__Load_TS_4_for_year_ BOOST_CHECK_EQUAL(actual, tsNumber-1); } +// ======================== +// Tests on TSNumberData +// ======================== +BOOST_FIXTURE_TEST_CASE(thermalTSNumberData, Fixture) +{ + ScenarioBuilder::thermalTSNumberData tsdata; + tsdata.attachArea(area_1); + tsdata.reset(*study); + tsdata.setTSnumber(thCluster_12.get(), 2, 22); + tsdata.setTSnumber(thCluster_12.get(), 5, 32); //out of bounds + + study->parameters.nbTimeSeriesThermal = 1; + thCluster_12->tsGenBehavior = LocalTSGenerationBehavior::forceNoGen; + thCluster_12->series.timeSeries.resize(30, 8760); + + tsdata.apply(*study); + + BOOST_CHECK_EQUAL(thCluster_12->series.timeseriesNumbers[0][2], 21); + BOOST_CHECK_EQUAL(thCluster_12->series.timeseriesNumbers[0][5], 0); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp index db422282dc..a91d0732d1 100644 --- a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp +++ b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test save scenario - builder.dat -#define BOOST_TEST_DYN_LINK + #include #include diff --git a/src/tests/src/libs/antares/study/series/timeseries-tests.cpp b/src/tests/src/libs/antares/study/series/timeseries-tests.cpp index b5e2fadeed..69abdbc00c 100644 --- a/src/tests/src/libs/antares/study/series/timeseries-tests.cpp +++ b/src/tests/src/libs/antares/study/series/timeseries-tests.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE "test time series" -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp b/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp index fa73cc6306..d6a51bce85 100644 --- a/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp +++ b/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE "test short term storage" -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/study/test_study.cpp b/src/tests/src/libs/antares/study/test_study.cpp index f934754ce5..55044c3762 100644 --- a/src/tests/src/libs/antares/study/test_study.cpp +++ b/src/tests/src/libs/antares/study/test_study.cpp @@ -26,7 +26,7 @@ */ #define BOOST_TEST_MODULE study -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN #include diff --git a/src/tests/src/libs/antares/study/thermal-price-definition/thermal-price-definition.cpp b/src/tests/src/libs/antares/study/thermal-price-definition/thermal-price-definition.cpp index cbd8895d80..285135f484 100644 --- a/src/tests/src/libs/antares/study/thermal-price-definition/thermal-price-definition.cpp +++ b/src/tests/src/libs/antares/study/thermal-price-definition/thermal-price-definition.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE "test thermal price definition" -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/libs/antares/test_utils.cpp b/src/tests/src/libs/antares/test_utils.cpp index d6de5507a2..fc13941459 100644 --- a/src/tests/src/libs/antares/test_utils.cpp +++ b/src/tests/src/libs/antares/test_utils.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test utils -#define BOOST_TEST_DYN_LINK + #include #include diff --git a/src/tests/src/libs/antares/writer/test_zip_writer.cpp b/src/tests/src/libs/antares/writer/test_zip_writer.cpp index c934b0d316..efe98e994b 100644 --- a/src/tests/src/libs/antares/writer/test_zip_writer.cpp +++ b/src/tests/src/libs/antares/writer/test_zip_writer.cpp @@ -25,7 +25,7 @@ ** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions */ #define BOOST_TEST_MODULE test-writer tests -#define BOOST_TEST_DYN_LINK + #include #include diff --git a/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp b/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp index 0ae7bba823..352dbbc520 100644 --- a/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp +++ b/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp @@ -26,7 +26,7 @@ */ #define WIN32_LEAN_AND_MEAN #define BOOST_TEST_MODULE unfeasible_problem_analyzer -#define BOOST_TEST_DYN_LINK + #include #include diff --git a/src/tests/src/solver/optimisation/adequacy_patch.cpp b/src/tests/src/solver/optimisation/adequacy_patch.cpp index fcc06b4fd2..a8055e8634 100644 --- a/src/tests/src/solver/optimisation/adequacy_patch.cpp +++ b/src/tests/src/solver/optimisation/adequacy_patch.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test adequacy patch functions -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN @@ -8,6 +8,7 @@ #include "adequacy_patch_local_matching/adq_patch_local_matching.h" #include "adequacy_patch_csr/adq_patch_curtailment_sharing.h" #include +#include "adequacy_patch_csr/post_processing.h" #include "antares/study/parameters/adq-patch-params.h" #include #include @@ -19,43 +20,7 @@ static double origineExtremite = -1; static double extremiteOrigine = 5; using namespace Antares::Data::AdequacyPatch; - -// NOTE -// Xmax limits the flux origin -> extremity (direct) -// -Xmin limits the flux extremity -> origin (indirect) - -std::pair setNTCboundsForOneTimeStep(AdequacyPatchMode originType, - AdequacyPatchMode extremityType, - bool SetNTCOutsideToOutsideToZero, - bool SetNTCOutsideToInsideToZero) -{ - PROBLEME_HEBDO problem; - problem.adequacyPatchRuntimeData = std::make_shared(); - problem.adequacyPatchRuntimeData->originAreaMode.resize(1); - problem.adequacyPatchRuntimeData->extremityAreaMode.resize(1); - - problem.adequacyPatchRuntimeData->originAreaMode[0] = originType; - problem.adequacyPatchRuntimeData->extremityAreaMode[0] = extremityType; - problem.adequacyPatchRuntimeData->AdequacyFirstStep = true; - - AdqPatchParams adqPatchParams; - adqPatchParams.enabled = true; - adqPatchParams.localMatching.setToZeroOutsideOutsideLinks = SetNTCOutsideToOutsideToZero; - adqPatchParams.localMatching.setToZeroOutsideInsideLinks = SetNTCOutsideToInsideToZero; - - VALEURS_DE_NTC_ET_RESISTANCES ValeursDeNTC; - ValeursDeNTC.ValeurDeNTCOrigineVersExtremite.assign(1, 0.); - ValeursDeNTC.ValeurDeNTCExtremiteVersOrigine.assign(1, 0.); - ValeursDeNTC.ValeurDeNTCOrigineVersExtremite[0] = origineExtremite; - ValeursDeNTC.ValeurDeNTCExtremiteVersOrigine[0] = extremiteOrigine; - - double Xmin(0.); - double Xmax(0.); - - setNTCbounds(Xmax, Xmin, ValeursDeNTC, 0, &problem, adqPatchParams); - - return std::make_pair(Xmin, Xmax); -} +namespace tt = boost::test_tools; static const double flowArea0toArea1_positive = 10; static const double flowArea0toArea1_negative = -10; @@ -120,107 +85,11 @@ AdqPatchParams createParams() AdqPatchParams p; p.enabled = true; p.curtailmentSharing.includeHurdleCost = true; - p.localMatching.enabled = true; p.curtailmentSharing.priceTakingOrder = AdqPatchPTO::isDens; return p; } -// Virtual -> Virtual (0 -> 0) -// No change in bounds is expected -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_virtual_virtual_no_change_expected) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep( - virtualArea, virtualArea, true /*SetNTCOutsideToOutsideToZero*/, false); - BOOST_CHECK_EQUAL(Xmax, origineExtremite); - BOOST_CHECK_EQUAL(Xmin, -extremiteOrigine); -} - -// Virtual -> physical area inside adq-patch (0 -> 2) -// No change in bounds is expected -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_virtual_inside_no_change_expected) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep( - virtualArea, physicalAreaInsideAdqPatch, true /*SetNTCOutsideToOutsideToZero*/, false); - BOOST_CHECK_EQUAL(Xmax, origineExtremite); - BOOST_CHECK_EQUAL(Xmin, -extremiteOrigine); -} - -// Virtual -> physical area outside adq-patch (0 -> 1) -// No change in bounds is expected -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_virtual_outside_no_change_expected) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep( - virtualArea, physicalAreaOutsideAdqPatch, true /*SetNTCOutsideToOutsideToZero*/, false); - BOOST_CHECK_EQUAL(Xmax, origineExtremite); - BOOST_CHECK_EQUAL(Xmin, -extremiteOrigine); -} - -// physical area outside adq-patch -> physical area outside adq-patch (1 -> 1) -// NTC should be set to 0 in both directions -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_outside_outside_zero_expected_both_directions) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep(physicalAreaOutsideAdqPatch, - physicalAreaOutsideAdqPatch, - true /*SetNTCOutsideToOutsideToZero*/, - false); - BOOST_CHECK_EQUAL(Xmax, 0); - BOOST_CHECK_EQUAL(Xmin, 0); -} - -// physical area outside adq-patch -> physical area outside adq-patch (1 -> 1) -// SetNTCOutsideToOutsideToZero = true -// NTC should be set to 0 in both directions -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_outside_outside_no_change_expected) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep( - physicalAreaOutsideAdqPatch, physicalAreaOutsideAdqPatch, false, false); - - BOOST_CHECK_EQUAL(Xmax, origineExtremite); - BOOST_CHECK_EQUAL(Xmin, -extremiteOrigine); -} - -// physical area inside adq-patch -> physical area outside adq-patch (2 -> 1) -// NTC should be set to 0 in both directions -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_inside_outside_zero_expected_both_directions) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep( - physicalAreaInsideAdqPatch, physicalAreaOutsideAdqPatch, false, false); - BOOST_CHECK_EQUAL(Xmax, 0); - BOOST_CHECK_EQUAL(Xmin, 0); -} - -// physical area outside adq-patch -> physical area inside adq-patch (1 -> 2) -// NTC should be set to 0 in both directions -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_outside_inside_zero_expected_both_directions) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep(physicalAreaOutsideAdqPatch, - physicalAreaInsideAdqPatch, - false, - true /*SetNTCOutsideToInsideToZero*/); - BOOST_CHECK_EQUAL(Xmax, 0); - BOOST_CHECK_EQUAL(Xmin, 0); -} - -// physical area outside adq-patch -> physical area inside adq-patch (1 -> 2) -// NTC should be unchanged in direction origin->extremity (direct) -// NTC should be set to 0 in direction extremity->origin (indirect) -BOOST_AUTO_TEST_CASE(setNTCboundsForOneTimeStep_outside_inside_change_expected_one_direction) -{ - double Xmin, Xmax; - std::tie(Xmin, Xmax) = setNTCboundsForOneTimeStep( - physicalAreaOutsideAdqPatch, physicalAreaInsideAdqPatch, false, false); - BOOST_CHECK_EQUAL(Xmax, origineExtremite); - BOOST_CHECK_EQUAL(Xmin, 0); -} - // Area 0 is physical area inside adq-patch connected to two areas: // Area1 virtual-area, and Area2-virtual area // flow from Area0 -> Area1 is positive @@ -489,7 +358,6 @@ BOOST_AUTO_TEST_CASE(check_valid_adq_param) auto p = createParams(); BOOST_CHECK_NO_THROW(p.checkAdqPatchSimulationModeEconomyOnly(Antares::Data::SimulationMode::Economy)); BOOST_CHECK_NO_THROW(p.checkAdqPatchIncludeHurdleCost(true)); - BOOST_CHECK_NO_THROW(p.checkAdqPatchDisabledLocalMatching()); } BOOST_AUTO_TEST_CASE(check_adq_param_wrong_mode) @@ -505,9 +373,74 @@ BOOST_AUTO_TEST_CASE(check_adq_param_wrong_hurdle_cost) BOOST_CHECK_THROW(p.checkAdqPatchIncludeHurdleCost(false), Error::IncompatibleHurdleCostCSR); } -BOOST_AUTO_TEST_CASE(check_adq_param_wrong_lmr_disabled) +BOOST_AUTO_TEST_SUITE(adq_patch_post_processing) + +BOOST_AUTO_TEST_CASE(dtg_mrg_triggered_low_ens) { - auto p = createParams(); - p.localMatching.enabled = false; - BOOST_CHECK_THROW(p.checkAdqPatchDisabledLocalMatching(), Error::AdqPatchDisabledLMR); + const bool triggered = true; + const double dtgMrg = 32.; + const double ens = 21.; + + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) == 11., tt::tolerance(1.e-6)); + + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) + recomputeENS_MRG(triggered, dtgMrg, ens) + == std::abs(dtgMrg - ens), + tt::tolerance(1.e-6)); +} + +BOOST_AUTO_TEST_CASE(dtg_mrg_triggered_high_ens) +{ + const bool triggered = true; + const double dtgMrg = 32.; + const double ens = 42.; + + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) == 0., tt::tolerance(1.e-6)); + + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) + recomputeENS_MRG(triggered, dtgMrg, ens) + == std::abs(dtgMrg - ens), + tt::tolerance(1.e-6)); +} + +BOOST_AUTO_TEST_CASE(dtg_mrg_not_triggered_low_ens) +{ + const bool triggered = false; + const double dtgMrg = 32.; + const double ens = 21.; + + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) == dtgMrg, tt::tolerance(1.e-6)); + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) + recomputeENS_MRG(triggered, dtgMrg, ens) + == dtgMrg + ens, + tt::tolerance(1.e-6)); +} + +BOOST_AUTO_TEST_CASE(dtg_mrg_not_triggered_high_ens) +{ + const bool triggered = false; + const double dtgMrg = 32.; + const double ens = 42.; + + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) == dtgMrg, tt::tolerance(1.e-6)); + BOOST_TEST(recomputeDTG_MRG(triggered, dtgMrg, ens) + recomputeENS_MRG(triggered, dtgMrg, ens) + == dtgMrg + ens, + tt::tolerance(1.e-6)); +} + +BOOST_AUTO_TEST_CASE(mrgprice_high_enscsr) +{ + const double ensCsr = 21.; + const double originalCost = 3.; + const double unsuppliedEnergyCost = 1000.; + BOOST_TEST(recomputeMRGPrice(ensCsr, originalCost, unsuppliedEnergyCost) + == -unsuppliedEnergyCost, + tt::tolerance(1.e-6)); +} + +BOOST_AUTO_TEST_CASE(mrgprice_low_enscsr) +{ + const double ensCsr = 0.; + const double originalCost = 3.; + const double unsuppliedEnergyCost = 1000.; + BOOST_TEST(recomputeMRGPrice(ensCsr, originalCost, unsuppliedEnergyCost) == originalCost, + tt::tolerance(1.e-6)); } +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/src/solver/simulation/test-store-timeseries-number.cpp b/src/tests/src/solver/simulation/test-store-timeseries-number.cpp index 43948da1a8..f92628e3cc 100644 --- a/src/tests/src/solver/simulation/test-store-timeseries-number.cpp +++ b/src/tests/src/solver/simulation/test-store-timeseries-number.cpp @@ -2,7 +2,7 @@ // Created by marechaljas on 15/03/23. // #define BOOST_TEST_MODULE store-timeseries-number -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tests/src/solver/simulation/test-time_series.cpp b/src/tests/src/solver/simulation/test-time_series.cpp index 00357c5299..a040f1a7c3 100644 --- a/src/tests/src/solver/simulation/test-time_series.cpp +++ b/src/tests/src/solver/simulation/test-time_series.cpp @@ -2,7 +2,7 @@ // Created by marechaljas on 07/04/23. // #define BOOST_TEST_MODULE rhsTimeSeries -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN #include diff --git a/src/tests/src/solver/simulation/tests-ts-numbers.cpp b/src/tests/src/solver/simulation/tests-ts-numbers.cpp index c12bf028dd..167025a242 100644 --- a/src/tests/src/solver/simulation/tests-ts-numbers.cpp +++ b/src/tests/src/solver/simulation/tests-ts-numbers.cpp @@ -1,5 +1,5 @@ #define BOOST_TEST_MODULE test solver simulation things -#define BOOST_TEST_DYN_LINK + #define WIN32_LEAN_AND_MEAN diff --git a/src/tools/batchrun/main.cpp b/src/tools/batchrun/main.cpp index dd0fb3187b..b905c6fac4 100644 --- a/src/tools/batchrun/main.cpp +++ b/src/tools/batchrun/main.cpp @@ -32,12 +32,16 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include + #ifdef YUNI_OS_WINDOWS #include #endif @@ -84,7 +88,8 @@ int main(int argc, char* argv[]) Antares::Resources::Initialize(argc, argv, true); // options - String optInput; + std::string optInput; + std::string ortoolsSolver; bool optNoTSImport = false; bool optIgnoreAllConstraints = false; bool optForceExpansion = false; @@ -95,6 +100,7 @@ int main(int argc, char* argv[]) bool optNoOutput = false; bool optParallel = false; bool optVerbose = false; + bool ortoolsUsed = false; Nullable optYears; Nullable optSolver; Nullable optName; @@ -143,6 +149,19 @@ int main(int argc, char* argv[]) ' ', "force-parallel", "Override the max number of years computed simultaneously"); + + // add option for ortools use + // --use-ortools + options.addFlag(ortoolsUsed, ' ', "use-ortools", "Use ortools library to launch solver"); + + //--ortools-solver + options.add(ortoolsSolver, + ' ', + "ortools-solver", + "Ortools solver used for simulation (only available with use-ortools " + "option)\nAvailable solver list : " + + availableOrToolsSolversString()); + options.remainingArguments(optInput); // Version options.addParagraph("\nMisc."); @@ -176,163 +195,179 @@ int main(int argc, char* argv[]) logs.error() << "contradictory options: --economy and --adequacy"; return EXIT_FAILURE; } - } - - // Source Folder - logs.debug() << "Folder : `" << optInput << '`'; - String solver; - if (optSolver.empty()) - { - Solver::FindLocation(solver); - if (solver.empty()) + if (ortoolsUsed) { - logs.fatal() << "The solver has not been found"; - return EXIT_FAILURE; - } - } - else - { - String tmp; - IO::MakeAbsolute(tmp, *optSolver); - IO::Normalize(solver, tmp); - if (not IO::File::Exists(solver)) - { - logs.fatal() << "The solver has not been found. specify --solver=" << solver; - return EXIT_FAILURE; + const auto availableSolvers = getAvailableOrtoolsSolverName(); + if (auto it + = std::find(availableSolvers.begin(), availableSolvers.end(), ortoolsSolver); + it == availableSolvers.end()) + { + logs.error() << "Please specify a solver using --ortools-solver. Available solvers " + << availableOrToolsSolversString() << ""; + return EXIT_FAILURE; + } } - } - logs.info() << " Solver: '" << solver << "'"; - logs.info(); - logs.info(); - logs.info() << "Searching for studies..."; - logs.info(); - MyStudyFinder finder; - finder.lookup(optInput); - finder.wait(); + // Source Folder + logs.debug() << "Folder : `" << optInput << '`'; - if (not finder.list.empty()) - { - if (finder.list.size() > 1) - logs.info() << "Found " << finder.list.size() << " studyies"; + String solver; + if (optSolver.empty()) + { + Solver::FindLocation(solver); + if (solver.empty()) + { + logs.fatal() << "The solver has not been found"; + return EXIT_FAILURE; + } + } else - logs.info() << "Found 1 study"; - logs.info() << "Starting..."; - - if (!(!optName)) { - String name; - name = *optName; - name.replace("\"", "\\\""); - *optName = name; + String tmp; + IO::MakeAbsolute(tmp, *optSolver); + IO::Normalize(solver, tmp); + if (not IO::File::Exists(solver)) + { + logs.fatal() << "The solver has not been found. specify --solver=" << solver; + return EXIT_FAILURE; + } } - // The folder that contains the solver - String dirname; - IO::parentPath(dirname, solver); + logs.info() << " Solver: '" << solver << "'"; + logs.info(); + logs.info(); + logs.info() << "Searching for studies..."; + logs.info(); + MyStudyFinder finder; + finder.lookup(optInput); + finder.wait(); - String cmd; - - uint studyIndx = 0; - foreach (auto& studypath, finder.list) + if (not finder.list.empty()) { - ++studyIndx; - - logs.info(); - if (optVerbose) - logs.info(); - - logs.checkpoint() << "Running simulation: `" << studypath << "` (" << studyIndx << '/' - << (uint)finder.list.size() << ')'; - if (optVerbose) - logs.debug(); - - cmd.clear(); - if (not System::windows) - cmd << "nice "; + if (finder.list.size() > 1) + logs.info() << "Found " << finder.list.size() << " studyies"; else - cmd << "call "; // why is it required for working ??? - cmd << "\"" << solver << "\""; - if (optForce) - cmd << " --force"; - if (optForceExpansion) - cmd << " --economy"; - if (optForceEconomy) - cmd << " --economy"; - if (optForceAdequacy) - cmd << " --adequacy"; + logs.info() << "Found 1 study"; + logs.info() << "Starting..."; + if (!(!optName)) - cmd << " --name=\"" << *optName << "\""; - if (!(!optYears)) - cmd << " --year=" << *optYears; - if (optNoOutput) - cmd << " --no-output"; - if (optYearByYear) - cmd << " --year-by-year"; - if (optNoTSImport) - cmd << " --no-ts-import"; - if (optIgnoreAllConstraints) - cmd << " --no-constraints"; - if (optParallel) - cmd << " --parallel"; - if (!(!optForceParallel)) - cmd << " --force-parallel=" << *optForceParallel; - cmd << " \"" << studypath << "\""; - if (!optVerbose) - cmd << sendToNull(); - - // Changing the current working directory - IO::Directory::Current::Set(dirname); - // Executing the converter - if (optVerbose) - logs.info() << "Executing " << cmd; + { + String name; + name = *optName; + name.replace("\"", "\\\""); + *optName = name; + } - // Execute the command - int cmd_return_code = system(cmd.c_str()); + // The folder that contains the solver + String dirname; + IO::parentPath(dirname, solver); - if (cmd_return_code != 0) - logs.error() << "An error occured."; - else - logs.info() << "Success."; + String cmd; - if (cmd_return_code == -1) + uint studyIndx = 0; + foreach (auto& studypath, finder.list) { -#ifdef YUNI_OS_WINDOWS - switch (errno) + ++studyIndx; + + logs.info(); + if (optVerbose) + logs.info(); + + logs.checkpoint() << "Running simulation: `" << studypath << "` (" << studyIndx + << '/' << (uint)finder.list.size() << ')'; + if (optVerbose) + logs.debug(); + + cmd.clear(); + if (not System::windows) + cmd << "nice "; + else + cmd << "call "; // why is it required for working ??? + cmd << "\"" << solver << "\""; + if (optForce) + cmd << " --force"; + if (optForceExpansion) + cmd << " --economy"; + if (optForceEconomy) + cmd << " --economy"; + if (optForceAdequacy) + cmd << " --adequacy"; + if (!(!optName)) + cmd << " --name=\"" << *optName << "\""; + if (!(!optYears)) + cmd << " --year=" << *optYears; + if (optNoOutput) + cmd << " --no-output"; + if (optYearByYear) + cmd << " --year-by-year"; + if (optNoTSImport) + cmd << " --no-ts-import"; + if (optIgnoreAllConstraints) + cmd << " --no-constraints"; + if (optParallel) + cmd << " --parallel"; + if (optForceParallel) + cmd << " --force-parallel=" << *optForceParallel; + if (ortoolsUsed) + cmd << " --use-ortools --ortools-solver=" << ortoolsSolver; + + cmd << " \"" << studypath << "\""; + if (!optVerbose) + cmd << sendToNull(); + + // Changing the current working directory + IO::Directory::Current::Set(dirname); + // Executing the converter + if (optVerbose) + logs.info() << "Executing " << cmd; + + // Execute the command + int cmd_return_code = system(cmd.c_str()); + + if (cmd_return_code != 0) + logs.error() << "An error occured."; + else + logs.info() << "Success."; + + if (cmd_return_code == -1) { - case E2BIG: - logs.error() << "Argument list (which is system dependent) is too big"; - break; - case ENOENT: - logs.error() << "Command interpreter cannot be found"; - break; - case ENOEXEC: - logs.error() - << "Command-interpreter file has invalid format and is not executable"; - break; - case ENOMEM: - logs.error() << "Not enough memory is available to execute command"; - break; - } +#ifdef YUNI_OS_WINDOWS + switch (errno) + { + case E2BIG: + logs.error() << "Argument list (which is system dependent) is too big"; + break; + case ENOENT: + logs.error() << "Command interpreter cannot be found"; + break; + case ENOEXEC: + logs.error() + << "Command-interpreter file has invalid format and is not executable"; + break; + case ENOMEM: + logs.error() << "Not enough memory is available to execute command"; + break; + } #endif + } } - } - logs.info() << "Done."; + logs.info() << "Done."; - // Time interval - if (optVerbose) + // Time interval + if (optVerbose) + { + logs.debug(); + logs.debug(); + } + } + else { - logs.debug(); - logs.debug(); + logs.fatal() << "No study has been found."; + return 4; } - } - else - { - logs.fatal() << "No study has been found."; - return 4; - } - return 0; + return 0; + } } diff --git a/vcpkg b/vcpkg index 9d47b24eac..c82f746672 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit 9d47b24eacbd1cd94f139457ef6cd35e5d92cc84 +Subproject commit c82f74667287d3dc386bce81e44964370c91a289