Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Select Git revision
  • 99c9bc27cb87ea5ed687c8de73af23205d09f2ce
  • stable default
  • feat-fchv-bus
  • fix-h2-ice-bus
  • powertrains-multiple-axles
  • amdm3/develop
  • issue-1039
  • amdm3/main
  • test/nuget_publish
  • IEPC-experiments
  • amdm2/main
  • amdm2/develop
  • aptngearbox-not-auto
  • playground
  • official/main
  • official/develop
  • issue-templates
  • pdf-reports
  • HEV-timeruns-dev
  • timerun-empower-hybrids
  • timerun-pwheel-hybrids
  • Release/v5.0.3
  • Release/v5.0.1
  • Release/5.0.0-RC
  • Nuget/v0.11.4-DEV
  • Release/v0.11.4-DEV
  • Release/4.3.4-DEV
  • Release/4.3.3
  • Release/4.3.2-RC
  • Release/v4.3.0-DEV
  • Release/4.2.7
  • XMLConverterTool/4.2.6.0
  • Release/4.2.6-RC
  • Release/v4.2.5
  • Release/v4.2.3
  • Release/v4.2.2.3539-RC
  • Release/v4.2.1.3469
  • Release/v0.11.2.3456-DEV
  • Release/v4.2.0.3448-RC
  • Release/v4.1.3.3415
  • Release/v4.1.1.3413
41 results

DeclarationSegmentComplete3AxleBusesTest.cs

