public ArraySimulationStepOutput()
 {
     Strings = new ArraySimStringOutput[0];
 }
        /// <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;
        }