internal static void turbinesCalculations(out double dTurb, out int nTurb, out double kWake, out ILArray<double> x, out int gridX, out int gridY, out ILArray<double> yOrder, out double dy, out ILArray<int> xTurbC, out ILArray<int> yTurbC, WindTurbineParameters parm, SimParm simParm) { #region "Used variables declaration" ILArray<double> data; double rotA; int gridRes; int endSize; ILArray<double> y; ILArray<double> xCoor; ILArray<double> yCoor; ILArray<double> xTurb; ILArray<double> yTurb; ILArray<double> xOrder; int ppp; ILArray<double> xGrid; ILArray<double> yGrid; #endregion data = parm.wf.C; dTurb = 2 * parm.radius._(1); nTurb = parm.N; rotA = parm.rotA; kWake = parm.kWake; gridRes = simParm.gridRes; // Grid Resolution, the lower the number, the higher the amount of points computed. endSize = simParm.grid; //if (Ct < 0) // !ILMath.isreal(Ct) | { //disp('Ct is negative or complex'); } x = _c(1.0, gridRes, endSize);// x-grid. y = _c(1.0, gridRes, endSize);// y-grid. gridX = length(x); // Number of grid points. gridY = length(y); // Number of grid points. xCoor = data[_(':'), _(1)]; // Coordiante of turbine, x-position yCoor = data[_(':'), _(2)]; // Coordinate of turbine, y-position ROTATE_corrd(out xTurb, out yTurb, xCoor, yCoor, rotA); // Rotated (and scaled) coordinates WT_order(out xOrder, out yOrder, xTurb, yTurb); // Ordered turbines. ppp = 2; // This parameter is also a bit weird.. But it changes the grid. DOMAIN_pt(out xGrid, out _double, gridX, dTurb, xOrder, ppp); // ppp = 5; DOMAIN_pt(out yGrid, out dy, gridY, dTurb, yOrder, ppp); Turb_centr_coord(out xTurbC, nTurb, gridX, xGrid, xOrder, gridRes); // Determines the grid point closest to the turbine. Turb_centr_coord(out yTurbC, nTurb, gridY, yGrid, yOrder, gridRes); // Determines the grid point closest to the turbine. }
//% v_nac = WAKECALCULATION(Ct,i,wind) // RLC, Aalborg // The below is based on the .F90 code developed by ?, and will give a // better estimate of the actual wake the individual turbines experience. #endregion internal static void wakeCalculationsRLC(out ILArray<double> vNac, double dTurb, int nTurb, double kWake, ILArray<double> x, int gridX, int gridY, ILArray<double> yOrder, double dy, ILArray<int> xTurbC, ILArray<int> yTurbC, ILArray<double> Ct, ILArray<double> wField, double[] vHub, WindTurbineParameters parm, SimParm simParm) { #region "Used variables declaration" double[,] Velocity; int j; #endregion // Velocity Computation Compute_Vell(out Velocity, yOrder, xTurbC, yTurbC, x, wField, vHub, kWake, gridX, gridY, nTurb, dTurb, Ct, dy); // Extracting the individual Nacelle Wind Speeds from the wind velocity matrix. //Velocity = Velocity'; vNac = zeros(nTurb, 1); for (j = 1; j <= length(xTurbC); j++) { vNac._(j, '=', Velocity[yTurbC._(j) - 1, xTurbC._(j) - 1]); } }
//P_ref is a vector of power refenreces for tehe wind turbine with dimension 1xN //v_nac is a vector of wind speed at each wind turbine with dimension 1xN //P_demand is a scale of the wind farm power demand. //parm is a struct of wind turbine parameters e.g. NREL5MW #endregion internal static void powerDistributionControl(out ILArray<double> P_ref, out ILArray<double> P_a, double[] v_nac, double P_demand, WindTurbineParameters parm) { #region "Used variables declaration" double rho; ILArray<double> R; ILArray<double> rated; ILArray<double> Cp; int i; #endregion rho = parm.rho; //air density for each wind turbine(probably the same for all) R = parm.radius.C; //rotor radius for each wind turbine(NREL.r=63m) rated = parm.rated.C; //Rated power for each wind turbine(NREL.Prated=5MW) Cp = parm.Cp.C; // Max cp of the turbines for each wind turbine(NREL.Cp.max=0.45) P_a = zeros(parm.N, 1); P_ref = zeros(parm.N, 1); // Compute available power at each turbine for (i = 1; i <= parm.N; i++) { P_a._(i, '=', min_(__[ rated._(i), (pi / 2) * rho * _p(R._(i), 2) * _p(v_nac[i - 1], 3) * Cp._(i) ])); } var sum_P_a_ = sum_(P_a); //Distribute power according to availibility for (i = 1; i <= parm.N; i++) { if (P_demand < sum_P_a_) { P_ref._(i, '=', max_(__[ 0, min_(__[ rated._(i), P_demand * P_a._(i) / sum_P_a_ ]) ])); } else { P_ref._(i, '=', P_a._(i)); } } }
//% v_nac = WAKECALCULATION(Ct,i,wind) // RLC, Aalborg // The below is based on the .F90 code developed by ?, and will give a // better estimate of the actual wake the individual turbines experience. #endregion internal static void wakeCalculationsRLC(out ILArray <double> vNac, double dTurb, int nTurb, double kWake, ILArray <double> x, int gridX, int gridY, ILArray <double> yOrder, double dy, ILArray <int> xTurbC, ILArray <int> yTurbC, ILArray <double> Ct, ILArray <double> wField, double[] vHub, WindTurbineParameters parm, SimParm simParm) { #region "Used variables declaration" double[,] Velocity; int j; #endregion // Velocity Computation Compute_Vell(out Velocity, yOrder, xTurbC, yTurbC, x, wField, vHub, kWake, gridX, gridY, nTurb, dTurb, Ct, dy); // Extracting the individual Nacelle Wind Speeds from the wind velocity matrix. //Velocity = Velocity'; vNac = zeros(nTurb, 1); for (j = 1; j <= length(xTurbC); j++) { vNac._(j, '=', Velocity[yTurbC._(j) - 1, xTurbC._(j) - 1]); } }
//P_ref is a vector of power refenreces for tehe wind turbine with dimension 1xN //v_nac is a vector of wind speed at each wind turbine with dimension 1xN //P_demand is a scale of the wind farm power demand. //parm is a struct of wind turbine parameters e.g. NREL5MW #endregion internal static void powerDistributionControl(out ILArray <double> P_ref, out ILArray <double> P_a, double[] v_nac, double P_demand, WindTurbineParameters parm) { #region "Used variables declaration" double rho; ILArray <double> R; ILArray <double> rated; ILArray <double> Cp; int i; #endregion rho = parm.rho; //air density for each wind turbine(probably the same for all) R = parm.radius.C; //rotor radius for each wind turbine(NREL.r=63m) rated = parm.rated.C; //Rated power for each wind turbine(NREL.Prated=5MW) Cp = parm.Cp.C; // Max cp of the turbines for each wind turbine(NREL.Cp.max=0.45) P_a = zeros(parm.N, 1); P_ref = zeros(parm.N, 1); // Compute available power at each turbine for (i = 1; i <= parm.N; i++) { P_a._(i, '=', min_(__[rated._(i), (pi / 2) * rho * _p(R._(i), 2) * _p(v_nac[i - 1], 3) * Cp._(i)])); } var sum_P_a_ = sum_(P_a); //Distribute power according to availibility for (i = 1; i <= parm.N; i++) { if (P_demand < sum_P_a_) { P_ref._(i, '=', max_(__[0, min_(__[rated._(i), P_demand * P_a._(i) / sum_P_a_])])); } else { P_ref._(i, '=', P_a._(i)); } } }
public static double[][] FarmControl(WakeFarmControlConfig config, out double[][] dataOut) { #region "Used variables declaration" bool saveData; bool enablePowerDistribution; bool enableTurbineDynamics; bool powerRefInterpolation; bool enableVaryingDemand; SimParm simParm; ILArray<double> wind; WindTurbineParameters parm; EnvMatFileDataStructure env; WtMatFileDataStructure wt; ILArray<int> idx; double Mg_max_rate; double Ki, Kp, Umax, Umin; double VS_CtInSp, VS_RtGnSp, VS_Rgn2K; double omega0, beta0, power0; ILArray<double> initMatrix; ILArray<double> sumPower; ILArray<double> sumRef; ILArray<double> sumAvai; ILArray<double> P_ref_new; ILArray<double> P_demand; ILArray<double> v_nac; ILArray<double> P_ref; ILArray<double> Pa; ILArray<double> Power; ILArray<double> beta; ILArray<double> Omega; ILArray<double> initVector; ILArray<double> Mg___i_; ILArray<double> wField; double dZ; ILArray<double> du; double[] x___1_; double[] x___2_; double[] u___1_; double[] u___2_; double alpha; int j; ILArray<double> dx; ILArray<double> time; #endregion //% Initialization //General settings to be changed saveData = config.saveData; // Save all the simulated data to a .mat file? enablePowerDistribution = config.enablePowerDistribution; // Enable wind farm control and not only constant power enableTurbineDynamics = config.enableTurbineDynamics; // Enable dynamical turbine model. Disabling this will increase the speed significantly, but also lower the fidelity of the results (setting to false does not work properly yet) powerRefInterpolation = config.powerRefInterpolation; // Power Reference table interpolation. enableVaryingDemand = config.enableVaryingDemand; // Varying Reference // Simulation Properties: simParm = new SimParm(); simParm.tStart = config.SimParm.tStart; // time start simParm.timeStep = config.SimParm.timeStep; // time step, 8Hz - the NREL model is 80Hz (for reasons unknown) simParm.tEnd = config.SimParm.tEnd; // time end int _simParm_tEnd_simParm_tStart__simParm_timeStep = (int)((simParm.tEnd - simParm.tStart) / simParm.timeStep); simParm.gridRes = config.SimParm.gridRes; // Grid Resolution simParm.grid = config.SimParm.grid; // Grid Size simParm.ctrlUpdate = config.SimParm.ctrlUpdate; // Update inverval for farm controller simParm.powerUpdate = config.SimParm.powerUpdate; // How often the control algorithm should update! load(config.Wind_MatFile, out wind); // Load Wind Data // Wind farm and Turbine Properties properties parm = new WindTurbineParameters(); parm.wf = load(config.Turbines); // Loads the Wind Farm Layout. parm.N = length(parm.wf); // number of turbines in farm parm.rotA = -48.80; // Angle of Attack parm.kWake = 0.06; //% Turbine properties - Loaded from the NREL5MW.mat file load(config.NREL5MW_MatFile, out env, out wt); // Load parameters from the NREL 5MW Reference turbine struct. parm.rho = env.rho; // air density parm.radius = wt.rotor.radius * ones(1, parm.N); // rotor radius (NREL5MW) parm.rated = wt.ctrl.p_rated * wt.gen.effeciency * ones(1, parm.N); //rated power (NREL5MW) parm.ratedSpeed = wt.rotor.ratedspeed; //rated rotor speed max(out idx, wt.cp.table[_(':')]); // Find index for max Cp parm.Ct = 0.0 * wt.ct.table[_(idx)] * ones(parm.N, (_simParm_tEnd_simParm_tStart__simParm_timeStep)); // Define initial Ct as the optimal Ct. parm.Cp = wt.cp.table[_(idx)] * ones(parm.N, (_simParm_tEnd_simParm_tStart__simParm_timeStep)); // Define initial Cp as the optimal Cp. Mg_max_rate = wt.ctrl.torq.ratelim; // Rate-limit on Torque Change. //Pitch control Ki = wt.ctrl.pitch.Igain; // 0.008068634*360/2/pi; % integral gain (NREL5MW). Kp = wt.ctrl.pitch.Pgain; // 0.01882681*360/2/pi; % proportional gain (NREL5MW). Umax = wt.ctrl.pitch.ulim; // Upper limit of the pitch controller Umin = wt.ctrl.pitch.llim; // Lower limit of the pitch controller. // NREL Regional Control - extracted from the NREL report. VS_CtInSp = 70.162240; VS_RtGnSp = 121.680500; VS_Rgn2K = 2.332287; //% Set initial conditions omega0 = wt.rotor.ratedspeed; // Desired Rotation speed beta0 = 0; // wt.ctrl.pitch.llim; % Initial pitch at zero. power0 = 0; // Power Production //% Memory Allocation and Memory Initialization initMatrix = zeros(parm.N, _simParm_tEnd_simParm_tStart__simParm_timeStep); sumPower = initMatrix[_(1), _(':')]; // Initialize produced power vector sumRef = initMatrix[_(1), _(':')]; // Initialize reference power vector sumAvai = initMatrix[_(1), _(':')]; // Initialize available power vector P_ref_new = initMatrix[_(1), _(':')]; // Initialize new reference vector P_demand = initMatrix[_(1), _(':')]; // Initialize power demand vector v_nac = initMatrix.C; // Initialize hub velocity matrix. P_ref = initMatrix.C; // Initialize matrix to save the power production history for each turbine. Pa = initMatrix.C; // Initialize available power matrix. Power = initMatrix.C; // Initialize individual WT power production matrix. beta = initMatrix.C; // Initialize pitch matrix. Omega = initMatrix.C; // Initialize revolutional velocity matrix. initVector = ones(parm.N, 1); Mg___i_ = 0 * initVector; wField = zeros(simParm.grid / simParm.gridRes, simParm.grid / simParm.gridRes); // Wind field matrix //% Controller Initizliation dZ = 0; // Integrator Initialization. du = zeros(parm.N, 1); // Integration variable. x___1_ = (omega0 * initVector).GetArrayForRead(); x___2_ = (0 * initVector).GetArrayForRead(); u___1_ = (beta0 * initVector).GetArrayForRead(); // u0 u___2_ = (power0 * initVector).GetArrayForRead(); // u0 P_demand._(1, '=', config.InitialPowerDemand); // Power Demand. double turbinesDTurb; int turbinesNTurb; double turbinesKWake; ILArray<double> turbinesX; int turbinesGridX; int turbinesGridY; ILArray<double> turbinesYOrder; double turbinesDy; ILArray<int> turbinesXTurbC; ILArray<int> turbinesYTurbC; turbinesCalculations(out turbinesDTurb, out turbinesNTurb, out turbinesKWake, out turbinesX, out turbinesGridX, out turbinesGridY, out turbinesYOrder, out turbinesDy, out turbinesXTurbC, out turbinesYTurbC, parm, simParm); //% Simulate wind farm operation for (var i = 2; i <= _simParm_tEnd_simParm_tStart__simParm_timeStep; i++) // At each sample time (DT) from Tstart to Tend { //clc; //fprintf('Iteration Counter: %i out of %i \n', i, config.SimParm.tEnd * (1 / config.SimParm.timeStep)); //%%%%%%%%%%%%%%% WIND FIELD FIFO MATRIX %%%%%%%%%%%%%%%%%%% wField[_(':'), _(2, ':', end)] = wField[_(':'), _(1, ':', (end - 1))]; wField[_(':'), _(1)] = wind._(i, 2) * ones(simParm.grid / simParm.gridRes, 1) + randn(simParm.grid / simParm.gridRes, 1) * 0.5; //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Calculate the wake using the last Ct values ILArray<double> v_nac___i_; wakeCalculationsRLC(out v_nac___i_, turbinesDTurb, turbinesNTurb, turbinesKWake, turbinesX, turbinesGridX, turbinesGridY, turbinesYOrder, turbinesDy, turbinesXTurbC, turbinesYTurbC, parm.Ct[_(':'), _(i - 1)], transpose(wField), x___2_, parm, simParm); v_nac[_(':'), _(i)] = v_nac___i_; x___2_ = (v_nac[_(':'), _(i)]).GetArrayForRead(); if (enableVaryingDemand) // A random walk to simulate fluctuations in the power demand. { P_demand._(i, '=', P_demand._(i - 1) + randn() * 50000); } else { P_demand._(i, '=', P_demand._(i - 1)); } // Farm control // Calculate the power distribution references for each turbine if (enablePowerDistribution) { ILArray<double> Pa___i_; powerDistributionControl(out P_ref_new, out Pa___i_, x___2_, P_demand._(i), parm); Pa[_(':'), _(i)] = Pa___i_; } //Hold the demand for some seconds if (mod(i, round(simParm.ctrlUpdate / simParm.timeStep)) == simParm.powerUpdate) { P_ref[_(':'), _(i)] = P_ref_new; } else { if (powerRefInterpolation) { alpha = 0.01; P_ref[_(':'), _(i)] = (1 - alpha) * P_ref[_(':'), _(i - 1)] + (alpha) * P_ref_new; } else { P_ref[_(':'), _(i)] = P_ref_new; } } //Torque controller for (j = 1; j <= parm.N; j++) { if ((x___1_[j - 1] * 97 >= VS_RtGnSp) || (u___1_[j - 1] >= 1)) //! We are in region 3 - power is constant { u___2_[j - 1] = P_ref._(j, i) / x___1_[j - 1]; } else if (x___1_[j - 1] * 97 <= VS_CtInSp) //! We are in region 1 - torque is zero { u___2_[j - 1] = 0.0; } else //! We are in region 2 - optimal torque is proportional to the square of the generator speed { u___2_[j - 1] = 97 * VS_Rgn2K * x___1_[j - 1] * x___1_[j - 1] * _p(97, 2); } } dx = (omega0 - ((ILArray<double>)x___1_)) - (omega0 - Omega[_(':'), _(i - 1)]); du = Kp * dx + Ki * simParm.timeStep * (omega0 - ((ILArray<double>)(x___1_))); du = min(max(du, -wt.ctrl.pitch.ratelim), wt.ctrl.pitch.ratelim); u___1_ = (min(max(((ILArray<double>)u___1_) + du * simParm.timeStep, Umin), Umax)).GetArrayForRead(); Mg___i_ = u___2_; // Torque Input beta[_(':'), _(i)] = u___1_; // Pitch Input // Turbine dynamics - can be simplified: if (enableTurbineDynamics) { for (j = 1; j <= parm.N; j++) { double x_j_1_; double parm_Ct_j_i_; double parm_Cp_j_i_; turbineDrivetrainModel(out x_j_1_, out parm_Ct_j_i_, out parm_Cp_j_i_, x___1_[j - 1], x___2_[j - 1], u___1_[j - 1], u___2_[j - 1], wt, env, simParm.timeStep); x___1_[j - 1] = x_j_1_; parm.Ct._(j, i, '=', parm_Ct_j_i_); parm.Cp._(j, i, '=', parm_Cp_j_i_); } } else { x___1_.Fill(parm.ratedSpeed); // Rotational speed } Omega[_(':'), _(i)] = x___1_; Power[_(':'), _(i)] = Omega[_(':'), _(i)] * Mg___i_; // Power Summations sumPower._(i, '=', sum_(Power[_(':'), _(i)]) * _p(10, -6)); sumRef._(i, '=', sum_(P_ref[_(':'), _(i)]) * _p(10, -6)); sumAvai._(i, '=', sum_(Pa[_(':'), _(i)]) * _p(10, -6)); // NOWCASTING FUNKTION HER // powerPrediction(i) = powerPrediction(i,sumPower(i:-1:i-10)) % or something // similar. } //% //time = (_c((decimal)simParm.tStart, (decimal)simParm.timeStep, (decimal)simParm.tEnd - (decimal)simParm.timeStep)).T; time = (_c(0.0, 1.0, _simParm_tEnd_simParm_tStart__simParm_timeStep - 1) * simParm.timeStep + simParm.tStart).T; if (saveData) { //throw new ApplicationException(string.Format("{0}\t{1}\t{2}\t{3}", time.Dimensions, sumPower.T.Dimensions, sumRef.T.Dimensions, sumAvai.T.Dimensions)); dataOut = (__[ time, sumPower.T, sumRef.T, sumAvai.T ]).ToDoubleArray(); //save dataOut; } else { dataOut = new double[][] { new double[] { 0, 0, 0, 0 } }; } //% Plotting //Below a number of different plots are made. Most of them for test purposes ILArray<double> plotsData; plotsData = time.C; plotsData = __[plotsData, P_ref.T * (_p(10, -6))]; // f1 = figure(1); xlabel('Time [s]'); ylabel('Power Reference [MW]'); title('Individual Power Reference'); plotsData = __[ plotsData, v_nac.T ]; // f2 = figure(2); xlabel('Time [s]'); ylabel('Wind Speed [m/s]'); title('Wind Speed @ individual turbine'); plotsData = __[ plotsData, beta.T ]; // f4 = figure(4); xlabel('Time [s]'); ylabel('Pitch Angle [deg]'); title('Evolution of Pitch angle over time'); plotsData = __[ plotsData, Omega.T ]; // f5 = figure(5); xlabel('Time [s]'); ylabel('Revolutional Velocity [rpm]'); title('Evolution of Revolutional Velocity over time'); plotsData = __[ plotsData, sumRef.T, sumAvai.T, sumPower.T ]; // f3 = figure(3); xlabel('Time [s]'); ylabel('Power [MW]'); title('Power Plot'); legend('Reference','Available','Produced'); return plotsData.ToDoubleArray(); }
internal static void turbinesCalculations(out double dTurb, out int nTurb, out double kWake, out ILArray <double> x, out int gridX, out int gridY, out ILArray <double> yOrder, out double dy, out ILArray <int> xTurbC, out ILArray <int> yTurbC, WindTurbineParameters parm, SimParm simParm) { #region "Used variables declaration" ILArray <double> data; double rotA; int gridRes; int endSize; ILArray <double> y; ILArray <double> xCoor; ILArray <double> yCoor; ILArray <double> xTurb; ILArray <double> yTurb; ILArray <double> xOrder; int ppp; ILArray <double> xGrid; ILArray <double> yGrid; #endregion data = parm.wf.C; dTurb = 2 * parm.radius._(1); nTurb = parm.N; rotA = parm.rotA; kWake = parm.kWake; gridRes = simParm.gridRes; // Grid Resolution, the lower the number, the higher the amount of points computed. endSize = simParm.grid; //if (Ct < 0) // !ILMath.isreal(Ct) | { //disp('Ct is negative or complex'); } x = _c(1.0, gridRes, endSize); // x-grid. y = _c(1.0, gridRes, endSize); // y-grid. gridX = length(x); // Number of grid points. gridY = length(y); // Number of grid points. xCoor = data[_(':'), _(1)]; // Coordiante of turbine, x-position yCoor = data[_(':'), _(2)]; // Coordinate of turbine, y-position ROTATE_corrd(out xTurb, out yTurb, xCoor, yCoor, rotA); // Rotated (and scaled) coordinates WT_order(out xOrder, out yOrder, xTurb, yTurb); // Ordered turbines. ppp = 2; // This parameter is also a bit weird.. But it changes the grid. DOMAIN_pt(out xGrid, out _double, gridX, dTurb, xOrder, ppp); // ppp = 5; DOMAIN_pt(out yGrid, out dy, gridY, dTurb, yOrder, ppp); Turb_centr_coord(out xTurbC, nTurb, gridX, xGrid, xOrder, gridRes); // Determines the grid point closest to the turbine. Turb_centr_coord(out yTurbC, nTurb, gridY, yGrid, yOrder, gridRes); // Determines the grid point closest to the turbine. }
public static double[][] FarmControl(WakeFarmControlConfig config, out double[][] dataOut) { #region "Used variables declaration" bool saveData; bool enablePowerDistribution; bool enableTurbineDynamics; bool powerRefInterpolation; bool enableVaryingDemand; SimParm simParm; ILArray <double> wind; WindTurbineParameters parm; EnvMatFileDataStructure env; WtMatFileDataStructure wt; ILArray <int> idx; double Mg_max_rate; double Ki, Kp, Umax, Umin; double VS_CtInSp, VS_RtGnSp, VS_Rgn2K; double omega0, beta0, power0; ILArray <double> initMatrix; ILArray <double> sumPower; ILArray <double> sumRef; ILArray <double> sumAvai; ILArray <double> P_ref_new; ILArray <double> P_demand; ILArray <double> v_nac; ILArray <double> P_ref; ILArray <double> Pa; ILArray <double> Power; ILArray <double> beta; ILArray <double> Omega; ILArray <double> initVector; ILArray <double> Mg___i_; ILArray <double> wField; double dZ; ILArray <double> du; double[] x___1_; double[] x___2_; double[] u___1_; double[] u___2_; double alpha; int j; ILArray <double> dx; ILArray <double> time; #endregion //% Initialization //General settings to be changed saveData = config.saveData; // Save all the simulated data to a .mat file? enablePowerDistribution = config.enablePowerDistribution; // Enable wind farm control and not only constant power enableTurbineDynamics = config.enableTurbineDynamics; // Enable dynamical turbine model. Disabling this will increase the speed significantly, but also lower the fidelity of the results (setting to false does not work properly yet) powerRefInterpolation = config.powerRefInterpolation; // Power Reference table interpolation. enableVaryingDemand = config.enableVaryingDemand; // Varying Reference // Simulation Properties: simParm = new SimParm(); simParm.tStart = config.SimParm.tStart; // time start simParm.timeStep = config.SimParm.timeStep; // time step, 8Hz - the NREL model is 80Hz (for reasons unknown) simParm.tEnd = config.SimParm.tEnd; // time end int _simParm_tEnd_simParm_tStart__simParm_timeStep = (int)((simParm.tEnd - simParm.tStart) / simParm.timeStep); simParm.gridRes = config.SimParm.gridRes; // Grid Resolution simParm.grid = config.SimParm.grid; // Grid Size simParm.ctrlUpdate = config.SimParm.ctrlUpdate; // Update inverval for farm controller simParm.powerUpdate = config.SimParm.powerUpdate; // How often the control algorithm should update! load(config.Wind_MatFile, out wind); // Load Wind Data // Wind farm and Turbine Properties properties parm = new WindTurbineParameters(); parm.wf = load(config.Turbines); // Loads the Wind Farm Layout. parm.N = length(parm.wf); // number of turbines in farm parm.rotA = -48.80; // Angle of Attack parm.kWake = 0.06; //% Turbine properties - Loaded from the NREL5MW.mat file load(config.NREL5MW_MatFile, out env, out wt); // Load parameters from the NREL 5MW Reference turbine struct. parm.rho = env.rho; // air density parm.radius = wt.rotor.radius * ones(1, parm.N); // rotor radius (NREL5MW) parm.rated = wt.ctrl.p_rated * wt.gen.effeciency * ones(1, parm.N); //rated power (NREL5MW) parm.ratedSpeed = wt.rotor.ratedspeed; //rated rotor speed max(out idx, wt.cp.table[_(':')]); // Find index for max Cp parm.Ct = 0.0 * wt.ct.table[_(idx)] * ones(parm.N, (_simParm_tEnd_simParm_tStart__simParm_timeStep)); // Define initial Ct as the optimal Ct. parm.Cp = wt.cp.table[_(idx)] * ones(parm.N, (_simParm_tEnd_simParm_tStart__simParm_timeStep)); // Define initial Cp as the optimal Cp. Mg_max_rate = wt.ctrl.torq.ratelim; // Rate-limit on Torque Change. //Pitch control Ki = wt.ctrl.pitch.Igain; // 0.008068634*360/2/pi; % integral gain (NREL5MW). Kp = wt.ctrl.pitch.Pgain; // 0.01882681*360/2/pi; % proportional gain (NREL5MW). Umax = wt.ctrl.pitch.ulim; // Upper limit of the pitch controller Umin = wt.ctrl.pitch.llim; // Lower limit of the pitch controller. // NREL Regional Control - extracted from the NREL report. VS_CtInSp = 70.162240; VS_RtGnSp = 121.680500; VS_Rgn2K = 2.332287; //% Set initial conditions omega0 = wt.rotor.ratedspeed; // Desired Rotation speed beta0 = 0; // wt.ctrl.pitch.llim; % Initial pitch at zero. power0 = 0; // Power Production //% Memory Allocation and Memory Initialization initMatrix = zeros(parm.N, _simParm_tEnd_simParm_tStart__simParm_timeStep); sumPower = initMatrix[_(1), _(':')]; // Initialize produced power vector sumRef = initMatrix[_(1), _(':')]; // Initialize reference power vector sumAvai = initMatrix[_(1), _(':')]; // Initialize available power vector P_ref_new = initMatrix[_(1), _(':')]; // Initialize new reference vector P_demand = initMatrix[_(1), _(':')]; // Initialize power demand vector v_nac = initMatrix.C; // Initialize hub velocity matrix. P_ref = initMatrix.C; // Initialize matrix to save the power production history for each turbine. Pa = initMatrix.C; // Initialize available power matrix. Power = initMatrix.C; // Initialize individual WT power production matrix. beta = initMatrix.C; // Initialize pitch matrix. Omega = initMatrix.C; // Initialize revolutional velocity matrix. initVector = ones(parm.N, 1); Mg___i_ = 0 * initVector; wField = zeros(simParm.grid / simParm.gridRes, simParm.grid / simParm.gridRes); // Wind field matrix //% Controller Initizliation dZ = 0; // Integrator Initialization. du = zeros(parm.N, 1); // Integration variable. x___1_ = (omega0 * initVector).GetArrayForRead(); x___2_ = (0 * initVector).GetArrayForRead(); u___1_ = (beta0 * initVector).GetArrayForRead(); // u0 u___2_ = (power0 * initVector).GetArrayForRead(); // u0 P_demand._(1, '=', config.InitialPowerDemand); // Power Demand. double turbinesDTurb; int turbinesNTurb; double turbinesKWake; ILArray <double> turbinesX; int turbinesGridX; int turbinesGridY; ILArray <double> turbinesYOrder; double turbinesDy; ILArray <int> turbinesXTurbC; ILArray <int> turbinesYTurbC; turbinesCalculations(out turbinesDTurb, out turbinesNTurb, out turbinesKWake, out turbinesX, out turbinesGridX, out turbinesGridY, out turbinesYOrder, out turbinesDy, out turbinesXTurbC, out turbinesYTurbC, parm, simParm); //% Simulate wind farm operation for (var i = 2; i <= _simParm_tEnd_simParm_tStart__simParm_timeStep; i++) // At each sample time (DT) from Tstart to Tend { //clc; //fprintf('Iteration Counter: %i out of %i \n', i, config.SimParm.tEnd * (1 / config.SimParm.timeStep)); //%%%%%%%%%%%%%%% WIND FIELD FIFO MATRIX %%%%%%%%%%%%%%%%%%% wField[_(':'), _(2, ':', end)] = wField[_(':'), _(1, ':', (end - 1))]; wField[_(':'), _(1)] = wind._(i, 2) * ones(simParm.grid / simParm.gridRes, 1) + randn(simParm.grid / simParm.gridRes, 1) * 0.5; //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Calculate the wake using the last Ct values ILArray <double> v_nac___i_; wakeCalculationsRLC(out v_nac___i_, turbinesDTurb, turbinesNTurb, turbinesKWake, turbinesX, turbinesGridX, turbinesGridY, turbinesYOrder, turbinesDy, turbinesXTurbC, turbinesYTurbC, parm.Ct[_(':'), _(i - 1)], transpose(wField), x___2_, parm, simParm); v_nac[_(':'), _(i)] = v_nac___i_; x___2_ = (v_nac[_(':'), _(i)]).GetArrayForRead(); if (enableVaryingDemand) // A random walk to simulate fluctuations in the power demand. { P_demand._(i, '=', P_demand._(i - 1) + randn() * 50000); } else { P_demand._(i, '=', P_demand._(i - 1)); } // Farm control // Calculate the power distribution references for each turbine if (enablePowerDistribution) { ILArray <double> Pa___i_; powerDistributionControl(out P_ref_new, out Pa___i_, x___2_, P_demand._(i), parm); Pa[_(':'), _(i)] = Pa___i_; } //Hold the demand for some seconds if (mod(i, round(simParm.ctrlUpdate / simParm.timeStep)) == simParm.powerUpdate) { P_ref[_(':'), _(i)] = P_ref_new; } else { if (powerRefInterpolation) { alpha = 0.01; P_ref[_(':'), _(i)] = (1 - alpha) * P_ref[_(':'), _(i - 1)] + (alpha) * P_ref_new; } else { P_ref[_(':'), _(i)] = P_ref_new; } } //Torque controller for (j = 1; j <= parm.N; j++) { if ((x___1_[j - 1] * 97 >= VS_RtGnSp) || (u___1_[j - 1] >= 1)) //! We are in region 3 - power is constant { u___2_[j - 1] = P_ref._(j, i) / x___1_[j - 1]; } else if (x___1_[j - 1] * 97 <= VS_CtInSp) //! We are in region 1 - torque is zero { u___2_[j - 1] = 0.0; } else //! We are in region 2 - optimal torque is proportional to the square of the generator speed { u___2_[j - 1] = 97 * VS_Rgn2K * x___1_[j - 1] * x___1_[j - 1] * _p(97, 2); } } dx = (omega0 - ((ILArray <double>)x___1_)) - (omega0 - Omega[_(':'), _(i - 1)]); du = Kp * dx + Ki * simParm.timeStep * (omega0 - ((ILArray <double>)(x___1_))); du = min(max(du, -wt.ctrl.pitch.ratelim), wt.ctrl.pitch.ratelim); u___1_ = (min(max(((ILArray <double>)u___1_) + du * simParm.timeStep, Umin), Umax)).GetArrayForRead(); Mg___i_ = u___2_; // Torque Input beta[_(':'), _(i)] = u___1_; // Pitch Input // Turbine dynamics - can be simplified: if (enableTurbineDynamics) { for (j = 1; j <= parm.N; j++) { double x_j_1_; double parm_Ct_j_i_; double parm_Cp_j_i_; turbineDrivetrainModel(out x_j_1_, out parm_Ct_j_i_, out parm_Cp_j_i_, x___1_[j - 1], x___2_[j - 1], u___1_[j - 1], u___2_[j - 1], wt, env, simParm.timeStep); x___1_[j - 1] = x_j_1_; parm.Ct._(j, i, '=', parm_Ct_j_i_); parm.Cp._(j, i, '=', parm_Cp_j_i_); } } else { x___1_.Fill(parm.ratedSpeed); // Rotational speed } Omega[_(':'), _(i)] = x___1_; Power[_(':'), _(i)] = Omega[_(':'), _(i)] * Mg___i_; // Power Summations sumPower._(i, '=', sum_(Power[_(':'), _(i)]) * _p(10, -6)); sumRef._(i, '=', sum_(P_ref[_(':'), _(i)]) * _p(10, -6)); sumAvai._(i, '=', sum_(Pa[_(':'), _(i)]) * _p(10, -6)); // NOWCASTING FUNKTION HER // powerPrediction(i) = powerPrediction(i,sumPower(i:-1:i-10)) % or something // similar. } //% //time = (_c((decimal)simParm.tStart, (decimal)simParm.timeStep, (decimal)simParm.tEnd - (decimal)simParm.timeStep)).T; time = (_c(0.0, 1.0, _simParm_tEnd_simParm_tStart__simParm_timeStep - 1) * simParm.timeStep + simParm.tStart).T; if (saveData) { //throw new ApplicationException(string.Format("{0}\t{1}\t{2}\t{3}", time.Dimensions, sumPower.T.Dimensions, sumRef.T.Dimensions, sumAvai.T.Dimensions)); dataOut = (__[time, sumPower.T, sumRef.T, sumAvai.T]).ToDoubleArray(); //save dataOut; } else { dataOut = new double[][] { new double[] { 0, 0, 0, 0 } }; } //% Plotting //Below a number of different plots are made. Most of them for test purposes ILArray <double> plotsData; plotsData = time.C; plotsData = __[plotsData, P_ref.T *(_p(10, -6))]; // f1 = figure(1); xlabel('Time [s]'); ylabel('Power Reference [MW]'); title('Individual Power Reference'); plotsData = __[plotsData, v_nac.T]; // f2 = figure(2); xlabel('Time [s]'); ylabel('Wind Speed [m/s]'); title('Wind Speed @ individual turbine'); plotsData = __[plotsData, beta.T]; // f4 = figure(4); xlabel('Time [s]'); ylabel('Pitch Angle [deg]'); title('Evolution of Pitch angle over time'); plotsData = __[plotsData, Omega.T]; // f5 = figure(5); xlabel('Time [s]'); ylabel('Revolutional Velocity [rpm]'); title('Evolution of Revolutional Velocity over time'); plotsData = __[plotsData, sumRef.T, sumAvai.T, sumPower.T]; // f3 = figure(3); xlabel('Time [s]'); ylabel('Power [MW]'); title('Power Plot'); legend('Reference','Available','Produced'); return(plotsData.ToDoubleArray()); }