Blame
  • Forked from VECTO / VECTO Sim
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    DrivingCycleDataReader.cs 19.67 KiB
    /*
    * Copyright 2015 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/eupl5
    *
    * 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 Licence for the specific language governing permissions and 
    * limitations under the Licence.
    */
    
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.IO;
    using System.Linq;
    using TUGraz.VectoCore.Configuration;
    using TUGraz.VectoCore.Exceptions;
    using TUGraz.VectoCore.Models;
    using TUGraz.VectoCore.Models.SimulationComponent.Data;
    using TUGraz.VectoCore.Utils;
    
    namespace TUGraz.VectoCore.InputData.Reader
    {
    	public class DrivingCycleDataReader : LoggingObject
    	{
    		/// <summary>
    		/// Gets the appropriate cycle type for a cycle in a DataTable.
    		/// </summary>
    		/// <param name="cycleData">The cycle data.</param>
    		/// <returns></returns>
    		/// <exception cref="VectoException">CycleFile Format is unknown.</exception>
    		public static CycleType GetCycleType(DataTable cycleData)
    		{
    			var cols = cycleData.Columns.Cast<DataColumn>().Select(c => c.ColumnName).ToArray();
    
    			if (PWheelCycleDataParser.ValidateHeader(cols, false)) {
    				return CycleType.PWheel;
    			}
    			if (MeasuredSpeedDataParser.ValidateHeader(cols, false)) {
    				return CycleType.MeasuredSpeed;
    			}
    			if (EngineOnlyCycleDataParser.ValidateHeader(cols, false)) {
    				return CycleType.EngineOnly;
    			}
    			if (TimeBasedCycleDataParser.ValidateHeader(cols, false)) {
    				return CycleType.TimeBased;
    			}
    			if (DistanceBasedCycleDataParser.ValidateHeader(cols, false)) {
    				return CycleType.DistanceBased;
    			}
    			throw new VectoException("CycleFile Format is unknown.");
    		}
    
    		private static ICycleDataParser GetDataParser(CycleType type)
    		{
    			switch (type) {
    				case CycleType.EngineOnly:
    					return new EngineOnlyCycleDataParser();
    				case CycleType.TimeBased:
    					return new TimeBasedCycleDataParser();
    				case CycleType.DistanceBased:
    					return new DistanceBasedCycleDataParser();
    				case CycleType.PWheel:
    					return new PWheelCycleDataParser();
    				case CycleType.MeasuredSpeed:
    					return new MeasuredSpeedDataParser();
    				default:
    					throw new ArgumentOutOfRangeException("type");
    			}
    		}
    
    		/// <summary>
    		/// Reads a cycle from a file.
    		/// </summary>
    		/// <param name="fileName">Name of the file.</param>
    		/// <param name="type">The type.</param>
    		/// <returns></returns>
    		/// <exception cref="VectoException">ERROR while reading DrivingCycle File:  + ex.Message</exception>
    		public static DrivingCycleData ReadFromFile(string fileName, CycleType type)
    		{
    			try {
    				var stream = File.OpenRead(fileName);
    				return ReadFromStream(stream, type, Path.GetFileNameWithoutExtension(fileName));
    			} catch (Exception ex) {
    				throw new VectoException("ERROR while opening DrivingCycle File: " + ex.Message);
    			}
    		}
    
    		/// <summary>
    		/// Reads the cycle from a stream.
    		/// </summary>
    		/// <param name="stream">The stream.</param>
    		/// <param name="type">The type.</param>
    		/// <param name="name">the name of the cycle</param>
    		/// <returns></returns>
    		public static DrivingCycleData ReadFromStream(Stream stream, CycleType type, string name = null)
    		{
    			try {
    				return ReadFromDataTable(VectoCSVFile.ReadStream(stream), type, name);
    			} catch (Exception ex) {
    				throw new VectoException("ERROR while reading DrivingCycle Stream: " + ex.Message);
    			}
    		}
    
    		/// <summary>
    		/// Creates a cycle from a DataTable with automatic determination of Cycle Type.
    		/// </summary>
    		/// <param name="data">The cycle data.</param>
    		/// <param name="name">The name.</param>
    		/// <returns></returns>
    		public static DrivingCycleData ReadFromDataTable(DataTable data, string name)
    		{
    			if (data == null) {
    				Logger<DistanceBasedCycleDataParser>().Warn("Invalid data for DrivingCycle -- dataTable is null");
    				throw new VectoException("Invalid data for DrivingCycle -- dataTable is null");
    			}
    			return ReadFromDataTable(data, GetCycleType(data), name);
    		}
    
    		/// <summary>
    		/// Creates a cycle from a DataTable
    		/// </summary>
    		/// <param name="data">The cycle data.</param>
    		/// <param name="type">The type.</param>
    		/// <param name="name">The name.</param>
    		/// <returns></returns>
    		public static DrivingCycleData ReadFromDataTable(DataTable data, CycleType type, string name)
    		{
    			if (data == null) {
    				Logger<DistanceBasedCycleDataParser>().Warn("Invalid data for DrivingCycle -- dataTable is null");
    				throw new VectoException("Invalid data for DrivingCycle -- dataTable is null");
    			}
    			var entries = GetDataParser(type).Parse(data).ToList();
    
    			if (type == CycleType.DistanceBased) {
    				entries = FilterDrivingCycleEntries(entries);
    			}
    			var cycle = new DrivingCycleData {
    				Entries = entries,
    				CycleType = type,
    				Name = name
    			};
    			return cycle;
    		}
    
    
    		private static List<DrivingCycleData.DrivingCycleEntry> FilterDrivingCycleEntries(
    			List<DrivingCycleData.DrivingCycleEntry> entries)
    		{
    			var filtered = new List<DrivingCycleData.DrivingCycleEntry>();
    			var current = entries.First();
    			current.Altitude = 0.SI<Meter>();
    			filtered.Add(current);
    			var distance = current.Distance;
    			var altitude = current.Altitude;
    			for (var i = 0; i < entries.Count; i++) {
    				var entry = entries[i];
    				if (!entry.StoppingTime.IsEqual(0) && !entry.VehicleTargetSpeed.IsEqual(0)) {
    					throw new VectoException(
    						"Error in DrivingCycle: stop time specified but target-speed > 0! Distance: {0}, stop-time: {1}, target speed: {2}",
    						entry.Distance, entry.StoppingTime, entry.VehicleTargetSpeed);
    				}
    				if (entry.Distance < distance) {
    					throw new VectoException(
    						"Error in DrivingCycle: distance entry is smaller than last distance! last distance: {0}, current distance: {1} ",
    						distance, entry.Distance);
    				}
    				if (i > 0) {
    					altitude += (entry.Distance - distance) * entries[i - 1].RoadGradientPercent / 100.0;
    				}
    				entry.Altitude = altitude;
    				// if something changes in the cycle, add it to the filtered cycle but always add last entry
    				if (!CycleEntriesAreEqual(current, entry) || i == entries.Count - 1) {
    					entry.Altitude = altitude;
    					filtered.Add(entry);
    					current = entry;
    				}
    				if (!entry.StoppingTime.IsEqual(0) && entry.VehicleTargetSpeed.IsEqual(0)) {
    					// vehicle stops. duplicate current distance entry with 0 waiting time
    					var tmp = new DrivingCycleData.DrivingCycleEntry(entry) {
    						StoppingTime = 0.SI<Second>(),
    						VehicleTargetSpeed = i < entries.Count - 1 ? entries[i + 1].VehicleTargetSpeed : 0.SI<MeterPerSecond>()
    					};
    					filtered.Add(tmp);
    					current = tmp;
    				}
    
    				distance = entry.Distance;
    			}
    			Logger<DrivingCycleDataReader>()
    				.Info("Data loaded. Number of Entries: {0}, filtered Entries: {1}", entries.Count, filtered.Count);
    			entries = filtered;
    
    			AdjustDistanceAfterStop(entries);
    
    			return entries;
    		}
    
    		private static void AdjustDistanceAfterStop(List<DrivingCycleData.DrivingCycleEntry> entries)
    		{
    			var currentIt = entries.GetEnumerator();
    			var nextIt = entries.GetEnumerator();
    			nextIt.MoveNext();
    			while (currentIt.MoveNext() && nextIt.MoveNext()) {
    				if (currentIt.Current != null && !currentIt.Current.StoppingTime.IsEqual(0)) {
    					if (nextIt.Current != null) {
    						nextIt.Current.Distance = currentIt.Current.Distance;
    					}
    				}
    			}
    		}
    
    		private static bool CycleEntriesAreEqual(DrivingCycleData.DrivingCycleEntry first,
    			DrivingCycleData.DrivingCycleEntry second)
    		{
    			if (first.Distance.IsEqual(second.Distance)) {
    				return true;
    			}
    
    			if (first.VehicleTargetSpeed != second.VehicleTargetSpeed) {
    				return false;
    			}
    
    			if (!first.RoadGradient.IsEqual(second.RoadGradient, Constants.SimulationSettings.DrivingCycleRoadGradientTolerance)) {
    				return false;
    			}
    
    			if (!(first.StoppingTime.IsEqual(0) && second.StoppingTime.IsEqual(0))) {
    				return false;
    			}
    
    			if (first.AdditionalAuxPowerDemand != second.AdditionalAuxPowerDemand) {
    				return false;
    			}
    
    			if (first.AuxiliarySupplyPower.Count != second.AuxiliarySupplyPower.Count) {
    				return false;
    			}
    
    			if (!first.AuxiliarySupplyPower.SequenceEqual(second.AuxiliarySupplyPower)) {
    				return false;
    			}
    
    			return true;
    		}
    
    		// todo MK-2016-01-19: move fields to resource file
    		private static class Fields
    		{
    			public const string PWheel = "Pwheel";
    			public const string Distance = "s";
    			public const string Time = "t";
    			public const string VehicleSpeed = "v";
    			public const string RoadGradient = "grad";
    			public const string StoppingTime = "stop";
    			public const string AuxiliarySupplyPower = "Aux_";
    			public const string EngineSpeed = "n";
    			public const string Gear = "gear";
    			public const string AdditionalAuxPowerDemand = "Padd";
    			public const string AirSpeedRelativeToVehicle = "vair_res";
    			public const string WindYawAngle = "vair_beta";
    			public const string EnginePower = "Pe";
    			public const string EngineTorque = "Me";
    		}
    
    		#region DataParser
    
    		private interface ICycleDataParser
    		{
    			IEnumerable<DrivingCycleData.DrivingCycleEntry> Parse(DataTable table);
    		}
    
    		private class DistanceBasedCycleDataParser : ICycleDataParser
    		{
    			public IEnumerable<DrivingCycleData.DrivingCycleEntry> Parse(DataTable table)
    			{
    				ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray());
    
    				return table.Rows.Cast<DataRow>().Select(row => new DrivingCycleData.DrivingCycleEntry {
    					Distance = row.ParseDouble(Fields.Distance).SI<Meter>(),
    					VehicleTargetSpeed = row.ParseDouble(Fields.VehicleSpeed).KMPHtoMeterPerSecond(),
    					RoadGradientPercent = row.ParseDoubleOrGetDefault(Fields.RoadGradient),
    					RoadGradient = VectoMath.InclinationToAngle(row.ParseDoubleOrGetDefault(Fields.RoadGradient) / 100.0),
    					StoppingTime = row.ParseDouble(Fields.StoppingTime).SI<Second>(),
    					AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<Watt>(),
    					EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).RPMtoRad(),
    					Gear = row.ParseDoubleOrGetDefault(Fields.Gear),
    					AirSpeedRelativeToVehicle = row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle).KMPHtoMeterPerSecond(),
    					WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle),
    					AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row)
    				});
    			}
    
    			public static bool ValidateHeader(string[] header, bool throwExceptions = true)
    			{
    				var requiredCols = new[] {
    					Fields.VehicleSpeed,
    					Fields.Distance,
    					Fields.StoppingTime
    				};
    
    				var allowedCols = new[] {
    					Fields.Distance,
    					Fields.VehicleSpeed,
    					Fields.RoadGradient,
    					Fields.StoppingTime,
    					Fields.EngineSpeed,
    					Fields.Gear,
    					Fields.AdditionalAuxPowerDemand,
    					Fields.AirSpeedRelativeToVehicle,
    					Fields.WindYawAngle
    				};
    
    				header = header.Where(c => !c.StartsWith(Fields.AuxiliarySupplyPower)).ToArray();
    
    				var diff = header.Except(allowedCols).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) not allowed: " + string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				diff = requiredCols.Except(header).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) missing: " + string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^
    					header.Contains(Fields.WindYawAngle)) {
    					if (throwExceptions) {
    						throw new VectoException("Both Columns, '{0}' and '{1}', must be defined, or none of them.",
    							Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle);
    					}
    					return false;
    				}
    				return true;
    			}
    		}
    
    		private class TimeBasedCycleDataParser : ICycleDataParser
    		{
    			public IEnumerable<DrivingCycleData.DrivingCycleEntry> Parse(DataTable table)
    			{
    				ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray());
    
    				var entries = table.Rows.Cast<DataRow>().Select((row, index) => new DrivingCycleData.DrivingCycleEntry {
    					Time = row.ParseDoubleOrGetDefault(Fields.Time, index).SI<Second>(),
    					VehicleTargetSpeed = row.ParseDouble(Fields.VehicleSpeed).KMPHtoMeterPerSecond(),
    					RoadGradientPercent = row.ParseDoubleOrGetDefault(Fields.RoadGradient),
    					RoadGradient = VectoMath.InclinationToAngle(row.ParseDoubleOrGetDefault(Fields.RoadGradient) / 100.0),
    					AdditionalAuxPowerDemand = row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<Watt>(),
    					Gear = row.ParseDoubleOrGetDefault(Fields.Gear),
    					EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).RPMtoRad(),
    					AirSpeedRelativeToVehicle = row.ParseDoubleOrGetDefault(Fields.AirSpeedRelativeToVehicle).KMPHtoMeterPerSecond(),
    					WindYawAngle = row.ParseDoubleOrGetDefault(Fields.WindYawAngle),
    					AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row)
    				}).ToArray();
    
    				return entries;
    			}
    
    			public static bool ValidateHeader(string[] header, bool throwExceptions = true)
    			{
    				var allowedCols = new[] {
    					Fields.Time,
    					Fields.VehicleSpeed,
    					Fields.RoadGradient,
    					Fields.EngineSpeed,
    					Fields.Gear,
    					Fields.AdditionalAuxPowerDemand,
    					Fields.AirSpeedRelativeToVehicle,
    					Fields.WindYawAngle,
    				};
    
    				var requiredCols = new[] {
    					Fields.VehicleSpeed,
    				};
    
    
    				header = header.Where(c => !c.StartsWith(Fields.AuxiliarySupplyPower)).ToArray();
    
    				var diff = header.Except(allowedCols).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) not allowed: " + string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				diff = requiredCols.Except(header).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) missing: " + string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				if (header.Contains(Fields.AirSpeedRelativeToVehicle) ^
    					header.Contains(Fields.WindYawAngle)) {
    					if (throwExceptions) {
    						throw new VectoException("Both Columns, '{0}' and '{1}', must be defined, or none of them.",
    							Fields.AirSpeedRelativeToVehicle, Fields.WindYawAngle);
    					}
    					return false;
    				}
    				return true;
    			}
    		}
    
    		private class EngineOnlyCycleDataParser : ICycleDataParser
    		{
    			public IEnumerable<DrivingCycleData.DrivingCycleEntry> Parse(DataTable table)
    			{
    				ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray());
    
    				var absTime = 0.SI<Second>();
    				foreach (DataRow row in table.Rows) {
    					var entry = new DrivingCycleData.DrivingCycleEntry {
    						EngineSpeed = row.ParseDoubleOrGetDefault(Fields.EngineSpeed).RPMtoRad(),
    						AdditionalAuxPowerDemand =
    							row.ParseDoubleOrGetDefault(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<Watt>(),
    						AuxiliarySupplyPower = AuxSupplyPowerReader.Read(row)
    					};
    
    					if (row.Table.Columns.Contains(Fields.EngineTorque)) {
    						if (row.Field<string>(Fields.EngineTorque).Equals("<DRAG>")) {
    							entry.Drag = true;
    						} else {
    							entry.EngineTorque = row.ParseDouble(Fields.EngineTorque).SI<NewtonMeter>();
    						}
    					} else {
    						if (row.Field<string>(Fields.EnginePower).Equals("<DRAG>")) {
    							entry.Drag = true;
    						} else {
    							entry.EngineTorque = row.ParseDouble(Fields.EnginePower).SI().Kilo.Watt.Cast<Watt>() / entry.EngineSpeed;
    						}
    					}
    					entry.Time = absTime;
    					absTime += 1.SI<Second>();
    
    					yield return entry;
    				}
    			}
    
    			public static bool ValidateHeader(string[] header, bool throwExceptions = true)
    			{
    				var allowedCols = new[] {
    					Fields.EngineTorque,
    					Fields.EnginePower,
    					Fields.EngineSpeed,
    					Fields.AdditionalAuxPowerDemand
    				};
    
    				var requiredCols = new[] {
    					Fields.EngineSpeed
    				};
    
    				var diff = header.Except(allowedCols).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) not allowed: " + string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				diff = requiredCols.Except(header).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) missing: " + string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				if (!(header.Contains(Fields.EngineTorque) || header.Contains(Fields.EnginePower))) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) missing: Either column '{0}' or column '{1}' must be defined.",
    							Fields.EngineTorque, Fields.EnginePower);
    					}
    					return false;
    				}
    
    				if (header.Contains(Fields.EngineTorque) && header.Contains(Fields.EnginePower)) {
    					Logger<DrivingCycleDataReader>().Warn("Found column '{0}' and column '{1}': Only column '{0}' will be used.",
    						Fields.EngineTorque, Fields.EnginePower);
    				}
    				return true;
    			}
    		}
    
    		private class PWheelCycleDataParser : ICycleDataParser
    		{
    			public IEnumerable<DrivingCycleData.DrivingCycleEntry> Parse(DataTable table)
    			{
    				ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray());
    
    				var entries = table.Rows.Cast<DataRow>().Select(row => new DrivingCycleData.DrivingCycleEntry {
    					Time = row.ParseDouble(Fields.Time).SI<Second>(),
    					PWheel = row.ParseDouble(Fields.PWheel).SI().Kilo.Watt.Cast<Watt>(),
    					Gear = row.ParseDouble(Fields.Gear),
    					EngineSpeed = row.ParseDouble(Fields.EngineSpeed).RPMtoRad(),
    					AdditionalAuxPowerDemand = row.ParseDouble(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<Watt>(),
    				}).ToArray();
    
    				return entries;
    			}
    
    			public static bool ValidateHeader(string[] header, bool throwExceptions = true)
    			{
    				var allowedCols = new[] {
    					Fields.Time,
    					Fields.PWheel,
    					Fields.Gear,
    					Fields.EngineSpeed,
    					Fields.AdditionalAuxPowerDemand
    				};
    
    				var diff = header.Except(allowedCols).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) '{0}' not allowed.", string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				diff = allowedCols.Except(header).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) '{0}' missing.", string.Join(", ", diff));
    					}
    					return false;
    				}
    				return true;
    			}
    		}
    
    		private class MeasuredSpeedDataParser : ICycleDataParser
    		{
    			public IEnumerable<DrivingCycleData.DrivingCycleEntry> Parse(DataTable table)
    			{
    				ValidateHeader(table.Columns.Cast<DataColumn>().Select(col => col.ColumnName).ToArray());
    
    				var entries = table.Rows.Cast<DataRow>().Select(row => new DrivingCycleData.DrivingCycleEntry {
    					Time = row.ParseDouble(Fields.Time).SI<Second>(),
    					VehicleTargetSpeed = row.ParseDouble(Fields.VehicleSpeed).KMPHtoMeterPerSecond(),
    					RoadGradient = VectoMath.InclinationToAngle(row.ParseDoubleOrGetDefault(Fields.RoadGradient) / 100.0),
    					Gear = row.ParseDouble(Fields.Gear),
    					AdditionalAuxPowerDemand = row.ParseDouble(Fields.AdditionalAuxPowerDemand).SI().Kilo.Watt.Cast<Watt>(),
    				}).ToArray();
    
    				return entries;
    			}
    
    			public static bool ValidateHeader(string[] header, bool throwExceptions = true)
    			{
    				var allowedCols = new[] {
    					Fields.Time,
    					Fields.VehicleSpeed,
    					Fields.RoadGradient,
    					Fields.Gear,
    					Fields.AdditionalAuxPowerDemand
    				};
    
    				var requiredCols = allowedCols;
    
    				var diff = header.Except(allowedCols).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) '{0}' not allowed.", string.Join(", ", diff));
    					}
    					return false;
    				}
    
    				diff = requiredCols.Except(header).ToList();
    				if (diff.Any()) {
    					if (throwExceptions) {
    						throw new VectoException("Column(s) '{0}' missing.", string.Join(", ", diff));
    					}
    					return false;
    				}
    				return true;
    			}
    		}
    	}
    
    	#endregion
    }