//% The main file for running the wind farm controll and wake simulation. // It is not completely done yet. Further updates will come // Currently there are only 4 turbines, for test purposes. But is should be // easily updated to a larger number of turbines. // Similarly there is a lot of room for speed optimizations, even though it // now runs slowly with only 4 turbines // 19/07-13 MS public static double[][] Simulation(WakeFarmControlConfig config) { var parm = new WindTurbineParameters(); ILMatFile env; ILMatFile wt; ILArray <int> idx; ILArray <double> ee; double Ki; double Kp; int PC_MaxPit; int PC_MinPit; double VS_CtInSp; double VS_RtGnSp; double VS_Rgn2K; double omega0; double beta0; double power0; ILArray <double> x; ILArray <double> u0; ILArray <double> u; ILArray <double> Mg_old; ILArray <double> P_ref; ILArray <double> Pa; ILArray <double> Power; ILArray <double> Ct; ILArray <double> P_ref_new; ILArray <double> v_nac; double alpha; double Mg_max_rate; ILArray <double> e; ILArray <double> Mg; ILArray <double> beta; ILArray <double> Cp; ILArray <double> Omega; ILArray <double> out_; if (config.NTurbines == 0) { return(null); } // Wind farm properties //turbine properties env = wt = new ILMatFile(config.NREL5MW_MatFile); //Load parameters from the NREL 5MW turbine parm.N = config.NTurbines; // number of turbines in farm parm.rho = (double)env.GetArray <double>("env_rho"); //air density parm.radius = ((double)(wt.GetArray <double>("wt_rotor_radius"))) * ILMath.ones(1, config.NTurbines); // rotor radius (NREL5MW) parm.rated = 5e6 * ILMath.ones(1, config.NTurbines); //rated power (NREL5MW) parm.ratedSpeed = (double)wt.GetArray <double>("wt_rotor_ratedspeed"); //rated rotor speed idx = ILMath.empty <int>(); ILMath.max(wt.GetArray <double>("wt_cp_table")[ILMath.full], idx); //Find index for max Cp; parm.Cp = ILMath.ones(1, config.NTurbines) * wt.GetArray <double>("wt_cp_table").GetValue(idx.ToArray()); //Set power coefficent to maximum value in the cp table parm.Ct = ILMath.ones(1, config.NTurbines) * wt.GetArray <double>("wt_ct_table").GetValue(idx.ToArray()); //Set power coefficent to maximum value in the ct table // NOTE: controller parameters should be imported from the wt....struct in //Pitch control ee = 0; //blade pitch integrator Ki = 0.008068634 * 360 / 2 / ILMath.pi; // integral gain (NREL5MW) Kp = 0.01882681 * 360 / 2 / ILMath.pi; // proportional gain (NREL5MW) PC_MaxPit = 90; PC_MinPit = 0; //region control NREL VS_CtInSp = 70.16224; VS_RtGnSp = 121.6805; VS_Rgn2K = 2.332287; // load initial wind data var wind = new ILMatFile(config.Wind_MatFile); //% Set initial conditions omega0 = 1.267; //Rotation speed beta0 = 0; //Pitch var timeLine = (int)config.TimeLine(); power0 = parm.rated.GetValue(0); //Power production x = (omega0 * ILMath.ones(parm.N, 1)).Concat((wind.GetArray <double>("wind").GetValue(0, 1) * ILMath.ones(parm.N, 1)), 1); u0 = (beta0 * ILMath.ones(parm.N, 1)).Concat((power0 * ILMath.ones(parm.N, 1)), 1); u = u0.C; Mg_old = u[ILMath.full, 1]; P_ref = ILMath.zeros(parm.N, (int)config.TimeLine()); //Initialize matrix to save the power production history for each turbine Pa = P_ref.C; //Initialize available power matrix Power = P_ref.C; Ct = parm.Ct.C; //Initialize Ct - is this correct? Ct[timeLine - 1, ILMath.full] = Ct[0, ILMath.full]; P_ref_new = power0 * ILMath.ones(config.NTurbines, 1); v_nac = ILMath.zeros(Ct.Size[1], timeLine); Mg = ILMath.zeros(u.Size[0], timeLine); beta = ILMath.zeros(u.Size[0], timeLine); Omega = ILMath.zeros(Ct.Size[1], timeLine); Cp = ILMath.zeros(timeLine, parm.Cp.Size[1]); var turbineModel = new TurbineDrivetrainModel(); //% Simulate wind farm operation //var timeLine = (int) config.TimeLine(); for (var i = 2; i <= timeLine; i++) //At each sample time(DT) from Tstart to Tend { //Calculate the wake using the current Ct values { ILArray <double> out_v_nac; WakeCalculation.Calculate((Ct[i - 1 - 1, ILMath.full]), i, wind, out out_v_nac); v_nac[ILMath.full, i - 1] = out_v_nac; } x[ILMath.full, 1] = v_nac[ILMath.full, i - 1]; //Farm control //Calculate the power distribution references for each turbine if (config.EnablePowerDistribution) { ILArray <double> out_Pa; PowerDistributionControl.DistributePower(v_nac[ILMath.full, i - 1], config.Pdemand, Power[ILMath.full, i - 1 - 1], parm, out P_ref_new, out out_Pa); Pa[ILMath.full, i - 1] = out_Pa; } //Hold the demand for some seconds if (ILMath.mod(i, ILMath.round(config.PRefSampleTime / config.DT)) == 2) //??? { P_ref[ILMath.full, i - 1] = P_ref_new; } else { if (config.PowerRefInterpolation) { alpha = 0.01; P_ref[ILMath.full, i - 1] = (1 - alpha) * P_ref[ILMath.full, i - 1 - 1] + (alpha) * P_ref_new; } else { P_ref[ILMath.full, i - 1] = P_ref_new; } } //Calculate control for each individual turbine - should be moved to the //turbine (drivetrain) model. //Torque controller for (var j = 1; j <= parm.N; j++) { if ((x.GetValue(j - 1, 0) * 97 >= VS_RtGnSp) || (u.GetValue(j - 1, 0) >= 1)) // We are in region 3 - power is constant { u.SetValue(P_ref.GetValue(j - 1, i - 1) / x.GetValue(j - 1, 0), j - 1, 1); } else if (x.GetValue(j - 1, 0) * 97 <= VS_CtInSp) //! We are in region 1 - torque is zero { u.SetValue(0.0, j - 1, 1); } else //! We are in region 2 - optimal torque is proportional to the square of the generator speed { u.SetValue(97 * VS_Rgn2K * x.GetValue(j - 1, 0) * x.GetValue(j - 1, 0) * Math.Pow(97, 2), j - 1, 1); } } //Rate limit torque change // u(:,2) - Mg_old; Mg_max_rate = 1e6 * config.DT; u[ILMath.full, 1] = ILMath.sign(u[ILMath.full, 1] - Mg_old) * ILMath.min(ILMath.abs(u[ILMath.full, 1] - Mg_old), Mg_max_rate) + Mg_old; //Pitch controller e = 97 * (omega0 * ILMath.ones(parm.N, 1) - x[ILMath.full, 0]); ee = ee - config.DT * e; ee = ILMath.min(ILMath.max(ee, PC_MinPit / Ki), PC_MaxPit / Ki); u[ILMath.full, 0] = -Kp * config.DT * e + Ki * ee; for (var j = 1; j <= parm.N; j++) { u.SetValue(Math.Min(Math.Max(u.GetValue(j - 1, 0), PC_MinPit), PC_MaxPit), j - 1, 0); } if (!config.EnableTurbineDynamics) { u = u0; } Mg[ILMath.full, i - 1] = u[ILMath.full, 1]; Mg_old = Mg[ILMath.full, i - 1]; beta[ILMath.full, i - 1] = u[ILMath.full, 0]; //Set pitch //Turbine dynamics - can be simplified if (config.EnableTurbineDynamics) { for (var j = 1; j <= parm.N; j++) { double out_x; double out_Ct; double out_Cp; turbineModel.Model(x[j - 1, ILMath.full], u[j - 1, ILMath.full], wt, env, config.DT, out out_x, out out_Ct, out out_Cp); x.SetValue(out_x, j - 1, 0); Ct.SetValue(out_Ct, i - 1, j - 1); Cp.SetValue(out_Cp, i - 1, j - 1); } } else { Ct[i - 1, ILMath.full] = parm.Ct; Cp[i - 1, ILMath.full] = parm.Cp; x[ILMath.full, 0] = parm.ratedSpeed;//Rotational speed } Omega[ILMath.full, i - 1] = x[ILMath.full, 0]; Power[ILMath.full, i - 1] = Omega[ILMath.full, i - 1] * Mg[ILMath.full, i - 1]; } //% Save output data out_ = (config.DT * (ILMath.counter(0, 1, config.TimeLine()))); out_ = out_.Concat(v_nac.T, 1); out_ = out_.Concat(Omega.T, 1); out_ = out_.Concat(beta.T, 1); out_ = out_.Concat(P_ref.T, 1); out_ = out_.Concat(Ct, 1); out_ = out_.Concat(Cp, 1); out_ = out_.Concat(Pa.T, 1); out_ = out_.Concat(Mg.T, 1); out_ = out_.Concat(Power.T, 1); //Ttotal power demand var l = config.NTurbines * 3 + 1; var r = l + config.NTurbines - 1; out_ = out_.Concat(ILMath.sum(out_[ILMath.full, ILMath.r(l, r)], 1) / 1e6, 1); // P_ref sum l = config.NTurbines * 6 + 1; r = l + config.NTurbines - 1; out_ = out_.Concat(ILMath.sum(out_[ILMath.full, ILMath.r(l, r)], 1) / 1e6, 1); // Pa sum. 'Power Demand' out_ = out_.Concat(ILMath.sum(Power).T / 1e6, 1); // 'Actual Production' //Ttotal power demand out_ = out_.Concat(ILMath.sum(P_ref.T, 1), 1); // 'Demand' out_ = out_.Concat(ILMath.sum(Pa.T, 1), 1); // 'Available' out_ = out_.Concat(ILMath.sum(Mg * Omega).T, 1); // 'Actual' //Total power produced out_ = out_.Concat((Mg * Omega).T, 1); var out_doubleArray = new double[out_.Size[0]][]; for (int i = 0; i <= out_doubleArray.GetLength(0) - 1; i++) { out_doubleArray[i] = new double[out_.Size[1]]; for (int j = 0; j <= out_doubleArray[i].GetLength(0) - 1; j++) { out_doubleArray[i][j] = out_.GetValue(i, j); } } return(out_doubleArray); }
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()); }