From 0ed5c891e20a990e6925445ebf9affc9f25d6edc Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 13:57:27 +0200 Subject: [PATCH 01/26] Add first unit test --- test/test_windfield.m | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/test_windfield.m diff --git a/test/test_windfield.m b/test/test_windfield.m new file mode 100644 index 0000000..a298d1e --- /dev/null +++ b/test/test_windfield.m @@ -0,0 +1,20 @@ +% dir_mode = Direction_Constant() +% WindDir = 270 +% iT = [1, 2, 3] +% % phi = getWindDirT(dir_mode, WindDir, iT, nothing) +% for ph in phi +% @test ph ≈ 270.0 +% end + +addpath('./WindField/Direction_Constant'); +WindDir = 270; +iT = [1, 2, 3]; +phi = getWindDirT(WindDir, iT); + +tol = 1e-8; % set a tolerance for floating-point comparison +for i = 1:length(phi) + ph = phi(i); + assert(abs(ph - 270.0) < tol); +end + + From f1b2fb17777dc9bb689b54d075841008608102e0 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 14:02:30 +0200 Subject: [PATCH 02/26] Improve test --- test/test_windfield.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_windfield.m b/test/test_windfield.m index a298d1e..53b6b2c 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -1,7 +1,7 @@ % dir_mode = Direction_Constant() % WindDir = 270 % iT = [1, 2, 3] -% % phi = getWindDirT(dir_mode, WindDir, iT, nothing) +% phi = getWindDirT(dir_mode, WindDir, iT, nothing) % for ph in phi % @test ph ≈ 270.0 % end @@ -16,5 +16,8 @@ ph = phi(i); assert(abs(ph - 270.0) < tol); end +rmpath('./WindField/Direction_Constant') + + From d4f4d9c604a85cdc7694c474eea081148f46b1d4 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 14:23:16 +0200 Subject: [PATCH 03/26] Add test --- test/test_windfield.m | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/test_windfield.m b/test/test_windfield.m index 53b6b2c..107ace1 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -6,6 +6,7 @@ % @test ph ≈ 270.0 % end +clear all addpath('./WindField/Direction_Constant'); WindDir = 270; iT = [1, 2, 3]; @@ -18,6 +19,42 @@ end rmpath('./WindField/Direction_Constant') +% dir_mode = Direction_Constant_wErrCov() +% struct WindDirType +% Data::Float64 +% CholSig::Matrix{Float64} +% end +% WindDir = WindDirType(270.0, cholesky(Matrix{Float64}(I, 3, 3)).L) +% iT = [1, 2, 3] +% phi = getWindDirT(dir_mode, WindDir, iT) +% result = [269.6402710931765, 271.0872084924286, 269.5804103830612] +% for (i, ph) in pairs(phi) +% @test ph ≈ result[i] +% end + +clear all +addpath('./WindField/Direction_Constant_wErrorCov'); +rng(1234); + +% Cholesky factor of 3x3 identity +L = chol(eye(3), 'lower'); + +% WindDir structure +WindDir.Data = 270.0; +WindDir.CholSig = L; + +iT = [1, 2, 3]; + +% Expected result +result = [269.6402710931765, 271.0872084924286, 269.5804103830612]; + +phi = getWindDirT(WindDir, iT); +for i = 1:length(phi) + assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); +end +disp('All tests passed.'); + +rmpath('./WindField/Direction_Constant_wErrorCov'); From c747685070a3a0589db05272f5eb4683e4df8c0b Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 16:43:46 +0200 Subject: [PATCH 04/26] Add one failing test, add one new test --- test/test_windfield.m | 54 ++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/test/test_windfield.m b/test/test_windfield.m index 107ace1..4d2788c 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -32,29 +32,41 @@ % @test ph ≈ result[i] % end -clear all -addpath('./WindField/Direction_Constant_wErrorCov'); -rng(1234); - -% Cholesky factor of 3x3 identity -L = chol(eye(3), 'lower'); - -% WindDir structure -WindDir.Data = 270.0; -WindDir.CholSig = L; +% % The following test fails +% % clear all +% % addpath('./WindField/Direction_Constant_wErrorCov'); +% % rng(1234); +% % +% % % Cholesky factor of 3x3 identity +% % L = chol(eye(3), 'lower'); +% % +% % % WindDir structure +% % WindDir.Data = 270.0; +% % WindDir.CholSig = L; +% % +% % iT = [1, 2, 3]; +% % +% % % Expected result +% % result = [269.6402710931765, 271.0872084924286, 269.5804103830612]; +% % +% % phi = getWindDirT(WindDir, iT); +% % for i = 1:length(phi) +% % assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); +% % end +% % rmpath('./WindField/Direction_Constant_wErrorCov'); -iT = [1, 2, 3]; +% % dir_mode = Direction_EnKF_InterpTurbine() +% # Suppose WindDir is a matrix where each row is [time, phi_T0, phi_T1, ...] +% WindDir = [ +% 0.0 10.0 20.0 +% 1.0 12.0 22.0 +% 2.0 14.0 24.0 +% ] +% phi = getWindDirT_EnKF(dir_mode, WindDir, 1, 0.5) +% @test phi ≈ 11.0 -% Expected result -result = [269.6402710931765, 271.0872084924286, 269.5804103830612]; +clear all +addpath('./WindField/Direction_EnKF_InterpTurbine'); -phi = getWindDirT(WindDir, iT); -for i = 1:length(phi) - assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); -end disp('All tests passed.'); -rmpath('./WindField/Direction_Constant_wErrorCov'); - - - From 933e760e3df6c8ede054ef348a9da87b76996b3c Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 16:43:59 +0200 Subject: [PATCH 05/26] Add files to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02efc18 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +test/test_windfield.asv From 6bcffb6768ef3e0410296af94fb705a949aee51f Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 17:22:54 +0200 Subject: [PATCH 06/26] One more test passes --- test/test_windfield.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_windfield.m b/test/test_windfield.m index 4d2788c..c84e03f 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -67,6 +67,15 @@ clear all addpath('./WindField/Direction_EnKF_InterpTurbine'); +WindDir = [ + 0.0 10.0 20.0 + 1.0 12.0 22.0 + 2.0 14.0 24.0 +]; +phi = getWindDirT_EnKF(WindDir, 1, 0.5); +assert(abs(phi - 11.0) < 1e-8, 'Test failed'); + +rmpath('./WindField/Direction_EnKF_InterpTurbine'); disp('All tests passed.'); From e2330c80d4c9e4f46f92cb670a4b6836af221e3e Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 17:28:18 +0200 Subject: [PATCH 07/26] One more test --- test/test_windfield.m | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/test_windfield.m b/test/test_windfield.m index c84e03f..7c761f6 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -74,8 +74,35 @@ ]; phi = getWindDirT_EnKF(WindDir, 1, 0.5); assert(abs(phi - 11.0) < 1e-8, 'Test failed'); - rmpath('./WindField/Direction_EnKF_InterpTurbine'); +% dir_mode = Direction_Interpolation_wErrorCov() +% +% # Example wind direction data (time, phi) +% wind_data = [ +% 0.0 10.0; +% 5.0 20.0; +% 10.0 30.0 +% ] +% +% # Example Cholesky factor (for 2 turbines) +% chol_sig = cholesky([1.0 0.5; 0.5 1.0]).L +% +% # Create WindDir instance +% WindDir = WindDirMatrix(wind_data, chol_sig) +% +% # Example turbine indices (for 2 turbines) +% iT = [1, 2] +% +% # Example time +% t = 7.0 +% +% # Call the function +% phi = getWindDirT(dir_mode, WindDir, iT, t) +% @test size(phi) == (2,1) +% @test phi[1] ≈ 24.92903352636283 +% @test phi[2] ≈ 24.363944731838128 + + disp('All tests passed.'); From b6e02e96e0360556b1516eb414ad1112f82bf94b Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 14 Jun 2025 17:29:51 +0200 Subject: [PATCH 08/26] Some progress --- test/test_windfield.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_windfield.m b/test/test_windfield.m index 7c761f6..4c4d8be 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -103,6 +103,10 @@ % @test phi[1] ≈ 24.92903352636283 % @test phi[2] ≈ 24.363944731838128 +addpath('./WindField/Direction_Interpolation_wErrorCov'); + + +rmpath('./WindField/Direction_Interpolation_wErrorCov'); disp('All tests passed.'); From 71b9819f134adea2bde74b3370d52c2da73e5dad Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Mon, 16 Jun 2025 15:44:50 +0200 Subject: [PATCH 09/26] Add test_broken.m --- test/test_broken.m | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/test_broken.m diff --git a/test/test_broken.m b/test/test_broken.m new file mode 100644 index 0000000..69207b5 --- /dev/null +++ b/test/test_broken.m @@ -0,0 +1,38 @@ +% Julia version: +% dir_mode = Direction_Constant_wErrCov() +% struct WindDirType +% Data::Float64 +% CholSig::Matrix{Float64} +% end +% WindDir = WindDirType(270.0, cholesky(Matrix{Float64}(I, 3, 3)).L) +% iT = [1, 2, 3] +% phi = getWindDirT(dir_mode, WindDir, iT) +% result = [269.6402710931765, 271.0872084924286, 269.5804103830612] +% for (i, ph) in pairs(phi) +% @test ph ≈ result[i] +% end + +% The following test (Matlab version) fails. It returns an array instead of +% a vector. +clear all +addpath('./WindField/Direction_Constant_wErrorCov'); +rng(1234); + +% Cholesky factor of 3x3 identity +L = chol(eye(3), 'lower'); + +% WindDir structure +WindDir.Data = 270.0; +WindDir.CholSig = L; + +iT = [1, 2, 3]; + +% Expected result +result = [269.6402710931765, 271.0872084924286, 269.5804103830612]; + +phi = getWindDirT(WindDir, iT) +% for i = 1:length(phi) +% assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); +% end +rmpath('./WindField/Direction_Constant_wErrorCov'); + From eaf4215db5fa3bb55be1f803dad29b7f7c48c120 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Mon, 16 Jun 2025 15:54:25 +0200 Subject: [PATCH 10/26] Change paths --- test/test_broken.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_broken.m b/test/test_broken.m index 69207b5..bd92224 100644 --- a/test/test_broken.m +++ b/test/test_broken.m @@ -15,7 +15,7 @@ % The following test (Matlab version) fails. It returns an array instead of % a vector. clear all -addpath('./WindField/Direction_Constant_wErrorCov'); +addpath('../WindField/Direction_Constant_wErrorCov'); rng(1234); % Cholesky factor of 3x3 identity @@ -34,5 +34,5 @@ % for i = 1:length(phi) % assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); % end -rmpath('./WindField/Direction_Constant_wErrorCov'); +rmpath('../WindField/Direction_Constant_wErrorCov'); From 89fc4c78e17bf4b8894d6d5fc6f20c3394ed4fc6 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Tue, 17 Jun 2025 18:09:34 +0200 Subject: [PATCH 11/26] Remove broken test, not longer broken --- test/test_broken.m | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 test/test_broken.m diff --git a/test/test_broken.m b/test/test_broken.m deleted file mode 100644 index bd92224..0000000 --- a/test/test_broken.m +++ /dev/null @@ -1,38 +0,0 @@ -% Julia version: -% dir_mode = Direction_Constant_wErrCov() -% struct WindDirType -% Data::Float64 -% CholSig::Matrix{Float64} -% end -% WindDir = WindDirType(270.0, cholesky(Matrix{Float64}(I, 3, 3)).L) -% iT = [1, 2, 3] -% phi = getWindDirT(dir_mode, WindDir, iT) -% result = [269.6402710931765, 271.0872084924286, 269.5804103830612] -% for (i, ph) in pairs(phi) -% @test ph ≈ result[i] -% end - -% The following test (Matlab version) fails. It returns an array instead of -% a vector. -clear all -addpath('../WindField/Direction_Constant_wErrorCov'); -rng(1234); - -% Cholesky factor of 3x3 identity -L = chol(eye(3), 'lower'); - -% WindDir structure -WindDir.Data = 270.0; -WindDir.CholSig = L; - -iT = [1, 2, 3]; - -% Expected result -result = [269.6402710931765, 271.0872084924286, 269.5804103830612]; - -phi = getWindDirT(WindDir, iT) -% for i = 1:length(phi) -% assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); -% end -rmpath('../WindField/Direction_Constant_wErrorCov'); - From 58b326077a1db3fc7ef75d97eb70a95444b5ace2 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Tue, 17 Jun 2025 18:10:32 +0200 Subject: [PATCH 12/26] Add file to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 02efc18..fc9179b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ test/test_windfield.asv +test/.mat From e7bef61820bb9e7ea376ebe95f842a8f86593521 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Tue, 17 Jun 2025 18:10:52 +0200 Subject: [PATCH 13/26] Update create_random_numbers.m --- test/create_random_numbers.m | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/create_random_numbers.m diff --git a/test/create_random_numbers.m b/test/create_random_numbers.m new file mode 100644 index 0000000..ea3dc76 --- /dev/null +++ b/test/create_random_numbers.m @@ -0,0 +1,8 @@ +% a vector. +clear all +rng(1234); +N=1000; + +vec=randn(1,N); +filename = 'test/randn.mat'; +save(filename, 'vec') \ No newline at end of file From 537f1e3c0e8abdab21fade472e02de8fc9cf051f Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Tue, 17 Jun 2025 18:11:07 +0200 Subject: [PATCH 14/26] Update test_windfield.m --- test/test_windfield.m | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/test/test_windfield.m b/test/test_windfield.m index 4c4d8be..19c688c 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -32,28 +32,27 @@ % @test ph ≈ result[i] % end -% % The following test fails -% % clear all -% % addpath('./WindField/Direction_Constant_wErrorCov'); -% % rng(1234); -% % -% % % Cholesky factor of 3x3 identity -% % L = chol(eye(3), 'lower'); -% % -% % % WindDir structure -% % WindDir.Data = 270.0; -% % WindDir.CholSig = L; -% % -% % iT = [1, 2, 3]; -% % -% % % Expected result -% % result = [269.6402710931765, 271.0872084924286, 269.5804103830612]; -% % -% % phi = getWindDirT(WindDir, iT); -% % for i = 1:length(phi) -% % assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); -% % end -% % rmpath('./WindField/Direction_Constant_wErrorCov'); +clear all +addpath('./WindField/Direction_Constant_wErrorCov'); +rng(1234); + +% Cholesky factor of 3x3 identity +L = chol(eye(3), 'lower'); + +% WindDir structure +WindDir.Data = 270.0; +WindDir.CholSig = L; + +iT = [1, 2, 3]'; + +% Expected result +result = [269.0527533560426, 270.54014974707036, 269.78339785902375]; + +phi = getWindDirT(WindDir, iT); +for i = 1:length(phi) + assert(abs(phi(i) - result(i)) < 1e-8, 'Test failed at index %d', i); +end +rmpath('./WindField/Direction_Constant_wErrorCov'); % % dir_mode = Direction_EnKF_InterpTurbine() % # Suppose WindDir is a matrix where each row is [time, phi_T0, phi_T1, ...] From dfe60971e282aa6415b92d744110a727e79917ca Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Tue, 17 Jun 2025 18:23:09 +0200 Subject: [PATCH 15/26] One more test passes --- test/test_windfield.m | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/test_windfield.m b/test/test_windfield.m index 19c688c..81eaf76 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -102,8 +102,33 @@ % @test phi[1] ≈ 24.92903352636283 % @test phi[2] ≈ 24.363944731838128 +clear all addpath('./WindField/Direction_Interpolation_wErrorCov'); +% Wind direction data: [time, direction] +wind_data = [ + 0.0, 10.0; + 5.0, 20.0; + 10.0, 30.0 +]; + +% Cholesky factor (lower triangular) +chol_sig = chol([1.0 0.5; 0.5 1.0], 'lower'); + +% Structure to hold data +WindDir.Data = wind_data; +WindDir.CholSig = chol_sig; + +% Turbine indices and time +iT = [1, 2]'; +t = 7.0; + +phi = getWindDirT(WindDir, iT, t); + +% Test +assert(isequal(size(phi), [2,1])); +assert(abs(phi(1) - 25.84752589085377) < 1e-6); +assert(abs(phi(2) - 25.140544918823198128) < 1e-6); rmpath('./WindField/Direction_Interpolation_wErrorCov'); From 328eb02d82037a3f9715d69087b0cd9d6f89440d Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Tue, 17 Jun 2025 20:13:32 +0200 Subject: [PATCH 16/26] Update comments --- test/test_windfield.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_windfield.m b/test/test_windfield.m index 81eaf76..5ccc2f4 100644 --- a/test/test_windfield.m +++ b/test/test_windfield.m @@ -54,7 +54,7 @@ end rmpath('./WindField/Direction_Constant_wErrorCov'); -% % dir_mode = Direction_EnKF_InterpTurbine() +% dir_mode = Direction_EnKF_InterpTurbine() % # Suppose WindDir is a matrix where each row is [time, phi_T0, phi_T1, ...] % WindDir = [ % 0.0 10.0 20.0 From f884637d29c6d8aa7a9cd3805c5203f9628601a8 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 10 Jul 2025 13:50:12 +0200 Subject: [PATCH 17/26] Add file to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fc9179b..552fcc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ test/test_windfield.asv test/.mat +test/test_windshear.asv From d856ac5a339ff5f093b60bfbaa43fd2a48b7a533 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 10 Jul 2025 13:50:23 +0200 Subject: [PATCH 18/26] Add test_windshear.m --- test/test_windshear.m | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/test_windshear.m diff --git a/test/test_windshear.m b/test/test_windshear.m new file mode 100644 index 0000000..47c0f8a --- /dev/null +++ b/test/test_windshear.m @@ -0,0 +1,29 @@ +% shear_mode = Shear_Interpolation() +% WindShear = [10 0.8; 20 0.9; 30 1.0] # Example data +% z = [5, 15, 25, 35] # Heights to interpolate at +% shear = getWindShearT(shear_mode, WindShear, z) +% @test shear[1] ≈ 0.8 +% @test shear[2] ≈ 0.85 +% @test shear[3] ≈ 0.95 +% @test shear[4] ≈ 1.0 + +clear all +addpath('./WindField/Shear_Interpolation'); +% Example data +WindShear = [10 0.8; 20 0.9; 30 1.0]; + +% Heights to interpolate at +z = [5, 15, 25, 35]; + +% Interpolation (linear by default) +shear = getWindShearT(WindShear, z); + +% Tests (using assert with a tolerance) +assert(abs(shear(1) - 0.8) < 1e-6) +assert(abs(shear(2) - 0.85) < 1e-6) +assert(abs(shear(3) - 0.95) < 1e-6) +assert(abs(shear(4) - 1.0) < 1e-6) +rmpath('./WindField/Shear_Interpolation') + +disp('All tests passed.'); + From a27e1895c2ac04c5eaa2799c7ded18c86258421c Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 17 Jul 2025 13:28:07 +0200 Subject: [PATCH 19/26] Add single wind turbine test --- test/test_single_turbine.m | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 test/test_single_turbine.m diff --git a/test/test_single_turbine.m b/test/test_single_turbine.m new file mode 100644 index 0000000..5a2f5ee --- /dev/null +++ b/test/test_single_turbine.m @@ -0,0 +1,49 @@ +clear all +addpath('./WindField/Velocity_Interpolation_wErrorCov/'); + +% Simple linear data: speed = 5 at t=0, speed = 15 at t=10 +times = [0.0, 10.0]; +speeds = [5.0, 15.0]; +data = [times(:), speeds(:)]; % Column-wise concatenation + +% Simple 2x2 identity Cholesky: noise still generated but uncorrelated +chol = chol(eye(2)); % chol returns upper triangular by default in MATLAB + +% Define a WindVelMatrix function equivalent +wind = WindVelMatrix(data, chol); + +% Test 5: Single turbine (scalar iT input) +iT_scalar = 1; +vel_scalar = getWindSpeedT(wind, iT_scalar, 5.0); + +% assert(length(vel_scalar) == 1); +% assert(abs(vel_scalar(1) - 10.0) <= 3.0); + + +%% --- Supporting functions --- + +function wind = WindVelMatrix(data, chol) + % Store data and chol in a struct (MATLAB equivalent of a Julia type) + wind.Data = data; + wind.CholSig = chol; +end + +% function vel = getWindSpeedT(wind, iT, tQuery) +% % Simple linear interpolation to get speed at time tQuery for turbine iT +% % Assuming wind.data(:,1) = times, wind.data(:,2) = speeds +% +% times = wind.data(:,1); +% speeds = wind.data(:,2); +% +% % Interpolate speed linearly at time tQuery +% speed_t = interp1(times, speeds, tQuery, 'linear'); +% +% % Add noise using chol matrix (noise generation simplified) +% noise = wind.chol * randn(2,1); +% +% % For this example just take the first element noise as perturbation +% speed_noisy = speed_t + noise(1); +% +% % Return speed as scalar (for single turbine) +% vel = speed_noisy; +% end From 3bdacf0d1feb247e123d5774cb7c402024c3da1a Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Fri, 15 Aug 2025 10:48:21 +0200 Subject: [PATCH 20/26] Add saving of mat file, comment sections --- main.m | 67 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/main.m b/main.m index 70f9d14..5322a28 100644 --- a/main.m +++ b/main.m @@ -20,8 +20,9 @@ % ======================================================================= % %% MainFLORIDyn Center-Line model -% Improved FLORIDyn approach over the gaussian FLORIDyn model +% % Improved FLORIDyn approach over the gaussian FLORIDyn model % Link folder with simulation data +tic() pathToSimulation = '2021_9T_Data'; %% Load data from the simulation @@ -48,47 +49,55 @@ %% ====== Init simulation % Run initial conditions until no more change happens T = initSimulation(T,Wind,Sim,Con,Vis,paramFLORIDyn,paramFLORIS); - +Vis.FlowField.Plot.Online = false; %% ============ RUN SIMULATION ============ % T := Simulation state (OP states, Turbine states, wind field states(OPs)) % M := Measurements from the simulation (T_red, T_addedTI, T_Ueff, T_pow) +toc() tic +% Sim.EndTime = 20000; +Sim.nSimSteps = 2; + +filename = "after_init_simulation_T.mat"; +save(filename, 'T'); +display("Saved: "+filename); + [T,M,Vis,Mint] = FLORIDynCL(T,Wind,Sim,Con,Vis,paramFLORIDyn,paramFLORIS); t = toc; disp(['Sec. per sim. step: ' num2str(t/Sim.nSimSteps) ' with '... num2str(T.nT) ' turbine(s), total sim. time: ' num2str(t) ' s.']) %% Plotting & visualization -% Measurements -if Vis.Msmnts.Output - PlotMeasurements(T,M,Wind,Sim,Vis,paramFLORIDyn,paramFLORIS); - pause(0.1) -end +% % Measurements +% if Vis.Msmnts.Output +% PlotMeasurements(T,M,Wind,Sim,Vis,paramFLORIDyn,paramFLORIS); +% pause(0.1) +% end % Flow Field if Vis.FlowField.Plot.Post - PlotFlowField(T,M,Wind,Sim,Vis,paramFLORIDyn,paramFLORIS) - pause(0.1) + PlotFlowField(T,M,Wind,Sim,Vis,paramFLORIDyn,paramFLORIS); + pause(0.1); end -% 3D Flow Field (.vtk for ParaView) -if Vis.FlowField3D.Generate - gen3DFlowField(T,M,Wind,Sim,Vis,paramFLORIDyn,paramFLORIS); -end - -% Final state to skip initialization -if Sim.SaveFinalState - save([Sim.PathToSim 'T_final.mat'],'T') -end +% % 3D Flow Field (.vtk for ParaView) +% if Vis.FlowField3D.Generate +% gen3DFlowField(T,M,Wind,Sim,Vis,paramFLORIDyn,paramFLORIS); +% end -% Save movie if online plotting was activated -if Vis.FlowField.Plot.Online - Vis.Film.InProgress = false; - % Write saved frames as a movie to the simulation folder - v = VideoWriter(Vis.Film.MovFileEffU); - v.FrameRate = 20; - v.Quality = 100; - open(v) - writeVideo(v,Vis.Film.FrmFileEffU) - close(v) -end +% % Final state to skip initialization +% if Sim.SaveFinalState +% save([Sim.PathToSim 'T_final.mat'],'T') +% end +% +% % Save movie if online plotting was activated +% if Vis.FlowField.Plot.Online +% Vis.Film.InProgress = false; +% % Write saved frames as a movie to the simulation folder +% v = VideoWriter(Vis.Film.MovFileEffU); +% v.FrameRate = 20; +% v.Quality = 100; +% open(v) +% writeVideo(v,Vis.Film.FrmFileEffU) +% close(v) +% end From 2c8eac8b272d8e1b1157e34dc0a30871b9a9fd76 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 30 Aug 2025 13:33:13 +0200 Subject: [PATCH 21/26] Add code to save the flow field --- Visualization/PlotFlowField.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Visualization/PlotFlowField.m b/Visualization/PlotFlowField.m index 54e4708..8525595 100644 --- a/Visualization/PlotFlowField.m +++ b/Visualization/PlotFlowField.m @@ -22,6 +22,11 @@ function [Vis] = PlotFlowField(T,~,Wind,Sim,Vis,paramFLORIDyn,paramFLORIS,SimTime) %PLOTFLOWFIELD Generate full flow field plot % exportgraphics(gcf,'9T_flow.pdf','ContentType','vector') + +filename = "input_T_"+Sim.nSimSteps+"_steps.mat"; +save(filename, 'T'); +display("Saved: "+filename); + %% Preallocate field nM = countMeasurements(Vis.FlowField.Data); nM = 3; @@ -42,6 +47,14 @@ Z = getMeasurements(X,Y,nM,T.posNac(1,3),T,paramFLORIS,Wind,Vis); end +out = "v_min: " + min(min(Z(:,:,3))); +out = sprintf('%s\n', out); +out = out + "v_max: " + max(max(Z(:,:,3))); +display(out); +filename = "flowfield_xyz_"+Sim.nSimSteps+"_steps.mat"; +save(filename, 'X', 'Y', 'Z'); +display("Saved: "+filename); + %% Generate & save plots % figure % contourf(X,Y,Z(:,:,1),40,'LineColor','none') From fca4a47f5ae93a5441c7f5bfa001c55e0ec023c8 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 4 Sep 2025 14:44:30 +0200 Subject: [PATCH 22/26] Add files to .gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 552fcc1..363095d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ test/test_windfield.asv test/.mat test/test_windshear.asv +Simulations/2021_9T_Data/setup.mlx +Simulations/2021_9T_Data/setup.mlx +after_init_simulation_T.mat +flowfield_xyz_1_steps.mat +flowfield_xyz_2_steps.mat +input_T_1_steps.mat +input_T_2_steps.mat +input_setUpTmpWFAndRun_2_steps.mat From c26e9f152eddce79427cf0c1aa9667f428461d88 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 4 Sep 2025 14:45:08 +0200 Subject: [PATCH 23/26] Add comment --- main.m | 1 + 1 file changed, 1 insertion(+) diff --git a/main.m b/main.m index 5322a28..b936438 100644 --- a/main.m +++ b/main.m @@ -31,6 +31,7 @@ % Get the settings for the wind field, visualization, controller and Sim. [Wind, Vis, Sim, Con] = setup(); +% Sim.Dyn.OPiteration='IterateOPs_average'; % Add according functions to the search path addFLORISPaths; From f59aa72c6ec5f71221be152cfcb011c56fb4f303 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 4 Sep 2025 14:45:18 +0200 Subject: [PATCH 24/26] Add count_loc --- bin/count_loc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 bin/count_loc diff --git a/bin/count_loc b/bin/count_loc new file mode 100755 index 0000000..087f80c --- /dev/null +++ b/bin/count_loc @@ -0,0 +1,4 @@ +# Copyright (c) 2025 Uwe Fechner +# SPDX-License-Identifier: BSD-3-Clause + +scc -x csv -x toml .. \ No newline at end of file From c6b3bee983e456a7297b98b2a07f3f56ffdc3420 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 4 Sep 2025 14:45:56 +0200 Subject: [PATCH 25/26] Add code to save intermediate results --- FLORIDynCL/FLORIDynCL.m | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/FLORIDynCL/FLORIDynCL.m b/FLORIDynCL/FLORIDynCL.m index d229726..d07a5af 100644 --- a/FLORIDynCL/FLORIDynCL.m +++ b/FLORIDynCL/FLORIDynCL.m @@ -53,6 +53,9 @@ SimTime = Sim.StartTime; for it = 1:Sim.nSimSteps Sim.SimStep = it; + turbulence = T.States_T(201, 3); + display("Info: 1: "+turbulence); + % ========== PREDICTION ========== % Iterate OPs and states T = iterateOPs(T,Sim,paramFLORIS,paramFLORIDyn); @@ -68,11 +71,23 @@ T.intOPs = interpolateOPs(T); % ======= Set up temp Wind farms & run FLORIS + if it == 2 + filename = "input_setUpTmpWFAndRun_"+Sim.nSimSteps+"_steps.mat"; + save(filename, 'T', 'paramFLORIS', 'Wind'); + display("Saved: "+filename); + end [tmpM,T] = setUpTmpWFAndRun(T,paramFLORIS,Wind); + turbulence = T.States_T(201, 3); + display("Info: 4: "+turbulence); + M((it-1)*T.nT+1:it*T.nT,2:4) = tmpM; M((it-1)*T.nT+1:it*T.nT,1) = SimTime; T.States_T(T.StartI,3) = tmpM(:,2); M_int{it} = T.red_arr; + turbulence = T.States_T(201, 3); + format long e + display(tmpM(:,2)); + display("Info: 5: "+turbulence); % ========== Get Wind field variables & correct values [T,Wind] = correctVel(T,Wind,SimTime,paramFLORIS,tmpM); From 6bb87d47bfd44dcf877ad03a1908b31a54e2e218 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 4 Sep 2025 14:46:38 +0200 Subject: [PATCH 26/26] Change settings --- Simulations/2021_9T_Data/setup.mlx | Bin 11676 -> 11475 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Simulations/2021_9T_Data/setup.mlx b/Simulations/2021_9T_Data/setup.mlx index 434fe7f65607051e2e0f3390db944b7c0b9bb359..03b068c36c6cbfb55d8b5490b6017f1f70904aad 100644 GIT binary patch delta 9336 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 delta 9505 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�cg_H7s;!fYGrpi?&A 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