diff --git a/.github/workflows/centos7.yml b/.github/workflows/centos7.yml index 330df8e719..61ac1ec3dd 100644 --- a/.github/workflows/centos7.yml +++ b/.github/workflows/centos7.yml @@ -2,7 +2,7 @@ name: Centos7 CI (push and/or release) on: release: - types: [created] + types: [ created ] push: branches: - develop @@ -29,135 +29,107 @@ jobs: build: name: Build - env: - ORTOOLSDIR: ${{ github.workspace }}/or-tools runs-on: ubuntu-latest - container: 'antaresrte/rte-antares:centos7-simulator-no-deps' steps: - - name: Checkout - run: | - git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git -b ${{ env.REF }} . - - - name: Install gcc 10 - run: | - # update mirrors, official centos7 is deprecated - sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo &&\ - sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo &&\ - sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo &&\ - yum update -y - - # not a typo, centos-release-scl is needed to install devtoolset-10 but introduce deprecated mirror - sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo &&\ - sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo &&\ - sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo - - yum install -y centos-release-scl - yum install -y devtoolset-11-gcc* - - - name: Install cmake 3.28 - run: pip3 install cmake==3.28.4 - - - name: Install VCPKG - # Note: we need to use environment variables instead of workflow variables - # because github messes up path variables when running in container, - # see https://github.com/actions/runner/issues/2058 - run: | - git submodule update --init vcpkg && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics - echo "VCPKG_ROOT=$GITHUB_WORKSPACE/vcpkg" >> $GITHUB_ENV - echo "VCPKG_CACHE_DIR=$GITHUB_WORKSPACE/vcpkg_cache" >> $GITHUB_ENV - echo "VCPKG_BINARY_SOURCES=clear;files,$GITHUB_WORKSPACE/vcpkg_cache,readwrite" >> $GITHUB_ENV - - - name: Restore vcpkg binary dir from cache - id: cache-vcpkg-binary - # Note: we are stuck with v3, because v4 is not compatible with oracle8 image - uses: actions/cache/restore@v3 - with: - path: ${{ env.VCPKG_CACHE_DIR }} - key: vcpkg-cache-centos7-${{ hashFiles('src/vcpkg.json', '.git/modules/vcpkg/HEAD') }} - # Allows to restore a cache when deps have only partially changed (like adding a dependency) - restore-keys: vcpkg-cache-centos7- - - - name: Init submodule - run: | - git submodule update --init --remote src/tests/resources/Antares_Simulator_Tests - - - name: Config OR-Tools URL - run: | - 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 - run: | - mkdir -p ${{ env.ORTOOLSDIR }} && cd ${{ env.ORTOOLSDIR }} - wget -q -O ortools.zip ${{ env.URL_ORTOOLS }} - unzip -q ortools.zip - rm ortools.zip - - - name: Install gh if needed - if: ${{ env.IS_RELEASE == 'true' }} - run: | - 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-11/enable - source /opt/rh/rh-git227/enable - cmake -B _build -S src \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DCMAKE_TOOLCHAIN_FILE=$GITHUB_WORKSPACE/vcpkg/scripts/buildsystems/vcpkg.cmake \ - -DVCPKG_TARGET_TRIPLET=x64-linux-release \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_TESTING=ON \ - -DBUILD_TOOLS=ON \ - -DBUILD_UI=OFF \ - -DCMAKE_PREFIX_PATH=${{ env.ORTOOLSDIR }}/install \ - - - - name: Build - run: | - source /opt/rh/devtoolset-11/enable - source /opt/rh/rh-git227/enable - cmake --build _build --config Release -j$(nproc) - ccache -s - - - name: Installer .rpm creation - run: | - cd _build - cpack -G RPM - - - name: Solver archive creation - run: | - cd _build - cmake --install . --prefix install - pushd . - cd install/bin - tar czf ../../antares-solver_centos7.tar.gz antares-solver libsirius_solver.so - popd - rm -rf install - - - name: .tar.gz creation - run: | - cd _build - cpack -G TGZ - - - 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 - - - name: Cache vcpkg binary dir - if: always() - id: save-cache-vcpkg-binary - uses: actions/cache/save@v3 - with: - path: ${{ env.VCPKG_CACHE_DIR }} - key: vcpkg-cache-centos7-${{ hashFiles('src/vcpkg.json', '.git/modules/vcpkg/HEAD') }} + + #sparse checkout -- only needed files + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + sparse-checkout: | + docker + src/vcpkg.json + ref: ${{ env.REF }} + + - name: set env variables -- DockerFiles + run: | + echo "DOCKERFILE=$(pwd)/docker/Dockerfile" >> $GITHUB_ENV + echo "DOCKERDIR=$(pwd)/docker" >> $GITHUB_ENV + + + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v45 + with: + files: | + docker/AntaresDeps + + - name: Docker file push + id: docker_push + + if: steps.changed-files.outputs.any_changed == 'true' + uses: elgohr/Publish-Docker-Github-Action@main + with: + name: antaresrte/antaressystemdeps + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + workdir: ${{ env.DOCKERDIR }} + dockerfile: ${{ env.DOCKERDIR }}/AntaresDeps + cache: false + tags: latest + + - name: create vcpkg cache dir + run: | + echo "VCPKG_CACHE_DIR=$GITHUB_WORKSPACE/vcpkg_cache" >> $GITHUB_ENV + mkdir -p ${{ github.workspace }}/vcpkg_cache + + + - name: Restore vcpkg binary dir from cache + id: cache-vcpkg-binary + uses: actions/cache/restore@v4 + with: + path: ${{ github.workspace }}/vcpkg_cache + key: vcpkg-cache-centos7-${{ hashFiles('src/vcpkg.json', '.git/modules/vcpkg/HEAD') }} + # Allows to restore a cache when deps have only partially changed (like adding a dependency) + restore-keys: vcpkg-cache-centos7- + + - name: Setup ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: centos7-on-${{ runner.os }} + + + - name: Build the image and Antares + run: | + ls -latr .ccache/ && \ + docker build \ + -t antares:centos7 \ + --build-arg="BRANCH=${{ env.REF }}" \ + --build-arg="NPROC=$(nproc)" \ + --build-arg="VCPKG_CACHE_DIR=./vcpkg_cache" \ + --build-arg CCACHE_DIR=./.ccache \ + --build-arg CCACHE_KEY=centos7-on-${{ runner.os }} \ + -f ${{ env.DOCKERFILE }} . + + + - name: create a container without starting it && retrieve the .tgz + run: | + container_id=$(docker create antares:centos7) + docker cp $container_id:/workspace/Antares_Simulator/_build/archive archive + docker cp $container_id:/workspace/vcpkg_cache ${{ env.VCPKG_CACHE_DIR }} + docker cp $container_id:/workspace/.ccache/. .ccache + ls -la .ccache + docker rm $container_id + + + - name: Publish assets + if: ${{ env.IS_RELEASE == 'true' }} + env: + GITHUB_TOKEN: ${{ github.token }} + tag: ${{ github.event.inputs.release_tag }} + run: | + gh release upload "$tag" archive/*.tar.gz archive/*.rpm + + + - name: Cache vcpkg binary dir + if: always() + id: save-cache-vcpkg-binary + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}/vcpkg_cache + key: vcpkg-cache-centos7-${{ hashFiles('src/vcpkg.json', '.git/modules/vcpkg/HEAD') }} + diff --git a/docker/AntaresDeps b/docker/AntaresDeps new file mode 100644 index 0000000000..bfe47cb44e --- /dev/null +++ b/docker/AntaresDeps @@ -0,0 +1,37 @@ +FROM centos:7 + +ENV LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + DEBIAN_FRONTEND=noninteractive \ + VCPKG_ROOT=/vcpkg \ + ORTOOLS_DIR=ortools + +CMD ["/bin/bash"] + +# Install requirements : update repo +RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo &&\ + sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo &&\ + sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo &&\ + yum install -y epel-release &&\ + yum install -y git redhat-lsb-core make wget centos-release-scl scl-utils python3 &&\ + sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo &&\ + sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo &&\ + sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo + +RUN yum update -y +RUN yum install -y epel-release +RUN yum install -y git redhat-lsb-core make wget centos-release-scl scl-utils rpm-build && \ + yum install -y devtoolset-11 && \ + yum install -y rh-git227-git ccache +RUN yum install -y unzip libuuid-devel wxGTK3-devel +RUN yum install -y python3-pip && python3 -m pip install --upgrade pip && pip3 install pytest numpy pandas + +# Install requirements +RUN rm -rf /var/cache/yum + +RUN echo "source /opt/rh/devtoolset-11/enable" >> /etc/bashrc +SHELL ["/bin/bash", "--login", "-c"] + +# Install CMake +RUN pip3 install cmake==3.28.4 + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..17a21b3063 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,95 @@ +FROM antaresrte/antaressystemdeps:latest + + + +CMD ["/bin/bash"] + +RUN mkdir /workspace + +RUN echo "source /opt/rh/devtoolset-11/enable" >> /etc/bashrc +SHELL ["/bin/bash", "--login", "-c"] + + +RUN mkdir -p /workspace/vcpkg_cache +ARG VCPKG_CACHE_DIR +COPY $VCPKG_CACHE_DIR /workspace/ + + +ARG BRANCH=develop +RUN cd /workspace && \ + git clone https://github.com/AntaresSimulatorTeam/Antares_Simulator.git --branch $BRANCH && \ + cd Antares_Simulator && \ + git submodule update --init vcpkg && ./vcpkg/bootstrap-vcpkg.sh -disableMetrics && \ + export VCPKG_ROOT=/workspace/Antares_Simulator/vcpkg + + + + + +RUN ORTOOLS_TAG=$(cat /workspace/Antares_Simulator/ortools_tag) && \ + echo "ORTOOLS_TAG=$ORTOOLS_TAG" && \ + URL_ORTOOLS=https://github.com/rte-france/or-tools-rte/releases/download/$ORTOOLS_TAG/ortools_cxx_centos7_static_sirius.zip && \ + mkdir -p ortools && cd ortools && \ + wget -q -O ortools.zip $URL_ORTOOLS && \ + unzip -q ortools.zip && \ + rm ortools.zip + +WORKDIR /workspace/Antares_Simulator +# Accept build arguments for ccache +ARG CCACHE_DIR +ARG CCACHE_KEY + +# Copy ccache directory into the container +COPY ${CCACHE_DIR:-/dev/null} /workspace/.ccache + +# Configure ccache environment variables +ENV CCACHE_DIR=/workspace/.ccache +ENV CCACHE_BASEDIR=/workspace +ENV CCACHE_COMPRESS=1 +ENV PATH="/usr/lib/ccache:$PATH" + +RUN source /opt/rh/devtoolset-11/enable && \ + source /opt/rh/rh-git227/enable && \ + export VCPKG_BINARY_SOURCES="clear;files,/workspace/vcpkg_cache,readwrite" && \ + cmake -B _build -S src \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCMAKE_TOOLCHAIN_FILE=/workspace/Antares_Simulator/vcpkg/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET=x64-linux-release \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING=OFF \ + -DBUILD_TOOLS=ON \ + -DBUILD_UI=OFF \ + -DCMAKE_PREFIX_PATH=/ortools/install + +ARG NPROC=2 +RUN source /opt/rh/devtoolset-11/enable && \ + source /opt/rh/rh-git227/enable && \ + export VCPKG_BINARY_SOURCES="clear;files,/workspace/vcpkg_cache,readwrite" && \ + cmake --build _build --config Release -j${NPROC} &&\ + ccache -s + +#Installer .rpm creation +RUN cd _build && \ + cpack -G RPM + +#Solver archive creation +RUN cd _build && \ + cpack -G RPM + +RUN cd _build && \ + cmake --install . --prefix install && \ + pushd . && \ + cd install/bin && \ + tar czf ../../antares-solver_centos7.tar.gz antares-solver libsirius_solver.so && \ + popd && \ + rm -rf install + +#.tar.gz creation +RUN cd _build && \ + cpack -G TGZ +#mv .rpm and .tar.gz +RUN cd _build && \ + mkdir archive && \ + mv *.tar.gz archive && \ + mv *.rpm archive diff --git a/docs/developer-guide/ortools-integration.md b/docs/developer-guide/ortools-integration.md index 4c0b4b8d8d..c953e04d57 100644 --- a/docs/developer-guide/ortools-integration.md +++ b/docs/developer-guide/ortools-integration.md @@ -52,10 +52,10 @@ These are the solver names and enum, defined in the [OR-Tools API](https://githu * A mixed real integer solver While the OR-Tools interface allows using multiple solvers, Antares restricts this usage. -The *Antares Simulator* user can select the solvers using the `ortools-solver` command-line option. Here are the allowed +The *Antares Simulator* user can select the solvers using the `solver` command-line option. Here are the allowed values: -| `ortools-solver` | Linear solver | Mixed real integer solver | +| `solver` | Linear solver | Mixed real integer solver | |:-------------------|--------------------------------|----------------------------------| | `sirius` (default) | SIRIUS_LINEAR_PROGRAMMING | SIRIUS_MIXED_INTEGER_PROGRAMMING | | `coin` | CLP_LINEAR_PROGRAMMING | CBC_MIXED_INTEGER_PROGRAMMING | @@ -65,7 +65,7 @@ values: The following commercial solvers are not yet supported by *Antares Simulator*, because of unmet pre-requisites: -| `ortools-solver` | Linear solver | Mixed real integer | Waiting for | +| `solver` | Linear solver | Mixed real integer | Waiting for | |:-----------------|---------------------------|----------------------------------|-----------------------------------------------------| | `cplex` | CPLEX_LINEAR_PROGRAMMING | CPLEX_MIXED_INTEGER_PROGRAMMING | Update OR-Tools building process for CPLEX support | | `gurobi` | GUROBI_LINEAR_PROGRAMMING | GUROBI_MIXED_INTEGER_PROGRAMMING | Update OR-Tools building process for GUROBI support | diff --git a/docs/user-guide/04-migration-guides.md b/docs/user-guide/04-migration-guides.md index 23f10a2668..1088f3766a 100644 --- a/docs/user-guide/04-migration-guides.md +++ b/docs/user-guide/04-migration-guides.md @@ -183,6 +183,13 @@ Using this property requires OR-Tools and a MILP solver (XPRESS, COIN) antares-8.8-solver --use-ortools --ortools-solver coin|xpress ... ``` +Starting from version 9.2, the syntax is as following + +``` +antares-solver --solver coin|xpress ... +``` + + ### Output ### Cashflow by short-term storage diff --git a/docs/user-guide/solver/02-command-line.md b/docs/user-guide/solver/02-command-line.md index 7cc927a662..88defa9062 100644 --- a/docs/user-guide/solver/02-command-line.md +++ b/docs/user-guide/solver/02-command-line.md @@ -17,7 +17,7 @@ hide: | --adequacy | Force the simulation in [adequacy](static-modeler/04-parameters.md#mode) mode | | --parallel | Enable [parallel](optional-features/multi-threading.md) computation of MC years | | --force-parallel=VALUE | Override the max number of years computed [simultaneously](optional-features/multi-threading.md) | -| --ortools-solver=VALUE | The optimization solver to use. Possible values are: `sirius` (default), `coin`, `xpress`, `scip` | +| --solver=VALUE | The optimization solver to use. Possible values are: `sirius` (default), `coin`, `xpress`, `scip` | ## Parameters diff --git a/docs/user-guide/solver/optional-features/xpress.md b/docs/user-guide/solver/optional-features/xpress.md index e8e5242f73..dfa1a07438 100644 --- a/docs/user-guide/solver/optional-features/xpress.md +++ b/docs/user-guide/solver/optional-features/xpress.md @@ -10,7 +10,7 @@ Antares Solver only uses LP, with plans to use MILP at some point in the future. ## Using Xpress in the command-line ``` -antares-solver --ortools-solver xpress [options] +antares-solver --solver xpress [options] ``` ## Setup diff --git a/simtest.json b/simtest.json index 03ea9af205..11b8cee038 100644 --- a/simtest.json +++ b/simtest.json @@ -1,3 +1,3 @@ { - "version": "v9.2.0e" + "version": "v9.2.0g" } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d7eec127b1..13c67fe7a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,7 @@ set(ANTARES_VERSION_REVISION 0) # Beta release set(ANTARES_BETA 0) -set(ANTARES_RC 5) +set(ANTARES_RC 7) set(ANTARES_VERSION_YEAR 2024) diff --git a/src/libs/antares/study/CMakeLists.txt b/src/libs/antares/study/CMakeLists.txt index a354de3058..29a89fc631 100644 --- a/src/libs/antares/study/CMakeLists.txt +++ b/src/libs/antares/study/CMakeLists.txt @@ -71,8 +71,11 @@ set(SRC_STUDY_PART_THERMAL include/antares/study/parts/thermal/ecoInput.h parts/thermal/ecoInput.cpp include/antares/study/parts/thermal/cluster.h + include/antares/study/parts/thermal/cost_provider.h include/antares/study/parts/thermal/cluster.hxx parts/thermal/cluster.cpp + parts/thermal/scenarized_cost_provider.cpp + parts/thermal/constant_cost_provider.cpp include/antares/study/parts/thermal/cluster_list.h parts/thermal/cluster_list.cpp include/antares/study/parts/thermal/pollutant.h diff --git a/src/libs/antares/study/area/list.cpp b/src/libs/antares/study/area/list.cpp index b9353937d9..cf2624515f 100644 --- a/src/libs/antares/study/area/list.cpp +++ b/src/libs/antares/study/area/list.cpp @@ -950,20 +950,29 @@ static bool AreaListLoadFromFolderSingleArea(Study& study, ret = area.hydro.series->loadGenerationTS(area.id, hydroSeries, studyVersion) && ret; } - if (studyVersion < StudyVersion(9, 1)) + switch (study.parameters.compatibility.hydroPmax) + { + case Parameters::Compatibility::HydroPmax::Daily: { HydroMaxTimeSeriesReader reader(area.hydro, area.id.to(), area.name.to()); ret = reader.read(pathHydro.string(), study.usedByTheSolver) && ret; + break; } - else + case Parameters::Compatibility::HydroPmax::Hourly: { ret = area.hydro.series->LoadMaxPower(area.id, hydroSeries) && ret; + break; + } + default: + throw std::invalid_argument( + "Value not supported for study.parameters.compatibility.hydroPmax"); } area.hydro.series->resizeTSinDeratedMode(study.parameters.derated, studyVersion, + study.parameters.compatibility.hydroPmax, study.usedByTheSolver); } diff --git a/src/libs/antares/study/include/antares/study/parameters.h b/src/libs/antares/study/include/antares/study/parameters.h index 0df3a8b157..38228bc23b 100644 --- a/src/libs/antares/study/include/antares/study/parameters.h +++ b/src/libs/antares/study/include/antares/study/parameters.h @@ -399,6 +399,18 @@ class Parameters final } include; + struct Compatibility + { + enum class HydroPmax + { + Daily, + Hourly + }; + HydroPmax hydroPmax = HydroPmax::Daily; + }; + + Compatibility compatibility; + // Shedding struct { @@ -518,6 +530,9 @@ const char* SimulationModeToCString(SimulationMode mode); */ bool StringToSimulationMode(SimulationMode& mode, Yuni::CString<20, false> text); +const char* CompatibilityHydroPmaxToCString(Parameters::Compatibility::HydroPmax); +bool StringToCompatibilityHydroPmax(Parameters::Compatibility::HydroPmax&, const std::string& text); + } // namespace Antares::Data #endif // __ANTARES_LIBS_STUDY_PARAMETERS_H__ diff --git a/src/libs/antares/study/include/antares/study/parts/hydro/series.h b/src/libs/antares/study/include/antares/study/parts/hydro/series.h index 4bb8367ac3..c3b587c0de 100644 --- a/src/libs/antares/study/include/antares/study/parts/hydro/series.h +++ b/src/libs/antares/study/include/antares/study/parts/hydro/series.h @@ -23,6 +23,7 @@ #include #include +#include #include #include "../../fwd.h" @@ -137,7 +138,10 @@ class DataSeriesHydro uint TScount() const; // Setting TS's when derated mode is on - void resizeTSinDeratedMode(bool derated, StudyVersion version, bool useBySolver); + void resizeTSinDeratedMode(bool derated, + StudyVersion version, + Parameters::Compatibility::HydroPmax hydroPmax, + bool useBySolver); }; // class DataSeriesHydro } // namespace Data } // namespace Antares diff --git a/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h b/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h index 09ada6b8c1..6b6523673c 100644 --- a/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h +++ b/src/libs/antares/study/include/antares/study/parts/thermal/cluster.h @@ -34,6 +34,7 @@ #include "../../fwd.h" #include "../common/cluster.h" +#include "cost_provider.h" #include "defines.h" #include "ecoInput.h" #include "pollutant.h" @@ -64,6 +65,12 @@ enum class LocalTSGenerationBehavior forceNoGen }; +double computeMarketBidCost(double fuelCost, + double fuelEfficiency, + double co2EmissionFactor, + double co2cost, + double variableomcost); + /*! ** \brief A single thermal cluster */ @@ -147,13 +154,6 @@ class ThermalCluster final: public Cluster, public std::enable_shared_from_this< */ void calculationOfSpinning(); - //! \name MarketBid and Marginal Costs - //@{ - /*! - ** \brief Calculation of market bid and marginals costs per hour - */ - void ComputeCostTimeSeries(); - /*! ** \brief Calculation of spinning (reverse) ** @@ -207,10 +207,6 @@ class ThermalCluster final: public Cluster, public std::enable_shared_from_this< bool doWeGenerateTS(bool globalTSgeneration) const; - double getOperatingCost(uint tsIndex, uint hourInTheYear) const; - double getMarginalCost(uint tsIndex, uint hourInTheYear) const; - double getMarketBidCost(uint hourInTheYear, uint year) const; - // Check & correct availability timeseries for thermal availability // Only applies if time-series are ready-made void checkAndCorrectAvailability(); @@ -349,29 +345,16 @@ class ThermalCluster final: public Cluster, public std::enable_shared_from_this< //! Data for the preprocessor std::unique_ptr prepro; - /*! - ** \brief Production Cost, Market Bid Cost and Marginal Cost Matrixes - Per Hour and per Time - *Series - */ - struct CostsTimeSeries - { - std::array productionCostTs; - std::array marketBidCostTS; - std::array marginalCostTS; - }; - - std::vector costsTimeSeries; - EconomicInputData ecoInput; LocalTSGenerationBehavior tsGenBehavior = LocalTSGenerationBehavior::useGlobalParameter; friend class ThermalClusterList; - double computeMarketBidCost(double fuelCost, double co2EmissionFactor, double co2cost); - unsigned int precision() const override; + CostProvider& getCostProvider(); + private: // Calculation of marketBid and marginal costs hourly time series // @@ -383,12 +366,7 @@ class ThermalCluster final: public Cluster, public std::enable_shared_from_this< // Marginal_Cost[€/MWh] = Market_Bid_Cost[€/MWh] = (Fuel_Cost[€/GJ] * 3.6 * 100 / Efficiency[%]) // CO2_emission_factor[tons/MWh] * C02_cost[€/tons] + Variable_O&M_cost[€/MWh] - void fillMarketBidCostTS(); - void fillMarginalCostTS(); - void resizeCostTS(); - void ComputeMarketBidTS(); - void MarginalCostEqualsMarketBid(); - void ComputeProductionCostTS(); + std::unique_ptr costProvider; }; // class ThermalCluster } // namespace Data diff --git a/src/libs/antares/study/include/antares/study/parts/thermal/cost_provider.h b/src/libs/antares/study/include/antares/study/parts/thermal/cost_provider.h new file mode 100644 index 0000000000..2a3b1b1d54 --- /dev/null +++ b/src/libs/antares/study/include/antares/study/parts/thermal/cost_provider.h @@ -0,0 +1,82 @@ +/* + * 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 + +#include +#include + +namespace Antares::Data +{ +class CostProvider +{ +public: + virtual ~CostProvider() = default; + virtual double getOperatingCost(uint serieIndex, uint hourInTheYear) const = 0; + virtual double getMarginalCost(uint serieIndex, uint hourInTheYear) const = 0; + virtual double getMarketBidCost(uint hourInTheYear, uint year) const = 0; +}; + +class ThermalCluster; + +class ConstantCostProvider: public CostProvider +{ +public: + explicit ConstantCostProvider(const ThermalCluster& cluster); + virtual ~ConstantCostProvider() = default; + double getOperatingCost(uint serieIndex, uint hourInTheYear) const override; + double getMarginalCost(uint serieIndex, uint hourInTheYear) const override; + double getMarketBidCost(uint hourInTheYear, uint year) const override; + +private: + const ThermalCluster& cluster; +}; + +class ScenarizedCostProvider: public CostProvider +{ +public: + explicit ScenarizedCostProvider(const ThermalCluster& cluster); + virtual ~ScenarizedCostProvider() = default; + double getOperatingCost(uint serieIndex, uint hourInTheYear) const override; + double getMarginalCost(uint serieIndex, uint hourInTheYear) const override; + double getMarketBidCost(uint hourInTheYear, uint year) const override; + +private: + /*! + ** \brief Production Cost, Market Bid Cost and Marginal Cost Matrixes - Per Hour and per Time + *Series + */ + struct CostsTimeSeries + { + std::array productionCostTs; + std::array marketBidCostTS; + std::array marginalCostTS; + }; + + void resizeCostTS(); + void ComputeMarketBidTS(); + void MarginalCostEqualsMarketBid(); + void ComputeProductionCostTS(); + + std::vector costsTimeSeries; + const ThermalCluster& cluster; +}; +} // namespace Antares::Data diff --git a/src/libs/antares/study/include/antares/study/parts/thermal/defines.h b/src/libs/antares/study/include/antares/study/parts/thermal/defines.h index 5d74c795ff..52d7cd9798 100644 --- a/src/libs/antares/study/include/antares/study/parts/thermal/defines.h +++ b/src/libs/antares/study/include/antares/study/parts/thermal/defines.h @@ -21,16 +21,13 @@ #ifndef __ANTARES_LIBS_STUDY_PARTS_THERMAL_DEFINES_H__ #define __ANTARES_LIBS_STUDY_PARTS_THERMAL_DEFINES_H__ -namespace Antares -{ -namespace Data +namespace Antares::Data { // Forward declaration class ThermalCluster; class ThermalClusterList; class PreproAvailability; -} // namespace Data -} // namespace Antares +} // namespace Antares::Data #endif // __ANTARES_LIBS_STUDY_PARTS_THERMAL_DEFINES_H__ diff --git a/src/libs/antares/study/parameters.cpp b/src/libs/antares/study/parameters.cpp index e26e57c6f7..54f5b6db74 100644 --- a/src/libs/antares/study/parameters.cpp +++ b/src/libs/antares/study/parameters.cpp @@ -208,6 +208,39 @@ const char* SimulationModeToCString(SimulationMode mode) } } +const char* CompatibilityHydroPmaxToCString(Parameters::Compatibility::HydroPmax mode) +{ + switch (mode) + { + case Parameters::Compatibility::HydroPmax::Daily: + return "daily"; + case Parameters::Compatibility::HydroPmax::Hourly: + return "hourly"; + default: + return "Unknown"; + } +} + +bool StringToCompatibilityHydroPmax(Parameters::Compatibility::HydroPmax& mode, + const std::string& text) +{ + if (text.empty()) + { + return false; + } + if (text == "daily") + { + mode = Parameters::Compatibility::HydroPmax::Daily; + return true; + } + if (text == "hourly") + { + mode = Parameters::Compatibility::HydroPmax::Hourly; + return true; + } + return false; +} + bool Parameters::economy() const { return mode == SimulationMode::Economy; @@ -1053,6 +1086,19 @@ static bool SGDIntLoadFamily_SeedsMersenneTwister(Parameters& d, return false; } +static bool SGDIntLoadFamily_Compatibility(Parameters& d, + const String& key, + const String& value, + const String&) +{ + if (key == "hydro-pmax") + { + return StringToCompatibilityHydroPmax(d.compatibility.hydroPmax, value); + } + + return false; +} + static bool SGDIntLoadFamily_Legacy(Parameters& d, const String& key, const String& value, @@ -1170,7 +1216,8 @@ bool Parameters::loadFromINI(const IniFile& ini, const StudyVersion& version) {"advanced parameters", &SGDIntLoadFamily_AdvancedParameters}, {"playlist", &SGDIntLoadFamily_Playlist}, {"variables selection", &SGDIntLoadFamily_VariablesSelection}, - {"seeds - mersenne twister", &SGDIntLoadFamily_SeedsMersenneTwister}}; + {"seeds - mersenne twister", &SGDIntLoadFamily_SeedsMersenneTwister}, + {"compatibility", &SGDIntLoadFamily_Compatibility}}; Callback handleAllKeysInSection; // Foreach section on the ini file... @@ -1967,6 +2014,10 @@ void Parameters::saveToINI(IniFile& ini) const section->add(SeedToID((SeedIndex)sd), seed[sd]); } } + { + auto* section = ini.addSection("compatibility"); + section->add("hydro-pmax", CompatibilityHydroPmaxToCString(compatibility.hydroPmax)); + } } bool Parameters::loadFromFile(const std::filesystem::path& filename, const StudyVersion& version) diff --git a/src/libs/antares/study/parts/hydro/container.cpp b/src/libs/antares/study/parts/hydro/container.cpp index 6ee131b4f3..52e6ef1d83 100644 --- a/src/libs/antares/study/parts/hydro/container.cpp +++ b/src/libs/antares/study/parts/hydro/container.cpp @@ -156,7 +156,8 @@ bool PartHydro::LoadFromFolder(Study& study, const fs::path& folder) area.hydro.pumpingEfficiency = 1.; area.hydro.deltaBetweenFinalAndInitialLevels.resize(study.parameters.nbYears); - if (study.header.version >= StudyVersion(9, 1)) + if (study.parameters.compatibility.hydroPmax + == Parameters::Compatibility::HydroPmax::Hourly) { // GUI part patch : // We need to know, when estimating the RAM required by the solver, if the current diff --git a/src/libs/antares/study/parts/hydro/series.cpp b/src/libs/antares/study/parts/hydro/series.cpp index a9f62664c8..2ae774fbdf 100644 --- a/src/libs/antares/study/parts/hydro/series.cpp +++ b/src/libs/antares/study/parts/hydro/series.cpp @@ -230,6 +230,7 @@ uint DataSeriesHydro::TScount() const void DataSeriesHydro::resizeTSinDeratedMode(bool derated, StudyVersion studyVersion, + Parameters::Compatibility::HydroPmax hydroPmax, bool usedBySolver) { if (!(derated && usedBySolver)) @@ -243,7 +244,7 @@ void DataSeriesHydro::resizeTSinDeratedMode(bool derated, { mingen.averageTimeseries(); - if (studyVersion >= StudyVersion(9, 1)) + if (hydroPmax == Parameters::Compatibility::HydroPmax::Hourly) { maxHourlyGenPower.averageTimeseries(); maxHourlyPumpPower.averageTimeseries(); diff --git a/src/libs/antares/study/parts/thermal/cluster.cpp b/src/libs/antares/study/parts/thermal/cluster.cpp index e8f7d6499b..cfd16f521e 100644 --- a/src/libs/antares/study/parts/thermal/cluster.cpp +++ b/src/libs/antares/study/parts/thermal/cluster.cpp @@ -120,8 +120,7 @@ namespace Data { Data::ThermalCluster::ThermalCluster(Area* parent): Cluster(parent), - PthetaInf(HOURS_PER_YEAR, 0), - costsTimeSeries(1, CostsTimeSeries()) + PthetaInf(HOURS_PER_YEAR, 0) { // assert assert(parent && "A parent for a thermal dispatchable cluster can not be null"); @@ -184,8 +183,6 @@ void Data::ThermalCluster::copyFrom(const ThermalCluster& cluster) fixedCost = cluster.fixedCost; startupCost = cluster.startupCost; marketBidCost = cluster.marketBidCost; - // assignment “=” operator can be used to copy-paste vector by value - costsTimeSeries = cluster.costsTimeSeries; // modulation modulation = cluster.modulation; @@ -299,106 +296,6 @@ void Data::ThermalCluster::calculationOfSpinning() } } -void Data::ThermalCluster::ComputeCostTimeSeries() -{ - switch (costgeneration) - { - case Data::setManually: - fillMarketBidCostTS(); - fillMarginalCostTS(); - break; - case Data::useCostTimeseries: - resizeCostTS(); - ComputeMarketBidTS(); - MarginalCostEqualsMarketBid(); - ComputeProductionCostTS(); - break; - } -} - -void Data::ThermalCluster::fillMarketBidCostTS() -{ - std::fill(costsTimeSeries[0].marketBidCostTS.begin(), - costsTimeSeries[0].marketBidCostTS.end(), - marketBidCost); -} - -void Data::ThermalCluster::fillMarginalCostTS() -{ - std::fill(costsTimeSeries[0].marginalCostTS.begin(), - costsTimeSeries[0].marginalCostTS.end(), - marginalCost); -} - -void ThermalCluster::resizeCostTS() -{ - const uint fuelCostWidth = ecoInput.fuelcost.width; - const uint co2CostWidth = ecoInput.co2cost.width; - const uint tsCount = std::max(fuelCostWidth, co2CostWidth); - - costsTimeSeries.resize(tsCount, CostsTimeSeries()); -} - -void ThermalCluster::ComputeMarketBidTS() -{ - const uint fuelCostWidth = ecoInput.fuelcost.width; - const uint co2CostWidth = ecoInput.co2cost.width; - - double& co2EmissionFactor = emissions.factors[Pollutant::CO2]; - - for (uint tsIndex = 0; tsIndex < costsTimeSeries.size(); ++tsIndex) - { - uint tsIndexFuel = std::min(fuelCostWidth - 1, tsIndex); - uint tsIndexCo2 = std::min(co2CostWidth - 1, tsIndex); - for (uint hour = 0; hour < HOURS_PER_YEAR; ++hour) - { - double& marketBidCostTS = costsTimeSeries[tsIndex].marketBidCostTS[hour]; - - double& fuelcost = ecoInput.fuelcost[tsIndexFuel][hour]; - double& co2cost = ecoInput.co2cost[tsIndexCo2][hour]; - - marketBidCostTS = computeMarketBidCost(fuelcost, co2EmissionFactor, co2cost); - } - } -} - -void ThermalCluster::MarginalCostEqualsMarketBid() -{ - for (auto& timeSeries: costsTimeSeries) - { - auto& source = timeSeries.marketBidCostTS; - auto& destination = timeSeries.marginalCostTS; - std::copy(source.begin(), source.end(), destination.begin()); - } -} - -void ThermalCluster::ComputeProductionCostTS() -{ - if (modulation.width == 0) - { - return; - } - - for (auto& timeSeries: costsTimeSeries) - { - auto& productionCostTS = timeSeries.productionCostTs; - auto& marginalCostTS = timeSeries.marginalCostTS; - - for (uint hour = 0; hour < HOURS_PER_YEAR; ++hour) - { - double& houtlyModulation = modulation[Data::thermalModulationCost][hour]; - productionCostTS[hour] = marginalCostTS[hour] * houtlyModulation; - } - } -} - -double Data::ThermalCluster::computeMarketBidCost(double fuelCost, - double co2EmissionFactor, - double co2cost) -{ - return fuelCost * 360.0 / fuelEfficiency + co2EmissionFactor * co2cost + variableomcost; -} - void Data::ThermalCluster::reverseCalculationOfSpinning() { if (tsGenBehavior == LocalTSGenerationBehavior::forceNoGen) @@ -469,7 +366,6 @@ void Data::ThermalCluster::reset() startupCost = 0.; marketBidCost = 0.; variableomcost = 0.; - costsTimeSeries.resize(1, CostsTimeSeries()); // modulation modulation.resize(thermalModulationMax, HOURS_PER_YEAR); @@ -719,53 +615,23 @@ unsigned int ThermalCluster::precision() const return 0; } -double ThermalCluster::getOperatingCost(uint serieIndex, uint hourInTheYear) const -{ - if (costgeneration == Data::setManually) - { - const auto* modCost = modulation[thermalModulationCost]; - return marginalCost * modCost[hourInTheYear]; - } - else - { - const uint tsIndex = std::min(serieIndex, (uint)costsTimeSeries.size() - 1); - return costsTimeSeries[tsIndex].productionCostTs[hourInTheYear]; - } -} - -double ThermalCluster::getMarginalCost(uint serieIndex, uint hourInTheYear) const +CostProvider& ThermalCluster::getCostProvider() { - const double mod = modulation[Data::thermalModulationCost][hourInTheYear]; - - if (costgeneration == Data::setManually) - { - return marginalCost * mod; - } - else + if (!costProvider) { - const uint tsIndex = std::min(serieIndex, (uint)costsTimeSeries.size() - 1); - return costsTimeSeries[tsIndex].marginalCostTS[hourInTheYear] * mod; - } - /* std::min is necessary in case Availability has e.g 10 TS and both FuelCost & Co2Cost have - only 1TS. Then - > In order to save memory marginalCostTS vector has only one array - inside -> that is used for all (e.g.10) TS*/ -} - -double ThermalCluster::getMarketBidCost(uint hourInTheYear, uint year) const -{ - uint serieIndex = series.getSeriesIndex(year); - - double mod = modulation[thermalModulationMarketBid][serieIndex]; - - if (costgeneration == Data::setManually) - { - return marketBidCost * mod; - } - else - { - const uint tsIndex = std::min(serieIndex, (uint)costsTimeSeries.size() - 1); - return costsTimeSeries[tsIndex].marketBidCostTS[hourInTheYear] * mod; + switch (costgeneration) + { + case Data::setManually: + costProvider = std::make_unique(*this); + break; + case Data::useCostTimeseries: + costProvider = std::make_unique(*this); + break; + default: + throw std::runtime_error("Invalid costgeneration parameter"); + } } + return *costProvider; } void ThermalCluster::checkAndCorrectAvailability() diff --git a/src/libs/antares/study/parts/thermal/cluster_list.cpp b/src/libs/antares/study/parts/thermal/cluster_list.cpp index 99d7fc21ac..e9d7e785e9 100644 --- a/src/libs/antares/study/parts/thermal/cluster_list.cpp +++ b/src/libs/antares/study/parts/thermal/cluster_list.cpp @@ -622,7 +622,6 @@ bool ThermalClusterList::loadEconomicCosts(Study& study, const fs::path& folder) auto filePath = folder / c->parentArea->id.c_str() / c->id(); bool result = c->ecoInput.loadFromFolder(study, filePath); - c->ComputeCostTimeSeries(); return result; }); } diff --git a/src/libs/antares/study/parts/thermal/constant_cost_provider.cpp b/src/libs/antares/study/parts/thermal/constant_cost_provider.cpp new file mode 100644 index 0000000000..0f7133f19d --- /dev/null +++ b/src/libs/antares/study/parts/thermal/constant_cost_provider.cpp @@ -0,0 +1,47 @@ +/* + * 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 "antares/study/parts/thermal/cluster.h" + +namespace Antares::Data +{ +ConstantCostProvider::ConstantCostProvider(const ThermalCluster& cluster): + cluster(cluster) +{ +} + +double ConstantCostProvider::getOperatingCost(uint serieIndex, uint hourInTheYear) const +{ + const auto* modCost = cluster.modulation[thermalModulationCost]; + return cluster.marginalCost * modCost[hourInTheYear]; +} + +double ConstantCostProvider::getMarginalCost(uint serieIndex, uint hourInTheYear) const +{ + const double mod = cluster.modulation[Data::thermalModulationCost][hourInTheYear]; + return cluster.marginalCost * mod; +} + +double ConstantCostProvider::getMarketBidCost(uint hourInTheYear, uint year) const +{ + const double mod = cluster.modulation[thermalModulationMarketBid][hourInTheYear]; + return cluster.marketBidCost * mod; +} +} // namespace Antares::Data diff --git a/src/libs/antares/study/parts/thermal/scenarized_cost_provider.cpp b/src/libs/antares/study/parts/thermal/scenarized_cost_provider.cpp new file mode 100644 index 0000000000..7a3232db40 --- /dev/null +++ b/src/libs/antares/study/parts/thermal/scenarized_cost_provider.cpp @@ -0,0 +1,131 @@ +/* + * 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 "antares/study/parts/thermal/cluster.h" + +namespace Antares::Data +{ + +ScenarizedCostProvider::ScenarizedCostProvider(const ThermalCluster& cluster): + cluster(cluster) +{ + resizeCostTS(); + ComputeMarketBidTS(); + MarginalCostEqualsMarketBid(); + ComputeProductionCostTS(); +} + +void ScenarizedCostProvider::ComputeProductionCostTS() +{ + if (cluster.modulation.width == 0) + { + return; + } + + for (auto& timeSeries: costsTimeSeries) + { + auto& productionCostTS = timeSeries.productionCostTs; + auto& marginalCostTS = timeSeries.marginalCostTS; + + for (uint hour = 0; hour < HOURS_PER_YEAR; ++hour) + { + double hourlyModulation = cluster.modulation[Data::thermalModulationCost][hour]; + productionCostTS[hour] = marginalCostTS[hour] * hourlyModulation; + } + } +} + +void ScenarizedCostProvider::resizeCostTS() +{ + const uint fuelCostWidth = cluster.ecoInput.fuelcost.width; + const uint co2CostWidth = cluster.ecoInput.co2cost.width; + const uint tsCount = std::max(fuelCostWidth, co2CostWidth); + + costsTimeSeries.resize(tsCount, CostsTimeSeries()); +} + +void ScenarizedCostProvider::MarginalCostEqualsMarketBid() +{ + for (auto& timeSeries: costsTimeSeries) + { + auto& source = timeSeries.marketBidCostTS; + auto& destination = timeSeries.marginalCostTS; + std::copy(source.begin(), source.end(), destination.begin()); + } +} + +double computeMarketBidCost(double fuelCost, + double fuelEfficiency, + double co2EmissionFactor, + double co2cost, + double variableomcost) +{ + return fuelCost * 360.0 / fuelEfficiency + co2EmissionFactor * co2cost + variableomcost; +} + +void ScenarizedCostProvider::ComputeMarketBidTS() +{ + const uint fuelCostWidth = cluster.ecoInput.fuelcost.width; + const uint co2CostWidth = cluster.ecoInput.co2cost.width; + + double co2EmissionFactor = cluster.emissions.factors[Pollutant::CO2]; + + for (uint tsIndex = 0; tsIndex < costsTimeSeries.size(); ++tsIndex) + { + uint tsIndexFuel = std::min(fuelCostWidth - 1, tsIndex); + uint tsIndexCo2 = std::min(co2CostWidth - 1, tsIndex); + for (uint hour = 0; hour < HOURS_PER_YEAR; ++hour) + { + double fuelcost = cluster.ecoInput.fuelcost[tsIndexFuel][hour]; + double co2cost = cluster.ecoInput.co2cost[tsIndexCo2][hour]; + + costsTimeSeries[tsIndex].marketBidCostTS[hour] = computeMarketBidCost( + fuelcost, + cluster.fuelEfficiency, + co2EmissionFactor, + co2cost, + cluster.variableomcost); + } + } +} + +double ScenarizedCostProvider::getOperatingCost(uint serieIndex, uint hourInTheYear) const +{ + const uint tsIndex = std::min(serieIndex, static_cast(costsTimeSeries.size()) - 1); + return costsTimeSeries[tsIndex].productionCostTs[hourInTheYear]; +} + +double ScenarizedCostProvider::getMarginalCost(uint serieIndex, uint hourInTheYear) const +{ + const double mod = cluster.modulation[thermalModulationMarketBid][hourInTheYear]; + const uint tsIndex = std::min(serieIndex, static_cast(costsTimeSeries.size()) - 1); + return costsTimeSeries[tsIndex].marginalCostTS[hourInTheYear] * mod; +} + +double ScenarizedCostProvider::getMarketBidCost(uint hourInTheYear, uint year) const +{ + const double mod = cluster.modulation[thermalModulationMarketBid][hourInTheYear]; + const uint serieIndex = cluster.series.getSeriesIndex(year); + const uint tsIndex = std::min(serieIndex, static_cast(costsTimeSeries.size()) - 1); + return costsTimeSeries[tsIndex].marketBidCostTS[hourInTheYear] * mod; +} + +} // namespace Antares::Data diff --git a/src/libs/antares/study/study.cpp b/src/libs/antares/study/study.cpp index 9db4360b03..0595bfadb5 100644 --- a/src/libs/antares/study/study.cpp +++ b/src/libs/antares/study/study.cpp @@ -490,6 +490,8 @@ fs::path StudyCreateOutputPath(SimulationMode mode, void Study::prepareOutput() { + pStartTime = DateTime::Now(); + if (parameters.noOutput || !usedByTheSolver) { return; diff --git a/src/solver/CMakeLists.txt b/src/solver/CMakeLists.txt index adf9825864..5ae822209b 100644 --- a/src/solver/CMakeLists.txt +++ b/src/solver/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(simulation) add_subdirectory(ts-generator) add_subdirectory(utils) add_subdirectory(variable) +add_subdirectory(optim-model-filler) # # Resource file for Windows diff --git a/src/solver/expressions/include/antares/solver/expressions/NodeRegistry.h b/src/solver/expressions/include/antares/solver/expressions/NodeRegistry.h index 994b8f6886..22b40bf011 100644 --- a/src/solver/expressions/include/antares/solver/expressions/NodeRegistry.h +++ b/src/solver/expressions/include/antares/solver/expressions/NodeRegistry.h @@ -9,14 +9,13 @@ class NodeRegistry { public: NodeRegistry() = default; - NodeRegistry(Antares::Solver::Nodes::Node* node, - Antares::Solver::Registry registry); + NodeRegistry(Nodes::Node* node, Registry registry); // Shallow copy NodeRegistry(NodeRegistry&&) = default; NodeRegistry& operator=(NodeRegistry&&) = default; - Antares::Solver::Nodes::Node* node; - Antares::Solver::Registry registry; + Nodes::Node* node; + Registry registry; }; } // namespace Antares::Solver diff --git a/src/solver/misc/options.cpp b/src/solver/misc/options.cpp index b228d00ddf..00572039a9 100644 --- a/src/solver/misc/options.cpp +++ b/src/solver/misc/options.cpp @@ -75,7 +75,7 @@ std::unique_ptr CreateParser(Settings& settings, StudyLoad "force-parallel", "Override the max number of years computed simultaneously"); - //--ortools-solver + //--solver parser->add(options.optOptions.ortoolsSolver, ' ', "solver", diff --git a/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h b/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h index 94c95e4e31..6885d81ac1 100644 --- a/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h +++ b/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h @@ -35,7 +35,7 @@ namespace Antares::Solver::Modeler::Api /** * Linear Problem * This class is aimed at creating and manipulating variables/constraints - * Also used to to control the objective, maximization or minimization, and to solve the problem + * Also used to control the objective, maximization or minimization, and to solve the problem */ class ILinearProblem { diff --git a/src/solver/modeler/api/include/antares/solver/modeler/api/mipVariable.h b/src/solver/modeler/api/include/antares/solver/modeler/api/mipVariable.h index 1caac002bf..c8e93acb53 100644 --- a/src/solver/modeler/api/include/antares/solver/modeler/api/mipVariable.h +++ b/src/solver/modeler/api/include/antares/solver/modeler/api/mipVariable.h @@ -29,6 +29,8 @@ namespace Antares::Solver::Modeler::Api class IMipVariable: public IHasBounds, public IHasName { +public: + virtual bool isInteger() const = 0; }; } // namespace Antares::Solver::Modeler::Api diff --git a/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipVariable.h b/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipVariable.h index 528ee0cd9e..a1c0a3fa91 100644 --- a/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipVariable.h +++ b/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipVariable.h @@ -44,6 +44,8 @@ class OrtoolsMipVariable final: public Api::IMipVariable const std::string& getName() const override; + bool isInteger() const override; + const operations_research::MPVariable* getMpVar() const; ~OrtoolsMipVariable() override = default; diff --git a/src/solver/modeler/ortoolsImpl/mipVariable.cpp b/src/solver/modeler/ortoolsImpl/mipVariable.cpp index 5e21924a8f..b29d0000a3 100644 --- a/src/solver/modeler/ortoolsImpl/mipVariable.cpp +++ b/src/solver/modeler/ortoolsImpl/mipVariable.cpp @@ -66,4 +66,9 @@ const std::string& OrtoolsMipVariable::getName() const return mpVar_->name(); } +bool OrtoolsMipVariable::isInteger() const +{ + return mpVar_->integer(); +} + } // namespace Antares::Solver::Modeler::OrtoolsImpl diff --git a/src/solver/optim-model-filler/CMakeLists.txt b/src/solver/optim-model-filler/CMakeLists.txt new file mode 100644 index 0000000000..8fb6bdc0e0 --- /dev/null +++ b/src/solver/optim-model-filler/CMakeLists.txt @@ -0,0 +1,29 @@ +set(PROJ optim-model-filler) + +set(SRC_optim_model_filler + include/antares/solver/optim-model-filler/ComponentFiller.h + include/antares/solver/optim-model-filler/LinearExpression.h + include/antares/solver/optim-model-filler/ReadLinearConstraintVisitor.h + include/antares/solver/optim-model-filler/ReadLinearExpressionVisitor.h + ComponentFiller.cpp + LinearExpression.cpp + ReadLinearConstraintVisitor.cpp + ReadLinearExpressionVisitor.cpp +) + +add_library(${PROJ} ${SRC_optim_model_filler}) +add_library(Antares::${PROJ} ALIAS ${PROJ}) + +set_target_properties(${PROJ} PROPERTIES LINKER_LANGUAGE CXX) + +target_link_libraries(${PROJ} + PRIVATE + Antares::solver-expressions + Antares::antares-study-system-model + Antares::modeler_api +) + +target_include_directories(${PROJ} + PUBLIC + $ +) diff --git a/src/solver/optim-model-filler/ComponentFiller.cpp b/src/solver/optim-model-filler/ComponentFiller.cpp new file mode 100644 index 0000000000..49a66cda11 --- /dev/null +++ b/src/solver/optim-model-filler/ComponentFiller.cpp @@ -0,0 +1,95 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +namespace Antares::Optimization +{ + +ComponentFiller::ComponentFiller(const Study::SystemModel::Component& component): + component_(component), + evaluationContext_(component_.getParameterValues(), {}) +{ +} + +void ComponentFiller::addVariables(Solver::Modeler::Api::ILinearProblem& pb, + Solver::Modeler::Api::LinearProblemData& data, + Solver::Modeler::Api::FillContext& ctx) +{ + auto evaluator = std::make_unique(evaluationContext_); + for (const auto& variable: component_.getModel()->Variables() | std::views::values) + { + pb.addVariable(evaluator->dispatch(variable.LowerBound().RootNode()), + evaluator->dispatch(variable.UpperBound().RootNode()), + variable.Type() != Study::SystemModel::ValueType::FLOAT, + component_.Id() + "." + variable.Id()); + } +} + +void ComponentFiller::addConstraints(Solver::Modeler::Api::ILinearProblem& pb, + Solver::Modeler::Api::LinearProblemData& data, + Solver::Modeler::Api::FillContext& ctx) +{ + ReadLinearConstraintVisitor visitor(evaluationContext_); + for (const auto& constraint: component_.getModel()->getConstraints() | std::views::values) + { + auto linear_constraint = visitor.dispatch(constraint.expression().RootNode()); + auto* ct = pb.addConstraint(linear_constraint.lb, + linear_constraint.ub, + component_.Id() + "." + constraint.Id()); + for (auto [var_id, coef]: linear_constraint.coef_per_var) + { + auto* variable = pb.getVariable(component_.Id() + "." + var_id); + ct->setCoefficient(variable, coef); + } + } +} + +void ComponentFiller::addObjective(Solver::Modeler::Api::ILinearProblem& pb, + Solver::Modeler::Api::LinearProblemData& data, + Solver::Modeler::Api::FillContext& ctx) +{ + if (component_.getModel()->Objective().Empty()) + { + return; + } + ReadLinearExpressionVisitor visitor(evaluationContext_); + auto linear_expression = visitor.dispatch(component_.getModel()->Objective().RootNode()); + if (abs(linear_expression.offset()) > 1e-10) + { + throw std::invalid_argument("Antares does not support objective offsets (found in model '" + + component_.getModel()->Id() + "' of component '" + + component_.Id() + "')."); + } + for (auto [var_id, coef]: linear_expression.coefPerVar()) + { + auto* variable = pb.getVariable(component_.Id() + "." + var_id); + pb.setObjectiveCoefficient(variable, coef); + } +} + +} // namespace Antares::Optimization diff --git a/src/solver/optim-model-filler/LinearExpression.cpp b/src/solver/optim-model-filler/LinearExpression.cpp new file mode 100644 index 0000000000..dd3d961215 --- /dev/null +++ b/src/solver/optim-model-filler/LinearExpression.cpp @@ -0,0 +1,120 @@ +/* + * 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 + +#include + +namespace Antares::Optimization +{ + +/** + * Element-wise sum of two [string, double] maps, preceded an element-wise multiplication of the + * right-hand-side map. Keys that do not exist in one of the two maps are considered to have a zero + * value. For every key: value = left_value + rhs_multiplier * right_value + * @param left The left hand side map + * @param right The right hand side map + * @param rhs_multiplier The multiplier to apply to the right hand side map + * @return The map resulting from the operation + */ +static std::map add_maps(const std::map& left, + const std::map& right, + double rhs_multiplier) +{ + std::map result(left); + for (auto [key, value]: right) + { + if (result.contains(key)) + { + result[key] += rhs_multiplier * value; + } + else + { + result[key] = rhs_multiplier * value; + } + } + return result; +} + +/** + * Element-wise multiplication of a map by a scale. + * For every key: final_value = scale * initial_value + * @param map The [string, double] map to scale + * @param scale The scale + * @return The scaled map + */ +static std::map scale_map(const std::map& map, + double scale) +{ + std::map result; + for (auto [key, value]: map) + { + result[key] = scale * value; + } + return result; +} + +LinearExpression::LinearExpression(double offset, std::map coef_per_var): + offset_(offset), + coef_per_var_(std::move(coef_per_var)) +{ +} + +LinearExpression LinearExpression::operator+(const LinearExpression& other) const +{ + return {offset_ + other.offset_, add_maps(coef_per_var_, other.coef_per_var_, 1)}; +} + +LinearExpression LinearExpression::operator-(const LinearExpression& other) const +{ + return {offset_ - other.offset_, add_maps(coef_per_var_, other.coef_per_var_, -1)}; +} + +LinearExpression LinearExpression::operator*(const LinearExpression& other) const +{ + if (coef_per_var_.empty()) + { + return {offset_ * other.offset_, scale_map(other.coef_per_var_, offset_)}; + } + else if (other.coef_per_var_.empty()) + { + return {offset_ * other.offset_, scale_map(coef_per_var_, other.offset_)}; + } + else + { + throw std::invalid_argument("A linear expression can't have quadratic terms."); + } +} + +LinearExpression LinearExpression::operator/(const LinearExpression& other) const +{ + if (!other.coef_per_var_.empty()) + { + throw std::invalid_argument("A linear expression can't have a variable as a dividend."); + } + return LinearExpression(offset_ / other.offset_, scale_map(coef_per_var_, 1 / other.offset_)); +} + +LinearExpression LinearExpression::negate() const +{ + return {-offset_, scale_map(coef_per_var_, -1)}; +} +} // namespace Antares::Optimization diff --git a/src/solver/optim-model-filler/ReadLinearConstraintVisitor.cpp b/src/solver/optim-model-filler/ReadLinearConstraintVisitor.cpp new file mode 100644 index 0000000000..9e33b5868e --- /dev/null +++ b/src/solver/optim-model-filler/ReadLinearConstraintVisitor.cpp @@ -0,0 +1,131 @@ +/* + * 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 +#include +#include + +using namespace Antares::Solver::Nodes; + +namespace Antares::Optimization +{ + +ReadLinearConstraintVisitor::ReadLinearConstraintVisitor( + Solver::Visitors::EvaluationContext context): + linear_expression_visitor_(std::move(context)) +{ +} + +std::string ReadLinearConstraintVisitor::name() const +{ + return "ReadLinearConstraintVisitor"; +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const EqualNode* node) +{ + auto leftMinusRight = linear_expression_visitor_.dispatch(node->left()) + - linear_expression_visitor_.dispatch(node->right()); + return LinearConstraint{.coef_per_var = leftMinusRight.coefPerVar(), + .lb = -leftMinusRight.offset(), + .ub = -leftMinusRight.offset()}; +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const LessThanOrEqualNode* node) +{ + auto leftMinusRight = linear_expression_visitor_.dispatch(node->left()) + - linear_expression_visitor_.dispatch(node->right()); + return LinearConstraint{.coef_per_var = leftMinusRight.coefPerVar(), + .ub = -leftMinusRight.offset()}; +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const GreaterThanOrEqualNode* node) +{ + auto leftMinusRight = linear_expression_visitor_.dispatch(node->left()) + - linear_expression_visitor_.dispatch(node->right()); + return LinearConstraint{.coef_per_var = leftMinusRight.coefPerVar(), + .lb = -leftMinusRight.offset()}; +} + +static std::invalid_argument IllegalNodeException() +{ + return std::invalid_argument("Root node of a constraint must be a comparator."); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const SumNode* sum_node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const SubtractionNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const MultiplicationNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const DivisionNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const NegationNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const VariableNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const ParameterNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const LiteralNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const PortFieldNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const PortFieldSumNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const ComponentVariableNode* node) +{ + throw IllegalNodeException(); +} + +LinearConstraint ReadLinearConstraintVisitor::visit(const ComponentParameterNode* node) +{ + throw IllegalNodeException(); +} +} // namespace Antares::Optimization diff --git a/src/solver/optim-model-filler/ReadLinearExpressionVisitor.cpp b/src/solver/optim-model-filler/ReadLinearExpressionVisitor.cpp new file mode 100644 index 0000000000..ccc722bdc0 --- /dev/null +++ b/src/solver/optim-model-filler/ReadLinearExpressionVisitor.cpp @@ -0,0 +1,124 @@ +/* + * 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 + +#include +#include +#include +#include + +using namespace Antares::Solver::Nodes; + +namespace Antares::Optimization +{ + +ReadLinearExpressionVisitor::ReadLinearExpressionVisitor( + Solver::Visitors::EvaluationContext context): + context_(std::move(context)) +{ +} + +std::string ReadLinearExpressionVisitor::name() const +{ + return "ReadLinearExpressionVisitor"; +} + +LinearExpression ReadLinearExpressionVisitor::visit(const SumNode* node) +{ + auto operands = node->getOperands(); + return std::accumulate(std::begin(operands), + std::end(operands), + LinearExpression(), + [this](LinearExpression sum, Node* operand) + { return sum + dispatch(operand); }); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const SubtractionNode* node) +{ + return dispatch(node->left()) - dispatch(node->right()); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const MultiplicationNode* node) +{ + return dispatch(node->left()) * dispatch(node->right()); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const DivisionNode* node) +{ + return dispatch(node->left()) / dispatch(node->right()); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const EqualNode* node) +{ + throw std::invalid_argument("A linear expression can't contain comparison operators."); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const LessThanOrEqualNode* node) +{ + throw std::invalid_argument("A linear expression can't contain comparison operators."); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const GreaterThanOrEqualNode* node) +{ + throw std::invalid_argument("A linear expression can't contain comparison operators."); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const NegationNode* node) +{ + return dispatch(node->child()).negate(); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const VariableNode* node) +{ + return LinearExpression(0, {{node->value(), 1}}); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const ParameterNode* node) +{ + return {context_.getParameterValue(node->value()), {}}; +} + +LinearExpression ReadLinearExpressionVisitor::visit(const LiteralNode* node) +{ + return {node->value(), {}}; +} + +LinearExpression ReadLinearExpressionVisitor::visit(const PortFieldNode* node) +{ + throw std::invalid_argument("ReadLinearExpressionVisitor cannot visit PortFieldNodes"); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const PortFieldSumNode* node) +{ + throw std::invalid_argument("ReadLinearExpressionVisitor cannot visit PortFieldSumNodes"); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const ComponentVariableNode* node) +{ + throw std::invalid_argument("ReadLinearExpressionVisitor cannot visit ComponentVariableNodes"); +} + +LinearExpression ReadLinearExpressionVisitor::visit(const ComponentParameterNode* node) +{ + throw std::invalid_argument("ReadLinearExpressionVisitor cannot visit ComponentParameterNodes"); +} +} // namespace Antares::Optimization diff --git a/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ComponentFiller.h b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ComponentFiller.h new file mode 100644 index 0000000000..db75c170b5 --- /dev/null +++ b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ComponentFiller.h @@ -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 . + */ + +#pragma once + +#include +#include +#include "antares/solver/expressions/visitors/EvaluationContext.h" + +namespace Antares::Study::SystemModel +{ +class Component; +} + +namespace Antares::Optimization +{ +/** + * Component filler + * Implements LinearProbleFiller interface. + * Fills a LinearProblem with variables, constraints, and objective coefficients of a Component + */ +class ComponentFiller: public Solver::Modeler::Api::LinearProblemFiller +{ +public: + ComponentFiller() = delete; + ComponentFiller(ComponentFiller& other) = delete; + /// Create a ComponentFiller for a Component + explicit ComponentFiller(const Study::SystemModel::Component& component); + + void addVariables(Solver::Modeler::Api::ILinearProblem& pb, + Solver::Modeler::Api::LinearProblemData& data, + Solver::Modeler::Api::FillContext& ctx) override; + void addConstraints(Solver::Modeler::Api::ILinearProblem& pb, + Solver::Modeler::Api::LinearProblemData& data, + Solver::Modeler::Api::FillContext& ctx) override; + void addObjective(Solver::Modeler::Api::ILinearProblem& pb, + Solver::Modeler::Api::LinearProblemData& data, + Solver::Modeler::Api::FillContext& ctx) override; + +private: + const Study::SystemModel::Component& component_; + Solver::Visitors::EvaluationContext evaluationContext_; +}; +} // namespace Antares::Optimization diff --git a/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/LinearExpression.h b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/LinearExpression.h new file mode 100644 index 0000000000..e53877b774 --- /dev/null +++ b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/LinearExpression.h @@ -0,0 +1,73 @@ +/* + * 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 + +#include +#include + +namespace Antares::Optimization +{ +/** + * Linear Expression + * Represents an expression that is linear in regard to an optimization problem's variables. + * It can be fully defined by: + * - the non-zero coefficients of the variables + * - a scalar offset + */ +class LinearExpression +{ +public: + /// Build a linear expression with zero offset and zero coefficients + LinearExpression() = default; + /// Build a linear expression with a given offset and a given map of non-zero coefficients per + /// variable ID + LinearExpression(double offset, std::map coef_per_var); + /// Sum two linear expressions + LinearExpression operator+(const LinearExpression& other) const; + /// Subtract two linear expressions + LinearExpression operator-(const LinearExpression& other) const; + /// Multiply two linear expressions + /// Only one can have non-zero coefficients, otherwise the result cannot be linear + LinearExpression operator*(const LinearExpression& other) const; + /// Divide two linear expressions + /// Only first expression can have non-zero coefficients, otherwise the result cannot be linear + LinearExpression operator/(const LinearExpression& other) const; + /// Multiply linear expression by -1 + LinearExpression negate() const; + + /// Get the offset + double offset() const + { + return offset_; + } + + /// Get the non-zero coefficients per variable ID + std::map coefPerVar() const + { + return coef_per_var_; + } + +private: + double offset_ = 0; + std::map coef_per_var_; +}; +} // namespace Antares::Optimization diff --git a/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ReadLinearConstraintVisitor.h b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ReadLinearConstraintVisitor.h new file mode 100644 index 0000000000..2951f7d1bf --- /dev/null +++ b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ReadLinearConstraintVisitor.h @@ -0,0 +1,78 @@ +/* + * 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 + +#include + +#include +#include + +#include "ReadLinearExpressionVisitor.h" + +/** + * Read Linear Constraint Visitor + * Visits a Node and produces a Linear Constraint (defined by its Linear Expression and bounds). + * The root node is expected to be a comparison node. + */ +namespace Antares::Optimization +{ + +/** + * Linear Constraint + * Represents a linear constraint in an optimization problem. + * It is fully defined by: + * - a Linear Expression (defined by an offset and non-zero variable coefficients) + * - a lower and an upper bounds + */ +struct LinearConstraint +{ + std::map coef_per_var; + double lb = -std::numeric_limits::infinity(); + double ub = std::numeric_limits::infinity(); +}; + +class ReadLinearConstraintVisitor: public Solver::Visitors::NodeVisitor +{ +public: + ReadLinearConstraintVisitor() = default; + explicit ReadLinearConstraintVisitor(Solver::Visitors::EvaluationContext context); + std::string name() const override; + +private: + ReadLinearExpressionVisitor linear_expression_visitor_; + LinearConstraint visit(const Solver::Nodes::SumNode* node) override; + LinearConstraint visit(const Solver::Nodes::SubtractionNode* node) override; + LinearConstraint visit(const Solver::Nodes::MultiplicationNode* node) override; + LinearConstraint visit(const Solver::Nodes::DivisionNode* node) override; + LinearConstraint visit(const Solver::Nodes::EqualNode* node) override; + LinearConstraint visit(const Solver::Nodes::LessThanOrEqualNode* node) override; + LinearConstraint visit(const Solver::Nodes::GreaterThanOrEqualNode* node) override; + LinearConstraint visit(const Solver::Nodes::NegationNode* node) override; + LinearConstraint visit(const Solver::Nodes::VariableNode* node) override; + LinearConstraint visit(const Solver::Nodes::ParameterNode* node) override; + LinearConstraint visit(const Solver::Nodes::LiteralNode* node) override; + LinearConstraint visit(const Solver::Nodes::PortFieldNode* node) override; + LinearConstraint visit(const Solver::Nodes::PortFieldSumNode* node) override; + LinearConstraint visit(const Solver::Nodes::ComponentVariableNode* node) override; + LinearConstraint visit(const Solver::Nodes::ComponentParameterNode* node) override; +}; +} // namespace Antares::Optimization diff --git a/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ReadLinearExpressionVisitor.h b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ReadLinearExpressionVisitor.h new file mode 100644 index 0000000000..ed86bf9617 --- /dev/null +++ b/src/solver/optim-model-filler/include/antares/solver/optim-model-filler/ReadLinearExpressionVisitor.h @@ -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 . + */ + +#pragma once + +#include +#include +#include + +/** + * Read Linear Expression Visitor + * Visits a Node and produces a Linear Expression (defined by an offset and non-zero + * coefficients of variables) + * Comparison Nodes are not allowed + */ +namespace Antares::Optimization +{ + +class ReadLinearExpressionVisitor: public Solver::Visitors::NodeVisitor +{ +public: + ReadLinearExpressionVisitor() = default; + explicit ReadLinearExpressionVisitor(Solver::Visitors::EvaluationContext context); + std::string name() const override; + +private: + const Solver::Visitors::EvaluationContext context_; + LinearExpression visit(const Solver::Nodes::SumNode* node) override; + LinearExpression visit(const Solver::Nodes::SubtractionNode* node) override; + LinearExpression visit(const Solver::Nodes::MultiplicationNode* node) override; + LinearExpression visit(const Solver::Nodes::DivisionNode* node) override; + LinearExpression visit(const Solver::Nodes::EqualNode* node) override; + LinearExpression visit(const Solver::Nodes::LessThanOrEqualNode* node) override; + LinearExpression visit(const Solver::Nodes::GreaterThanOrEqualNode* node) override; + LinearExpression visit(const Solver::Nodes::NegationNode* node) override; + LinearExpression visit(const Solver::Nodes::VariableNode* node) override; + LinearExpression visit(const Solver::Nodes::ParameterNode* node) override; + LinearExpression visit(const Solver::Nodes::LiteralNode* node) override; + LinearExpression visit(const Solver::Nodes::PortFieldNode* node) override; + LinearExpression visit(const Solver::Nodes::PortFieldSumNode* node) override; + LinearExpression visit(const Solver::Nodes::ComponentVariableNode* node) override; + LinearExpression visit(const Solver::Nodes::ComponentParameterNode* node) override; +}; +} // namespace Antares::Optimization diff --git a/src/solver/optimisation/LegacyFiller.cpp b/src/solver/optimisation/LegacyFiller.cpp index 7cbcaff820..c1f2f7edcb 100644 --- a/src/solver/optimisation/LegacyFiller.cpp +++ b/src/solver/optimisation/LegacyFiller.cpp @@ -46,9 +46,18 @@ void LegacyFiller::CopyMatrix(ILinearProblem& pb) const void LegacyFiller::CreateVariable(unsigned idxVar, ILinearProblem& pb) const { - double min_l = problemeSimplexe_->Xmin[idxVar]; - double max_l = problemeSimplexe_->Xmax[idxVar]; - bool isIntegerVariable = problemeSimplexe_->IntegerVariable(idxVar); + const double bMin = problemeSimplexe_->Xmin[idxVar]; + const double bMax = problemeSimplexe_->Xmax[idxVar]; + const int typeVar = problemeSimplexe_->TypeDeVariable[idxVar]; + + double min_l = (typeVar == VARIABLE_NON_BORNEE || typeVar == VARIABLE_BORNEE_SUPERIEUREMENT) + ? -pb.infinity() + : bMin; + double max_l = (typeVar == VARIABLE_NON_BORNEE || typeVar == VARIABLE_BORNEE_INFERIEUREMENT) + ? pb.infinity() + : bMax; + const bool isIntegerVariable = problemeSimplexe_->IntegerVariable(idxVar); + auto* var = pb.addVariable(min_l, max_l, isIntegerVariable, GetVariableName(idxVar)); pb.setObjectiveCoefficient(var, problemeSimplexe_->CoutLineaire[idxVar]); } 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 7030e38e4c..f9f8aa389b 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 @@ -27,15 +27,15 @@ namespace Antares::Solver::Simulation { AdqPatchPostProcessList::AdqPatchPostProcessList(const AdqPatchParams& adqPatchParams, PROBLEME_HEBDO* problemeHebdo, - uint thread_number, + uint numSpace, AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization splxOptimization, Calendar& calendar): - interfacePostProcessList(problemeHebdo, thread_number) + interfacePostProcessList(problemeHebdo, numSpace) { post_process_list.push_back( - std::make_unique(problemeHebdo_, thread_number_, areas)); + std::make_unique(problemeHebdo_, numSpace_, areas)); // Here a post process particular to adq patch post_process_list.push_back( std::make_unique(problemeHebdo_, areas, false, false)); @@ -43,15 +43,15 @@ AdqPatchPostProcessList::AdqPatchPostProcessList(const AdqPatchParams& adqPatchP areas, sheddingPolicy, splxOptimization, - thread_number)); + numSpace)); post_process_list.push_back(std::make_unique(adqPatchParams, problemeHebdo_, areas, - thread_number_)); + numSpace_)); post_process_list.push_back( - std::make_unique(problemeHebdo_, areas, thread_number)); + std::make_unique(problemeHebdo_, areas, numSpace)); post_process_list.push_back( - std::make_unique(problemeHebdo_, areas, thread_number)); + std::make_unique(problemeHebdo_, areas, numSpace)); post_process_list.push_back( std::make_unique(problemeHebdo_, areas, true, false)); post_process_list.push_back( diff --git a/src/solver/optimisation/include/antares/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.h b/src/solver/optimisation/include/antares/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.h index cba4657fb2..d73ce8cfd2 100644 --- a/src/solver/optimisation/include/antares/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.h +++ b/src/solver/optimisation/include/antares/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.h @@ -34,7 +34,7 @@ class AdqPatchPostProcessList: public interfacePostProcessList public: AdqPatchPostProcessList(const AdqPatchParams& adqPatchParams, PROBLEME_HEBDO* problemeHebdo, - uint thread_number, + uint numSpace, AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization splxOptimization, diff --git a/src/solver/optimisation/include/antares/solver/optimisation/optim_post_process_list.h b/src/solver/optimisation/include/antares/solver/optimisation/optim_post_process_list.h index eecd9edfba..833a556db1 100644 --- a/src/solver/optimisation/include/antares/solver/optimisation/optim_post_process_list.h +++ b/src/solver/optimisation/include/antares/solver/optimisation/optim_post_process_list.h @@ -29,7 +29,7 @@ class OptPostProcessList: public interfacePostProcessList { public: OptPostProcessList(PROBLEME_HEBDO* problemeHebdo, - uint thread_number, + uint numSpace, AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization splxOptimization, diff --git a/src/solver/optimisation/include/antares/solver/optimisation/post_process_commands.h b/src/solver/optimisation/include/antares/solver/optimisation/post_process_commands.h index 521225cde7..04c464fb39 100644 --- a/src/solver/optimisation/include/antares/solver/optimisation/post_process_commands.h +++ b/src/solver/optimisation/include/antares/solver/optimisation/post_process_commands.h @@ -28,12 +28,12 @@ class DispatchableMarginPostProcessCmd: public basePostProcessCommand { public: DispatchableMarginPostProcessCmd(PROBLEME_HEBDO* problemeHebdo, - unsigned int thread_number, + unsigned int numSpace, AreaList& areas); void execute(const optRuntimeData& opt_runtime_data) override; private: - unsigned int thread_number_ = 0; + unsigned int numSpace_ = 0; const AreaList& area_list_; }; @@ -59,12 +59,12 @@ class RemixHydroPostProcessCmd: public basePostProcessCommand AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization simplexOptimization, - unsigned int thread_number); + unsigned int numSpace); void execute(const optRuntimeData& opt_runtime_data) override; private: const AreaList& area_list_; - unsigned int thread_number_ = 0; + unsigned int numSpace_ = 0; SheddingPolicy shedding_policy_; SimplexOptimization splx_optimization_; }; @@ -74,12 +74,12 @@ class UpdateMrgPriceAfterCSRcmd: public basePostProcessCommand public: UpdateMrgPriceAfterCSRcmd(PROBLEME_HEBDO* problemeHebdo, AreaList& areas, - unsigned int thread_number); + unsigned int numSpace); void execute(const optRuntimeData&) override; private: const AreaList& area_list_; - unsigned int thread_number_ = 0; + unsigned int numSpace_ = 0; }; class DTGnettingAfterCSRcmd: public basePostProcessCommand @@ -87,12 +87,12 @@ class DTGnettingAfterCSRcmd: public basePostProcessCommand public: DTGnettingAfterCSRcmd(PROBLEME_HEBDO* problemeHebdo, AreaList& areas, - unsigned int thread_number); + unsigned int numSpace); void execute(const optRuntimeData&) override; private: const AreaList& area_list_; - unsigned int thread_number_ = 0; + unsigned int numSpace_ = 0; }; class InterpolateWaterValuePostProcessCmd: public basePostProcessCommand @@ -126,7 +126,7 @@ class CurtailmentSharingPostProcessCmd: public basePostProcessCommand CurtailmentSharingPostProcessCmd(const AdqPatchParams& adqPatchParams, PROBLEME_HEBDO* problemeHebdo, AreaList& areas, - unsigned int thread_number); + unsigned int numSpace); void execute(const optRuntimeData& opt_runtime_data) override; @@ -139,7 +139,7 @@ class CurtailmentSharingPostProcessCmd: public basePostProcessCommand using AdqPatchParams = Antares::Data::AdequacyPatch::AdqPatchParams; const AreaList& area_list_; const AdqPatchParams& adqPatchParams_; - unsigned int thread_number_ = 0; + unsigned int numSpace_ = 0; }; } // namespace Antares::Solver::Simulation diff --git a/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp b/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp index be6d609387..aa6219d905 100644 --- a/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp +++ b/src/solver/optimisation/opt_gestion_des_couts_cas_lineaire.cpp @@ -26,6 +26,7 @@ void OPT_InitialiserLesCoutsLineaireCoutsDeDemarrage(PROBLEME_HEBDO*, const int, const int); static void shortTermStorageCost( + int weekInTheYear, int PremierPdtDeLIntervalle, int DernierPdtDeLIntervalle, int NombreDePays, @@ -33,6 +34,7 @@ static void shortTermStorageCost( VariableManagement::VariableManager& variableManager, std::vector& linearCost) { + const int weekFirstHour = weekInTheYear * 168; for (int pays = 0; pays < NombreDePays; ++pays) { for (const auto& storage: shortTermStorageInput[pays]) @@ -41,12 +43,13 @@ static void shortTermStorageCost( pdtHebdo < DernierPdtDeLIntervalle; pdtHebdo++, pdtJour++) { + int hourInTheYear = weekFirstHour + pdtHebdo; const int clusterGlobalIndex = storage.clusterGlobalIndex; if (const int varLevel = variableManager.ShortTermStorageLevel(clusterGlobalIndex, pdtJour); varLevel >= 0) { - linearCost[varLevel] = storage.series->costLevel[pdtHebdo]; + linearCost[varLevel] = storage.series->costLevel[hourInTheYear]; } if (const int varInjection = variableManager.ShortTermStorageInjection( @@ -54,7 +57,7 @@ static void shortTermStorageCost( pdtJour); varInjection >= 0) { - linearCost[varInjection] = storage.series->costInjection[pdtHebdo]; + linearCost[varInjection] = storage.series->costInjection[hourInTheYear]; } if (const int varWithdrawal = variableManager.ShortTermStorageWithdrawal( @@ -62,7 +65,7 @@ static void shortTermStorageCost( pdtJour); varWithdrawal >= 0) { - linearCost[varWithdrawal] = storage.series->costWithdrawal[pdtHebdo]; + linearCost[varWithdrawal] = storage.series->costWithdrawal[hourInTheYear]; } if (const int varCostVariationInjection = variableManager .ShortTermStorageCostVariationInjection( @@ -70,16 +73,16 @@ static void shortTermStorageCost( pdtJour); storage.penalizeVariationInjection && varCostVariationInjection >= 0) { - linearCost[varCostVariationInjection] = storage.series - ->costVariationInjection[pdtHebdo]; + linearCost[varCostVariationInjection] = storage.series->costVariationInjection + [hourInTheYear]; } if (const int varCostVariationWithdrawal = variableManager.ShortTermStorageCostVariationWithdrawal(clusterGlobalIndex, pdtJour); storage.penalizeVariationWithdrawal && varCostVariationWithdrawal >= 0) { - linearCost[varCostVariationWithdrawal] = storage.series - ->costVariationWithdrawal[pdtHebdo]; + linearCost[varCostVariationWithdrawal] = storage.series->costVariationWithdrawal + [hourInTheYear]; } } } @@ -97,7 +100,8 @@ void OPT_InitialiserLesCoutsLineaire(PROBLEME_HEBDO* problemeHebdo, ProblemeAResoudre->CoutQuadratique.assign(ProblemeAResoudre->NombreDeVariables, 0.); auto variableManager = VariableManagerFromProblemHebdo(problemeHebdo); - shortTermStorageCost(PremierPdtDeLIntervalle, + shortTermStorageCost(problemeHebdo->weekInTheYear, + PremierPdtDeLIntervalle, DernierPdtDeLIntervalle, problemeHebdo->NombreDePays, problemeHebdo->ShortTermStorage, diff --git a/src/solver/optimisation/optim_post_process_list.cpp b/src/solver/optimisation/optim_post_process_list.cpp index a9eb38f7e8..9f52a11ac1 100644 --- a/src/solver/optimisation/optim_post_process_list.cpp +++ b/src/solver/optimisation/optim_post_process_list.cpp @@ -26,24 +26,24 @@ namespace Antares::Solver::Simulation { OptPostProcessList::OptPostProcessList(PROBLEME_HEBDO* problemeHebdo, - uint thread_number, + uint numSpace, AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization splxOptimization, Calendar& calendar) : - interfacePostProcessList(problemeHebdo, thread_number) + interfacePostProcessList(problemeHebdo, numSpace) { post_process_list.push_back( - std::make_unique(problemeHebdo_, thread_number_, areas)); + std::make_unique(problemeHebdo_, numSpace_, areas)); 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)); + numSpace)); post_process_list.push_back( std::make_unique(problemeHebdo_, areas, true, false)); post_process_list.push_back( diff --git a/src/solver/optimisation/post_process_commands.cpp b/src/solver/optimisation/post_process_commands.cpp index c768cac3e2..087b5e91a9 100644 --- a/src/solver/optimisation/post_process_commands.cpp +++ b/src/solver/optimisation/post_process_commands.cpp @@ -33,10 +33,10 @@ const uint nbHoursInWeek = 168; // Dispatchable Margin // ----------------------------- DispatchableMarginPostProcessCmd::DispatchableMarginPostProcessCmd(PROBLEME_HEBDO* problemeHebdo, - unsigned int thread_number, + unsigned int numSpace, AreaList& areas): basePostProcessCommand(problemeHebdo), - thread_number_(thread_number), + numSpace_(numSpace), area_list_(areas) { } @@ -48,7 +48,7 @@ void DispatchableMarginPostProcessCmd::execute(const optRuntimeData& opt_runtime area_list_.each( [this, &hourInYear, &year](Data::Area& area) { - double* dtgmrg = area.scratchpad[thread_number_].dispatchableGenerationMargin; + double* dtgmrg = area.scratchpad[numSpace_].dispatchableGenerationMargin; for (uint h = 0; h != nbHoursInWeek; ++h) { dtgmrg[h] = 0.; @@ -95,10 +95,10 @@ RemixHydroPostProcessCmd::RemixHydroPostProcessCmd(PROBLEME_HEBDO* problemeHebdo AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization simplexOptimization, - unsigned int thread_number): + unsigned int numSpace): basePostProcessCommand(problemeHebdo), area_list_(areas), - thread_number_(thread_number), + numSpace_(numSpace), shedding_policy_(sheddingPolicy), splx_optimization_(simplexOptimization) { @@ -111,7 +111,7 @@ void RemixHydroPostProcessCmd::execute(const optRuntimeData& opt_runtime_data) *problemeHebdo_, shedding_policy_, splx_optimization_, - thread_number_, + numSpace_, hourInYear); } @@ -122,10 +122,10 @@ using namespace Antares::Data::AdequacyPatch; UpdateMrgPriceAfterCSRcmd::UpdateMrgPriceAfterCSRcmd(PROBLEME_HEBDO* problemeHebdo, AreaList& areas, - unsigned int thread_number): + unsigned int numSpace): basePostProcessCommand(problemeHebdo), area_list_(areas), - thread_number_(thread_number) + numSpace_(numSpace) { } @@ -134,7 +134,7 @@ void UpdateMrgPriceAfterCSRcmd::execute(const optRuntimeData&) for (uint32_t Area = 0; Area < problemeHebdo_->NombreDePays; Area++) { auto& hourlyResults = problemeHebdo_->ResultatsHoraires[Area]; - const auto& scratchpad = area_list_[Area]->scratchpad[thread_number_]; + const auto& scratchpad = area_list_[Area]->scratchpad[numSpace_]; const double unsuppliedEnergyCost = area_list_[Area]->thermal.unsuppliedEnergyCost; const bool areaInside = problemeHebdo_->adequacyPatchRuntimeData->areaMode[Area] == physicalAreaInsideAdqPatch; @@ -157,10 +157,10 @@ void UpdateMrgPriceAfterCSRcmd::execute(const optRuntimeData&) // ----------------------------- DTGnettingAfterCSRcmd::DTGnettingAfterCSRcmd(PROBLEME_HEBDO* problemeHebdo, AreaList& areas, - unsigned int thread_number): + unsigned int numSpace): basePostProcessCommand(problemeHebdo), area_list_(areas), - thread_number_(thread_number) + numSpace_(numSpace) { } @@ -169,7 +169,7 @@ void DTGnettingAfterCSRcmd::execute(const optRuntimeData&) for (uint32_t Area = 0; Area < problemeHebdo_->NombreDePays; Area++) { auto& hourlyResults = problemeHebdo_->ResultatsHoraires[Area]; - const auto& scratchpad = area_list_[Area]->scratchpad[thread_number_]; + const auto& scratchpad = area_list_[Area]->scratchpad[numSpace_]; const double unsuppliedEnergyCost = area_list_[Area]->thermal.unsuppliedEnergyCost; for (uint hour = 0; hour < nbHoursInWeek; hour++) @@ -242,11 +242,11 @@ CurtailmentSharingPostProcessCmd::CurtailmentSharingPostProcessCmd( const AdqPatchParams& adqPatchParams, PROBLEME_HEBDO* problemeHebdo, AreaList& areas, - unsigned int thread_number): + unsigned int numSpace): basePostProcessCommand(problemeHebdo), area_list_(areas), adqPatchParams_(adqPatchParams), - thread_number_(thread_number) + numSpace_(numSpace) { } @@ -287,7 +287,7 @@ double CurtailmentSharingPostProcessCmd::calculateDensNewAndTotalLmrViolation() // 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)] */ - const auto& scratchpad = area_list_[Area]->scratchpad[thread_number_]; + const auto& scratchpad = area_list_[Area]->scratchpad[numSpace_]; double dtgMrg = scratchpad.dispatchableGenerationMargin[hour]; // write down densNew values for all the hours problemeHebdo_->ResultatsHoraires[Area].ValeursHorairesDENS[hour] = std::max( diff --git a/src/solver/simulation/base_post_process.cpp b/src/solver/simulation/base_post_process.cpp index 4cd7bb8cf9..d3c2da7f3c 100644 --- a/src/solver/simulation/base_post_process.cpp +++ b/src/solver/simulation/base_post_process.cpp @@ -36,14 +36,14 @@ basePostProcessCommand::basePostProcessCommand(PROBLEME_HEBDO* problemeHebdo): interfacePostProcessList::interfacePostProcessList(PROBLEME_HEBDO* problemesHebdo, uint numSpace): problemeHebdo_(problemesHebdo), - thread_number_(numSpace) + numSpace_(numSpace) { } std::unique_ptr interfacePostProcessList::create( AdqPatchParams& adqPatchParams, PROBLEME_HEBDO* problemeHebdo, - uint thread_number, + uint numSpace, AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization splxOptimization, @@ -53,7 +53,7 @@ std::unique_ptr interfacePostProcessList::create( { return std::make_unique(adqPatchParams, problemeHebdo, - thread_number, + numSpace, areas, sheddingPolicy, splxOptimization, @@ -62,7 +62,7 @@ std::unique_ptr interfacePostProcessList::create( else { return std::make_unique(problemeHebdo, - thread_number, + numSpace, areas, sheddingPolicy, splxOptimization, diff --git a/src/solver/simulation/common-eco-adq.cpp b/src/solver/simulation/common-eco-adq.cpp index 5b46e35cce..59eb02e9b4 100644 --- a/src/solver/simulation/common-eco-adq.cpp +++ b/src/solver/simulation/common-eco-adq.cpp @@ -378,7 +378,7 @@ void BuildThermalPartOfWeeklyProblem(Data::Study& study, .PuissanceDisponibleEtCout[cluster->index]; Pt.CoutHoraireDeProductionDuPalierThermique[hourInWeek] - = cluster->getMarketBidCost(hourInYear, year) + = cluster->getCostProvider().getMarketBidCost(hourInYear, year) + thermalNoises[areaIdx][cluster->areaWideIndex]; Pt.PuissanceDisponibleDuPalierThermique[hourInWeek] = cluster->series diff --git a/src/solver/simulation/include/antares/solver/simulation/base_post_process.h b/src/solver/simulation/include/antares/solver/simulation/base_post_process.h index 6918fb85d3..f1e6215415 100644 --- a/src/solver/simulation/include/antares/solver/simulation/base_post_process.h +++ b/src/solver/simulation/include/antares/solver/simulation/base_post_process.h @@ -71,7 +71,7 @@ class interfacePostProcessList // gp : the constructors' signatures of the post process list classes. static std::unique_ptr create(AdqPatchParams& adqPatchParams, PROBLEME_HEBDO* problemeHebdo, - uint thread_number, + uint numSpace, AreaList& areas, SheddingPolicy sheddingPolicy, SimplexOptimization splxOptimization, @@ -80,11 +80,11 @@ class interfacePostProcessList protected: // Member functions - interfacePostProcessList(PROBLEME_HEBDO* problemeHebdo, uint thread_number); + interfacePostProcessList(PROBLEME_HEBDO* problemeHebdo, uint numSpace); // Data mambers PROBLEME_HEBDO* const problemeHebdo_ = nullptr; - const unsigned int thread_number_ = 0; + const unsigned int numSpace_; std::vector> post_process_list; }; diff --git a/src/solver/variable/include/antares/solver/variable/economy/profitByPlant.h b/src/solver/variable/include/antares/solver/variable/economy/profitByPlant.h index 2dd9378067..20e1d6e228 100644 --- a/src/solver/variable/include/antares/solver/variable/economy/profitByPlant.h +++ b/src/solver/variable/include/antares/solver/variable/economy/profitByPlant.h @@ -288,7 +288,7 @@ class ProfitByPlant: public Variable::IVariable, NextT, VCa pValuesForTheCurrentYear[numSpace][cluster->areaWideIndex].hour[hourInTheYear] = std::max((hourlyClusterProduction - cluster->PthetaInf[hourInTheYear]), 0.) * (-areaMarginalCosts[hourInTheWeek] - - cluster->getMarginalCost(tsIndex, hourInTheYear)); + - cluster->getCostProvider().getMarginalCost(tsIndex, hourInTheYear)); } // Next variable diff --git a/src/solver/variable/state.cpp b/src/solver/variable/state.cpp index 6657a5a23b..0e9f0eca8e 100644 --- a/src/solver/variable/state.cpp +++ b/src/solver/variable/state.cpp @@ -199,7 +199,7 @@ void State::initFromThermalClusterIndexProduction(const uint clusterAreaWideInde // calculating the operating cost for the current hour // O(h) = MA * P(h) * Modulation thermal[area->index].thermalClustersOperatingCost[clusterAreaWideIndex] - = (p * thermalCluster->getOperatingCost(serieIndex, hourInTheYear)); + = (p * thermalCluster->getCostProvider().getOperatingCost(serieIndex, hourInTheYear)); // Startup cost if (newUnitCount > previousUnitCount && hourInTheSimulation != 0u) @@ -297,7 +297,8 @@ void State::yearEndBuildFromThermalClusterIndex(const uint clusterAreaWideIndex) uint serieIndex = currentCluster->series.timeseriesNumbers[this->year]; thermalClusterOperatingCostForYear[h] = thermalClusterProduction - * currentCluster->getOperatingCost(serieIndex, h); + * currentCluster->getCostProvider() + .getOperatingCost(serieIndex, h); switch (unitCommitmentMode) { diff --git a/src/study/system-model/CMakeLists.txt b/src/study/system-model/CMakeLists.txt index 19cbfa7abb..c7e2208c41 100644 --- a/src/study/system-model/CMakeLists.txt +++ b/src/study/system-model/CMakeLists.txt @@ -31,7 +31,7 @@ target_include_directories(antares-study-system-model ) target_link_libraries(antares-study-system-model PUBLIC - Antares::solver-expressions + Antares::solver-expressions ) install(DIRECTORY include/antares DESTINATION "include" diff --git a/src/study/system-model/component.cpp b/src/study/system-model/component.cpp index b9b7c6a03b..1c3d360e64 100644 --- a/src/study/system-model/component.cpp +++ b/src/study/system-model/component.cpp @@ -81,7 +81,7 @@ ComponentBuilder& ComponentBuilder::withId(const std::string_view id) * \param model The model to set. * \return Reference to the ComponentBuilder object. */ -ComponentBuilder& ComponentBuilder::withModel(Model* model) +ComponentBuilder& ComponentBuilder::withModel(const Model* model) { data_.model = model; return *this; @@ -118,9 +118,11 @@ ComponentBuilder& ComponentBuilder::withScenarioGroupId(const std::string& scena * * \return The constructed Component object. */ -Component ComponentBuilder::build() const +Component ComponentBuilder::build() { - return Component(data_); + Component component(data_); + data_.reset(); // makes the ComponentBuilder re-usable + return component; } } // namespace Antares::Study::SystemModel diff --git a/src/study/system-model/include/antares/study/system-model/component.h b/src/study/system-model/include/antares/study/system-model/component.h index d3be08ba80..6195a6496c 100644 --- a/src/study/system-model/include/antares/study/system-model/component.h +++ b/src/study/system-model/include/antares/study/system-model/component.h @@ -31,12 +31,21 @@ namespace Antares::Study::SystemModel * Defines the attributes of the Component class * Made into a struct to avoid duplication in ComponentBuilder */ -struct ComponentData +class ComponentData { +public: std::string id; - Model* model = nullptr; + const Model* model = nullptr; std::map parameter_values; std::string scenario_group_id; + + void reset() + { + id.clear(); + model = nullptr; + parameter_values.clear(); + scenario_group_id.clear(); + } }; /** @@ -53,11 +62,16 @@ class Component return data_.id; } - Model* getModel() const + const Model* getModel() const { return data_.model; } + const std::map& getParameterValues() const + { + return data_.parameter_values; + } + double getParameterValue(const std::string& parameter_id) const { if (!data_.parameter_values.contains(parameter_id)) @@ -84,10 +98,10 @@ class ComponentBuilder { public: ComponentBuilder& withId(std::string_view id); - ComponentBuilder& withModel(Model* model); + ComponentBuilder& withModel(const Model* model); ComponentBuilder& withParameterValues(std::map parameter_values); ComponentBuilder& withScenarioGroupId(const std::string& scenario_group_id); - Component build() const; + Component build(); private: ComponentData data_; diff --git a/src/study/system-model/include/antares/study/system-model/expression.h b/src/study/system-model/include/antares/study/system-model/expression.h index 0b52d82f91..cf8ea30a61 100644 --- a/src/study/system-model/include/antares/study/system-model/expression.h +++ b/src/study/system-model/include/antares/study/system-model/expression.h @@ -32,14 +32,16 @@ class Node; namespace Antares::Study::SystemModel { +// TODO: add unit tests for this class class Expression { public: Expression() = default; - explicit Expression(const std::string& value, Antares::Solver::NodeRegistry root): + explicit Expression(const std::string& value, Solver::NodeRegistry root): value_(value), - root_(std::move(root)) + root_(std::move(root)), + empty_(false) { } @@ -48,9 +50,20 @@ class Expression return value_; } + Solver::Nodes::Node* RootNode() const + { + return root_.node; + } + + bool Empty() const + { + return empty_; + } + private: std::string value_; - Antares::Solver::NodeRegistry root_; + Solver::NodeRegistry root_; + bool empty_ = true; }; } // namespace Antares::Study::SystemModel diff --git a/src/study/system-model/include/antares/study/system-model/model.h b/src/study/system-model/include/antares/study/system-model/model.h index a050424131..a878838315 100644 --- a/src/study/system-model/include/antares/study/system-model/model.h +++ b/src/study/system-model/include/antares/study/system-model/model.h @@ -36,6 +36,7 @@ namespace Antares::Study::SystemModel * Defines a model that can be referenced by actual components. * A model defines the behaviour of those components. */ +// TODO: add unit tests for this class class Model { public: @@ -69,6 +70,7 @@ class Model const std::map& Variables() const { + // TODO : convert to vector? return variables_; } diff --git a/src/study/system-model/model.cpp b/src/study/system-model/model.cpp index fd77cb3cf2..18d6b15e8c 100644 --- a/src/study/system-model/model.cpp +++ b/src/study/system-model/model.cpp @@ -36,7 +36,9 @@ namespace Antares::Study::SystemModel */ Model ModelBuilder::build() { - return std::move(model_); + Model model = std::move(model_); + model_ = Model(); // makes ModelBuilder re-usable + return std::move(model); } /** diff --git a/src/tests/examples/conftest.py b/src/tests/examples/conftest.py index 49bfbdbce4..89d6f8ca7b 100644 --- a/src/tests/examples/conftest.py +++ b/src/tests/examples/conftest.py @@ -1,19 +1,13 @@ import pytest def pytest_addoption(parser): - parser.addoption("--use-ortools", action="store_true", default=False) - parser.addoption("--ortools-solver", action="store", default="sirius") + parser.addoption("--solver", action="store", default="sirius") parser.addoption("--solver-path", action="store") @pytest.fixture() def ortools_solver(request): - return request.config.getoption("--ortools-solver") + return request.config.getoption("--solver") -@pytest.fixture() -def use_ortools(request): - return request.config.getoption("--use-ortools") - @pytest.fixture() def solver_path(request): return request.config.getoption("--solver-path") - diff --git a/src/tests/examples/specific_test.py b/src/tests/examples/specific_test.py index 6372f5892a..bf4d997381 100644 --- a/src/tests/examples/specific_test.py +++ b/src/tests/examples/specific_test.py @@ -72,16 +72,14 @@ def remove_outputs(study_path): shutil.rmtree(f) -def launch_solver(solver_path, study_path, use_ortools=False, ortools_solver="sirius"): +def launch_solver(solver_path, study_path, ortools_solver="sirius"): # Clean study output remove_outputs(study_path) solver_path_full = str(Path(solver_path).resolve()) command = [solver_path_full, "-i", str(study_path)] - if use_ortools: - command.append('--use-ortools') - command.append('--ortools-solver=' + ortools_solver) + command.append('--solver=' + ortools_solver) process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None) output = process.communicate() diff --git a/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp b/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp index 5e0da57a10..6015fcbbc0 100644 --- a/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp +++ b/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp @@ -104,6 +104,26 @@ BOOST_FIXTURE_TEST_CASE(invalidValues, Fixture) BOOST_CHECK_EQUAL(p.renewableGeneration(), rgUnknown); } +BOOST_FIXTURE_TEST_CASE(hydroPmax, Fixture) +{ + BOOST_CHECK(p.compatibility.hydroPmax == Parameters::Compatibility::HydroPmax::Daily); + + writeValidFile(); + BOOST_CHECK(p.loadFromFile(path.string(), version)); + p.validateOptions(options); + p.fixBadValues(); + + BOOST_CHECK(p.compatibility.hydroPmax == Parameters::Compatibility::HydroPmax::Hourly); + + BOOST_CHECK_EQUAL(CompatibilityHydroPmaxToCString(p.compatibility.hydroPmax), "hourly"); + BOOST_CHECK_EQUAL(CompatibilityHydroPmaxToCString(Parameters::Compatibility::HydroPmax::Daily), + "daily"); + + BOOST_CHECK(StringToCompatibilityHydroPmax(p.compatibility.hydroPmax, "daily")); + BOOST_CHECK(!StringToCompatibilityHydroPmax(p.compatibility.hydroPmax, "")); + BOOST_CHECK(!StringToCompatibilityHydroPmax(p.compatibility.hydroPmax, "abc")); +} + BOOST_AUTO_TEST_SUITE_END() void Fixture::writeInvalidFile() @@ -215,7 +235,10 @@ void Fixture::writeValidFile() seed-spilled-energy-costs = 7005489 seed-thermal-costs = 8005489 seed-hydro-costs = 9005489 - seed-initial-reservoir-levels = 10005489)"; + seed-initial-reservoir-levels = 10005489 + + [compatibility] + hydro-pmax = hourly)"; outfile.close(); } 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 cfef5909e5..b8eeb41d51 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 @@ -101,13 +101,6 @@ struct TimeSeriesFile const std::string name_; }; -void fillThermalEconomicTimeSeries(ThermalCluster* c) -{ - c->costsTimeSeries[0].productionCostTs.fill(1); - c->costsTimeSeries[0].marketBidCostTS.fill(1); - c->costsTimeSeries[0].marginalCostTS.fill(1); -} - // ================= // The fixture // ================= @@ -231,11 +224,14 @@ BOOST_FIXTURE_TEST_CASE(ThermalCluster_costGenManualCalculationOfMarketBidAndMar clusterList.loadFromFolder(*study, folder, area); auto cluster = clusterList.findInAll("some cluster"); + cluster->modulation.resize(thermalModulationMax, HOURS_PER_YEAR); + cluster->modulation.fill(1.); + cluster->costgeneration = Data::setManually; - cluster->ComputeCostTimeSeries(); - BOOST_CHECK_EQUAL(cluster->costsTimeSeries[0].marketBidCostTS[2637], 35); - BOOST_CHECK_EQUAL(cluster->costsTimeSeries[0].marginalCostTS[6737], 23); + auto& cp = cluster->getCostProvider(); + BOOST_CHECK_EQUAL(cp.getMarketBidCost(2637, 0), 35); + BOOST_CHECK_EQUAL(cp.getMarginalCost(6737, 0), 23); } BOOST_FIXTURE_TEST_CASE( @@ -248,21 +244,48 @@ BOOST_FIXTURE_TEST_CASE( clusterList.loadFromFolder(*study, folder, area); auto cluster = clusterList.findInAll("some cluster"); - cluster->modulation.reset(1, 8760); + cluster->modulation.resize(thermalModulationMax, HOURS_PER_YEAR); + cluster->modulation.fill(1.); + cluster->ecoInput.loadFromFolder(*study, folder); - fillThermalEconomicTimeSeries(cluster); - cluster->ComputeCostTimeSeries(); + cluster->tsNumbers.reset(1); - BOOST_CHECK_CLOSE(cluster->costsTimeSeries[0].marketBidCostTS[0], 24.12, 0.001); - BOOST_CHECK_CLOSE(cluster->costsTimeSeries[0].marketBidCostTS[2637], 24.12, 0.001); + auto& cp = cluster->getCostProvider(); + BOOST_CHECK_CLOSE(cp.getMarginalCost(0, 0), 24.12, 0.001); + BOOST_CHECK_CLOSE(cp.getMarketBidCost(2637, 0), 24.12, 0.001); } -BOOST_FIXTURE_TEST_CASE(computeMarketBidCost, FixtureFull) +BOOST_FIXTURE_TEST_CASE(computeMarketBidCost_useTimeSeries, FixtureFull) { clusterList.loadFromFolder(*study, folder, area); auto cluster = clusterList.findInAll("some cluster"); + BOOST_CHECK_CLOSE( + computeMarketBidCost(1, cluster->fuelEfficiency, 2, 1, cluster->variableomcost), + 24.12, + 0.001); +} + +BOOST_AUTO_TEST_CASE(non_constant_marketbid_modulation) +{ + Area area; + ThermalCluster cluster(&area); + cluster.costgeneration = setManually; + cluster.marketBidCost = 120; - BOOST_CHECK_CLOSE(cluster->computeMarketBidCost(1, 2, 1), 24.12, 0.001); + auto& mod = cluster.modulation; + mod.resize(thermalModulationMax, HOURS_PER_YEAR); + mod.fill(1.); + + { + mod[thermalModulationMarketBid][0] = .5; + BOOST_CHECK_EQUAL(cluster.getCostProvider().getMarketBidCost(0, 0), .5 * 120); + } + + { + mod[thermalModulationMarketBid][1] = .8; + BOOST_CHECK_EQUAL(cluster.getCostProvider().getMarketBidCost(1, 0), .8 * 120); + } } + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/src/solver/CMakeLists.txt b/src/tests/src/solver/CMakeLists.txt index 2152a8e1bf..9cf4843bd8 100644 --- a/src/tests/src/solver/CMakeLists.txt +++ b/src/tests/src/solver/CMakeLists.txt @@ -4,5 +4,6 @@ add_subdirectory(lps) add_subdirectory(modelParser) add_subdirectory(modeler) add_subdirectory(optimisation) +add_subdirectory(optim-model-filler) add_subdirectory(simulation) add_subdirectory(utils) diff --git a/src/tests/src/solver/modeler/api/testModelerLinearProblemWithOrtools.cpp b/src/tests/src/solver/modeler/api/testModelerLinearProblemWithOrtools.cpp index 7c1f8f84e3..efad36d77c 100644 --- a/src/tests/src/solver/modeler/api/testModelerLinearProblemWithOrtools.cpp +++ b/src/tests/src/solver/modeler/api/testModelerLinearProblemWithOrtools.cpp @@ -65,6 +65,7 @@ BOOST_FIXTURE_TEST_CASE(add_int_variable_to_problem___check_var_exists, FixtureE pb->addIntVariable(5, 15, "var"); auto* var = pb->getVariable("var"); BOOST_CHECK(var); + BOOST_CHECK(var->isInteger()); BOOST_CHECK_EQUAL(var->getLb(), 5); BOOST_CHECK_EQUAL(var->getUb(), 15); } @@ -74,6 +75,7 @@ BOOST_FIXTURE_TEST_CASE(add_num_variable_to_problem___check_var_exists, FixtureE pb->addNumVariable(2., 7., "var"); auto* var = pb->getVariable("var"); BOOST_CHECK(var); + BOOST_CHECK(!var->isInteger()); BOOST_CHECK_EQUAL(var->getLb(), 2.); BOOST_CHECK_EQUAL(var->getUb(), 7.); } diff --git a/src/tests/src/solver/optim-model-filler/CMakeLists.txt b/src/tests/src/solver/optim-model-filler/CMakeLists.txt new file mode 100644 index 0000000000..fa81c819b7 --- /dev/null +++ b/src/tests/src/solver/optim-model-filler/CMakeLists.txt @@ -0,0 +1,29 @@ +set(EXECUTABLE_NAME unit-tests-for-component-filler) +add_executable(${EXECUTABLE_NAME}) + +target_sources(${EXECUTABLE_NAME} + PRIVATE + test_main.cpp + test_componentFiller.cpp + test_linearExpression.cpp + test_readLinearExpressionVisitor.cpp + test_readLinearConstraintVisitor.cpp +) +target_include_directories(${EXECUTABLE_NAME} + PRIVATE + "${src_solver_optimisation}" +) + +target_link_libraries(${EXECUTABLE_NAME} + PRIVATE + Boost::unit_test_framework + antares-study-system-model + Antares::modeler-ortools-impl + Antares::optim-model-filler + test_utils_unit +) + +set_target_properties(${EXECUTABLE_NAME} PROPERTIES FOLDER Unit-tests) +add_test(NAME ${EXECUTABLE_NAME} COMMAND ${EXECUTABLE_NAME}) +set_property(TEST ${EXECUTABLE_NAME} PROPERTY LABELS unit) + diff --git a/src/tests/src/solver/optim-model-filler/test_componentFiller.cpp b/src/tests/src/solver/optim-model-filler/test_componentFiller.cpp new file mode 100644 index 0000000000..c1a5a52ec9 --- /dev/null +++ b/src/tests/src/solver/optim-model-filler/test_componentFiller.cpp @@ -0,0 +1,514 @@ +/* + * 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 . + */ + +#define WIN32_LEAN_AND_MEAN + +#include + +#include "antares/solver/expressions/nodes/ExpressionsNodes.h" +#include "antares/solver/modeler/api/linearProblemBuilder.h" +#include "antares/solver/modeler/ortoolsImpl/linearProblem.h" +#include "antares/solver/optim-model-filler/ComponentFiller.h" +#include "antares/study/system-model/component.h" +#include "antares/study/system-model/parameter.h" + +#include "unit_test_utils.h" + +using namespace Antares::Solver::Modeler::Api; +using namespace Antares::Study::SystemModel; +using namespace Antares::Optimization; +using namespace Antares::Solver::Nodes; +using namespace std; + +struct VariableData +{ + string id; + ValueType type; + Node* lb; + Node* ub; +}; + +struct ConstraintData +{ + string id; + Node* expression; +}; + +struct LinearProblemBuildingFixture +{ + map models; + Antares::Solver::Registry nodes; + vector components; + unique_ptr pb; + + void createModel(string modelId, + vector parameterIds, + vector variablesData, + vector constraintsData, + Node* objective = nullptr); + + void createModelWithOneFloatVar(const string& modelId, + const vector& parameterIds, + const string& varId, + Node* lb, + Node* ub, + const vector& constraintsData, + Node* objective = nullptr) + { + createModel(modelId, + parameterIds, + {{varId, ValueType::FLOAT, lb, ub}}, + constraintsData, + objective); + } + + void createComponent(const string& modelId, + const string& componentId, + map parameterValues = {}); + + Node* literal(double value) + { + return nodes.create(value); + } + + Node* parameter(const string& paramId) + { + return nodes.create(paramId); + } + + Node* variable(const string& varId) + { + return nodes.create(varId); + } + + Node* multiply(Node* node1, Node* node2) + { + return nodes.create(node1, node2); + } + + Node* negate(Node* node) + { + return nodes.create(node); + } + + void buildLinearProblem(); +}; + +void LinearProblemBuildingFixture::createModel(string modelId, + vector parameterIds, + vector variablesData, + vector constraintsData, + Node* objective) +{ + auto createExpression = [this](Node* node) + { + Antares::Solver::NodeRegistry node_registry(node, move(nodes)); + Expression expression("expression", move(node_registry)); + return move(expression); + }; + vector parameters; + for (auto parameter_id: parameterIds) + { + parameters.push_back( + Parameter(parameter_id, Parameter::TimeDependent::NO, Parameter::ScenarioDependent::NO)); + } + vector variables; + for (auto [id, type, lb, ub]: variablesData) + { + variables.push_back(move(Variable(id, createExpression(lb), createExpression(ub), type))); + } + vector constraints; + for (auto [id, expression]: constraintsData) + { + constraints.push_back(move(Constraint(id, createExpression(expression)))); + } + ModelBuilder model_builder; + model_builder.withId(modelId) + .withParameters(move(parameters)) + .withVariables(move(variables)) + .withConstraints(move(constraints)); + if (objective) + { + model_builder.withObjective(createExpression(objective)); + } + auto model = model_builder.build(); + models[modelId] = move(model); +} + +void LinearProblemBuildingFixture::createComponent(const string& modelId, + const string& componentId, + map parameterValues) +{ + BOOST_CHECK_NO_THROW(models.at(modelId)); + ComponentBuilder component_builder; + auto component = component_builder.withId(componentId) + .withModel(&models.at(modelId)) + .withScenarioGroupId("scenario_group") + .withParameterValues(move(parameterValues)) + .build(); + components.push_back(move(component)); +} + +void LinearProblemBuildingFixture::buildLinearProblem() +{ + vector> fillers; + vector fillers_ptr; + for (auto& component: components) + { + auto cf = make_unique(component); + fillers.push_back(move(cf)); + } + for (auto& component_filler: fillers) + { + fillers_ptr.push_back(component_filler.get()); + } + pb = make_unique(false, "scip"); + LinearProblemBuilder linear_problem_builder(fillers_ptr); + LinearProblemData dummy_data; + FillContext dummy_time_scenario_ctx = {0, 0}; + linear_problem_builder.build(*pb.get(), dummy_data, dummy_time_scenario_ctx); +} + +BOOST_FIXTURE_TEST_SUITE(_ComponentFiller_addVariables_, LinearProblemBuildingFixture) + +BOOST_AUTO_TEST_CASE(var_with_literal_bounds_to_filler__problem_contains_one_var) +{ + createModelWithOneFloatVar("some_model", {}, "var1", literal(-5), literal(10), {}); + createComponent("some_model", "some_component"); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 1); + BOOST_CHECK_EQUAL(pb->constraintCount(), 0); + auto* var = pb->getVariable("some_component.var1"); + BOOST_CHECK(var); + BOOST_CHECK_EQUAL(var->getLb(), -5); + BOOST_CHECK_EQUAL(var->getUb(), 10); + BOOST_CHECK(!var->isInteger()); + BOOST_CHECK_EQUAL(pb->getObjectiveCoefficient(var), 0); +} + +BOOST_AUTO_TEST_CASE(var_with_wrong_parameter_lb__exception_is_raised) +{ + createModel("my-model", + {}, + {{"variable", ValueType::FLOAT, parameter("parameter-not-in-model"), literal(10)}}, + {}); + createComponent("my-model", "my-component"); + // TODO : improve exception message in eval visitor + BOOST_CHECK_THROW(buildLinearProblem(), out_of_range); +} + +BOOST_AUTO_TEST_CASE(var_with_wrong_variable_ub__exception_is_raised) +{ + createModel("my-model", + {}, + {{"variable", ValueType::FLOAT, literal(10), variable("variable")}}, + {}); + createComponent("my-model", "my-component"); + // TODO : improve exception message in eval visitor + BOOST_CHECK_THROW(buildLinearProblem(), out_of_range); +} + +BOOST_AUTO_TEST_CASE(two_variables_given_to_different_fillers__LP_contains_the_two_variables) +{ + createModelWithOneFloatVar("m1", {}, "var1", literal(-1), literal(6), {}); + createModelWithOneFloatVar("m2", {}, "var2", literal(-3), literal(2), {}); + createComponent("m1", "component_1"); + createComponent("m2", "component_2"); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 2); + + auto* var1 = pb->getVariable("component_1.var1"); + BOOST_CHECK(var1); + BOOST_CHECK(!var1->isInteger()); + BOOST_CHECK_EQUAL(var1->getLb(), -1.); + BOOST_CHECK_EQUAL(var1->getUb(), 6.); + + auto* var2 = pb->getVariable("component_2.var2"); + BOOST_CHECK(var2); + BOOST_CHECK(!var2->isInteger()); + BOOST_CHECK_EQUAL(var2->getLb(), -3.); + BOOST_CHECK_EQUAL(var2->getUb(), 2.); +} + +BOOST_AUTO_TEST_CASE(var_whose_bounds_are_parameters_given_to_component__problem_contains_this_var) +{ + createModel("model", + {"pmin", "pmax"}, + {{"var1", ValueType::INTEGER, parameter("pmin"), parameter("pmax")}}, + {}); + createComponent("model", "componentToto", {{"pmin", -3.}, {"pmax", 4.}}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 1); + BOOST_CHECK_EQUAL(pb->constraintCount(), 0); + auto* var = pb->getVariable("componentToto.var1"); + BOOST_CHECK(var); + BOOST_CHECK(var->isInteger()); + BOOST_CHECK_EQUAL(var->getLb(), -3.); + BOOST_CHECK_EQUAL(var->getUb(), 4.); +} + +BOOST_AUTO_TEST_CASE(three_different_vars__exist) +{ + VariableData var1 = {"is_cluster_on", ValueType::BOOL, literal(0), literal(1)}; + VariableData var2 = {"n_started_units", ValueType::INTEGER, literal(0), parameter("nUnits")}; + VariableData var3 = {"p_per_unit", ValueType::FLOAT, parameter("pmin"), parameter("pmax")}; + createModel("thermalClusterModel", {"pmin", "pmax", "nUnits"}, {var1, var2, var3}, {}); + createComponent("thermalClusterModel", + "thermalCluster1", + {{"pmin", 100.248}, {"pmax", 950.6784}, {"nUnits", 17.}}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 3); + BOOST_CHECK_EQUAL(pb->constraintCount(), 0); + auto* is_cluster_on = pb->getVariable("thermalCluster1.is_cluster_on"); + BOOST_CHECK(is_cluster_on); + BOOST_CHECK(is_cluster_on->isInteger()); + BOOST_CHECK_EQUAL(is_cluster_on->getLb(), 0); + BOOST_CHECK_EQUAL(is_cluster_on->getUb(), 1); + auto* n_started_units = pb->getVariable("thermalCluster1.n_started_units"); + BOOST_CHECK(n_started_units); + BOOST_CHECK(n_started_units->isInteger()); + BOOST_CHECK_EQUAL(n_started_units->getLb(), 0); + BOOST_CHECK_EQUAL(n_started_units->getUb(), 17); + auto* p_per_unit = pb->getVariable("thermalCluster1.p_per_unit"); + BOOST_CHECK(p_per_unit); + BOOST_CHECK(!p_per_unit->isInteger()); + BOOST_CHECK_EQUAL(p_per_unit->getLb(), 100.248); + BOOST_CHECK_EQUAL(p_per_unit->getUb(), 950.6784); +} + +BOOST_AUTO_TEST_CASE(one_model_two_components__dont_clash) +{ + createModelWithOneFloatVar("m1", {"ub"}, "var1", literal(-100), parameter("ub"), {}); + createComponent("m1", "component_1", {{"ub", 15}}); + createComponent("m1", "component_2", {{"ub", 48}}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 2); + BOOST_CHECK_EQUAL(pb->constraintCount(), 0); + auto* c1_var1 = pb->getVariable("component_1.var1"); + BOOST_CHECK(c1_var1); + BOOST_CHECK(!c1_var1->isInteger()); + BOOST_CHECK_EQUAL(c1_var1->getLb(), -100); + BOOST_CHECK_EQUAL(c1_var1->getUb(), 15); + auto* c2_var1 = pb->getVariable("component_2.var1"); + BOOST_CHECK(c2_var1); + BOOST_CHECK(!c2_var1->isInteger()); + BOOST_CHECK_EQUAL(c2_var1->getLb(), -100); + BOOST_CHECK_EQUAL(c2_var1->getUb(), 48); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_FIXTURE_TEST_SUITE(_ComponentFiller_addConstraints_, LinearProblemBuildingFixture) + +BOOST_AUTO_TEST_CASE(ct_one_var__pb_contains_the_ct) +{ + // var1 <= 3 + auto var_node = variable("var1"); + auto three = literal(3); + auto ct_node = nodes.create(var_node, three); + createModel("model", + {}, + {{"var1", ValueType::BOOL, literal(-5), literal(10)}}, + {{"ct1", ct_node}}); + createComponent("model", "componentToto"); + buildLinearProblem(); + + auto var = pb->getVariable("componentToto.var1"); + BOOST_CHECK(var); + BOOST_CHECK(var->isInteger()); + BOOST_CHECK_EQUAL(pb->variableCount(), 1); + BOOST_CHECK_EQUAL(pb->constraintCount(), 1); + auto ct = pb->getConstraint("componentToto.ct1"); + BOOST_CHECK(ct); + BOOST_CHECK_EQUAL(ct->getLb(), -pb->infinity()); + BOOST_CHECK_EQUAL(ct->getUb(), 3); + BOOST_CHECK_EQUAL(ct->getCoefficient(var), 1); +} + +BOOST_AUTO_TEST_CASE(ct_one_var_with_coef__pb_contains_the_ct) +{ + // 3 * var1 >= 5 * var1 + 5 + // simplified to : -2 * var1 >= 5 + auto var_node = variable("var__1"); + auto five = literal(5); + auto coef_node_left = multiply(literal(3), var_node); + auto coef_node_right = multiply(var_node, five); + auto sum_node_right = nodes.create(coef_node_right, five); + auto ct_node = nodes.create(coef_node_left, sum_node_right); + + createModelWithOneFloatVar("model", + {}, + "var__1", + literal(-5), + literal(10), + {{"ct_1", ct_node}}); + createComponent("model", "componentTata"); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 1); + BOOST_CHECK_NO_THROW(pb->getVariable("componentTata.var__1")); + auto var = pb->getVariable("componentTata.var__1"); + BOOST_CHECK_EQUAL(pb->constraintCount(), 1); + BOOST_CHECK_NO_THROW(pb->getConstraint("componentTata.ct_1")); + auto ct = pb->getConstraint("componentTata.ct_1"); + BOOST_CHECK(ct); + BOOST_CHECK_EQUAL(ct->getLb(), 5); + BOOST_CHECK_EQUAL(ct->getUb(), pb->infinity()); + BOOST_CHECK_EQUAL(ct->getCoefficient(var), -2); +} + +BOOST_AUTO_TEST_CASE(ct_with_two_vars) +{ + // param1(-16) * v1 + 8 * v2 + 5 - param2(8) = 7 * v1 + param3(5) * v2 + 89 + 5 * param4(-3) + // simplifies to: -23 * v1 + 3 * v2 = 77 + vector params = {"param1", "param2", "param3", "param4"}; + VariableData var1Data = {"v1", ValueType::FLOAT, literal(-50.), literal(300.)}; + VariableData var2Data = {"v2", ValueType::FLOAT, literal(60.), literal(75.)}; + + auto sum_node_left = nodes.create(multiply(variable("v1"), parameter("param1")), + multiply(literal(8), variable("v2")), + literal(5), + negate(parameter("param2"))); + auto sum_node_right = nodes.create(multiply(variable("v1"), literal(7)), + multiply(parameter("param3"), variable("v2")), + literal(89), + multiply(literal(5), parameter("param4"))); + auto ct_node = nodes.create(sum_node_left, sum_node_right); + + createModel("my_new_model", params, {var1Data, var2Data}, {{"constraint1", ct_node}}); + createComponent("my_new_model", + "my_component", + {{"param1", -16}, {"param2", 8}, {"param3", 5}, {"param4", -3}}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 2); + BOOST_CHECK_EQUAL(pb->constraintCount(), 1); + BOOST_CHECK_NO_THROW(pb->getConstraint("my_component.constraint1")); + auto ct = pb->getConstraint("my_component.constraint1"); + BOOST_CHECK(ct); + BOOST_CHECK_EQUAL(ct->getLb(), 77); + BOOST_CHECK_EQUAL(ct->getUb(), 77); + BOOST_CHECK_EQUAL(ct->getCoefficient(pb->getVariable("my_component.v1")), -23); + BOOST_CHECK_EQUAL(ct->getCoefficient(pb->getVariable("my_component.v2")), 3); +} + +BOOST_AUTO_TEST_CASE(two_constraints__they_are_created) +{ + // 3 * v1 -2 <= v2 (simplifies to : 3 * v1 - 2 * v2 <= 2) + // v2 <= v1 / 2 (simplifies to : -0.5 * v1 + v2 <= 0) + VariableData var1Data = {"v1", ValueType::FLOAT, literal(-50.), literal(300.)}; + VariableData var2Data = {"v2", ValueType::FLOAT, literal(60.), literal(75.)}; + + auto ct1_node = nodes.create( + nodes.create(multiply(literal(3), variable("v1")), literal(2)), + variable("v2")); + auto ct2_node = nodes.create(variable("v2"), + nodes.create(variable("v1"), + literal(2))); + + createModel("my_new_model", {}, {var1Data, var2Data}, {{"ct1", ct1_node}, {"ct2", ct2_node}}); + createComponent("my_new_model", "my_component", {}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 2); + BOOST_CHECK_EQUAL(pb->constraintCount(), 2); + + BOOST_CHECK_NO_THROW(pb->getConstraint("my_component.ct1")); + auto ct1 = pb->getConstraint("my_component.ct1"); + BOOST_CHECK(ct1); + BOOST_CHECK_EQUAL(ct1->getLb(), -numeric_limits::infinity()); + BOOST_CHECK_EQUAL(ct1->getUb(), 2); + BOOST_CHECK_EQUAL(ct1->getCoefficient(pb->getVariable("my_component.v1")), 3); + BOOST_CHECK_EQUAL(ct1->getCoefficient(pb->getVariable("my_component.v2")), -1); + + BOOST_CHECK_NO_THROW(pb->getConstraint("my_component.ct2")); + auto ct2 = pb->getConstraint("my_component.ct2"); + BOOST_CHECK(ct2); + BOOST_CHECK_EQUAL(ct2->getLb(), -numeric_limits::infinity()); + BOOST_CHECK_EQUAL(ct2->getUb(), 0); + BOOST_CHECK_EQUAL(ct2->getCoefficient(pb->getVariable("my_component.v1")), -0.5); + BOOST_CHECK_EQUAL(ct2->getCoefficient(pb->getVariable("my_component.v2")), 1); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_FIXTURE_TEST_SUITE(_ComponentFiller_addObjective_, LinearProblemBuildingFixture) + +BOOST_AUTO_TEST_CASE(one_var_with_objective) +{ + auto objective = variable("x"); + createModelWithOneFloatVar("model", {}, "x", literal(-50), literal(-40), {}, objective); + createComponent("model", "componentA", {}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 1); + BOOST_CHECK_NO_THROW(pb->getVariable("componentA.x")); + BOOST_CHECK_EQUAL(pb->getObjectiveCoefficient(pb->getVariable("componentA.x")), 1); +} + +BOOST_AUTO_TEST_CASE(two_vars_but_only_one_in_objective) +{ + VariableData var1Data = {"v1", ValueType::FLOAT, literal(-50.), literal(300.)}; + VariableData var2Data = {"v2", ValueType::FLOAT, literal(60.), literal(75.)}; + auto objective = multiply(variable("v2"), literal(37)); + + createModel("model", {}, {var1Data, var2Data}, {}, objective); + createComponent("model", "componentA", {}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 2); + BOOST_CHECK_NO_THROW(pb->getVariable("componentA.v1")); + BOOST_CHECK_NO_THROW(pb->getVariable("componentA.v2")); + BOOST_CHECK_EQUAL(pb->getObjectiveCoefficient(pb->getVariable("componentA.v1")), 0); + BOOST_CHECK_EQUAL(pb->getObjectiveCoefficient(pb->getVariable("componentA.v2")), 37); +} + +BOOST_AUTO_TEST_CASE(one_var_with_param_objective) +{ + // -param(5)*param(5) * x + auto objective = multiply(negate(multiply(parameter("param"), parameter("param"))), + variable("x")); + createModelWithOneFloatVar("model", {"param"}, "x", literal(-50), literal(-40), {}, objective); + createComponent("model", "componentA", {{"param", 5}}); + buildLinearProblem(); + + BOOST_CHECK_EQUAL(pb->variableCount(), 1); + BOOST_CHECK_NO_THROW(pb->getVariable("componentA.x")); + BOOST_CHECK_EQUAL(pb->getObjectiveCoefficient(pb->getVariable("componentA.x")), -25); +} + +BOOST_AUTO_TEST_CASE(offset_in_objective__throws_exception) +{ + auto objective = literal(6); + createModelWithOneFloatVar("model", {}, "x", literal(-50), literal(-40), {}, objective); + createComponent("model", "componentA", {}); + BOOST_CHECK_EXCEPTION(buildLinearProblem(), + invalid_argument, + checkMessage("Antares does not support objective offsets (found in model " + "'model' of component 'componentA').")); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/src/solver/optim-model-filler/test_linearExpression.cpp b/src/tests/src/solver/optim-model-filler/test_linearExpression.cpp new file mode 100644 index 0000000000..b8e7ed0441 --- /dev/null +++ b/src/tests/src/solver/optim-model-filler/test_linearExpression.cpp @@ -0,0 +1,150 @@ +/* + * 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 . + */ + +#define WIN32_LEAN_AND_MEAN + +#include + +#include + +#include + +using namespace Antares::Optimization; + +BOOST_AUTO_TEST_SUITE(_linear_expressions_) + +BOOST_AUTO_TEST_CASE(default_linear_expression) +{ + LinearExpression linearExpression; + + BOOST_CHECK_EQUAL(linearExpression.offset(), 0.); + BOOST_CHECK(linearExpression.coefPerVar().empty()); +} + +BOOST_AUTO_TEST_CASE(linear_expression_explicit_construction) +{ + LinearExpression linearExpression(4., {{"some key", -5.}}); + + BOOST_CHECK_EQUAL(linearExpression.offset(), 4.); + BOOST_CHECK_EQUAL(linearExpression.coefPerVar().size(), 1); + BOOST_CHECK_EQUAL(linearExpression.coefPerVar()["some key"], -5.); +} + +BOOST_AUTO_TEST_CASE(sum_two_linear_expressions) +{ + LinearExpression linearExpression1(4., {{"var1", -5.}, {"var2", 6.}}); + LinearExpression linearExpression2(-1., {{"var3", 20.}, {"var2", -4.}}); + + auto sum = linearExpression1 + linearExpression2; + + BOOST_CHECK_EQUAL(sum.offset(), 3.); + BOOST_CHECK_EQUAL(sum.coefPerVar().size(), 3); + BOOST_CHECK_EQUAL(sum.coefPerVar()["var1"], -5.); + BOOST_CHECK_EQUAL(sum.coefPerVar()["var2"], 2.); + BOOST_CHECK_EQUAL(sum.coefPerVar()["var3"], 20.); +} + +BOOST_AUTO_TEST_CASE(subtract_two_linear_expressions) +{ + LinearExpression linearExpression1(4., {{"var1", -5.}, {"var2", 6.}}); + LinearExpression linearExpression2(-1., {{"var2", -4.}, {"var3", 20.}}); + + auto subtract = linearExpression1 - linearExpression2; + + BOOST_CHECK_EQUAL(subtract.offset(), 5.); + BOOST_CHECK_EQUAL(subtract.coefPerVar().size(), 3); + BOOST_CHECK_EQUAL(subtract.coefPerVar()["var1"], -5.); + BOOST_CHECK_EQUAL(subtract.coefPerVar()["var2"], 10.); + BOOST_CHECK_EQUAL(subtract.coefPerVar()["var3"], -20.); +} + +BOOST_AUTO_TEST_CASE(multiply_linear_expression_by_scalar) +{ + LinearExpression linearExpression(4., {{"var1", -5.}, {"var2", 6.}}); + LinearExpression someScalar(-2., {}); + + auto product = linearExpression * someScalar; + + BOOST_CHECK_EQUAL(product.offset(), -8.); + BOOST_CHECK_EQUAL(product.coefPerVar().size(), 2); + BOOST_CHECK_EQUAL(product.coefPerVar()["var1"], 10.); + BOOST_CHECK_EQUAL(product.coefPerVar()["var2"], -12.); +} + +BOOST_AUTO_TEST_CASE(multiply_scalar_by_linear_expression) +{ + LinearExpression linearExpression(4., {{"var1", -5.}, {"var2", 6.}}); + LinearExpression someScalar(-2., {}); + + auto product = someScalar * linearExpression; + + BOOST_CHECK_EQUAL(product.offset(), -8.); + BOOST_CHECK_EQUAL(product.coefPerVar().size(), 2); + BOOST_CHECK_EQUAL(product.coefPerVar()["var1"], 10.); + BOOST_CHECK_EQUAL(product.coefPerVar()["var2"], -12.); +} + +BOOST_AUTO_TEST_CASE(multiply_two_linear_expressions_containing_variables__exception_raised) +{ + LinearExpression linearExpression1(4., {{"var1", -5.}, {"var2", 6.}}); + LinearExpression linearExpression2(-1., {{"var2", -4.}, {"var3", 20.}}); + + BOOST_CHECK_EXCEPTION(linearExpression1 * linearExpression2, + std::invalid_argument, + checkMessage("A linear expression can't have quadratic terms.")); +} + +BOOST_AUTO_TEST_CASE(divide_linear_expression_by_scalar) +{ + LinearExpression linearExpression(4., {{"var1", -5.}, {"var2", 6.}}); + LinearExpression someScalar(-2., {}); + + auto product = linearExpression / someScalar; + + BOOST_CHECK_EQUAL(product.offset(), -2.); + BOOST_CHECK_EQUAL(product.coefPerVar().size(), 2); + BOOST_CHECK_EQUAL(product.coefPerVar()["var1"], 2.5); + BOOST_CHECK_EQUAL(product.coefPerVar()["var2"], -3.); +} + +BOOST_AUTO_TEST_CASE(divide_scalar_by_linear_expression__exception_raised) +{ + LinearExpression linearExpression(4., {{"var1", -5.}, {"var2", 6.}}); + LinearExpression someScalar(-2., {}); + + BOOST_CHECK_EXCEPTION(someScalar / linearExpression, + std::invalid_argument, + checkMessage("A linear expression can't have a variable as a dividend.")); +} + +BOOST_AUTO_TEST_CASE(negate_linear_expression) +{ + LinearExpression linearExpression(4., {{"var1", -5.}, {"var2", 6.}}); + + auto negative = linearExpression.negate(); + + BOOST_CHECK_EQUAL(negative.offset(), -4.); + BOOST_CHECK_EQUAL(negative.coefPerVar().size(), 2); + BOOST_CHECK_EQUAL(negative.coefPerVar()["var1"], 5.); + BOOST_CHECK_EQUAL(negative.coefPerVar()["var2"], -6.); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/src/solver/optim-model-filler/test_main.cpp b/src/tests/src/solver/optim-model-filler/test_main.cpp new file mode 100644 index 0000000000..6f0c4178e7 --- /dev/null +++ b/src/tests/src/solver/optim-model-filler/test_main.cpp @@ -0,0 +1,26 @@ +/* + * 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 . + */ + +#define BOOST_TEST_MODULE test modeler impl + +#define WIN32_LEAN_AND_MEAN + +#include diff --git a/src/tests/src/solver/optim-model-filler/test_readLinearConstraintVisitor.cpp b/src/tests/src/solver/optim-model-filler/test_readLinearConstraintVisitor.cpp new file mode 100644 index 0000000000..da7b16eb86 --- /dev/null +++ b/src/tests/src/solver/optim-model-filler/test_readLinearConstraintVisitor.cpp @@ -0,0 +1,129 @@ +/* + * 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 . + */ + +#define WIN32_LEAN_AND_MEAN + +#include + +#include + +#include +#include +#include + +using namespace Antares::Solver; +using namespace Antares::Solver::Nodes; +using namespace Antares::Solver::Visitors; + +using namespace Antares::Optimization; + +BOOST_AUTO_TEST_SUITE(_read_linear_constraint_visitor_) + +BOOST_AUTO_TEST_CASE(test_name) +{ + ReadLinearConstraintVisitor visitor; + BOOST_CHECK_EQUAL(visitor.name(), "ReadLinearConstraintVisitor"); +} + +BOOST_FIXTURE_TEST_CASE(test_visit_equal_node, Registry) +{ + // 5 + var1 = var2 + 3 * var1 - param1(9) ==> -2 * var1 - var2 = -14 + Node* lhs = create(create(5.), create("var1")); + Node* rhs = create(create("var2"), + create(create(3.), + create("var1")), + create(create("param1"))); + Node* node = create(lhs, rhs); + EvaluationContext context({{"param1", 9.}}, {}); + ReadLinearConstraintVisitor visitor(context); + auto constraint = visitor.dispatch(node); + BOOST_CHECK_EQUAL(constraint.lb, -14.); + BOOST_CHECK_EQUAL(constraint.ub, -14.); + BOOST_CHECK_EQUAL(constraint.coef_per_var.size(), 2); + BOOST_CHECK_EQUAL(constraint.coef_per_var["var1"], -2); + BOOST_CHECK_EQUAL(constraint.coef_per_var["var2"], -1); +} + +BOOST_FIXTURE_TEST_CASE(test_visit_less_than_or_equal_node, Registry) +{ + // -9 + var3 <= var1 + 5 * var2 - param1(10) ==> - var1 - 5 * var2 + var3 <= -1 + Node* lhs = create(create(-9.), create("var3")); + Node* rhs = create(create("var1"), + create(create(5.), + create("var2")), + create(create("param1"))); + Node* node = create(lhs, rhs); + EvaluationContext context({{"param1", 10.}}, {}); + ReadLinearConstraintVisitor visitor(context); + auto constraint = visitor.dispatch(node); + BOOST_CHECK_EQUAL(constraint.lb, -std::numeric_limits::infinity()); + BOOST_CHECK_EQUAL(constraint.ub, -1.); + BOOST_CHECK_EQUAL(constraint.coef_per_var.size(), 3); + BOOST_CHECK_EQUAL(constraint.coef_per_var["var1"], -1); + BOOST_CHECK_EQUAL(constraint.coef_per_var["var2"], -5); + BOOST_CHECK_EQUAL(constraint.coef_per_var["var3"], 1); +} + +BOOST_FIXTURE_TEST_CASE(test_visit_greater_than_or_equal_node, Registry) +{ + // 5 + var1 >= var2 + 3 * var1 - param1(9) ==> -2 * var1 - var2 >= -14 + Node* lhs = create(create(5.), create("var1")); + Node* rhs = create(create("var2"), + create(create(3.), + create("var1")), + create(create("param1"))); + Node* node = create(lhs, rhs); + EvaluationContext context({{"param1", 9.}}, {}); + ReadLinearConstraintVisitor visitor(context); + auto constraint = visitor.dispatch(node); + BOOST_CHECK_EQUAL(constraint.lb, -14); + BOOST_CHECK_EQUAL(constraint.ub, std::numeric_limits::infinity()); + BOOST_CHECK_EQUAL(constraint.coef_per_var.size(), 2); + BOOST_CHECK_EQUAL(constraint.coef_per_var["var1"], -2); + BOOST_CHECK_EQUAL(constraint.coef_per_var["var2"], -1); +} + +BOOST_FIXTURE_TEST_CASE(test_visit_illegal_node, Registry) +{ + auto lit = create(5.); + std::vector illegal_nodes = {create(), + create(lit, lit), + create(lit, lit), + create(lit, lit), + create(lit), + create("var"), + create("param"), + create(5.), + create("port", "field"), + create("port", "field"), + create("x", "y"), + create("x", "y")}; + + for (Node* node: illegal_nodes) + { + ReadLinearConstraintVisitor visitor; + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), + std::invalid_argument, + checkMessage("Root node of a constraint must be a comparator.")); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/src/solver/optim-model-filler/test_readLinearExpressionVisitor.cpp b/src/tests/src/solver/optim-model-filler/test_readLinearExpressionVisitor.cpp new file mode 100644 index 0000000000..e9ce35c934 --- /dev/null +++ b/src/tests/src/solver/optim-model-filler/test_readLinearExpressionVisitor.cpp @@ -0,0 +1,179 @@ +/* + * 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 . + */ + +#define WIN32_LEAN_AND_MEAN + +#include + +#include + +#include +#include +#include + +using namespace Antares::Solver; +using namespace Antares::Solver::Nodes; +using namespace Antares::Solver::Visitors; + +using namespace Antares::Optimization; + +BOOST_AUTO_TEST_SUITE(_read_linear_expression_visitor_) + +BOOST_AUTO_TEST_CASE(name) +{ + ReadLinearExpressionVisitor visitor; + BOOST_CHECK_EQUAL(visitor.name(), "ReadLinearExpressionVisitor"); +} + +BOOST_FIXTURE_TEST_CASE(visit_literal, Registry) +{ + Node* node = create(5.); + ReadLinearExpressionVisitor visitor; + auto linear_expression = visitor.dispatch(node); + BOOST_CHECK_EQUAL(linear_expression.offset(), 5.); + BOOST_CHECK(linear_expression.coefPerVar().empty()); +} + +BOOST_FIXTURE_TEST_CASE(visit_literal_plus_param, Registry) +{ + // 5 + param(3) = 8 + Node* sum = create(create(5.), create("param")); + EvaluationContext evaluation_context({{"param", 3.}}, {}); + ReadLinearExpressionVisitor visitor(evaluation_context); + auto linear_expression = visitor.dispatch(sum); + BOOST_CHECK_EQUAL(linear_expression.offset(), 8.); + BOOST_CHECK(linear_expression.coefPerVar().empty()); +} + +BOOST_FIXTURE_TEST_CASE(visit_literal_plus_param_plus_var, Registry) +{ + // 60 + param(-5) + 7 * var = { 55, {var : 7} } + Node* product = create(create(7.), + create("var")); + Node* sum = create(create(60.), create("param"), product); + EvaluationContext evaluation_context({{"param", -5.}}, {}); + ReadLinearExpressionVisitor visitor(evaluation_context); + auto linear_expression = visitor.dispatch(sum); + BOOST_CHECK_EQUAL(linear_expression.offset(), 55.); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar().size(), 1); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar()["var"], 7.); +} + +BOOST_FIXTURE_TEST_CASE(visit_negate_literal_plus_var, Registry) +{ + // -(60 + 7 * var) = { -60, {var : -7} } + Node* product = create(create(7.), + create("var")); + Node* sum = create(create(60.), product); + Node* neg = create(sum); + ReadLinearExpressionVisitor visitor; + auto linear_expression = visitor.dispatch(neg); + BOOST_CHECK_EQUAL(linear_expression.offset(), -60.); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar().size(), 1); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar()["var"], -7.); +} + +BOOST_FIXTURE_TEST_CASE(visit_literal_minus_var, Registry) +{ + // 60 - 7 * var = { 60, {var : -7} } + Node* product = create(create(7.), + create("var")); + Node* sub = create(create(60.), product); + ReadLinearExpressionVisitor visitor; + auto linear_expression = visitor.dispatch(sub); + BOOST_CHECK_EQUAL(linear_expression.offset(), 60.); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar().size(), 1); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar()["var"], -7.); +} + +BOOST_FIXTURE_TEST_CASE(visit_complex_expression, Registry) +{ + // 2 * (13 + 3 * param1(-2) + 14 * var1) / 7 + param2(8) + 6 * var2 = {10 ; {var1:4, var2:6}} + + // small_sum = 13 + 3 * param1(-2) + 14 * var1 + Node* small_sum = create(create(13.), + create(create(3), + create("param1")), + create(create(14), + create("var1"))); + + // big_sum = 2 * small_sum / 7 + param2(8) + 6 * var2 + Node* big_sum = create( + create(create(create(2.), small_sum), + create(7.)), // 2 * small_sum / 7 + create("param2"), // param2 + create(create(6.), create("var2")) // 6 * var2 + ); + + EvaluationContext evaluation_context({{"param1", -2.}, {"param2", 8.}}, {}); + ReadLinearExpressionVisitor visitor(evaluation_context); + auto linear_expression = visitor.dispatch(big_sum); + BOOST_CHECK_EQUAL(linear_expression.offset(), 10.); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar().size(), 2); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar()["var1"], 4.); + BOOST_CHECK_EQUAL(linear_expression.coefPerVar()["var2"], 6.); +} + +BOOST_FIXTURE_TEST_CASE(comparison_nodes__exception_thrown, Registry) +{ + Node* literal = create(5.); + ReadLinearExpressionVisitor visitor; + auto predicate = checkMessage("A linear expression can't contain comparison operators."); + + Node* node = create(literal, literal); + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), std::invalid_argument, predicate); + + node = create(literal, literal); + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), std::invalid_argument, predicate); + + node = create(literal, literal); + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), std::invalid_argument, predicate); +} + +BOOST_FIXTURE_TEST_CASE(not_implemented_nodes__exception_thrown, Registry) +{ + ReadLinearExpressionVisitor visitor; + + Node* node = create("port", "field"); + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), + std::invalid_argument, + checkMessage("ReadLinearExpressionVisitor cannot visit PortFieldNodes")); + + node = create("port", "field"); + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), + std::invalid_argument, + checkMessage( + "ReadLinearExpressionVisitor cannot visit PortFieldSumNodes")); + + node = create("id", "y"); + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), + std::invalid_argument, + checkMessage( + "ReadLinearExpressionVisitor cannot visit ComponentVariableNodes")); + + node = create("id", "y"); + BOOST_CHECK_EXCEPTION(visitor.dispatch(node), + std::invalid_argument, + checkMessage( + "ReadLinearExpressionVisitor cannot visit ComponentParameterNodes")); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/src/study/system-model/CMakeLists.txt b/src/tests/src/study/system-model/CMakeLists.txt index 0f5f956ac8..727635be3e 100644 --- a/src/tests/src/study/system-model/CMakeLists.txt +++ b/src/tests/src/study/system-model/CMakeLists.txt @@ -12,7 +12,7 @@ target_link_libraries(${EXECUTABLE_NAME} PRIVATE Boost::unit_test_framework antares-study-system-model - test_utils_unit + test_utils_unit ) set_target_properties(${EXECUTABLE_NAME} PROPERTIES FOLDER Unit-tests) diff --git a/src/tests/src/study/system-model/test_component.cpp b/src/tests/src/study/system-model/test_component.cpp index e8c69007a9..3f52b33880 100644 --- a/src/tests/src/study/system-model/test_component.cpp +++ b/src/tests/src/study/system-model/test_component.cpp @@ -21,12 +21,12 @@ #define WIN32_LEAN_AND_MEAN +#include + #include #include "antares/study/system-model/component.h" -#include "../../utils/unit_test_utils.h" - using namespace Antares::Study::SystemModel; struct ComponentBuilderCreationFixture @@ -98,6 +98,33 @@ BOOST_AUTO_TEST_CASE(nominal_build_without_parameters2) BOOST_CHECK_EQUAL(component.getScenarioGroupId(), "scenario_group3"); } +BOOST_AUTO_TEST_CASE(reuse_builder) +{ + Model model1 = createModelWithoutParameters(); + auto component1 = component_builder.withId("component1") + .withModel(&model1) + .withScenarioGroupId("scenario_group1") + .build(); + Model model2 = createModelWithParameters(); + auto component2 = component_builder.withId("component2") + .withModel(&model2) + .withParameterValues({{"param1", 5}, {"param2", 3}}) + .withScenarioGroupId("scenario_group2") + .build(); + + BOOST_CHECK_EQUAL(component1.Id(), "component1"); + BOOST_CHECK_EQUAL(component1.getModel(), &model1); + BOOST_CHECK_EQUAL(component1.getScenarioGroupId(), "scenario_group1"); + BOOST_CHECK(component1.getParameterValues().empty()); + + BOOST_CHECK_EQUAL(component2.Id(), "component2"); + BOOST_CHECK_EQUAL(component2.getModel(), &model2); + BOOST_CHECK_EQUAL(component2.getScenarioGroupId(), "scenario_group2"); + BOOST_CHECK_EQUAL(component2.getParameterValues().size(), 2); + BOOST_CHECK_EQUAL(component2.getParameterValues().at("param1"), 5); + BOOST_CHECK_EQUAL(component2.getParameterValues().at("param2"), 3); +} + BOOST_AUTO_TEST_CASE(fail_on_no_id) { Model model = createModelWithoutParameters(); diff --git a/src/tests/src/study/system-model/test_system.cpp b/src/tests/src/study/system-model/test_system.cpp index 9500b6118b..550ca59693 100644 --- a/src/tests/src/study/system-model/test_system.cpp +++ b/src/tests/src/study/system-model/test_system.cpp @@ -21,12 +21,12 @@ #define WIN32_LEAN_AND_MEAN +#include + #include #include "antares/study/system-model/system.h" -#include "../../utils/unit_test_utils.h" - using namespace Antares::Study::SystemModel; struct SystemBuilderCreationFixture diff --git a/src/tools/batchrun/main.cpp b/src/tools/batchrun/main.cpp index 420dc060dd..4da485effd 100644 --- a/src/tools/batchrun/main.cpp +++ b/src/tools/batchrun/main.cpp @@ -175,7 +175,7 @@ int main(int argc, const char* argv[]) { // Wrap spaces around quotes, if any // example - // antares-batchrun directory --use-ortools --ortools-solver xpress + // antares-batchrun directory --solver xpress // --solver-parameters "PRESOLVE 1" cmd << " \"" << arg << "\""; } diff --git a/src/ui/simulator/application/study.cpp b/src/ui/simulator/application/study.cpp index c19ea5a1f8..ab4e65e472 100644 --- a/src/ui/simulator/application/study.cpp +++ b/src/ui/simulator/application/study.cpp @@ -1207,7 +1207,10 @@ void RunSimulationOnTheStudy(Data::Study::Ptr study, cmd << " --parallel"; // add solver name for ortools - cmd << " --solver=" << solverName; + if (!solverName.empty()) + { + cmd << " --solver=" << solverName; + } // Go go go ! logs.debug() << "running " << cmd; diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/area/thermalmodulation.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/area/thermalmodulation.cpp index bf72ed3dc0..a53872ee64 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/area/thermalmodulation.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/area/thermalmodulation.cpp @@ -95,17 +95,16 @@ wxString ThermalClusterCommonModulation::cellValue(int x, int y) const return DoubleToWxString(Math::Round((*pMatrix)[Data::thermalMinGenModulation][y], 3)); case (Data::thermalModulationCost + Data::thermalModulationMax): return DoubleToWxString( - Math::Round((*pMatrix)[Data::thermalModulationCost][y] - * pCluster->costsTimeSeries[0].marginalCostTS[y], + Math::Round(pCluster->getCostProvider().getMarginalCost(0, y), 3)); case (Data::thermalModulationMarketBid + Data::thermalModulationMax): return DoubleToWxString( - Math::Round((*pMatrix)[Data::thermalModulationMarketBid][y] - * pCluster->costsTimeSeries[0].marketBidCostTS[y], + Math::Round(pCluster->getCostProvider().getMarketBidCost(y, 0), 3)); case (Data::thermalModulationCapacity + Data::thermalModulationMax): - return DoubleToWxString(Math::Round( - (*pMatrix)[Data::thermalModulationCapacity][y] * pCluster->nominalCapacity, 2)); + return DoubleToWxString(Math::Round((*pMatrix)[Data::thermalModulationCapacity][y] + * pCluster->nominalCapacity, + 2)); case (Data::thermalMinGenModulation + Data::thermalModulationMax): return DoubleToWxString(Math::Round((*pMatrix)[Data::thermalMinGenModulation][y] * pCluster->unitCount * pCluster->nominalCapacity, @@ -131,11 +130,9 @@ double ThermalClusterCommonModulation::cellNumericValue(int x, int y) const case Data::thermalMinGenModulation: return (*pMatrix)[Data::thermalMinGenModulation][y]; case (Data::thermalModulationCost + Data::thermalModulationMax): - return (*pMatrix)[Data::thermalModulationCost][y] - * pCluster->costsTimeSeries[0].marginalCostTS[y]; + return pCluster->getCostProvider().getMarginalCost(0, y); case (Data::thermalModulationMarketBid + Data::thermalModulationMax): - return (*pMatrix)[Data::thermalModulationMarketBid][y] - * pCluster->costsTimeSeries[0].marketBidCostTS[y]; + return pCluster->getCostProvider().getMarketBidCost(y, 0); case (Data::thermalModulationCapacity + Data::thermalModulationMax): return (*pMatrix)[Data::thermalModulationCapacity][y] * pCluster->nominalCapacity; case (Data::thermalMinGenModulation + Data::thermalModulationMax): diff --git a/src/ui/simulator/windows/inspector/property.update.cpp b/src/ui/simulator/windows/inspector/property.update.cpp index 1c1879dfae..dc0e7eef1e 100644 --- a/src/ui/simulator/windows/inspector/property.update.cpp +++ b/src/ui/simulator/windows/inspector/property.update.cpp @@ -724,7 +724,6 @@ bool InspectorGrid::onPropertyChanging_ThermalCluster(wxPGProperty*, for (; i != end; ++i) { (*i)->fuelEfficiency = d; - (*i)->ComputeCostTimeSeries(); } // update } OnStudyThermalClusterCommonSettingsChanged(); @@ -750,7 +749,6 @@ bool InspectorGrid::onPropertyChanging_ThermalCluster(wxPGProperty*, for (; i != end; ++i) { (*i)->costgeneration = costgeneration; - (*i)->ComputeCostTimeSeries(); // update } AccumulatorCheck::ApplyGreyColor( pFrame.pPGThClusterMarginalCost, @@ -796,7 +794,6 @@ bool InspectorGrid::onPropertyChanging_ThermalCluster(wxPGProperty*, for (; i != end; ++i) { (*i)->marginalCost = d; - (*i)->ComputeCostTimeSeries(); // update } pFrame.delayApply(); @@ -879,7 +876,6 @@ bool InspectorGrid::onPropertyChanging_ThermalCluster(wxPGProperty*, for (; i != end; ++i) { (*i)->marketBidCost = d; - (*i)->ComputeCostTimeSeries(); } pFrame.delayApply(); @@ -922,7 +918,6 @@ bool InspectorGrid::onPropertyChanging_ThermalCluster(wxPGProperty*, for (; i != end; ++i) { (*i)->variableomcost = d; - (*i)->ComputeCostTimeSeries(); // update } } OnStudyThermalClusterCommonSettingsChanged(); diff --git a/src/ui/simulator/windows/simulation/run.cpp b/src/ui/simulator/windows/simulation/run.cpp index e4bfa1cc69..086a405e08 100644 --- a/src/ui/simulator/windows/simulation/run.cpp +++ b/src/ui/simulator/windows/simulation/run.cpp @@ -313,7 +313,7 @@ Run::Run(wxWindow* parent, bool preproOnly) : pTitleOrtoolsSolverCombox = Antares::Component::CreateLabel(pBigDaddy, wxT("Ortools solver : ")); - pOrtoolsSolverCombox = new wxComboBox(pBigDaddy, wxID_ANY); + pOrtoolsSolverCombox = new wxComboBox(pBigDaddy, wxID_ANY, "sirius"); std::list solverList = getAvailableOrtoolsSolverName(); for (const std::string& solverName : solverList) {