public void Add(UtcDateTimeEnumerator enumer, SortedDictionary<UtcDateTime, double?> sumPowerInParkAtTimeTAtTimeT, SortedDictionary<UtcDateTime, double?> sumObsPowerInParkAtTimeT, int hoursAheadOffset) { nrOfRegisteredForecastTimePoints++; int index = hoursAheadOffset; foreach (UtcDateTime time in enumer) { if (sumPowerInParkAtTimeTAtTimeT.ContainsKey(time) && sumObsPowerInParkAtTimeT.ContainsKey(time)) { if (sumPowerInParkAtTimeTAtTimeT[time].HasValue && sumObsPowerInParkAtTimeT[time].HasValue) { if (skillScoreHourBins.ContainsKey(Any)) skillScoreHourBins[Any].Register(sumObsPowerInParkAtTimeT[time].Value, sumPowerInParkAtTimeTAtTimeT[time].Value); int hourAhead = index; if (skillScoreHourBins.ContainsKey(hourAhead)) { skillScoreHourBins[hourAhead].Register(sumObsPowerInParkAtTimeT[time].Value, sumPowerInParkAtTimeTAtTimeT[time].Value); } } } index++; } }
internal static SortedDictionary<UtcDateTime, double?> CalculateMarketResolutionAverage(UtcDateTime firstTimePoint, UtcDateTime first, UtcDateTime last, TimeSpan marketResolution, TimeSpan dataResolution, SortedDictionary<UtcDateTime, double?> prod, bool isForecast=false) { int steps; TimeResolutionType timeResolutionType = dataResolution.ToTimeResolution(out steps); // Create TimePoints that we are interested to aggregate data on: var time = last;//.Subtract(marketResolution); var timePoints = new List<UtcDateTime> { last }; do { time = time.Subtract(marketResolution); if (time >= first) timePoints.Add(time); } while (time >= first); var resolutionSamples = new SortedDictionary<UtcDateTime, List<double?>>(); for (int index = 0; index < timePoints.Count; index++) { if (index + 1 < timePoints.Count) { UtcDateTime point = timePoints[index]; // this should be end time e.g 12:00 UtcDateTime prevPoint = timePoints[index + 1].Add(dataResolution); // this should be time resolution before, e.g. an hour, thus 11:00, but maybe adding data resolution e.g. 10. var enumer = new UtcDateTimeEnumerator(prevPoint, point, timeResolutionType, steps); foreach (UtcDateTime utcDateTime in enumer) { if (prod.ContainsKey(utcDateTime)) { double? production = prod[utcDateTime]; if (!resolutionSamples.ContainsKey(point)) resolutionSamples.Add(point, new List<double?>()); resolutionSamples[point].Add(production); } } } else // This is for the last point... { UtcDateTime point = timePoints[index]; // this should be end time e.g 12:00 UtcDateTime prevPoint = timePoints[index].Subtract(marketResolution).Add(dataResolution); // this should be time resolution before, e.g. an hour, thus 11:00, but maybe adding data resolution e.g. 10. var enumer = new UtcDateTimeEnumerator(prevPoint, point, timeResolutionType, steps); foreach (UtcDateTime utcDateTime in enumer) { if (prod.ContainsKey(utcDateTime)) { double? production = prod[utcDateTime]; if (!resolutionSamples.ContainsKey(point)) resolutionSamples.Add(point, new List<double?>()); resolutionSamples[point].Add(production); } } } } int productionStepsInResolution = CalculateProductionStepsInResolution(marketResolution, dataResolution); var avgProds = new SortedDictionary<UtcDateTime, double?>(); foreach (UtcDateTime dateTime in resolutionSamples.Keys) { var prodInResolution = resolutionSamples[dateTime]; // When calculating observed production, we always calculate the average as sum devided by time steps in period (resolution) double? avgPower; if (!isForecast) avgPower = prodInResolution.Sum()/productionStepsInResolution; else { // When calculating predicted production, we always calculate the pure average double sum = 0; int nrOfObsInPeriod = 0; foreach (double? p in prodInResolution) { if (p.HasValue) { nrOfObsInPeriod++; sum += p.Value; } } if (nrOfObsInPeriod > 0) avgPower = sum/nrOfObsInPeriod; else avgPower = null; } avgProds.Add(dateTime, avgPower); } return avgProds; }
public static ConcurrentDictionary<UtcDateTime, HourlySkillScoreCalculator> Calculate(ForecastMetaData fmd, string path, ObservationMetaData omd, string file, int[] scope, bool includeNegObs = false, bool useFixedHours = false, string siteId=null) { var startTimesInMinutes = FindUsualForecastStartTimePoints(path, fmd); var results = new ConcurrentDictionary<UtcDateTime, HourlySkillScoreCalculator>(); bool isNotPointingToANumber = false; // 1. Load observations string[] obsLines = File.ReadAllLines(file); var obsPowerInFarm = new SortedDictionary<UtcDateTime, double?>(); string dateTimePattern = string.Empty; string prevDateTimePattern = string.Empty; if (!obsLines[0].Contains(omd.ObservationSep)) throw new Exception("Observations series does not contain the char: '" + omd.ObservationSep + "' \ras column seperator. Open the document and check what \rdelimiter char is used to seperate the columns \rand specify this char in the Observations Column Seperator field."); for (int i=1; i<obsLines.Length; i++) // Skip the heading... { var line = obsLines[i]; var cols = line.Split(omd.ObservationSep); if (cols.Length > 1) { string timeStampStr = cols[omd.ObservationTimeIndex]; string valueStr = cols[omd.ObservationValueIndex]; UtcDateTime? dateTime = DateTimeUtil.ParseTimeStamp(timeStampStr, prevDateTimePattern, out dateTimePattern); if (dateTime.HasValue) { if (string.IsNullOrWhiteSpace(prevDateTimePattern) && !string.IsNullOrWhiteSpace(dateTimePattern)) prevDateTimePattern = dateTimePattern; double? value = DoubleUtil.ParseDoubleValue(valueStr); if (value.HasValue) { double val = value.Value; if (omd.ObservationUnitType == "MW") val *= 1000; if (val < 0 && !includeNegObs) obsPowerInFarm.Add(dateTime.Value, null); else obsPowerInFarm.Add(dateTime.Value, val); } else { if (i <= 3) isNotPointingToANumber = true; obsPowerInFarm.Add(dateTime.Value, null); } } } } if (isNotPointingToANumber && obsPowerInFarm.Count == 0) throw new Exception("The Observation Column Index Value is not pointing to a column which contains a double value. Change index value!"); int obsSteps; TimeResolutionType? obsResolution = IdentifyResolution(obsPowerInFarm, out obsSteps); // 2. Identify Time Resolution of obs series, if less than an hour, make it hourly avg. var firstObs = obsPowerInFarm.First().Key; var minutes = startTimesInMinutes.Keys.ToArray(); foreach (var minute in minutes) { if (firstObs.Minute == minute) { SortedDictionary<UtcDateTime, double?> convertedObsPowerInFarm = ConvertTimeSeriesToHourlyResolution(firstObs, obsPowerInFarm, useFixedHours); startTimesInMinutes[minute] = convertedObsPowerInFarm; } else if (minute > 0) { var first = new UtcDateTime(firstObs.Year, firstObs.Month, firstObs.Day, firstObs.Hour, minute); SortedDictionary<UtcDateTime, double?> convertedObsPowerInFarm = ConvertTimeSeriesToHourlyResolution(first, obsPowerInFarm, useFixedHours, minute); startTimesInMinutes[minute] = convertedObsPowerInFarm; } else { var tmp = firstObs.AddHours(1); firstObs = new UtcDateTime(tmp.Year, tmp.Month, tmp.Day, tmp.Hour); SortedDictionary<UtcDateTime, double?> convertedObsPowerInFarm = ConvertTimeSeriesToHourlyResolution(firstObs, obsPowerInFarm, useFixedHours); startTimesInMinutes[minute] = convertedObsPowerInFarm; } } // 3. Search forecastPath for forecast documents... dateTimePattern = string.Empty; prevDateTimePattern = string.Empty; FileInfo[] files; string filter = "*.csv"; if (Directory.Exists(path)) { if (!string.IsNullOrEmpty(fmd.ForecastFileFilter)) filter = fmd.ForecastFileFilter; var dir = new DirectoryInfo(path); files = dir.GetFiles(filter, SearchOption.AllDirectories); } else files = new FileInfo[]{new FileInfo(path) }; // If we only have one single forecast file, then this forecast file is usually 99% of the time a long // time series with continous time steps, meaning there are no overlaps. Usually, only the unique hours // from the original forecasts have been extracted. In these situations, we are interested in comparing // the skill of all hours and only use a single skill-bucket. Instead of making this an // explicit setting in the view (which it should be ultimately), it is no supporting this case by // convention. This should be changed later for flexibility... bool continousSerie = files.Length == 1; prevDateTimePattern = string.Empty; foreach (var fileInfo in files) { isNotPointingToANumber = false; UtcDateTime? firstTimePoint = null; UtcDateTime? firstPossibleHour = null; SortedDictionary<UtcDateTime, double?> predPowerInFarm; var stream = fileInfo.OpenRead(); using (var reader = new StreamReader(stream)) { predPowerInFarm = ReadPredictionFile(reader, fmd, includeNegObs, useFixedHours, siteId, prevDateTimePattern, out firstTimePoint, out firstPossibleHour, out isNotPointingToANumber); } if (isNotPointingToANumber) throw new Exception("The Forecast Column Index Value is not pointing to a column which contains a double value. Change index value!"); if (predPowerInFarm.Count == 0) continue; if (!firstPossibleHour.HasValue) throw new Exception("The first possible forecast hour could not be determined. Please check forecast file: " + fileInfo.FullName); if (!firstTimePoint.HasValue) throw new Exception("First time point in forecast file is not specified. Please check forecast file: " + fileInfo.FullName); if (obsResolution.HasValue) { var firstForecastHour = firstTimePoint.Value; if (useFixedHours) firstForecastHour = firstPossibleHour.Value; var convertedObsPowerInFarm = startTimesInMinutes[firstForecastHour.Minute]; var convertedPredPowerInFarm = ConvertTimeSeriesToHourlyResolution(firstForecastHour, predPowerInFarm, useFixedHours, firstForecastHour.Minute); if (convertedPredPowerInFarm.Count > 0) { UtcDateTime first = convertedPredPowerInFarm.Keys.First(); var firstTimePointInMonth = new UtcDateTime(first.Year, first.Month, 1); if (continousSerie) { // Split up into months UtcDateTime last = convertedPredPowerInFarm.Keys.Last(); var firstTimePointInLastMonth = new UtcDateTime(last.Year, last.Month, 1); for (var time = firstTimePointInMonth; time.UtcTime <= firstTimePointInLastMonth.UtcTime; time = new UtcDateTime(time.UtcTime.AddMonths(1))) { if (!results.ContainsKey(time)) results.TryAdd(time, new HourlySkillScoreCalculator(new List<Turbine>(), scope, (int) omd.NormalizationValue)); HourlySkillScoreCalculator skillCalculator = results[time]; var enumer = new UtcDateTimeEnumerator(time, new UtcDateTime(time.UtcTime.AddMonths(1)), TimeResolution.Hour); skillCalculator.AddContinousSerie(enumer, convertedPredPowerInFarm, convertedObsPowerInFarm); } } else { if (!results.ContainsKey(firstTimePointInMonth)) results.TryAdd(firstTimePointInMonth, new HourlySkillScoreCalculator(new List<Turbine>(), scope, (int) omd.NormalizationValue)); HourlySkillScoreCalculator skillCalculator = results[firstTimePointInMonth]; var enumer = new UtcDateTimeEnumerator(first, convertedPredPowerInFarm.Keys.Last(), TimeResolution.Hour); skillCalculator.Add(enumer, convertedPredPowerInFarm, convertedObsPowerInFarm, fmd.OffsetHoursAhead); } } } } // 2.1 Print hourly obs series to file for end-user debugging and verification: if(useFixedHours) PrintObsPowerInFarmToDebugFile(obsPowerInFarm); return results; }
public void Add(UtcDateTimeEnumerator enumer, SortedDictionary<UtcDateTime, SortedDictionary<int, double>> powerInParkAtTimeT, SortedDictionary<UtcDateTime, SortedDictionary<int, double>> obsPowerInParkAtTimeT, SortedDictionary<UtcDateTime, double?> obsMastWeather) { nrOfRegisteredForecastTimePoints++; var sumPowerInParkAtTimeTAtTimeT = new SortedDictionary<UtcDateTime, double>(); var sumObsPowerInParkAtTimeT = new SortedDictionary<UtcDateTime, double>(); UtcDateTime forecastStart = enumer.First(); var forecastSuccess = new SortedDictionary<UtcDateTime, RestrictedRangeInt>(); if (!Success.ContainsKey(forecastStart)) Success.Add(forecastStart, forecastSuccess); double prevRotSpeed = -1; int timeIndex = 0; foreach (UtcDateTime currentTime in enumer) { forecastSuccess.Add(currentTime, new RestrictedRangeInt(0, turbines.Count(), 0)); if (timeIndex > 10 && sumObsPowerInParkAtTimeT.Count == 0) { break; // If we have nothing for the ten first hours then this forecast point is broken and should not be taken into account... } if (useObservedWeatherData) { if (obsMastWeather.Count == 0) throw new Exception("No MetMast Data provided, calculating skill score without met mast data must be implemented."); } bool isOkData = true; int nrOfTubrines = 0; if (useObservedWeatherData && obsMastWeather.ContainsKey(currentTime) && obsMastWeather[currentTime].HasValue && (obsMastWeather[currentTime].Value - prevRotSpeed).AboutEqual(0.0)) isOkData = false; if (isOkData) { if (useObservedWeatherData && obsMastWeather.ContainsKey(currentTime) && obsMastWeather[currentTime].HasValue) prevRotSpeed = obsMastWeather[currentTime].Value; if (powerInParkAtTimeT.ContainsKey(currentTime)) { foreach (Turbine turbine in turbines) { if (obsPowerInParkAtTimeT.ContainsKey(currentTime) && obsPowerInParkAtTimeT[currentTime].ContainsKey(turbine.ScadaId)) { double power = 0; if (powerInParkAtTimeT.ContainsKey(currentTime) && powerInParkAtTimeT[currentTime].ContainsKey(turbine.ReferenceId)) power = powerInParkAtTimeT[currentTime][turbine.ReferenceId]; double obsPower = obsPowerInParkAtTimeT[currentTime][turbine.ScadaId]; if (!sumPowerInParkAtTimeTAtTimeT.ContainsKey(currentTime)) sumPowerInParkAtTimeTAtTimeT.Add(currentTime, 0); sumPowerInParkAtTimeTAtTimeT[currentTime] += power; if (!sumObsPowerInParkAtTimeT.ContainsKey(currentTime)) sumObsPowerInParkAtTimeT.Add(currentTime, 0); sumObsPowerInParkAtTimeT[currentTime] += obsPower; nrOfTubrines++; } } } } forecastSuccess[currentTime].Value = nrOfTubrines; timeIndex++; } int index = 0; foreach (UtcDateTime time in enumer) { if (sumPowerInParkAtTimeTAtTimeT.ContainsKey(time) && sumObsPowerInParkAtTimeT.ContainsKey(time)) { if (skillScoreHourBins.ContainsKey(Any)) skillScoreHourBins[Any].Register(sumObsPowerInParkAtTimeT[time], sumPowerInParkAtTimeTAtTimeT[time]); int hourAhead = index; if (skillScoreHourBins.ContainsKey(hourAhead)) { skillScoreHourBins[hourAhead].Register(sumObsPowerInParkAtTimeT[time], sumPowerInParkAtTimeTAtTimeT[time]); } } index++; } }
public void AddContinousSerie(UtcDateTimeEnumerator enumer, SortedDictionary<UtcDateTime, double?> predictionFrames, SortedDictionary<UtcDateTime, double?> productionFrames) { Dictionary<int, int> hourScope = Scope.ToDictionary(e => e, e => e); nrOfRegisteredForecastTimePoints++; double sumPower = 0; double sumObsPower = 0; double prevPower = -1; int timeIndex = 0; int regIndex = 0; foreach (UtcDateTime currentTime in enumer) { if (timeIndex > 10 && sumObsPower <= 1) { break; // If we have nothing for the ten first hours then this forecast point is broken and should not be taken into account... } if (predictionFrames.ContainsKey(currentTime) && productionFrames.ContainsKey(currentTime) && predictionFrames[currentTime].HasValue && productionFrames[currentTime].HasValue) { double power = predictionFrames[currentTime].Value; double obsPower = productionFrames[currentTime].Value; sumPower += power; sumObsPower += obsPower; if(skillScoreHourBins.ContainsKey(Any)) skillScoreHourBins[Any].Register(obsPower, power); int currentHour = currentTime.UtcTime.Hour; if (hourScope.ContainsKey(currentHour)) { int scopeHour = hourScope[currentHour]; if (skillScoreHourBins.ContainsKey(scopeHour)) { skillScoreHourBins[scopeHour].Register(obsPower, power); regIndex++; } } prevPower = obsPower; } timeIndex++; } var first = enumer.First(); var suc = new SortedDictionary<UtcDateTime, RestrictedRangeInt>() { {first, new RestrictedRangeInt(0, timeIndex, regIndex)} }; Success.Add(first, suc); }