diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/AlternatorMap.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/AlternatorMap.cs new file mode 100644 index 0000000000000000000000000000000000000000..2909cd614a71debc40151dee8e69fce754bb2916 --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/AlternatorMap.cs @@ -0,0 +1,446 @@ +// Copyright 2017 European Union. +// Licensed under the EUPL (the 'Licence'); +// +// * You may not use this work except in compliance with the Licence. +// * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl +// * Unless required by applicable law or agreed to in writing, +// software distributed under the Licence is distributed on an "AS IS" basis, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the LICENSE.txt for the specific language governing permissions and limitations. +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.BusAuxiliaries.Interfaces; +using TUGraz.VectoCore.BusAuxiliaries.Interfaces.DownstreamModules.Electrics; + +namespace Electrics +{ + public class AlternatorMap : IAlternatorMap + { + private readonly string filePath; + + private List<MapPoint> _map = new List<MapPoint>(); + private List<double> _yRange; + private List<double> _xRange; + private double _minX, _minY, _maxX, _maxY; + + // Required Action Test or Interpolation Type + public bool OnBoundaryYInterpolatedX(double x, double y) + { + return _yRange.Contains(y) && !_xRange.Contains(x); + } + + public bool OnBoundaryXInterpolatedY(double x, double y) + { + return !_yRange.Contains(y) && _xRange.Contains(x); + } + + public bool ONBoundaryXY(double x, double y) + { + return (from sector in _map + where sector.Y == y && sector.x == x + select sector).Count() == 1; + } + + // Determine Value Methods + private double GetOnBoundaryXY(double x, double y) + { + return (from sector in _map + where sector.Y == y && sector.x == x + select sector).First().v; + } + + private double GetOnBoundaryYInterpolatedX(double x, double y) + { + double x0, x1, v0, v1, slope, dx; + + x0 = (from p in _xRange + orderby p + where p < x + select p).Last(); + x1 = (from p in _xRange + orderby p + where p > x + select p).First(); + dx = x1 - x0; + + v0 = GetOnBoundaryXY(x0, y); + v1 = GetOnBoundaryXY(x1, y); + + slope = (v1 - v0) / (x1 - x0); + + return v0 + ((x - x0) * slope); + } + + private double GetOnBoundaryXInterpolatedY(double x, double y) + { + double y0, y1, v0, v1, dy, v, slope; + + y0 = (from p in _yRange + orderby p + where p < y + select p).Last(); + y1 = (from p in _yRange + orderby p + where p > y + select p).First(); + dy = y1 - y0; + + v0 = GetOnBoundaryXY(x, y0); + v1 = GetOnBoundaryXY(x, y1); + + slope = (v1 - v0) / (y1 - y0); + + v = v0 + ((y - y0) * slope); + + return v; + } + + private double GetBiLinearInterpolatedValue(double x, double y) + { + double q11, q12, q21, q22, x1, x2, y1, y2, r1, r2, p; + + y1 = (from mapSector in _map + where mapSector.Y < y + select mapSector).Last().Y; + y2 = (from mapSector in _map + where mapSector.Y > y + select mapSector).First().Y; + + x1 = (from mapSector in _map + where mapSector.x < x + select mapSector).Last().x; + x2 = (from mapSector in _map + where mapSector.x > x + select mapSector).First().x; + + q11 = GetOnBoundaryXY(x1, y1); + q12 = GetOnBoundaryXY(x1, y2); + + q21 = GetOnBoundaryXY(x2, y1); + q22 = GetOnBoundaryXY(x2, y2); + + r1 = ((x2 - x) / (x2 - x1)) * q11 + ((x - x1) / (x2 - x1)) * q21; + + r2 = ((x2 - x) / (x2 - x1)) * q12 + ((x - x1) / (x2 - x1)) * q22; + + + p = ((y2 - y) / (y2 - y1)) * r1 + ((y - y1) / (y2 - y1)) * r2; + + + return p; + } + + // Utilities + private void fillMapWithDefaults() + { + _map.Add(new MapPoint(10, 1500, 0.615)); + _map.Add(new MapPoint(27, 1500, 0.7)); + _map.Add(new MapPoint(53, 1500, 0.1947)); + _map.Add(new MapPoint(63, 1500, 0.0)); + _map.Add(new MapPoint(68, 1500, 0.0)); + _map.Add(new MapPoint(125, 1500, 0.0)); + _map.Add(new MapPoint(136, 1500, 0.0)); + _map.Add(new MapPoint(10, 2000, 0.62)); + _map.Add(new MapPoint(27, 2000, 0.7)); + _map.Add(new MapPoint(53, 2000, 0.3)); + _map.Add(new MapPoint(63, 2000, 0.1462)); + _map.Add(new MapPoint(68, 2000, 0.692)); + _map.Add(new MapPoint(125, 2000, 0.0)); + _map.Add(new MapPoint(136, 2000, 0.0)); + _map.Add(new MapPoint(10, 4000, 0.64)); + _map.Add(new MapPoint(27, 4000, 0.6721)); + _map.Add(new MapPoint(53, 4000, 0.7211)); + _map.Add(new MapPoint(63, 4000, 0.74)); + _map.Add(new MapPoint(68, 4000, 0.7352)); + _map.Add(new MapPoint(125, 4000, 0.68)); + _map.Add(new MapPoint(136, 4000, 0.6694)); + _map.Add(new MapPoint(10, 6000, 0.53)); + _map.Add(new MapPoint(27, 6000, 0.5798)); + _map.Add(new MapPoint(53, 6000, 0.656)); + _map.Add(new MapPoint(63, 6000, 0.6853)); + _map.Add(new MapPoint(68, 6000, 0.7)); + _map.Add(new MapPoint(125, 6000, 0.6329)); + _map.Add(new MapPoint(136, 6000, 0.62)); + _map.Add(new MapPoint(10, 7000, 0.475)); + _map.Add(new MapPoint(27, 7000, 0.5337)); + _map.Add(new MapPoint(53, 7000, 0.6235)); + _map.Add(new MapPoint(63, 7000, 0.658)); + _map.Add(new MapPoint(68, 7000, 0.6824)); + _map.Add(new MapPoint(125, 7000, 0.6094)); + _map.Add(new MapPoint(136, 7000, 0.5953)); + } + + private void getMapRanges() + { + ;/* Cannot convert AssignmentStatementSyntax, System.NotImplementedException: Conversion for query clause with kind 'DistinctClause' not implemented + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.<>c__DisplayClass99_0.<ConvertQueryBodyClause>b__0(QueryClauseSyntax _) + at ICSharpCode.CodeConverter.Util.ObjectExtensions.TypeSwitch[TBaseType,TDerivedType1,TDerivedType2,TDerivedType3,TDerivedType4,TResult](TBaseType obj, Func`2 matchFunc1, Func`2 matchFunc2, Func`2 matchFunc3, Func`2 matchFunc4, Func`2 defaultFunc) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.ConvertQueryBodyClause(QueryClauseSyntax node) + at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext() + at Microsoft.CodeAnalysis.SyntaxList`1.CreateNode(IEnumerable`1 nodes) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitQueryExpression(QueryExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.QueryExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitQueryExpression(QueryExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.QueryExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitParenthesizedExpression(ParenthesizedExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.ParenthesizedExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitParenthesizedExpression(ParenthesizedExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.ParenthesizedExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitInvocationExpression(InvocationExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.InvocationExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitInvocationExpression(InvocationExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.InvocationExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.MethodBodyVisitor.VisitAssignmentStatement(AssignmentStatementSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.AssignmentStatementSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingMethodBodyVisitor.ConvertWithTrivia(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingMethodBodyVisitor.DefaultVisit(SyntaxNode node) + +Input: + + _yRange = (From coords As MapPoint In _map Order By coords.Y Select coords.Y Distinct).ToList() + + */ + ;/* Cannot convert AssignmentStatementSyntax, System.NotImplementedException: Conversion for query clause with kind 'DistinctClause' not implemented + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.<>c__DisplayClass99_0.<ConvertQueryBodyClause>b__0(QueryClauseSyntax _) + at ICSharpCode.CodeConverter.Util.ObjectExtensions.TypeSwitch[TBaseType,TDerivedType1,TDerivedType2,TDerivedType3,TDerivedType4,TResult](TBaseType obj, Func`2 matchFunc1, Func`2 matchFunc2, Func`2 matchFunc3, Func`2 matchFunc4, Func`2 defaultFunc) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.ConvertQueryBodyClause(QueryClauseSyntax node) + at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext() + at Microsoft.CodeAnalysis.SyntaxList`1.CreateNode(IEnumerable`1 nodes) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitQueryExpression(QueryExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.QueryExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitQueryExpression(QueryExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.QueryExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitParenthesizedExpression(ParenthesizedExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.ParenthesizedExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitParenthesizedExpression(ParenthesizedExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.ParenthesizedExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.MemberAccessExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.NodesVisitor.VisitInvocationExpression(InvocationExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.InvocationExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingNodesVisitor.DefaultVisit(SyntaxNode node) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.VisitInvocationExpression(InvocationExpressionSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.InvocationExpressionSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at ICSharpCode.CodeConverter.CSharp.VisualBasicConverter.MethodBodyVisitor.VisitAssignmentStatement(AssignmentStatementSyntax node) + at Microsoft.CodeAnalysis.VisualBasic.Syntax.AssignmentStatementSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor) + at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxVisitor`1.Visit(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingMethodBodyVisitor.ConvertWithTrivia(SyntaxNode node) + at ICSharpCode.CodeConverter.CSharp.CommentConvertingMethodBodyVisitor.DefaultVisit(SyntaxNode node) + +Input: + _xRange = (From coords As MapPoint In _map Order By coords.x Select coords.x Distinct).ToList() + + */ + _minX = _xRange.First(); + _maxX = _xRange.Last(); + _minY = _yRange.First(); + _maxY = _yRange.Last(); + } + + // Single entry point to determine Value on map + public double GetValue(double x, double y) + { + if (x < _minX || x > _maxX || y < _minY || y > _maxY) { + + // OnAuxiliaryEvent(String.Format("Alternator Map Limiting : RPM{0}, AMPS{1}",x,y),AdvancedAuxiliaryMessageType.Warning) + + + // Limiting + if (x < _minX) + x = _minX; + if (x > _maxX) + x = _maxX; + if (y < _minY) + y = _minY; + if (y > _maxY) + y = _maxY; + } + + + // Satisfies both data points - non interpolated value + if (ONBoundaryXY(x, y)) + return GetOnBoundaryXY(x, y); + + // Satisfies only x or y - single interpolation value + if (OnBoundaryXInterpolatedY(x, y)) + return GetOnBoundaryXInterpolatedY(x, y); + if (OnBoundaryYInterpolatedX(x, y)) + return GetOnBoundaryYInterpolatedX(x, y); + + // satisfies no data points - Bi-Linear interpolation + return GetBiLinearInterpolatedValue(x, y); + } + + public string ReturnDefaultMapValueTests() + { + var sb = new StringBuilder(); + + // All Sector Values + sb.AppendLine("All Values From Map"); + sb.AppendLine("-------------------"); + foreach (var xr in _xRange) { + foreach (var yr in _yRange) + sb.AppendLine(string.Format("X:{0}, Y:{1}, V:{2}", xr, yr, GetValue(xr, yr))); + } + + sb.AppendLine(""); + sb.AppendLine("Four Corners with interpolated other"); + sb.AppendLine("-------------------"); + var x = 1500.0; + var y = 18.5; + sb.AppendLine(string.Format("X:{0}, Y:{1}, V:{2}", x, y, GetValue(x, y))); + x = 7000; + y = 96.5; + sb.AppendLine(string.Format("X:{0}, Y:{1}, V:{2}", x, y, GetValue(x, y))); + x = 1750; + y = 10; + sb.AppendLine(string.Format("X:{0}, Y:{1}, V:{2}", x, y, GetValue(x, y))); + x = 6500; + y = 10; + sb.AppendLine(string.Format("X:{0}, Y:{1}, V:{2}", x, y, GetValue(x, y))); + + sb.AppendLine(""); + sb.AppendLine("Interpolated both"); + sb.AppendLine("-------------------"); + + double mx, my; + int x2, y2; + for (x2 = 0; x2 <= _xRange.Count - 2; x2++) { + for (y2 = 0; y2 <= _yRange.Count - 2; y2++) { + mx = _xRange[x2] + (_xRange[x2 + 1] - _xRange[x2]) / 2; + my = _yRange[y2] + (_yRange[y2 + 1] - _yRange[y2]) / 2; + + sb.AppendLine(string.Format("X:{0}, Y:{1}, V:{2}", mx, my, GetValue(mx, my))); + } + } + + sb.AppendLine(""); + sb.AppendLine("MIKE -> 40 & 1000"); + sb.AppendLine("-------------------"); + x = 1000; + y = 40; + sb.AppendLine(string.Format("X:{0}, Y:{1}, V:{2}", x, y, GetValue(x, y))); + + + return sb.ToString(); + } + + // Constructors + public AlternatorMap(string filepath) + { + this.filePath = filepath; + + Initialise(); + + getMapRanges(); + } + + private class MapPoint + { + public double Y; + public double x; + public double v; + + public MapPoint(double y, double x, double v) + { + this.Y = y; + this.x = x; + this.v = v; + } + } + + // Get Alternator Efficiency + public AlternatorMapValues GetEfficiency(double rpm, Ampere amps) + { + return new AlternatorMapValues(GetValue(rpm, amps.Value())); + } + + // Initialises the map. + public bool Initialise() + { + if (File.Exists(filePath)) { + using (StreamReader sr = new StreamReader(filePath)) { + // get array og lines fron csv + var lines = sr.ReadToEnd().Split(new [] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries); + + // Must have at least 2 entries in map to make it usable [dont forget the header row] + if (lines.Count() < 3) + throw new ArgumentException("Insufficient rows in csv to build a usable map"); + + _map = new List<MapPoint>(); + var firstline = true; + + foreach (var line in lines) { + if (!firstline) { + + // Advanced Alternator Source Check. + if (line.Contains("[MODELSOURCE")) + break; + + // split the line + string[] elements = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + // 3 entries per line required + if ((elements.Length != 3)) + throw new ArgumentException("Incorrect number of values in csv file"); + // add values to map + + // Create AlternatorKey + var newPoint = new MapPoint(float.Parse(elements[0], CultureInfo.InvariantCulture), float.Parse(elements[1], CultureInfo.InvariantCulture), float.Parse(elements[2], CultureInfo.InvariantCulture)); + _map.Add(newPoint); + } + firstline = false; + } + } + return true; + } + throw new ArgumentException("Supplied input file does not exist"); + } + + + // Public Events + public event AuxiliaryEventEventHandler AuxiliaryEvent; + + //public delegate void AuxiliaryEventEventHandler(ref object sender, string message, AdvancedAuxiliaryMessageType messageType); + + protected void OnAuxiliaryEvent(string message, AdvancedAuxiliaryMessageType messageType) + { + object alternatorMap = this; + AuxiliaryEvent?.Invoke(ref alternatorMap, message, messageType); + } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/Alternator.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/Alternator.cs new file mode 100644 index 0000000000000000000000000000000000000000..0d563b0884250c51d95bfa84e6306d81df93ace5 --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/Alternator.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; + +namespace Electrics +{ + // Model based on CombinedALTS_V02_Editable.xlsx + public class Alternator : IAlternator + { + private ICombinedAlternatorSignals signals; + + // D6 + public string AlternatorName { get; set; } + + // G6 + public double PulleyRatio { get; set; } + + // C10-D15 + public List<AltUserInput> InputTable2000 { get; set; } = new List<AltUserInput>(); + + // F10-G15 + public List<AltUserInput> InputTable4000 { get; set; } = new List<AltUserInput>(); + + // I10-J15 + public List<AltUserInput> InputTable6000 { get; set; } = new List<AltUserInput>(); + + // M10-N15 + public List<Table4Row> RangeTable { get; set; } = new List<Table4Row>(); + + // S9 + public double SpindleSpeed + { + get { return signals.CrankRPM * PulleyRatio; } + } + + // S10 + public double Efficiency + { + get { + // First build RangeTable, table 4 + InitialiseRangeTable(); + CalculateRangeTable(); + + // Calculate ( Interpolate ) Efficiency + var range = RangeTable.Select(s => new AltUserInput(s.RPM, s.Efficiency)).ToList(); + + return Alternator.Iterpolate(range, Convert.ToSingle(SpindleSpeed)); + } + } + + + // Constructors + public Alternator() { } + + public Alternator(ICombinedAlternatorSignals isignals, List<ICombinedAlternatorMapRow> inputs) + { + if (isignals == null) + throw new ArgumentException("Alternator - ISignals supplied is nothing"); + + signals = isignals; + + AlternatorName = inputs.First().AlternatorName; + PulleyRatio = inputs.First().PulleyRatio; + + var values2k = inputs.Where(x => x.RPM == 2000) + .Select(x => new KeyValuePair<double, double>(x.Amps, x.Efficiency)) + .ToDictionary(x => x.Key, x => x.Value); + var values4k = inputs.Where(x => x.RPM == 4000) + .Select(x => new KeyValuePair<double, double>(x.Amps, x.Efficiency)) + .ToDictionary(x => x.Key, x => x.Value); + var values6k = inputs.Where(x => x.RPM == 6000) + .Select(x => new KeyValuePair<double, double>(x.Amps, x.Efficiency)) + .ToDictionary(x => x.Key, x => x.Value); + + BuildInputTable(values2k, InputTable2000); + BuildInputTable(values4k, InputTable4000); + BuildInputTable(values6k, InputTable6000); + + CreateRangeTable(); + } + + public static double Iterpolate(List<AltUserInput> values, double x) + { + var lowestX = values.Min(m => m.Amps); + var highestX = values.Max(m => m.Amps); + + // Out of range, returns efficiency for lowest + if (x < lowestX) + return values.First(f => f.Amps == lowestX).Eff; + + // Out of range, efficiency for highest + if (x > highestX) + return values.First(f => f.Amps == highestX).Eff; + + // On Bounds check + if (values.Where(w => w.Amps == x).Count() == 1) + return values.First(w => w.Amps == x).Eff; + + // OK, we need to interpolate. + var preKey = values.Last(l => l.Amps < x).Amps; + var postKey = values.First(l => l.Amps > x).Amps; + var preEff = values.First(f => f.Amps == preKey).Eff; + var postEff = values.First(f => f.Amps == postKey).Eff; + + var deltaX = postKey - preKey; + var deltaEff = postEff - preEff; + + // slopes + var effSlope = deltaEff / deltaX; + + var retVal = ((x - preKey) * effSlope) + preEff; + + return retVal; + } + + private void CalculateRangeTable() + { + // M10=Row0-Rpm - N10=Row0-Eff + // M11=Row1-Rpm - N11=Row1-Eff + // M12=Row2-Rpm - N12=Row2-Eff - 2000 + // M13=Row3-Rpm - N13=Row3-Eff - 4000 + // M14=Row4-Rpm - N14=Row4-Eff - 6000 + // M15=Row5-Rpm - N15=Row5-Eff + // M16=Row6-Rpm - N16=Row6-Eff + + double N10, N11, N12, N13, N14, N15, N16; + double M10, M11, M12, M13, M14, M15, M16; + + // EFFICIENCY + + // 2000 + N12 = Alternator.Iterpolate(InputTable2000, signals.CurrentDemandAmps.Value()); + RangeTable[2].Efficiency = N12; + + // 4000 + N13 = Alternator.Iterpolate(InputTable4000, signals.CurrentDemandAmps.Value()); + RangeTable[3].Efficiency = N13; + + // 6000 + N14 = Alternator.Iterpolate(InputTable6000, signals.CurrentDemandAmps.Value()); + RangeTable[4].Efficiency = N14; + + // Row0 & Row1 Efficiency =IF(N13>N12,0,MAX(N12:N14)) - Example Alt 1 N13= + N11 = N13 > N12 ? 0 : Math.Max(Math.Max(N12, N13), N14); + RangeTable[1].Efficiency = N11; + N10 = N11; + RangeTable[0].Efficiency = N10; + + // Row 5 Efficiency + N15 = N13 > N14 ? 0 : Math.Max(Math.Max(N12, N13), N14); + RangeTable[5].Efficiency = N15; + + // Row 6 - Efficiency + N16 = N15; + RangeTable[6].Efficiency = N16; + + // RPM + + // 2000 Row 2 - RPM + M12 = 2000; + RangeTable[2].RPM = M12; + + // 4000 Row 3 - RPM + M13 = 4000; + RangeTable[3].RPM = M13; + + // 6000 Row 4 - RPM + M14 = 6000; + RangeTable[4].RPM = M14; + + // Row 1 - RPM + // NOTE: Update to reflect CombineALternatorSchematicV02 20150429 + // IF(M12=IF(N12>N13,M12-((M12-M13)/(N12-N13))*(N12-N11),M12-((M12-M13)/(N12-N13))*(N12-N11)), M12-0.01, IF(N12>N13,M12-((M12-M13)/(N12-N13))*(N12-N11),M12-((M12-M13)/(N12-N13))*(N12-N11))) + M11 = + Convert.ToSingle( + N12 - N13 == 0 + ? 0 + : ( + M12 == (N12 > N13 + ? M12 - (M12 - M13) / (N12 - N13) * (N12 - N11) + : M12 - (M12 - M13) / (N12 - N13) * (N12 - N11)) + ? M12 - 0.01 + : (N12 > N13 + ? M12 - (M12 - M13) / (N12 - N13) * (N12 - N11) + : M12 - (M12 - M13) / (N12 - N13) * (N12 - N11)))); + + RangeTable[1].RPM = M11; + + // Row 0 - RPM + M10 = M11 < 1500 ? M11 - 1 : 1500; + RangeTable[0].RPM = M10; + + // Row 5 - RPM + M15 = + Convert.ToSingle( + M14 == (N14 == 0 || N14 == N13 + ? M14 + 1 + : (N13 > N14 ? (M14 - M13) / (N13 - N14) * N14 + M14 : (M14 - M13) / (N13 - N14) * (N14 - N15) + M14) + ) + ? M14 + 0.01 + : (N14 == 0 || N14 == N13 + ? M14 + 1 + : (N13 > N14 ? (M14 - M13) / (N13 - N14) * N14 + M14 : (M14 - M13) / (N13 - N14) * (N14 - N15) + M14))); + + RangeTable[5].RPM = M15; + + // Row 6 - RPM + M16 = M15 > 10000 ? M15 + 1 : 10000; + RangeTable[6].RPM = M16; + } + + private void InitialiseRangeTable() + { + RangeTable[0].RPM = 0; + RangeTable[0].Efficiency = 0; + RangeTable[1].RPM = 0; + RangeTable[0].Efficiency = 0; + RangeTable[2].RPM = 2000; + RangeTable[0].Efficiency = 0; + RangeTable[3].RPM = 4000; + RangeTable[0].Efficiency = 0; + RangeTable[4].RPM = 6000; + RangeTable[0].Efficiency = 0; + RangeTable[5].RPM = 0; + RangeTable[0].Efficiency = 0; + RangeTable[6].RPM = 0; + RangeTable[0].Efficiency = 0; + } + + private void CreateRangeTable() + { + RangeTable.Clear(); + + RangeTable.Add(new Table4Row(0, 0)); + RangeTable.Add(new Table4Row(0, 0)); + RangeTable.Add(new Table4Row(0, 0)); + RangeTable.Add(new Table4Row(0, 0)); + RangeTable.Add(new Table4Row(0, 0)); + RangeTable.Add(new Table4Row(0, 0)); + RangeTable.Add(new Table4Row(0, 0)); + } + + public void BuildInputTable(Dictionary<double, double> inputs, List<AltUserInput> targetTable) + { + double C11, C12, C13, C14, C15, D11, D12, D13, D14, D15; + targetTable.Clear(); + + // Row0 + D14 = 0; + targetTable.Add(new AltUserInput(0, D14)); + + // Row1 + targetTable.Add(new AltUserInput(inputs.OrderBy(x => x.Key).First().Key, inputs.OrderBy(x => x.Key).First().Value)); + + // Row2 + targetTable.Add( + new AltUserInput(inputs.OrderBy(x => x.Key).Skip(1).First().Key, inputs.OrderBy(x => x.Key).Skip(1).First().Value)); + + // Row3 + targetTable.Add( + new AltUserInput(inputs.OrderBy(x => x.Key).Skip(2).First().Key, inputs.OrderBy(x => x.Key).Skip(2).First().Value)); + + C11 = targetTable[1].Amps; + C12 = targetTable[2].Amps; + C13 = targetTable[3].Amps; + + D11 = targetTable[1].Eff; + D12 = targetTable[2].Eff; + D13 = targetTable[3].Eff; + + D14 = D12 > D13 ? 0 : Math.Max(Math.Max(D11, D12), D13); + + // Row4 - Eff + targetTable.Add(new AltUserInput(0, D14)); + + // Row4 - Amps + // Should probably refactor this into some sort of helper/extension method + var numarray = new[] { D11, D12, D13 }; + var maxD11_D13 = numarray.Max(); + + // =IF(OR(D13=0,D13=D12),C13+1,IF(D12>D13,((((C13-C12)/(D12-D13))*D13)+C13),((((C13-C12)/(D12-D13))*(D13-D14))+C13))) + C14 = (D13 == 0 || D13 == D12 || D13 == maxD11_D13) + ? C13 + 1 + : D12 > D13 + ? ((((C13 - C12) / (D12 - D13)) * D13) + C13) + : ((((C13 - C12) / (D12 - D13)) * (D13 - D14)) + C13); + targetTable[4].Amps = C14; + + // Row5 + C15 = C14 > 200 ? C14 + 1 : 200; + D15 = D14; + targetTable.Add(new AltUserInput(C15, D15)); + + // Row0 + targetTable[0].Eff = D11; + } + + public bool IsEqualTo(IAlternator other) + { + if (AlternatorName != other.AlternatorName) { + return false; + } + if (PulleyRatio != other.PulleyRatio) { + return false; + } + + for (var i = 1; i <= 3; i++) { + if (InputTable2000[i].Eff != other.InputTable2000[i].Eff) + return false; + if (InputTable4000[i].Eff != other.InputTable4000[i].Eff) + return false; + if (InputTable6000[i].Eff != other.InputTable6000[i].Eff) + return false; + } + + return true; + } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternator.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternator.cs new file mode 100644 index 0000000000000000000000000000000000000000..de15bb33d8be780b1e0abd559df6abafc0bb5fb4 --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternator.cs @@ -0,0 +1,401 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.BusAuxiliaries.Interfaces; +using TUGraz.VectoCore.BusAuxiliaries.Interfaces.DownstreamModules.Electrics; + + +namespace Electrics +{ + public class CombinedAlternator : IAlternatorMap, ICombinedAlternator + { + private List<ICombinedAlternatorMapRow> map = new List<ICombinedAlternatorMapRow>(); + public List<IAlternator> Alternators { get; set; } = new List<IAlternator>(); + private List<IAlternator> OriginalAlternators = new List<IAlternator>(); + private string FilePath; + private ICombinedAlternatorSignals altSignals; + private ISignals Signals; + private AlternatorMapValues AverageAlternatorsEfficiency; + + // Interface Implementation + public AlternatorMapValues GetEfficiency(double CrankRPM, Ampere Amps) + { + altSignals.CrankRPM = CrankRPM; + altSignals.CurrentDemandAmps = (Amps.Value() / (double)Alternators.Count).SI<Ampere>(); + + AlternatorMapValues alternatorMapValues; /* TODO Change to default(_) if this is not a reference type */; + + if (Signals == null || Signals.RunningCalc) + // If running calc cycle get efficiency from interpolation function + alternatorMapValues = new AlternatorMapValues(Convert.ToSingle(Alternators.Average(a => a.Efficiency) / (double)100)); + else + // If running Pre calc cycle get an average of inputs + alternatorMapValues = AverageAlternatorsEfficiency; + + if (alternatorMapValues.Efficiency <= 0) + alternatorMapValues = new AlternatorMapValues(0.01); + + return alternatorMapValues; + } + + public bool Initialise() + { + + // From the map we construct this CombinedAlternator object and original CombinedAlternator Object + + Alternators.Clear(); + OriginalAlternators.Clear(); + + + foreach (var alt in map.GroupBy(g => g.AlternatorName)) { + var altName = alt.First().AlternatorName; + var pulleyRatio = alt.First().PulleyRatio; + + + IAlternator alternator = new Alternator(altSignals, alt.ToList()); + + Alternators.Add(alternator); + } + + return true; + } + + // Constructors + public CombinedAlternator(string filePath, ISignals signals = null/* TODO Change to default(_) if this is not a reference type */) + { + string feedback = string.Empty; + this.Signals = signals; + + if (!FilePathUtils.ValidateFilePath(filePath, ".aalt", ref feedback)) + throw new ArgumentException(string.Format("Combined Alternator requires a valid .AALT filename. : {0}", feedback)); + else + this.FilePath = filePath; + + + this.altSignals = new CombinedAlternatorSignals(); + + + // IF file exists then read it otherwise create a default. + + if (File.Exists(filePath) && InitialiseMap(filePath)) + Initialise(); + else { + // Create Default Map + CreateDefaultMap(); + Initialise(); + } + + // Calculate alternators average which is used only in the pre-run + var efficiencySum = 0.0; + + foreach (IAlternator alt in Alternators) { + efficiencySum += alt.InputTable2000.ElementAt(1).Eff; + efficiencySum += alt.InputTable2000.ElementAt(2).Eff; + efficiencySum += alt.InputTable2000.ElementAt(3).Eff; + + efficiencySum += alt.InputTable4000.ElementAt(1).Eff; + efficiencySum += alt.InputTable4000.ElementAt(2).Eff; + efficiencySum += alt.InputTable4000.ElementAt(3).Eff; + + efficiencySum += alt.InputTable6000.ElementAt(1).Eff; + efficiencySum += alt.InputTable6000.ElementAt(2).Eff; + efficiencySum += alt.InputTable6000.ElementAt(3).Eff; + } + + var efficiencyAverage = efficiencySum / (Alternators.Count * 9); + AverageAlternatorsEfficiency = new AlternatorMapValues(efficiencyAverage / 100); + } + + event TUGraz.VectoCore.BusAuxiliaries.Interfaces.AuxiliaryEventEventHandler IAuxiliaryEvent.AuxiliaryEvent + { + add { + throw new NotImplementedException(); + } + + remove { + throw new NotImplementedException(); + } + } + + // Helpers + private void CreateDefaultMap() + { + map.Clear(); + + map.Add(new CombinedAlternatorMapRow("Alt1", 2000, 10, 62, 3.6)); + map.Add(new CombinedAlternatorMapRow("Alt1", 2000, 27, 70, 3.6)); + map.Add(new CombinedAlternatorMapRow("Alt1", 2000, 53, 30, 3.6)); + + map.Add(new CombinedAlternatorMapRow("Alt1", 4000, 10, 64, 3.6)); + map.Add(new CombinedAlternatorMapRow("Alt1", 4000, 63, 74, 3.6)); + map.Add(new CombinedAlternatorMapRow("Alt1", 4000, 125, 68, 3.6)); + + map.Add(new CombinedAlternatorMapRow("Alt1", 6000, 10, 53, 3.6)); + map.Add(new CombinedAlternatorMapRow("Alt1", 6000, 68, 70, 3.6)); + map.Add(new CombinedAlternatorMapRow("Alt1", 6000, 136, 62, 3.6)); + + map.Add(new CombinedAlternatorMapRow("Alt2", 2000, 10, 62, 3)); + map.Add(new CombinedAlternatorMapRow("Alt2", 2000, 27, 70, 3)); + map.Add(new CombinedAlternatorMapRow("Alt2", 2000, 53, 30, 3)); + + map.Add(new CombinedAlternatorMapRow("Alt2", 4000, 10, 64, 3)); + map.Add(new CombinedAlternatorMapRow("Alt2", 4000, 63, 74, 3)); + map.Add(new CombinedAlternatorMapRow("Alt2", 4000, 125, 68, 3)); + + map.Add(new CombinedAlternatorMapRow("Alt2", 6000, 10, 53, 3)); + map.Add(new CombinedAlternatorMapRow("Alt2", 6000, 68, 70, 3)); + map.Add(new CombinedAlternatorMapRow("Alt2", 6000, 136, 62, 3)); + } + + // Grid Management + private bool AddNewAlternator(List<ICombinedAlternatorMapRow> list, ref string feeback) + { + var altName = list.First().AlternatorName; + var pulleyRatio = list.First().PulleyRatio; + + // Check alt does not already exist in list + if (Alternators.Any(w => w.AlternatorName == altName)) { + feeback = "This alternator already exists in in the list, operation not completed."; + return false; + } + + IAlternator alternator = new Alternator(altSignals, list.ToList()); + + Alternators.Add(alternator); + + + return true; + } + + public bool AddAlternator(List<ICombinedAlternatorMapRow> rows, ref string feedback) + { + if (!AddNewAlternator(rows, ref feedback)) { + feedback = string.Format("Unable to add new alternator : {0}", feedback); + return false; + } + + return true; + } + + public bool DeleteAlternator(string alternatorName, ref string feedback, bool CountValidation) + { + + // Is this the last alternator, if so deny the user the right to remove it. + if (CountValidation && Alternators.Count < 2) { + feedback = "There must be at least one alternator remaining, operation aborted."; + return false; + } + + if (Alternators.All(w => w.AlternatorName != alternatorName)) { + feedback = "This alternator does not exist"; + return false; + } + + IAlternator altToRemove = Alternators.First(w => w.AlternatorName == alternatorName); + int numAlternators = Alternators.Count; + + Alternators.Remove(altToRemove); + + if (Alternators.Count == numAlternators - 1) { + return true; + } + + feedback = string.Format("The alternator {0} could not be removed : {1}", alternatorName, feedback); + return false; + } + + + public bool Save(string aaltPath) + { + var sb = new StringBuilder(); + + // write headers + sb.AppendLine("[AlternatorName],[RPM],[Amps],[Efficiency],[PulleyRatio]"); + + // write details + foreach (IAlternator alt in Alternators.OrderBy(o => o.AlternatorName)) { + // 2000 - IE Alt1,2000,10,50,3 + for (var row = 1; row <= 3; row++) { + var amps = alt.InputTable2000[row].Amps; + var eff = alt.InputTable2000[row].Eff; + sb.Append(alt.AlternatorName + ",2000," + amps.ToString("0.000") + "," + eff.ToString("0.000") + "," + alt.PulleyRatio.ToString("0.000")); + sb.AppendLine(""); + } + + // 4000 - IE Alt1,2000,10,50,3 + for (var row = 1; row <= 3; row++) { + var amps = alt.InputTable4000[row].Amps; + var eff = alt.InputTable4000[row].Eff; + sb.Append(alt.AlternatorName + ",4000," + amps.ToString("0.000") + "," + eff.ToString("0.000") + "," + alt.PulleyRatio.ToString("0.000")); + sb.AppendLine(""); + } + + // 6000 - IE Alt1,2000,10,50,3 + for (var row = 1; row <= 3; row++) { + var amps = alt.InputTable6000[row].Amps; + var eff = alt.InputTable6000[row].Eff; + sb.Append(alt.AlternatorName + ",6000," + amps.ToString("0.000") + "," + eff.ToString("0.000") + "," + alt.PulleyRatio.ToString("0.000")); + sb.AppendLine(""); + } + } + + // Add Model Source + sb.AppendLine("[MODELSOURCE]"); + sb.Append(ToString()); + + // Write the stream cotnents to a new file named "AllTxtFiles.txt" + using (StreamWriter outfile = new StreamWriter(aaltPath)) { + outfile.Write(sb.ToString()); + } + + return true; + } + + private bool Load() + { + if (!InitialiseMap(FilePath)) + return false; + + + return true; + } + + // Initialises the map, only valid when loadingUI for first time in edit mode or always in operational mode. + private bool InitialiseMap(string filePath) + { + bool returnValue = false; + string[] elements; + + if (File.Exists(filePath)) { + using (StreamReader sr = new StreamReader(filePath)) { + // get array og lines fron csv + string[] lines = sr.ReadToEnd().Split(new[] { Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries); + + // Must have at least 2 entries in map to make it usable [dont forget the header row] + if ((lines.Count() < 10)) + throw new ArgumentException("Insufficient rows in csv to build a usable map"); + + map = new List<ICombinedAlternatorMapRow>(); + + bool firstline = true; + + foreach (string line in lines) { + if (!firstline) { + + // Advanced Alternator Source Check. + if (line.Contains("[MODELSOURCE")) + break; + + // split the line + elements = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + // 3 entries per line required + if ((elements.Length != 5)) + throw new ArgumentException("Incorrect number of values in csv file"); + // add values to map + + map.Add(new CombinedAlternatorMapRow(elements[0], float.Parse(elements[1], CultureInfo.InvariantCulture), float.Parse(elements[2], CultureInfo.InvariantCulture), float.Parse(elements[3], CultureInfo.InvariantCulture), float.Parse(elements[4], CultureInfo.InvariantCulture))); + } else + firstline = false; + } + } + return true; + } else + throw new ArgumentException("Supplied input file does not exist"); + + return returnValue; + } + + // Can be used to send messages to Vecto. + public event AuxiliaryEventEventHandler AuxiliaryEvent; + + public delegate void AuxiliaryEventEventHandler(ref object sender, string message, AdvancedAuxiliaryMessageType messageType); + + // This is used to generate a diagnostics output which enables the user to + // Determine if they beleive the resulting map is what is expected + // Basically it is a check against the model/Spreadsheet + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + string a1, a2, a3, e1, e2, e3; + + const string vbTab = "\t"; + foreach (Alternator alt in Alternators.OrderBy(o => o.AlternatorName)) { + sb.AppendLine(""); + sb.AppendFormat("** {0} ** , PulleyRatio {1}", alt.AlternatorName, alt.PulleyRatio); + sb.AppendLine(""); + sb.AppendLine("******************************************************************"); + sb.AppendLine(""); + + int i = 1; + sb.AppendLine("Table 1 (2000)" + vbTab + "Table 2 (4000)" + vbTab + "Table 3 (6000)"); + sb.AppendLine("Amps" + vbTab + "Eff" + vbTab + "Amps" + vbTab + "Eff" + vbTab + "Amps" + vbTab + "Eff" + vbTab); + sb.AppendLine(""); + for (i = 1; i <= 3; i++) { + a1 = alt.InputTable2000[i].Amps.ToString("0"); + e1 = alt.InputTable2000[i].Eff.ToString("0.000"); + a2 = alt.InputTable4000[i].Amps.ToString("0"); + e2 = alt.InputTable4000[i].Eff.ToString("0.000"); + a3 = alt.InputTable6000[i].Amps.ToString("0"); + e3 = alt.InputTable6000[i].Eff.ToString("0.000"); + sb.AppendLine(a1 + vbTab + e1 + vbTab + a2 + vbTab + e2 + vbTab + a3 + vbTab + e3 + vbTab); + } + } + + // sb.AppendLine("") + // sb.AppendLine("********* COMBINED EFFICIENCY VALUES **************") + // sb.AppendLine("") + // sb.AppendLine(vbTab + "RPM VALUES") + // sb.AppendLine("AMPS" + vbTab + "500" + vbTab + "1500" + vbTab + "2500" + vbTab + "3500" + vbTab + "4500" + vbTab + "5500" + vbTab + "6500" + vbTab + "7500") + // For a As Single = 1 To Alternators.Count * 50 + + // sb.Append(a.ToString("0") + vbTab) + // For Each r As Single In {500, 1500, 2500, 3500, 4500, 5500, 6500, 7500} + + // Dim eff As Single = GetEfficiency(r, a).Efficiency + + // sb.Append(eff.ToString("0.000") + vbTab) + + // Next + // sb.AppendLine("") + + // Next + + + return sb.ToString(); + } + + + // Equality + public bool IsEqualTo(ICombinedAlternator other) + { + + // Count Check. + if (this.Alternators.Count != other.Alternators.Count) + return false; + + foreach (IAlternator alt in this.Alternators) { + + // Can we find the same alternatorName in other + if (other.Alternators.Where(f => f.AlternatorName == alt.AlternatorName).Count() != 1) + return false; + + // get the alternator to compare and compare it. + if (!alt.IsEqualTo(other.Alternators.First(f => f.AlternatorName == alt.AlternatorName))) + return false; + } + + return true; + } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternatorMapRow.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternatorMapRow.cs new file mode 100644 index 0000000000000000000000000000000000000000..bf92600190ea7a800bf539695fa68014eebcb22a --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternatorMapRow.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; + +namespace Electrics +{ + // This class is reflective of the stored entries for the combined alternator + // And is used by the Combined Alternator Form and any related classes. + + public class CombinedAlternatorMapRow : ICombinedAlternatorMapRow + { + public string AlternatorName { get; set; } + public double RPM { get; set; } + public double Amps { get; set; } + public double Efficiency { get; set; } + public double PulleyRatio { get; set; } + + // Constructors + public CombinedAlternatorMapRow() + { + } + + public CombinedAlternatorMapRow(string alternatorName, double rpm, double amps, double efficiency, double pulleyRatio) + { + + // Sanity Check + if (alternatorName.Trim().Length == 0) + throw new ArgumentException("Alternator name cannot be zero length"); + if (efficiency < 0 | efficiency > 100) + throw new ArgumentException("Alternator Efficiency must be between 0 and 100"); + if (pulleyRatio <= 0) + throw new ArgumentException("Alternator Pully ratio must be a positive number"); + + // Assignments + AlternatorName = alternatorName; + RPM = rpm; + Amps = amps; + Efficiency = efficiency; + PulleyRatio = pulleyRatio; + } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternatorSignals.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternatorSignals.cs new file mode 100644 index 0000000000000000000000000000000000000000..7133c281b8bbcf7154a286d93cf6c1dbb2fd9f03 --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/CombinedAlternatorSignals.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; +using TUGraz.VectoCommon.Utils; + +namespace Electrics +{ + // Used by the CombinedAlternator class and any other related classes. + public class CombinedAlternatorSignals : ICombinedAlternatorSignals + { + public double CrankRPM { get; set; } + + public Ampere CurrentDemandAmps { get; set; } + + // Number of alternators in the Combined Alternator + public int NumberOfAlternators { get; set; } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricalConsumer.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricalConsumer.cs new file mode 100644 index 0000000000000000000000000000000000000000..4fa98ab74e92b1c846f3aab2e0f8616a24de4ac9 --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricalConsumer.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using System.ComponentModel; +using DownstreamModules.Electrics; +using TUGraz.VectoCommon.Utils; + +// Copyright 2017 European Union. +// Licensed under the EUPL (the 'Licence'); +// +// * You may not use this work except in compliance with the Licence. +// * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl +// * Unless required by applicable law or agreed to in writing, +// software distributed under the Licence is distributed on an "AS IS" basis, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the LICENSE.txt for the specific language governing permissions and limitations. + +namespace Electrics +{ + /// <summary> + /// ''' Described a consumer of Alternator electrical power + /// ''' </summary> + /// ''' <remarks></remarks> + public class ElectricalConsumer : IElectricalConsumer + { + + // Fields + private bool _BaseVehicle; + private string _Category; + private string _ConsumerName; + private double _NominalConsumptionAmps; + private int _NumberInActualVehicle; + private double _PhaseIdle_TractionOn; + private double _PowerNetVoltage; + private string _Info; + + // Calculated + public double AvgConsumptionAmps { get; set; } + + // Properties + public bool BaseVehicle + { + get { + return _BaseVehicle; + } + set { + _BaseVehicle = value; + NotifyPropertyChanged("BaseVehicle"); + } + } + + public string Category + { + get { + return _Category; + } + set { + _Category = value; + NotifyPropertyChanged("Category"); + } + } + + public string ConsumerName + { + get { + return _ConsumerName; + } + set { + _ConsumerName = value; + NotifyPropertyChanged("ConsumerName"); + } + } + + public double NominalConsumptionAmps + { + get { + return _NominalConsumptionAmps; + } + set { + _NominalConsumptionAmps = value; + NotifyPropertyChanged("NominalConsumptionAmps"); + } + } + + public int NumberInActualVehicle + { + get { + return _NumberInActualVehicle; + } + set { + _NumberInActualVehicle = value; + NotifyPropertyChanged("NumberInActualVehicle"); + } + } + + public double PhaseIdle_TractionOn + { + get { + return _PhaseIdle_TractionOn; + } + set { + _PhaseIdle_TractionOn = value; + NotifyPropertyChanged("PhaseIdle_TractionOn"); + } + } + + public double PowerNetVoltage + { + get { + return _PowerNetVoltage; + } + set { + _PowerNetVoltage = value; + NotifyPropertyChanged("PowerNetVoltage"); + } + } + + public string Info + { + get { + return _Info; + } + set { + _Info = value; + NotifyPropertyChanged("Info"); + } + } + + + // Public class outputs + public Ampere TotalAvgConumptionAmps(double PhaseIdle_TractionOnBasedOnCycle = default(Double)) + { + if (ConsumerName == "Doors per Door") + return NominalConsumptionAmps.SI<Ampere>() * (NumberInActualVehicle * PhaseIdle_TractionOnBasedOnCycle); + else + return NominalConsumptionAmps.SI<Ampere>() * (NumberInActualVehicle * PhaseIdle_TractionOn); + } + + public Watt TotalAvgConsumptionInWatts(double PhaseIdle_TractionOnBasedOnCycle = 0.0) + { + return TotalAvgConumptionAmps(PhaseIdle_TractionOnBasedOnCycle) * PowerNetVoltage.SI<Volt>(); + } + + // Constructor + public ElectricalConsumer(bool BaseVehicle, string Category, string ConsumerName, double NominalConsumptionAmps, double PhaseIdle_TractionOn, double PowerNetVoltage, int numberInVehicle, string info) + { + + // Illegal Value Check. + if (Category.Trim().Length == 0) + throw new ArgumentException("Category Name cannot be empty"); + if (ConsumerName.Trim().Length == 0) + throw new ArgumentException("ConsumerName Name cannot be empty"); + if (PhaseIdle_TractionOn < ElectricConstants.PhaseIdleTractionOnMin | PhaseIdle_TractionOn > ElectricConstants.PhaseIdleTractionMax) + throw new ArgumentException("PhaseIdle_TractionOn must have a value between 0 and 1"); + if (NominalConsumptionAmps < ElectricConstants.NonminalConsumerConsumptionAmpsMin | NominalConsumptionAmps > ElectricConstants.NominalConsumptionAmpsMax) + throw new ArgumentException("NominalConsumptionAmps must have a value between 0 and 100"); + if (PowerNetVoltage < ElectricConstants.PowenetVoltageMin | PowerNetVoltage > ElectricConstants.PowenetVoltageMax) + throw new ArgumentException("PowerNetVoltage must have a value between 6 and 48"); + if (numberInVehicle < 0) + throw new ArgumentException("Cannot have less than 0 consumers in the vehicle"); + + // Good, now assign. + this.BaseVehicle = BaseVehicle; + this.Category = Category; + this.ConsumerName = ConsumerName; + this.NominalConsumptionAmps = NominalConsumptionAmps; + this.PhaseIdle_TractionOn = PhaseIdle_TractionOn; + this.PowerNetVoltage = PowerNetVoltage; + this.NumberInActualVehicle = numberInVehicle; + this.Info = info; + } + + // Comparison Overrides + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + return false; + + IElectricalConsumer other = (IElectricalConsumer)obj; + + + return this.ConsumerName == other.ConsumerName; + } + + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public override int GetHashCode() + { + return 0; + } + + + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyPropertyChanged(string p) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p)); + } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricalConsumerList.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricalConsumerList.cs new file mode 100644 index 0000000000000000000000000000000000000000..f5bf2b460689927c4fba0ca3a7f7c30352e3650b --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricalConsumerList.cs @@ -0,0 +1,177 @@ +// Copyright 2017 European Union. +// Licensed under the EUPL (the 'Licence'); +// +// * You may not use this work except in compliance with the Licence. +// * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl +// * Unless required by applicable law or agreed to in writing, +// software distributed under the Licence is distributed on an "AS IS" basis, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the LICENSE.txt for the specific language governing permissions and limitations. + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; +using TUGraz.VectoCommon.Utils; + +namespace Electrics +{ + public class ElectricalConsumerList : IElectricalConsumerList + { + private List<IElectricalConsumer> _items = new List<IElectricalConsumer>(); + private double _powernetVoltage; + private double _doorDutyCycleZeroToOne; + + + // Constructor + public ElectricalConsumerList(double powernetVoltage, double doorDutyCycle_ZeroToOne, bool createDefaultList = false) + { + _powernetVoltage = powernetVoltage; + + if (createDefaultList) + _items = GetDefaultConsumerList(); + + + _doorDutyCycleZeroToOne = doorDutyCycle_ZeroToOne; + } + + // Transfers the Info comments from a default set of consumables to a live set. + // This way makes the comments not dependent on saved data. + public void MergeInfoData() + { + if (_items.Count != GetDefaultConsumerList().Count) + return; + + List<IElectricalConsumer> dflt = GetDefaultConsumerList(); + + for (int idx = 0; idx <= _items.Count - 1; idx++) + + _items[idx].Info = dflt[idx].Info; + } + + // Initialise default set of consumers + public List<IElectricalConsumer> GetDefaultConsumerList() + { + + // This populates the default settings as per engineering spreadsheet. + // Vehicle Basic Equipment' category can be added or remove by customers. + // At some time in the future, this may be removed and replace with file based consumer lists. + + List<IElectricalConsumer> items = new List<IElectricalConsumer>(); + + IElectricalConsumer c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20; + + c1 = (IElectricalConsumer)new ElectricalConsumer(false, "Doors", "Doors per Door", 3.0, 0.096339, _powernetVoltage, 3, ""); + c2 = (IElectricalConsumer)new ElectricalConsumer(true, "Veh Electronics &Engine", "Controllers,Valves etc", 25.0, 1.0, _powernetVoltage, 1, ""); + c3 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "Radio City", 2.0, 0.8, _powernetVoltage, 1, ""); + c4 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "Radio Intercity", 5.0, 0.8, _powernetVoltage, 0, ""); + c5 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "Radio/Audio Tourism", 9.0, 0.8, _powernetVoltage, 0, ""); + c6 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "Fridge", 4.0, 0.5, _powernetVoltage, 0, ""); + c7 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "Kitchen Standard", 67.0, 0.05, _powernetVoltage, 0, ""); + c8 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "Interior lights City/ Intercity + Doorlights [Should be 1/m]", 1.0, 0.7, _powernetVoltage, 12, "1 Per metre length of bus"); + c9 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "LED Interior lights ceiling city/Intercity + door [Should be 1/m]", 0.6, 0.7, _powernetVoltage, 0, "1 Per metre length of bus"); + c10 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "Interior lights Tourism + reading [1/m]", 1.1, 0.7, _powernetVoltage, 0, "1 Per metre length of bus"); + c11 = (IElectricalConsumer)new ElectricalConsumer(false, "Vehicle basic equipment", "LED Interior lights ceiling Tourism + LED reading [Should be 1/m]", 0.66, 0.7, _powernetVoltage, 0, "1 Per metre length of bus"); + c12 = (IElectricalConsumer)new ElectricalConsumer(false, "Customer Specific Equipment", "External Displays Font/Side/Rear", 2.65017667844523, 1.0, _powernetVoltage, 4, ""); + c13 = (IElectricalConsumer)new ElectricalConsumer(false, "Customer Specific Equipment", "Internal display per unit ( front side rear)", 1.06007067137809, 1.0, _powernetVoltage, 1, ""); + c14 = (IElectricalConsumer)new ElectricalConsumer(false, "Customer Specific Equipment", "CityBus Ref EBSF Table4 Devices ITS No Displays", 9.3, 1.0, _powernetVoltage, 1, ""); + c15 = (IElectricalConsumer)new ElectricalConsumer(false, "Lights", "Exterior Lights BULB", 7.4, 1.0, _powernetVoltage, 1, ""); + c16 = (IElectricalConsumer)new ElectricalConsumer(false, "Lights", "Day running lights LED bonus", -0.723, 1.0, _powernetVoltage, 1, ""); + c17 = (IElectricalConsumer)new ElectricalConsumer(false, "Lights", "Antifog rear lights LED bonus", -0.17, 1.0, _powernetVoltage, 1, ""); + c18 = (IElectricalConsumer)new ElectricalConsumer(false, "Lights", "Position lights LED bonus", -1.2, 1.0, _powernetVoltage, 1, ""); + c19 = (IElectricalConsumer)new ElectricalConsumer(false, "Lights", "Direction lights LED bonus", -0.3, 1.0, _powernetVoltage, 1, ""); + c20 = (IElectricalConsumer)new ElectricalConsumer(false, "Lights", "Brake Lights LED bonus", -1.2, 1.0, _powernetVoltage, 1, ""); + + items.Add(c1); + items.Add(c2); + items.Add(c3); + items.Add(c4); + items.Add(c5); + items.Add(c6); + items.Add(c7); + items.Add(c8); + items.Add(c9); + items.Add(c10); + items.Add(c11); + items.Add(c12); + items.Add(c13); + items.Add(c14); + items.Add(c15); + items.Add(c16); + items.Add(c17); + items.Add(c18); + items.Add(c19); + items.Add(c20); + + return items; + } + + + // Interface implementation + public double DoorDutyCycleFraction + { + get { + return _doorDutyCycleZeroToOne; + } + set { + _doorDutyCycleZeroToOne = value; + } + } + + public List<IElectricalConsumer> Items + { + get { + return _items; + } + } + + public void AddConsumer(IElectricalConsumer consumer) + { + if (!_items.Contains(consumer)) + _items.Add(consumer); + else + throw new ArgumentException("Consumer Already Present in the list"); + } + + public void RemoveConsumer(IElectricalConsumer consumer) + { + if (_items.Contains(consumer)) + _items.Remove(consumer); + else + throw new ArgumentException("Consumer Not In List"); + } + + + public Ampere GetTotalAverageDemandAmps(bool excludeOnBase) + { + Ampere Amps; + + if (excludeOnBase) + + Amps = Items.Where(x => x.BaseVehicle == false) + .Sum(consumer => consumer.TotalAvgConumptionAmps(DoorDutyCycleFraction)); + + // Aggregate item In Items Where item.BaseVehicle = False Into Sum(item.TotalAvgConumptionAmps(DoorDutyCycleFraction)) + + + else + + Amps = Items.Sum(x => x.TotalAvgConumptionAmps(DoorDutyCycleFraction)); + //Aggregate item In Items Into Sum(item.TotalAvgConumptionAmps(DoorDutyCycleFraction)) + + + return Amps; + } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricsUserInputsConfig.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricsUserInputsConfig.cs new file mode 100644 index 0000000000000000000000000000000000000000..ab170636b3575763369d6b9e5ec97222cc089556 --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ElectricsUserInputsConfig.cs @@ -0,0 +1,66 @@ +// Copyright 2017 European Union. +// Licensed under the EUPL (the 'Licence'); +// +// * You may not use this work except in compliance with the Licence. +// * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl +// * Unless required by applicable law or agreed to in writing, +// software distributed under the Licence is distributed on an "AS IS" basis, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the LICENSE.txt for the specific language governing permissions and limitations. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; +using TUGraz.VectoCore.BusAuxiliaries.Interfaces; +using TUGraz.VectoCore.BusAuxiliaries.Interfaces.DownstreamModules.Electrics; + +namespace Electrics +{ + public class ElectricsUserInputsConfig : IElectricsUserInputsConfig + { + public double PowerNetVoltage { get; set; } + public string AlternatorMap { get; set; } + public double AlternatorGearEfficiency { get; set; } + + public IElectricalConsumerList ElectricalConsumers { get; set; } + + public int DoorActuationTimeSecond { get; set; } + public double StoredEnergyEfficiency { get; set; } + + public IResultCard ResultCardIdle { get; set; } + public IResultCard ResultCardTraction { get; set; } + public IResultCard ResultCardOverrun { get; set; } + + public bool SmartElectrical { get; set; } + + public ElectricsUserInputsConfig(bool setToDefaults = false, VectoInputs vectoInputs = null/* TODO Change to default(_) if this is not a reference type */) + { + if (setToDefaults) + SetPropertiesToDefaults(vectoInputs); + } + + public void SetPropertiesToDefaults(VectoInputs vectoInputs) + { + DoorActuationTimeSecond = 4; + StoredEnergyEfficiency = 0.935; + AlternatorGearEfficiency = 0.92; + PowerNetVoltage = vectoInputs.PowerNetVoltage.Value(); + ResultCardIdle = new ResultCard(new List<SmartResult>()); + ResultCardOverrun = new ResultCard(new List<SmartResult>()); + ResultCardTraction = new ResultCard(new List<SmartResult>()); + SmartElectrical = false; + AlternatorMap = string.Empty; + } + } +} diff --git a/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ResultCard.cs b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ResultCard.cs new file mode 100644 index 0000000000000000000000000000000000000000..10376e08cb2b4b32591dbc358fd8c0344a118aee --- /dev/null +++ b/VECTOAux/BusAuxiliaries/DownstreamModules/Impl/Electrics/ResultCard.cs @@ -0,0 +1,182 @@ +// Copyright 2017 European Union. +// Licensed under the EUPL (the 'Licence'); +// +// * You may not use this work except in compliance with the Licence. +// * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl +// * Unless required by applicable law or agreed to in writing, +// software distributed under the Licence is distributed on an "AS IS" basis, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the LICENSE.txt for the specific language governing permissions and limitations. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualBasic; +using DownstreamModules.Electrics; +using TUGraz.VectoCommon.Utils; +using TUGraz.VectoCore.BusAuxiliaries.Interfaces.DownstreamModules.Electrics; + +namespace Electrics +{ + public class ResultCard : IResultCard + { + private List<SmartResult> _results; + + // Constructor + public ResultCard(List<SmartResult> results) + { + if (results == null) + throw new ArgumentException("A list of smart results must be supplied."); + + _results = results; + } + + + // Public class outputs + public List<SmartResult> Results + { + get { return _results; } + } + + public Ampere GetSmartCurrentResult(Ampere Amps) + { + if (_results.Count < 2) + return 10.SI<Ampere>(); + + return GetOrInterpolate(Amps.Value()).SI<Ampere>(); + } + + + // Helpers + /// <summary> + /// ''' Gets or interpolates value (A) + /// ''' </summary> + /// ''' <param name="amps"></param> + /// ''' <returns></returns> + /// ''' <remarks></remarks> + private double GetOrInterpolate(double amps) + { + double pre; + double post; + double dAmps; + double dSmartAmps; + double smartAmpsSlope; + double smartAmps; + double maxKey; + double minKey; + + maxKey = _results.Max().Amps; + minKey = _results.Min().Amps; + + SmartResult compareKey = new SmartResult(amps, 0); + + // Is on boundary check + if (_results.Contains(compareKey)) + return _results.OrderBy(x => x.Amps).First(x => x.Amps == compareKey.Amps).SmartAmps; + + // Is over map - Extrapolate + if (amps > maxKey) { + // get the entries before and after the supplied key + pre = (from a in _results + orderby a.Amps + where a.Amps < maxKey + select a).Last().Amps; + post = maxKey; + + // get the delta values + dAmps = post - pre; + dSmartAmps = (from da in _results + orderby da.Amps + where da.Amps == post + select da).First().SmartAmps - (from da in _results + orderby da.Amps + where da.Amps == pre + select da).First().SmartAmps; + + // calculate the slopes + smartAmpsSlope = dSmartAmps / dAmps; + + // calculate the new values + smartAmps = ((amps - post) * smartAmpsSlope) + (from da in _results + orderby da.Amps + where da.Amps == post + select da).First().SmartAmps; + + return smartAmps; + } + + // Is under map - Extrapolate + if (amps < minKey) { + // get the entries before and after the supplied key + // Post is the first entry and pre is the penultimate to first entry + post = minKey; + pre = (from k in _results + orderby k.Amps + where k.Amps > minKey + select k).First().Amps; + + // get the delta values + dAmps = post - pre; + dSmartAmps = (from da in _results + orderby da.Amps + where da.Amps == post + select da).First().SmartAmps - (from da in _results + orderby da.Amps + where da.Amps == pre + select da).First().SmartAmps; + + // calculate the slopes + smartAmpsSlope = dSmartAmps / dAmps; + + // calculate the new values + smartAmps = ((amps - post) * smartAmpsSlope) + (from da in _results + orderby da.Amps + where da.Amps == post + select da).First().SmartAmps; + + return smartAmps; + } + + // Is Inside map - Interpolate + + // get the entries before and after the supplied rpm + pre = (from m in _results + orderby m.Amps + where m.Amps < amps + select m).Last().Amps; + post = (from m in _results + where m.Amps > amps + select m).First().Amps; + + // get the delta values for rpm and the map values + dAmps = post - pre; + dSmartAmps = (from da in _results + orderby da.Amps + where da.Amps == post + select da).First().SmartAmps - (from da in _results + orderby da.Amps + where da.Amps == pre + select da).First().SmartAmps; + + // calculate the slopes + smartAmpsSlope = dSmartAmps / dAmps; + + // calculate the new values + smartAmps = ((amps - post) * smartAmpsSlope) + (from da in _results + orderby da.Amps + where da.Amps == post + select da).First().SmartAmps; + + return smartAmps; + } + } +} diff --git a/VECTOAux/BusAuxiliaries/Util/FilePathUtils.cs b/VECTOAux/BusAuxiliaries/Util/FilePathUtils.cs new file mode 100644 index 0000000000000000000000000000000000000000..4cbe6fe130a95f166552b2cbf18783a046c8f94c --- /dev/null +++ b/VECTOAux/BusAuxiliaries/Util/FilePathUtils.cs @@ -0,0 +1,148 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.VisualBasic; + +public class FilePathUtils +{ + + public static string Left(string str, int length) + { + return str.Substring(0, Math.Min(length, str.Length)); + } + + public static string Right(string value, int length) + { + if (string.IsNullOrEmpty(value)) return string.Empty; + + return value.Length <= length ? value : value.Substring(value.Length - length); + } + + public static bool ValidateFilePath(string filePath, string expectedExtension, ref string message) + { + char[] illegalFileNameCharacters = new[] { '<', '>', ':', '"', '/', '\\', '|', '?', '*', '~' }; + string detectedExtention = fileExtentionOnly(filePath); + string pathOnly = filePathOnly(filePath); + string fileNameOnlyWithExtension = fileNameOnly(filePath, true); + string fileNameOnlyNoExtension = fileNameOnly(filePath, false); + + // Is this filePath empty + if (filePath.Trim().Length == 0 || Right(filePath, 1) == @"\") { + message = "A filename cannot be empty"; + return false; + } + + + // Extension Expected, but not match + if (expectedExtension.Trim().Length > 0) { + if (string.Compare(expectedExtension, detectedExtention, true) != 0) { + message = string.Format("The file extension type does not match the expected type of {0}", expectedExtension); + return false; + } + } + + // Extension Not Expected, but was supplied + if (expectedExtension.Trim().Length > 0) { + if (detectedExtention.Length == 0) { + message = string.Format("No Extension was supplied, but an extension of {0}, this is not required", detectedExtention); + return false; + } + } + + + // Illegal characters + if (!fileNameLegal(fileNameOnlyWithExtension)) { + message = string.Format("The filenames have one or more illegal characters"); + return false; + } + + + message = "OK"; + return true; + } + + + public static bool fileNameLegal(string fileName) + { + char[] illegalFileNameCharacters = new[] { '<', '>', ':', '"', '/', '\\', '|', '?', '*', '~' }; + + + // Illegal characters + foreach (char ch in illegalFileNameCharacters) { + if (fileName.Contains(ch)) + return false; + } + return true; + } + + + public static string ResolveFilePath(string vectoPath, string filename) + { + + // No Vecto Path supplied + if (vectoPath == "") + return filename; + + // This is not relative + if (filename.Contains(@":\")) + + // Filepath is already absolute + return filename; + else + return Path.Combine(vectoPath, filename);// vectoPath & filename + } + + + /// <summary> + /// ''' File name without the path "C:\temp\TEST.txt" >> "TEST.txt" oder "TEST" + /// ''' </summary> + /// ''' <param name="filePath"></param> + /// ''' <param name="WithExtention"></param> + /// ''' <returns>Return file portion of the path, with or without the extension</returns> + /// ''' <remarks></remarks> + public static string fileNameOnly(string filePath, bool WithExtention) + { + int x; + x = filePath.LastIndexOf(@"\") + 1; + filePath = Right(filePath, filePath.Length - x); + if (!WithExtention) { + x = filePath.LastIndexOf("."); + if (x > 0) + filePath = Left(filePath, x); + } + return filePath; + } + + + /// <summary> + /// ''' Extension alone "C:\temp\TEST.txt" >> ".txt" + /// ''' </summary> + /// ''' <param name="filePath"></param> + /// ''' <returns>Extension alone Including the dot IE .EXT</returns> + /// ''' <remarks></remarks> + public static string fileExtentionOnly(string filePath) + { + int x; + x = filePath.LastIndexOf("."); + if (x == -1) + return ""; + else + return Right(filePath, filePath.Length - x); + } + + /// <summary> + /// ''' File Path alone "C:\temp\TEST.txt" >> "C:\temp\" + /// ''' "TEST.txt" >> "" + /// ''' </summary> + /// ''' <param name="filePath"></param> + /// ''' <returns>Filepath without the extension</returns> + /// ''' <remarks></remarks> + public static string filePathOnly(string filePath) + { + int x; + if (filePath == null || filePath.Length < 3 || filePath.Substring(1, 2) != @":\") + return ""; + x = filePath.LastIndexOf(@"\"); + return Left(filePath, x + 1); + } +}