namespace ResultsShared
{
using System.Globalization;
///
/// Useful utility classes relating to calculation results that have been saved to file
/// after a calculation has been run.
///
public static class Utils
{
public const string PerformanceStatisticsXmlFileName = "Performance Statistics.xml";
public const string LayoutStatisticsFileName = "Site Layout Statistics.xml";
public const string CalculationSettingsFileName = "Calculation Settings.xml";
public const string AlbedoFileName = "Albedo Settings.xml";
public const string SoilingFileName = "Soiling Settings.xml";
public const string YearlyResultsFileNameFormatString = "Annual Yield Results and Effects ({0}).xml";
public const string YearlyResultsFileSearchString = "Annual Yield Results and Effects (*).xml";
public const string LogFileSearchString = "SolarFarmer log *";
public const string MetaDataFileName = "Metadata.tsv";
private static readonly string RowPerResultsTsvFileName = "Results (row per result).tsv";
private static readonly string RowPerColumnTsvFileName = "Results (column per result).tsv";
///
/// Output the results list to TSV files. It goes through each sub-folder in the specified folder looking
/// for the necessary statistics files (e.g. layout statistics, yearly results, etc.) and if it finds them, creates
/// a object, then outputs two TSV files - listing the results so they are more
/// easily compared.
///
/// The full path of the folder containing all the folders containing results.
public static void SummariseResultsToTsv(string resultsFolderPath)
{
if (string.IsNullOrWhiteSpace(resultsFolderPath))
{
Toolbox.Log("The folder path is empty. Please supply a folder path", LogLevel.Error);
return;
}
if (!Directory.Exists(resultsFolderPath))
{
Toolbox.Log(string.Format("The folder path \"{0}\" does not exist. Please supply a folder path that exists.", resultsFolderPath), LogLevel.Error);
return;
}
// Go through each sub-folder, looking for the necessary files (layout statistics, performance statistics, yearly results)
// and if they exist, create a Results object - then push it through the same function as before.
List resultsList = new List();
foreach (string subFolderPath in Directory.EnumerateDirectories(resultsFolderPath))
{
Results results = ReadResultsFromFolder(subFolderPath);
if (results != null)
{
resultsList.Add(results);
}
}
OutputResultsListToTsv(resultsList, resultsFolderPath);
}
///
/// Creates a results folder (from the current time and date) in the base folder you provide.
///
/// The base folder.
/// The full folder path of the created results folder.
public static string CreateResultsFolderFromCurrentDateAndTime(string baseFolderPath)
{
string currentDateTimeFolderName = DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss");
string resultsFolderPath = Path.Combine(baseFolderPath, currentDateTimeFolderName);
Directory.CreateDirectory(resultsFolderPath);
return resultsFolderPath;
}
private static Results ReadResultsFromFolder(string folderPath)
{
if (string.IsNullOrWhiteSpace(folderPath))
{
Toolbox.Log("The folder path is empty. Please supply a folder path", LogLevel.Error);
return null;
}
if (!Directory.Exists(folderPath))
{
Toolbox.Log(string.Format("The folder path \"{0}\" does not exist. Please supply a folder path that exists.", folderPath), LogLevel.Error);
return null;
}
string folderName = Path.GetFileName(folderPath);
string layoutStatsFilePath = Path.Combine(folderPath, LayoutStatisticsFileName);
string perfStatsFilePath = Path.Combine(folderPath, PerformanceStatisticsXmlFileName);
string calcSettingsFilePath = Path.Combine(folderPath, CalculationSettingsFileName);
string albedoFilePath = Path.Combine(folderPath, AlbedoFileName);
if (File.Exists(layoutStatsFilePath))
{
// Now look for any yearly results files
string[] yearlyResultsFilePaths = Directory.GetFiles(folderPath, YearlyResultsFileSearchString);
if (yearlyResultsFilePaths.Any())
{
// There is at least one. So good to go
SiteLayoutStatistics siteLayoutStatistics = SiteLayoutStatistics.LoadFromFile(layoutStatsFilePath);
PerformanceStatistics performanceStatistics = new PerformanceStatistics();
if (File.Exists(perfStatsFilePath))
{
performanceStatistics = PerformanceStatistics.LoadFromFile(perfStatsFilePath);
}
CalculationSettings calculationSettings = CalculationSettings.LoadFromFile(calcSettingsFilePath);
ScenarioAlbedoData albedoData = ScenarioAlbedoData.LoadFromFile(albedoFilePath);
List metaData = LoadMetaData(Path.Combine(folderPath, MetaDataFileName));
List allAnnualResults = new List();
foreach (string yearlyResultsFilePath in yearlyResultsFilePaths)
{
AnnualEnergyYieldResults annualResults = AnnualEnergyYieldResults.LoadFromFile(yearlyResultsFilePath);
allAnnualResults.Add(annualResults);
}
return new Results(folderName, siteLayoutStatistics, performanceStatistics, calculationSettings, albedoData, allAnnualResults, metaData);
}
}
// Was not a valid folder - i.e. it didn't contain the appropriate files.
return null;
}
///
/// Outputs the specified results list to two TSV files in the specified folder path.
/// One TSV file has a result per row.
/// The other TSV file has a results per column.
///
/// The results list to use in the TSV files.
/// The full folder path to save the TSV files.
private static void OutputResultsListToTsv(List resultsList, string folderPathToSaveTsvFiles)
{
Results firstResults = resultsList.FirstOrDefault();
List columns = new List()
{
new HeaderAndFunc("Site Layout", r => r.SiteLayoutName),
};
// Add any meta data columns. This currently assumes that all results will have the same meta data
if (firstResults != null)
{
for (int i = 0; i < firstResults.MetaDataList.Count; i++)
{
int index = i;
columns.Add(new HeaderAndFunc(firstResults.MetaDataList[index].PropertyName, r => r.MetaDataList[index].PropertyValue.ToString()));
}
}
columns.Add(new HeaderAndFunc("Num inverters", r => r.SiteLayoutStatistics.ComponentStatistics.FirstOrDefault(c => c.ComponentType == "Inverter").Count.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Num racks", r => r.SiteLayoutStatistics.PvSystemStatistics.First().RackCount.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Num modules", r => r.SiteLayoutStatistics.PvSystemStatistics.First().ModuleCount.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Num strings", r => r.SiteLayoutStatistics.PvSystemStatistics.First().StringCount.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Calc type", r => r.CalculationSettings.EnergyYieldCalculationType.ToString()));
columns.Add(new HeaderAndFunc("Annual albedo", r => r.AlbedoData.OverallAlbedoValue.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Energy Yield (kWh/kWp)", r => r.YearlyResultsList.First().EnergyYield.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Net energy (MWh/year)", r => r.YearlyResultsList.First().NetEnergy.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Performance Ratio (%)", r => r.YearlyResultsList.First().PerformanceRatio.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Near shading effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.NearShadingIrradiance * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Production DC (kWh)", r => r.YearlyResultsList.First().PDC.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Production AC (kWh)", r => r.YearlyResultsList.First().PAC.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("GHI (kWh/m2)", r => r.YearlyResultsList.First().GHI.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("GI (kWh/m2)", r => r.YearlyResultsList.First().GI.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Average Temp (C)", r => r.YearlyResultsList.First().AverageTemperature.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Gain on tilted plane (%)", r => r.YearlyResultsList.First().GainOnTiltedPlane.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Far Shading/Horizon effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Horizon * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Angular effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Angular * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Soiling effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Soiling * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Modelling effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Modelling * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Modelling correction effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.ModellingCorrection * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Temperature effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Temperature * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Irradiance effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Irradiance * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Module mismatch effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.ModuleMismatch * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Electrical mismatch effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.ElectricalMismatch * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Module quality effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.ModuleQuality * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Spectral effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Spectral * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Back irradiance gain effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.BackIrradianceGain * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Power binning effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.PowerBinning * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("LID effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.LightInducedDegradation * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Ohmic DC effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.OhmicDc * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Ohmic AC effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.OhmicAc * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Transformer effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Transformer * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Availability effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.Availability * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Min DC effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterMinDcVoltage * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Max AC Power effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterMaxAcPower * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Max DC Current effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterMaxDcCurrent * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Max DC Voltage effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterMaxDcVoltage * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Min DC Power effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterMinDcPower * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Efficiency effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterEfficiency * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Overpower Shutdown effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterOverPowerShutdown * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("Inverter Tare effect (%)", r => (r.YearlyResultsList.First().AnnualEffects.InverterTare * 100.0).ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("AC output (kW)", r => r.SiteLayoutStatistics.PvSystemStatistics.First().AcOutput.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("DC Power (kW)", r => r.SiteLayoutStatistics.PvSystemStatistics.First().DcPower.ToString(CultureInfo.CurrentCulture)));
columns.Add(new HeaderAndFunc("DC/AC Ratio", r => r.SiteLayoutStatistics.PvSystemStatistics.First().DcAcRatio.ToString(CultureInfo.CurrentCulture)));
Toolbox.Log(string.Format("Added {0} columns", columns.Count));
OutputResultsToTsvRowForEachResult(resultsList, columns, Path.Combine(folderPathToSaveTsvFiles, RowPerResultsTsvFileName));
OutputResultsToTsvColumnForEachResult(resultsList, columns, Path.Combine(folderPathToSaveTsvFiles, RowPerColumnTsvFileName));
}
///
/// Loads metadata from the specified file.
///
/// The file path.
/// A list of meta data (or an empty list if there isn't a file).
private static List LoadMetaData(string filePath)
{
List metaDataList = new List();
if ((string.IsNullOrWhiteSpace(filePath)) || (!File.Exists(filePath)))
{
return metaDataList;
}
string fileContents = File.ReadAllText(filePath);
string[] lines = fileContents.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines.Where(l => !string.IsNullOrWhiteSpace(l)))
{
string[] content = line.Split('\t');
if (content.Length == 2)
{
string propertyName = content[0];
object propertyValue = content[1];
metaDataList.Add(new MetaData(propertyName, propertyValue));
}
}
return metaDataList;
}
private static void OutputResultsToTsvRowForEachResult(List resultsList, List columns, string outputFilePath)
{
StringBuilder sb = new StringBuilder();
// First, output the headers
sb.AppendLine(string.Join("\t", columns.Select(c => c.Header)));
foreach (Results results in resultsList)
{
if (!results.YearlyResultsList.Any())
{
// Hmmm, no yearly results
Toolbox.Log(string.Format("!!! There are no yearly results for site layout {0}", results.SiteLayoutName), LogLevel.Error);
}
else
{
List strings = new List();
foreach (HeaderAndFunc headerAndFunc in columns)
{
strings.Add(headerAndFunc.Func(results));
}
sb.AppendLine(string.Join("\t", strings));
}
}
SaveStringBuilderToFile(sb, outputFilePath);
}
private static void SaveStringBuilderToFile(StringBuilder sb, string filePath)
{
try
{
System.IO.File.WriteAllText(filePath, sb.ToString());
Toolbox.Log(string.Format("Written file to \"{0}\"", filePath));
}
catch (Exception ex)
{
Toolbox.Log(string.Format("Could not write file to \"{0}\". Error message: {1}", filePath, ex.Message), LogLevel.Error);
}
}
private static void OutputResultsToTsvColumnForEachResult(List resultsList, List columns, string outputFilePath)
{
StringBuilder sb = new StringBuilder();
// Do a row for each result
foreach (HeaderAndFunc headerAndFunc in columns)
{
List strings = new List();
strings.Add(headerAndFunc.Header);
foreach (Results results in resultsList)
{
strings.Add(headerAndFunc.Func(results));
}
sb.AppendLine(string.Join("\t", strings));
}
SaveStringBuilderToFile(sb, outputFilePath);
}
///
/// Represents a header and a function for how to get the value from a Results object.
///
private class HeaderAndFunc
{
///
/// Initializes a new instance of the class.
///
/// The header.
/// The function to get the value (string) from a Results object.
public HeaderAndFunc(string header, Func func)
{
this.Header = header;
this.Func = func;
}
///
/// Gets or sets the header.
///
public string Header { get; set; }
///
/// Gets or sets the function.
///
public Func Func { get; set; }
}
}
///
/// Class to hold any additional meta-data you would like output with the results.
///
public class MetaData
{
///
/// Initializes a new instance of the class.
///
/// The name of the property of the metadata.
/// The value of the metadata.
public MetaData(string propertyName, object propertyValue)
{
this.PropertyName = propertyName;
this.PropertyValue = propertyValue;
}
///
/// The name of the property of the metadata.
///
public string PropertyName { get; private set; }
///
/// The value fo the metadata.
///
public object PropertyValue { get; private set; }
}
///
/// Encapsulates the results from an energy yield calculation scenario
///
public class Results
{
///
/// Initializes a new instance of the class.
///
/// The site layout that the results were generated from.
/// The calculation scenario produced from running the energy calculation.
/// The performance statistics from running the energy calculation.
/// The calculation settings used when running the energy calculation.
/// Extra meta data - used to add extra columns to .tsv files.
public Results(
SiteLayout siteLayout,
EnergyYieldCalculationScenario scenario,
PerformanceStatistics performanceStatistics,
CalculationSettings calculationSettings,
IEnumerable extraMetaData)
: this(extraMetaData)
{
this.SiteLayoutName = siteLayout.Name;
this.SiteLayoutStatistics = siteLayout.Statistics;
this.YearlyResultsList = new List();
this.YearlyResultsList.AddRange(scenario.YearlyResultsList);
this.PerformanceStatistics = performanceStatistics;
this.CalculationSettings = calculationSettings;
}
///
/// Initializes a new instance of the class.
///
/// The name of the site layout.
/// The site layout statistics of the site.
/// The performance statistics from running the energy calculation.
/// The calculation settings used when running the energy calculation.
/// The albedo data.
/// The yearly results from running the energy calculation.
/// Extra meta data - used to add extra columns to .tsv files.
public Results(
string siteLayoutName,
SiteLayoutStatistics siteLayoutStatistics,
PerformanceStatistics performanceStatistics,
CalculationSettings calculationSettings,
ScenarioAlbedoData albedoData,
IEnumerable yearlyResults,
IEnumerable extraMetaData)
: this(extraMetaData)
{
this.SiteLayoutName = siteLayoutName;
this.SiteLayoutStatistics = siteLayoutStatistics;
this.PerformanceStatistics = performanceStatistics;
this.YearlyResultsList = new List();
this.YearlyResultsList.AddRange(yearlyResults);
this.CalculationSettings = calculationSettings;
this.AlbedoData = albedoData;
}
///
/// The name of the site layout.
///
public string SiteLayoutName { get; set; }
///
/// The site layout statistics.
///
public SiteLayoutStatistics SiteLayoutStatistics { get; set; }
///
/// The performance statistics.
///
public PerformanceStatistics PerformanceStatistics { get; set; }
///
/// The calculation statistics.
///
public CalculationSettings CalculationSettings { get; set; }
///
/// The list of yearly results.
///
public List YearlyResultsList { get; set; }
///
/// Gets or sets the albedo data.
///
public ScenarioAlbedoData AlbedoData { get; set; }
///
/// The list of optional metadata.
///
public List MetaDataList { get; private set; }
protected Results(IEnumerable extraMetaData)
{
this.MetaDataList = new List();
if (extraMetaData != null)
{
this.MetaDataList.AddRange(extraMetaData);
}
}
}
}