From d8f31c79347f7d7e1e6cd222e77c5489017580ff Mon Sep 17 00:00:00 2001
From: Robin Tamburrini <robin.tamburrini@student.uliege.be>
Date: Thu, 15 Feb 2024 16:31:59 +0100
Subject: [PATCH] feat: add a loop on the lower rotor operation parameter to
 cancel the torque

---
 src/solvers/bemt.m | 64 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 60 insertions(+), 4 deletions(-)

diff --git a/src/solvers/bemt.m b/src/solvers/bemt.m
index 8924ea4..e596b1e 100644
--- a/src/solvers/bemt.m
+++ b/src/solvers/bemt.m
@@ -37,12 +37,66 @@ function bemt(OpRot, Mod)
 
     % First rotor always calculated as if isolated
     bemtsinglerot(OpRot(1), Mod);
+    
+    % Deriving parameters of the second rotor to neutralize the overall torque 
+    if length(OpRot) == 2 % The following torque cancellation method is only valid for N_ROTORS = 2
+        
+        % Solve iteratively
+        isConverged = 0;
+        loopCount = 0;
+        
+        % Parameters to vary to make both rotor torques coincide
+        strategy = Mod.coax.strategy;
+            if ~strcmp(strategy, 'rpm') && ~strcmp(strategy, 'coll')
+                error('bemt: the strategy must be''rpm'' or''coll''.');
+            end
+            
+        % Lower and Upper bounds for bissection method
+        if strcmp(strategy, 'rpm')
+            OpRot_l = 1/10*OpRot(1).Op.(strategy);
+            OpRot_u = 10*OpRot(1).Op.(strategy);
+        else
+            OpRot_l = -10*OpRot(1).Op.(strategy);
+            OpRot_u = 10*OpRot(1).Op.(strategy);
+        end
+        
+        while (~isConverged) && loopCount <= Mod.Num.maxIter
+            
+            OpRot(2).Op.(strategy) = (OpRot_l + OpRot_u)/2; %Midpoint
+            
+            OpRot(2).ElPerf.updateupstreamvel(OpRot(1), Mod); % Update upstream velocity
+            bemtsinglerot(OpRot(2), Mod); % Calculate BEMT as usual, with new upstream vel distribution
+            
+            % Convergence criterion 
+            if abs(OpRot(1).torque - OpRot(2).torque) < Mod.Num.convCrit
+                isConverged = 1;
+            else
+                % Update the bounds based on the torque
+                if OpRot(2).torque < OpRot(1).torque
+                    OpRot_l = OpRot(2).Op.(strategy);
+                else
+                    OpRot_u = OpRot(2).Op.(strategy);
+                end
+            end
+            
+            if loopCount == Mod.Num.maxIter
+            warning('Rotare:Solvers:bemt:NotConverged', ...
+                    ['Solution not converged for the coaxial case after reaching the '...
+                     'maximum number of iterations (%d iter).\n'], loopCount);
+            end
+            loopCount = loopCount + 1;
+        end
+        
+    else % If there are more than 2 blades, the function can not support to cancel the total torque
+        
+         warning('bemt: the total number of rotors in coaxial configuration must be equal to 2.')
 
-    for i = 2:length(OpRot)
-        OpRot(i).ElPerf.updateupstreamvel(OpRot(i - 1), Mod); % Update upstream velocity
-        bemtsinglerot(OpRot(i), Mod); % Calculate BEMT as usual, with new upstream vel distribution
+         for i = 2:length(OpRot)
+                OpRot(i).ElPerf.updateupstreamvel(OpRot(i - 1), Mod); % Update upstream velocity
+                bemtsinglerot(OpRot(i), Mod); % Calculate BEMT as usual, with new upstream vel distribution
+         end
+         
     end
-
 end
 
 function bemtsinglerot(OpRot, Mod)
@@ -53,9 +107,11 @@ function bemtsinglerot(OpRot, Mod)
     % Determine angles, velocities and forces for each element using the proper solver
     solver = str2func(Mod.solver);
     solver(OpRot, Mod);
+            
 
     % Calculate elemental forces, torque and power
     OpRot.ElPerf.calcforces;
+    
 
     % Calculate overall rotor performance
     OpRot.calcperf;
-- 
GitLab