using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Application.ATG_Classic_App.Model;
namespace Application.ATG_Classic_App
{
public class HeightToVolumeCaculator
{
public enum Mode
{
///
/// when query height is not defined in profiles, the nearest(could be bigger or smaller) existed height value will be chosen for caculation.
///
PreferNearest = 1,
///
/// when query height is not defined in profiles, the next bigger value will be chosen for caculation.
///
PreferCeiling = 2,
///
/// when query height is not defined in profiles, the prior smaller value will be chosen for caculation.
///
PreferFloor = 4,
}
public Mode EstimateMode { get; set; }
private IOrderedEnumerable tankProfiles_by_Ascending;
///
/// Default use EstimateMode = Mode.PreferNearest | Mode.PreferCeiling
///
///
public HeightToVolumeCaculator(IEnumerable tankProfiles)
{
if (tankProfiles == null || !tankProfiles.Any()) throw new ArgumentNullException(nameof(tankProfiles));
this.EstimateMode = Mode.PreferNearest | Mode.PreferCeiling;
this.tankProfiles_by_Ascending = tankProfiles.OrderBy(p => p.Height);
}
public double GetVolume(double height)
{
var find = this.tankProfiles_by_Ascending.FirstOrDefault(p => p.Height == height);
if (find != null) return find.Volume;
TankProfileData estimation;
if (this.EstimateMode.HasFlag(Mode.PreferNearest))
{
var next = this.tankProfiles_by_Ascending.FirstOrDefault(p => p.Height > height);
if (next == null) return this.tankProfiles_by_Ascending.Last().Volume;
var prior = this.tankProfiles_by_Ascending.LastOrDefault(p => p.Height < height);
if (prior == null) return this.tankProfiles_by_Ascending.First().Volume;
var offsetToNext = next.Height - height;
var offsetToPrior = Math.Abs(prior.Height - height);
if (offsetToNext < offsetToPrior)
estimation = this.tankProfiles_by_Ascending.FirstOrDefault(p => p.Height == next.Height);
else if (offsetToNext > offsetToPrior)
estimation = this.tankProfiles_by_Ascending.FirstOrDefault(p => p.Height == prior.Height);
else
{
if (this.EstimateMode.HasFlag(Mode.PreferCeiling))
estimation = next;
else
estimation = prior;
}
}
else if (this.EstimateMode == Mode.PreferCeiling)
{
// use the nearest up value for this query, so the queried volume would > real volume.
estimation = this.tankProfiles_by_Ascending.FirstOrDefault(p => p.Height > height);
if (estimation == null) return this.tankProfiles_by_Ascending.Last().Volume;
}
else if (this.EstimateMode == Mode.PreferFloor)
{
// use the nearest down value for this query, so the queried volume would < real volume.
estimation = this.tankProfiles_by_Ascending.LastOrDefault(p => p.Height < height);
if (estimation == null) return this.tankProfiles_by_Ascending.First().Volume;
}
else return 0;
return estimation.Volume;
}
//public double GetTcVolume(double height)
//{
// return this.GetVolume(height);
//}
}
public class TemperatureCompensationCaculator
{
private double coeff;
///
///
///
/// 0.0007 is a typical value.
public TemperatureCompensationCaculator(double expansionCoefficient)
{
if (expansionCoefficient > 1 || expansionCoefficient < 0) throw new ArgumentException("invalid expansionCoefficient");
this.coeff = expansionCoefficient;
}
///
/// Get the volume on standard temperature.
///
///
///
///
///
public double CaculateCompensatedVolume(double standardTemp, double currentVol, double currentTemp)
{
var volDiffPerLiter = 1 - this.coeff * (currentTemp - standardTemp);
return volDiffPerLiter * currentVol;
}
}
//public static class DeliveryDectector
//{
// /*according from Thomas.Gan: ATG will capture it as start delivery when it detect the product volume increase by >200 liter in the short period of time
// * when the product has finish the delivery, the driver will wait for 15 minutes and go back to ATG console print out the latest tank slip.
// * the driver will compare the volme before and after deliver and get the station staff to sign off
// * on ATG, it will declar stop deivery when the product has not increase for the last 15 minutes
// */
// /* 1 meter squre = 1000L.
// * in china, for one site, all tanks vol typically <=150000L, single tank <=50000L.
// *
// *
// * by read a real site delivery reports with tank max vol 50000L, can get below facts:
// * 需要记录,实发标准体积, 以V20温度计算,一般在15000L左右。
// * each delivery vol amount is around 15000L.
// *
// */
// private static MLContext mlContext;
// public static DeviceProcessor.ATG.Delivery Get(IEnumerable inventories)
// {
// IOrderedEnumerable data = inventories.OrderBy(i => i.TimeStamp);
// // Create MLContext to be shared across the model creation workflow objects
// mlContext = new MLContext();
// //assign the Number of records in dataset file to cosntant variable
// int size = inventories.Count();
// //Load the data into IDataView.
// //This dataset is used while prediction/detecting spikes or changes.
// IDataView dataView = mlContext.Data.LoadFromEnumerable(
// data.Select(d =>
// new ProductInventoryData() { TimeStamp = d.TimeStamp.ToString("yyyy-MM-dd HH:mm:ss fff") }));
// //To detech temporay changes in the pattern
// //DetectSpike(size, dataView);
// //To detect persistent change in the pattern
// var predictions = DetectChangepoint(size, dataView).ToList();
// for (int i = 0; i < predictions.Count(); i++)
// {
// if (predictions[i].Prediction[0] == 1)
// {
// //return new De
// }
// }
// return null;
// }
// public class ProductInventoryData
// {
// public string TimeStamp;
// public float Volume;
// }
// public class ProductInventoryPrediction
// {
// //vector to hold alert,score,p-value values
// [VectorType(3)]
// public double[] Prediction { get; set; }
// }
// static IEnumerable DetectChangepoint(int size, IDataView dataView)
// {
// //Console.WriteLine("===============Detect Persistent changes in pattern===============");
// //STEP 1: Setup transformations using DetectIidChangePoint
// var estimator = mlContext.Transforms.DetectIidChangePoint(
// outputColumnName: "Prediction",
// inputColumnName: "Volume", confidence: 95, changeHistoryLength: size / 4);
// //STEP 2:The Transformed Model.
// //In IID Change point detection, we don't need need to do training, we just need to do transformation.
// //As you are not training the model, there is no need to load IDataView with real data, you just need schema of data.
// //So create empty data view and pass to Fit() method.
// ITransformer tansformedModel = estimator.Fit(CreateEmptyDataView());
// //STEP 3: Use/test model
// //Apply data transformation to create predictions.
// IDataView transformedData = tansformedModel.Transform(dataView);
// var predictions = mlContext.Data.CreateEnumerable(transformedData, reuseRowObject: false);
// //Console.WriteLine($"{nameof(ProductInventoryPrediction.Prediction)} column obtained post-transformation.");
// //Console.WriteLine("Alert\tScore\tP-Value\tMartingale value");
// foreach (var p in predictions)
// {
// if (p.Prediction[0] == 1)
// {
// //Console.WriteLine("{0}\t{1:0.00}\t{2:0.00}\t{3:0.00} <-- alert is on, predicted changepoint",
// // p.Prediction[0], p.Prediction[1], p.Prediction[2], p.Prediction[3]);
// }
// else
// {
// //Console.WriteLine("{0}\t{1:0.00}\t{2:0.00}\t{3:0.00}",
// // p.Prediction[0], p.Prediction[1], p.Prediction[2], p.Prediction[3]);
// }
// }
// //Console.WriteLine("");
// return predictions;//.Select(p => p.Prediction[0] == 1);
// }
// private static IDataView CreateEmptyDataView()
// {
// //Create empty DataView. We just need the schema to call fit()
// IEnumerable enumerableData = new List();
// var dv = mlContext.Data.LoadFromEnumerable(enumerableData);
// return dv;
// }
//}
}