diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000..a78b0f4b --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,43 @@ +name: Run Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + run-tests: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + + - name: Add vstest to PATH + uses: darenm/Setup-VSTest@v1.2 + + - name: Create empty TeeChart license file + run: | + copy /Y nul > "BlueM.Opt\Main\My Project\TeeChart.licenses" + copy /Y nul > "BlueM.Wave\source\My Project\TeeChart.licenses" + copy /Y nul > "BlueM.Opt\Tests\SensiPlot_ParameterSampling\My Project\TeeChart.licenses" + shell: cmd + + - name: Build BlueM.Opt.Tests + run: msbuild BlueM.Opt\Tests\BlueM.Opt.Tests\BlueM.Opt.Tests.vbproj -restore -property:Platform=x64 -property:Configuration=Debug + + - name: Run Tests + run: | + vstest.console.exe BlueM.Opt\tests\BlueM.Opt.Tests\bin\x64\Debug\net48\BlueM.Opt.Tests.exe /Settings:BlueM.Opt\tests\BlueM.Opt.Tests\tests.runsettings + + - name: Upload test results + uses: actions/upload-artifact@v4 + with: + name: test-results + path: BlueM.Opt\Tests\BlueM.Opt.Tests\TestResults \ No newline at end of file diff --git a/BlueM.Opt.sln b/BlueM.Opt.sln index d0f1728f..f2f3b8d9 100644 --- a/BlueM.Opt.sln +++ b/BlueM.Opt.sln @@ -36,6 +36,8 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "SensiPlot_ParameterSampling EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BlueM.Opt.Tests", "BlueM.Opt.Tests", "{2846C8D3-E679-4D5C-95B4-4CF3E4BF7122}" EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "BlueM.Opt.Tests", "BlueM.Opt\Tests\BlueM.Opt.Tests\BlueM.Opt.Tests.vbproj", "{69B4CC2F-9BBB-DE40-7F29-C0F416AF0222}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -162,6 +164,11 @@ Global {2BC34399-890A-4C37-85A4-3F8D633835B9}.Debug|x86.Build.0 = Debug|x86 {2BC34399-890A-4C37-85A4-3F8D633835B9}.Release|x64.ActiveCfg = Release|x64 {2BC34399-890A-4C37-85A4-3F8D633835B9}.Release|x86.ActiveCfg = Release|x86 + {69B4CC2F-9BBB-DE40-7F29-C0F416AF0222}.Debug|x64.ActiveCfg = Debug|x64 + {69B4CC2F-9BBB-DE40-7F29-C0F416AF0222}.Debug|x64.Build.0 = Debug|x64 + {69B4CC2F-9BBB-DE40-7F29-C0F416AF0222}.Debug|x86.ActiveCfg = Debug|x64 + {69B4CC2F-9BBB-DE40-7F29-C0F416AF0222}.Release|x64.ActiveCfg = Release|x64 + {69B4CC2F-9BBB-DE40-7F29-C0F416AF0222}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -175,6 +182,7 @@ Global {A9272445-616D-4B4F-ACFF-DE6F5591AF63} = {44D440EE-4641-44A1-B177-2DD0FDD6FB75} {6FCD5583-4A2A-4298-8A48-D46FAEFC5D7C} = {44D440EE-4641-44A1-B177-2DD0FDD6FB75} {2BC34399-890A-4C37-85A4-3F8D633835B9} = {2846C8D3-E679-4D5C-95B4-4CF3E4BF7122} + {69B4CC2F-9BBB-DE40-7F29-C0F416AF0222} = {2846C8D3-E679-4D5C-95B4-4CF3E4BF7122} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4558EFAB-BB97-40C0-BF67-4380356C931B} diff --git a/BlueM.Opt/Common/ObjectiveFunctions/ObjectiveFunction.vb b/BlueM.Opt/Common/ObjectiveFunctions/ObjectiveFunction.vb index b649b6c6..b728a0a2 100644 --- a/BlueM.Opt/Common/ObjectiveFunctions/ObjectiveFunction.vb +++ b/BlueM.Opt/Common/ObjectiveFunctions/ObjectiveFunction.vb @@ -175,7 +175,7 @@ Public MustInherit Class ObjectiveFunction ''' comparison function ''' function value ''' Konstante und gleiche Zeitschrittweiten vorausgesetzt! (#151) - Protected Shared Function compareSeries(ByVal SimSeries As Wave.TimeSeries, ByVal RefSeries As Wave.TimeSeries, ByVal [Function] As String) As Double + Public Shared Function compareSeries(ByVal SimSeries As Wave.TimeSeries, ByVal RefSeries As Wave.TimeSeries, ByVal [Function] As String) As Double Dim objectiveValue As Double Dim i As Integer diff --git a/BlueM.Opt/Tests/BlueM.Opt.Tests/BlueM.Opt.Tests.vbproj b/BlueM.Opt/Tests/BlueM.Opt.Tests/BlueM.Opt.Tests.vbproj new file mode 100644 index 00000000..fbb8e31a --- /dev/null +++ b/BlueM.Opt/Tests/BlueM.Opt.Tests/BlueM.Opt.Tests.vbproj @@ -0,0 +1,25 @@ + + + + net48 + latest + enable + enable + + true + x64 + + + + + + + + + + + + diff --git a/BlueM.Opt/Tests/BlueM.Opt.Tests/MSTestSettings.vb b/BlueM.Opt/Tests/BlueM.Opt.Tests/MSTestSettings.vb new file mode 100644 index 00000000..432a6654 --- /dev/null +++ b/BlueM.Opt/Tests/BlueM.Opt.Tests/MSTestSettings.vb @@ -0,0 +1,3 @@ +Imports Microsoft.VisualStudio.TestTools.UnitTesting + + diff --git a/BlueM.Opt/Tests/BlueM.Opt.Tests/TestObjectiveFunctions.vb b/BlueM.Opt/Tests/BlueM.Opt.Tests/TestObjectiveFunctions.vb new file mode 100644 index 00000000..9dd064f4 --- /dev/null +++ b/BlueM.Opt/Tests/BlueM.Opt.Tests/TestObjectiveFunctions.vb @@ -0,0 +1,182 @@ +'BlueM.Opt +'Copyright (C) BlueM Dev Group +'Website: +' +'This program is free software: you can redistribute it and/or modify +'it under the terms of the GNU General Public License as published by +'the Free Software Foundation, either version 3 of the License, or +'(at your option) any later version. +' +'This program is distributed in the hope that it will be useful, +'but WITHOUT ANY WARRANTY; without even the implied warranty of +'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +'GNU General Public License for more details. +' +'You should have received a copy of the GNU General Public License +'along with this program. If not, see . +' +Imports Microsoft.VisualStudio.TestTools.UnitTesting +Imports BlueM.Opt.Common +Imports BlueM.Wave + + +Public Class TestObjectiveFunctions + + Private Function CreateTimeSeries(values As Double(), Optional title As String = "Test") As TimeSeries + Dim ts As New TimeSeries() + ts.Title = title + ts.Interpretation = TimeSeries.InterpretationEnum.BlockRight + Dim startdate As New DateTime(2020, 1, 1) + For i = 0 To values.Length - 1 + ts.AddNode(startdate.AddDays(i), values(i)) + Next + Return ts + End Function + + + Public Sub CompareSeries_SSE_ReturnsSumOfSquaredErrors() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "SSE") + Assert.AreEqual(2.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_MSE_ReturnsMeanSquaredError() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "MSE") + Assert.AreEqual(2.0 / 3.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_SAE_ReturnsSumOfAbsoluteErrors() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "SAE") + Assert.AreEqual(2.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_MAE_ReturnsMeanAbsoluteError() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "MAE") + Assert.AreEqual(2.0 / 3.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_BIAS_ReturnsAbsoluteVolumeError() + Dim sim As TimeSeries = CreateTimeSeries({2.0, 3.0, 4.0}) + Dim ref As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "BIAS") + Assert.AreEqual((9.0 - 6.0) / 6.0 * 100.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_NLT_ReturnsRelativeNumberOfTimestepsLessThan() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "NLT") + Assert.AreEqual(1.0 / 3.0 * 100, result, 0.000001) + End Sub + + + Public Sub CompareSeries_SLT_ReturnsSumOfTimestepsLessThan() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "SLT") + Assert.AreEqual(1.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_NGT_ReturnsRelativeNumberOfTimestepsGreaterThan() + Dim sim As TimeSeries = CreateTimeSeries({3.0, 2.0, 1.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "NGT") + Assert.AreEqual(1.0 / 3.0 * 100, result, 0.000001) + End Sub + + + Public Sub CompareSeries_SGT_ReturnsSumOfTimestepsGreaterThan() + Dim sim As TimeSeries = CreateTimeSeries({3.0, 2.0, 1.0}) + Dim ref As TimeSeries = CreateTimeSeries({2.0, 2.0, 2.0}) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "SGT") + Assert.AreEqual(1.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_APFB_ReturnsAnnualPeakFlowBias() + 'create timeseries with two years of monthly data + Dim sim As New TimeSeries("sim") + sim.Interpretation = TimeSeries.InterpretationEnum.BlockRight + For i As Integer = 1 To 12 + sim.AddNode(New DateTime(2020, i, 1), i) + sim.AddNode(New DateTime(2021, i, 1), i * 2) + Next + Dim ref As New TimeSeries("ref") + ref.Interpretation = TimeSeries.InterpretationEnum.BlockRight + For i As Integer = 1 To 12 + ref.AddNode(New DateTime(2020, i, 1), i * 1.1) + ref.AddNode(New DateTime(2021, i, 1), i * 0.9 * 2) + Next + Dim peakSim = {12.0, 24.0} + Dim peakRef = {13.2, 21.6} + Dim expected = Math.Sqrt(Math.Pow((peakSim.Average() / peakRef.Average()) - 1.0, 2)) + Dim result = ObjectiveFunction.compareSeries(sim, ref, "APFB") + Assert.AreEqual(expected, result, 0.000001) + End Sub + + + Public Sub CompareSeries_NSE_ReturnsNashSutcliffeEfficiency() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + ' Identical series, NSE = 1 + Dim result = ObjectiveFunction.compareSeries(sim, ref, "NSE") + Assert.AreEqual(1.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_LNNSE_ReturnsLnNashSutcliffeEfficiency() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + ' Identical series, lnNSE = 1 + Dim result = ObjectiveFunction.compareSeries(sim, ref, "LNNSE") + Assert.AreEqual(1.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_DET_ReturnsCoefficientOfDetermination() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + ' Identical series, DET = 1 + Dim result = ObjectiveFunction.compareSeries(sim, ref, "DET") + Assert.AreEqual(1.0, result, 0.000001) + End Sub + + + Public Sub CompareSeries_KGE_ReturnsKlingGuptaEfficiency() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + ' Identical series, KGE = 1 + Dim result = ObjectiveFunction.compareSeries(sim, ref, "KGE") + Assert.AreEqual(1.0, result, 0.000001) + End Sub + + + + Public Sub CompareSeries_DifferentLength_ThrowsException() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0}) + Dim ref As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + ObjectiveFunction.compareSeries(sim, ref, "SSE") + End Sub + + + + Public Sub CompareSeries_UnsupportedFunction_ThrowsException() + Dim sim As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + Dim ref As TimeSeries = CreateTimeSeries({1.0, 2.0, 3.0}) + ObjectiveFunction.compareSeries(sim, ref, "UNSUPPORTED") + End Sub + +End Class \ No newline at end of file diff --git a/BlueM.Opt/Tests/BlueM.Opt.Tests/tests.runsettings b/BlueM.Opt/Tests/BlueM.Opt.Tests/tests.runsettings new file mode 100644 index 00000000..e796c390 --- /dev/null +++ b/BlueM.Opt/Tests/BlueM.Opt.Tests/tests.runsettings @@ -0,0 +1,52 @@ + + + + + + 1 + + .\TestResults + + + + + x64 + + + + net48 + + + + + + + + + + test-results.trx + + + + + test-results.html + + + + + + + + + + + True + false + False + False + + + + + + \ No newline at end of file