From 3fb4da5f7d3007dc37f6aae1ca5d86c04ca422ee Mon Sep 17 00:00:00 2001 From: Vitaliy Dobriyan Date: Fri, 27 Sep 2024 14:58:40 +0300 Subject: [PATCH] rework of core --- README.md | 29 +- src/{ChaosSoft.sln => ChaosSoft.Core.sln} | 0 src/ChaosSoft.Core/ChaosSoft.Core.csproj | 4 +- src/ChaosSoft.Core/ChaosSoft.Core.nuspec | 7 +- src/ChaosSoft.Core/Data/DataPoint.cs | 63 +-- src/ChaosSoft.Core/Data/DataSeries.cs | 323 ++++++------- src/ChaosSoft.Core/Data/SourceData.cs | 219 +++++---- src/ChaosSoft.Core/DataUtils/Matrix.cs | 426 +++++++++--------- src/ChaosSoft.Core/DataUtils/Vector.cs | 330 +++++++------- src/ChaosSoft.Core/IHasDescription.cs | 12 + src/ChaosSoft.Core/IHasName.cs | 12 + src/ChaosSoft.Core/IO/BinaryDataFileReader.cs | 30 ++ src/ChaosSoft.Core/IO/BinaryDataFileWriter.cs | 27 ++ src/ChaosSoft.Core/IO/DataReader.cs | 96 ---- src/ChaosSoft.Core/IO/DataWriter.cs | 79 ---- src/ChaosSoft.Core/IO/FileUtils.cs | 33 ++ src/ChaosSoft.Core/IO/Format.cs | 48 -- src/ChaosSoft.Core/IO/IDataReader.cs | 14 + src/ChaosSoft.Core/IO/IDataWriter.cs | 14 + src/ChaosSoft.Core/IO/Model3D.cs | 62 --- src/ChaosSoft.Core/IO/PlainTextFileReader.cs | 112 +++++ src/ChaosSoft.Core/IO/PlainTextFileWriter.cs | 56 +++ src/ChaosSoft.Core/IO/Sound.cs | 121 ----- src/ChaosSoft.Core/Logging/ConsoleLogger.cs | 51 +++ src/ChaosSoft.Core/Logging/ILogger.cs | 46 ++ src/ChaosSoft.Core/Logging/Log.cs | 88 ++++ src/ChaosSoft.Core/Logging/LogLevel.cs | 32 ++ src/ChaosSoft.Core/NumFormat.cs | 100 ++++ 28 files changed, 1340 insertions(+), 1094 deletions(-) rename src/{ChaosSoft.sln => ChaosSoft.Core.sln} (100%) create mode 100644 src/ChaosSoft.Core/IHasDescription.cs create mode 100644 src/ChaosSoft.Core/IHasName.cs create mode 100644 src/ChaosSoft.Core/IO/BinaryDataFileReader.cs create mode 100644 src/ChaosSoft.Core/IO/BinaryDataFileWriter.cs delete mode 100644 src/ChaosSoft.Core/IO/DataReader.cs delete mode 100644 src/ChaosSoft.Core/IO/DataWriter.cs create mode 100644 src/ChaosSoft.Core/IO/FileUtils.cs delete mode 100644 src/ChaosSoft.Core/IO/Format.cs create mode 100644 src/ChaosSoft.Core/IO/IDataReader.cs create mode 100644 src/ChaosSoft.Core/IO/IDataWriter.cs delete mode 100644 src/ChaosSoft.Core/IO/Model3D.cs create mode 100644 src/ChaosSoft.Core/IO/PlainTextFileReader.cs create mode 100644 src/ChaosSoft.Core/IO/PlainTextFileWriter.cs delete mode 100644 src/ChaosSoft.Core/IO/Sound.cs create mode 100644 src/ChaosSoft.Core/Logging/ConsoleLogger.cs create mode 100644 src/ChaosSoft.Core/Logging/ILogger.cs create mode 100644 src/ChaosSoft.Core/Logging/Log.cs create mode 100644 src/ChaosSoft.Core/Logging/LogLevel.cs create mode 100644 src/ChaosSoft.Core/NumFormat.cs diff --git a/README.md b/README.md index 5ee2fc0..6aed24e 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,22 @@ Contains: * Custom data series implementation * Data I/O * Helpers for Matrixes and Vectors +* Logger instance (_+ default ConsoleLogger implementation_) ## Code examples -> Read source data from file +### Read source data from file ```csharp using ChaosSoft.Core.Data; - +using ChaosSoft.Core.IO; string fileName = "some_data.dat"; int linesToSkip = 2; int linesToRead = 500; // get source data from file -var data = new SourceData(fileName, linesToSkip, linesToRead); +IDataReader dataReader = new PlainTextFileReader(linesToSkip, linesToRead); +SourceData data = new SourceData(dataReader, linesToRead); // assume file has two columns (1st - timestamp, 2nd - series) int seriesColumn = 1; @@ -39,7 +41,7 @@ data.SetTimeSeries( var amplitude = data.TimeSeries.Amplitude.Y; //X property contains amplitude of X values (timestamp) ``` -> Work with matrixes +### Work with matrixes ```csharp using ChaosSoft.Core.DataUtils; @@ -50,7 +52,7 @@ var matrix = Matrix.Create(3, 5, 5.3); var column = Matrix.GetColumn(matrix, 2); ``` -> Work with vectors +### Work with vectors ```csharp using ChaosSoft.Core.DataUtils; @@ -59,4 +61,21 @@ var vector = Vector.CreateUniform(5, -9, 3); // find vector's Max absolute value var maxAbs = Vector.MaxAbs(vector); +``` + +### Numbers formatting +By default numbers formatting is set to `"0.#######"` with `InvariantCulture` + +```csharp +// To apply user formatting used by default (for example F2) +NumFormat.CurrentFormat = "F2"; + +// To apply culture used by default (for example en-US) +NumFormat.CurrentCulture = new CultureInfo("en-US"); + +// To reset formatting defaults +NumFormat.Reset(); + +// To set default precision (for example 11 decimal digits) +NumFormat.SetPrecision(11); ``` \ No newline at end of file diff --git a/src/ChaosSoft.sln b/src/ChaosSoft.Core.sln similarity index 100% rename from src/ChaosSoft.sln rename to src/ChaosSoft.Core.sln diff --git a/src/ChaosSoft.Core/ChaosSoft.Core.csproj b/src/ChaosSoft.Core/ChaosSoft.Core.csproj index 6f8587a..20a523f 100644 --- a/src/ChaosSoft.Core/ChaosSoft.Core.csproj +++ b/src/ChaosSoft.Core/ChaosSoft.Core.csproj @@ -1,8 +1,10 @@  - net6.0;netstandard2.0 + netstandard2.0 + 10 version=$(Version) + true diff --git a/src/ChaosSoft.Core/ChaosSoft.Core.nuspec b/src/ChaosSoft.Core/ChaosSoft.Core.nuspec index ba2d061..aea41d1 100644 --- a/src/ChaosSoft.Core/ChaosSoft.Core.nuspec +++ b/src/ChaosSoft.Core/ChaosSoft.Core.nuspec @@ -6,7 +6,7 @@ ChaosSoft.Core Vitaliy Dobriyan Apache-2.0 - Copyright (c) Vitaliy Dobriyan 2023 + Copyright (c) Vitaliy Dobriyan 2024 https://github.com/chaossoftware/core images\icon.png @@ -15,11 +15,10 @@ This package includes data and timeseries related objects and data specific IO operations, some math helpers. - First release: SourceData and DataSeries objects, date series specific IO operations, helpers for vectors and matrixes. + Rework of IO, added common interfaces, removed 3d and sound output classes, added logger math io timeseries - @@ -27,7 +26,5 @@ - - \ No newline at end of file diff --git a/src/ChaosSoft.Core/Data/DataPoint.cs b/src/ChaosSoft.Core/Data/DataPoint.cs index acbace5..b1d0b48 100644 --- a/src/ChaosSoft.Core/Data/DataPoint.cs +++ b/src/ChaosSoft.Core/Data/DataPoint.cs @@ -1,38 +1,43 @@ -using ChaosSoft.Core.IO; +namespace ChaosSoft.Core.Data; -namespace ChaosSoft.Core.Data +/// +/// Represents 2D point object. +/// +public readonly struct DataPoint { /// - /// Represents 2D point object. + /// Initializes a new instance of the class for specific X and Y coordinates. /// - public readonly struct DataPoint + /// x coordinate + /// y coordinate + public DataPoint(double x, double y) { - /// - /// Initializes a new instance of the class for specific X and Y coordinates. - /// - /// x coordinate - /// y coordinate - public DataPoint(double x, double y) - { - X = x; - Y = y; - } + X = x; + Y = y; + } - /// - /// Gets X coordinate. - /// - public double X { get; } + /// + /// Gets X coordinate. + /// + public double X { get; } - /// - /// Gets Y coordinate. - /// - public double Y { get; } + /// + /// Gets Y coordinate. + /// + public double Y { get; } - /// - /// Gets string representation of the data point (G6 number format is used). - /// - /// - public override string ToString() => - $"[{Format.General(X)}, {Format.General(Y)}]"; - } + /// + /// Gets string representation of the data point ( format is used). + /// + /// string representation + public override string ToString() => + $"[{X.ToString(NumFormat.CurrentFormat)}, {Y.ToString(NumFormat.CurrentFormat)}]"; + + /// + /// Gets string representation of the data point with specified number format. + /// + /// number format to use + /// string representation + public string ToString(string format) => + $"[{X.ToString(format)}, {Y.ToString(format)}]"; } diff --git a/src/ChaosSoft.Core/Data/DataSeries.cs b/src/ChaosSoft.Core/Data/DataSeries.cs index e0ddc58..ef8b499 100644 --- a/src/ChaosSoft.Core/Data/DataSeries.cs +++ b/src/ChaosSoft.Core/Data/DataSeries.cs @@ -1,211 +1,220 @@ using ChaosSoft.Core.DataUtils; -using ChaosSoft.Core.IO; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text; -namespace ChaosSoft.Core.Data +namespace ChaosSoft.Core.Data; + +/// +/// Represents data series (series of ).
+/// Properties calculation is optimized (re-calculated only if data changed). +///
+public sealed class DataSeries : IHasName { + private int length; + private DataPoint max; + private DataPoint min; + private DataPoint amplitude; + private double[] xValues; + private double[] yValues; + private bool outdated; + /// - /// Represents data series (series of ).
- /// Properties calculation is optimized (re-calculated only if data changed). + /// Initializes a new instance of the class with empty data. ///
- public sealed class DataSeries + public DataSeries() { - private int length; - private DataPoint max; - private DataPoint min; - private DataPoint amplitude; - private double[] xValues; - private double[] yValues; - private bool outdated; - - /// - /// Initializes a new instance of the class with empty data. - /// - public DataSeries() - { - DataPoints = new List(); - length = 0; - min = new DataPoint(0, 0); - max = new DataPoint(0, 0); - amplitude = new DataPoint(0, 0); - outdated = true; - } + DataPoints = new List(); + length = 0; + min = new DataPoint(0, 0); + max = new DataPoint(0, 0); + amplitude = new DataPoint(0, 0); + outdated = true; + } - /// - /// Initializes a new instance of the class for specific one-dimension timeseries.
- /// In this case timestep is equal to 1. - ///
- /// source timeseries - public DataSeries(double[] timeSeries) : this() + /// + /// Initializes a new instance of the class for specific one-dimension timeseries.
+ /// In this case timestep is equal to 1. + ///
+ /// source timeseries + public DataSeries(double[] timeSeries) : this() + { + foreach (double val in timeSeries) { - foreach (double val in timeSeries) - { - AddDataPoint(val); - } + AddDataPoint(val); } + } - /// - /// Gets data series name (if any). - /// - public string Name { get; set; } + /// + /// Gets data series name (if any). + /// + public string Name { get; set; } - /// - /// Gets list of data points. - /// - public List DataPoints { get; } + /// + /// Gets list of data points. + /// + public List DataPoints { get; } - /// - /// Gets Max Y value of data series (re-calculated only of series changed). - /// - public DataPoint Max + /// + /// Gets Max Y value of data series (re-calculated only of series changed). + /// + public DataPoint Max + { + get { - get + if (outdated) { - if (outdated) - { - UpdateProperties(); - } - - return max; + UpdateProperties(); } + + return max; } + } - /// - /// Gets Min Y value of data series (re-calculated only of series changed). - /// - public DataPoint Min + /// + /// Gets Min Y value of data series (re-calculated only of series changed). + /// + public DataPoint Min + { + get { - get + if (outdated) { - if (outdated) - { - UpdateProperties(); - } - - return min; + UpdateProperties(); } + + return min; } + } - /// - /// Gets amplitude of Y values of data series (re-calculated only of series changed). - /// - public DataPoint Amplitude + /// + /// Gets amplitude of Y values of data series (re-calculated only of series changed). + /// + public DataPoint Amplitude + { + get { - get + if (outdated) { - if (outdated) - { - UpdateProperties(); - } - - return amplitude; + UpdateProperties(); } + + return amplitude; } + } - /// - /// Gets length of data series (re-calculated only of series changed). - /// - public int Length + /// + /// Gets length of data series (re-calculated only of series changed). + /// + public int Length + { + get { - get + if (outdated) { - if (outdated) - { - UpdateProperties(); - } - - return length; + UpdateProperties(); } + + return length; } + } - /// - /// Gets array of X values (re-calculated only of series changed). - /// - public double[] XValues + /// + /// Gets array of X values (re-calculated only of series changed). + /// + public double[] XValues + { + get { - get + if (outdated) { - if (outdated) - { - xValues = (from dp in DataPoints select dp.X).ToArray(); - } - - return xValues; + xValues = (from dp in DataPoints select dp.X).ToArray(); } + + return xValues; } + } - /// - /// Gets array of Y values (re-calculated only of series changed). - /// - public double[] YValues + /// + /// Gets array of Y values (re-calculated only of series changed). + /// + public double[] YValues + { + get { - get + if (outdated) { - if (outdated) - { - yValues = (from dp in DataPoints select dp.Y).ToArray(); - } - - return yValues; + yValues = (from dp in DataPoints select dp.Y).ToArray(); } - } - /// - /// Adds new based on two coordinates. - /// - /// X coordinate - /// Y coordinate - public void AddDataPoint(double x, double y) - { - DataPoints.Add(new DataPoint(x, y)); - outdated = true; + return yValues; } + } - /// - /// Adds new based on Y coordinate. - /// (x coordinate is calculated as current length of series) - /// - /// Y coordinate - public void AddDataPoint(double y) - { - DataPoints.Add(new DataPoint(DataPoints.Count, y)); - outdated = true; - } + /// + /// Adds new based on two coordinates. + /// + /// X coordinate + /// Y coordinate + public void AddDataPoint(double x, double y) + { + DataPoints.Add(new DataPoint(x, y)); + outdated = true; + } - /// - /// Gets string representation of the series with specified number format. - /// - /// number format to use - /// - public string ToString(string format) - { - StringBuilder sb = new StringBuilder(); + /// + /// Adds new based on Y coordinate. + /// (x coordinate is calculated as current length of series) + /// + /// Y coordinate + public void AddDataPoint(double y) + { + DataPoints.Add(new DataPoint(DataPoints.Count, y)); + outdated = true; + } - foreach (DataPoint dp in DataPoints) - { - sb.AppendLine($"{dp.X.ToString(format, CultureInfo.InvariantCulture)}\t{dp.Y.ToString(format, CultureInfo.InvariantCulture)}"); - } + /// + /// Gets string representation of the series with specified number format. + /// + /// number format to use + /// columns delimiter + /// + public string ToString(string format, string columnDelimiter) + { + StringBuilder sb = new(); - return sb.ToString(); + foreach (DataPoint dp in DataPoints) + { + sb.Append(NumFormat.Format(dp.X, format)) + .Append(columnDelimiter) + .AppendLine(NumFormat.Format(dp.Y, format)); } - /// - /// Gets string representation of the series (G6 number format is used). - /// - /// - public override string ToString() => - ToString(Format.Default); + return sb.ToString(); + } - private void UpdateProperties() - { - length = DataPoints.Count; - min = new DataPoint(Vector.Min(XValues), Vector.Min(YValues)); - max = new DataPoint(Vector.Max(XValues), Vector.Max(YValues)); - amplitude = new DataPoint(max.X - min.X, max.Y - min.Y); - outdated = false; - } + /// + /// Gets string representation of the series with specified number format and TAB delimiter. + /// + /// number format to use + /// + public string ToString(string format) => + ToString(format, "\t"); + + /// + /// Gets string representation of the series with + /// number format and TAB delimiter. + /// + /// + public override string ToString() => + ToString(NumFormat.CurrentFormat, "\t"); + + private void UpdateProperties() + { + length = DataPoints.Count; + min = new DataPoint(Vector.Min(XValues), Vector.Min(YValues)); + max = new DataPoint(Vector.Max(XValues), Vector.Max(YValues)); + amplitude = new DataPoint(max.X - min.X, max.Y - min.Y); + outdated = false; } } diff --git a/src/ChaosSoft.Core/Data/SourceData.cs b/src/ChaosSoft.Core/Data/SourceData.cs index 1732e3b..c843e6d 100644 --- a/src/ChaosSoft.Core/Data/SourceData.cs +++ b/src/ChaosSoft.Core/Data/SourceData.cs @@ -1,130 +1,127 @@ -using System.IO; +using System; +using System.IO; using ChaosSoft.Core.IO; -namespace ChaosSoft.Core.Data +namespace ChaosSoft.Core.Data; + +/// +/// Represents source file data with any number of data colmns. +/// +public sealed class SourceData { + private readonly double[][] _dataColumns; + /// - /// Represents source file data with any number of data colmns. + /// Initializes a new instance of the class based on specified data reader instance and + /// source. /// - public sealed class SourceData + /// + /// + public SourceData(IDataReader dataReader, string filePath) { - private readonly double[][] _dataColumns; - - /// - /// Initializes a new instance of the class based on source file and data read range. - /// - /// path to source file - /// amount of lines to skip for reading - /// amount of lines to read - public SourceData(string filePath, int startOffset, int readLines) : - this(DataReader.ReadColumnsFromFile(filePath, startOffset, readLines), filePath) - { - } + _dataColumns = dataReader.ReadColumnsFromFile(filePath); - /// - /// Initializes a new instance of the class based on source file - /// (all lines will be read). - /// - /// path to source file - public SourceData(string filePath) : - this(DataReader.ReadColumnsFromFile(filePath, 0, 0), filePath) + if (!string.IsNullOrEmpty(filePath)) { + FileName = Path.GetFileName(filePath); + Folder = Path.GetDirectoryName(filePath); } + + LinesCount = _dataColumns[0].Length; + ColumnsCount = _dataColumns.Length; + + SetTimeSeries(0, 0, LinesCount, 1, false); + } + + /// + /// Gets count of lines in source data. + /// + public int LinesCount { get; } + + /// + /// Gets count of columns in source data. + /// + public int ColumnsCount { get; } + + /// + /// Gets source file name. + /// + public string FileName { get; } + + /// + /// Gets source folder. + /// + public string Folder { get; } + + /// + /// Gets current data series. + /// + public DataSeries TimeSeries { get; private set; } - private SourceData(double[][] data, string filePath) + /// + /// Gets current data step size. + /// + public double Step { get; private set; } + + /// + /// Gets source data from double[][] serializaed to a file. + /// + /// path to file with serializaed data + /// + public static SourceData FromBytesFile(string filePath) => + new(new BinaryDataFileReader(), filePath); + + /// + /// Gets source data from plain text file using default reader constructor. + /// + /// path to file with text data + /// + public static SourceData FromPlainTextFile(string filePath) => + new(new PlainTextFileReader(), filePath); + + /// + /// Set current time series from column and data range. + /// + /// index of column + /// start point for time series + /// end point for time series + /// use each N point from range + /// specify whether to use first column values as time or not + public void SetTimeSeries(int colIndex, int startPoint, int endPoint, int pts, bool timeInFirstColumn) + { + int max = (endPoint - startPoint) / pts; + TimeSeries = new DataSeries(); + + for (int i = 0; i < max; i++) { - _dataColumns = data; - - if (!string.IsNullOrEmpty(filePath)) - { - FileName = Path.GetFileName(filePath); - Folder = Path.GetDirectoryName(filePath); - } - - LinesCount = _dataColumns[0].Length; - ColumnsCount = _dataColumns.Length; - - SetTimeSeries(0, 0, LinesCount, 1, false); + int row = startPoint + i * pts; + var x = timeInFirstColumn ? _dataColumns[0][row] : i + 1; + var y = _dataColumns[colIndex][row]; + TimeSeries.AddDataPoint(x, y); } - /// - /// Gets count of lines in source data. - /// - public int LinesCount { get; } - - /// - /// Gets count of columns in source data. - /// - public int ColumnsCount { get; } - - /// - /// Gets source file name. - /// - public string FileName { get; } - - /// - /// Gets source folder. - /// - public string Folder { get; } - - /// - /// Gets current data series. - /// - public DataSeries TimeSeries { get; private set; } - - /// - /// Gets current data step size. - /// - public double Step { get; private set; } - - /// - /// Gets source data from double[][] serializaed to a file. - /// - /// path to file with serializaed data - /// - public static SourceData FromBytesFile(string filePath) + try { - double[][] data = DataReader.ReadColumnsFromByteFile(filePath); - return new SourceData(data, filePath); + Step = timeInFirstColumn ? _dataColumns[0][pts] - _dataColumns[0][0] : double.NaN; } - - /// - /// Set current time series from column and data range. - /// - /// index of column - /// start point for time series - /// end point for time series - /// use each N point from range - /// specify whether to use first column values as time or not - public void SetTimeSeries(int colIndex, int startPoint, int endPoint, int pts, bool timeInFirstColumn) + catch (IndexOutOfRangeException) { - int max = (endPoint - startPoint) / pts; - TimeSeries = new DataSeries(); - - for (int i = 0; i < max; i++) - { - int row = startPoint + i * pts; - var x = timeInFirstColumn ? _dataColumns[0][row] : i + 1; - var y = _dataColumns[colIndex][row]; - TimeSeries.AddDataPoint(x, y); - } - - Step = TimeSeries.DataPoints[1].X - TimeSeries.DataPoints[0].X; + Step = double.NaN; } - - /// - /// Gets data column with specific index. - /// - /// column index in data file - /// - public double[] GetColumn(int index) => - _dataColumns[index]; - - /// - /// Gets main information on source data file. - /// - /// - public override string ToString() => - $"File: {FileName}\nLines: {LinesCount}\nColumns: {ColumnsCount}"; } + + /// + /// Gets data column with specific index. + /// + /// column index in data file + /// + public double[] GetColumn(int index) => + _dataColumns[index]; + + /// + /// Gets main information on source data file. + /// + /// + public override string ToString() => + $"File: {FileName} (lines: {LinesCount}, columns: {ColumnsCount})"; } diff --git a/src/ChaosSoft.Core/DataUtils/Matrix.cs b/src/ChaosSoft.Core/DataUtils/Matrix.cs index b5e587d..a8fabb9 100644 --- a/src/ChaosSoft.Core/DataUtils/Matrix.cs +++ b/src/ChaosSoft.Core/DataUtils/Matrix.cs @@ -1,285 +1,283 @@ using System; -namespace ChaosSoft.Core.DataUtils +namespace ChaosSoft.Core.DataUtils; + +/// +/// Common operations on matrixes. +/// +public static class Matrix { /// - /// Common operations on matrixes. + /// Creates matrix with specific dimensions lengths. /// - public static class Matrix + /// rows count + /// columns count + /// initial value to fill with + /// + public static double[,] Create(int rows, int columns, double initialValue) { - /// - /// Creates matrix with specific dimensions lengths. - /// - /// rows count - /// columns count - /// initial value to fill with - /// - public static double[,] Create(int rows, int columns, double initialValue) + double[,] matrix = new double[rows, columns]; + + if (initialValue != 0) { - double[,] matrix = new double[rows, columns]; + FillWith(matrix, initialValue); + } - if (initialValue != 0) - { - FillWith(matrix, initialValue); - } + return matrix; + } - return matrix; - } + /// + /// Creates matrix with specific dimensions lengths. + /// + /// rows count + /// columns count + /// initial value to fill with + /// + public static int[,] Create(int rows, int columns, int initialValue) + { + int[,] matrix = new int[rows, columns]; - /// - /// Creates matrix with specific dimensions lengths. - /// - /// rows count - /// columns count - /// initial value to fill with - /// - public static int[,] Create(int rows, int columns, int initialValue) + if (initialValue != 0) { - int[,] matrix = new int[rows, columns]; + FillWith(matrix, initialValue); + } - if (initialValue != 0) - { - FillWith(matrix, initialValue); - } + return matrix; + } - return matrix; - } + /// + /// Fills matrix with specific value. + /// + /// matrix to fill + /// value to fill with + public static void FillWith(double[,] matrix, double value) + { + int i, j; + int xLen = matrix.GetLength(0); + int yLen = matrix.GetLength(1); - /// - /// Fills matrix with specific value. - /// - /// matrix to fill - /// value to fill with - public static void FillWith(double[,] matrix, double value) + for (i = 0; i < xLen; i++) { - int i, j; - int xLen = matrix.GetLength(0); - int yLen = matrix.GetLength(1); - - for (i = 0; i < xLen; i++) + for (j = 0; j < yLen; j++) { - for (j = 0; j < yLen; j++) - { - matrix[i, j] = value; - } + matrix[i, j] = value; } } + } - /// - /// Fills matrix with specific value. - /// - /// matrix to fill - /// value to fill with - public static void FillWith(int[,] matrix, int value) - { - int i, j; - int xLen = matrix.GetLength(0); - int yLen = matrix.GetLength(1); + /// + /// Fills matrix with specific value. + /// + /// matrix to fill + /// value to fill with + public static void FillWith(int[,] matrix, int value) + { + int i, j; + int xLen = matrix.GetLength(0); + int yLen = matrix.GetLength(1); - for (i = 0; i < xLen; i++) + for (i = 0; i < xLen; i++) + { + for (j = 0; j < yLen; j++) { - for (j = 0; j < yLen; j++) - { - matrix[i, j] = value; - } + matrix[i, j] = value; } } + } - /// - /// Gets column at specific position from matrix. - /// - /// source matrix - /// column index - /// column as a vector - public static double[] GetColumn(double[,] matrix, int index) + /// + /// Gets column at specific position from matrix. + /// + /// source matrix + /// column index + /// column as a vector + public static double[] GetColumn(double[,] matrix, int index) + { + int length = matrix.GetLength(1); + double[] row = new double[length]; + + for (int i = 0; i < length; i++) { - int length = matrix.GetLength(1); - double[] row = new double[length]; + row[i] = matrix[index, i]; + } - for (int i = 0; i < length; i++) - { - row[i] = matrix[index, i]; - } + return row; + } - return row; - } + /// + /// Gets column at specific position from matrix. + /// + /// source matrix + /// column index + /// column as a vector + public static int[] GetColumn(int[,] matrix, int index) + { + int length = matrix.GetLength(1); + int[] column = new int[length]; - /// - /// Gets column at specific position from matrix. - /// - /// source matrix - /// column index - /// column as a vector - public static int[] GetColumn(int[,] matrix, int index) + for (int i = 0; i < length; i++) { - int length = matrix.GetLength(1); - int[] column = new int[length]; + column[i] = matrix[index, i]; + } - for (int i = 0; i < length; i++) - { - column[i] = matrix[index, i]; - } + return column; + } - return column; - } + /// + /// Gets row at specific position from matrix. + /// + /// source matrix + /// row index + /// row as a vector + public static double[] GetRow(double[,] matrix, int index) + { + int length = matrix.GetLength(0); + double[] row = new double[length]; - /// - /// Gets row at specific position from matrix. - /// - /// source matrix - /// row index - /// row as a vector - public static double[] GetRow(double[,] matrix, int index) + for (int i = 0; i < length; i++) { - int length = matrix.GetLength(0); - double[] row = new double[length]; + row[i] = matrix[i, index]; + } - for (int i = 0; i < length; i++) - { - row[i] = matrix[i, index]; - } + return row; + } - return row; - } + /// + /// Gets row at specific position from matrix. + /// + /// source matrix + /// row index + /// row as a vector + public static int[] GetRow(int[,] matrix, int index) + { + int length = matrix.GetLength(0); + int[] row = new int[length]; - /// - /// Gets row at specific position from matrix. - /// - /// source matrix - /// row index - /// row as a vector - public static int[] GetRow(int[,] matrix, int index) + for (int i = 0; i < length; i++) { - int length = matrix.GetLength(0); - int[] row = new int[length]; + row[i] = matrix[i, index]; + } - for (int i = 0; i < length; i++) - { - row[i] = matrix[i, index]; - } + return row; + } - return row; - } + /// + /// Gets minimum value from matrix. + /// + /// matrix + /// minimum value + public static double Min(double[,] matrix) + { + double min = double.MaxValue; - /// - /// Gets minimum value from matrix. - /// - /// matrix - /// minimum value - public static double Min(double[,] matrix) + for (int x = 0; x < matrix.GetLength(0); x++) { - double min = double.MaxValue; - - for (int x = 0; x < matrix.GetLength(0); x++) + for (int y = 0; y < matrix.GetLength(1); y++) { - for (int y = 0; y < matrix.GetLength(1); y++) - { - min = Math.Min(min, matrix[x, y]); - } + min = Math.Min(min, matrix[x, y]); } - - return min; } - /// - /// Gets minimum value from matrix. - /// - /// matrix - /// minimum value - public static int Min(int[,] matrix) - { - int min = int.MaxValue; + return min; + } - for (int x = 0; x < matrix.GetLength(0); x++) + /// + /// Gets minimum value from matrix. + /// + /// matrix + /// minimum value + public static int Min(int[,] matrix) + { + int min = int.MaxValue; + + for (int x = 0; x < matrix.GetLength(0); x++) + { + for (int y = 0; y < matrix.GetLength(1); y++) { - for (int y = 0; y < matrix.GetLength(1); y++) - { - min = Math.Min(min, matrix[x, y]); - } + min = Math.Min(min, matrix[x, y]); } - - return min; } - /// - /// Gets maximum value from matrix. - /// - /// matrix - /// minimum value - public static double Max(double[,] matrix) - { - double maxVal = double.MinValue; + return min; + } - for (int x = 0; x < matrix.GetLength(0); x++) + /// + /// Gets maximum value from matrix. + /// + /// matrix + /// minimum value + public static double Max(double[,] matrix) + { + double maxVal = double.MinValue; + + for (int x = 0; x < matrix.GetLength(0); x++) + { + for (int y = 0; y < matrix.GetLength(1); y++) { - for (int y = 0; y < matrix.GetLength(1); y++) - { - maxVal = Math.Max(maxVal, matrix[x, y]); - } + maxVal = Math.Max(maxVal, matrix[x, y]); } - - return maxVal; } - /// - /// Gets maximum value from matrix. - /// - /// matrix - /// minimum value - public static int Max(int[,] matrix) - { - int maxVal = int.MinValue; + return maxVal; + } - for (int x = 0; x < matrix.GetLength(0); x++) + /// + /// Gets maximum value from matrix. + /// + /// matrix + /// minimum value + public static int Max(int[,] matrix) + { + int maxVal = int.MinValue; + + for (int x = 0; x < matrix.GetLength(0); x++) + { + for (int y = 0; y < matrix.GetLength(1); y++) { - for (int y = 0; y < matrix.GetLength(1); y++) - { - maxVal = Math.Max(maxVal, matrix[x, y]); - } + maxVal = Math.Max(maxVal, matrix[x, y]); } - - return maxVal; } - /// - /// Gets maximum absolute value from matrix. - /// - /// matrix - /// maximum absolute value - public static double MaxAbs(double[,] matrix) - { - double maxVal = double.MinValue; + return maxVal; + } + + /// + /// Gets maximum absolute value from matrix. + /// + /// matrix + /// maximum absolute value + public static double MaxAbs(double[,] matrix) + { + double maxVal = double.MinValue; - for (int x = 0; x < matrix.GetLength(0); x++) + for (int x = 0; x < matrix.GetLength(0); x++) + { + for (int y = 0; y < matrix.GetLength(1); y++) { - for (int y = 0; y < matrix.GetLength(1); y++) - { - maxVal = Math.Max(maxVal, Math.Abs(matrix[x, y])); - } + maxVal = Math.Max(maxVal, Math.Abs(matrix[x, y])); } - - return maxVal; } - /// - /// Gets maximum absolute value from matrix. - /// - /// matrix - /// maximum absolute value - public static int MaxAbs(int[,] matrix) - { - int maxVal = int.MinValue; + return maxVal; + } - for (int x = 0; x < matrix.GetLength(0); x++) + /// + /// Gets maximum absolute value from matrix. + /// + /// matrix + /// maximum absolute value + public static int MaxAbs(int[,] matrix) + { + int maxVal = int.MinValue; + + for (int x = 0; x < matrix.GetLength(0); x++) + { + for (int y = 0; y < matrix.GetLength(1); y++) { - for (int y = 0; y < matrix.GetLength(1); y++) - { - maxVal = Math.Max(maxVal, Math.Abs(matrix[x, y])); - } + maxVal = Math.Max(maxVal, Math.Abs(matrix[x, y])); } - - return maxVal; } - } + return maxVal; + } } diff --git a/src/ChaosSoft.Core/DataUtils/Vector.cs b/src/ChaosSoft.Core/DataUtils/Vector.cs index 0f3cddb..8b08f25 100644 --- a/src/ChaosSoft.Core/DataUtils/Vector.cs +++ b/src/ChaosSoft.Core/DataUtils/Vector.cs @@ -1,220 +1,218 @@ using System; -namespace ChaosSoft.Core.DataUtils +namespace ChaosSoft.Core.DataUtils; + +/// +/// Common operations on vectors. +/// +public static class Vector { /// - /// Common operations on vectors. + /// Creates uniform vector starting from some value with specific step. /// - public static class Vector + /// vector length + /// value start from + /// value increment + /// uniform vector + public static double[] CreateUniform(int length, double start, double step) { - /// - /// Creates uniform vector starting from some value with specific step. - /// - /// vector length - /// value start from - /// value increment - /// uniform vector - public static double[] CreateUniform(int length, double start, double step) + double[] array = new double[length]; + + for (int i = 0; i < length; i++) { - double[] array = new double[length]; + array[i] = start + step * i; + } - for (int i = 0; i < length; i++) - { - array[i] = start + step * i; - } + return array; + } - return array; - } + /// + /// Creates uniform vector starting from some value with specific step. + /// + /// vector length + /// value start from + /// value increment + /// uniform vector + public static int[] CreateUniform(int length, int start, int step) + { + int[] array = new int[length]; - /// - /// Creates uniform vector starting from some value with specific step. - /// - /// vector length - /// value start from - /// value increment - /// uniform vector - public static int[] CreateUniform(int length, int start, int step) + for (int i = 0; i < length; i++) { - int[] array = new int[length]; + array[i] = start + step * i; + } - for (int i = 0; i < length; i++) - { - array[i] = start + step * i; - } + return array; + } - return array; - } + /// + /// Fills vector with scpecific value. + /// + /// vector to fill + /// value to fill with + public static void FillWith(double[] vector, double value) + { + int i; + int len = vector.Length; - /// - /// Fills vector with scpecific value. - /// - /// vector to fill - /// value to fill with - public static void FillWith(double[] vector, double value) + for (i = 0; i < len; i++) { - int i; - int len = vector.Length; - - for (i = 0; i < len; i++) - { - vector[i] = value; - } + vector[i] = value; } + } - /// - /// Fills vector with scpecific value. - /// - /// vector to fill - /// value to fill with - public static void FillWith(int[] vector, int value) - { - int i; - int len = vector.Length; + /// + /// Fills vector with scpecific value. + /// + /// vector to fill + /// value to fill with + public static void FillWith(int[] vector, int value) + { + int i; + int len = vector.Length; - for (i = 0; i < len; i++) - { - vector[i] = value; - } + for (i = 0; i < len; i++) + { + vector[i] = value; } + } - /// - /// Gets minimum value from vector. - /// - /// array - /// minimum value - public static double Min(double[] vector) - { - double minVal = double.MaxValue; + /// + /// Gets minimum value from vector. + /// + /// array + /// minimum value + public static double Min(double[] vector) + { + double minVal = double.MaxValue; - foreach (double val in vector) + foreach (double val in vector) + { + if (val < minVal) { - if (val < minVal) - { - minVal = val; - } + minVal = val; } - - return minVal; } - /// - /// Gets minimum value from vector. - /// - /// array - /// minimum value - public static int Min(int[] vector) - { - int minVal = int.MaxValue; + return minVal; + } - foreach (int val in vector) + /// + /// Gets minimum value from vector. + /// + /// array + /// minimum value + public static int Min(int[] vector) + { + int minVal = int.MaxValue; + + foreach (int val in vector) + { + if (val < minVal) { - if (val < minVal) - { - minVal = val; - } + minVal = val; } - - return minVal; } - /// - /// Gets maximum value from vector. - /// - /// array - /// maximum value - public static double Max(double[] vector) - { - double maxVal = double.MinValue; + return minVal; + } + + /// + /// Gets maximum value from vector. + /// + /// array + /// maximum value + public static double Max(double[] vector) + { + double maxVal = double.MinValue; - foreach (double val in vector) + foreach (double val in vector) + { + if (val > maxVal) { - if (val > maxVal) - { - maxVal = val; - } + maxVal = val; } - - return maxVal; } - /// - /// Gets maximum value from vector. - /// - /// array - /// maximum value - public static int Max(int[] vector) - { - int maxVal = int.MinValue; + return maxVal; + } - foreach (int val in vector) + /// + /// Gets maximum value from vector. + /// + /// array + /// maximum value + public static int Max(int[] vector) + { + int maxVal = int.MinValue; + + foreach (int val in vector) + { + if (val > maxVal) { - if (val > maxVal) - { - maxVal = val; - } + maxVal = val; } - - return maxVal; } - /// - /// Gets maximum absolute value from vecor. - /// - /// array - /// maximum absolute value - public static double MaxAbs(double[] vector) - { - double maxVal = double.MinValue; + return maxVal; + } - foreach (double val in vector) - { - maxVal = Math.Max(maxVal, Math.Abs(val)); - } + /// + /// Gets maximum absolute value from vecor. + /// + /// array + /// maximum absolute value + public static double MaxAbs(double[] vector) + { + double maxVal = double.MinValue; - return maxVal; + foreach (double val in vector) + { + maxVal = Math.Max(maxVal, Math.Abs(val)); } - /// - /// Gets maximum absolute value from vecor. - /// - /// array - /// maximum absolute value - public static int MaxAbs(int[] vector) - { - int maxVal = int.MinValue; + return maxVal; + } - foreach (int val in vector) - { - maxVal = Math.Max(maxVal, Math.Abs(val)); - } + /// + /// Gets maximum absolute value from vecor. + /// + /// array + /// maximum absolute value + public static int MaxAbs(int[] vector) + { + int maxVal = int.MinValue; - return maxVal; + foreach (int val in vector) + { + maxVal = Math.Max(maxVal, Math.Abs(val)); } - /// - /// Shifts vector until it's min == 0 and rescales it by it's interval. - /// - /// vector to rescale - /// original vector interval - /// - public static double Rescale(double[] vector) - { - var max = Max(vector); - var min = Min(vector); - var interval = max - min; + return maxVal; + } - if (interval == 0d) - { - throw new ArgumentException("Data amplitude is zero, it makes no sense to continue."); - } + /// + /// Shifts vector until it's min == 0 and rescales it by it's interval. + /// + /// vector to rescale + /// original vector interval + /// + public static double Rescale(double[] vector) + { + var max = Max(vector); + var min = Min(vector); + var interval = max - min; - for (int i = 0; i < vector.Length; i++) - { - vector[i] = (vector[i] - min) / interval; - } + if (interval == 0d) + { + throw new ArgumentException("Data amplitude is zero, it makes no sense to continue."); + } - return interval; + for (int i = 0; i < vector.Length; i++) + { + vector[i] = (vector[i] - min) / interval; } - } + return interval; + } } diff --git a/src/ChaosSoft.Core/IHasDescription.cs b/src/ChaosSoft.Core/IHasDescription.cs new file mode 100644 index 0000000..a7b09c8 --- /dev/null +++ b/src/ChaosSoft.Core/IHasDescription.cs @@ -0,0 +1,12 @@ +namespace ChaosSoft.Core; + +/// +/// Interface for components which should have description. +/// +public interface IHasDescription +{ + /// + /// Gets item description. + /// + string Description { get; } +} diff --git a/src/ChaosSoft.Core/IHasName.cs b/src/ChaosSoft.Core/IHasName.cs new file mode 100644 index 0000000..522db62 --- /dev/null +++ b/src/ChaosSoft.Core/IHasName.cs @@ -0,0 +1,12 @@ +namespace ChaosSoft.Core; + +/// +/// Interface for components which should have name. +/// +public interface IHasName +{ + /// + /// Gets readable humanized item name. + /// + string Name { get; } +} diff --git a/src/ChaosSoft.Core/IO/BinaryDataFileReader.cs b/src/ChaosSoft.Core/IO/BinaryDataFileReader.cs new file mode 100644 index 0000000..067d022 --- /dev/null +++ b/src/ChaosSoft.Core/IO/BinaryDataFileReader.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +namespace ChaosSoft.Core.IO; + +/// +/// The reader reads source file as serialized double[][] object. +/// +public class BinaryDataFileReader : IDataReader +{ + /// + /// Reads source data file as serialized double[][]. If the file exists it will be overwritten. + /// + /// path to file to read + /// + /// + public double[][] ReadColumnsFromFile(string source) + { + if (!File.Exists(source)) + { + throw new FileNotFoundException("Source data file not found.", source); + } + + byte[] bytes = File.ReadAllBytes(source); + + using MemoryStream ms = new(bytes); + ms.Seek(0, 0); + return (double[][]) new BinaryFormatter().Deserialize(ms); + } +} diff --git a/src/ChaosSoft.Core/IO/BinaryDataFileWriter.cs b/src/ChaosSoft.Core/IO/BinaryDataFileWriter.cs new file mode 100644 index 0000000..755ba78 --- /dev/null +++ b/src/ChaosSoft.Core/IO/BinaryDataFileWriter.cs @@ -0,0 +1,27 @@ +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +namespace ChaosSoft.Core.IO; + +/// +/// Serializes double[][] data to a file. +/// +public class BinaryDataFileWriter : IDataWriter +{ + /// + /// Create source data file and serialize object[][] data into it. + /// + /// path to file to create + /// data to serialize + public void WriteData(string destination, double[][] data) + { + using MemoryStream ms = new(); + + new BinaryFormatter().Serialize(ms, data); + byte[] bytes = ms.ToArray(); + + using FileStream fs = new(destination, FileMode.Create); + fs.Write(bytes, 0, bytes.Length); + fs.Close(); + } +} diff --git a/src/ChaosSoft.Core/IO/DataReader.cs b/src/ChaosSoft.Core/IO/DataReader.cs deleted file mode 100644 index 7bf11fa..0000000 --- a/src/ChaosSoft.Core/IO/DataReader.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text.RegularExpressions; - -namespace ChaosSoft.Core.IO -{ - /// - /// Provides with methods for reading of data files with timeseries. - /// - public static class DataReader - { - /// - /// Reads file from specific path and gets specified data range from the file using column delimiter regex. - /// - /// path to file to read - /// amount of lines to skip for reading - /// amount of lines to read - /// regex fo column delimeter - /// - /// - /// - public static double[][] ReadColumnsFromFile(string file, int startOffset, int readLines, string delimiterRegex) - { - if (!File.Exists(file)) - { - throw new FileNotFoundException("Source data file not found.", file); - } - - int i, j; - - string[] sourceData = File.ReadAllLines(file); - - // Determine how many numbers in line. - int columns = Regex.Split(sourceData[startOffset].Trim(), delimiterRegex).Length; - - int length = readLines == 0 ? sourceData.Length - startOffset : readLines; - - double[][] dataColumns = new double[columns][]; - - for (i = 0; i < dataColumns.Length; i++) - { - dataColumns[i] = new double[length]; - } - - for (i = startOffset; i < length + startOffset; i++) - { - var numbers = Regex.Split(sourceData[i].Trim(), delimiterRegex); - - for (j = 0; j < columns; j++) - { - if (double.TryParse(numbers[j], NumberStyles.Any, CultureInfo.InvariantCulture, out double value)) - { - dataColumns[j][i - startOffset] = value; - } - else - { - throw new ArgumentException( - $"Unable to parse value (Line: {i + 1}, Column: {j + 1} [value: {numbers[j]}])"); - } - } - } - - return dataColumns; - } - - /// - /// Reads file from specific path and gets specified data range from the file considering column delimeter as whitespace. - /// - /// path to file to read - /// amount of lines to skip for reading - /// amount of lines to read - /// - /// - /// - public static double[][] ReadColumnsFromFile(string file, int startOffset, int readLines) => - ReadColumnsFromFile(file, startOffset, readLines, "\\s+"); - - /// - /// Reads source data file as serialized double[][]. - /// - /// path to file to read - /// - public static double[][] ReadColumnsFromByteFile(string fileName) - { - byte[] bytes = File.ReadAllBytes(fileName); - - using (MemoryStream ms = new MemoryStream(bytes)) - { - ms.Seek(0, 0); - return (double[][])(new BinaryFormatter().Deserialize(ms)); - } - } - } -} diff --git a/src/ChaosSoft.Core/IO/DataWriter.cs b/src/ChaosSoft.Core/IO/DataWriter.cs deleted file mode 100644 index 24f9b8d..0000000 --- a/src/ChaosSoft.Core/IO/DataWriter.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Globalization; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; - -namespace ChaosSoft.Core.IO -{ - /// - /// Provides with methods for data writing. - /// - public class DataWriter - { - /// - /// Created file with specific name and data. - /// - /// file name to create - /// string data to write - public static void CreateDataFile(string fileName, string data) - { - File.Delete(fileName); - FileStream outFile = File.Create(fileName); - byte[] info = new UTF8Encoding(true).GetBytes(data); - outFile.Write(info, 0, info.Length); - outFile.Close(); - } - - /// - /// Writes multidimensional array data into specified file with specified number format. - /// - /// path to file to create - /// data to write - /// numbers format to use - public static void CreateDataFile(string fileName, double[,] data, string format) - { - var output = new StringBuilder(); - - for (int i = 0; i < data.GetLength(1); i++) - { - for (int j = 0; j < data.GetLength(0); j++) - { - output.Append($"{data[j, i].ToString(format, CultureInfo.InvariantCulture)}\t"); - } - - output.AppendLine(); - } - - CreateDataFile(fileName, output.ToString()); - } - - /// - /// Writes multidimensional array data into specified file with G6 number format. - /// - /// path to file to create - /// data to write - public static void CreateDataFile(string fileName, double[,] data) => - CreateDataFile(fileName, data, Format.Default); - - /// - /// Create source data file and serialize data into it. - /// - /// path to file to create - /// data to serialize - /// - public static void CreateBytesDataFile(string fileName, double[][] data) - { - using (MemoryStream ms = new MemoryStream()) - { - new BinaryFormatter().Serialize(ms, data); - byte[] bytes = ms.ToArray(); - - using (FileStream fs = new FileStream(fileName, FileMode.Create)) - { - fs.Write(bytes, 0, bytes.Length); - fs.Close(); - } - } - } - } -} diff --git a/src/ChaosSoft.Core/IO/FileUtils.cs b/src/ChaosSoft.Core/IO/FileUtils.cs new file mode 100644 index 0000000..d34b0b4 --- /dev/null +++ b/src/ChaosSoft.Core/IO/FileUtils.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Text; + +namespace ChaosSoft.Core.IO; + +/// +/// Contains utilities to work with file. +/// +public static class FileUtils +{ + /// + /// Creates file with specific name and string data. If the file exists it will be overwritten. + /// + /// file name to create + /// string data to write + public static void CreateDataFile(string fileName, string data) => + CreateDataFile(fileName, data, new UTF8Encoding(true)); + + /// + /// Creates file with specific name and string data. If the file exists it will be overwritten. + /// + /// file name to create + /// string data to write + /// file encoding + public static void CreateDataFile(string fileName, string data, Encoding encoding) + { + File.Delete(fileName); + FileStream outFile = File.Create(fileName); + byte[] info = encoding.GetBytes(data); + outFile.Write(info, 0, info.Length); + outFile.Close(); + } +} diff --git a/src/ChaosSoft.Core/IO/Format.cs b/src/ChaosSoft.Core/IO/Format.cs deleted file mode 100644 index 2e61020..0000000 --- a/src/ChaosSoft.Core/IO/Format.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Globalization; -using System.Linq; - -namespace ChaosSoft.Core.IO -{ - /// - /// Provides with helpers for formatting. - /// - public static class Format - { - internal const string Default = "G6"; - - /// - /// Formats double value in general format (G6). - /// - /// value to format - /// - public static string General(double value) => - value.ToString(Default, CultureInfo.InvariantCulture); - - /// - /// Formats double value in general format with specific digits count. - /// - /// value to format - /// significant digits to display - /// - public static string General(double value, int digits) => - value.ToString("G" + digits, CultureInfo.InvariantCulture); - - /// - /// Formats array of double values in general format (G6) with '; ' delimiter. - /// - /// values to format - /// - public static string General(double[] values) => - General(values, "; ", 6); - - /// - /// Formats array of double values in general format with specific digits count and delimiter. - /// - /// values to format - /// values delimiter - /// significant digits to display - /// - public static string General(double[] values, string delimiter, int digits) => - string.Join(delimiter, values.Select(v => General(v, digits))); - } -} diff --git a/src/ChaosSoft.Core/IO/IDataReader.cs b/src/ChaosSoft.Core/IO/IDataReader.cs new file mode 100644 index 0000000..ff784fd --- /dev/null +++ b/src/ChaosSoft.Core/IO/IDataReader.cs @@ -0,0 +1,14 @@ +namespace ChaosSoft.Core.IO; + +/// +/// Interface for components which should read data from source. +/// +public interface IDataReader +{ + /// + /// Reads data from source as double[][] which represents columns and rows of the source + /// + /// data source + /// double[][] of data columns and rows + double[][] ReadColumnsFromFile(string source); +} diff --git a/src/ChaosSoft.Core/IO/IDataWriter.cs b/src/ChaosSoft.Core/IO/IDataWriter.cs new file mode 100644 index 0000000..21a942e --- /dev/null +++ b/src/ChaosSoft.Core/IO/IDataWriter.cs @@ -0,0 +1,14 @@ +namespace ChaosSoft.Core.IO; + +/// +/// Interface for components which should read data from source. +/// +public interface IDataWriter +{ + /// + /// Writes retrievedd data to specified destination. + /// + /// destination to write the data + /// data to write + void WriteData(string destination, T data); +} diff --git a/src/ChaosSoft.Core/IO/Model3D.cs b/src/ChaosSoft.Core/IO/Model3D.cs deleted file mode 100644 index a8db45e..0000000 --- a/src/ChaosSoft.Core/IO/Model3D.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Globalization; -using System.Text; - -namespace ChaosSoft.Core.IO -{ - /// - /// Provides methods to transform series to 3D model files. - /// - public static class Model3D - { - /// - /// Create file with 3D model in PLY format.
- /// Accuracy is up to 7 decimal places. - ///
- /// output 3d model file name - /// array of points X coordinates - /// array of points Y coordinates - /// array of points Z coordinates - public static void Create3dPlyModelFile(string filePath, double[] xt, double[] yt, double[] zt) - { - int pts = xt.Length; - - var model3D = new StringBuilder() - .AppendLine("ply") - .AppendLine("format ascii 1.0") - .AppendLine("comment object: " + "model") - .AppendFormat("element vertex {0}\n", pts) - .AppendLine("property double x") - .AppendLine("property double y") - .AppendLine("property double z") - .AppendLine("end_header"); - - for (int t = 0; t < pts; t++) - { - model3D.AppendFormat(CultureInfo.InvariantCulture, "{0:G7} {1:G7} {2:G7}\n", xt[t], yt[t], zt[t]); - } - - DataWriter.CreateDataFile(filePath, model3D.ToString()); - } - - /// - /// Create file with 3D model in 3DA format (simple list of coordinates (x, y, z)).
- /// Accuracy is up to 7 decimal places. - ///
- /// output 3d model file name - /// array of points X coordinates - /// array of points Y coordinates - /// array of points Z coordinates - public static void Create3daModelFile(string filePath, double[] xt, double[] yt, double[] zt) - { - int pts = xt.Length; - StringBuilder model3D = new StringBuilder(); - - for (int t = 0; t < pts; t++) - { - model3D.AppendFormat(CultureInfo.InvariantCulture, "{0:G7} {1:G7} {2:G7}\n", xt[t], yt[t], zt[t]); - } - - DataWriter.CreateDataFile(filePath, model3D.ToString()); - } - } -} diff --git a/src/ChaosSoft.Core/IO/PlainTextFileReader.cs b/src/ChaosSoft.Core/IO/PlainTextFileReader.cs new file mode 100644 index 0000000..dff1fc2 --- /dev/null +++ b/src/ChaosSoft.Core/IO/PlainTextFileReader.cs @@ -0,0 +1,112 @@ +using System.Globalization; +using System.IO; +using System.Text.RegularExpressions; + +namespace ChaosSoft.Core.IO; + +/// +/// Provides with methods for reading of data files with timeseries. +/// +public class PlainTextFileReader : IDataReader +{ + /// + /// Default regex for column delimiter ("\s+") + /// + public const string DefaultDelimiterRegex = "\\s+"; + + private readonly int _skipLines; + private readonly int _readLines; + private readonly string _delimiterRegex; + + /// + /// Initializes a new instance of configured to read whole file + /// using + /// + public PlainTextFileReader() : this(0, 0, DefaultDelimiterRegex) + { + } + + /// + /// Initializes a new instance of configured to skip specified count of lines + /// from the beginning and to read rest of file using + /// + /// count of lines to skip from the beginning + public PlainTextFileReader(int skipLines) : this(skipLines, 0, DefaultDelimiterRegex) + { + } + + /// + /// Initializes a new instance of configured to read specified range from file + /// using . + /// + /// count of lines to skip from the beginning + /// count of lines to read + public PlainTextFileReader(int skipLines, int readLines) : this(skipLines, readLines, DefaultDelimiterRegex) + { + } + + /// + /// Initializes a new instance of configured to read specified range from file + /// using specified columns delimiter regex. + /// + /// count of lines to skip from the beginning + /// count of lines to read + /// regex of columns delimiter + public PlainTextFileReader(int skipLines, int readLines, string delimiterRegex) + { + _skipLines = skipLines; + _readLines = readLines; + _delimiterRegex = delimiterRegex; + } + + /// + /// Reads file from specific path and gets specified data range from the file using column delimiter regex. + /// + /// path to file to read + /// + /// + /// + public double[][] ReadColumnsFromFile(string file) + { + if (!File.Exists(file)) + { + throw new FileNotFoundException("Source data file not found.", file); + } + + int i, j; + + string[] sourceData = File.ReadAllLines(file); + + // Determine how many numbers in line. + int columns = Regex.Split(sourceData[_skipLines].Trim(), _delimiterRegex).Length; + + int length = _readLines == 0 ? sourceData.Length - _skipLines : _readLines; + + double[][] dataColumns = new double[columns][]; + + for (i = 0; i < dataColumns.Length; i++) + { + dataColumns[i] = new double[length]; + } + + for (i = _skipLines; i < length + _skipLines; i++) + { + var numbers = Regex.Split(sourceData[i].Trim(), _delimiterRegex); + + for (j = 0; j < columns; j++) + { + if (double.TryParse(numbers[j], NumberStyles.Any, NumFormat.CurrentCulture, out double value)) + { + dataColumns[j][i - _skipLines] = value; + } + else + { + throw new InvalidDataException( + $"Unable to parse value (Line: {i + 1}, Column: {j + 1} [value: {numbers[j]}])"); + } + } + } + + return dataColumns; + } +} diff --git a/src/ChaosSoft.Core/IO/PlainTextFileWriter.cs b/src/ChaosSoft.Core/IO/PlainTextFileWriter.cs new file mode 100644 index 0000000..d0dcaea --- /dev/null +++ b/src/ChaosSoft.Core/IO/PlainTextFileWriter.cs @@ -0,0 +1,56 @@ +using System.Text; + +namespace ChaosSoft.Core.IO; + +/// +/// Writes double[,] data to a file. +/// +public class PlainTextFileWriter : IDataWriter +{ + private const string ColumnDelimiter = "\t"; + private readonly string _format; + + /// + /// Initializes a new instance of with + /// + public PlainTextFileWriter() : this(NumFormat.CurrentFormat) + { + + } + + /// + /// Initializes a new instance of with specified number format. + /// + /// number format + public PlainTextFileWriter(string format) + { + _format = format; + } + + /// + /// Create source data file and writes multidimensional array into it. If the file exists it will be overwritten. + /// + /// path to file to create + /// data to write + public void WriteData(string destination, double[,] data) + { + var output = new StringBuilder(); + + for (int i = 0; i < data.GetLength(1); i++) + { + for (int j = 0; j < data.GetLength(0); j++) + { + if (j > 0) + { + output.Append(ColumnDelimiter); + } + + output.Append(data[j, i].ToString(_format, NumFormat.CurrentCulture)); + } + + output.AppendLine(); + } + + FileUtils.CreateDataFile(destination, output.ToString()); + } +} diff --git a/src/ChaosSoft.Core/IO/Sound.cs b/src/ChaosSoft.Core/IO/Sound.cs deleted file mode 100644 index 93fde57..0000000 --- a/src/ChaosSoft.Core/IO/Sound.cs +++ /dev/null @@ -1,121 +0,0 @@ -using ChaosSoft.Core.DataUtils; -using System; -using System.IO; -using System.Text; - -namespace ChaosSoft.Core.IO -{ - /// - /// Provides methods to transform series to sound. - /// - public static class Sound - { - /// - /// Create WAV file of signal "sound" with specified quality params. - /// - /// output sound file name - /// frequency of quantization - /// digits of quantization - /// signal - public static void CreateWavFile(string filePath, int freq, int bits, double[] series) - { - long pts = series.Length; - - double xtmin = Vector.Min(series); - double xtmax = Vector.Max(series); - - File.Delete(filePath); - FileStream wavFile = File.Create(filePath); - byte[] info; - - // (4 bytes) File description header - info = new UTF8Encoding(true).GetBytes("RIFF"); - wavFile.Write(info, 0, info.Length); - - // (4 bytes) Size of file - // The file size not including the "RIFF" description (4 bytes) - // and file description (4 bytes). This is file size - 8. - info = BitConverter.GetBytes((int)(pts - 8)); - wavFile.Write(info, 0, info.Length); - - // (4 bytes) WAV description header - info = new UTF8Encoding(true).GetBytes("WAVE"); - wavFile.Write(info, 0, info.Length); - - // (4 bytes) Format description header - info = new UTF8Encoding(true).GetBytes("fmt "); - wavFile.Write(info, 0, info.Length); - - // (4 bytes) Size of WAVE section chunck - // The size of the WAVE type format (2 bytes) + - // mono/stereo flag (2 bytes) + - // sample rate (4 bytes) + - // bytes per sec (4 bytes) + - // block alignment (2 bytes) + - // bits per sample (2 bytes). - // This is usually 16. - info = BitConverter.GetBytes(16); - wavFile.Write(info, 0, info.Length); - - // (2 bytes) WAVE type format - // Type of WAVE format. This is a PCM header = $01 (linear quntization). - // Other values indicates some forms of compression. - info = BitConverter.GetBytes((short)1); - wavFile.Write(info, 0, info.Length); - - // (2 bytes) Number of channels - // mono ($01) or stereo ($02) - info = BitConverter.GetBytes((short)1); - wavFile.Write(info, 0, info.Length); - - // (4 bytes) Samples per second - // The frequency of quantization (usually 44100 Hz, 22050 Hz, ...) - info = BitConverter.GetBytes(freq); - wavFile.Write(info, 0, info.Length); - - // (4 bytes) Bytes per second - // Speed of data stream = Number_of_channels * Samples_per_second * Bits_per_Sample/8 - info = BitConverter.GetBytes(freq * bits / 8); - wavFile.Write(info, 0, info.Length); - - // (2 bytes) Block alignment - // Number of bytes in elementary quantization = Number_of_channels*Bits_per_Sample/8 - info = BitConverter.GetBytes((short)(bits / 8)); - wavFile.Write(info, 0, info.Length); - - // (2 bytes) Bits per sample - // Digits of quantization (usually 32, 24, 16, 8) - info = BitConverter.GetBytes((short)bits); - wavFile.Write(info, 0, info.Length); - - // Data description header - info = new UTF8Encoding(true).GetBytes("data"); - wavFile.Write(info, 0, info.Length); - - // (4 bytes) Size of data - info = BitConverter.GetBytes((int)pts); - wavFile.Write(info, 0, info.Length); - - // as bytes array - byte[] data = new byte[pts]; - - for (int t = 0; t < pts; t++) - { - double _yt = 255 * (series[t] - xtmin) / (xtmax - xtmin); - data[t] = (byte)_yt; - } - - // Data - wavFile.Write(data, 0, data.Length); - wavFile.Close(); - } - - /// - /// Create WAV file (8Khz8bit) of signal "sound" - /// - /// output sound file name - /// signal - public static void CreateWavFile(string filePath, double[] series) => - CreateWavFile(filePath, 8000, 8, series); - } -} diff --git a/src/ChaosSoft.Core/Logging/ConsoleLogger.cs b/src/ChaosSoft.Core/Logging/ConsoleLogger.cs new file mode 100644 index 0000000..3ac71e2 --- /dev/null +++ b/src/ChaosSoft.Core/Logging/ConsoleLogger.cs @@ -0,0 +1,51 @@ +using System; + +namespace ChaosSoft.Core.Logging; + +/// +/// Logging to console. +/// +public class ConsoleLogger : ILogger +{ + /// + /// Logs message with error level + /// + /// message to log + /// parameters to substitute to message template + public void Error(string message, params object[] parameters) => + Console.WriteLine("Error :: " + message, parameters); + + /// + /// Logs message with warning level + /// + /// message to log + /// parameters to substitute to message template + public void Warn(string message, params object[] parameters) => + Console.WriteLine("Warn :: " + message, parameters); + + /// + /// Logs message with informational level. + /// + /// message to log + /// parameters to substitute to message template + public void Info(string message, params object[] parameters) => + Console.WriteLine(message, parameters); + + /// + /// Logs message with debug level + /// + /// message to log + /// parameters to substitute to message template + public void Debug(string message, params object[] parameters) => + Console.WriteLine("Debug :: " + message, parameters); + + /// + /// Logs message with trace level + /// + /// message to log + /// parameters to substitute to message template + public void Trace(string message, params object[] parameters) => + Console.WriteLine("Trace :: " + message, parameters); + + +} diff --git a/src/ChaosSoft.Core/Logging/ILogger.cs b/src/ChaosSoft.Core/Logging/ILogger.cs new file mode 100644 index 0000000..8948715 --- /dev/null +++ b/src/ChaosSoft.Core/Logging/ILogger.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ChaosSoft.Core.Logging; + +/// +/// Interface of logger. Provides an ability to log message with specified severity. +/// +public interface ILogger +{ + /// + /// Logs message with error level + /// + /// message to log + /// parameters to substitute to message template + void Error(string message, params object[] parameters); + + /// + /// Logs message with warning level + /// + /// message to log + /// parameters to substitute to message template + void Warn(string message, params object[] parameters); + + /// + /// Logs message with informational level. + /// + /// message to log + /// parameters to substitute to message template + void Info(string message, params object[] parameters); + + /// + /// Logs message with debug level + /// + /// message to log + /// parameters to substitute to message template + void Debug(string message, params object[] parameters); + + /// + /// Logs message with trace level + /// + /// message to log + /// parameters to substitute to message template + void Trace(string message, params object[] parameters); +} diff --git a/src/ChaosSoft.Core/Logging/Log.cs b/src/ChaosSoft.Core/Logging/Log.cs new file mode 100644 index 0000000..55b0a45 --- /dev/null +++ b/src/ChaosSoft.Core/Logging/Log.cs @@ -0,0 +1,88 @@ +namespace ChaosSoft.Core.Logging; + +/// +/// Main framework logger (by default logs to console with Info level). +/// +public static class Log +{ + private static LogLevel Level = LogLevel.Info; + private static ILogger Instance = new ConsoleLogger(); + + /// + /// Sets active logger implementation. + /// + /// logger instance + public static void SetLogger(ILogger logger) => + Instance = logger; + + /// + /// Sets minimum log level. All records with level lower than current will not appear in logs. + /// + /// verbosity level + public static void SetLevel(LogLevel level) + { + Level = level; + } + + /// + /// Logs message with error level. + /// + /// message to log + /// parameters to substitute to message template + public static void Error(string message, params object[] parameters) + { + Instance.Error(message, parameters); + } + + /// + /// Logs message with warning level. + /// + /// message to log + /// parameters to substitute to message template + public static void Warn(string message, params object[] parameters) + { + if (Level >= LogLevel.Warning) + { + Instance.Warn(message, parameters); + } + } + + /// + /// Logs message with informational level. + /// + /// message to log + /// parameters to substitute to message template + public static void Info(string message, params object[] parameters) + { + if (Level >= LogLevel.Info) + { + Instance.Info(message, parameters); + } + } + + /// + /// Logs message with debug level. + /// + /// message to log + /// parameters to substitute to message template + public static void Debug(string message, params object[] parameters) + { + if (Level >= LogLevel.Debug) + { + Instance.Debug(message, parameters); + } + } + + /// + /// Logs message with trace level. + /// + /// message to log + /// parameters to substitute to message template + public static void Trace(string message, params object[] parameters) + { + if (Level >= LogLevel.Trace) + { + Instance.Trace(message, parameters); + } + } +} diff --git a/src/ChaosSoft.Core/Logging/LogLevel.cs b/src/ChaosSoft.Core/Logging/LogLevel.cs new file mode 100644 index 0000000..8b417f6 --- /dev/null +++ b/src/ChaosSoft.Core/Logging/LogLevel.cs @@ -0,0 +1,32 @@ +namespace ChaosSoft.Core.Logging; + +/// +/// Represents severity levels for framework logger. +/// +public enum LogLevel +{ + /// + /// Used to log errors which are not handled. + /// + Error, + + /// + /// Used to log errors which are handled. + /// + Warning, + + /// + /// Used to log general information. + /// + Info, + + /// + /// Used to log information for debugging. + /// + Debug, + + /// + /// Used to log low level information. + /// + Trace +} diff --git a/src/ChaosSoft.Core/NumFormat.cs b/src/ChaosSoft.Core/NumFormat.cs new file mode 100644 index 0000000..8051ab2 --- /dev/null +++ b/src/ChaosSoft.Core/NumFormat.cs @@ -0,0 +1,100 @@ +using System.Globalization; +using System.Linq; + +namespace ChaosSoft.Core; + +/// +/// Provides with helpers for formatting. +/// +public static class NumFormat +{ + // To avoid format generation each time for more performance at least for default formats. + private const int DefaultPrecision = 7; + private const string DefaultGeneralFormat = "G7"; + private const string DefaultCustomFormat = "0.#######"; + + private static readonly CultureInfo DefaultCulture = CultureInfo.InvariantCulture; + + /// + /// Gets or sets currently used number format (default 0.#######). + /// + public static string CurrentFormat { get; set; } = DefaultCustomFormat; + + /// + /// Gets or sets currently used culture info (default InvariantCulture) + /// + public static CultureInfo CurrentCulture { get; set; } = DefaultCulture; + + /// + /// Gets general (G) format with currently specified precision. + /// + public static string General { get; private set; } = DefaultGeneralFormat; + + /// + /// Gets custom (0.##...) format with currently specified precision. + /// + public static string FixPointTrimZeros { get; private set; } = DefaultCustomFormat; + + /// + /// Resets format configuration to defaults. + /// + public static void Reset() + { + CurrentFormat = DefaultCustomFormat; + CurrentCulture = DefaultCulture; + SetPrecision(DefaultPrecision); + } + + /// + /// Resets format configuration to defaults. + /// + public static void SetPrecision(int precision) + { + General = $"G{precision}"; + FixPointTrimZeros = $"0.{new string('#', precision)}"; + } + + /// + /// Formats double value in specified format using current culture info. + /// + /// value to format + /// number format + /// + public static string Format(double value, string format) => + value.ToString(format, CurrentCulture); + + /// + /// Formats double value in specified format using current culture info and current format. + /// + /// value to format + /// + public static string Format(double value) => + Format(value, CurrentFormat); + + /// + /// Formats double value in specified format using current culture info and current format. + /// + /// value to format + /// + public static string Format(double[] value) => + Format(value, CurrentFormat, "; "); + + /// + /// Formats array of double values using current format and specified delimiter. + /// + /// values to format + /// values delimiter + /// + public static string Format(double[] values, string delimiter) => + Format(values, CurrentFormat, delimiter); + + /// + /// Formats array of double values using specified format and delimiter. + /// + /// values to format + /// number format + /// values delimiter + /// + public static string Format(double[] values, string format, string delimiter) => + string.Join(delimiter, values.Select(v => v.ToString(format, CurrentCulture))); +}