/// <summary> /// Digraph of bypass diodes for calculation. /// </summary> /*private class BypassNode { public int minCell, maxCell; public List<BypassNode> childs = new List<BypassNode>(); } private static BypassNode GetRoot(ArraySpec.CellString cellStr) { BypassNode root = new BypassNode(); root.minCell = 0; root.maxCell = cellStr.Cells.Count; foreach (ArraySpec.BypassDiode diode in cellStr.BypassDiodes) { BypassNode node = root; while(true){ bool found = false; foreach (BypassNode child in node.childs) { if (diode.CellIxs.First >= node.minCell && diode.CellIxs.Second <= node.maxCell) { node = child; found = true; break; } } if (!found) break; } BypassNode newNode = new BypassNode(); newNode.minCell = diode.CellIxs.First; newNode.maxCell = diode.CellIxs.Second; node.childs.Add(newNode); } return root; }*/ public static IVTrace CalcStringIV(ArraySpec.CellString cellStr, IVTrace[] cellTraces, DiodeSpec bypassSpec) { Debug.Assert(cellTraces.Length != 0); Debug.Assert(cellTraces.Length == cellStr.Cells.Count); int ncells = cellTraces.Length; double strIsc = cellTraces[0].Isc; for (int i = 0; i < cellTraces.Length; i++) { strIsc = Math.Max(strIsc, cellTraces[i].Isc); } // which nodes connect to which others via bypass diode? // there's one node between each cell (subtotal ncells-1), // plus one at each end of the string, total ncells+1 List<int>[] links = new List<int>[ncells+1]; for(int i = 0; i <= ncells; i++){ links[i] = new List<int>(); } foreach (ArraySpec.BypassDiode diode in cellStr.BypassDiodes) { links[diode.CellIxs.Second + 1].Add(diode.CellIxs.First); } // sweep current this time, compute voltage int nsamples = 200; int ngoodsamples = nsamples; double[] veci = new double[nsamples]; double[] vecv = new double[nsamples]; for (int i = 0; i < nsamples; i++) { double current = i * strIsc / (nsamples-1); // what cells are in bypass? double[] nodevs = new double[ncells+1]; nodevs[0] = 0; // string starts at ground, zero volts for (int j = 1; j <= ncells; j++) { // first, voltage assuming no bypass double nodev = nodevs[j - 1]; if (current < cellTraces[j - 1].Isc) { nodev += cellTraces[j - 1].InterpV(current); } else { nodev = Double.NegativeInfinity; } // then, can we do better with bypass? foreach (int linkIx in links[j]) { nodev = Math.Max(nodev, nodevs[linkIx] - bypassSpec.VoltageDrop); } nodevs[j] = nodev; } veci[i] = current; vecv[i] = Math.Max(nodevs[ncells], 0); // cut off the part of the trace that's invalid (unachievable current) if (nodevs[ncells] >= 0) { Debug.Assert(ngoodsamples == nsamples); // should not "bounce" } else if(ngoodsamples == nsamples){ ngoodsamples = i; } } // calculate summary info such as mppt power double vmp = 0, imp = 0; for (int i = 0; i < nsamples; i++) { if (veci[i] * vecv[i] > vmp * imp) { vmp = vecv[i]; imp = veci[i]; } } // return a trace. supports lin interp, etc IVTrace trace = new IVTrace(); ngoodsamples = Math.Min(ngoodsamples + 1, nsamples); Debug.Assert(ngoodsamples > 0); trace.I = new double[ngoodsamples]; trace.V = new double[ngoodsamples]; Array.Copy(veci, trace.I, ngoodsamples); Array.Copy(vecv, trace.V, ngoodsamples); trace.Isc = veci[ngoodsamples - 1]; trace.Voc = vecv[0]; trace.Imp = imp; trace.Vmp = vmp; return trace; }
public static IVTrace CalcSweep(CellSpec cell, double insolationW, double tempC) { double voc = cell.CalcVoc(insolationW, tempC); double isc = cell.CalcIsc(insolationW, tempC); int n = 100; double[] vecv = new double[n + 1]; for (int i = 0; i <= n; i++) { vecv[i] = voc * (double)i / n; } double[] veci = CalcIV(cell, vecv, insolationW, tempC); //Debug.Assert(Math.Abs(veci[0] - isc) < 0.001); //Debug.Assert(Math.Abs(veci[n]) < 0.001); double pmp = 0.0; double imp = 0.0, vmp = 0.0; for (int i = 0; i < n; i++) { double p = veci[i] * vecv[i]; if (p > pmp) { pmp = p; vmp = vecv[i]; imp = veci[i]; } } IVTrace trace = new IVTrace(); trace.I = veci; trace.V = vecv; trace.Isc = isc; trace.Voc = voc; trace.Imp = imp; trace.Vmp = vmp; return trace; }
/// <summary> /// Reads the compute textures from OpenGL. /// This gives insolation for each cell. /// /// Uses this to calculate IV curves, etc, and ultimately array power. /// </summary> private ArraySimulationStepOutput AnalyzeComputeTex(ArraySpec array, double wPerM2Insolation, double wPerM2Iindirect, double encapLoss, double cTemp) { Color[] texColors = ReadColorTexture(FramebufferAttachment.ColorAttachment0); float[] texWattsIn = ReadFloatTexture(FramebufferAttachment.ColorAttachment1, 0.0001); double arrayDimM = ComputeArrayMaxDimension(array); double m2PerPixel = arrayDimM * arrayDimM / COMPUTE_TEX_SIZE / COMPUTE_TEX_SIZE; float[] texArea = ReadFloatTexture(FramebufferAttachment.ColorAttachment2, m2PerPixel / 4); double dbgmin = texArea[0], dbgmax = texArea[0], dbgavg = 0; for (int i = 0; i < texArea.Length; i++) { dbgmin = Math.Min(dbgmin, texArea[i]); dbgmax = Math.Max(dbgmax, texArea[i]); dbgavg += texArea[i]; } dbgavg /= texArea.Length; // find the cell at each fragment... int ncells = 0; var cells = new List<ArraySpec.Cell>(); var colorToId = new Dictionary<Color, int>(); foreach (ArraySpec.CellString cellStr in array.Strings) { foreach (ArraySpec.Cell cell in cellStr.Cells) { cells.Add(cell); colorToId.Add(cell.Color, ncells); ncells++; } } // finally, find the area and insolation for each cell double[] wattsIn = new double[ncells]; double[] areas = new double[ncells]; double wattsInUnlinked = 0, areaUnlinked = 0; for (int i = 0; i < computeWidth * computeHeight; i++) { Color color = texColors[i]; if (ColorUtils.IsGrayscale(color)) continue; if (colorToId.ContainsKey(color)) { int id = colorToId[color]; wattsIn[id] += texWattsIn[i]; areas[id] += texArea[i]; } else { wattsInUnlinked += texWattsIn[i]; areaUnlinked += texArea[i]; } } if (areaUnlinked > 0 || wattsInUnlinked > 0) { Logger.warn("Found texels that are not grayscale, " + "but also doesn't correspond to any cell. Have you finished your layout?" + "\n\tTotal of {0}m^2 cell area not in any string, with {1}W insolation.", areaUnlinked,wattsInUnlinked); } // add indirect insolation, encapsulation loss for (int i = 0; i < ncells; i++) { wattsIn[i] += array.CellSpec.Area * wPerM2Iindirect; wattsIn[i] *= (1.0 - encapLoss); } // find totals double totalArea = 0, totalWattsIn = 0; for (int i = 0; i < ncells; i++) { totalWattsIn += wattsIn[i]; totalArea += areas[i]; Debug.WriteLine("cell {0}: {1}W, {2}m^2", i, wattsIn[i], areas[i]); } Debug.WriteLine("total: {0}W, {1}m^2", totalWattsIn, totalArea); // MPPT sweeps, for each cell and each string. // Inputs: CellSpec cellSpec = array.CellSpec; int nstrings = array.Strings.Count; // Outputs: double totalWattsOutByCell = 0; double totalWattsOutByString = 0; var strings = new ArraySimStringOutput[nstrings]; int cellIx = 0; for(int i = 0; i < nstrings; i++){ var cellStr = array.Strings[i]; double stringWattsIn = 0, stringWattsOutByCell = 0, stringLitArea = 0; // per-cell sweeps var cellSweeps = new IVTrace[cellStr.Cells.Count]; for(int j = 0; j < cellStr.Cells.Count; j++){ double cellWattsIn = wattsIn[cellIx++]; double cellInsolation = cellWattsIn / cellSpec.Area; IVTrace cellSweep = CellSimulator.CalcSweep(cellSpec, cellInsolation, cTemp); cellSweeps[j] = cellSweep; stringWattsIn += cellWattsIn; stringWattsOutByCell += cellSweep.Pmp; totalWattsOutByCell += cellSweep.Pmp; // shading stats stringLitArea += areas[i]; } // string sweep strings[i] = new ArraySimStringOutput(); strings[i].WattsIn = stringWattsIn; strings[i].WattsOutputByCell = stringWattsOutByCell; IVTrace stringSweep = StringSimulator.CalcStringIV(cellStr, cellSweeps, array.BypassDiodeSpec); strings[i].WattsOutput = stringSweep.Pmp; strings[i].IVTrace = stringSweep; // higher-level string info strings[i].String = cellStr; strings[i].Area = cellStr.Cells.Count*cellSpec.Area; strings[i].AreaShaded = strings[i].Area - stringLitArea; IVTrace cellSweepIdeal = CellSimulator.CalcSweep(cellSpec,wPerM2Insolation,cTemp); strings[i].WattsOutputIdeal = cellSweepIdeal.Pmp * cellStr.Cells.Count; // total array power totalWattsOutByString += stringSweep.Pmp; } ArraySimulationStepOutput output = new ArraySimulationStepOutput(); output.ArrayArea = ncells * cellSpec.Area; output.ArrayLitArea = totalArea; output.WattsInsolation = totalWattsIn; output.WattsOutputByCell = totalWattsOutByCell; output.WattsOutput = totalWattsOutByString; output.Strings = strings; return output; }