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