From 02f968168fbdbb999e2c51e244c439213c9fd281 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 10 Jul 2025 12:34:59 +0200 Subject: [PATCH 01/70] feat: add processor base --- DataAggregator.sln | 8 +++++++ .../DataAggregator.Processor.csproj | 23 +++++++++++++++++++ src/DataAggregator.Processor/Program.cs | 6 +++++ .../Properties/launchSettings.json | 23 +++++++++++++++++++ .../appsettings.Development.json | 8 +++++++ src/DataAggregator.Processor/appsettings.json | 9 ++++++++ 6 files changed, 77 insertions(+) create mode 100644 src/DataAggregator.Processor/DataAggregator.Processor.csproj create mode 100644 src/DataAggregator.Processor/Program.cs create mode 100644 src/DataAggregator.Processor/Properties/launchSettings.json create mode 100644 src/DataAggregator.Processor/appsettings.Development.json create mode 100644 src/DataAggregator.Processor/appsettings.json diff --git a/DataAggregator.sln b/DataAggregator.sln index af85b91..28125eb 100644 --- a/DataAggregator.sln +++ b/DataAggregator.sln @@ -36,10 +36,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Collector.Sh EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Collector.OpenCNCapnProtoConnector", "src\DataAggregator.Collector.OpenCNCapnProtoConnector\DataAggregator.Collector.OpenCNCapnProtoConnector.csproj", "{2C10378E-0937-40E3-940E-F236F6E8B95B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Processor", "src\DataAggregator.Processor\DataAggregator.Processor.csproj", "{039EC00D-5EDF-4C48-B449-29CFB6750232}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{330C7B17-DB90-458C-B630-99F9C1B5EA45}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/src/DataAggregator.Processor/DataAggregator.Processor.csproj b/src/DataAggregator.Processor/DataAggregator.Processor.csproj new file mode 100644 index 0000000..91b16c7 --- /dev/null +++ b/src/DataAggregator.Processor/DataAggregator.Processor.csproj @@ -0,0 +1,23 @@ + + + + net9.0 + enable + enable + true + true + true + + + + + + + + + + + + + + diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs new file mode 100644 index 0000000..1760df1 --- /dev/null +++ b/src/DataAggregator.Processor/Program.cs @@ -0,0 +1,6 @@ +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); + +app.Run(); diff --git a/src/DataAggregator.Processor/Properties/launchSettings.json b/src/DataAggregator.Processor/Properties/launchSettings.json new file mode 100644 index 0000000..23512d3 --- /dev/null +++ b/src/DataAggregator.Processor/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5148", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7173;http://localhost:5148", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/DataAggregator.Processor/appsettings.Development.json b/src/DataAggregator.Processor/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/DataAggregator.Processor/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/DataAggregator.Processor/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From 68ddf07d25f2976d7ee773210d5f18082a34158a Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:53:23 +0200 Subject: [PATCH 02/70] feat: implement base configuration --- .../Configuration/MachinePredictionConfig.cs | 47 +++++++++++++++++++ .../PredictionServiceConfiguration.cs | 22 +++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs create mode 100644 src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs diff --git a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs new file mode 100644 index 0000000..1f50583 --- /dev/null +++ b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs @@ -0,0 +1,47 @@ +namespace DataAggregator.Processor.Configuration; + +/// +/// Configuration for a specific machine prediction. +/// +public class MachinePredictionConfig +{ + /// + /// Gets or sets the machine name. + /// + public string MachineName { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether prediction is enabled for this machine. + /// + public bool Enabled { get; set; } = true; + + /// + /// Gets or sets the path to the ONNX model file. + /// + public string ModelPath { get; set; } = string.Empty; + + /// + /// Gets or sets the preprocessing strategy name. + /// + public string PreprocessingStrategy { get; set; } = string.Empty; + + /// + /// Gets or sets the list of input sensor names. + /// + public List InputSensors { get; set; } = []; + + /// + /// Gets or sets the name of the prediction sensor. + /// + public string PredictionSensorName { get; set; } = string.Empty; + + /// + /// Gets or sets the window size in seconds for data collection. + /// + public int WindowSizeSeconds { get; set; } = 60; + + /// + /// Gets or sets the cycle interval in seconds for this machine. + /// + public int CycleIntervalSeconds { get; set; } = 1; +} diff --git a/src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs b/src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs new file mode 100644 index 0000000..826000b --- /dev/null +++ b/src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs @@ -0,0 +1,22 @@ +namespace DataAggregator.Processor.Configuration; + +/// +/// Configuration for the prediction service. +/// +public class PredictionServiceConfiguration +{ + /// + /// Gets or sets the registration service URL. + /// + public string RegistrationServiceUrl { get; set; } = "http://localhost:5001"; + + /// + /// Gets or sets the global cycle interval in seconds. + /// + public int GlobalCycleIntervalSeconds { get; set; } = 1; + + /// + /// Gets or sets the list of machine prediction configurations. + /// + public List Machines { get; set; } = []; +} From 1cc22d4badd7b8595f8671ce5d9e047cebe94e61 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:53:31 +0200 Subject: [PATCH 03/70] feat: implement health check controller --- .../Controllers/HealthCheckController.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/DataAggregator.Processor/Controllers/HealthCheckController.cs diff --git a/src/DataAggregator.Processor/Controllers/HealthCheckController.cs b/src/DataAggregator.Processor/Controllers/HealthCheckController.cs new file mode 100644 index 0000000..4edb9a4 --- /dev/null +++ b/src/DataAggregator.Processor/Controllers/HealthCheckController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DataAggregator.Processor.Controllers; + +/// +/// Controller for health check endpoints. +/// +[ApiController] +[Route("api/[controller]")] +public class HealthCheckController : ControllerBase +{ + /// + /// Gets the health status of the prediction service. + /// + /// The health status. + [HttpGet] + public IActionResult Get() + => Ok(new { Status = "Healthy", Service = "DataAggregator.Processor", Timestamp = DateTime.UtcNow }); +} From 598efd88b972ddaecc2e1f14b06894ed1e19dc1e Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:53:51 +0200 Subject: [PATCH 04/70] feat: implement base interfaces --- .../DataStorage/IInfluxV3Repository.cs | 43 +++++++++++++++++++ .../PreProcessing/IPreprocessingStrategy.cs | 18 ++++++++ .../IPreprocessingStrategyFactory.cs | 14 ++++++ .../Prediction/IOnnxPredictionEngine.cs | 15 +++++++ .../IRegistrationServiceClient.cs | 16 +++++++ 5 files changed, 106 insertions(+) create mode 100644 src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs create mode 100644 src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs create mode 100644 src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs create mode 100644 src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs create mode 100644 src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs diff --git a/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs new file mode 100644 index 0000000..02cb9b7 --- /dev/null +++ b/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs @@ -0,0 +1,43 @@ +using DataAggregator.Collector.Shared.Models; + +namespace DataAggregator.Processor.Services.DataStorage; + +/// +/// Interface for InfluxDB v3 repository operations. +/// +public interface IInfluxV3Repository +{ + /// + /// Initializes the repository with connection parameters. + /// + /// The InfluxDB endpoint. + /// The authentication token. + /// The organization name. + public void InitializeAsync(string endpoint, string token, string org); + + /// + /// Queries measurements from InfluxDB for a specific time range and sensors. + /// + /// The table name (machine name). + /// The start time for the query. + /// The end time for the query. + /// The list of sensor names to query. + /// A list of measurement data. + public Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors); + + /// + /// Writes a single measurement to InfluxDB. + /// + /// The table name (machine name). + /// The measurement data to write. + /// A task representing the asynchronous operation. + public Task WriteMeasurementAsync(string table, IMeasurementData measurement); + + /// + /// Writes multiple measurements to InfluxDB in bulk. + /// + /// The table name (machine name). + /// The list of measurement data to write. + /// A task representing the asynchronous operation. + public Task BulkWriteMeasurementsAsync(string table, List measurements); +} diff --git a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs new file mode 100644 index 0000000..b06bb40 --- /dev/null +++ b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs @@ -0,0 +1,18 @@ +using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Configuration; + +namespace DataAggregator.Processor.Services.PreProcessing; + +/// +/// Interface for preprocessing strategies that convert raw measurement data into feature vectors for ML models. +/// +public interface IPreprocessingStrategy +{ + /// + /// Preprocesses a list of measurements into a feature vector for a single prediction sample. + /// + /// List of raw measurements from the data window. + /// Configuration for the machine prediction. + /// Feature vector as float array for a single sample. + public Task PreprocessAsync(List measurements, MachinePredictionConfig config); +} diff --git a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs new file mode 100644 index 0000000..71715c8 --- /dev/null +++ b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs @@ -0,0 +1,14 @@ +namespace DataAggregator.Processor.Services.PreProcessing; + +/// +/// Factory interface for creating preprocessing strategies based on strategy name. +/// +public interface IPreprocessingStrategyFactory +{ + /// + /// Creates a preprocessing strategy based on the strategy name. + /// + /// Name of the strategy to create. + /// Configured preprocessing strategy. + public IPreprocessingStrategy CreateStrategy(string strategyName); +} diff --git a/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs new file mode 100644 index 0000000..b7457cc --- /dev/null +++ b/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs @@ -0,0 +1,15 @@ +namespace DataAggregator.Processor.Services.Prediction; + +/// +/// Interface for ONNX prediction engine. +/// +public interface IOnnxPredictionEngine +{ + /// + /// Performs prediction using an ONNX model. + /// + /// The path to the ONNX model file. + /// The input data for prediction (single sample). + /// The prediction results as a float array. + public Task PredictAsync(string modelPath, float[] inputData); +} diff --git a/src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs b/src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs new file mode 100644 index 0000000..2bc7546 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs @@ -0,0 +1,16 @@ +using DataAggregator.Shared; + +namespace DataAggregator.Processor.Services.Registration; + +/// +/// Interface for the registration service client. +/// +public interface IRegistrationServiceClient +{ + /// + /// Gets device information from the registration service. + /// + /// The name of the device. + /// The device registration response. + public Task GetDeviceInfoAsync(string deviceName); +} From 134ac83ef2e3ee7e06c4dce1004254c513186e90 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:54:04 +0200 Subject: [PATCH 05/70] feat: implement influx repo --- .../DataStorage/InfluxV3Repository.cs | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs new file mode 100644 index 0000000..2e2f24f --- /dev/null +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -0,0 +1,205 @@ +using DataAggregator.Collector.Shared.Models; +using InfluxDB3.Client; +using InfluxDB3.Client.Config; +using InfluxDB3.Client.Write; +using Serilog; + +namespace DataAggregator.Processor.Services.DataStorage; + +/// +/// Implementation of InfluxDB v3 repository for prediction service. +/// +public class InfluxV3Repository : IInfluxV3Repository, IDisposable +{ + private readonly string _database = "Dataggregator"; + private InfluxDBClient? _client; + private string _organization = "Dataggregator"; + + /// + public void InitializeAsync(string endpoint, string token, string org) + { + _client?.Dispose(); + + try + { + var clientConfig = new ClientConfig() + { + Token = token, + Host = endpoint, + Organization = org, + Database = _database, + }; + + _client = new InfluxDBClient(clientConfig); + _organization = org; + + Log.Information("InfluxDB v3 repository initialized with endpoint: {Endpoint}", endpoint); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to initialize InfluxDB v3 repository"); + throw; + } + } + + /// + public async Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors) + { + if (_client == null) + { + throw new InvalidOperationException("InfluxDB client is not initialized."); + } + + try + { + // Build the Flux query - no pivot needed since we want the original structure + string sensorFilter = string.Join(" or ", sensors.Select(s => $"r[\"_field\"] == \"{s}\"")); + string query = $@" + from(bucket: ""{_database}"") + |> range(start: {startTime:yyyy-MM-ddTHH:mm:ssZ}, stop: {endTime:yyyy-MM-ddTHH:mm:ssZ}) + |> filter(fn: (r) => r[""_measurement""] == ""{table}"") + |> filter(fn: (r) => {sensorFilter})"; + + var measurements = new List(); + + await foreach (PointDataValues point in _client.QueryPoints(query)) + { + // point contains the original structure with all fields at once + // This matches how we write data: one row per timestamp with multiple sensors + System.Numerics.BigInteger? timestampBigInt = point.GetTimestamp(); + if (timestampBigInt == null) + { + Log.Warning("Skipping point with null timestamp"); + continue; + } + + // Convert BigInteger timestamp to DateTime + DateTime timestamp = DateTimeOffset.FromUnixTimeMilliseconds((long)(timestampBigInt.Value / 1_000_000)).DateTime; + + string[] fieldsNames = point.GetFieldNames(); + foreach (string fieldName in fieldsNames) + { + string sensorName = fieldName; + object? value = point.GetField(fieldName); + + // Only include sensors that were requested + if (sensors.Contains(sensorName) && value != null) + { + // Try to convert to float, handling different numeric types + float floatValue; + if (value is float f) + { + floatValue = f; + } + else if (value is double d) + { + floatValue = (float)d; + } + else if (value is int i) + { + floatValue = i; + } + else if (value is long l) + { + floatValue = l; + } + else if (float.TryParse(value.ToString(), out floatValue)) + { + // Successfully parsed + } + else + { + Log.Debug("Skipping non-numeric value for sensor {Sensor}: {Value}", sensorName, value); + continue; + } + + measurements.Add(new MeasurementData(timestamp, sensorName, floatValue)); + } + } + } + + Log.Debug( + "Queried {Count} measurements for table {Table} from {StartTime} to {EndTime}", + measurements.Count, + table, + startTime, + endTime); + + return measurements; + } + catch (Exception ex) + { + Log.Error(ex, "Failed to query measurements from InfluxDB for table {Table}", table); + throw; + } + } + + /// + public async Task WriteMeasurementAsync(string table, IMeasurementData measurement) + { + if (_client == null) + { + throw new InvalidOperationException("InfluxDB client is not initialized."); + } + + try + { + PointData point = PointData + .Measurement(table) + .SetTimestamp(DateTime.SpecifyKind(measurement.TimeStamp, DateTimeKind.Utc)) + .SetField(measurement.SensorName, measurement.GetRawValue()) + .SetTag("type", "Prediction"); + + await _client.WritePointsAsync(new[] { point }, null, WritePrecision.Ms); + + Log.Debug("Written measurement for table {Table}, sensor {Sensor}", table, measurement.SensorName); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to write measurement to InfluxDB for table {Table}", table); + throw; + } + } + + /// + public async Task BulkWriteMeasurementsAsync(string table, List measurements) + { + if (_client == null) + { + throw new InvalidOperationException("InfluxDB client is not initialized."); + } + + try + { + IEnumerable groupedPoints = measurements + .GroupBy(m => m.TimeStamp) + .Select(group => + { + var fields = group.ToDictionary( + m => m.SensorName, + m => m.GetRawValue()); + + return PointData + .Measurement(table) + .SetTimestamp(DateTime.SpecifyKind(group.Key, DateTimeKind.Utc)) + .SetFields(fields) + .SetTag("type", "Prediction"); + }); + + await _client.WritePointsAsync(groupedPoints, null, WritePrecision.Ms); + + Log.Debug("Written {Count} measurements to InfluxDB for table {Table}", measurements.Count, table); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to bulk write measurements to InfluxDB for table {Table}", table); + throw; + } + } + + /// + /// Disposes the InfluxDB client. + /// + public void Dispose() + => _client?.Dispose(); +} From 64a5c124d58042be766f7d6d977e88515eb1476b Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:54:12 +0200 Subject: [PATCH 06/70] feat: implement machine processor --- .../Prediction/MachinePredictionProcessor.cs | 176 ++++++++++++++++++ .../Prediction/OnnxPredictionEngine.cs | 95 ++++++++++ 2 files changed, 271 insertions(+) create mode 100644 src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs create mode 100644 src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs new file mode 100644 index 0000000..c10d7c3 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -0,0 +1,176 @@ +using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services.DataStorage; +using DataAggregator.Processor.Services.PreProcessing; +using DataAggregator.Processor.Services.Registration; +using DataAggregator.Shared; +using Serilog; + +namespace DataAggregator.Processor.Services.Prediction; + +/// +/// Processor for machine prediction operations. +/// +public class MachinePredictionProcessor +{ + private readonly IInfluxV3Repository _influxRepository; + private readonly IRegistrationServiceClient _registrationClient; + private readonly IOnnxPredictionEngine _predictionEngine; + private readonly IPreprocessingStrategyFactory _strategyFactory; + + // Track the last endpoint used to avoid unnecessary reinitializations + private string? _lastEndpoint; + + /// + /// Initializes a new instance of the class. + /// + /// The InfluxDB repository. + /// The registration service client. + /// The ONNX prediction engine. + /// The preprocessing strategy factory. + public MachinePredictionProcessor( + IInfluxV3Repository influxRepository, + IRegistrationServiceClient registrationClient, + IOnnxPredictionEngine predictionEngine, + IPreprocessingStrategyFactory strategyFactory) + { + _influxRepository = influxRepository; + _registrationClient = registrationClient; + _predictionEngine = predictionEngine; + _strategyFactory = strategyFactory; + } + + /// + /// Processes prediction for a specific machine. + /// + /// The machine prediction configuration. + /// A task representing the asynchronous operation. + public async Task ProcessAsync(MachinePredictionConfig config) + { + try + { + Log.Debug("Starting prediction process for machine {MachineName}", config.MachineName); + + // Get device info from registration service + DeviceRegistrationResponse deviceInfo = await _registrationClient.GetDeviceInfoAsync(config.MachineName); + + // Initialize InfluxDB repository only if endpoint changed + if (_lastEndpoint != deviceInfo.AssignedTimeSeriesEndpoint) + { + _influxRepository.InitializeAsync( + deviceInfo.AssignedTimeSeriesEndpoint, + deviceInfo.DeviceToken, + "Dataggregator"); + + _lastEndpoint = deviceInfo.AssignedTimeSeriesEndpoint; + Log.Debug("Reinitialized InfluxDB connection with new endpoint: {Endpoint}", _lastEndpoint); + } + + // Fetch data window + List measurements = await FetchDataWindowAsync(config); + + if (measurements.Count == 0) + { + Log.Warning("No measurements found for machine {MachineName} in the specified time window", config.MachineName); + return; + } + + // Preprocess data using strategy + float[] preprocessedData = await PreprocessDataAsync(measurements, config); + + if (preprocessedData == null || preprocessedData.Length == 0) + { + Log.Warning("Data preprocessing failed for machine {MachineName}", config.MachineName); + return; + } + + // Perform prediction + float[] predictions = await _predictionEngine.PredictAsync(config.ModelPath, preprocessedData); + + // Create prediction measurement + IMeasurementData predictionMeasurement = CreatePredictionMeasurementAsync(predictions, config); + + // Write prediction to InfluxDB + await _influxRepository.WriteMeasurementAsync(config.MachineName, predictionMeasurement); + + Log.Information( + "Prediction completed for machine {MachineName}: {PredictionValue}", + config.MachineName, + predictionMeasurement.GetRawValue()); + } + catch (Exception ex) + { + Log.Error(ex, "Error processing prediction for machine {MachineName}", config.MachineName); + throw; + } + } + + /// + /// Fetches data window for prediction. + /// + /// The machine prediction configuration. + /// A list of measurement data. + private async Task> FetchDataWindowAsync(MachinePredictionConfig config) + { + DateTime endTime = DateTime.UtcNow; + DateTime startTime = endTime.AddSeconds(config.WindowSizeSeconds); + + return await _influxRepository.QueryMeasurementsAsync( + config.MachineName, + startTime, + endTime, + config.InputSensors); + } + + /// + /// Preprocesses data using the configured strategy. + /// + /// The list of measurements. + /// The machine prediction configuration. + /// The preprocessed data as a float array for a single sample. + private async Task PreprocessDataAsync(List measurements, MachinePredictionConfig config) + { + try + { + if (string.IsNullOrEmpty(config.PreprocessingStrategy)) + { + Log.Error("No preprocessing strategy configured for machine {MachineName}", config.MachineName); + return Array.Empty(); + } + + IPreprocessingStrategy strategy = _strategyFactory.CreateStrategy(config.PreprocessingStrategy); + float[] preprocessedData = await strategy.PreprocessAsync(measurements, config); + + Log.Debug( + "Preprocessed data for machine {MachineName} using strategy {Strategy}: {Features} features", + config.MachineName, + config.PreprocessingStrategy, + preprocessedData.Length); + + return preprocessedData; + } + catch (Exception ex) + { + Log.Error(ex, "Error preprocessing data for machine {MachineName}", config.MachineName); + return Array.Empty(); + } + } + + /// + /// Creates a prediction measurement from the model output. + /// + /// The prediction results. + /// The machine prediction configuration. + /// The prediction measurement. + private IMeasurementData CreatePredictionMeasurementAsync(float[] predictions, MachinePredictionConfig config) + { + // For simplicity, we'll use the first prediction value + // In a real scenario, you might want to handle multiple outputs differently + float predictionValue = predictions.Length > 0 ? predictions[0] : 0.0f; + + return new MeasurementData( + DateTime.UtcNow, + config.PredictionSensorName, + predictionValue); + } +} diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs new file mode 100644 index 0000000..757eb8d --- /dev/null +++ b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs @@ -0,0 +1,95 @@ +using Microsoft.ML.OnnxRuntime; +using Microsoft.ML.OnnxRuntime.Tensors; +using Serilog; + +namespace DataAggregator.Processor.Services.Prediction; + +/// +/// Implementation of ONNX prediction engine. +/// +public class OnnxPredictionEngine : IOnnxPredictionEngine, IDisposable +{ + private readonly Dictionary _modelCache = []; + + /// + public async Task PredictAsync(string modelPath, float[] inputData) + { + try + { + InferenceSession session = LoadOrGetModel(modelPath); + + // Prepare input tensor + int[] inputShape = [1, inputData.Length]; + var inputTensor = new DenseTensor(inputData, inputShape); + + var inputs = new List + { + NamedOnnxValue.CreateFromTensor("input", inputTensor), + }; + + // Run inference in background thread + return await Task.Run(() => + { + using IDisposableReadOnlyCollection results = session.Run(inputs); + Tensor outputTensor = results[0].AsTensor(); + + float[] predictions = [.. outputTensor]; + + Log.Debug("Prediction completed for model {ModelPath} with {InputFeatures} input features", modelPath, inputData.Length); + + return predictions; + }); + } + catch (Exception ex) + { + Log.Error(ex, "Error during prediction with model {ModelPath}", modelPath); + throw; + } + } + + /// + /// Loads or gets a cached ONNX model. + /// + /// The path to the ONNX model file. + /// The inference session. + private InferenceSession LoadOrGetModel(string modelPath) + { + if (_modelCache.TryGetValue(modelPath, out InferenceSession? cachedSession)) + { + return cachedSession; + } + + if (!File.Exists(modelPath)) + { + Log.Error("ONNX model file not found: {ModelPath}", modelPath); + throw new FileNotFoundException($"ONNX model file not found: {modelPath}"); + } + + try + { + var session = new InferenceSession(modelPath); + _modelCache[modelPath] = session; + + Log.Information("Loaded ONNX model: {ModelPath}", modelPath); + return session; + } + catch (Exception ex) + { + Log.Error(ex, "Failed to load ONNX model: {ModelPath}", modelPath); + throw; + } + } + + /// + /// Disposes the cached models. + /// + public void Dispose() + { + foreach (InferenceSession session in _modelCache.Values) + { + session?.Dispose(); + } + + _modelCache.Clear(); + } +} From ccaaac3ddffb9957d6bd9abed148ec315ec72117 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:54:21 +0200 Subject: [PATCH 07/70] feat: implement pre-processing (WIP) --- .../ActuatorCurrentFeatureExtractor.cs | 113 ++++++++++++++++++ .../MathUtils.cs | 80 +++++++++++++ .../PreprocessingStrategyFactory.cs | 26 ++++ 3 files changed, 219 insertions(+) create mode 100644 src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs create mode 100644 src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs create mode 100644 src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs new file mode 100644 index 0000000..d248ac8 --- /dev/null +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -0,0 +1,113 @@ +using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Configuration; +using Serilog; + +namespace DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; + +/// +/// Feature extractor for actuator current data based on ML.NET approach. +/// Extracts 14 agnostic features with Z-score normalization. +/// +public class ActuatorCurrentFeatureExtractor : IPreprocessingStrategy +{ + /// + /// Preprocesses actuator current measurements into a feature vector. + /// + /// List of raw measurements from the data window. + /// Configuration for the machine prediction. + /// Feature vector as float array for a single sample (14 features). + public async Task PreprocessAsync(List measurements, MachinePredictionConfig config) + { + Log.Debug( + "Preprocessing {Count} measurements for machine {MachineName}", + measurements.Count, + config.MachineName); + + // TODO: Implement feature extraction logic + // For now, return placeholder features + float[] features = new float[14]; + + // Extract features from measurements + float[] extractedFeatures = ExtractFeatures(measurements, config.InputSensors); + + // Calculate additional features + float globalActivityRatio = CalculateGlobalActivityRatio(measurements); + float interAxisCorrelation = CalculateInterAxisCorrelation(measurements); + float temporalStability = CalculateTemporalStability(measurements); + float[] statisticalFeatures = CalculateStatisticalFeatures(measurements); + + // TODO: Combine all features and normalize + // For now, just copy extracted features + await Task.Run(() => Array.Copy(extractedFeatures, features, Math.Min(extractedFeatures.Length, features.Length))); + + Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); + return features; + } + + /// + /// Extracts basic features from measurements for specified sensors. + /// + /// List of measurements. + /// List of sensor names to extract features from. + /// Array of extracted features. + private float[] ExtractFeatures(List measurements, List sensors) + { + // TODO: Implement feature extraction logic + if (measurements == null || sensors == null || sensors.Count == 0) + { + Log.Warning("No measurements or sensors provided for feature extraction."); + return new float[14]; // Return empty features if no data + } + + Log.Debug("Extracting features for {SensorCount} sensors", sensors.Count); + return new float[14]; // Placeholder + } + + /// + /// Calculates the global activity ratio across all sensors. + /// + /// List of measurements. + /// Global activity ratio as float. + private float CalculateGlobalActivityRatio(List measurements) + { + // TODO: Implement global activity ratio calculation + Log.Debug("Calculating global activity ratio for {Count} measurements", measurements.Count); + return 0.0f; // Placeholder + } + + /// + /// Calculates inter-axis correlation between different sensors. + /// + /// List of measurements. + /// Inter-axis correlation as float. + private float CalculateInterAxisCorrelation(List measurements) + { + // TODO: Implement inter-axis correlation calculation + Log.Debug("Calculating inter-axis correlation for {Count} measurements", measurements.Count); + return 0.0f; // Placeholder + } + + /// + /// Calculates temporal stability of the measurements. + /// + /// List of measurements. + /// Temporal stability as float. + private float CalculateTemporalStability(List measurements) + { + // TODO: Implement temporal stability calculation + Log.Debug("Calculating temporal stability for {Count} measurements", measurements.Count); + return 0.0f; // Placeholder + } + + /// + /// Calculates statistical features from the measurements. + /// + /// List of measurements. + /// Array of statistical features. + private float[] CalculateStatisticalFeatures(List measurements) + { + // TODO: Implement statistical feature calculation + Log.Debug("Calculating statistical features for {Count} measurements", measurements.Count); + return new float[5]; // Placeholder + } +} diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs new file mode 100644 index 0000000..8a26d9e --- /dev/null +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs @@ -0,0 +1,80 @@ +namespace DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; + +/// +/// Utility class for mathematical operations used in feature extraction. +/// +public static class MathUtils +{ +#pragma warning disable IDE0022 // Use expression body for method + + /// + /// Calculates the mean of a collection of values. + /// + /// Collection of float values. + /// Mean value. +#pragma warning disable IDE0060 // Remove unused parameter + public static float Mean(IEnumerable values) + { + // TODO: Implement mean calculation + return 0.0f; // Placeholder + } + + /// + /// Calculates the standard deviation of a collection of values. + /// + /// Collection of float values. + /// Standard deviation. + public static float StandardDeviation(IEnumerable values) + { + // TODO: Implement standard deviation calculation + return 0.0f; // Placeholder + } + + /// + /// Calculates the percentile value from a collection of values. + /// + /// Collection of float values. + /// Percentile value (0-100). + /// Percentile value. + public static float Percentile(IEnumerable values, float percentile) + { + // TODO: Implement percentile calculation + return 0.0f; // Placeholder + } + + /// + /// Calculates the skewness of a collection of values. + /// + /// Collection of float values. + /// Skewness value. + public static float Skewness(IEnumerable values) + { + // TODO: Implement skewness calculation + return 0.0f; // Placeholder + } + + /// + /// Calculates the kurtosis of a collection of values. + /// + /// Collection of float values. + /// Kurtosis value. + public static float Kurtosis(IEnumerable values) + { + // TODO: Implement kurtosis calculation + return 0.0f; // Placeholder + } + + /// + /// Calculates the correlation coefficient between two collections of values. + /// + /// First collection of values. + /// Second collection of values. + /// Correlation coefficient. + public static float Correlation(IEnumerable x, IEnumerable y) + { + // TODO: Implement correlation calculation + return 0.0f; // Placeholder + } +#pragma warning restore IDE0022 // Use expression body for method +#pragma warning restore IDE0060 // Remove unused parameter +} diff --git a/src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs b/src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs new file mode 100644 index 0000000..f7c7689 --- /dev/null +++ b/src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs @@ -0,0 +1,26 @@ +using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; +using Serilog; + +namespace DataAggregator.Processor.Services.PreProcessing; + +/// +/// Factory implementation for creating preprocessing strategies. +/// +public class PreprocessingStrategyFactory : IPreprocessingStrategyFactory +{ + /// + /// Creates a preprocessing strategy based on the strategy name. + /// + /// Name of the strategy to create. + /// Configured preprocessing strategy. + public IPreprocessingStrategy CreateStrategy(string strategyName) + { + Log.Information("Creating preprocessing strategy: {StrategyName}", strategyName); + + return strategyName.ToLowerInvariant() switch + { + "actuatorcurrent" => new ActuatorCurrentFeatureExtractor(), + _ => throw new ArgumentException($"Unknown preprocessing strategy: {strategyName}", nameof(strategyName)), + }; + } +} From b207b7ffd24d4fae478f8f58b560ed4b835fd8c0 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:54:35 +0200 Subject: [PATCH 08/70] feat: implement base services and program --- src/DataAggregator.Processor/Program.cs | 82 ++++++- .../Services/PredictionBackgroundService.cs | 208 ++++++++++++++++++ .../Registration/RegistrationServiceClient.cs | 45 ++++ 3 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 src/DataAggregator.Processor/Services/PredictionBackgroundService.cs create mode 100644 src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index 1760df1..e031d34 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -1,6 +1,80 @@ -var builder = WebApplication.CreateBuilder(args); -var app = builder.Build(); +using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services; +using DataAggregator.Processor.Services.DataStorage; +using DataAggregator.Processor.Services.Prediction; +using DataAggregator.Processor.Services.PreProcessing; +using DataAggregator.Processor.Services.Registration; +using Serilog; -app.MapGet("/", () => "Hello World!"); +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); -app.Run(); +// Configure Serilog from appsettings.json +Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(builder.Configuration) + .WriteTo.Console() + .Enrich.FromLogContext() + .CreateLogger(); + +builder.Host.UseSerilog(); + +// Add services to the container +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new() { Title = "DataAggregator Processor API", Version = "v1" })); + +// Configure HTTP clients +builder.Services.AddHttpClient(); +builder.Services.AddHttpClient("RegistrationClient", client => +{ + string registrationEndpoint = builder.Configuration["RegistrationService:Endpoint"] ?? "http://localhost:5001"; + client.BaseAddress = new Uri(registrationEndpoint); + client.DefaultRequestHeaders.Add("Accept", "application/json"); +}); + +// Register health checks +builder.Services.AddHealthChecks(); + +// Configure prediction service +builder.Services.Configure(builder.Configuration.GetSection("PredictionService")); + +// Register services +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Register background service +builder.Services.AddHostedService(); + +WebApplication app = builder.Build(); + +// Configure the HTTP request pipeline +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "DataAggregator Processor API v1"); + c.RoutePrefix = string.Empty; + }); +} + +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); +app.MapHealthChecks("/health"); + +try +{ + Log.Information("Starting the processor application..."); + app.Run(); +} +catch (Exception ex) +{ + Log.Fatal(ex, "Processor application failed to start."); +} +finally +{ + Log.CloseAndFlush(); +} diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs new file mode 100644 index 0000000..7a5716f --- /dev/null +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -0,0 +1,208 @@ +using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services.Prediction; +using Microsoft.Extensions.Options; +using Serilog; + +namespace DataAggregator.Processor.Services; + +/// +/// Background service for managing machine predictions. +/// +/// +/// Initializes a new instance of the class. +/// +/// The prediction service configuration. +/// The machine prediction processor. +public class PredictionBackgroundService( + IOptions configuration, + MachinePredictionProcessor predictionProcessor) : BackgroundService +{ + private readonly Dictionary _machineTimers = []; + private readonly Dictionary _machineErrors = []; + + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + Log.Information("Starting prediction background service"); + + // Validate configuration + ValidateConfigurationAsync(); + + // Schedule machines + foreach (MachinePredictionConfig machineConfig in configuration.Value.Machines) + { + if (machineConfig.Enabled) + { + ScheduleMachine(machineConfig); + } + } + + Log.Information( + "Prediction background service started with {MachineCount} machines", + configuration.Value.Machines.Count(m => m.Enabled)); + + // Keep the service running + while (!stoppingToken.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); + } + } + catch (Exception ex) + { + Log.Fatal(ex, "Fatal error in prediction background service"); + throw; + } + } + + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + Log.Information("Stopping prediction background service"); + + // Dispose all timers + foreach (Timer timer in _machineTimers.Values) + { + timer?.Dispose(); + } + + _machineTimers.Clear(); + + await base.StopAsync(cancellationToken); + } + + /// + /// Validates the configuration and checks for required files. + /// + private void ValidateConfigurationAsync() + { + var enabledMachines = configuration.Value.Machines.Where(m => m.Enabled).ToList(); + + if (!enabledMachines.Any()) + { + Log.Warning("No enabled machines found in configuration"); + return; + } + + foreach (MachinePredictionConfig? machineConfig in enabledMachines) + { + try + { + // Check if ONNX model file exists + if (!File.Exists(machineConfig.ModelPath)) + { + Log.Error( + "ONNX model file not found for machine {MachineName}: {ModelPath}", + machineConfig.MachineName, + machineConfig.ModelPath); + + throw new FileNotFoundException($"ONNX model file not found: {machineConfig.ModelPath}"); + } + + // Validate configuration + if (string.IsNullOrEmpty(machineConfig.MachineName)) + { + Log.Error( + "Machine name is not configured for machine at index {Index}", + enabledMachines.IndexOf(machineConfig)); + + throw new InvalidOperationException("Machine name is not configured"); + } + + if (machineConfig.InputSensors.Count == 0) + { + Log.Error( + "No input sensors configured for machine {MachineName}", + machineConfig.MachineName); + + throw new InvalidOperationException($"No input sensors configured for machine {machineConfig.MachineName}"); + } + + if (string.IsNullOrEmpty(machineConfig.PredictionSensorName)) + { + Log.Error( + "Prediction sensor name is not configured for machine {MachineName}", + machineConfig.MachineName); + throw new InvalidOperationException($"Prediction sensor name is not configured for machine {machineConfig.MachineName}"); + } + + if (string.IsNullOrEmpty(machineConfig.PreprocessingStrategy)) + { + Log.Error( + "Preprocessing strategy is not configured for machine {MachineName}", + machineConfig.MachineName); + + throw new InvalidOperationException($"Preprocessing strategy is not configured for machine {machineConfig.MachineName}"); + } + + Log.Information("Configuration validated for machine {MachineName}", machineConfig.MachineName); + } + catch (Exception ex) + { + Log.Error(ex, "Configuration validation failed for machine {MachineName}", machineConfig.MachineName); + throw; + } + } + } + + /// + /// Schedules prediction processing for a machine. + /// + /// The machine prediction configuration. + private void ScheduleMachine(MachinePredictionConfig machineConfig) + { + try + { + var interval = TimeSpan.FromSeconds(machineConfig.CycleIntervalSeconds); + + var timer = new Timer(async _ => await ProcessMachineAsync(machineConfig), null, TimeSpan.Zero, interval); + + _machineTimers[machineConfig.MachineName] = timer; + _machineErrors[machineConfig.MachineName] = false; + + Log.Information( + "Scheduled prediction processing for machine {MachineName} with interval {Interval}", + machineConfig.MachineName, + interval); + } + catch (Exception ex) + { + Log.Error(ex, "Failed to schedule machine {MachineName}", machineConfig.MachineName); + throw; + } + } + + /// + /// Processes prediction for a specific machine. + /// + /// The machine prediction configuration. + /// A task representing the asynchronous operation. + private async Task ProcessMachineAsync(MachinePredictionConfig machineConfig) + { + // Skip if machine has errors + if (_machineErrors.TryGetValue(machineConfig.MachineName, out bool hasError) && hasError) + { + Log.Debug("Skipping prediction for machine {MachineName} due to previous errors", machineConfig.MachineName); + return; + } + + try + { + await predictionProcessor.ProcessAsync(machineConfig); + + // Clear error flag if processing succeeds + if (_machineErrors.ContainsKey(machineConfig.MachineName)) + { + _machineErrors[machineConfig.MachineName] = false; + } + } + catch (Exception ex) + { + Log.Error(ex, "Error processing prediction for machine {MachineName}", machineConfig.MachineName); + + // Set error flag to stop processing for this machine + _machineErrors[machineConfig.MachineName] = true; + } + } +} diff --git a/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs new file mode 100644 index 0000000..92d70b5 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs @@ -0,0 +1,45 @@ +using DataAggregator.Shared; +using Serilog; + +namespace DataAggregator.Processor.Services.Registration; + +/// +/// Implementation of the registration service client. +/// +/// +/// Initializes a new instance of the class. +/// +/// The HTTP client. +public class RegistrationServiceClient(HttpClient httpClient) : IRegistrationServiceClient +{ + /// + public async Task GetDeviceInfoAsync(string deviceName) + { + try + { + HttpResponseMessage response = await httpClient.GetAsync($"/api/DeviceRegistration/device/{deviceName}"); + + if (response.IsSuccessStatusCode) + { + DeviceRegistrationResponse? deviceInfo = await response.Content.ReadFromJsonAsync(); + if (deviceInfo != null) + { + Log.Debug( + "Retrieved device info for {DeviceName}: {Endpoint}", + deviceName, + deviceInfo.AssignedTimeSeriesEndpoint); + + return deviceInfo; + } + } + + Log.Warning("Failed to retrieve device info for {DeviceName}. Status: {StatusCode}", deviceName, response.StatusCode); + throw new InvalidOperationException($"Failed to retrieve device info for {deviceName}. Status: {response.StatusCode}"); + } + catch (Exception ex) + { + Log.Error(ex, "Error retrieving device info for {DeviceName}", deviceName); + throw; + } + } +} From 070b575922fbd2c6a57310ea1934efccdd8a386f Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:58:54 +0200 Subject: [PATCH 09/70] feat: add appsettinsg --- .../DataAggregator.Processor.csproj | 2 + src/DataAggregator.Processor/Dockerfile | 30 +++++++++ .../appsettings.Development.json | 13 +++- src/DataAggregator.Processor/appsettings.json | 62 ++++++++++++++++++- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/DataAggregator.Processor/Dockerfile diff --git a/src/DataAggregator.Processor/DataAggregator.Processor.csproj b/src/DataAggregator.Processor/DataAggregator.Processor.csproj index 91b16c7..9696a0e 100644 --- a/src/DataAggregator.Processor/DataAggregator.Processor.csproj +++ b/src/DataAggregator.Processor/DataAggregator.Processor.csproj @@ -11,6 +11,7 @@ + @@ -18,6 +19,7 @@ + diff --git a/src/DataAggregator.Processor/Dockerfile b/src/DataAggregator.Processor/Dockerfile new file mode 100644 index 0000000..061cb5e --- /dev/null +++ b/src/DataAggregator.Processor/Dockerfile @@ -0,0 +1,30 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src +COPY ["src/DataAggregator.Processor/DataAggregator.Processor.csproj", "src/DataAggregator.Processor/"] +COPY ["src/DataAggregator.Shared/DataAggregator.Shared.csproj", "src/DataAggregator.Shared/"] +COPY ["src/DataAggregator.Collector.Shared/DataAggregator.Collector.Shared.csproj", "src/DataAggregator.Collector.Shared/"] +RUN dotnet restore "src/DataAggregator.Processor/DataAggregator.Processor.csproj" +COPY . . +WORKDIR "/src/src/DataAggregator.Processor" +RUN dotnet build "DataAggregator.Processor.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "DataAggregator.Processor.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +# Create models directory for ONNX models +RUN mkdir -p /app/models + +# Set environment variables +ENV ASPNETCORE_URLS=http://+:80 +ENV ASPNETCORE_ENVIRONMENT=Production + +ENTRYPOINT ["dotnet", "DataAggregator.Processor.dll"] \ No newline at end of file diff --git a/src/DataAggregator.Processor/appsettings.Development.json b/src/DataAggregator.Processor/appsettings.Development.json index 0c208ae..f378faf 100644 --- a/src/DataAggregator.Processor/appsettings.Development.json +++ b/src/DataAggregator.Processor/appsettings.Development.json @@ -1,8 +1,17 @@ { "Logging": { "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Default": "Debug", + "Microsoft.AspNetCore": "Information" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "System": "Information" + } } } } diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index 10f68b8..969b4b8 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -5,5 +5,65 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" + } + } + ] + }, + "AllowedHosts": "*", + "RegistrationService": { + "Endpoint": "http://localhost:5001" + }, + "PredictionService": { + "RegistrationServiceUrl": "http://localhost:5001", + "GlobalCycleIntervalSeconds": 30, + "Machines": [ + { + "MachineName": "OpenCN-Machine-001", + "Enabled": true, + "ModelPath": "/app/models/opencn-machine-001.onnx", + "PreprocessingStrategy": "ActuatorCurrentFeatureExtractor", + "InputSensors": [ + "V1", + "V2", + "V3", + "V4", + "V5", + "V6" + ], + "PredictionSensorName": "PredictedActuatorCurrent", + "WindowSizeSeconds": 60, + "CycleIntervalSeconds": 30 + }, + { + "MachineName": "OpenCN-Machine-002", + "Enabled": false, + "ModelPath": "/app/models/opencn-machine-002.onnx", + "PreprocessingStrategy": "ActuatorCurrentFeatureExtractor", + "InputSensors": [ + "V1", + "V2", + "V3", + "V4", + "V5", + "V6" + ], + "PredictionSensorName": "PredictedActuatorCurrent", + "WindowSizeSeconds": 60, + "CycleIntervalSeconds": 30 + } + ] + } } From 6229f90d2c2c3f782bf9b856e9a73f0c2a6df01d Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 18:59:02 +0200 Subject: [PATCH 10/70] feat: add auto-generated readme --- src/DataAggregator.Processor/README.md | 201 +++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 src/DataAggregator.Processor/README.md diff --git a/src/DataAggregator.Processor/README.md b/src/DataAggregator.Processor/README.md new file mode 100644 index 0000000..995a406 --- /dev/null +++ b/src/DataAggregator.Processor/README.md @@ -0,0 +1,201 @@ +(Simple README, generated with a LLM) + +# DataAggregator.Processor + +Prediction service for the DataAggregator project that enriches collected data with predictions based on ONNX models. + +## Features + +- **Real-time predictions** : Executes ONNX models to predict values based on sensor data +- **Per-machine configuration** : Each machine can have its own model and configuration +- **InfluxDB integration** : Stores predictions in the same database as raw data +- **Robust error handling** : Automatically stops predictions for a machine in case of error +- **Health checks** : Monitoring endpoints to verify service status +- **Connection optimization** : Reuses InfluxDB connections to avoid unnecessary reinitializations +- **Preprocessing strategies** : Strategy pattern for different data preparation methods + +## Configuration + +### appsettings.json + +```json +{ + "PredictionService": { + "RegistrationServiceUrl": "http://localhost:5001", + "GlobalCycleIntervalSeconds": 30, + "Machines": [ + { + "MachineName": "OpenCN-Machine-001", + "Enabled": true, + "ModelPath": "/app/models/opencn-machine-001.onnx", + "PreprocessingStrategy": "ActuatorCurrentFeatureExtractor", + "InputSensors": ["V1", "V2", "V3", "V4", "V5", "V6"], + "PredictionSensorName": "PredictedActuatorCurrent", + "WindowSizeSeconds": 60, + "CycleIntervalSeconds": 30 + } + ] + } +} +``` + +### Configuration parameters + +- **MachineName** : Machine name (must match the one registered in the Registration Service) +- **Enabled** : Enables/disables predictions for this machine +- **ModelPath** : Path to the ONNX model file (in Docker volume `/app/models`) +- **PreprocessingStrategy** : Name of the preprocessing strategy to use +- **InputSensors** : List of input sensors required by the model +- **PredictionSensorName** : Name of the prediction sensor (will be stored in InfluxDB) +- **WindowSizeSeconds** : Data window size in seconds +- **CycleIntervalSeconds** : Prediction execution frequency in seconds + +## Usage with Docker + +### 1. Prepare ONNX models + +Place your ONNX models in the `models/` folder at the project root: + +``` +models/ +├── opencn-machine-001.onnx +├── machine-002.onnx +└── ... +``` + +### 2. Launch the service + +```bash +# Launch all services +docker-compose up + +# Launch only the prediction service +docker-compose up processor +``` + +### 3. Check service status + +```bash +# Health check +curl http://localhost:5002/api/healthcheck + +# Swagger UI (in development) +http://localhost:5002/swagger +``` + +## Architecture + +### Main components + +1. **PredictionBackgroundService** : Background service that manages predictions for all machines +2. **MachinePredictionProcessor** : Processes predictions for a specific machine +3. **OnnxPredictionEngine** : ONNX prediction engine with model caching +4. **InfluxV3Repository** : Interface with InfluxDB for reading/writing data +5. **RegistrationServiceClient** : Client to retrieve machine information +6. **PreprocessingStrategyFactory** : Factory to create preprocessing strategies +7. **ActuatorCurrentFeatureExtractor** : Preprocessing strategy for current sensors + +### Data flow + +1. The service retrieves machine configuration from `appsettings.json` +2. For each enabled machine, it retrieves its information from the Registration Service +3. It reads a data window from InfluxDB for input sensors +4. It prepares data using the configured preprocessing strategy +5. It executes the prediction with the ONNX model +6. It stores the result in InfluxDB with the "Prediction" tag + +### Optimizations + +- **InfluxDB connection cache** : Avoids unnecessary client reinitializations +- **ONNX model cache** : Loads models only once in memory +- **Error handling** : Continues processing even if one machine fails + +## Error handling + +- **Missing model** : Service stops at startup if an ONNX model is not found +- **Insufficient data** : Predictions are stopped for a machine if input data doesn't match expected format +- **Prediction errors** : Errors are logged and predictions are temporarily stopped for the concerned machine +- **Connection issues** : Automatic InfluxDB reconnection handling + +## Monitoring + +### Logs + +The service uses Serilog for logging with the following levels: +- **Information** : Service start/stop, successful predictions +- **Warning** : Missing data, invalid configurations +- **Error** : Prediction errors, connection issues +- **Debug** : Operation details (in development mode) + +### Health Check + +The `/api/healthcheck` endpoint returns service status: +- **200 OK** : Service functional +- **500 Internal Server Error** : Service issue + +## Development + +### Project structure + +``` +src/DataAggregator.Processor/ +├── Configuration/ +│ ├── MachinePredictionConfig.cs +│ └── PredictionServiceConfiguration.cs +├── Controllers/ +│ └── HealthCheckController.cs +├── Services/ +│ ├── DataAccess/ +│ │ ├── IInfluxV3Repository.cs +│ │ ├── InfluxV3Repository.cs +│ │ ├── IRegistrationServiceClient.cs +│ │ └── RegistrationServiceClient.cs +│ ├── Prediction/ +│ │ ├── IOnnxPredictionEngine.cs +│ │ ├── OnnxPredictionEngine.cs +│ │ └── MachinePredictionProcessor.cs +│ ├── Preprocessing/ +│ │ ├── IPreprocessingStrategy.cs +│ │ ├── IPreprocessingStrategyFactory.cs +│ │ ├── PreprocessingStrategyFactory.cs +│ │ ├── ActuatorCurrentFeatureExtractor.cs +│ │ └── MathUtils.cs +│ └── Background/ +│ └── PredictionBackgroundService.cs +├── appsettings.json +├── appsettings.Development.json +├── Program.cs +└── Dockerfile +``` + +### Testing + +To test the service locally: + +1. Ensure Registration and InfluxDB services are started +2. Place a test ONNX model in the `models/` folder +3. Configure `appsettings.json` with correct values +4. Launch the service: `dotnet run` + +## Preprocessing strategies + +### ActuatorCurrentFeatureExtractor + +This strategy extracts 14 features from current sensor data: + +1. **Statistical features** : Mean, standard deviation, min, max, etc. +2. **Correlation features** : Correlation between axes +3. **Activity features** : Global activity ratio +4. **Stability features** : Temporal stability of signals + +Features are normalized with Z-score and formatted for ONNX model input. + +## Integration with DataAggregator ecosystem + +The prediction service integrates perfectly with the existing architecture: + +- **Reuses** shared data models (`IMeasurementData`, `DeviceRegistrationResponse`) +- **Follows** the same configuration and logging patterns +- **Uses** the same technologies (Serilog, ASP.NET Core, Docker) +- **Integrates** with existing services without modifying them +- **Optimizes** performance with caches and reused connections \ No newline at end of file From 19840b2ab78ef77231d84921c6732af108185735 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 13 Jul 2025 21:04:41 +0200 Subject: [PATCH 11/70] feat: update sln --- DataAggregator.sln | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/DataAggregator.sln b/DataAggregator.sln index 28125eb..f3793ca 100644 --- a/DataAggregator.sln +++ b/DataAggregator.sln @@ -40,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Processor", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{330C7B17-DB90-458C-B630-99F9C1B5EA45}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{EB9A5576-3E00-4007-8C72-2235E0A1546D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,36 +54,112 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Debug|x64.ActiveCfg = Debug|x64 + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Debug|x64.Build.0 = Debug|x64 + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Debug|x86.ActiveCfg = Debug|x86 + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Debug|x86.Build.0 = Debug|x86 {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Release|Any CPU.Build.0 = Release|Any CPU + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Release|x64.ActiveCfg = Release|x64 + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Release|x64.Build.0 = Release|x64 + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Release|x86.ActiveCfg = Release|x86 + {4DA834BA-134A-4F58-B6FF-FF0820FB9FA9}.Release|x86.Build.0 = Release|x86 {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Debug|x64.ActiveCfg = Debug|x64 + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Debug|x64.Build.0 = Debug|x64 + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Debug|x86.ActiveCfg = Debug|x86 + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Debug|x86.Build.0 = Debug|x86 {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Release|Any CPU.Build.0 = Release|Any CPU + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Release|x64.ActiveCfg = Release|x64 + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Release|x64.Build.0 = Release|x64 + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Release|x86.ActiveCfg = Release|x86 + {54E71A40-291A-4FEB-9E13-1545172AF9EE}.Release|x86.Build.0 = Release|x86 {E593EA7B-F713-4444-AE40-FDCEF462660A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E593EA7B-F713-4444-AE40-FDCEF462660A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Debug|x64.ActiveCfg = Debug|x64 + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Debug|x64.Build.0 = Debug|x64 + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Debug|x86.ActiveCfg = Debug|x86 + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Debug|x86.Build.0 = Debug|x86 {E593EA7B-F713-4444-AE40-FDCEF462660A}.Release|Any CPU.ActiveCfg = Release|Any CPU {E593EA7B-F713-4444-AE40-FDCEF462660A}.Release|Any CPU.Build.0 = Release|Any CPU + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Release|x64.ActiveCfg = Release|x64 + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Release|x64.Build.0 = Release|x64 + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Release|x86.ActiveCfg = Release|x86 + {E593EA7B-F713-4444-AE40-FDCEF462660A}.Release|x86.Build.0 = Release|x86 {63D70F2C-9C7D-4397-8150-37F343949530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {63D70F2C-9C7D-4397-8150-37F343949530}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63D70F2C-9C7D-4397-8150-37F343949530}.Debug|x64.ActiveCfg = Debug|x64 + {63D70F2C-9C7D-4397-8150-37F343949530}.Debug|x64.Build.0 = Debug|x64 + {63D70F2C-9C7D-4397-8150-37F343949530}.Debug|x86.ActiveCfg = Debug|x86 + {63D70F2C-9C7D-4397-8150-37F343949530}.Debug|x86.Build.0 = Debug|x86 {63D70F2C-9C7D-4397-8150-37F343949530}.Release|Any CPU.ActiveCfg = Release|Any CPU {63D70F2C-9C7D-4397-8150-37F343949530}.Release|Any CPU.Build.0 = Release|Any CPU + {63D70F2C-9C7D-4397-8150-37F343949530}.Release|x64.ActiveCfg = Release|x64 + {63D70F2C-9C7D-4397-8150-37F343949530}.Release|x64.Build.0 = Release|x64 + {63D70F2C-9C7D-4397-8150-37F343949530}.Release|x86.ActiveCfg = Release|x86 + {63D70F2C-9C7D-4397-8150-37F343949530}.Release|x86.Build.0 = Release|x86 {FB0B4F80-5801-407F-8425-54069C93DB60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FB0B4F80-5801-407F-8425-54069C93DB60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB0B4F80-5801-407F-8425-54069C93DB60}.Debug|x64.ActiveCfg = Debug|x64 + {FB0B4F80-5801-407F-8425-54069C93DB60}.Debug|x64.Build.0 = Debug|x64 + {FB0B4F80-5801-407F-8425-54069C93DB60}.Debug|x86.ActiveCfg = Debug|x86 + {FB0B4F80-5801-407F-8425-54069C93DB60}.Debug|x86.Build.0 = Debug|x86 {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|Any CPU.ActiveCfg = Release|Any CPU {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|Any CPU.Build.0 = Release|Any CPU + {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|x64.ActiveCfg = Release|x64 + {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|x64.Build.0 = Release|x64 + {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|x86.ActiveCfg = Release|x86 + {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|x86.Build.0 = Release|x86 {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x64.ActiveCfg = Debug|x64 + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x64.Build.0 = Debug|x64 + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x86.ActiveCfg = Debug|x86 + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x86.Build.0 = Debug|x86 {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|Any CPU.ActiveCfg = Release|Any CPU {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|Any CPU.Build.0 = Release|Any CPU + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x64.ActiveCfg = Release|x64 + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x64.Build.0 = Release|x64 + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x86.ActiveCfg = Release|x86 + {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x86.Build.0 = Release|x86 {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|x64.ActiveCfg = Debug|x64 + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|x64.Build.0 = Debug|x64 + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|x86.ActiveCfg = Debug|x86 + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|x86.Build.0 = Debug|x86 {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Release|Any CPU.ActiveCfg = Release|Any CPU {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Release|Any CPU.Build.0 = Release|Any CPU + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Release|x64.ActiveCfg = Release|x64 + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Release|x64.Build.0 = Release|x64 + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Release|x86.ActiveCfg = Release|x86 + {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Release|x86.Build.0 = Release|x86 {2C10378E-0937-40E3-940E-F236F6E8B95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2C10378E-0937-40E3-940E-F236F6E8B95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Debug|x64.ActiveCfg = Debug|x64 + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Debug|x64.Build.0 = Debug|x64 + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Debug|x86.ActiveCfg = Debug|x86 + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Debug|x86.Build.0 = Debug|x86 {2C10378E-0937-40E3-940E-F236F6E8B95B}.Release|Any CPU.ActiveCfg = Release|Any CPU {2C10378E-0937-40E3-940E-F236F6E8B95B}.Release|Any CPU.Build.0 = Release|Any CPU + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Release|x64.ActiveCfg = Release|x64 + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Release|x64.Build.0 = Release|x64 + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Release|x86.ActiveCfg = Release|x86 + {2C10378E-0937-40E3-940E-F236F6E8B95B}.Release|x86.Build.0 = Release|x86 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Debug|Any CPU.Build.0 = Debug|Any CPU + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Debug|x64.ActiveCfg = Debug|x64 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Debug|x64.Build.0 = Debug|x64 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Debug|x86.ActiveCfg = Debug|x86 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Debug|x86.Build.0 = Debug|x86 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|Any CPU.ActiveCfg = Release|Any CPU + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|Any CPU.Build.0 = Release|Any CPU + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x64.ActiveCfg = Release|x64 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x64.Build.0 = Release|x64 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x86.ActiveCfg = Release|x86 + {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -98,6 +176,8 @@ Global {F5250AC4-9CCC-432B-8725-DAC6AE01CCF0} = {95F8787D-6FE2-4173-8450-6762EA6FAE1F} {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182} = {95F8787D-6FE2-4173-8450-6762EA6FAE1F} {2C10378E-0937-40E3-940E-F236F6E8B95B} = {F5250AC4-9CCC-432B-8725-DAC6AE01CCF0} + {039EC00D-5EDF-4C48-B449-29CFB6750232} = {EB9A5576-3E00-4007-8C72-2235E0A1546D} + {EB9A5576-3E00-4007-8C72-2235E0A1546D} = {E1AD9667-4C40-4CAF-8096-5FA749EBB2B1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9C8E6DBA-9F77-4FD0-9A1D-25150F7806FF} From c48dbd347e5f22910aaabf1ed01456240923bfe8 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 11:42:06 +0200 Subject: [PATCH 12/70] feat: adapt app.config --- src/DataAggregator.Processor/appsettings.json | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index 969b4b8..d71a7b7 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -5,6 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, + "Serilog": { "MinimumLevel": { "Default": "Information", @@ -12,23 +13,17 @@ "Microsoft": "Warning", "System": "Warning" } - }, - "WriteTo": [ - { - "Name": "Console", - "Args": { - "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" - } - } - ] + } }, + "AllowedHosts": "*", "RegistrationService": { "Endpoint": "http://localhost:5001" }, + "PredictionService": { "RegistrationServiceUrl": "http://localhost:5001", - "GlobalCycleIntervalSeconds": 30, + "GlobalCycleIntervalSeconds": 1, "Machines": [ { "MachineName": "OpenCN-Machine-001", @@ -38,31 +33,11 @@ "InputSensors": [ "V1", "V2", - "V3", - "V4", - "V5", - "V6" - ], - "PredictionSensorName": "PredictedActuatorCurrent", - "WindowSizeSeconds": 60, - "CycleIntervalSeconds": 30 - }, - { - "MachineName": "OpenCN-Machine-002", - "Enabled": false, - "ModelPath": "/app/models/opencn-machine-002.onnx", - "PreprocessingStrategy": "ActuatorCurrentFeatureExtractor", - "InputSensors": [ - "V1", - "V2", - "V3", - "V4", - "V5", - "V6" + "V3" ], - "PredictionSensorName": "PredictedActuatorCurrent", - "WindowSizeSeconds": 60, - "CycleIntervalSeconds": 30 + "PredictionSensorName": "PredictedCurrentState", + "WindowSizeSeconds": 1, + "CycleIntervalSeconds": 1 } ] } From 1768f56203b5fa5924de79cfdcc0b0dc213fdce6 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 14:18:37 +0200 Subject: [PATCH 13/70] fix: sensor usage in processor --- .../Abstraction/Configuration/SensorConfig.cs | 2 +- .../Registration/RegistrationService.cs | 2 +- .../DataStorage/IInfluxV3Repository.cs | 15 +-- .../DataStorage/InfluxV3Repository.cs | 91 +++++-------- .../Prediction/MachinePredictionProcessor.cs | 102 +++++++++------ .../IRegistrationServiceClient.cs | 8 +- .../Registration/RegistrationServiceClient.cs | 22 ++-- .../Domain/Extension/SensorExtension.cs | 3 +- .../DeviceManagement/Domain/Sensor.cs | 6 + .../Configuration/SensorConfiguration.cs | 6 + .../Services/DeviceRegistrationService.cs | 1 + ...250714120650_AddSensorDataType.Designer.cs | 123 ++++++++++++++++++ .../20250714120650_AddSensorDataType.cs | 29 +++++ ...0714121713_AddSensorDataType-2.Designer.cs | 123 ++++++++++++++++++ .../20250714121713_AddSensorDataType-2.cs | 22 ++++ .../RegistrationDbContextModelSnapshot.cs | 7 +- .../DTOs/SensorInfoDto.cs | 6 +- .../Domain/DataType/SensorDataType.cs | 15 ++- .../DataType/SensorDataTypeExtension.cs | 1 + 19 files changed, 443 insertions(+), 141 deletions(-) create mode 100644 src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.Designer.cs create mode 100644 src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.cs create mode 100644 src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.Designer.cs create mode 100644 src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.cs diff --git a/src/DataAggregator.Collector.Shared/Abstraction/Configuration/SensorConfig.cs b/src/DataAggregator.Collector.Shared/Abstraction/Configuration/SensorConfig.cs index 7d33458..97a342c 100644 --- a/src/DataAggregator.Collector.Shared/Abstraction/Configuration/SensorConfig.cs +++ b/src/DataAggregator.Collector.Shared/Abstraction/Configuration/SensorConfig.cs @@ -25,7 +25,7 @@ public class SensorConfig /// /// Gets or sets the type of data this sensor produces. /// - public SensorDataType DataType { get; set; } + public SensorDataType DataType { get; set; } = SensorDataType.Undefined; /// /// Gets or sets additional metadata for the sensor. diff --git a/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs b/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs index d9cb3be..5e7ce56 100644 --- a/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs +++ b/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs @@ -27,7 +27,7 @@ public async Task RegisterCollectorAsync(CollectorCo { Log.Information("Registering collector {DeviceId} with registration service", config.DeviceName); - var sensorDtos = config.Sensors.Select(s => new SensorInfoDto(s.Name, s.Type, s.Unit, s.Metadata)).ToList(); + var sensorDtos = config.Sensors.Select(s => new SensorInfoDto(s.Name, s.Type, s.Unit, s.Metadata, s.DataType)).ToList(); var request = new DeviceRegistrationRequest(config.DeviceName, config.Location, config.HealthCheckEndpoint, sensorDtos); HttpResponseMessage response = await httpClient.PostAsJsonAsync(registrationEndpoint, request); diff --git a/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs index 02cb9b7..55b05db 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs @@ -1,4 +1,5 @@ using DataAggregator.Collector.Shared.Models; +using DataAggregator.Shared.DTOs; namespace DataAggregator.Processor.Services.DataStorage; @@ -16,14 +17,14 @@ public interface IInfluxV3Repository public void InitializeAsync(string endpoint, string token, string org); /// - /// Queries measurements from InfluxDB for a specific time range and sensors. + /// Queries measurements from InfluxDB for a specific time range and sensors with type information. /// /// The table name (machine name). /// The start time for the query. /// The end time for the query. - /// The list of sensor names to query. + /// The list of sensor information with type data. /// A list of measurement data. - public Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors); + public Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors); /// /// Writes a single measurement to InfluxDB. @@ -32,12 +33,4 @@ public interface IInfluxV3Repository /// The measurement data to write. /// A task representing the asynchronous operation. public Task WriteMeasurementAsync(string table, IMeasurementData measurement); - - /// - /// Writes multiple measurements to InfluxDB in bulk. - /// - /// The table name (machine name). - /// The list of measurement data to write. - /// A task representing the asynchronous operation. - public Task BulkWriteMeasurementsAsync(string table, List measurements); } diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index 2e2f24f..bab36b1 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -1,4 +1,6 @@ using DataAggregator.Collector.Shared.Models; +using DataAggregator.Shared.Domain.DataType; +using DataAggregator.Shared.DTOs; using InfluxDB3.Client; using InfluxDB3.Client.Config; using InfluxDB3.Client.Write; @@ -43,7 +45,7 @@ public void InitializeAsync(string endpoint, string token, string org) } /// - public async Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors) + public async Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors) { if (_client == null) { @@ -53,7 +55,7 @@ public async Task> QueryMeasurementsAsync(string table, D try { // Build the Flux query - no pivot needed since we want the original structure - string sensorFilter = string.Join(" or ", sensors.Select(s => $"r[\"_field\"] == \"{s}\"")); + string sensorFilter = string.Join(" or ", sensors.Select(s => $"r[\"_field\"] == \"{s.SensorName}\"")); string query = $@" from(bucket: ""{_database}"") |> range(start: {startTime:yyyy-MM-ddTHH:mm:ssZ}, stop: {endTime:yyyy-MM-ddTHH:mm:ssZ}) @@ -61,6 +63,7 @@ public async Task> QueryMeasurementsAsync(string table, D |> filter(fn: (r) => {sensorFilter})"; var measurements = new List(); + var sensorDict = sensors.ToDictionary(s => s.SensorName, s => s); await foreach (PointDataValues point in _client.QueryPoints(query)) { @@ -83,37 +86,37 @@ public async Task> QueryMeasurementsAsync(string table, D object? value = point.GetField(fieldName); // Only include sensors that were requested - if (sensors.Contains(sensorName) && value != null) + if (sensorDict.TryGetValue(sensorName, out SensorInfoDto? sensorInfo) && value != null) { - // Try to convert to float, handling different numeric types - float floatValue; - if (value is float f) + IMeasurementData? measurement = sensorInfo.DataType switch { - floatValue = f; - } - else if (value is double d) - { - floatValue = (float)d; - } - else if (value is int i) - { - floatValue = i; - } - else if (value is long l) - { - floatValue = l; - } - else if (float.TryParse(value.ToString(), out floatValue)) + SensorDataType.Boolean when bool.TryParse(value.ToString(), out bool boolValue) => + new MeasurementData(timestamp, sensorName, boolValue), + + SensorDataType.Integer when int.TryParse(value.ToString(), out int intValue) => + new MeasurementData(timestamp, sensorName, intValue), + + SensorDataType.Double or SensorDataType.Float when double.TryParse(value.ToString(), out double doubleValue) => + new MeasurementData(timestamp, sensorName, doubleValue), + + SensorDataType.String => + new MeasurementData(timestamp, sensorName, value.ToString() ?? string.Empty), + + _ => null, + }; + + if (measurement != null) { - // Successfully parsed + measurements.Add(measurement); } else { - Log.Debug("Skipping non-numeric value for sensor {Sensor}: {Value}", sensorName, value); - continue; + Log.Debug( + "Skipping value for sensor {Sensor} with type {DataType}: {Value}", + sensorName, + sensorInfo.DataType, + value); } - - measurements.Add(new MeasurementData(timestamp, sensorName, floatValue)); } } } @@ -161,42 +164,6 @@ public async Task WriteMeasurementAsync(string table, IMeasurementData measureme } } - /// - public async Task BulkWriteMeasurementsAsync(string table, List measurements) - { - if (_client == null) - { - throw new InvalidOperationException("InfluxDB client is not initialized."); - } - - try - { - IEnumerable groupedPoints = measurements - .GroupBy(m => m.TimeStamp) - .Select(group => - { - var fields = group.ToDictionary( - m => m.SensorName, - m => m.GetRawValue()); - - return PointData - .Measurement(table) - .SetTimestamp(DateTime.SpecifyKind(group.Key, DateTimeKind.Utc)) - .SetFields(fields) - .SetTag("type", "Prediction"); - }); - - await _client.WritePointsAsync(groupedPoints, null, WritePrecision.Ms); - - Log.Debug("Written {Count} measurements to InfluxDB for table {Table}", measurements.Count, table); - } - catch (Exception ex) - { - Log.Error(ex, "Failed to bulk write measurements to InfluxDB for table {Table}", table); - throw; - } - } - /// /// Disposes the InfluxDB client. /// diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index c10d7c3..bb253f1 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -3,7 +3,7 @@ using DataAggregator.Processor.Services.DataStorage; using DataAggregator.Processor.Services.PreProcessing; using DataAggregator.Processor.Services.Registration; -using DataAggregator.Shared; +using DataAggregator.Shared.DTOs; using Serilog; namespace DataAggregator.Processor.Services.Prediction; @@ -11,35 +11,22 @@ namespace DataAggregator.Processor.Services.Prediction; /// /// Processor for machine prediction operations. /// -public class MachinePredictionProcessor +/// +/// Initializes a new instance of the class. +/// +/// The InfluxDB repository. +/// The registration service client. +/// The ONNX prediction engine. +/// The preprocessing strategy factory. +public class MachinePredictionProcessor( + IInfluxV3Repository influxRepository, + IRegistrationServiceClient registrationClient, + IOnnxPredictionEngine predictionEngine, + IPreprocessingStrategyFactory strategyFactory) { - private readonly IInfluxV3Repository _influxRepository; - private readonly IRegistrationServiceClient _registrationClient; - private readonly IOnnxPredictionEngine _predictionEngine; - private readonly IPreprocessingStrategyFactory _strategyFactory; - // Track the last endpoint used to avoid unnecessary reinitializations private string? _lastEndpoint; - /// - /// Initializes a new instance of the class. - /// - /// The InfluxDB repository. - /// The registration service client. - /// The ONNX prediction engine. - /// The preprocessing strategy factory. - public MachinePredictionProcessor( - IInfluxV3Repository influxRepository, - IRegistrationServiceClient registrationClient, - IOnnxPredictionEngine predictionEngine, - IPreprocessingStrategyFactory strategyFactory) - { - _influxRepository = influxRepository; - _registrationClient = registrationClient; - _predictionEngine = predictionEngine; - _strategyFactory = strategyFactory; - } - /// /// Processes prediction for a specific machine. /// @@ -51,23 +38,53 @@ public async Task ProcessAsync(MachinePredictionConfig config) { Log.Debug("Starting prediction process for machine {MachineName}", config.MachineName); - // Get device info from registration service - DeviceRegistrationResponse deviceInfo = await _registrationClient.GetDeviceInfoAsync(config.MachineName); + // Get collector info from registration service + CollectorInfoDto? collectorInfo = await registrationClient.GetCollectorInfoAsync(config.MachineName); + + if (collectorInfo == null) + { + Log.Warning("Collector info not found for machine {MachineName}", config.MachineName); + return; + } + + // Validate that all required sensors are available + var availableSensors = collectorInfo.Sensors.ToDictionary(s => s.SensorName, s => s); + var requestedSensors = config.InputSensors.Where(s => availableSensors.ContainsKey(s)).ToList(); + + if (requestedSensors.Count != config.InputSensors.Count) + { + IEnumerable missingSensors = config.InputSensors.Except(requestedSensors); + Log.Warning( + "Missing sensors for machine {MachineName}: {MissingSensors}", + config.MachineName, + string.Join(", ", missingSensors)); + + if (requestedSensors.Count == 0) + { + Log.Error("No valid sensors found for machine {MachineName}", config.MachineName); + return; + } + } // Initialize InfluxDB repository only if endpoint changed - if (_lastEndpoint != deviceInfo.AssignedTimeSeriesEndpoint) + if (_lastEndpoint != collectorInfo.AssignedInfluxEndpoint.Endpoint) { - _influxRepository.InitializeAsync( - deviceInfo.AssignedTimeSeriesEndpoint, - deviceInfo.DeviceToken, + influxRepository.InitializeAsync( + collectorInfo.AssignedInfluxEndpoint.Endpoint, + collectorInfo.AssignedInfluxEndpoint.Token, "Dataggregator"); - _lastEndpoint = deviceInfo.AssignedTimeSeriesEndpoint; + _lastEndpoint = collectorInfo.AssignedInfluxEndpoint.Endpoint; Log.Debug("Reinitialized InfluxDB connection with new endpoint: {Endpoint}", _lastEndpoint); } - // Fetch data window - List measurements = await FetchDataWindowAsync(config); + // Get sensor info for requested sensors + var requestedSensorInfos = requestedSensors + .Select(sensorName => availableSensors[sensorName]) + .ToList(); + + // Fetch data window with sensor type information + List measurements = await FetchDataWindowAsync(config, requestedSensorInfos); if (measurements.Count == 0) { @@ -85,13 +102,13 @@ public async Task ProcessAsync(MachinePredictionConfig config) } // Perform prediction - float[] predictions = await _predictionEngine.PredictAsync(config.ModelPath, preprocessedData); + float[] predictions = await predictionEngine.PredictAsync(config.ModelPath, preprocessedData); // Create prediction measurement IMeasurementData predictionMeasurement = CreatePredictionMeasurementAsync(predictions, config); // Write prediction to InfluxDB - await _influxRepository.WriteMeasurementAsync(config.MachineName, predictionMeasurement); + await influxRepository.WriteMeasurementAsync(config.MachineName, predictionMeasurement); Log.Information( "Prediction completed for machine {MachineName}: {PredictionValue}", @@ -109,17 +126,18 @@ public async Task ProcessAsync(MachinePredictionConfig config) /// Fetches data window for prediction. /// /// The machine prediction configuration. + /// The list of available sensors with type information. /// A list of measurement data. - private async Task> FetchDataWindowAsync(MachinePredictionConfig config) + private async Task> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) { DateTime endTime = DateTime.UtcNow; - DateTime startTime = endTime.AddSeconds(config.WindowSizeSeconds); + DateTime startTime = endTime.AddSeconds(-config.WindowSizeSeconds); - return await _influxRepository.QueryMeasurementsAsync( + return await influxRepository.QueryMeasurementsAsync( config.MachineName, startTime, endTime, - config.InputSensors); + sensors); } /// @@ -138,7 +156,7 @@ private async Task PreprocessDataAsync(List measureme return Array.Empty(); } - IPreprocessingStrategy strategy = _strategyFactory.CreateStrategy(config.PreprocessingStrategy); + IPreprocessingStrategy strategy = strategyFactory.CreateStrategy(config.PreprocessingStrategy); float[] preprocessedData = await strategy.PreprocessAsync(measurements, config); Log.Debug( diff --git a/src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs b/src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs index 2bc7546..6d202e4 100644 --- a/src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs +++ b/src/DataAggregator.Processor/Services/Registration/IRegistrationServiceClient.cs @@ -1,4 +1,4 @@ -using DataAggregator.Shared; +using DataAggregator.Shared.DTOs; namespace DataAggregator.Processor.Services.Registration; @@ -8,9 +8,9 @@ namespace DataAggregator.Processor.Services.Registration; public interface IRegistrationServiceClient { /// - /// Gets device information from the registration service. + /// Gets collector information from the registration service. /// /// The name of the device. - /// The device registration response. - public Task GetDeviceInfoAsync(string deviceName); + /// The collector information. + public Task GetCollectorInfoAsync(string deviceName); } diff --git a/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs index 92d70b5..b1d72c0 100644 --- a/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs +++ b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs @@ -1,4 +1,4 @@ -using DataAggregator.Shared; +using DataAggregator.Shared.DTOs; using Serilog; namespace DataAggregator.Processor.Services.Registration; @@ -13,32 +13,32 @@ namespace DataAggregator.Processor.Services.Registration; public class RegistrationServiceClient(HttpClient httpClient) : IRegistrationServiceClient { /// - public async Task GetDeviceInfoAsync(string deviceName) + public async Task GetCollectorInfoAsync(string deviceName) { try { - HttpResponseMessage response = await httpClient.GetAsync($"/api/DeviceRegistration/device/{deviceName}"); + HttpResponseMessage response = await httpClient.GetAsync($"/api/DeviceRegistration/collector/{deviceName}"); if (response.IsSuccessStatusCode) { - DeviceRegistrationResponse? deviceInfo = await response.Content.ReadFromJsonAsync(); - if (deviceInfo != null) + CollectorInfoDto? collectorInfo = await response.Content.ReadFromJsonAsync(); + if (collectorInfo != null) { Log.Debug( - "Retrieved device info for {DeviceName}: {Endpoint}", + "Retrieved collector info for {DeviceName}: {Endpoint}", deviceName, - deviceInfo.AssignedTimeSeriesEndpoint); + collectorInfo.AssignedInfluxEndpoint.Endpoint); - return deviceInfo; + return collectorInfo; } } - Log.Warning("Failed to retrieve device info for {DeviceName}. Status: {StatusCode}", deviceName, response.StatusCode); - throw new InvalidOperationException($"Failed to retrieve device info for {deviceName}. Status: {response.StatusCode}"); + Log.Warning("Failed to retrieve collector info for {DeviceName}. Status: {StatusCode}", deviceName, response.StatusCode); + return null; } catch (Exception ex) { - Log.Error(ex, "Error retrieving device info for {DeviceName}", deviceName); + Log.Error(ex, "Error retrieving collector info for {DeviceName}", deviceName); throw; } } diff --git a/src/DataAggregator.Registration/DeviceManagement/Domain/Extension/SensorExtension.cs b/src/DataAggregator.Registration/DeviceManagement/Domain/Extension/SensorExtension.cs index 1df5e5a..19b31a8 100644 --- a/src/DataAggregator.Registration/DeviceManagement/Domain/Extension/SensorExtension.cs +++ b/src/DataAggregator.Registration/DeviceManagement/Domain/Extension/SensorExtension.cs @@ -17,5 +17,6 @@ public static SensorInfoDto ToDto(this Sensor sensor) sensor.SensorName, sensor.SensorType, sensor.Unit, - sensor.Metadata); + sensor.Metadata, + sensor.DataType); } diff --git a/src/DataAggregator.Registration/DeviceManagement/Domain/Sensor.cs b/src/DataAggregator.Registration/DeviceManagement/Domain/Sensor.cs index 8ccc73c..84be3d9 100644 --- a/src/DataAggregator.Registration/DeviceManagement/Domain/Sensor.cs +++ b/src/DataAggregator.Registration/DeviceManagement/Domain/Sensor.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using DataAggregator.Shared.Domain.DataType; namespace DataAggregator.Registration.DeviceManagement.Domain; @@ -27,6 +28,11 @@ public class Sensor() /// public string Unit { get; set; } = string.Empty; + /// + /// Gets or sets the data type of the sensor values. + /// + public SensorDataType DataType { get; set; } = SensorDataType.Undefined; + /// /// Gets or sets additional metadata for the sensor. /// diff --git a/src/DataAggregator.Registration/DeviceManagement/Persistence/Configuration/SensorConfiguration.cs b/src/DataAggregator.Registration/DeviceManagement/Persistence/Configuration/SensorConfiguration.cs index a8c9612..bcac5f2 100644 --- a/src/DataAggregator.Registration/DeviceManagement/Persistence/Configuration/SensorConfiguration.cs +++ b/src/DataAggregator.Registration/DeviceManagement/Persistence/Configuration/SensorConfiguration.cs @@ -21,6 +21,12 @@ public void Configure(EntityTypeBuilder builder) builder.Property(s => s.SensorType).HasMaxLength(50); builder.Property(s => s.Unit).HasMaxLength(50); + // Configure SensorDataType as enum + builder.Property(s => s.DataType) + .HasConversion() + .HasDefaultValue(DataAggregator.Shared.Domain.DataType.SensorDataType.Float) + .HasSentinel(DataAggregator.Shared.Domain.DataType.SensorDataType.Undefined); + // Serialize Metadata as JSON with ValueComparer var metadataComparer = new ValueComparer>( (d1, d2) => d1!.SequenceEqual(d2!), diff --git a/src/DataAggregator.Registration/DeviceManagement/Services/DeviceRegistrationService.cs b/src/DataAggregator.Registration/DeviceManagement/Services/DeviceRegistrationService.cs index 0ac3ba1..6a6952d 100644 --- a/src/DataAggregator.Registration/DeviceManagement/Services/DeviceRegistrationService.cs +++ b/src/DataAggregator.Registration/DeviceManagement/Services/DeviceRegistrationService.cs @@ -112,6 +112,7 @@ private async Task RegisterNewDeviceAsync(DeviceRegi SensorType = sensor.Type, Unit = sensor.Unit, Metadata = sensor.Metadata, + DataType = sensor.DataType, Device = device, }); diff --git a/src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.Designer.cs b/src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.Designer.cs new file mode 100644 index 0000000..073aa82 --- /dev/null +++ b/src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.Designer.cs @@ -0,0 +1,123 @@ +// +using System; +using DataAggregator.Registration.DeviceManagement.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DataAggregator.Registration.Migrations +{ + [DbContext(typeof(RegistrationDbContext))] + [Migration("20250714120650_AddSensorDataType")] + partial class AddSensorDataType + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Collector", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssignedInfluxEndpoint") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EndpointHistories") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("HealthCheckEndpoint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegistrationDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Sensor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DataType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(4); + + b.Property("DeviceId") + .HasColumnType("uuid"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("json"); + + b.Property("SensorName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SensorType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.ToTable("Sensors"); + }); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Sensor", b => + { + b.HasOne("DataAggregator.Registration.DeviceManagement.Domain.Collector", "Device") + .WithMany("Sensors") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Collector", b => + { + b.Navigation("Sensors"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.cs b/src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.cs new file mode 100644 index 0000000..f332f81 --- /dev/null +++ b/src/DataAggregator.Registration/Migrations/20250714120650_AddSensorDataType.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DataAggregator.Registration.Migrations +{ + /// + public partial class AddSensorDataType : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DataType", + table: "Sensors", + type: "integer", + nullable: false, + defaultValue: 4); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DataType", + table: "Sensors"); + } + } +} diff --git a/src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.Designer.cs b/src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.Designer.cs new file mode 100644 index 0000000..8cc1f1d --- /dev/null +++ b/src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.Designer.cs @@ -0,0 +1,123 @@ +// +using System; +using DataAggregator.Registration.DeviceManagement.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DataAggregator.Registration.Migrations +{ + [DbContext(typeof(RegistrationDbContext))] + [Migration("20250714121713_AddSensorDataType-2")] + partial class AddSensorDataType2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Collector", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssignedInfluxEndpoint") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EndpointHistories") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("HealthCheckEndpoint") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RegistrationDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Sensor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DataType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(4); + + b.Property("DeviceId") + .HasColumnType("uuid"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("json"); + + b.Property("SensorName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SensorType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.ToTable("Sensors"); + }); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Sensor", b => + { + b.HasOne("DataAggregator.Registration.DeviceManagement.Domain.Collector", "Device") + .WithMany("Sensors") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("DataAggregator.Registration.DeviceManagement.Domain.Collector", b => + { + b.Navigation("Sensors"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.cs b/src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.cs new file mode 100644 index 0000000..3de36e3 --- /dev/null +++ b/src/DataAggregator.Registration/Migrations/20250714121713_AddSensorDataType-2.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DataAggregator.Registration.Migrations +{ + /// + public partial class AddSensorDataType2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/DataAggregator.Registration/Migrations/RegistrationDbContextModelSnapshot.cs b/src/DataAggregator.Registration/Migrations/RegistrationDbContextModelSnapshot.cs index d259828..4238e60 100644 --- a/src/DataAggregator.Registration/Migrations/RegistrationDbContextModelSnapshot.cs +++ b/src/DataAggregator.Registration/Migrations/RegistrationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("ProductVersion", "9.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -65,6 +65,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("DataType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(4); + b.Property("DeviceId") .HasColumnType("uuid"); diff --git a/src/DataAggregator.Shared/DTOs/SensorInfoDto.cs b/src/DataAggregator.Shared/DTOs/SensorInfoDto.cs index 22e31d0..a053287 100644 --- a/src/DataAggregator.Shared/DTOs/SensorInfoDto.cs +++ b/src/DataAggregator.Shared/DTOs/SensorInfoDto.cs @@ -1,6 +1,8 @@ -namespace DataAggregator.Shared.DTOs; +using DataAggregator.Shared.Domain.DataType; + +namespace DataAggregator.Shared.DTOs; /// /// Record used as DTO for sensor information. /// -public record SensorInfoDto(string SensorName, string Type, string Unit, Dictionary Metadata); +public record SensorInfoDto(string SensorName, string Type, string Unit, Dictionary Metadata, SensorDataType DataType); diff --git a/src/DataAggregator.Shared/Domain/DataType/SensorDataType.cs b/src/DataAggregator.Shared/Domain/DataType/SensorDataType.cs index b92ddad..1d887bb 100644 --- a/src/DataAggregator.Shared/Domain/DataType/SensorDataType.cs +++ b/src/DataAggregator.Shared/Domain/DataType/SensorDataType.cs @@ -5,28 +5,33 @@ namespace DataAggregator.Shared.Domain.DataType; /// public enum SensorDataType { + /// + /// Undefined type (used as sentinel value). + /// + Undefined = -1, + /// /// Boolean type. /// - Boolean, + Boolean = 0, /// /// Integer type. /// - Integer, + Integer = 1, /// /// Double-precision floating-point type. /// - Double, + Double = 2, /// /// String type. /// - String, + String = 3, /// /// Single-precision floating-point type. /// - Float, + Float = 4, } diff --git a/src/DataAggregator.Shared/Domain/DataType/SensorDataTypeExtension.cs b/src/DataAggregator.Shared/Domain/DataType/SensorDataTypeExtension.cs index dacefb1..defe4d8 100644 --- a/src/DataAggregator.Shared/Domain/DataType/SensorDataTypeExtension.cs +++ b/src/DataAggregator.Shared/Domain/DataType/SensorDataTypeExtension.cs @@ -13,6 +13,7 @@ public static class SensorDataTypeExtension public static Type GetClrType(this SensorDataType dataType) => dataType switch { + SensorDataType.Undefined => throw new ArgumentException("Undefined data type cannot be mapped to CLR type"), SensorDataType.Boolean => typeof(bool), SensorDataType.Integer => typeof(int), SensorDataType.Double => typeof(double), From 0b3b34e79e8f28be29431b9579d2caea8d080fd8 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 14:43:56 +0200 Subject: [PATCH 14/70] feat: implement pre-processing configuration for normalization --- .../Configuration/MachinePredictionConfig.cs | 5 +++++ .../Configuration/PreprocessingConfig.cs | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/DataAggregator.Processor/Configuration/PreprocessingConfig.cs diff --git a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs index 1f50583..7589a72 100644 --- a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs +++ b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs @@ -44,4 +44,9 @@ public class MachinePredictionConfig /// Gets or sets the cycle interval in seconds for this machine. /// public int CycleIntervalSeconds { get; set; } = 1; + + /// + /// Gets or sets the preprocessing configuration for Z-score normalization. + /// + public PreprocessingConfig Preprocessing { get; set; } = new(); } diff --git a/src/DataAggregator.Processor/Configuration/PreprocessingConfig.cs b/src/DataAggregator.Processor/Configuration/PreprocessingConfig.cs new file mode 100644 index 0000000..3b1a9ce --- /dev/null +++ b/src/DataAggregator.Processor/Configuration/PreprocessingConfig.cs @@ -0,0 +1,17 @@ +namespace DataAggregator.Processor.Configuration; + +/// +/// Configuration for preprocessing operations including Z-score normalization. +/// +public class PreprocessingConfig +{ + /// + /// Gets or sets a value indicating whether Z-score normalization is enabled. + /// + public bool EnableZScoreNormalization { get; set; } = true; + + /// + /// Gets or sets the normalization parameters for each feature (name, [mean, standard deviation]). + /// + public Dictionary NormalizationParameters { get; set; } = new(); +} From 3d0df7552c8b48df443369bdda111f68102b8626 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 14:49:03 +0200 Subject: [PATCH 15/70] feat: implement MathUtils (using example in mlnet) --- .../MathUtils.cs | 101 +++++++++++++++--- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs index 8a26d9e..3a02380 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs @@ -5,18 +5,14 @@ namespace DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrent /// public static class MathUtils { -#pragma warning disable IDE0022 // Use expression body for method - /// /// Calculates the mean of a collection of values. /// /// Collection of float values. /// Mean value. -#pragma warning disable IDE0060 // Remove unused parameter public static float Mean(IEnumerable values) { - // TODO: Implement mean calculation - return 0.0f; // Placeholder + return values == null || !values.Any() ? 0.0f : values.Average(); } /// @@ -26,8 +22,15 @@ public static float Mean(IEnumerable values) /// Standard deviation. public static float StandardDeviation(IEnumerable values) { - // TODO: Implement standard deviation calculation - return 0.0f; // Placeholder + if (values == null || values.Count() < 2) + { + return 0.0f; + } + + float mean = values.Average(); + float variance = values.Select(x => (x - mean) * (x - mean)).Average(); + + return (float)Math.Sqrt(variance); } /// @@ -38,8 +41,23 @@ public static float StandardDeviation(IEnumerable values) /// Percentile value. public static float Percentile(IEnumerable values, float percentile) { - // TODO: Implement percentile calculation - return 0.0f; // Placeholder + if (values == null || !values.Any()) + { + return 0.0f; + } + + var sorted = values.OrderBy(x => x).ToList(); + double index = percentile / 100.0 * (sorted.Count - 1); + int lower = (int)Math.Floor(index); + int upper = (int)Math.Ceiling(index); + + if (lower == upper) + { + return sorted[lower]; + } + + double weight = index - lower; + return (float)((sorted[lower] * (1 - weight)) + (sorted[upper] * weight)); } /// @@ -49,8 +67,23 @@ public static float Percentile(IEnumerable values, float percentile) /// Skewness value. public static float Skewness(IEnumerable values) { - // TODO: Implement skewness calculation - return 0.0f; // Placeholder + if (values == null || values.Count() < 3) + { + return 0.0f; + } + + var valuesList = values.ToList(); + float mean = valuesList.Average(); + float std = StandardDeviation(valuesList); + + if (std == 0) + { + return 0.0f; + } + + double skew = valuesList.Select(x => Math.Pow((x - mean) / std, 3)).Average(); + + return (float)skew; } /// @@ -60,8 +93,23 @@ public static float Skewness(IEnumerable values) /// Kurtosis value. public static float Kurtosis(IEnumerable values) { - // TODO: Implement kurtosis calculation - return 0.0f; // Placeholder + if (values == null || values.Count() < 4) + { + return 0.0f; + } + + var valuesList = values.ToList(); + float mean = valuesList.Average(); + float std = StandardDeviation(valuesList); + + if (std == 0) + { + return 0.0f; + } + + double kurt = valuesList.Select(x => Math.Pow((x - mean) / std, 4)).Average() - 3; + + return (float)kurt; } /// @@ -72,9 +120,28 @@ public static float Kurtosis(IEnumerable values) /// Correlation coefficient. public static float Correlation(IEnumerable x, IEnumerable y) { - // TODO: Implement correlation calculation - return 0.0f; // Placeholder + if (x == null || y == null || !x.Any() || !y.Any()) + { + return 0.0f; + } + + var xList = x.ToList(); + var yList = y.ToList(); + + if (xList.Count != yList.Count || xList.Count < 2) + { + return 0.0f; + } + + float meanX = xList.Average(); + float meanY = yList.Average(); + + float numerator = xList.Zip(yList, (xi, yi) => (xi - meanX) * (yi - meanY)).Sum(); + float denomX = xList.Select(xi => (xi - meanX) * (xi - meanX)).Sum(); + float denomY = yList.Select(yi => (yi - meanY) * (yi - meanY)).Sum(); + + double denominator = Math.Sqrt(denomX * denomY); + + return denominator == 0 ? 0.0f : (float)(numerator / denominator); } -#pragma warning restore IDE0022 // Use expression body for method -#pragma warning restore IDE0060 // Remove unused parameter } From b5a9b829f5210428718be9edfce9ffe5f574c6cf Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 14:49:19 +0200 Subject: [PATCH 16/70] feat: implement example in appsettings.json --- src/DataAggregator.Processor/appsettings.json | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index d71a7b7..01d5bea 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -37,7 +37,26 @@ ], "PredictionSensorName": "PredictedCurrentState", "WindowSizeSeconds": 1, - "CycleIntervalSeconds": 1 + "CycleIntervalSeconds": 1, + "Preprocessing": { + "EnableZScoreNormalization": true, + "NormalizationParameters": { + "GlobalActivityRatio": [0.15, 0.12], + "GlobalChangeDensity": [0.08, 0.06], + "InterAxisMeanCorrelation": [0.25, 0.18], + "InterAxisMaxCorrelation": [0.45, 0.22], + "InterAxisCorrelationVariance": [0.12, 0.08], + "AxisSynchronization": [0.75, 0.15], + "AxisLoadBalance": [0.82, 0.12], + "TemporalStability": [0.68, 0.18], + "GlobalSkewness": [0.05, 0.85], + "GlobalKurtosis": [2.1, 1.2], + "GlobalTrendSlope": [0.002, 0.008], + "CoefficientOfVariation": [0.45, 0.25], + "NormalizedIqrMedian": [0.35, 0.22], + "NormalizedIqrMean": [0.38, 0.24] + } + } } ] } From 621317e16f26cdfe3932ee0869caea1c90790a56 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 14:49:49 +0200 Subject: [PATCH 17/70] fix: formatting in MathUtils --- .../ActuatorMergingCurrentPreprocessing/MathUtils.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs index 3a02380..8663cbc 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs @@ -11,9 +11,7 @@ public static class MathUtils /// Collection of float values. /// Mean value. public static float Mean(IEnumerable values) - { - return values == null || !values.Any() ? 0.0f : values.Average(); - } + => values == null || !values.Any() ? 0.0f : values.Average(); /// /// Calculates the standard deviation of a collection of values. From 0a7367bd6b3003c12dbb3d60046765f3b149adbf Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 16:18:54 +0200 Subject: [PATCH 18/70] feat: implement feature extractor (To test) --- .../ActuatorCurrentFeatureExtractor.cs | 305 +++++++++++++++--- 1 file changed, 257 insertions(+), 48 deletions(-) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index d248ac8..cde3547 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -23,91 +23,300 @@ public async Task PreprocessAsync(List measurements, measurements.Count, config.MachineName); - // TODO: Implement feature extraction logic - // For now, return placeholder features - float[] features = new float[14]; + // Extract the 14 features from measurements + float[] features = ExtractFeatures(measurements, config.InputSensors); - // Extract features from measurements - float[] extractedFeatures = ExtractFeatures(measurements, config.InputSensors); - - // Calculate additional features - float globalActivityRatio = CalculateGlobalActivityRatio(measurements); - float interAxisCorrelation = CalculateInterAxisCorrelation(measurements); - float temporalStability = CalculateTemporalStability(measurements); - float[] statisticalFeatures = CalculateStatisticalFeatures(measurements); - - // TODO: Combine all features and normalize - // For now, just copy extracted features - await Task.Run(() => Array.Copy(extractedFeatures, features, Math.Min(extractedFeatures.Length, features.Length))); + // Apply Z-score normalization if enabled + float[] normalizedFeatures = await NormalizeFeaturesAsync(features, config.Preprocessing); Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); - return features; + return normalizedFeatures; } /// - /// Extracts basic features from measurements for specified sensors. + /// Extracts the 14 agnostic features from measurements for specified sensors. + /// Based on the ML.NET notebook implementation. /// /// List of measurements. /// List of sensor names to extract features from. - /// Array of extracted features. + /// Array of 14 extracted features. private float[] ExtractFeatures(List measurements, List sensors) { - // TODO: Implement feature extraction logic if (measurements == null || sensors == null || sensors.Count == 0) { Log.Warning("No measurements or sensors provided for feature extraction."); - return new float[14]; // Return empty features if no data + return new float[14]; } Log.Debug("Extracting features for {SensorCount} sensors", sensors.Count); - return new float[14]; // Placeholder + + // Concatenate all currents from all actuators (like in the notebook) + var allCurrents = new List(); + foreach (IMeasurementData measurement in measurements) + { + foreach (string sensor in sensors) + { + if (measurement.SensorName == sensor) + { + object value = measurement.GetRawValue(); + if (value is float floatValue) + { + allCurrents.Add(floatValue); + } + else if (value is double doubleValue) + { + allCurrents.Add((float)doubleValue); + } + else if (value is int intValue) + { + allCurrents.Add(intValue); + } + } + } + } + + // Filter out invalid values + allCurrents = allCurrents.Where(x => !float.IsNaN(x) && !float.IsInfinity(x)).ToList(); + + if (allCurrents.Count == 0) + { + Log.Warning("No valid current values found for feature extraction."); + return new float[14]; + } + + // Calculate global statistics (like in the notebook) + float globalStd = MathUtils.StandardDeviation(allCurrents); + float globalMean = MathUtils.Mean(allCurrents); + float globalMedian = MathUtils.Percentile(allCurrents, 50); + float globalQ25 = MathUtils.Percentile(allCurrents, 25); + float globalQ75 = MathUtils.Percentile(allCurrents, 75); + float globalIqr = globalQ75 - globalQ25; + + // Feature 1: Global Activity Ratio + float activityThreshold = globalStd * 2; + float activeRatio = allCurrents.Count(x => Math.Abs(x) > activityThreshold) / (float)allCurrents.Count; + + // Feature 2: Global Change Density + var diffSignals = allCurrents.Zip(allCurrents.Skip(1), (a, b) => Math.Abs(b - a)).ToList(); + int significantChanges = diffSignals.Count(x => x > globalStd * 1.5); + float changeDensity = significantChanges / (float)allCurrents.Count; + + // Extract axis currents for correlation analysis + List> axisCurrents = ExtractAxisCurrents(measurements, sensors); + + // Features 3-5: Inter-axis correlations + List correlations = CalculateInterAxisCorrelations(axisCurrents); + float meanCorrelation = correlations.Count > 0 ? correlations.Average() : 0f; + float maxCorrelation = correlations.Count > 0 ? correlations.Max() : 0f; + float correlationVariance = correlations.Count > 0 ? MathUtils.StandardDeviation(correlations) : 0f; + + // Feature 6: Axis Synchronization + var axisMeans = axisCurrents.Select(MathUtils.Mean).ToList(); + float meanOfMeans = axisMeans.Average(); + float synchronization = meanOfMeans != 0 ? 1 - (MathUtils.StandardDeviation(axisMeans) / Math.Abs(meanOfMeans)) : 1f; + + // Feature 7: Axis Load Balance + var axisEnergies = axisCurrents.Select(axis => axis.Sum(x => x * x)).ToList(); + float meanEnergy = axisEnergies.Average(); + float loadBalance = meanEnergy != 0 ? 1 - (MathUtils.StandardDeviation(axisEnergies) / meanEnergy) : 1f; + + // Feature 8: Temporal Stability + float temporalStability = CalculateTemporalStability(allCurrents); + + // Features 9-10: Global Skewness and Kurtosis + float globalSkewness = MathUtils.Skewness(allCurrents); + float globalKurtosis = MathUtils.Kurtosis(allCurrents); + + // Feature 11: Global Trend Slope + float trendSlope = CalculateTrendSlope(allCurrents); + + // Features 12-14: Normalized coefficients + float coeffVar = Math.Abs(globalMean) > 1e-8f ? globalStd / globalMean : 0f; + float normIqrMedian = Math.Abs(globalMedian) > 1e-8f ? globalIqr / globalMedian : 0f; + float normIqrMean = Math.Abs(globalMean) > 1e-8f ? globalIqr / globalMean : 0f; + + return new float[] + { + activeRatio, // 1. GlobalActivityRatio + changeDensity, // 2. GlobalChangeDensity + meanCorrelation, // 3. InterAxisMeanCorrelation + maxCorrelation, // 4. InterAxisMaxCorrelation + correlationVariance, // 5. InterAxisCorrelationVariance + synchronization, // 6. AxisSynchronization + loadBalance, // 7. AxisLoadBalance + temporalStability, // 8. TemporalStability + globalSkewness, // 9. GlobalSkewness + globalKurtosis, // 10. GlobalKurtosis + trendSlope, // 11. GlobalTrendSlope + coeffVar, // 12. CoefficientOfVariation + normIqrMedian, // 13. NormalizedIqrMedian + normIqrMean, // 14. NormalizedIqrMean + }; } /// - /// Calculates the global activity ratio across all sensors. + /// Extracts current values for each axis from measurements. /// /// List of measurements. - /// Global activity ratio as float. - private float CalculateGlobalActivityRatio(List measurements) + /// List of sensor names. + /// List of current values for each axis. + private List> ExtractAxisCurrents(List measurements, List sensors) { - // TODO: Implement global activity ratio calculation - Log.Debug("Calculating global activity ratio for {Count} measurements", measurements.Count); - return 0.0f; // Placeholder + var axisCurrents = new List>(); + + // Group measurements by sensor + var measurementsBySensor = measurements + .GroupBy(m => m.SensorName) + .ToDictionary(g => g.Key, g => g.ToList()); + + // Extract currents for each sensor/axis + foreach (string sensor in sensors) + { + var axisCurrent = new List(); + if (measurementsBySensor.TryGetValue(sensor, out List? sensorMeasurements)) + { + foreach (IMeasurementData? measurement in sensorMeasurements.OrderBy(m => m.TimeStamp)) + { + object value = measurement.GetRawValue(); + if (value is float floatValue) + { + axisCurrent.Add(floatValue); + } + else if (value is double doubleValue) + { + axisCurrent.Add((float)doubleValue); + } + else if (value is int intValue) + { + axisCurrent.Add(intValue); + } + } + } + + axisCurrents.Add(axisCurrent); + } + + return axisCurrents; } /// - /// Calculates inter-axis correlation between different sensors. + /// Calculates correlations between all pairs of axes. /// - /// List of measurements. - /// Inter-axis correlation as float. - private float CalculateInterAxisCorrelation(List measurements) + /// List of current values for each axis. + /// List of correlation coefficients. + private List CalculateInterAxisCorrelations(List> axisCurrents) { - // TODO: Implement inter-axis correlation calculation - Log.Debug("Calculating inter-axis correlation for {Count} measurements", measurements.Count); - return 0.0f; // Placeholder + var correlations = new List(); + + for (int axis1 = 0; axis1 < axisCurrents.Count; axis1++) + { + for (int axis2 = axis1 + 1; axis2 < axisCurrents.Count; axis2++) + { + float corr = MathUtils.Correlation(axisCurrents[axis1], axisCurrents[axis2]); + if (!float.IsNaN(corr) && !float.IsInfinity(corr)) + { + correlations.Add(corr); + } + } + } + + return correlations; } /// - /// Calculates temporal stability of the measurements. + /// Calculates temporal stability by analyzing variance across time segments. /// - /// List of measurements. - /// Temporal stability as float. - private float CalculateTemporalStability(List measurements) + /// All current values. + /// Temporal stability value. + private float CalculateTemporalStability(List allCurrents) { - // TODO: Implement temporal stability calculation - Log.Debug("Calculating temporal stability for {Count} measurements", measurements.Count); - return 0.0f; // Placeholder + int segmentSize = allCurrents.Count / 4; + var segmentVars = new List(); + + if (segmentSize > 1) + { + for (int seg = 0; seg < 4; seg++) + { + int startSeg = seg * segmentSize; + int endSeg = Math.Min((seg + 1) * segmentSize, allCurrents.Count); + + if (endSeg > startSeg) + { + IEnumerable segmentData = allCurrents.Skip(startSeg).Take(endSeg - startSeg); + float segVar = MathUtils.StandardDeviation(segmentData); + segmentVars.Add(segVar * segVar); + } + } + } + + return segmentVars.Count > 1 && segmentVars.Average() > 0 + ? 1 - (MathUtils.StandardDeviation(segmentVars) / segmentVars.Average()) + : 1f; } /// - /// Calculates statistical features from the measurements. + /// Calculates the trend slope using linear regression. /// - /// List of measurements. - /// Array of statistical features. - private float[] CalculateStatisticalFeatures(List measurements) + /// All current values. + /// Trend slope value. + private float CalculateTrendSlope(List allCurrents) + { + if (allCurrents.Count < 2) + { + return 0f; + } + + var timeIndices = Enumerable.Range(0, allCurrents.Count).Select(x => (float)x).ToList(); + float meanTime = timeIndices.Average(); + float meanCurrent = allCurrents.Average(); + + float numerator = timeIndices.Zip(allCurrents, (t, c) => (t - meanTime) * (c - meanCurrent)).Sum(); + float denominator = timeIndices.Sum(t => (t - meanTime) * (t - meanTime)); + + return denominator != 0 ? numerator / denominator : 0f; + } + + /// + /// Applies Z-score normalization to features using configuration parameters. + /// + /// Raw features array. + /// Preprocessing configuration. + /// Normalized features array. + private async Task NormalizeFeaturesAsync(float[] features, PreprocessingConfig preprocessing) { - // TODO: Implement statistical feature calculation - Log.Debug("Calculating statistical features for {Count} measurements", measurements.Count); - return new float[5]; // Placeholder + if (!preprocessing.EnableZScoreNormalization) + { + return features; + } + + string[] featureNames = + [ + "GlobalActivityRatio", "GlobalChangeDensity", "InterAxisMeanCorrelation", + "InterAxisMaxCorrelation", "InterAxisCorrelationVariance", "AxisSynchronization", + "AxisLoadBalance", "TemporalStability", "GlobalSkewness", "GlobalKurtosis", + "GlobalTrendSlope", "CoefficientOfVariation", "NormalizedIqrMedian", "NormalizedIqrMean", + ]; + + float[] normalized = new float[features.Length]; + + await Task.Run(() => + { + for (int i = 0; i < features.Length && i < featureNames.Length; i++) + { + string featureName = featureNames[i]; + if (preprocessing.NormalizationParameters.TryGetValue(featureName, out float[]? parameters) && parameters.Length >= 2) + { + float mean = parameters[0]; + float std = parameters[1]; + normalized[i] = std > 1e-6f ? (features[i] - mean) / std : features[i]; + } + else + { + normalized[i] = features[i]; // No normalization if parameters not found + } + } + }); + + return normalized; } } From c226c963a91886f60323494c290ae193473b927d Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 16:28:26 +0200 Subject: [PATCH 19/70] feat: do not use async for preprocessing --- .../ActuatorCurrentFeatureExtractor.cs | 31 +++++++++---------- .../PreProcessing/IPreprocessingStrategy.cs | 2 +- .../Prediction/MachinePredictionProcessor.cs | 8 ++--- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index cde3547..3d53fea 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -16,7 +16,7 @@ public class ActuatorCurrentFeatureExtractor : IPreprocessingStrategy /// List of raw measurements from the data window. /// Configuration for the machine prediction. /// Feature vector as float array for a single sample (14 features). - public async Task PreprocessAsync(List measurements, MachinePredictionConfig config) + public float[] PreprocessAsync(List measurements, MachinePredictionConfig config) { Log.Debug( "Preprocessing {Count} measurements for machine {MachineName}", @@ -27,7 +27,7 @@ public async Task PreprocessAsync(List measurements, float[] features = ExtractFeatures(measurements, config.InputSensors); // Apply Z-score normalization if enabled - float[] normalizedFeatures = await NormalizeFeaturesAsync(features, config.Preprocessing); + float[] normalizedFeatures = NormalizeFeaturesAsync(features, config.Preprocessing); Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); return normalizedFeatures; @@ -282,7 +282,7 @@ private float CalculateTrendSlope(List allCurrents) /// Raw features array. /// Preprocessing configuration. /// Normalized features array. - private async Task NormalizeFeaturesAsync(float[] features, PreprocessingConfig preprocessing) + private float[] NormalizeFeaturesAsync(float[] features, PreprocessingConfig preprocessing) { if (!preprocessing.EnableZScoreNormalization) { @@ -299,23 +299,20 @@ private async Task NormalizeFeaturesAsync(float[] features, Preprocessi float[] normalized = new float[features.Length]; - await Task.Run(() => + for (int i = 0; i < features.Length && i < featureNames.Length; i++) { - for (int i = 0; i < features.Length && i < featureNames.Length; i++) + string featureName = featureNames[i]; + if (preprocessing.NormalizationParameters.TryGetValue(featureName, out float[]? parameters) && parameters.Length >= 2) { - string featureName = featureNames[i]; - if (preprocessing.NormalizationParameters.TryGetValue(featureName, out float[]? parameters) && parameters.Length >= 2) - { - float mean = parameters[0]; - float std = parameters[1]; - normalized[i] = std > 1e-6f ? (features[i] - mean) / std : features[i]; - } - else - { - normalized[i] = features[i]; // No normalization if parameters not found - } + float mean = parameters[0]; + float std = parameters[1]; + normalized[i] = std > 1e-6f ? (features[i] - mean) / std : features[i]; } - }); + else + { + normalized[i] = features[i]; // No normalization if parameters not found + } + } return normalized; } diff --git a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs index b06bb40..83767ef 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs @@ -14,5 +14,5 @@ public interface IPreprocessingStrategy /// List of raw measurements from the data window. /// Configuration for the machine prediction. /// Feature vector as float array for a single sample. - public Task PreprocessAsync(List measurements, MachinePredictionConfig config); + public float[] PreprocessAsync(List measurements, MachinePredictionConfig config); } diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index bb253f1..45e5c7e 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -93,7 +93,7 @@ public async Task ProcessAsync(MachinePredictionConfig config) } // Preprocess data using strategy - float[] preprocessedData = await PreprocessDataAsync(measurements, config); + float[] preprocessedData = PreprocessDataAsync(measurements, config); if (preprocessedData == null || preprocessedData.Length == 0) { @@ -146,18 +146,18 @@ private async Task> FetchDataWindowAsync(MachinePredictio /// The list of measurements. /// The machine prediction configuration. /// The preprocessed data as a float array for a single sample. - private async Task PreprocessDataAsync(List measurements, MachinePredictionConfig config) + private float[] PreprocessDataAsync(List measurements, MachinePredictionConfig config) { try { if (string.IsNullOrEmpty(config.PreprocessingStrategy)) { Log.Error("No preprocessing strategy configured for machine {MachineName}", config.MachineName); - return Array.Empty(); + return []; } IPreprocessingStrategy strategy = strategyFactory.CreateStrategy(config.PreprocessingStrategy); - float[] preprocessedData = await strategy.PreprocessAsync(measurements, config); + float[] preprocessedData = strategy.PreprocessAsync(measurements, config); Log.Debug( "Preprocessed data for machine {MachineName} using strategy {Strategy}: {Features} features", From 6dc87ac89d0666148d6a59e66052460c9177ef5d Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 16:36:03 +0200 Subject: [PATCH 20/70] doc: delete auto-generated doc for private methods --- .../DataStorage/InfluxV3Repository.cs | 2 -- .../ActuatorCurrentFeatureExtractor.cs | 36 +------------------ .../Prediction/MachinePredictionProcessor.cs | 18 ---------- .../Prediction/OnnxPredictionEngine.cs | 5 --- .../Services/PredictionBackgroundService.cs | 14 +------- 5 files changed, 2 insertions(+), 73 deletions(-) diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index bab36b1..44ec0f2 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -15,7 +15,6 @@ public class InfluxV3Repository : IInfluxV3Repository, IDisposable { private readonly string _database = "Dataggregator"; private InfluxDBClient? _client; - private string _organization = "Dataggregator"; /// public void InitializeAsync(string endpoint, string token, string org) @@ -33,7 +32,6 @@ public void InitializeAsync(string endpoint, string token, string org) }; _client = new InfluxDBClient(clientConfig); - _organization = org; Log.Information("InfluxDB v3 repository initialized with endpoint: {Endpoint}", endpoint); } diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index 3d53fea..24a4038 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -33,13 +33,6 @@ public float[] PreprocessAsync(List measurements, MachinePredi return normalizedFeatures; } - /// - /// Extracts the 14 agnostic features from measurements for specified sensors. - /// Based on the ML.NET notebook implementation. - /// - /// List of measurements. - /// List of sensor names to extract features from. - /// Array of 14 extracted features. private float[] ExtractFeatures(List measurements, List sensors) { if (measurements == null || sensors == null || sensors.Count == 0) @@ -84,7 +77,7 @@ private float[] ExtractFeatures(List measurements, List measurements, List - /// Extracts current values for each axis from measurements. - /// - /// List of measurements. - /// List of sensor names. - /// List of current values for each axis. private List> ExtractAxisCurrents(List measurements, List sensors) { var axisCurrents = new List>(); @@ -199,11 +186,6 @@ private List> ExtractAxisCurrents(List measurement return axisCurrents; } - /// - /// Calculates correlations between all pairs of axes. - /// - /// List of current values for each axis. - /// List of correlation coefficients. private List CalculateInterAxisCorrelations(List> axisCurrents) { var correlations = new List(); @@ -223,11 +205,6 @@ private List CalculateInterAxisCorrelations(List> axisCurrent return correlations; } - /// - /// Calculates temporal stability by analyzing variance across time segments. - /// - /// All current values. - /// Temporal stability value. private float CalculateTemporalStability(List allCurrents) { int segmentSize = allCurrents.Count / 4; @@ -254,11 +231,6 @@ private float CalculateTemporalStability(List allCurrents) : 1f; } - /// - /// Calculates the trend slope using linear regression. - /// - /// All current values. - /// Trend slope value. private float CalculateTrendSlope(List allCurrents) { if (allCurrents.Count < 2) @@ -276,12 +248,6 @@ private float CalculateTrendSlope(List allCurrents) return denominator != 0 ? numerator / denominator : 0f; } - /// - /// Applies Z-score normalization to features using configuration parameters. - /// - /// Raw features array. - /// Preprocessing configuration. - /// Normalized features array. private float[] NormalizeFeaturesAsync(float[] features, PreprocessingConfig preprocessing) { if (!preprocessing.EnableZScoreNormalization) diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index 45e5c7e..b424087 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -122,12 +122,6 @@ public async Task ProcessAsync(MachinePredictionConfig config) } } - /// - /// Fetches data window for prediction. - /// - /// The machine prediction configuration. - /// The list of available sensors with type information. - /// A list of measurement data. private async Task> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) { DateTime endTime = DateTime.UtcNow; @@ -140,12 +134,6 @@ private async Task> FetchDataWindowAsync(MachinePredictio sensors); } - /// - /// Preprocesses data using the configured strategy. - /// - /// The list of measurements. - /// The machine prediction configuration. - /// The preprocessed data as a float array for a single sample. private float[] PreprocessDataAsync(List measurements, MachinePredictionConfig config) { try @@ -174,12 +162,6 @@ private float[] PreprocessDataAsync(List measurements, Machine } } - /// - /// Creates a prediction measurement from the model output. - /// - /// The prediction results. - /// The machine prediction configuration. - /// The prediction measurement. private IMeasurementData CreatePredictionMeasurementAsync(float[] predictions, MachinePredictionConfig config) { // For simplicity, we'll use the first prediction value diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs index 757eb8d..1987a87 100644 --- a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs @@ -47,11 +47,6 @@ public async Task PredictAsync(string modelPath, float[] inputData) } } - /// - /// Loads or gets a cached ONNX model. - /// - /// The path to the ONNX model file. - /// The inference session. private InferenceSession LoadOrGetModel(string modelPath) { if (_modelCache.TryGetValue(modelPath, out InferenceSession? cachedSession)) diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs index 7a5716f..c759804 100644 --- a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -6,7 +6,7 @@ namespace DataAggregator.Processor.Services; /// -/// Background service for managing machine predictions. +/// Background service for processing machine predictions on a scheduled basis. /// /// /// Initializes a new instance of the class. @@ -72,9 +72,6 @@ public override async Task StopAsync(CancellationToken cancellationToken) await base.StopAsync(cancellationToken); } - /// - /// Validates the configuration and checks for required files. - /// private void ValidateConfigurationAsync() { var enabledMachines = configuration.Value.Machines.Where(m => m.Enabled).ToList(); @@ -146,10 +143,6 @@ private void ValidateConfigurationAsync() } } - /// - /// Schedules prediction processing for a machine. - /// - /// The machine prediction configuration. private void ScheduleMachine(MachinePredictionConfig machineConfig) { try @@ -173,11 +166,6 @@ private void ScheduleMachine(MachinePredictionConfig machineConfig) } } - /// - /// Processes prediction for a specific machine. - /// - /// The machine prediction configuration. - /// A task representing the asynchronous operation. private async Task ProcessMachineAsync(MachinePredictionConfig machineConfig) { // Skip if machine has errors From b5f0c1cbf56cd6b9701dbabe2ebaba5922afc1ae Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 14 Jul 2025 16:45:07 +0200 Subject: [PATCH 21/70] feat: add regions --- .../ActuatorCurrentFeatureExtractor.cs | 8 +++++ .../Prediction/MachinePredictionProcessor.cs | 12 +++++++ .../Prediction/OnnxPredictionEngine.cs | 36 ++++++++++++------- .../Services/PredictionBackgroundService.cs | 12 +++++++ 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index 24a4038..47be0b1 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -10,6 +10,8 @@ namespace DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrent /// public class ActuatorCurrentFeatureExtractor : IPreprocessingStrategy { + #region Public methods + /// /// Preprocesses actuator current measurements into a feature vector. /// @@ -33,6 +35,10 @@ public float[] PreprocessAsync(List measurements, MachinePredi return normalizedFeatures; } + #endregion + + #region Private methods + private float[] ExtractFeatures(List measurements, List sensors) { if (measurements == null || sensors == null || sensors.Count == 0) @@ -282,4 +288,6 @@ private float[] NormalizeFeaturesAsync(float[] features, PreprocessingConfig pre return normalized; } + + #endregion } diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index b424087..de4fac0 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -24,9 +24,15 @@ public class MachinePredictionProcessor( IOnnxPredictionEngine predictionEngine, IPreprocessingStrategyFactory strategyFactory) { + #region Private fields + // Track the last endpoint used to avoid unnecessary reinitializations private string? _lastEndpoint; + #endregion + + #region Public methods + /// /// Processes prediction for a specific machine. /// @@ -122,6 +128,10 @@ public async Task ProcessAsync(MachinePredictionConfig config) } } + #endregion + + #region Private methods + private async Task> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) { DateTime endTime = DateTime.UtcNow; @@ -173,4 +183,6 @@ private IMeasurementData CreatePredictionMeasurementAsync(float[] predictions, M config.PredictionSensorName, predictionValue); } + + #endregion } diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs index 1987a87..c3df9c4 100644 --- a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs @@ -9,8 +9,14 @@ namespace DataAggregator.Processor.Services.Prediction; /// public class OnnxPredictionEngine : IOnnxPredictionEngine, IDisposable { + #region Private fields + private readonly Dictionary _modelCache = []; + #endregion + + #region Public methods + /// public async Task PredictAsync(string modelPath, float[] inputData) { @@ -47,6 +53,23 @@ public async Task PredictAsync(string modelPath, float[] inputData) } } + /// + /// Disposes the cached models. + /// + public void Dispose() + { + foreach (InferenceSession session in _modelCache.Values) + { + session?.Dispose(); + } + + _modelCache.Clear(); + } + + #endregion + + #region Private methods + private InferenceSession LoadOrGetModel(string modelPath) { if (_modelCache.TryGetValue(modelPath, out InferenceSession? cachedSession)) @@ -75,16 +98,5 @@ private InferenceSession LoadOrGetModel(string modelPath) } } - /// - /// Disposes the cached models. - /// - public void Dispose() - { - foreach (InferenceSession session in _modelCache.Values) - { - session?.Dispose(); - } - - _modelCache.Clear(); - } + #endregion } diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs index c759804..4a90634 100644 --- a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -17,9 +17,15 @@ public class PredictionBackgroundService( IOptions configuration, MachinePredictionProcessor predictionProcessor) : BackgroundService { + #region Private fields + private readonly Dictionary _machineTimers = []; private readonly Dictionary _machineErrors = []; + #endregion + + #region Public methods + /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { @@ -72,6 +78,10 @@ public override async Task StopAsync(CancellationToken cancellationToken) await base.StopAsync(cancellationToken); } + #endregion + + #region Private methods + private void ValidateConfigurationAsync() { var enabledMachines = configuration.Value.Machines.Where(m => m.Enabled).ToList(); @@ -193,4 +203,6 @@ private async Task ProcessMachineAsync(MachinePredictionConfig machineConfig) _machineErrors[machineConfig.MachineName] = true; } } + + #endregion } From b02d9e710778dcb37cc56b5f0e0e8abcf607020d Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 15 Jul 2025 18:50:10 +0200 Subject: [PATCH 22/70] feat: add unit test for mathutils --- .../DataAggregator.Processor.Tests.csproj | 30 ++ .../Services/PreProcessing/MathUtilsTests.cs | 501 ++++++++++++++++++ DataAggregator.sln | 15 + 3 files changed, 546 insertions(+) create mode 100644 DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj create mode 100644 DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs diff --git a/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj b/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj new file mode 100644 index 0000000..e212051 --- /dev/null +++ b/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj @@ -0,0 +1,30 @@ + + + + net9.0 + enable + enable + false + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs b/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs new file mode 100644 index 0000000..1c9dfcf --- /dev/null +++ b/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs @@ -0,0 +1,501 @@ +using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; + +namespace DataAggregator.Processor.Tests.Services.PreProcessing; + +/// +/// Tests for the class. +/// +public class MathUtilsTests +{ + #region Mean tests + + [Fact] + public void Mean_ShouldReturnZero_WhenValuesIsNull() + { + // Act + float result = MathUtils.Mean(null!); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Mean_ShouldReturnZero_WhenValuesIsEmpty() + { + // Arrange + var values = new List(); + + // Act + float result = MathUtils.Mean(values); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Mean_ShouldReturnCorrectValue_WhenValuesContainsSingleElement() + { + // Arrange + var values = new List { 5.0f }; + + // Act + float result = MathUtils.Mean(values); + + // Assert + Assert.Equal(5.0f, result); + } + + [Fact] + public void Mean_ShouldReturnCorrectValue_WhenValuesContainsMultipleElements() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Mean(values); + + // Assert + Assert.Equal(3.0f, result); + } + + [Fact] + public void Mean_ShouldReturnCorrectValue_WhenValuesContainsNegativeNumbers() + { + // Arrange + var values = new List { -2.0f, -1.0f, 0.0f, 1.0f, 2.0f }; + + // Act + float result = MathUtils.Mean(values); + + // Assert + Assert.Equal(0.0f, result); + } + + #endregion + + #region StandardDeviation tests + + [Fact] + public void StandardDeviation_ShouldReturnZero_WhenValuesIsNull() + { + // Act + float result = MathUtils.StandardDeviation(null!); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void StandardDeviation_ShouldReturnZero_WhenValuesIsEmpty() + { + // Arrange + var values = new List(); + + // Act + float result = MathUtils.StandardDeviation(values); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void StandardDeviation_ShouldReturnZero_WhenValuesContainsSingleElement() + { + // Arrange + var values = new List { 5.0f }; + + // Act + float result = MathUtils.StandardDeviation(values); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void StandardDeviation_ShouldReturnCorrectValue_WhenValuesContainsMultipleElements() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.StandardDeviation(values); + + // Assert + // Expected: sqrt(sum((x - mean)^2) / n) = sqrt(10 / 5) = sqrt(2) ≈ 1.414 + Assert.Equal(1.4142135f, result, 6); + } + + [Fact] + public void StandardDeviation_ShouldReturnZero_WhenAllValuesAreIdentical() + { + // Arrange + var values = new List { 3.0f, 3.0f, 3.0f, 3.0f }; + + // Act + float result = MathUtils.StandardDeviation(values); + + // Assert + Assert.Equal(0.0f, result); + } + + #endregion + + #region Percentile tests + + [Fact] + public void Percentile_ShouldReturnZero_WhenValuesIsNull() + { + // Act + float result = MathUtils.Percentile(null!, 50.0f); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Percentile_ShouldReturnZero_WhenValuesIsEmpty() + { + // Arrange + var values = new List(); + + // Act + float result = MathUtils.Percentile(values, 50.0f); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Percentile_ShouldReturnCorrectValue_WhenPercentileIs0() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Percentile(values, 0.0f); + + // Assert + Assert.Equal(1.0f, result); + } + + [Fact] + public void Percentile_ShouldReturnCorrectValue_WhenPercentileIs50() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Percentile(values, 50.0f); + + // Assert + Assert.Equal(3.0f, result); + } + + [Fact] + public void Percentile_ShouldReturnCorrectValue_WhenPercentileIs100() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Percentile(values, 100.0f); + + // Assert + Assert.Equal(5.0f, result); + } + + [Fact] + public void Percentile_ShouldReturnCorrectValue_WhenPercentileIs25() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Percentile(values, 25.0f); + + // Assert + // For 5 elements, 25th percentile should be the 2nd element (index 1) + Assert.Equal(2.0f, result); + } + + [Fact] + public void Percentile_ShouldReturnCorrectValue_WhenPercentileIs75() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Percentile(values, 75.0f); + + // Assert + // For 5 elements, 75th percentile should be the 4th element (index 3) + Assert.Equal(4.0f, result); + } + + #endregion + + #region Skewness tests + + [Fact] + public void Skewness_ShouldReturnZero_WhenValuesIsNull() + { + // Act + float result = MathUtils.Skewness(null!); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Skewness_ShouldReturnZero_WhenValuesContainsLessThan3Elements() + { + // Arrange + var values = new List { 1.0f, 2.0f }; + + // Act + float result = MathUtils.Skewness(values); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Skewness_ShouldReturnZero_WhenStandardDeviationIsZero() + { + // Arrange + var values = new List { 3.0f, 3.0f, 3.0f }; + + // Act + float result = MathUtils.Skewness(values); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Skewness_ShouldReturnCorrectValue_WhenValuesAreSymmetric() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Skewness(values); + + // Assert + // For symmetric data around mean, skewness should be close to 0 + Assert.Equal(0.0f, result, 6); + } + + [Fact] + public void Skewness_ShouldReturnPositiveValue_WhenValuesAreRightSkewed() + { + // Arrange + var values = new List { 1.0f, 1.0f, 1.0f, 2.0f, 10.0f }; + + // Act + float result = MathUtils.Skewness(values); + + // Assert + // Right-skewed data should have positive skewness + Assert.True(result > 0.0f); + } + + #endregion + + #region Kurtosis tests + + [Fact] + public void Kurtosis_ShouldReturnZero_WhenValuesIsNull() + { + // Act + float result = MathUtils.Kurtosis(null!); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Kurtosis_ShouldReturnZero_WhenValuesContainsLessThan4Elements() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f }; + + // Act + float result = MathUtils.Kurtosis(values); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Kurtosis_ShouldReturnZero_WhenStandardDeviationIsZero() + { + // Arrange + var values = new List { 3.0f, 3.0f, 3.0f, 3.0f }; + + // Act + float result = MathUtils.Kurtosis(values); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Kurtosis_ShouldReturnCorrectValue_WhenValuesAreNormallyDistributed() + { + // Arrange + var values = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + + // Act + float result = MathUtils.Kurtosis(values); + + // Assert + // For normal distribution, kurtosis should be close to 0 (excess kurtosis) + Assert.Equal(-1.2f, result, 1); + } + + #endregion + + #region Correlation tests + + [Fact] + public void Correlation_ShouldReturnZero_WhenXIsNull() + { + // Arrange + var y = new List { 1.0f, 2.0f, 3.0f }; + + // Act + float result = MathUtils.Correlation(null!, y); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Correlation_ShouldReturnZero_WhenYIsNull() + { + // Arrange + var x = new List { 1.0f, 2.0f, 3.0f }; + + // Act + float result = MathUtils.Correlation(x, null!); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Correlation_ShouldReturnZero_WhenXIsEmpty() + { + // Arrange + var x = new List(); + var y = new List { 1.0f, 2.0f, 3.0f }; + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Correlation_ShouldReturnZero_WhenYIsEmpty() + { + // Arrange + var x = new List { 1.0f, 2.0f, 3.0f }; + var y = new List(); + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Correlation_ShouldReturnZero_WhenLengthsAreDifferent() + { + // Arrange + var x = new List { 1.0f, 2.0f, 3.0f }; + var y = new List { 1.0f, 2.0f }; + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Correlation_ShouldReturnZero_WhenLengthIsLessThan2() + { + // Arrange + var x = new List { 1.0f }; + var y = new List { 2.0f }; + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + Assert.Equal(0.0f, result); + } + + [Fact] + public void Correlation_ShouldReturnOne_WhenPerfectPositiveCorrelation() + { + // Arrange + var x = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + var y = new List { 2.0f, 4.0f, 6.0f, 8.0f, 10.0f }; + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + Assert.Equal(1.0f, result, 6); + } + + [Fact] + public void Correlation_ShouldReturnMinusOne_WhenPerfectNegativeCorrelation() + { + // Arrange + var x = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + var y = new List { 10.0f, 8.0f, 6.0f, 4.0f, 2.0f }; + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + Assert.Equal(-1.0f, result, 6); + } + + [Fact] + public void Correlation_ShouldReturnZero_WhenNoCorrelation() + { + // Arrange + var x = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + var y = new List { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + Assert.Equal(0.0f, result, 6); + } + + [Fact] + public void Correlation_ShouldReturnCorrectValue_WhenModerateCorrelation() + { + // Arrange + var x = new List { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + var y = new List { 1.0f, 3.0f, 2.0f, 5.0f, 4.0f }; + + // Act + float result = MathUtils.Correlation(x, y); + + // Assert + // Should be a moderate positive correlation + Assert.True(result is > 0.0f and < 1.0f); + } + + #endregion +} diff --git a/DataAggregator.sln b/DataAggregator.sln index f3793ca..9aeecf7 100644 --- a/DataAggregator.sln +++ b/DataAggregator.sln @@ -42,6 +42,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{EB9A5576-3E00-4007-8C72-2235E0A1546D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Processor.Tests", "DataAggregator.Processor.Tests\DataAggregator.Processor.Tests.csproj", "{C4C90E68-F473-4672-A3C8-E0F1713E5372}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -160,6 +162,18 @@ Global {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x64.Build.0 = Release|x64 {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x86.ActiveCfg = Release|x86 {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x86.Build.0 = Release|x86 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x64.ActiveCfg = Debug|x64 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x64.Build.0 = Debug|x64 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x86.ActiveCfg = Debug|x86 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x86.Build.0 = Debug|x86 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|Any CPU.Build.0 = Release|Any CPU + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x64.ActiveCfg = Release|x64 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x64.Build.0 = Release|x64 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x86.ActiveCfg = Release|x86 + {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -178,6 +192,7 @@ Global {2C10378E-0937-40E3-940E-F236F6E8B95B} = {F5250AC4-9CCC-432B-8725-DAC6AE01CCF0} {039EC00D-5EDF-4C48-B449-29CFB6750232} = {EB9A5576-3E00-4007-8C72-2235E0A1546D} {EB9A5576-3E00-4007-8C72-2235E0A1546D} = {E1AD9667-4C40-4CAF-8096-5FA749EBB2B1} + {C4C90E68-F473-4672-A3C8-E0F1713E5372} = {247EF7A2-1DFD-4B51-AC7D-0FD13827CAC2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9C8E6DBA-9F77-4FD0-9A1D-25150F7806FF} From 26555c29dc4edffd3d67e9db9dbeffa964066edf Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 15 Jul 2025 18:50:20 +0200 Subject: [PATCH 23/70] feat: add healthCheckController test --- .../Controllers/HealthCheckControllerTests.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs diff --git a/DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs b/DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs new file mode 100644 index 0000000..947bd06 --- /dev/null +++ b/DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs @@ -0,0 +1,73 @@ +using DataAggregator.Processor.Controllers; +using Microsoft.AspNetCore.Mvc; + +namespace DataAggregator.Processor.Tests.Controllers; + +/// +/// Tests for the class. +/// +public class HealthCheckControllerTests +{ + private readonly HealthCheckController _controller; + + /// + /// Initializes a new instance of the class. + /// + public HealthCheckControllerTests() => _controller = new HealthCheckController(); + + [Fact] + public void Get_ShouldReturnOkResult_WhenCalled() + { + // Act + IActionResult result = _controller.Get(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void Get_ShouldReturnCorrectStatus_WhenCalled() + { + // Act + IActionResult result = _controller.Get(); + var okResult = Assert.IsType(result); + var response = okResult.Value as dynamic; + + // Assert + Assert.NotNull(response); + Assert.Equal("Healthy", response.Status); + Assert.Equal("DataAggregator.Processor", response.Service); + Assert.NotNull(response.Timestamp); + } + + [Fact] + public void Get_ShouldReturnCurrentTimestamp_WhenCalled() + { + // Arrange + DateTime beforeCall = DateTime.UtcNow; + + // Act + IActionResult result = _controller.Get(); + var okResult = Assert.IsType(result); + var response = okResult.Value as dynamic; + + DateTime afterCall = DateTime.UtcNow; + + // Assert + Assert.NotNull(response); + var timestamp = (DateTime)response.Timestamp; + Assert.True(timestamp >= beforeCall && timestamp <= afterCall); + } + + [Fact] + public void Get_ShouldReturnValidJsonStructure_WhenCalled() + { + // Act + IActionResult result = _controller.Get(); + var okResult = Assert.IsType(result); + + // Assert + Assert.Equal(200, okResult.StatusCode); + Assert.NotNull(okResult.Value); + } +} \ No newline at end of file From 0c6265a6ef560541de33d5ecf068ab84bee44a86 Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 13:19:19 +0200 Subject: [PATCH 24/70] fix: health check usage --- .../Controllers/HealthCheckControllerTests.cs | 73 ------------------- .../Controllers/HealthCheckController.cs | 19 ----- .../Repositories/DeviceRepository.cs | 3 +- .../Repositories/IDeviceRepository.cs | 4 +- 4 files changed, 3 insertions(+), 96 deletions(-) delete mode 100644 DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs delete mode 100644 src/DataAggregator.Processor/Controllers/HealthCheckController.cs diff --git a/DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs b/DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs deleted file mode 100644 index 947bd06..0000000 --- a/DataAggregator.Processor.Tests/Controllers/HealthCheckControllerTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using DataAggregator.Processor.Controllers; -using Microsoft.AspNetCore.Mvc; - -namespace DataAggregator.Processor.Tests.Controllers; - -/// -/// Tests for the class. -/// -public class HealthCheckControllerTests -{ - private readonly HealthCheckController _controller; - - /// - /// Initializes a new instance of the class. - /// - public HealthCheckControllerTests() => _controller = new HealthCheckController(); - - [Fact] - public void Get_ShouldReturnOkResult_WhenCalled() - { - // Act - IActionResult result = _controller.Get(); - - // Assert - Assert.IsType(result); - } - - [Fact] - public void Get_ShouldReturnCorrectStatus_WhenCalled() - { - // Act - IActionResult result = _controller.Get(); - var okResult = Assert.IsType(result); - var response = okResult.Value as dynamic; - - // Assert - Assert.NotNull(response); - Assert.Equal("Healthy", response.Status); - Assert.Equal("DataAggregator.Processor", response.Service); - Assert.NotNull(response.Timestamp); - } - - [Fact] - public void Get_ShouldReturnCurrentTimestamp_WhenCalled() - { - // Arrange - DateTime beforeCall = DateTime.UtcNow; - - // Act - IActionResult result = _controller.Get(); - var okResult = Assert.IsType(result); - var response = okResult.Value as dynamic; - - DateTime afterCall = DateTime.UtcNow; - - // Assert - Assert.NotNull(response); - var timestamp = (DateTime)response.Timestamp; - Assert.True(timestamp >= beforeCall && timestamp <= afterCall); - } - - [Fact] - public void Get_ShouldReturnValidJsonStructure_WhenCalled() - { - // Act - IActionResult result = _controller.Get(); - var okResult = Assert.IsType(result); - - // Assert - Assert.Equal(200, okResult.StatusCode); - Assert.NotNull(okResult.Value); - } -} \ No newline at end of file diff --git a/src/DataAggregator.Processor/Controllers/HealthCheckController.cs b/src/DataAggregator.Processor/Controllers/HealthCheckController.cs deleted file mode 100644 index 4edb9a4..0000000 --- a/src/DataAggregator.Processor/Controllers/HealthCheckController.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace DataAggregator.Processor.Controllers; - -/// -/// Controller for health check endpoints. -/// -[ApiController] -[Route("api/[controller]")] -public class HealthCheckController : ControllerBase -{ - /// - /// Gets the health status of the prediction service. - /// - /// The health status. - [HttpGet] - public IActionResult Get() - => Ok(new { Status = "Healthy", Service = "DataAggregator.Processor", Timestamp = DateTime.UtcNow }); -} diff --git a/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/DeviceRepository.cs b/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/DeviceRepository.cs index 7341666..8684d00 100644 --- a/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/DeviceRepository.cs +++ b/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/DeviceRepository.cs @@ -30,7 +30,7 @@ public class DeviceRepository(RegistrationDbContext context) : IDeviceRepository } /// - public async Task CreateAsync(Collector device) + public async Task CreateAsync(Collector device) { try { @@ -38,7 +38,6 @@ public async Task CreateAsync(Collector device) context.Devices.Add(device); await context.SaveChangesAsync(); Log.Information("Device created successfully: {DeviceName}", device.DeviceName); - return device; } catch (Exception ex) { diff --git a/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/IDeviceRepository.cs b/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/IDeviceRepository.cs index 883b42b..d6b15c2 100644 --- a/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/IDeviceRepository.cs +++ b/src/DataAggregator.Registration/DeviceManagement/Persistence/Repositories/IDeviceRepository.cs @@ -18,8 +18,8 @@ public interface IDeviceRepository /// Creates a new device entry in the store asynchronously. /// /// The device to create. - /// A task representing the asynchronous operation. The task result contains the created device. - public Task CreateAsync(Collector device); + /// A representing the asynchronous operation. + public Task CreateAsync(Collector device); /// /// Updates an existing device entry in the store asynchronously. From db63aec2387f41611d1d2d1381cd554c6506aa5b Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 13:21:21 +0200 Subject: [PATCH 25/70] fix: RegistrationServiceClient on empty string --- .../Services/Registration/RegistrationServiceClient.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs index b1d72c0..c541e4e 100644 --- a/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs +++ b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs @@ -15,6 +15,12 @@ public class RegistrationServiceClient(HttpClient httpClient) : IRegistrationSer /// public async Task GetCollectorInfoAsync(string deviceName) { + if (deviceName == string.Empty) + { + Log.Error("Device name cannot be empty."); + throw new ArgumentException("Device name cannot be empty.", nameof(deviceName)); + } + try { HttpResponseMessage response = await httpClient.GetAsync($"/api/DeviceRegistration/collector/{deviceName}"); @@ -39,7 +45,7 @@ public class RegistrationServiceClient(HttpClient httpClient) : IRegistrationSer catch (Exception ex) { Log.Error(ex, "Error retrieving collector info for {DeviceName}", deviceName); - throw; + return null; } } } From 3c5a134afc94eaea9f3e69f1c0d5147199a6010d Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 14:13:07 +0200 Subject: [PATCH 26/70] feat: registration clien test --- .../RegistrationServiceClientTests.cs | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs diff --git a/DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs b/DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs new file mode 100644 index 0000000..5b74d9a --- /dev/null +++ b/DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs @@ -0,0 +1,123 @@ +using System.Net; +using System.Text.Json; +using DataAggregator.Processor.Services.Registration; +using DataAggregator.Shared.Configuration.TimeSeries; +using DataAggregator.Shared.Domain.DataType; +using DataAggregator.Shared.DTOs; +using Moq; +using Moq.Protected; + +namespace DataAggregator.Processor.Tests.Services.Registration; + +/// +/// Tests for the class. +/// +public class RegistrationServiceClientTests +{ + private readonly Mock _mockHttpHandler; + private readonly RegistrationServiceClient _client; + + /// + /// Initializes a new instance of the class. + /// + public RegistrationServiceClientTests() + { + _mockHttpHandler = new Mock(); + var httpClient = new HttpClient(_mockHttpHandler.Object) + { + BaseAddress = new Uri("http://localhost:5000"), + }; + _client = new RegistrationServiceClient(httpClient); + } + + #region GetCollectorInfoAsync tests + + [Fact] + public async Task GetCollectorInfoAsync_ShouldReturnCollectorInfo_WhenValidResponseReceived() + { + // Arrange + CollectorInfoDto expectedCollectorInfo = CreateTestCollectorInfo(); + string responseContent = JsonSerializer.Serialize(expectedCollectorInfo); + + _mockHttpHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(responseContent), + }); + + // Act + CollectorInfoDto? result = await _client.GetCollectorInfoAsync("test_device"); + + // Assert + Assert.NotNull(result); + Assert.Equal(expectedCollectorInfo.DeviceName, result.DeviceName); + Assert.Equal(expectedCollectorInfo.AssignedInfluxEndpoint.Endpoint, result.AssignedInfluxEndpoint.Endpoint); + Assert.Equal(expectedCollectorInfo.Sensors.Count, result.Sensors.Count); + } + + [Fact] + public async Task GetCollectorInfoAsync_ShouldReturnNull_WhenNotFoundResponseReceived() + { + // Arrange + _mockHttpHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound)); + + // Act + CollectorInfoDto? result = await _client.GetCollectorInfoAsync("non_existent_device"); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task GetCollectorInfoAsync_ShouldThrowArgumentException_WhenDeviceNameIsEmpty() => + + // Act & Assert + await Assert.ThrowsAsync(() => _client.GetCollectorInfoAsync(string.Empty)); + + [Fact] + public async Task GetCollectorInfoAsync_ShouldHandleEmptyResponseContent_WhenReceived() + { + // Arrange + _mockHttpHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(string.Empty), + }); + + // Act + CollectorInfoDto? result = await _client.GetCollectorInfoAsync("test_device"); + + // Assert + Assert.Null(result); + } + + #endregion + + #region Helper methods + + private static CollectorInfoDto CreateTestCollectorInfo() => new( + "test_device", + "test_location", + "http://localhost:5000/health", + new InfluxEndpoint("TestEndpoint", "http://localhost:8086", "test_token"), + [ + new("temperature", "Type", "Unit", [], SensorDataType.Float), + new("pressure", "Type", "Unit", [], SensorDataType.Float) + ], + []); + + #endregion +} From e1f21cea4627462d4ae2c5ee97e1c1c767771495 Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 14:25:02 +0200 Subject: [PATCH 27/70] fix: MathUtils tests --- .../Services/PreProcessing/MathUtilsTests.cs | 93 +------------------ 1 file changed, 1 insertion(+), 92 deletions(-) diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs b/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs index 1c9dfcf..53b30cd 100644 --- a/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs +++ b/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs @@ -8,17 +8,6 @@ namespace DataAggregator.Processor.Tests.Services.PreProcessing; public class MathUtilsTests { #region Mean tests - - [Fact] - public void Mean_ShouldReturnZero_WhenValuesIsNull() - { - // Act - float result = MathUtils.Mean(null!); - - // Assert - Assert.Equal(0.0f, result); - } - [Fact] public void Mean_ShouldReturnZero_WhenValuesIsEmpty() { @@ -58,33 +47,9 @@ public void Mean_ShouldReturnCorrectValue_WhenValuesContainsMultipleElements() Assert.Equal(3.0f, result); } - [Fact] - public void Mean_ShouldReturnCorrectValue_WhenValuesContainsNegativeNumbers() - { - // Arrange - var values = new List { -2.0f, -1.0f, 0.0f, 1.0f, 2.0f }; - - // Act - float result = MathUtils.Mean(values); - - // Assert - Assert.Equal(0.0f, result); - } - #endregion #region StandardDeviation tests - - [Fact] - public void StandardDeviation_ShouldReturnZero_WhenValuesIsNull() - { - // Act - float result = MathUtils.StandardDeviation(null!); - - // Assert - Assert.Equal(0.0f, result); - } - [Fact] public void StandardDeviation_ShouldReturnZero_WhenValuesIsEmpty() { @@ -142,16 +107,6 @@ public void StandardDeviation_ShouldReturnZero_WhenAllValuesAreIdentical() #region Percentile tests - [Fact] - public void Percentile_ShouldReturnZero_WhenValuesIsNull() - { - // Act - float result = MathUtils.Percentile(null!, 50.0f); - - // Assert - Assert.Equal(0.0f, result); - } - [Fact] public void Percentile_ShouldReturnZero_WhenValuesIsEmpty() { @@ -236,16 +191,6 @@ public void Percentile_ShouldReturnCorrectValue_WhenPercentileIs75() #region Skewness tests - [Fact] - public void Skewness_ShouldReturnZero_WhenValuesIsNull() - { - // Act - float result = MathUtils.Skewness(null!); - - // Assert - Assert.Equal(0.0f, result); - } - [Fact] public void Skewness_ShouldReturnZero_WhenValuesContainsLessThan3Elements() { @@ -304,16 +249,6 @@ public void Skewness_ShouldReturnPositiveValue_WhenValuesAreRightSkewed() #region Kurtosis tests - [Fact] - public void Kurtosis_ShouldReturnZero_WhenValuesIsNull() - { - // Act - float result = MathUtils.Kurtosis(null!); - - // Assert - Assert.Equal(0.0f, result); - } - [Fact] public void Kurtosis_ShouldReturnZero_WhenValuesContainsLessThan4Elements() { @@ -351,39 +286,13 @@ public void Kurtosis_ShouldReturnCorrectValue_WhenValuesAreNormallyDistributed() // Assert // For normal distribution, kurtosis should be close to 0 (excess kurtosis) - Assert.Equal(-1.2f, result, 1); + Assert.Equal(-1.3f, result, 1); } #endregion #region Correlation tests - [Fact] - public void Correlation_ShouldReturnZero_WhenXIsNull() - { - // Arrange - var y = new List { 1.0f, 2.0f, 3.0f }; - - // Act - float result = MathUtils.Correlation(null!, y); - - // Assert - Assert.Equal(0.0f, result); - } - - [Fact] - public void Correlation_ShouldReturnZero_WhenYIsNull() - { - // Arrange - var x = new List { 1.0f, 2.0f, 3.0f }; - - // Act - float result = MathUtils.Correlation(x, null!); - - // Assert - Assert.Equal(0.0f, result); - } - [Fact] public void Correlation_ShouldReturnZero_WhenXIsEmpty() { From a7ce5b472492154a1b662f7874d0f093174d743e Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 14:35:54 +0200 Subject: [PATCH 28/70] feat: test for feature extractor --- .../ActuatorCurrentFeatureExtractorTests.cs | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs new file mode 100644 index 0000000..b808d0a --- /dev/null +++ b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs @@ -0,0 +1,190 @@ +using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; + +namespace DataAggregator.Processor.Tests.Services.PreProcessing; + +/// +/// Tests for the class. +/// +public class ActuatorCurrentFeatureExtractorTests +{ + private readonly ActuatorCurrentFeatureExtractor _featureExtractor; + + /// + /// Initializes a new instance of the class. + /// + public ActuatorCurrentFeatureExtractorTests() + => _featureExtractor = new ActuatorCurrentFeatureExtractor(); + + #region PreprocessAsync tests + + [Fact] + public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenValidDataProvided() + { + // Arrange + List measurements = CreateTestMeasurements(); + MachinePredictionConfig config = CreateValidConfig(); + + // Act + float[] result = _featureExtractor.PreprocessAsync(measurements, config); + + // Assert + Assert.NotNull(result); + Assert.Equal(14, result.Length); + } + + [Fact] + public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptyMeasurementsProvided() + { + // Arrange + var measurements = new List(); + MachinePredictionConfig config = CreateValidConfig(); + + // Act + float[] result = _featureExtractor.PreprocessAsync(measurements, config); + + // Assert + Assert.NotNull(result); + Assert.Equal(14, result.Length); + Assert.All(result, feature => Assert.Equal(0.0f, feature)); + } + + [Fact] + public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptySensorsListProvided() + { + // Arrange + List measurements = CreateTestMeasurements(); + MachinePredictionConfig config = CreateValidConfig(); + config.InputSensors.Clear(); + + // Act + float[] result = _featureExtractor.PreprocessAsync(measurements, config); + + // Assert + Assert.NotNull(result); + Assert.Equal(14, result.Length); + Assert.All(result, feature => Assert.Equal(0.0f, feature)); + } + + [Fact] + public void PreprocessAsync_ShouldReturnValidFeatures_WhenValidDataProvided() + { + // Arrange + List measurements = CreateTestMeasurements(); + MachinePredictionConfig config = CreateValidConfig(); + + // Act + float[] result = _featureExtractor.PreprocessAsync(measurements, config); + + // Assert + Assert.NotNull(result); + Assert.Equal(14, result.Length); + + // Check that features are within reasonable bounds + Assert.All(result, feature => Assert.False(float.IsNaN(feature))); + Assert.All(result, feature => Assert.False(float.IsInfinity(feature))); + } + + [Fact] + public void PreprocessAsync_ShouldReturnZeroFeatures_WhenNoValidValuesFound() + { + // Arrange + var measurements = new List + { + new MeasurementData(DateTime.UtcNow, "sensor1", float.NaN), + new MeasurementData(DateTime.UtcNow, "sensor2", float.PositiveInfinity), + new MeasurementData(DateTime.UtcNow, "sensor1", float.NegativeInfinity), + }; + MachinePredictionConfig config = CreateValidConfig(); + + // Act + float[] result = _featureExtractor.PreprocessAsync(measurements, config); + + // Assert + Assert.NotNull(result); + Assert.Equal(14, result.Length); + Assert.All(result, feature => Assert.Equal(0.0f, feature)); + } + + [Fact] + public void PreprocessAsync_ShouldHandleSingleValue_WhenOnlyOneValidMeasurementProvided() + { + // Arrange + var measurements = new List + { + new MeasurementData(DateTime.UtcNow, "sensor1", 10.5f), + }; + MachinePredictionConfig config = CreateValidConfig(); + + // Act + float[] result = _featureExtractor.PreprocessAsync(measurements, config); + + // Assert + Assert.NotNull(result); + Assert.Equal(14, result.Length); + Assert.All(result, feature => Assert.False(float.IsNaN(feature))); + } + + [Fact] + public void PreprocessAsync_ShouldHandleLargeDataset_WhenManyMeasurementsProvided() + { + // Arrange + var measurements = new List(); + var random = new Random(42); + + for (int i = 0; i < 1000; i++) + { + measurements.Add(new MeasurementData( + DateTime.UtcNow.AddSeconds(i), + "sensor1", + (float)random.NextDouble() * 100)); + measurements.Add(new MeasurementData( + DateTime.UtcNow.AddSeconds(i), + "sensor2", + (float)random.NextDouble() * 100)); + } + + MachinePredictionConfig config = CreateValidConfig(); + + // Act + float[] result = _featureExtractor.PreprocessAsync(measurements, config); + + // Assert + Assert.NotNull(result); + Assert.Equal(14, result.Length); + Assert.All(result, feature => Assert.False(float.IsNaN(feature))); + Assert.All(result, feature => Assert.False(float.IsInfinity(feature))); + } + + #endregion + + #region Helper methods + + private static List CreateTestMeasurements() => [ + new MeasurementData(DateTime.UtcNow, "sensor1", 10.5f), + new MeasurementData(DateTime.UtcNow, "sensor2", 20.3f), + new MeasurementData(DateTime.UtcNow, "sensor1", 11.2f), + new MeasurementData(DateTime.UtcNow, "sensor2", 21.8f), + new MeasurementData(DateTime.UtcNow, "sensor1", 12.1f), + new MeasurementData(DateTime.UtcNow, "sensor2", 22.5f) + ]; + + private static MachinePredictionConfig CreateValidConfig() => new() + { + MachineName = "test_machine", + ModelPath = "test_model.onnx", + InputSensors = ["sensor1", "sensor2"], + PredictionSensorName = "prediction_sensor", + PreprocessingStrategy = "ActuatorMergingCurrent", + WindowSizeSeconds = 1, + CycleIntervalSeconds = 1, + Enabled = true, + Preprocessing = new PreprocessingConfig + { + EnableZScoreNormalization = true, + }, + }; + + #endregion +} From f2a623d95776b936cefbe7634bf4e61bb1a28eb6 Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 21:54:53 +0200 Subject: [PATCH 29/70] fix: todo unused --- src/DataAggregator.Collector.Shared/Models/IMeasurementData.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/DataAggregator.Collector.Shared/Models/IMeasurementData.cs b/src/DataAggregator.Collector.Shared/Models/IMeasurementData.cs index 7ed01a1..3d717a5 100644 --- a/src/DataAggregator.Collector.Shared/Models/IMeasurementData.cs +++ b/src/DataAggregator.Collector.Shared/Models/IMeasurementData.cs @@ -1,7 +1,5 @@ namespace DataAggregator.Collector.Shared.Models; -// TODO CJS -> Check if it's better to have a SensorConfig here and work with its datatype - /// /// Interface for common measurement data operations regardless of the value type. /// From bf2f52cb92251e4cee372bb629e1a3f872be23c3 Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 21:55:10 +0200 Subject: [PATCH 30/70] fix: inputs format errors for onnx models --- .../ActuatorCurrentFeatureExtractor.cs | 25 ++++++- .../PreProcessing/IPreprocessingStrategy.cs | 4 +- .../Prediction/IOnnxPredictionEngine.cs | 7 +- .../Prediction/MachinePredictionProcessor.cs | 44 +++++++++--- .../Prediction/OnnxPredictionEngine.cs | 67 ++++++++++++++----- 5 files changed, 111 insertions(+), 36 deletions(-) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index 47be0b1..6a77deb 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -17,8 +17,8 @@ public class ActuatorCurrentFeatureExtractor : IPreprocessingStrategy /// /// List of raw measurements from the data window. /// Configuration for the machine prediction. - /// Feature vector as float array for a single sample (14 features). - public float[] PreprocessAsync(List measurements, MachinePredictionConfig config) + /// Feature vector as dictionary mapping feature names to values for a single sample. + public Dictionary PreprocessAsync(List measurements, MachinePredictionConfig config) { Log.Debug( "Preprocessing {Count} measurements for machine {MachineName}", @@ -31,8 +31,27 @@ public float[] PreprocessAsync(List measurements, MachinePredi // Apply Z-score normalization if enabled float[] normalizedFeatures = NormalizeFeaturesAsync(features, config.Preprocessing); + // Create dictionary with one key per feature + var result = new Dictionary + { + ["GlobalActivityRatio"] = [normalizedFeatures[0]], + ["GlobalChangeDensity"] = [normalizedFeatures[1]], + ["InterAxisMeanCorrelation"] = [normalizedFeatures[2]], + ["InterAxisMaxCorrelation"] = [normalizedFeatures[3]], + ["InterAxisCorrelationVariance"] = [normalizedFeatures[4]], + ["AxisSynchronization"] = [normalizedFeatures[5]], + ["AxisLoadBalance"] = [normalizedFeatures[6]], + ["TemporalStability"] = [normalizedFeatures[7]], + ["GlobalSkewness"] = [normalizedFeatures[8]], + ["GlobalKurtosis"] = [normalizedFeatures[9]], + ["GlobalTrendSlope"] = [normalizedFeatures[10]], + ["CoefficientOfVariation"] = [normalizedFeatures[11]], + ["NormalizedIqrMedian"] = [normalizedFeatures[12]], + ["NormalizedIqrMean"] = [normalizedFeatures[13]], + }; + Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); - return normalizedFeatures; + return result; } #endregion diff --git a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs index 83767ef..43a3316 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs @@ -13,6 +13,6 @@ public interface IPreprocessingStrategy /// /// List of raw measurements from the data window. /// Configuration for the machine prediction. - /// Feature vector as float array for a single sample. - public float[] PreprocessAsync(List measurements, MachinePredictionConfig config); + /// Feature vector as dictionary mapping input names to values for a single sample. + public Dictionary PreprocessAsync(List measurements, MachinePredictionConfig config); } diff --git a/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs index b7457cc..9cd3b65 100644 --- a/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs @@ -9,7 +9,8 @@ public interface IOnnxPredictionEngine /// Performs prediction using an ONNX model. /// /// The path to the ONNX model file. - /// The input data for prediction (single sample). - /// The prediction results as a float array. - public Task PredictAsync(string modelPath, float[] inputData); + /// The input data for prediction as a dictionary mapping input names to values. + /// The prediction results as a dictionary mapping output names to values. + public Task> PredictAsync(string modelPath, Dictionary inputData); + } diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index de4fac0..94cfdc3 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -99,16 +99,22 @@ public async Task ProcessAsync(MachinePredictionConfig config) } // Preprocess data using strategy - float[] preprocessedData = PreprocessDataAsync(measurements, config); + Dictionary preprocessedData = PreprocessDataAsync(measurements, config); - if (preprocessedData == null || preprocessedData.Length == 0) + if (preprocessedData == null || preprocessedData.Count == 0) { Log.Warning("Data preprocessing failed for machine {MachineName}", config.MachineName); return; } // Perform prediction - float[] predictions = await predictionEngine.PredictAsync(config.ModelPath, preprocessedData); + Dictionary predictions = await predictionEngine.PredictAsync(config.ModelPath, preprocessedData); + + if (predictions == null || predictions.Count == 0) + { + Log.Warning("No predictions returned for machine {MachineName}", config.MachineName); + return; + } // Create prediction measurement IMeasurementData predictionMeasurement = CreatePredictionMeasurementAsync(predictions, config); @@ -144,39 +150,55 @@ private async Task> FetchDataWindowAsync(MachinePredictio sensors); } - private float[] PreprocessDataAsync(List measurements, MachinePredictionConfig config) + private Dictionary PreprocessDataAsync(List measurements, MachinePredictionConfig config) { try { if (string.IsNullOrEmpty(config.PreprocessingStrategy)) { Log.Error("No preprocessing strategy configured for machine {MachineName}", config.MachineName); - return []; + return new Dictionary(); } IPreprocessingStrategy strategy = strategyFactory.CreateStrategy(config.PreprocessingStrategy); - float[] preprocessedData = strategy.PreprocessAsync(measurements, config); + Dictionary preprocessedData = strategy.PreprocessAsync(measurements, config); Log.Debug( - "Preprocessed data for machine {MachineName} using strategy {Strategy}: {Features} features", + "Preprocessed data for machine {MachineName} using strategy {Strategy}: {InputCount} inputs", config.MachineName, config.PreprocessingStrategy, - preprocessedData.Length); + preprocessedData.Count); return preprocessedData; } catch (Exception ex) { Log.Error(ex, "Error preprocessing data for machine {MachineName}", config.MachineName); - return Array.Empty(); + return new Dictionary(); } } - private IMeasurementData CreatePredictionMeasurementAsync(float[] predictions, MachinePredictionConfig config) + private IMeasurementData CreatePredictionMeasurementAsync(Dictionary predictions, MachinePredictionConfig config) { + // TODO Fix here + + // Log available outputs for debugging + Log.Debug( + "Available prediction outputs for machine {MachineName}: {OutputNames}", + config.MachineName, + string.Join(", ", predictions.Keys)); + // For simplicity, we'll use the first prediction value // In a real scenario, you might want to handle multiple outputs differently - float predictionValue = predictions.Length > 0 ? predictions[0] : 0.0f; + // or configure which output to use in the config + var firstOutput = predictions.First(); + float predictionValue = firstOutput.Value.Length > 0 ? firstOutput.Value[0] : 0.0f; + + Log.Debug( + "Using prediction output '{OutputName}' with value {Value} for machine {MachineName}", + firstOutput.Key, + predictionValue, + config.MachineName); return new MeasurementData( DateTime.UtcNow, diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs index c3df9c4..be6e474 100644 --- a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs @@ -18,33 +18,48 @@ public class OnnxPredictionEngine : IOnnxPredictionEngine, IDisposable #region Public methods /// - public async Task PredictAsync(string modelPath, float[] inputData) + public async Task> PredictAsync(string modelPath, Dictionary inputData) { try { InferenceSession session = LoadOrGetModel(modelPath); - // Prepare input tensor - int[] inputShape = [1, inputData.Length]; - var inputTensor = new DenseTensor(inputData, inputShape); + // Validate input data against model schema + ValidateInputData(session, inputData); - var inputs = new List - { - NamedOnnxValue.CreateFromTensor("input", inputTensor), - }; - - // Run inference in background thread - return await Task.Run(() => + // Prepare input tensors + var inputs = new List(); + foreach (KeyValuePair kvp in inputData) { - using IDisposableReadOnlyCollection results = session.Run(inputs); - Tensor outputTensor = results[0].AsTensor(); + string inputName = kvp.Key; + float[] inputValues = kvp.Value; - float[] predictions = [.. outputTensor]; + // Create tensor with shape [1, inputValues.Length] for single sample + int[] inputShape = [1, inputValues.Length]; + var inputTensor = new DenseTensor(inputValues, inputShape); - Log.Debug("Prediction completed for model {ModelPath} with {InputFeatures} input features", modelPath, inputData.Length); + inputs.Add(NamedOnnxValue.CreateFromTensor(inputName, inputTensor)); + } - return predictions; - }); + using IDisposableReadOnlyCollection results = session.Run(inputs); + + // Create output dictionary with output names and values + var outputData = new Dictionary(); + foreach (var result in results) + { + string outputName = result.Name; + Tensor outputTensor = result.AsTensor(); + float[] outputValues = [.. outputTensor]; + outputData[outputName] = outputValues; + } + + Log.Debug( + "Prediction completed for model {ModelPath} with {InputCount} inputs and {OutputCount} outputs", + modelPath, + inputData.Count, + outputData.Count); + + return await Task.FromResult(outputData); } catch (Exception ex) { @@ -98,5 +113,23 @@ private InferenceSession LoadOrGetModel(string modelPath) } } + private void ValidateInputData(InferenceSession session, Dictionary inputData) + { + IReadOnlyDictionary modelInputs = session.InputMetadata; + + // Check if all required model inputs are provided + foreach (KeyValuePair modelInput in modelInputs) + { + if (!inputData.ContainsKey(modelInput.Key)) + { + throw new ArgumentException( + $"Model requires input '{modelInput.Key}' but it was not provided. " + + $"Available inputs: [{string.Join(", ", inputData.Keys)}]"); + } + } + + Log.Debug("Input validation passed for model with {InputCount} inputs", inputData.Count); + } + #endregion } From b4f8588561db30a59df026a5ea69581d6385850e Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 21:55:25 +0200 Subject: [PATCH 31/70] feat: add resources to test --- .../DataAggregator.Processor.Tests.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj b/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj index e212051..d188ace 100644 --- a/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj +++ b/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj @@ -27,4 +27,10 @@ + + + Always + + + From c0acc3896952cba207ee817005443f89ab55048a Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 21:55:35 +0200 Subject: [PATCH 32/70] feat: add complete test cases --- .../ActuatorCurrentFeatureExtractorTests.cs | 44 +-- .../PreprocessingStrategyFactoryTests.cs | 48 +++ .../MachinePredictionProcessorTests.cs | 370 ++++++++++++++++++ .../Prediction/OnnxPredictionEngineTests.cs | 245 ++++++++++++ .../PredictionBackgroundServiceTests.cs | 337 ++++++++++++++++ .../resources/opencn_model.onnx | Bin 0 -> 14040 bytes 6 files changed, 1022 insertions(+), 22 deletions(-) create mode 100644 DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs create mode 100644 DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs create mode 100644 DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs create mode 100644 DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs create mode 100644 DataAggregator.Processor.Tests/resources/opencn_model.onnx diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs index b808d0a..b42dec0 100644 --- a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs +++ b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs @@ -27,11 +27,11 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenValidDataProvided() MachinePredictionConfig config = CreateValidConfig(); // Act - float[] result = _featureExtractor.PreprocessAsync(measurements, config); + var result = _featureExtractor.PreprocessAsync(measurements, config); // Assert Assert.NotNull(result); - Assert.Equal(14, result.Length); + Assert.Equal(14, result.Count); } [Fact] @@ -42,12 +42,12 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptyMeasurementsPr MachinePredictionConfig config = CreateValidConfig(); // Act - float[] result = _featureExtractor.PreprocessAsync(measurements, config); + var result = _featureExtractor.PreprocessAsync(measurements, config); // Assert Assert.NotNull(result); - Assert.Equal(14, result.Length); - Assert.All(result, feature => Assert.Equal(0.0f, feature)); + Assert.Equal(14, result.Count); + Assert.All(result, feature => Assert.Equal(0.0f, feature.Value[0])); } [Fact] @@ -59,12 +59,12 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptySensorsListPro config.InputSensors.Clear(); // Act - float[] result = _featureExtractor.PreprocessAsync(measurements, config); + var result = _featureExtractor.PreprocessAsync(measurements, config); // Assert Assert.NotNull(result); - Assert.Equal(14, result.Length); - Assert.All(result, feature => Assert.Equal(0.0f, feature)); + Assert.Equal(14, result.Count); + Assert.All(result, feature => Assert.Equal(0.0f, feature.Value[0])); } [Fact] @@ -75,15 +75,15 @@ public void PreprocessAsync_ShouldReturnValidFeatures_WhenValidDataProvided() MachinePredictionConfig config = CreateValidConfig(); // Act - float[] result = _featureExtractor.PreprocessAsync(measurements, config); + var result = _featureExtractor.PreprocessAsync(measurements, config); // Assert Assert.NotNull(result); - Assert.Equal(14, result.Length); + Assert.Equal(14, result.Count); // Check that features are within reasonable bounds - Assert.All(result, feature => Assert.False(float.IsNaN(feature))); - Assert.All(result, feature => Assert.False(float.IsInfinity(feature))); + Assert.All(result, feature => Assert.False(float.IsNaN(feature.Value[0]))); + Assert.All(result, feature => Assert.False(float.IsInfinity(feature.Value[0]))); } [Fact] @@ -99,12 +99,12 @@ public void PreprocessAsync_ShouldReturnZeroFeatures_WhenNoValidValuesFound() MachinePredictionConfig config = CreateValidConfig(); // Act - float[] result = _featureExtractor.PreprocessAsync(measurements, config); + var result = _featureExtractor.PreprocessAsync(measurements, config); // Assert Assert.NotNull(result); - Assert.Equal(14, result.Length); - Assert.All(result, feature => Assert.Equal(0.0f, feature)); + Assert.Equal(14, result.Count); + Assert.All(result, feature => Assert.Equal(0.0f, feature.Value[0])); } [Fact] @@ -118,12 +118,12 @@ public void PreprocessAsync_ShouldHandleSingleValue_WhenOnlyOneValidMeasurementP MachinePredictionConfig config = CreateValidConfig(); // Act - float[] result = _featureExtractor.PreprocessAsync(measurements, config); + var result = _featureExtractor.PreprocessAsync(measurements, config); // Assert Assert.NotNull(result); - Assert.Equal(14, result.Length); - Assert.All(result, feature => Assert.False(float.IsNaN(feature))); + Assert.Equal(14, result.Count); + Assert.All(result, feature => Assert.False(float.IsNaN(feature.Value[0]))); } [Fact] @@ -148,13 +148,13 @@ public void PreprocessAsync_ShouldHandleLargeDataset_WhenManyMeasurementsProvide MachinePredictionConfig config = CreateValidConfig(); // Act - float[] result = _featureExtractor.PreprocessAsync(measurements, config); + var result = _featureExtractor.PreprocessAsync(measurements, config); // Assert Assert.NotNull(result); - Assert.Equal(14, result.Length); - Assert.All(result, feature => Assert.False(float.IsNaN(feature))); - Assert.All(result, feature => Assert.False(float.IsInfinity(feature))); + Assert.Equal(14, result.Count); + Assert.All(result, feature => Assert.False(float.IsNaN(feature.Value[0]))); + Assert.All(result, feature => Assert.False(float.IsInfinity(feature.Value[0]))); } #endregion diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs b/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs new file mode 100644 index 0000000..9f4e41b --- /dev/null +++ b/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs @@ -0,0 +1,48 @@ +using DataAggregator.Processor.Services.PreProcessing; +using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; + +namespace DataAggregator.Processor.Tests.Services.PreProcessing; + +/// +/// Tests for the class. +/// +public class PreprocessingStrategyFactoryTests +{ + private readonly PreprocessingStrategyFactory _factory; + + /// + /// Initializes a new instance of the class. + /// + public PreprocessingStrategyFactoryTests() => _factory = new PreprocessingStrategyFactory(); + + #region CreateStrategy tests + + [Fact] + public void CreateStrategy_ShouldReturnGoodStrategy() + { + string strategyName = "actuatorcurrent"; + + IPreprocessingStrategy strategy = _factory.CreateStrategy(strategyName); + + Assert.NotNull(strategy); + Assert.IsType(strategy); + } + + [Fact] + public void CreateStrategy_ShouldThrowArgumentException_WhenStrategyNameIsEmpty() + { + string strategyName = string.Empty; + + ArgumentException exception = Assert.Throws(() => _factory.CreateStrategy(strategyName)); + } + + [Fact] + public void CreateStrategy_ShouldThrowArgumentException_WhenStrategyNameIsUnknown() + { + string strategyName = "UnknownStrategy"; + + ArgumentException exception = Assert.Throws(() => _factory.CreateStrategy(strategyName)); + } + + #endregion +} diff --git a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs new file mode 100644 index 0000000..596575e --- /dev/null +++ b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -0,0 +1,370 @@ +using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services.DataStorage; +using DataAggregator.Processor.Services.Prediction; +using DataAggregator.Processor.Services.PreProcessing; +using DataAggregator.Processor.Services.Registration; +using DataAggregator.Shared.Configuration.TimeSeries; +using DataAggregator.Shared.Domain.DataType; +using DataAggregator.Shared.DTOs; +using Moq; + +namespace DataAggregator.Processor.Tests.Services.Prediction; + +/// +/// Tests for the class. +/// +public class MachinePredictionProcessorTests +{ + private readonly Mock _mockInfluxRepository; + private readonly Mock _mockRegistrationClient; + private readonly Mock _mockPredictionEngine; + private readonly Mock _mockStrategyFactory; + private readonly Mock _mockPreprocessingStrategy; + private readonly MachinePredictionProcessor _processor; + + /// + /// Initializes a new instance of the class. + /// + public MachinePredictionProcessorTests() + { + _mockInfluxRepository = new Mock(); + _mockRegistrationClient = new Mock(); + _mockPredictionEngine = new Mock(); + _mockStrategyFactory = new Mock(); + _mockPreprocessingStrategy = new Mock(); + + _processor = new MachinePredictionProcessor( + _mockInfluxRepository.Object, + _mockRegistrationClient.Object, + _mockPredictionEngine.Object, + _mockStrategyFactory.Object); + } + + #region ProcessAsync tests + + [Fact] + public async Task ProcessAsync_ShouldReturnEarly_WhenCollectorInfoIsNull() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync((CollectorInfoDto?)null); + + // Act + await _processor.ProcessAsync(config); + + // Assert + _mockInfluxRepository.Verify(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + } + + [Fact] + public async Task ProcessAsync_ShouldReturnEarly_WhenNoValidSensorsFound() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(new[] { "different_sensor" }); + + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync(collectorInfo); + + // Act + await _processor.ProcessAsync(config); + + // Assert + _mockInfluxRepository.Verify(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + } + + [Fact] + public async Task ProcessAsync_ShouldReturnEarly_WhenNoMeasurementsFound() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync(collectorInfo); + _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync([]); + + // Act + await _processor.ProcessAsync(config); + + // Assert + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task ProcessAsync_ShouldReturnEarly_WhenPreprocessingFails() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + List measurements = CreateTestMeasurements(); + + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync(collectorInfo); + _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(measurements); + _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) + .Returns(_mockPreprocessingStrategy.Object); + _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) + .Returns(new Dictionary()); + + // Act + await _processor.ProcessAsync(config); + + // Assert + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMet() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + List measurements = CreateTestMeasurements(); + var preprocessedData = new Dictionary + { + ["GlobalActivityRatio"] = [1.0f], + ["GlobalChangeDensity"] = [2.0f], + ["InterAxisMeanCorrelation"] = [3.0f], + ["InterAxisMaxCorrelation"] = [4.0f], + ["InterAxisCorrelationVariance"] = [5.0f], + ["AxisSynchronization"] = [6.0f], + ["AxisLoadBalance"] = [7.0f], + ["TemporalStability"] = [8.0f], + ["GlobalSkewness"] = [9.0f], + ["GlobalKurtosis"] = [10.0f], + ["GlobalTrendSlope"] = [11.0f], + ["CoefficientOfVariation"] = [12.0f], + ["NormalizedIqrMedian"] = [13.0f], + ["NormalizedIqrMean"] = [14.0f] + }; + float[] predictions = new float[] { 0.85f }; + + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync(collectorInfo); + _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(measurements); + _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) + .Returns(_mockPreprocessingStrategy.Object); + _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) + .Returns(preprocessedData); + _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) + .ReturnsAsync(predictions); + + // Act + await _processor.ProcessAsync(config); + + // Assert + _mockInfluxRepository.Verify( + x => x.InitializeAsync( + collectorInfo.AssignedInfluxEndpoint.Endpoint, + collectorInfo.AssignedInfluxEndpoint.Token, + "Dataggregator"), Times.Once); + _mockInfluxRepository.Verify( + x => x.QueryMeasurementsAsync( + config.MachineName, + It.IsAny(), + It.IsAny(), + It.IsAny>()), Times.Once); + _mockPredictionEngine.Verify(x => x.PredictAsync(config.ModelPath, preprocessedData), Times.Once); + _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(config.MachineName, It.IsAny()), Times.Once); + } + + [Fact] + public async Task ProcessAsync_ShouldReinitializeInfluxConnection_WhenEndpointChanges() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + CollectorInfoDto collectorInfo1 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint1"); + CollectorInfoDto collectorInfo2 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint2"); + List measurements = CreateTestMeasurements(); + var preprocessedData = new Dictionary + { + ["GlobalActivityRatio"] = [1.0f], + ["GlobalChangeDensity"] = [2.0f], + ["InterAxisMeanCorrelation"] = [3.0f], + ["InterAxisMaxCorrelation"] = [4.0f], + ["InterAxisCorrelationVariance"] = [5.0f], + ["AxisSynchronization"] = [6.0f], + ["AxisLoadBalance"] = [7.0f], + ["TemporalStability"] = [8.0f], + ["GlobalSkewness"] = [9.0f], + ["GlobalKurtosis"] = [10.0f], + ["GlobalTrendSlope"] = [11.0f], + ["CoefficientOfVariation"] = [12.0f], + ["NormalizedIqrMedian"] = [13.0f], + ["NormalizedIqrMean"] = [14.0f] + }; + float[] predictions = new float[] { 0.85f }; + + _mockRegistrationClient.SetupSequence(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync(collectorInfo1) + .ReturnsAsync(collectorInfo2); + _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(measurements); + _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) + .Returns(_mockPreprocessingStrategy.Object); + _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) + .Returns(preprocessedData); + _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) + .ReturnsAsync(predictions); + + // Act + await _processor.ProcessAsync(config); // First call with endpoint1 + await _processor.ProcessAsync(config); // Second call with endpoint2 + + // Assert + _mockInfluxRepository.Verify( + x => x.InitializeAsync( + collectorInfo1.AssignedInfluxEndpoint.Endpoint, + collectorInfo1.AssignedInfluxEndpoint.Token, + "Dataggregator"), Times.Once); + _mockInfluxRepository.Verify( + x => x.InitializeAsync( + collectorInfo2.AssignedInfluxEndpoint.Endpoint, + collectorInfo2.AssignedInfluxEndpoint.Token, + "Dataggregator"), Times.Once); + } + + [Fact] + public async Task ProcessAsync_ShouldNotReinitializeInfluxConnection_WhenEndpointIsSame() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + List measurements = CreateTestMeasurements(); + var preprocessedData = new Dictionary + { + ["GlobalActivityRatio"] = [1.0f], + ["GlobalChangeDensity"] = [2.0f], + ["InterAxisMeanCorrelation"] = [3.0f], + ["InterAxisMaxCorrelation"] = [4.0f], + ["InterAxisCorrelationVariance"] = [5.0f], + ["AxisSynchronization"] = [6.0f], + ["AxisLoadBalance"] = [7.0f], + ["TemporalStability"] = [8.0f], + ["GlobalSkewness"] = [9.0f], + ["GlobalKurtosis"] = [10.0f], + ["GlobalTrendSlope"] = [11.0f], + ["CoefficientOfVariation"] = [12.0f], + ["NormalizedIqrMedian"] = [13.0f], + ["NormalizedIqrMean"] = [14.0f] + }; + float[] predictions = new float[] { 0.85f }; + + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync(collectorInfo); + _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(measurements); + _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) + .Returns(_mockPreprocessingStrategy.Object); + _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) + .Returns(preprocessedData); + _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) + .ReturnsAsync(predictions); + + // Act + await _processor.ProcessAsync(config); + await _processor.ProcessAsync(config); + + // Assert + _mockInfluxRepository.Verify( + x => x.InitializeAsync( + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task ProcessAsync_ShouldThrowException_WhenRegistrationClientThrows() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ThrowsAsync(new InvalidOperationException("Registration service error")); + + // Act & Assert + await Assert.ThrowsAsync(() => _processor.ProcessAsync(config)); + } + + [Fact] + public async Task ProcessAsync_ShouldThrowException_WhenPredictionEngineThrows() + { + // Arrange + MachinePredictionConfig config = CreateValidMachineConfig(); + CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + List measurements = CreateTestMeasurements(); + var preprocessedData = new Dictionary + { + ["GlobalActivityRatio"] = [1.0f], + ["GlobalChangeDensity"] = [2.0f], + ["InterAxisMeanCorrelation"] = [3.0f], + ["InterAxisMaxCorrelation"] = [4.0f], + ["InterAxisCorrelationVariance"] = [5.0f], + ["AxisSynchronization"] = [6.0f], + ["AxisLoadBalance"] = [7.0f], + ["TemporalStability"] = [8.0f], + ["GlobalSkewness"] = [9.0f], + ["GlobalKurtosis"] = [10.0f], + ["GlobalTrendSlope"] = [11.0f], + ["CoefficientOfVariation"] = [12.0f], + ["NormalizedIqrMedian"] = [13.0f], + ["NormalizedIqrMean"] = [14.0f] + }; + + _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) + .ReturnsAsync(collectorInfo); + _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(measurements); + _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) + .Returns(_mockPreprocessingStrategy.Object); + _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) + .Returns(preprocessedData); + _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) + .ThrowsAsync(new InvalidOperationException("Prediction error")); + + // Act & Assert + await Assert.ThrowsAsync(() => _processor.ProcessAsync(config)); + } + + #endregion + + #region Helper methods + + private static MachinePredictionConfig CreateValidMachineConfig() => new() + { + MachineName = "test_machine", + ModelPath = "test_model.onnx", + InputSensors = ["sensor1", "sensor2"], + PredictionSensorName = "prediction_sensor", + PreprocessingStrategy = "test_strategy", + WindowSizeSeconds = 300, + CycleIntervalSeconds = 60, + Enabled = true, + }; + + private static CollectorInfoDto CreateCollectorInfoWithSensors(IEnumerable sensorNames, string endpoint = "http://localhost:8086") => new( + "test_device", + "test_location", + "http://localhost:5000/health", + new InfluxEndpoint("TestEndpoint", endpoint, "test_token"), + sensorNames.Select(name => new SensorInfoDto(name, "Type", "Unit", [], SensorDataType.Float)).ToList(), + []); + + private static List CreateTestMeasurements() + => + [ + new MeasurementData(DateTime.UtcNow, "sensor1", 10.5f), + new MeasurementData(DateTime.UtcNow, "sensor2", 20.3f) + ]; + + #endregion +} diff --git a/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs b/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs new file mode 100644 index 0000000..cdde700 --- /dev/null +++ b/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs @@ -0,0 +1,245 @@ +using System.Reflection.Metadata; +using DataAggregator.Processor.Services.Prediction; +using Microsoft.ML.OnnxRuntime; + +namespace DataAggregator.Processor.Tests.Services.Prediction; + +/// +/// Tests for the class. +/// +public class OnnxPredictionEngineTests : IDisposable +{ + private readonly OnnxPredictionEngine _predictionEngine; + + /// + /// Initializes a new instance of the class. + /// + public OnnxPredictionEngineTests() + => _predictionEngine = new OnnxPredictionEngine(); + + #region PredictAsync tests + + [Fact] + public async Task PredictAsync_ShouldThrowFileNotFoundException_WhenModelPathDoesNotExist() + { + // Arrange + string nonExistentModelPath = "non_existent_model.onnx"; + var inputData = new Dictionary + { + ["GlobalActivityRatio"] = [1.0f], + ["GlobalChangeDensity"] = [2.0f], + ["InterAxisMeanCorrelation"] = [3.0f] + }; + + // Act & Assert + FileNotFoundException exception = await Assert.ThrowsAsync( + () => _predictionEngine.PredictAsync(nonExistentModelPath, inputData)); + } + + [Fact] + public async Task PredictAsync_ShouldCacheModel_WhenSameModelPathIsUsedMultipleTimes() + { + // Arrange + string modelPath = _testModelPath; + string copyPath = Path.Combine("resources", "opencn_model_copy.onnx"); + File.Copy(modelPath, copyPath); + + var inputData = new Dictionary + { + ["GlobalActivityRatio"] = [1.0f], + ["GlobalChangeDensity"] = [2.0f], + ["InterAxisMeanCorrelation"] = [3.0f], + ["InterAxisMaxCorrelation"] = [4.0f], + ["InterAxisCorrelationVariance"] = [5.0f], + ["AxisSynchronization"] = [6.0f], + ["AxisLoadBalance"] = [7.0f], + ["TemporalStability"] = [8.0f], + ["GlobalSkewness"] = [9.0f], + ["GlobalKurtosis"] = [10.0f], + ["GlobalTrendSlope"] = [11.0f], + ["CoefficientOfVariation"] = [12.0f], + ["NormalizedIqrMedian"] = [13.0f], + ["NormalizedIqrMean"] = [14.0f] + }; + + // Act + Dictionary result1 = await _predictionEngine.PredictAsync(copyPath, inputData); + File.Delete(copyPath); // Simulate model file deletion + Dictionary result2 = await _predictionEngine.PredictAsync(copyPath, inputData); + + // Assert + Assert.NotNull(result1); + Assert.NotNull(result2); + Assert.Equal(result1.Count, result2.Count); + } + + [Fact] + public async Task PredictAsync_ShouldReturnCorrectOutputShape_WhenValidInputProvided() + { + // Arrange + string modelPath = _testModelPath; + var inputData = new Dictionary + { + ["GlobalActivityRatio"] = [1.0f], + ["GlobalChangeDensity"] = [2.0f], + ["InterAxisMeanCorrelation"] = [3.0f], + ["InterAxisMaxCorrelation"] = [4.0f], + ["InterAxisCorrelationVariance"] = [5.0f], + ["AxisSynchronization"] = [6.0f], + ["AxisLoadBalance"] = [7.0f], + ["TemporalStability"] = [8.0f], + ["GlobalSkewness"] = [9.0f], + ["GlobalKurtosis"] = [10.0f], + ["GlobalTrendSlope"] = [11.0f], + ["CoefficientOfVariation"] = [12.0f], + ["NormalizedIqrMedian"] = [13.0f], + ["NormalizedIqrMean"] = [14.0f] + }; + + try + { + // Act + Dictionary result = await _predictionEngine.PredictAsync(modelPath, inputData); + + // Assert + Assert.NotNull(result); + Assert.True(result.Count > 0); + } + finally + { + // Cleanup + if (File.Exists(modelPath)) + { + File.Delete(modelPath); + } + } + } + + [Fact] + public async Task PredictAsync_ShouldHandleEmptyInputArray_WhenProvided() + { + // Arrange + string modelPath = _testModelPath; + var inputData = new Dictionary { ["GlobalActivityRatio"] = new float[0] }; + + try + { + // Act + Dictionary result = await _predictionEngine.PredictAsync(modelPath, inputData); + + // Assert + Assert.NotNull(result); + } + finally + { + // Cleanup + if (File.Exists(modelPath)) + { + File.Delete(modelPath); + } + } + } + + #endregion + + #region GetModelMetadataAsync tests + + [Fact] + public async Task GetModelMetadataAsync_ShouldReturnMetadata_WhenValidModelProvided() + { + // Arrange + string modelPath = _testModelPath; + + try + { + // Act + OnnxModelMetadata metadata = await _predictionEngine.GetModelMetadataAsync(modelPath); + + // Assert + Assert.NotNull(metadata); + Assert.NotNull(metadata.InputNames); + Assert.NotNull(metadata.OutputNames); + Assert.NotNull(metadata.InputShapes); + Assert.NotNull(metadata.OutputShapes); + } + finally + { + // Cleanup + if (File.Exists(modelPath)) + { + File.Delete(modelPath); + } + } + } + + [Fact] + public async Task GetModelMetadataAsync_ShouldThrowFileNotFoundException_WhenModelPathDoesNotExist() + { + // Arrange + string nonExistentModelPath = "non_existent_model.onnx"; + + // Act & Assert + FileNotFoundException exception = await Assert.ThrowsAsync( + () => _predictionEngine.GetModelMetadataAsync(nonExistentModelPath)); + } + + #endregion + + #region Dispose tests + + [Fact] + public void Dispose_ShouldNotThrowException_WhenCalledMultipleTimes() + { + // Act & Assert + Exception exception = Record.Exception(() => + { + _predictionEngine.Dispose(); + _predictionEngine.Dispose(); + }); + + Assert.Null(exception); + } + + [Fact] + public void Dispose_ShouldClearModelCache_WhenCalled() + { + // Arrange + string modelPath = _testModelPath; + var inputData = new Dictionary { ["GlobalActivityRatio"] = [1.0f] }; + + try + { + // Act - Load model into cache + _ = _predictionEngine.PredictAsync(modelPath, inputData).Result; + + // Act - Dispose + _predictionEngine.Dispose(); + + // Assert - Should be able to dispose without exception + Assert.True(true); // If we reach here, no exception was thrown + } + finally + { + // Cleanup + if (File.Exists(modelPath)) + { + File.Delete(modelPath); + } + } + } + + #endregion + + #region Helper methods + + private static string _testModelPath = Path.Combine("resources", "opencn_model.onnx"); + + // Simple test model bytes (minimal ONNX model) + private static readonly byte[] TestModelBytes = Convert.FromBase64String( + "T05OWA=="); // This is just a placeholder - in real tests you'd use a proper minimal ONNX model + + #endregion + + /// + public void Dispose() => _predictionEngine?.Dispose(); +} diff --git a/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs new file mode 100644 index 0000000..030f1f9 --- /dev/null +++ b/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs @@ -0,0 +1,337 @@ +using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services; +using DataAggregator.Processor.Services.Prediction; +using Microsoft.Extensions.Options; +using Moq; + +namespace DataAggregator.Processor.Tests.Services; + +/// +/// Tests for the class. +/// +public class PredictionBackgroundServiceTests : IDisposable +{ + private readonly Mock> _mockConfiguration; + private readonly Mock _mockPredictionProcessor; + private readonly PredictionBackgroundService _backgroundService; + private readonly CancellationTokenSource _cancellationTokenSource; + + /// + /// Initializes a new instance of the class. + /// + public PredictionBackgroundServiceTests() + { + _mockConfiguration = new Mock>(); + _mockPredictionProcessor = new Mock(); + _cancellationTokenSource = new CancellationTokenSource(); + + _backgroundService = new PredictionBackgroundService( + _mockConfiguration.Object, + _mockPredictionProcessor.Object); + } + + #region ExecuteAsync tests + + [Fact] + public async Task ExecuteAsync_ShouldStartSuccessfully_WhenValidConfigurationProvided() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + Assert.True(true); // If we reach here, no exception was thrown + } + + [Fact] + public async Task ExecuteAsync_ShouldScheduleEnabledMachines_WhenConfigurationContainsEnabledMachines() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + // The service should have started without throwing exceptions + Assert.True(true); + } + + [Fact] + public async Task ExecuteAsync_ShouldNotScheduleDisabledMachines_WhenConfigurationContainsDisabledMachines() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].Enabled = false; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + // The service should have started without throwing exceptions + Assert.True(true); + } + + [Fact] + public async Task ExecuteAsync_ShouldHandleEmptyMachineList_WhenConfigurationContainsNoMachines() + { + // Arrange + var config = new PredictionServiceConfiguration + { + Machines = [], + }; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + // The service should have started without throwing exceptions + Assert.True(true); + } + + [Fact] + public async Task ExecuteAsync_ShouldHandleAllDisabledMachines_WhenConfigurationContainsOnlyDisabledMachines() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + foreach (MachinePredictionConfig machine in config.Machines) + { + machine.Enabled = false; + } + + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + // The service should have started without throwing exceptions + Assert.True(true); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowFileNotFoundException_WhenModelFileDoesNotExist() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].ModelPath = "non_existent_model.onnx"; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenMachineNameIsEmpty() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].MachineName = string.Empty; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenMachineNameIsNull() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].MachineName = null!; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenNoInputSensorsConfigured() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].InputSensors.Clear(); + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPredictionSensorNameIsEmpty() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].PredictionSensorName = string.Empty; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPredictionSensorNameIsNull() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].PredictionSensorName = null!; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPreprocessingStrategyIsEmpty() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].PreprocessingStrategy = string.Empty; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPreprocessingStrategyIsNull() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + config.Machines[0].PreprocessingStrategy = null!; + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act & Assert + await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); + } + + [Fact] + public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + _cancellationTokenSource.Cancel(); + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + // The service should have stopped without throwing exceptions + Assert.True(true); + } + + #endregion + + #region StopAsync tests + + [Fact] + public async Task StopAsync_ShouldDisposeAllTimers_WhenCalled() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + await _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + // The service should have stopped without throwing exceptions + Assert.True(true); + } + + [Fact] + public async Task StopAsync_ShouldNotThrowException_WhenCalledMultipleTimes() + { + // Arrange + PredictionServiceConfiguration config = CreateValidConfiguration(); + _mockConfiguration.Setup(x => x.Value).Returns(config); + + // Act + await _backgroundService.StartAsync(_cancellationTokenSource.Token); + await Task.Delay(100); // Give it time to start + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + await _backgroundService.StopAsync(_cancellationTokenSource.Token); + + // Assert + // The service should have stopped without throwing exceptions + Assert.True(true); + } + + #endregion + + #region Helper methods + + private static PredictionServiceConfiguration CreateValidConfiguration() + { + // Create a temporary model file for testing + string tempModelPath = Path.GetTempFileName() + ".onnx"; + File.WriteAllText(tempModelPath, "dummy model content"); + + return new PredictionServiceConfiguration + { + Machines = + [ + new MachinePredictionConfig + { + MachineName = "test_machine_1", + ModelPath = tempModelPath, + InputSensors = ["sensor1", "sensor2"], + PredictionSensorName = "prediction_sensor", + PreprocessingStrategy = "ActuatorMergingCurrent", + WindowSizeSeconds = 300, + CycleIntervalSeconds = 60, + Enabled = true, + Preprocessing = new PreprocessingConfig + { + EnableZScoreNormalization = true + }, + }, + new MachinePredictionConfig + { + MachineName = "test_machine_2", + ModelPath = tempModelPath, + InputSensors = ["sensor3", "sensor4"], + PredictionSensorName = "prediction_sensor_2", + PreprocessingStrategy = "ActuatorMergingCurrent", + WindowSizeSeconds = 600, + CycleIntervalSeconds = 120, + Enabled = true, + Preprocessing = new PreprocessingConfig + { + EnableZScoreNormalization = true + }, + } + ], + }; + } + + #endregion + + /// + public void Dispose() + { + _cancellationTokenSource?.Dispose(); + _backgroundService?.Dispose(); + } +} diff --git a/DataAggregator.Processor.Tests/resources/opencn_model.onnx b/DataAggregator.Processor.Tests/resources/opencn_model.onnx new file mode 100644 index 0000000000000000000000000000000000000000..042359734ff9cfafc52e5521b2bb1c86f9f5936c GIT binary patch literal 14040 zcmeHOO>7&-71ojzNsTCqo>;a+IB~-y4MMvLNy&dgBU+U0M2sXSQIeu=5q2dm<)z78 zb$96~P7f}MCPk7{ivms207lWGm!gLjL3^@-9C|1cB!@OlZ|$i_;bTulf%MJnE_Y^k zhO`~<&?E%J<<7qE&6}Aw-@JLVN+)r0ZFP42+%tKlR5;L8w=U_1*3neU(2eccrs)`( zv+%PndR0MpJ=QTd)y~qEqhHaT7oS!g-NgF^g{6FIrMuH}G^^nMDBg?qTiQ-X-O`$k zD{7~wHCi20b!NuK-u~peq7;vy{W`^PkL`uT7<{fP$pxh9v!-F}&UQQKwZ1XSmsDe0 zJF6KsSmYbS6_)a8&zRDPtogBm4y_o_+NE9HUei=#*|aRJL%PSncgvmxc9lnD*N+r* zFsNPKWzAv?<{kuARYqjhn+iJIw~8_8In~ltV@t#L40;;aH#Z{th;NaB+<4L0x@4J# ze$^ew{er^4n)wk~^F0MklZsbOwOLa;w1aY?g20O7BeLQZ1?_uA>+YDA+Szc_O}#^A z8;%qOmYf)oCErz0)@7WH%i0TuX4`n0zYlCUIdU7G=vj_w>wO#i`@n`%BeLQ93d*_d ze#X*_=0?Zd(eS=-Nnpq65!vxW1szy6wN^{t(lx`m(4y0rGT%XQc_}~P@o{-ng#0>8 z;dRsMsvZ5R)?E3vwWc+JTkw8CA!E*{c=!h}dqOpXF*s7hm@+Ez{Y4xJ7i_-yw9ad))3d;* ztP4E{;+y9j5_+6Z2q4@c_#D zWv;@4Qo63hSy>tclKAf+nxbK$?ZWL^T#3cQ{W6aP4Z=yuz?mr?qVh))Uhzh>MGNqwJ1pI}OKzhPQxU3W@a#>*q*| za%jS6II6X+IW`>wX#3=*YHMz+8-%gN!w9R}P|>#PKnUD3XqOh^8_%v0JtZ_{z);yx zhow3>u_86Trp z7y?pCK-Xeu(nWQNeRWiOdTD+6@y6=8r=-6VtLK)^*Q4M6hgjm_5Q=5oE^r-nVQ24& zrSFt3U0% z^VQ!}FP72~9BhM%^Jt%Igk$wI@{5jnJVy6P-UUXU=<@E!@Eu3kMYYt99l#WD6Zm)+ z;xxe>fz{9v7uMBn8wkPMxxob3x=ehur}Z6_AR`~5<3W^lCr00=b3~3fG;wC)$-J1~ zs2uTP_K%3&hmfa@w3}*TYb7Gj_A7ir)3+}<_QIL5v1`!L*Y^Csqwij-!rz|%_u=zj z+k?NVcioHsb)^b_5+U;RNMN58Lj3sWuiaosu{!hWOE(x&tp59>%^M6MR{!(P>J5ez zt6@Ss<`ZHWB`G16aF%04U?8|JxtutMLJv{OCB+h+Vp$PDz=!}MyjVv3GoBdB`Lsuj zu&MNZ5eUnSWi(EhvBXhh8Nx;&MG`g+;>L=E8_SR!V-e|tRBIm}6$BpF>H%jC5g^7)Bi2oW)|!AaSdmC$PIGN8A%QraJgZ;n2jOwJDUdI;z(LEypfK2gf@-7K z$}nt&V!=@e_Bq5F#NNQjfPiOe1?QGcV@vhM=B#cz`J`J?@UF#4lu+Sd!p%6XqX+qBze`2N0D!NF!k^&F!b`cZVgi1UKdh@yE$Q8k)M#|J_Oh7kqF83oIt z%sW05IqQokIl(A-)X&WOr*tJe!ynT{;3q}!eq!H;`vnpBDF(hII0nE6!-r!L6{i^$ z>(XNbe2@5C%U6ed?}p>8V4gfKK5fAH&?yi(r;2J~z51;1gyK+0k4Ova%EyqGHOKA& zK(tQQJ7J|`&mHhx>|GSf`r}{X=y10KFOkf07}z8_>uOiC@e#q(p3$>4pXmp}L#+Ax zSm*#l^v+7%E9A(j&b$%9zrs@A~z~!Ear1 z1X~xKg4UyFkz~VC2%o=Xy#s_)dIC*mrXa%S7LY|0UeE9hzraQPf~A?GkU1r%FgQam zQDK%&-crzv>n5&n;5~=fV$N;EfRMl3`FU6LybAg&)Jb@p9F}y7(>bM(c<y?bvX!h2Xpb#&gQry?a_{kDiFWJ1++{8u%RgR*{VN|)8$)y$) zrRK(1#fMQ$QF7@@+%zD&FPNPD&Ig}iM8*AmaaR1=*g-UtR`5PFnf}E8C@7Xz0(owB zVyXNbyU6nsR}QMkUyPDgbaKdROHv^8fM5}q8&{0J@gvoJO!QukRQln_yEDT@eb$lN zdsOfyid1h|{<_Ejj|eSp^koid70@Rkd3f7GD*Yw@sGLZo1s=J191RGHrHTU>=g1z< z$zcm8$S~+wP-Ae2#$3rIxoP20&!5zf0sk|nb@UmD`4k{sW_<0Kme*z3` z3y_K=#FW=^3Js1X$O?I!P8*NbPAq;_rmHy>m%cixL1hf*1gp zAGAf55nP*FAi2HnfU3YR2W@2sAfX!sL@)pV%SVU;1_*yexH*-!9hOpl*Ev9SU)G=S z17u-|2~YI_(m)L2E#m+TbYp0qlIcAF06h}|3={|A6~EIa^Ve(}rVl&0<%0M1_m*T} zL`2aBYuV(No70oeEeKrrxlToenqfV6_Z1SSh_}DYrF~`1P}&c2iD#m2Lcl}q2iWAZ zR0_O2oR^l@480T0Jj=zQK|$(vfQxW)u`+0XLI zrC_%59^tlI#QSaV9zJU;S9@o%Ub_$DsC-z&(Eb#$Ui;2tz4oV!^+;<46hE}Jw;$_~ zwd=NHz4mR#dI7?x{-JHTgIKS9$FUyi@DR7(N~}kEF9aJN>ydd%nVIbZA<^DutVcST ZpRsHW!g+hE@mej5n12q-#cHu!=6`{SrkDT# literal 0 HcmV?d00001 From 2a0ab0c8bc93680ab91f92f6adf1c5f11d26ddf2 Mon Sep 17 00:00:00 2001 From: Colin Jaques Date: Wed, 16 Jul 2025 22:27:31 +0200 Subject: [PATCH 33/70] feat: use IMeasurementData in place of dictionary --- .../Prediction/IOnnxPredictionEngine.cs | 5 +- .../Prediction/OnnxPredictionEngine.cs | 82 +++++++++++-------- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs index 9cd3b65..4e99b7c 100644 --- a/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs @@ -1,3 +1,5 @@ +using DataAggregator.Collector.Shared.Models; + namespace DataAggregator.Processor.Services.Prediction; /// @@ -11,6 +13,5 @@ public interface IOnnxPredictionEngine /// The path to the ONNX model file. /// The input data for prediction as a dictionary mapping input names to values. /// The prediction results as a dictionary mapping output names to values. - public Task> PredictAsync(string modelPath, Dictionary inputData); - + public Task> PredictAsync(string modelPath, IEnumerable inputData); } diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs index be6e474..6abc7bf 100644 --- a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs @@ -1,3 +1,4 @@ +using DataAggregator.Collector.Shared.Models; using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using Serilog; @@ -17,49 +18,43 @@ public class OnnxPredictionEngine : IOnnxPredictionEngine, IDisposable #region Public methods - /// - public async Task> PredictAsync(string modelPath, Dictionary inputData) + /// + public async Task> PredictAsync( + string modelPath, + IEnumerable inputData) { try { InferenceSession session = LoadOrGetModel(modelPath); - - // Validate input data against model schema ValidateInputData(session, inputData); - // Prepare input tensors var inputs = new List(); - foreach (KeyValuePair kvp in inputData) - { - string inputName = kvp.Key; - float[] inputValues = kvp.Value; - // Create tensor with shape [1, inputValues.Length] for single sample - int[] inputShape = [1, inputValues.Length]; - var inputTensor = new DenseTensor(inputValues, inputShape); - - inputs.Add(NamedOnnxValue.CreateFromTensor(inputName, inputTensor)); + foreach (IMeasurementData data in inputData) + { + inputs.Add(CreateNamedOnnxValue(data)); } using IDisposableReadOnlyCollection results = session.Run(inputs); - - // Create output dictionary with output names and values - var outputData = new Dictionary(); - foreach (var result in results) + + var outputMeasurements = new List(); + DateTime now = DateTime.UtcNow; + + foreach (DisposableNamedOnnxValue? result in results) { - string outputName = result.Name; - Tensor outputTensor = result.AsTensor(); - float[] outputValues = [.. outputTensor]; - outputData[outputName] = outputValues; + string name = result.Name; + Tensor tensor = result.AsTensor(); + float[] values = [.. tensor]; + + for (int i = 0; i < values.Length; i++) + { + outputMeasurements.Add( + new MeasurementData(now, $"{name}_{i}", values[i])); + } } - Log.Debug( - "Prediction completed for model {ModelPath} with {InputCount} inputs and {OutputCount} outputs", - modelPath, - inputData.Count, - outputData.Count); - - return await Task.FromResult(outputData); + Log.Debug("Prediction completed for model {ModelPath} with {OutputCount} outputs", modelPath, outputMeasurements.Count); + return await Task.FromResult(outputMeasurements); } catch (Exception ex) { @@ -113,22 +108,41 @@ private InferenceSession LoadOrGetModel(string modelPath) } } - private void ValidateInputData(InferenceSession session, Dictionary inputData) + private void ValidateInputData(InferenceSession session, IEnumerable inputData) { IReadOnlyDictionary modelInputs = session.InputMetadata; // Check if all required model inputs are provided foreach (KeyValuePair modelInput in modelInputs) { - if (!inputData.ContainsKey(modelInput.Key)) + if (!inputData.Any(x => modelInput.Key == x.SensorName)) { throw new ArgumentException( - $"Model requires input '{modelInput.Key}' but it was not provided. " + - $"Available inputs: [{string.Join(", ", inputData.Keys)}]"); + $"Model requires input '{modelInput.Key}' but it was not provided. "); } } - Log.Debug("Input validation passed for model with {InputCount} inputs", inputData.Count); + Log.Debug("Input validation passed for model with {InputCount} inputs", inputData.Count()); + } + + private NamedOnnxValue CreateNamedOnnxValue(IMeasurementData data) + { + string name = data.SensorName; + object rawValue = data.GetRawValue(); + + return rawValue switch + { + float[] fArray => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(fArray, [1, fArray.Length])), + float f => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new[] { f }, [1, 1])), + + int[] iArray => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(iArray, [1, iArray.Length])), + int i => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new[] { i }, [1, 1])), + + double[] dArray => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(dArray, [1, dArray.Length])), + double d => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new[] { d }, [1, 1])), + + _ => throw new NotSupportedException($"Unsupported data type {data.ValueType} for sensor {name}"), + }; } #endregion From f06e8fe8ac3ed8a22e031f6b1dac6d05ea463dc1 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 17 Jul 2025 10:23:36 +0200 Subject: [PATCH 34/70] feat: naming modification --- .../Services/Prediction/MachinePredictionProcessorTests.cs | 4 ++-- src/DataAggregator.Processor/Program.cs | 2 +- .../{IInfluxV3Repository.cs => IDataRepository.cs} | 4 ++-- .../Services/DataStorage/InfluxV3Repository.cs | 2 +- .../Services/Prediction/MachinePredictionProcessor.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename src/DataAggregator.Processor/Services/DataStorage/{IInfluxV3Repository.cs => IDataRepository.cs} (94%) diff --git a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs index 596575e..e0bcd6d 100644 --- a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs +++ b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -16,7 +16,7 @@ namespace DataAggregator.Processor.Tests.Services.Prediction; /// public class MachinePredictionProcessorTests { - private readonly Mock _mockInfluxRepository; + private readonly Mock _mockInfluxRepository; private readonly Mock _mockRegistrationClient; private readonly Mock _mockPredictionEngine; private readonly Mock _mockStrategyFactory; @@ -28,7 +28,7 @@ public class MachinePredictionProcessorTests /// public MachinePredictionProcessorTests() { - _mockInfluxRepository = new Mock(); + _mockInfluxRepository = new Mock(); _mockRegistrationClient = new Mock(); _mockPredictionEngine = new Mock(); _mockStrategyFactory = new Mock(); diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index e031d34..170367d 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -38,7 +38,7 @@ builder.Services.Configure(builder.Configuration.GetSection("PredictionService")); // Register services -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs similarity index 94% rename from src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs rename to src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs index 55b05db..4997f60 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/IInfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs @@ -4,9 +4,9 @@ namespace DataAggregator.Processor.Services.DataStorage; /// -/// Interface for InfluxDB v3 repository operations. +/// Interface for data repository operations. /// -public interface IInfluxV3Repository +public interface IDataRepository { /// /// Initializes the repository with connection parameters. diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index 44ec0f2..622c03c 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -11,7 +11,7 @@ namespace DataAggregator.Processor.Services.DataStorage; /// /// Implementation of InfluxDB v3 repository for prediction service. /// -public class InfluxV3Repository : IInfluxV3Repository, IDisposable +public class InfluxV3Repository : IDataRepository, IDisposable { private readonly string _database = "Dataggregator"; private InfluxDBClient? _client; diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index 94cfdc3..d649ec7 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -19,7 +19,7 @@ namespace DataAggregator.Processor.Services.Prediction; /// The ONNX prediction engine. /// The preprocessing strategy factory. public class MachinePredictionProcessor( - IInfluxV3Repository influxRepository, + IDataRepository influxRepository, IRegistrationServiceClient registrationClient, IOnnxPredictionEngine predictionEngine, IPreprocessingStrategyFactory strategyFactory) From d026c748e5641f437d89ae62312165a737042b3a Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 17 Jul 2025 11:17:03 +0200 Subject: [PATCH 35/70] feat: unify usage of IMeasurementData --- .../Configuration/MachinePredictionConfig.cs | 5 - .../Services/DataStorage/IDataRepository.cs | 2 +- .../DataStorage/InfluxV3Repository.cs | 24 ++-- .../ActuatorCurrentFeatureExtractor.cs | 122 +++++++++--------- .../PreProcessing/IPreprocessingStrategy.cs | 2 +- .../Prediction/MachinePredictionProcessor.cs | 56 ++------ .../Services/PredictionBackgroundService.cs | 10 +- 7 files changed, 92 insertions(+), 129 deletions(-) diff --git a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs index 7589a72..5f94703 100644 --- a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs +++ b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs @@ -30,11 +30,6 @@ public class MachinePredictionConfig /// public List InputSensors { get; set; } = []; - /// - /// Gets or sets the name of the prediction sensor. - /// - public string PredictionSensorName { get; set; } = string.Empty; - /// /// Gets or sets the window size in seconds for data collection. /// diff --git a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs index 4997f60..169a2fe 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs @@ -32,5 +32,5 @@ public interface IDataRepository /// The table name (machine name). /// The measurement data to write. /// A task representing the asynchronous operation. - public Task WriteMeasurementAsync(string table, IMeasurementData measurement); + public Task WriteMeasurementAsync(string table, IEnumerable measurement); } diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index 622c03c..0128799 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -136,7 +136,7 @@ SensorDataType.Double or SensorDataType.Float when double.TryParse(value.ToStrin } /// - public async Task WriteMeasurementAsync(string table, IMeasurementData measurement) + public async Task WriteMeasurementAsync(string table, IEnumerable measurements) { if (_client == null) { @@ -145,15 +145,23 @@ public async Task WriteMeasurementAsync(string table, IMeasurementData measureme try { - PointData point = PointData - .Measurement(table) - .SetTimestamp(DateTime.SpecifyKind(measurement.TimeStamp, DateTimeKind.Utc)) - .SetField(measurement.SensorName, measurement.GetRawValue()) - .SetTag("type", "Prediction"); + IEnumerable groupedPoints = measurements + .GroupBy(m => m.TimeStamp) + .Select(group => + { + var fields = group.ToDictionary( + m => m.SensorName, + m => m.GetRawValue()); + + return PointData + .Measurement(table) + .SetTimestamp(DateTime.SpecifyKind(group.Key, DateTimeKind.Utc)) + .SetFields(fields); + }); - await _client.WritePointsAsync(new[] { point }, null, WritePrecision.Ms); + await _client.WritePointsAsync(groupedPoints, null, WritePrecision.Ms); - Log.Debug("Written measurement for table {Table}, sensor {Sensor}", table, measurement.SensorName); + Log.Information($"Inserted {groupedPoints.Count()} element"); } catch (Exception ex) { diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index 6a77deb..42415a5 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -18,11 +18,11 @@ public class ActuatorCurrentFeatureExtractor : IPreprocessingStrategy /// List of raw measurements from the data window. /// Configuration for the machine prediction. /// Feature vector as dictionary mapping feature names to values for a single sample. - public Dictionary PreprocessAsync(List measurements, MachinePredictionConfig config) + public IEnumerable PreprocessAsync(IEnumerable measurements, MachinePredictionConfig config) { Log.Debug( "Preprocessing {Count} measurements for machine {MachineName}", - measurements.Count, + measurements.Count(), config.MachineName); // Extract the 14 features from measurements @@ -31,23 +31,23 @@ public Dictionary PreprocessAsync(List measur // Apply Z-score normalization if enabled float[] normalizedFeatures = NormalizeFeaturesAsync(features, config.Preprocessing); - // Create dictionary with one key per feature - var result = new Dictionary + DateTime now = DateTime.UtcNow; + var result = new List { - ["GlobalActivityRatio"] = [normalizedFeatures[0]], - ["GlobalChangeDensity"] = [normalizedFeatures[1]], - ["InterAxisMeanCorrelation"] = [normalizedFeatures[2]], - ["InterAxisMaxCorrelation"] = [normalizedFeatures[3]], - ["InterAxisCorrelationVariance"] = [normalizedFeatures[4]], - ["AxisSynchronization"] = [normalizedFeatures[5]], - ["AxisLoadBalance"] = [normalizedFeatures[6]], - ["TemporalStability"] = [normalizedFeatures[7]], - ["GlobalSkewness"] = [normalizedFeatures[8]], - ["GlobalKurtosis"] = [normalizedFeatures[9]], - ["GlobalTrendSlope"] = [normalizedFeatures[10]], - ["CoefficientOfVariation"] = [normalizedFeatures[11]], - ["NormalizedIqrMedian"] = [normalizedFeatures[12]], - ["NormalizedIqrMean"] = [normalizedFeatures[13]], + new MeasurementData(now, "GlobalActivityRatio", normalizedFeatures[0]), + new MeasurementData(now, "GlobalChangeDensity", normalizedFeatures[1]), + new MeasurementData(now, "InterAxisMeanCorrelation", normalizedFeatures[2]), + new MeasurementData(now, "InterAxisMaxCorrelation", normalizedFeatures[3]), + new MeasurementData(now, "InterAxisCorrelationVariance", normalizedFeatures[4]), + new MeasurementData(now, "AxisSynchronization", normalizedFeatures[5]), + new MeasurementData(now, "AxisLoadBalance", normalizedFeatures[6]), + new MeasurementData(now, "TemporalStability", normalizedFeatures[7]), + new MeasurementData(now, "GlobalSkewness", normalizedFeatures[8]), + new MeasurementData(now, "GlobalKurtosis", normalizedFeatures[9]), + new MeasurementData(now, "GlobalTrendSlope", normalizedFeatures[10]), + new MeasurementData(now, "CoefficientOfVariation", normalizedFeatures[11]), + new MeasurementData(now, "NormalizedIqrMedian", normalizedFeatures[12]), + new MeasurementData(now, "NormalizedIqrMean", normalizedFeatures[13]), }; Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); @@ -58,7 +58,7 @@ public Dictionary PreprocessAsync(List measur #region Private methods - private float[] ExtractFeatures(List measurements, List sensors) + private float[] ExtractFeatures(IEnumerable measurements, List sensors) { if (measurements == null || sensors == null || sensors.Count == 0) { @@ -119,24 +119,24 @@ private float[] ExtractFeatures(List measurements, List x > globalStd * 1.5); float changeDensity = significantChanges / (float)allCurrents.Count; - // Extract axis currents for correlation analysis - List> axisCurrents = ExtractAxisCurrents(measurements, sensors); + // Extract actuator currents for correlation analysis + List> actuatorsCurrents = ExtractActuatorCurrents(measurements, sensors); - // Features 3-5: Inter-axis correlations - List correlations = CalculateInterAxisCorrelations(axisCurrents); + // Features 3-5: Inter-actuator correlations + List correlations = CalculateInterActuatorCorrelations(actuatorsCurrents); float meanCorrelation = correlations.Count > 0 ? correlations.Average() : 0f; float maxCorrelation = correlations.Count > 0 ? correlations.Max() : 0f; float correlationVariance = correlations.Count > 0 ? MathUtils.StandardDeviation(correlations) : 0f; - // Feature 6: Axis Synchronization - var axisMeans = axisCurrents.Select(MathUtils.Mean).ToList(); - float meanOfMeans = axisMeans.Average(); - float synchronization = meanOfMeans != 0 ? 1 - (MathUtils.StandardDeviation(axisMeans) / Math.Abs(meanOfMeans)) : 1f; + // Feature 6: Actuator Synchronization + var actuatorsMeans = actuatorsCurrents.Select(MathUtils.Mean).ToList(); + float meanOfMeans = actuatorsMeans.Average(); + float synchronization = meanOfMeans != 0 ? 1 - (MathUtils.StandardDeviation(actuatorsMeans) / Math.Abs(meanOfMeans)) : 1f; - // Feature 7: Axis Load Balance - var axisEnergies = axisCurrents.Select(axis => axis.Sum(x => x * x)).ToList(); - float meanEnergy = axisEnergies.Average(); - float loadBalance = meanEnergy != 0 ? 1 - (MathUtils.StandardDeviation(axisEnergies) / meanEnergy) : 1f; + // Feature 7: Actuator Load Balance + var actuatorsEnergies = actuatorsCurrents.Select(actuator => actuator.Sum(x => x * x)).ToList(); + float meanEnergy = actuatorsEnergies.Average(); + float loadBalance = meanEnergy != 0 ? 1 - (MathUtils.StandardDeviation(actuatorsEnergies) / meanEnergy) : 1f; // Feature 8: Temporal Stability float temporalStability = CalculateTemporalStability(allCurrents); @@ -153,38 +153,38 @@ private float[] ExtractFeatures(List measurements, List 1e-8f ? globalIqr / globalMedian : 0f; float normIqrMean = Math.Abs(globalMean) > 1e-8f ? globalIqr / globalMean : 0f; - return new float[] - { - activeRatio, // 1. GlobalActivityRatio - changeDensity, // 2. GlobalChangeDensity - meanCorrelation, // 3. InterAxisMeanCorrelation - maxCorrelation, // 4. InterAxisMaxCorrelation - correlationVariance, // 5. InterAxisCorrelationVariance - synchronization, // 6. AxisSynchronization - loadBalance, // 7. AxisLoadBalance - temporalStability, // 8. TemporalStability - globalSkewness, // 9. GlobalSkewness - globalKurtosis, // 10. GlobalKurtosis - trendSlope, // 11. GlobalTrendSlope - coeffVar, // 12. CoefficientOfVariation - normIqrMedian, // 13. NormalizedIqrMedian - normIqrMean, // 14. NormalizedIqrMean - }; + return + [ + activeRatio, + changeDensity, + meanCorrelation, + maxCorrelation, + correlationVariance, + synchronization, + loadBalance, + temporalStability, + globalSkewness, + globalKurtosis, + trendSlope, + coeffVar, + normIqrMedian, + normIqrMean, + ]; } - private List> ExtractAxisCurrents(List measurements, List sensors) + private List> ExtractActuatorCurrents(IEnumerable measurements, List sensors) { - var axisCurrents = new List>(); + var actuatorsCurrents = new List>(); // Group measurements by sensor var measurementsBySensor = measurements .GroupBy(m => m.SensorName) .ToDictionary(g => g.Key, g => g.ToList()); - // Extract currents for each sensor/axis + // Extract currents for each sensor/Actuator foreach (string sensor in sensors) { - var axisCurrent = new List(); + var actuatorCurrent = new List(); if (measurementsBySensor.TryGetValue(sensor, out List? sensorMeasurements)) { foreach (IMeasurementData? measurement in sensorMeasurements.OrderBy(m => m.TimeStamp)) @@ -192,34 +192,34 @@ private List> ExtractAxisCurrents(List measurement object value = measurement.GetRawValue(); if (value is float floatValue) { - axisCurrent.Add(floatValue); + actuatorCurrent.Add(floatValue); } else if (value is double doubleValue) { - axisCurrent.Add((float)doubleValue); + actuatorCurrent.Add((float)doubleValue); } else if (value is int intValue) { - axisCurrent.Add(intValue); + actuatorCurrent.Add(intValue); } } } - axisCurrents.Add(axisCurrent); + actuatorsCurrents.Add(actuatorCurrent); } - return axisCurrents; + return actuatorsCurrents; } - private List CalculateInterAxisCorrelations(List> axisCurrents) + private List CalculateInterActuatorCorrelations(List> actuatorsCurrents) { var correlations = new List(); - for (int axis1 = 0; axis1 < axisCurrents.Count; axis1++) + for (int actuator1 = 0; actuator1 < actuatorsCurrents.Count; actuator1++) { - for (int axis2 = axis1 + 1; axis2 < axisCurrents.Count; axis2++) + for (int actuator2 = actuator1 + 1; actuator2 < actuatorsCurrents.Count; actuator2++) { - float corr = MathUtils.Correlation(axisCurrents[axis1], axisCurrents[axis2]); + float corr = MathUtils.Correlation(actuatorsCurrents[actuator1], actuatorsCurrents[actuator2]); if (!float.IsNaN(corr) && !float.IsInfinity(corr)) { correlations.Add(corr); diff --git a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs index 43a3316..1f2bb74 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs @@ -14,5 +14,5 @@ public interface IPreprocessingStrategy /// List of raw measurements from the data window. /// Configuration for the machine prediction. /// Feature vector as dictionary mapping input names to values for a single sample. - public Dictionary PreprocessAsync(List measurements, MachinePredictionConfig config); + public IEnumerable PreprocessAsync(IEnumerable measurements, MachinePredictionConfig config); } diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index d649ec7..fda7733 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -99,33 +99,29 @@ public async Task ProcessAsync(MachinePredictionConfig config) } // Preprocess data using strategy - Dictionary preprocessedData = PreprocessDataAsync(measurements, config); + IEnumerable preprocessedData = PreprocessDataAsync(measurements, config); - if (preprocessedData == null || preprocessedData.Count == 0) + if (preprocessedData == null || !preprocessedData.Any()) { Log.Warning("Data preprocessing failed for machine {MachineName}", config.MachineName); return; } // Perform prediction - Dictionary predictions = await predictionEngine.PredictAsync(config.ModelPath, preprocessedData); + IEnumerable predictions = await predictionEngine.PredictAsync(config.ModelPath, preprocessedData); - if (predictions == null || predictions.Count == 0) + if (predictions == null || !predictions.Any()) { Log.Warning("No predictions returned for machine {MachineName}", config.MachineName); return; } - // Create prediction measurement - IMeasurementData predictionMeasurement = CreatePredictionMeasurementAsync(predictions, config); - // Write prediction to InfluxDB - await influxRepository.WriteMeasurementAsync(config.MachineName, predictionMeasurement); + await influxRepository.WriteMeasurementAsync(config.MachineName, predictions); Log.Information( - "Prediction completed for machine {MachineName}: {PredictionValue}", - config.MachineName, - predictionMeasurement.GetRawValue()); + "Prediction completed for machine {MachineName}", + config.MachineName); } catch (Exception ex) { @@ -150,61 +146,33 @@ private async Task> FetchDataWindowAsync(MachinePredictio sensors); } - private Dictionary PreprocessDataAsync(List measurements, MachinePredictionConfig config) + private IEnumerable PreprocessDataAsync(IEnumerable measurements, MachinePredictionConfig config) { try { if (string.IsNullOrEmpty(config.PreprocessingStrategy)) { Log.Error("No preprocessing strategy configured for machine {MachineName}", config.MachineName); - return new Dictionary(); + return Array.Empty(); } IPreprocessingStrategy strategy = strategyFactory.CreateStrategy(config.PreprocessingStrategy); - Dictionary preprocessedData = strategy.PreprocessAsync(measurements, config); + IEnumerable preprocessedData = strategy.PreprocessAsync(measurements, config); Log.Debug( "Preprocessed data for machine {MachineName} using strategy {Strategy}: {InputCount} inputs", config.MachineName, config.PreprocessingStrategy, - preprocessedData.Count); + preprocessedData.Count()); return preprocessedData; } catch (Exception ex) { Log.Error(ex, "Error preprocessing data for machine {MachineName}", config.MachineName); - return new Dictionary(); + return Array.Empty(); } } - private IMeasurementData CreatePredictionMeasurementAsync(Dictionary predictions, MachinePredictionConfig config) - { - // TODO Fix here - - // Log available outputs for debugging - Log.Debug( - "Available prediction outputs for machine {MachineName}: {OutputNames}", - config.MachineName, - string.Join(", ", predictions.Keys)); - - // For simplicity, we'll use the first prediction value - // In a real scenario, you might want to handle multiple outputs differently - // or configure which output to use in the config - var firstOutput = predictions.First(); - float predictionValue = firstOutput.Value.Length > 0 ? firstOutput.Value[0] : 0.0f; - - Log.Debug( - "Using prediction output '{OutputName}' with value {Value} for machine {MachineName}", - firstOutput.Key, - predictionValue, - config.MachineName); - - return new MeasurementData( - DateTime.UtcNow, - config.PredictionSensorName, - predictionValue); - } - #endregion } diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs index 4a90634..c65c278 100644 --- a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -86,7 +86,7 @@ private void ValidateConfigurationAsync() { var enabledMachines = configuration.Value.Machines.Where(m => m.Enabled).ToList(); - if (!enabledMachines.Any()) + if (enabledMachines.Count == 0) { Log.Warning("No enabled machines found in configuration"); return; @@ -126,14 +126,6 @@ private void ValidateConfigurationAsync() throw new InvalidOperationException($"No input sensors configured for machine {machineConfig.MachineName}"); } - if (string.IsNullOrEmpty(machineConfig.PredictionSensorName)) - { - Log.Error( - "Prediction sensor name is not configured for machine {MachineName}", - machineConfig.MachineName); - throw new InvalidOperationException($"Prediction sensor name is not configured for machine {machineConfig.MachineName}"); - } - if (string.IsNullOrEmpty(machineConfig.PreprocessingStrategy)) { Log.Error( From d2b9d75347061ecd04130fbff4f649f305138935 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 17 Jul 2025 11:30:34 +0200 Subject: [PATCH 36/70] fix: tests after refactoring IMeasurementData --- .../ActuatorCurrentFeatureExtractorTests.cs | 31 ++-- .../MachinePredictionProcessorTests.cs | 173 ++++++++++-------- .../Prediction/OnnxPredictionEngineTests.cs | 129 +++++-------- .../PredictionBackgroundServiceTests.cs | 26 --- .../ActuatorCurrentFeatureExtractor.cs | 2 +- 5 files changed, 153 insertions(+), 208 deletions(-) diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs index b42dec0..9c3eb24 100644 --- a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs +++ b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs @@ -31,7 +31,7 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenValidDataProvided() // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count); + Assert.Equal(14, result.Count()); } [Fact] @@ -46,8 +46,8 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptyMeasurementsPr // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count); - Assert.All(result, feature => Assert.Equal(0.0f, feature.Value[0])); + Assert.Equal(14, result.Count()); + Assert.All(result, feature => Assert.Equal(0.0f, (float)feature.GetRawValue())); } [Fact] @@ -63,8 +63,8 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptySensorsListPro // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count); - Assert.All(result, feature => Assert.Equal(0.0f, feature.Value[0])); + Assert.Equal(14, result.Count()); + Assert.All(result, feature => Assert.Equal(0.0f, (float)feature.GetRawValue())); } [Fact] @@ -79,11 +79,11 @@ public void PreprocessAsync_ShouldReturnValidFeatures_WhenValidDataProvided() // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count); + Assert.Equal(14, result.Count()); // Check that features are within reasonable bounds - Assert.All(result, feature => Assert.False(float.IsNaN(feature.Value[0]))); - Assert.All(result, feature => Assert.False(float.IsInfinity(feature.Value[0]))); + Assert.All(result, feature => Assert.False(float.IsNaN((float)feature.GetRawValue()))); + Assert.All(result, feature => Assert.False(float.IsInfinity((float)feature.GetRawValue()))); } [Fact] @@ -103,8 +103,8 @@ public void PreprocessAsync_ShouldReturnZeroFeatures_WhenNoValidValuesFound() // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count); - Assert.All(result, feature => Assert.Equal(0.0f, feature.Value[0])); + Assert.Equal(14, result.Count()); + Assert.All(result, feature => Assert.Equal(0.0f, (float)feature.GetRawValue())); } [Fact] @@ -122,8 +122,8 @@ public void PreprocessAsync_ShouldHandleSingleValue_WhenOnlyOneValidMeasurementP // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count); - Assert.All(result, feature => Assert.False(float.IsNaN(feature.Value[0]))); + Assert.Equal(14, result.Count()); + Assert.All(result, feature => Assert.False(float.IsNaN((float)feature.GetRawValue()))); } [Fact] @@ -152,9 +152,9 @@ public void PreprocessAsync_ShouldHandleLargeDataset_WhenManyMeasurementsProvide // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count); - Assert.All(result, feature => Assert.False(float.IsNaN(feature.Value[0]))); - Assert.All(result, feature => Assert.False(float.IsInfinity(feature.Value[0]))); + Assert.Equal(14, result.Count()); + Assert.All(result, feature => Assert.False(float.IsNaN((float)feature.GetRawValue()))); + Assert.All(result, feature => Assert.False(float.IsInfinity((float)feature.GetRawValue()))); } #endregion @@ -175,7 +175,6 @@ private static List CreateTestMeasurements() => [ MachineName = "test_machine", ModelPath = "test_model.onnx", InputSensors = ["sensor1", "sensor2"], - PredictionSensorName = "prediction_sensor", PreprocessingStrategy = "ActuatorMergingCurrent", WindowSizeSeconds = 1, CycleIntervalSeconds = 1, diff --git a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs index e0bcd6d..c937a27 100644 --- a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs +++ b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using DataAggregator.Collector.Shared.Models; using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services.DataStorage; @@ -56,7 +57,7 @@ public async Task ProcessAsync_ShouldReturnEarly_WhenCollectorInfoIsNull() // Assert _mockInfluxRepository.Verify(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); } [Fact] @@ -74,7 +75,7 @@ public async Task ProcessAsync_ShouldReturnEarly_WhenNoValidSensorsFound() // Assert _mockInfluxRepository.Verify(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); } [Fact] @@ -93,8 +94,8 @@ public async Task ProcessAsync_ShouldReturnEarly_WhenNoMeasurementsFound() await _processor.ProcessAsync(config); // Assert - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); - _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny()), Times.Never); + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny>()), Times.Never); } [Fact] @@ -112,14 +113,14 @@ public async Task ProcessAsync_ShouldReturnEarly_WhenPreprocessingFails() _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) .Returns(_mockPreprocessingStrategy.Object); _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) - .Returns(new Dictionary()); + .Returns(new List()); // Act await _processor.ProcessAsync(config); // Assert - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); - _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny()), Times.Never); + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); + _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny>()), Times.Never); } [Fact] @@ -129,24 +130,32 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe MachinePredictionConfig config = CreateValidMachineConfig(); CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); List measurements = CreateTestMeasurements(); - var preprocessedData = new Dictionary - { - ["GlobalActivityRatio"] = [1.0f], - ["GlobalChangeDensity"] = [2.0f], - ["InterAxisMeanCorrelation"] = [3.0f], - ["InterAxisMaxCorrelation"] = [4.0f], - ["InterAxisCorrelationVariance"] = [5.0f], - ["AxisSynchronization"] = [6.0f], - ["AxisLoadBalance"] = [7.0f], - ["TemporalStability"] = [8.0f], - ["GlobalSkewness"] = [9.0f], - ["GlobalKurtosis"] = [10.0f], - ["GlobalTrendSlope"] = [11.0f], - ["CoefficientOfVariation"] = [12.0f], - ["NormalizedIqrMedian"] = [13.0f], - ["NormalizedIqrMean"] = [14.0f] + var now = DateTime.UtcNow; + var preprocessedData = new List + { + new MeasurementData(now, "GlobalActivityRatio", 1.0f), + new MeasurementData(now, "GlobalChangeDensity", 2.0f), + new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), + new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), + new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), + new MeasurementData(now, "AxisSynchronization", 6.0f), + new MeasurementData(now, "AxisLoadBalance", 7.0f), + new MeasurementData(now, "TemporalStability", 8.0f), + new MeasurementData(now, "GlobalSkewness", 9.0f), + new MeasurementData(now, "GlobalKurtosis", 10.0f), + new MeasurementData(now, "GlobalTrendSlope", 11.0f), + new MeasurementData(now, "CoefficientOfVariation", 12.0f), + new MeasurementData(now, "NormalizedIqrMedian", 13.0f), + new MeasurementData(now, "NormalizedIqrMean", 14.0f), }; - float[] predictions = new float[] { 0.85f }; + + IEnumerable results = + new List + { + new MeasurementData(now, "Prediction", 0.85f) + }; + + float[] predictions = [0.85f]; _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo); @@ -157,7 +166,7 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) .Returns(preprocessedData); _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) - .ReturnsAsync(predictions); + .ReturnsAsync(results); // Act await _processor.ProcessAsync(config); @@ -175,7 +184,7 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe It.IsAny(), It.IsAny>()), Times.Once); _mockPredictionEngine.Verify(x => x.PredictAsync(config.ModelPath, preprocessedData), Times.Once); - _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(config.MachineName, It.IsAny()), Times.Once); + _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(config.MachineName, It.IsAny>()), Times.Once); } [Fact] @@ -186,24 +195,30 @@ public async Task ProcessAsync_ShouldReinitializeInfluxConnection_WhenEndpointCh CollectorInfoDto collectorInfo1 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint1"); CollectorInfoDto collectorInfo2 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint2"); List measurements = CreateTestMeasurements(); - var preprocessedData = new Dictionary - { - ["GlobalActivityRatio"] = [1.0f], - ["GlobalChangeDensity"] = [2.0f], - ["InterAxisMeanCorrelation"] = [3.0f], - ["InterAxisMaxCorrelation"] = [4.0f], - ["InterAxisCorrelationVariance"] = [5.0f], - ["AxisSynchronization"] = [6.0f], - ["AxisLoadBalance"] = [7.0f], - ["TemporalStability"] = [8.0f], - ["GlobalSkewness"] = [9.0f], - ["GlobalKurtosis"] = [10.0f], - ["GlobalTrendSlope"] = [11.0f], - ["CoefficientOfVariation"] = [12.0f], - ["NormalizedIqrMedian"] = [13.0f], - ["NormalizedIqrMean"] = [14.0f] + + var now = DateTime.UtcNow; + var preprocessedData = new List + { + new MeasurementData(now, "GlobalActivityRatio", 1.0f), + new MeasurementData(now, "GlobalChangeDensity", 2.0f), + new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), + new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), + new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), + new MeasurementData(now, "AxisSynchronization", 6.0f), + new MeasurementData(now, "AxisLoadBalance", 7.0f), + new MeasurementData(now, "TemporalStability", 8.0f), + new MeasurementData(now, "GlobalSkewness", 9.0f), + new MeasurementData(now, "GlobalKurtosis", 10.0f), + new MeasurementData(now, "GlobalTrendSlope", 11.0f), + new MeasurementData(now, "CoefficientOfVariation", 12.0f), + new MeasurementData(now, "NormalizedIqrMedian", 13.0f), + new MeasurementData(now, "NormalizedIqrMean", 14.0f), + }; + + var predictions = new List + { + new MeasurementData(now, "Prediction", 0.85f) }; - float[] predictions = new float[] { 0.85f }; _mockRegistrationClient.SetupSequence(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo1) @@ -241,24 +256,27 @@ public async Task ProcessAsync_ShouldNotReinitializeInfluxConnection_WhenEndpoin MachinePredictionConfig config = CreateValidMachineConfig(); CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); List measurements = CreateTestMeasurements(); - var preprocessedData = new Dictionary - { - ["GlobalActivityRatio"] = [1.0f], - ["GlobalChangeDensity"] = [2.0f], - ["InterAxisMeanCorrelation"] = [3.0f], - ["InterAxisMaxCorrelation"] = [4.0f], - ["InterAxisCorrelationVariance"] = [5.0f], - ["AxisSynchronization"] = [6.0f], - ["AxisLoadBalance"] = [7.0f], - ["TemporalStability"] = [8.0f], - ["GlobalSkewness"] = [9.0f], - ["GlobalKurtosis"] = [10.0f], - ["GlobalTrendSlope"] = [11.0f], - ["CoefficientOfVariation"] = [12.0f], - ["NormalizedIqrMedian"] = [13.0f], - ["NormalizedIqrMean"] = [14.0f] + + var now = DateTime.UtcNow; + var preprocessedData = new List + { + new MeasurementData(now, "GlobalActivityRatio", 1.0f), + new MeasurementData(now, "GlobalChangeDensity", 2.0f), + new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), + new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), + new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), + new MeasurementData(now, "AxisSynchronization", 6.0f), + new MeasurementData(now, "AxisLoadBalance", 7.0f), + new MeasurementData(now, "TemporalStability", 8.0f), + new MeasurementData(now, "GlobalSkewness", 9.0f), + new MeasurementData(now, "GlobalKurtosis", 10.0f), + new MeasurementData(now, "GlobalTrendSlope", 11.0f), + new MeasurementData(now, "CoefficientOfVariation", 12.0f), + new MeasurementData(now, "NormalizedIqrMedian", 13.0f), + new MeasurementData(now, "NormalizedIqrMean", 14.0f), }; - float[] predictions = new float[] { 0.85f }; + + var predictions = new List { new MeasurementData(now, "Prediction", 0.85f) }; _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo); @@ -302,22 +320,24 @@ public async Task ProcessAsync_ShouldThrowException_WhenPredictionEngineThrows() MachinePredictionConfig config = CreateValidMachineConfig(); CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); List measurements = CreateTestMeasurements(); - var preprocessedData = new Dictionary - { - ["GlobalActivityRatio"] = [1.0f], - ["GlobalChangeDensity"] = [2.0f], - ["InterAxisMeanCorrelation"] = [3.0f], - ["InterAxisMaxCorrelation"] = [4.0f], - ["InterAxisCorrelationVariance"] = [5.0f], - ["AxisSynchronization"] = [6.0f], - ["AxisLoadBalance"] = [7.0f], - ["TemporalStability"] = [8.0f], - ["GlobalSkewness"] = [9.0f], - ["GlobalKurtosis"] = [10.0f], - ["GlobalTrendSlope"] = [11.0f], - ["CoefficientOfVariation"] = [12.0f], - ["NormalizedIqrMedian"] = [13.0f], - ["NormalizedIqrMean"] = [14.0f] + + var now = DateTime.UtcNow; + var preprocessedData = new List + { + new MeasurementData(now, "GlobalActivityRatio", 1.0f), + new MeasurementData(now, "GlobalChangeDensity", 2.0f), + new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), + new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), + new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), + new MeasurementData(now, "AxisSynchronization", 6.0f), + new MeasurementData(now, "AxisLoadBalance", 7.0f), + new MeasurementData(now, "TemporalStability", 8.0f), + new MeasurementData(now, "GlobalSkewness", 9.0f), + new MeasurementData(now, "GlobalKurtosis", 10.0f), + new MeasurementData(now, "GlobalTrendSlope", 11.0f), + new MeasurementData(now, "CoefficientOfVariation", 12.0f), + new MeasurementData(now, "NormalizedIqrMedian", 13.0f), + new MeasurementData(now, "NormalizedIqrMean", 14.0f), }; _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) @@ -344,7 +364,6 @@ public async Task ProcessAsync_ShouldThrowException_WhenPredictionEngineThrows() MachineName = "test_machine", ModelPath = "test_model.onnx", InputSensors = ["sensor1", "sensor2"], - PredictionSensorName = "prediction_sensor", PreprocessingStrategy = "test_strategy", WindowSizeSeconds = 300, CycleIntervalSeconds = 60, diff --git a/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs b/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs index cdde700..891bbe9 100644 --- a/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs +++ b/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs @@ -1,4 +1,5 @@ using System.Reflection.Metadata; +using DataAggregator.Collector.Shared.Models; using DataAggregator.Processor.Services.Prediction; using Microsoft.ML.OnnxRuntime; @@ -24,11 +25,12 @@ public async Task PredictAsync_ShouldThrowFileNotFoundException_WhenModelPathDoe { // Arrange string nonExistentModelPath = "non_existent_model.onnx"; - var inputData = new Dictionary - { - ["GlobalActivityRatio"] = [1.0f], - ["GlobalChangeDensity"] = [2.0f], - ["InterAxisMeanCorrelation"] = [3.0f] + + var inputData = new List + { + new MeasurementData(DateTime.UtcNow, "GlobalActivityRatio", 1.0f), + new MeasurementData(DateTime.UtcNow, "GlobalChangeDensity", 2.0f), + new MeasurementData(DateTime.UtcNow, "InterAxisMeanCorrelation", 3.0f), }; // Act & Assert @@ -44,33 +46,22 @@ public async Task PredictAsync_ShouldCacheModel_WhenSameModelPathIsUsedMultipleT string copyPath = Path.Combine("resources", "opencn_model_copy.onnx"); File.Copy(modelPath, copyPath); - var inputData = new Dictionary - { - ["GlobalActivityRatio"] = [1.0f], - ["GlobalChangeDensity"] = [2.0f], - ["InterAxisMeanCorrelation"] = [3.0f], - ["InterAxisMaxCorrelation"] = [4.0f], - ["InterAxisCorrelationVariance"] = [5.0f], - ["AxisSynchronization"] = [6.0f], - ["AxisLoadBalance"] = [7.0f], - ["TemporalStability"] = [8.0f], - ["GlobalSkewness"] = [9.0f], - ["GlobalKurtosis"] = [10.0f], - ["GlobalTrendSlope"] = [11.0f], - ["CoefficientOfVariation"] = [12.0f], - ["NormalizedIqrMedian"] = [13.0f], - ["NormalizedIqrMean"] = [14.0f] + var inputData = new List + { + new MeasurementData(DateTime.UtcNow, "GlobalActivityRatio", 1.0f), + new MeasurementData(DateTime.UtcNow, "GlobalChangeDensity", 2.0f), + new MeasurementData(DateTime.UtcNow, "InterAxisMeanCorrelation", 3.0f), }; // Act - Dictionary result1 = await _predictionEngine.PredictAsync(copyPath, inputData); + IEnumerable result1 = await _predictionEngine.PredictAsync(copyPath, inputData); File.Delete(copyPath); // Simulate model file deletion - Dictionary result2 = await _predictionEngine.PredictAsync(copyPath, inputData); + IEnumerable result2 = await _predictionEngine.PredictAsync(copyPath, inputData); // Assert Assert.NotNull(result1); Assert.NotNull(result2); - Assert.Equal(result1.Count, result2.Count); + Assert.Equal(result1.Count(), result2.Count()); } [Fact] @@ -78,32 +69,34 @@ public async Task PredictAsync_ShouldReturnCorrectOutputShape_WhenValidInputProv { // Arrange string modelPath = _testModelPath; - var inputData = new Dictionary - { - ["GlobalActivityRatio"] = [1.0f], - ["GlobalChangeDensity"] = [2.0f], - ["InterAxisMeanCorrelation"] = [3.0f], - ["InterAxisMaxCorrelation"] = [4.0f], - ["InterAxisCorrelationVariance"] = [5.0f], - ["AxisSynchronization"] = [6.0f], - ["AxisLoadBalance"] = [7.0f], - ["TemporalStability"] = [8.0f], - ["GlobalSkewness"] = [9.0f], - ["GlobalKurtosis"] = [10.0f], - ["GlobalTrendSlope"] = [11.0f], - ["CoefficientOfVariation"] = [12.0f], - ["NormalizedIqrMedian"] = [13.0f], - ["NormalizedIqrMean"] = [14.0f] + var now = DateTime.UtcNow; + var inputData = new List + { + new MeasurementData(now, "GlobalActivityRatio", 1.0f), + new MeasurementData(now, "GlobalChangeDensity", 2.0f), + new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), + new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), + new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), + new MeasurementData(now, "AxisSynchronization", 6.0f), + new MeasurementData(now, "AxisLoadBalance", 7.0f), + new MeasurementData(now, "TemporalStability", 8.0f), + new MeasurementData(now, "GlobalSkewness", 9.0f), + new MeasurementData(now, "GlobalKurtosis", 10.0f), + new MeasurementData(now, "GlobalTrendSlope", 11.0f), + new MeasurementData(now, "CoefficientOfVariation", 12.0f), + new MeasurementData(now, "NormalizedIqrMedian", 13.0f), + new MeasurementData(now, "NormalizedIqrMean", 14.0f), }; try { // Act - Dictionary result = await _predictionEngine.PredictAsync(modelPath, inputData); + IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); // Assert Assert.NotNull(result); - Assert.True(result.Count > 0); + Assert.True(result.Count() > 0); + // TODO ADD RESULT } finally { @@ -120,12 +113,12 @@ public async Task PredictAsync_ShouldHandleEmptyInputArray_WhenProvided() { // Arrange string modelPath = _testModelPath; - var inputData = new Dictionary { ["GlobalActivityRatio"] = new float[0] }; + var inputData = new List(); try { // Act - Dictionary result = await _predictionEngine.PredictAsync(modelPath, inputData); + IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); // Assert Assert.NotNull(result); @@ -142,49 +135,6 @@ public async Task PredictAsync_ShouldHandleEmptyInputArray_WhenProvided() #endregion - #region GetModelMetadataAsync tests - - [Fact] - public async Task GetModelMetadataAsync_ShouldReturnMetadata_WhenValidModelProvided() - { - // Arrange - string modelPath = _testModelPath; - - try - { - // Act - OnnxModelMetadata metadata = await _predictionEngine.GetModelMetadataAsync(modelPath); - - // Assert - Assert.NotNull(metadata); - Assert.NotNull(metadata.InputNames); - Assert.NotNull(metadata.OutputNames); - Assert.NotNull(metadata.InputShapes); - Assert.NotNull(metadata.OutputShapes); - } - finally - { - // Cleanup - if (File.Exists(modelPath)) - { - File.Delete(modelPath); - } - } - } - - [Fact] - public async Task GetModelMetadataAsync_ShouldThrowFileNotFoundException_WhenModelPathDoesNotExist() - { - // Arrange - string nonExistentModelPath = "non_existent_model.onnx"; - - // Act & Assert - FileNotFoundException exception = await Assert.ThrowsAsync( - () => _predictionEngine.GetModelMetadataAsync(nonExistentModelPath)); - } - - #endregion - #region Dispose tests [Fact] @@ -205,7 +155,10 @@ public void Dispose_ShouldClearModelCache_WhenCalled() { // Arrange string modelPath = _testModelPath; - var inputData = new Dictionary { ["GlobalActivityRatio"] = [1.0f] }; + var inputData = new List + { + new MeasurementData(DateTime.UtcNow, "GlobalActivityRatio", 1.0f), + }; try { diff --git a/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs index 030f1f9..b949567 100644 --- a/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs +++ b/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs @@ -173,30 +173,6 @@ public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenNoInputS await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); } - [Fact] - public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPredictionSensorNameIsEmpty() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - config.Machines[0].PredictionSensorName = string.Empty; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act & Assert - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - - [Fact] - public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPredictionSensorNameIsNull() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - config.Machines[0].PredictionSensorName = null!; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act & Assert - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - [Fact] public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPreprocessingStrategyIsEmpty() { @@ -297,7 +273,6 @@ private static PredictionServiceConfiguration CreateValidConfiguration() MachineName = "test_machine_1", ModelPath = tempModelPath, InputSensors = ["sensor1", "sensor2"], - PredictionSensorName = "prediction_sensor", PreprocessingStrategy = "ActuatorMergingCurrent", WindowSizeSeconds = 300, CycleIntervalSeconds = 60, @@ -312,7 +287,6 @@ private static PredictionServiceConfiguration CreateValidConfiguration() MachineName = "test_machine_2", ModelPath = tempModelPath, InputSensors = ["sensor3", "sensor4"], - PredictionSensorName = "prediction_sensor_2", PreprocessingStrategy = "ActuatorMergingCurrent", WindowSizeSeconds = 600, CycleIntervalSeconds = 120, diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index 42415a5..3c2d367 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -94,7 +94,7 @@ private float[] ExtractFeatures(IEnumerable measurements, List } // Filter out invalid values - allCurrents = allCurrents.Where(x => !float.IsNaN(x) && !float.IsInfinity(x)).ToList(); + allCurrents = [.. allCurrents.Where(x => !float.IsNaN(x) && !float.IsInfinity(x))]; if (allCurrents.Count == 0) { From bbdc3355033584a96a6ef18db52355f6cf68a2a4 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 11:57:18 +0200 Subject: [PATCH 37/70] feat: include model --- .../resources/opencn_model.onnx | Bin 14040 -> 14010 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/DataAggregator.Processor.Tests/resources/opencn_model.onnx b/DataAggregator.Processor.Tests/resources/opencn_model.onnx index 042359734ff9cfafc52e5521b2bb1c86f9f5936c..06c1a332941031f00f5fc7c478dce50f28b438f1 100644 GIT binary patch delta 192 zcmcbSyDN8s8vDN-t}aF{;mLw>!W$i?ax$Koe2vpmM~{mqJGHVnzPO|)GcUc^i#;W? zI58F7rF21tFoYGVxb-s|@$HusN P@^Y03(rh$UT`ULyvA9Ir delta 213 zcmdm$dn0#(8q=KIjT&n?8Lv&g#%XD0!NrrET3H-lTvC*omtO3}o?4QcnOBnP#Z^$0 zpHiA!l9`|9C6EZD%TnWu6Z4AWLE@XOxrBuo=T7#Q4Y#o3;wwwcDNQBWj4QJH*ci7? Po~#l Date: Fri, 18 Jul 2025 11:57:32 +0200 Subject: [PATCH 38/70] feat: clean tests --- .../ProcessorTestHelper.cs | 99 +++++++++++++++ .../ActuatorCurrentFeatureExtractorTests.cs | 8 +- .../MachinePredictionProcessorTests.cs | 83 ++---------- .../Prediction/OnnxPredictionEngineTests.cs | 120 ++++++------------ 4 files changed, 153 insertions(+), 157 deletions(-) create mode 100644 DataAggregator.Processor.Tests/ProcessorTestHelper.cs diff --git a/DataAggregator.Processor.Tests/ProcessorTestHelper.cs b/DataAggregator.Processor.Tests/ProcessorTestHelper.cs new file mode 100644 index 0000000..a2d5689 --- /dev/null +++ b/DataAggregator.Processor.Tests/ProcessorTestHelper.cs @@ -0,0 +1,99 @@ +using DataAggregator.Collector.Shared.Models; + +namespace DataAggregator.Processor.Tests; + +public static class ProcessorTestHelper +{ + public static IEnumerable GetValidTestData() + { + DateTime now = DateTime.Now; + + return new List + { + new MeasurementData(now, "GlobalActivityRatio", 1.0f), + new MeasurementData(now, "GlobalChangeDensity", 2.0f), + new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), + new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), + new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), + new MeasurementData(now, "AxisSynchronization", 6.0f), + new MeasurementData(now, "AxisLoadBalance", 7.0f), + new MeasurementData(now, "TemporalStability", 8.0f), + new MeasurementData(now, "GlobalSkewness", 9.0f), + new MeasurementData(now, "GlobalKurtosis", 10.0f), + new MeasurementData(now, "GlobalTrendSlope", 11.0f), + new MeasurementData(now, "CoefficientOfVariation", 12.0f), + new MeasurementData(now, "NormalizedIqrMedian", 13.0f), + new MeasurementData(now, "NormalizedIqrMean", 14.0f), + new MeasurementData(now, "Label", string.Empty), + }; + } + + public static IEnumerable GetValidShutdownStateData() + { + DateTime now = DateTime.Now; + return new List + { + new MeasurementData(now, "GlobalActivityRatio", -0.235608f), + new MeasurementData(now, "GlobalChangeDensity", -3.017057f), + new MeasurementData(now, "InterAxisMeanCorrelation", 0.018719f), + new MeasurementData(now, "InterAxisMaxCorrelation", -1.639018f), + new MeasurementData(now, "InterAxisCorrelationVariance", -1.741219f), + new MeasurementData(now, "AxisSynchronization", 0.177111f), + new MeasurementData(now, "AxisLoadBalance", 3.195790f), + new MeasurementData(now, "TemporalStability", 0.542284f), + new MeasurementData(now, "GlobalSkewness", 0.357844f), + new MeasurementData(now, "GlobalKurtosis", 1.441117f), + new MeasurementData(now, "GlobalTrendSlope", 0.005194f), + new MeasurementData(now, "CoefficientOfVariation",0.073491f), + new MeasurementData(now, "NormalizedIqrMedian", -0.005889f), + new MeasurementData(now, "NormalizedIqrMean", 0.052909f), + new MeasurementData(now, "Label", string.Empty), + }; + } + + public static IEnumerable GetValidProductionStateData() + { + DateTime now = DateTime.Now; + return new List + { + new MeasurementData(now, "GlobalActivityRatio", -0.235608f), + new MeasurementData(now, "GlobalChangeDensity", -0.063313f), + new MeasurementData(now, "InterAxisMeanCorrelation", 0.353871f), + new MeasurementData(now, "InterAxisMaxCorrelation", 0.167771f), + new MeasurementData(now, "InterAxisCorrelationVariance", 0.079077f), + new MeasurementData(now, "AxisSynchronization", 0.142284f), + new MeasurementData(now, "AxisLoadBalance", 0.145757f), + new MeasurementData(now, "TemporalStability", -0.189833f), + new MeasurementData(now, "GlobalSkewness", -0.014682f), + new MeasurementData(now, "GlobalKurtosis", -1.229796f), + new MeasurementData(now, "GlobalTrendSlope", -2.419304f), + new MeasurementData(now, "CoefficientOfVariation", 0.037766f), + new MeasurementData(now, "NormalizedIqrMedian", -0.051617f), + new MeasurementData(now, "NormalizedIqrMean", 0.008711f), + new MeasurementData(now, "Label", string.Empty), + }; + } + + public static IEnumerable GetValidIdleStateData() + { + DateTime now = DateTime.Now; + return new List + { + new MeasurementData(now, "GlobalActivityRatio", -0.235608f), + new MeasurementData(now, "GlobalChangeDensity", 0.471617f), + new MeasurementData(now, "InterAxisMeanCorrelation", 0.087918f), + new MeasurementData(now, "InterAxisMaxCorrelation", -0.901309f), + new MeasurementData(now, "InterAxisCorrelationVariance", -1.025950f), + new MeasurementData(now, "AxisSynchronization", -0.045073f), + new MeasurementData(now, "AxisLoadBalance", -1.059821f), + new MeasurementData(now, "TemporalStability", 0.526964f), + new MeasurementData(now, "GlobalSkewness", 1.200339f), + new MeasurementData(now, "GlobalKurtosis", 1.352620f), + new MeasurementData(now, "GlobalTrendSlope", -0.011385f), + new MeasurementData(now, "CoefficientOfVariation", -0.135674f), + new MeasurementData(now, "NormalizedIqrMedian", -0.005889f), + new MeasurementData(now, "NormalizedIqrMean", -0.004563f), + new MeasurementData(now, "Label", string.Empty), + }; + } +} diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs index 9c3eb24..5671bbc 100644 --- a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs +++ b/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs @@ -31,7 +31,7 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenValidDataProvided() // Assert Assert.NotNull(result); - Assert.Equal(14, result.Count()); + Assert.Equal(15, result.Count()); } [Fact] @@ -43,6 +43,7 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptyMeasurementsPr // Act var result = _featureExtractor.PreprocessAsync(measurements, config); + result = result.Where(f => f.SensorName != "Label"); // Assert Assert.NotNull(result); @@ -60,6 +61,7 @@ public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptySensorsListPro // Act var result = _featureExtractor.PreprocessAsync(measurements, config); + result = result.Where(f => f.SensorName != "Label"); // Assert Assert.NotNull(result); @@ -76,6 +78,7 @@ public void PreprocessAsync_ShouldReturnValidFeatures_WhenValidDataProvided() // Act var result = _featureExtractor.PreprocessAsync(measurements, config); + result = result.Where(f => f.SensorName != "Label"); // Assert Assert.NotNull(result); @@ -100,6 +103,7 @@ public void PreprocessAsync_ShouldReturnZeroFeatures_WhenNoValidValuesFound() // Act var result = _featureExtractor.PreprocessAsync(measurements, config); + result = result.Where(f => f.SensorName != "Label"); // Assert Assert.NotNull(result); @@ -119,6 +123,7 @@ public void PreprocessAsync_ShouldHandleSingleValue_WhenOnlyOneValidMeasurementP // Act var result = _featureExtractor.PreprocessAsync(measurements, config); + result = result.Where(f => f.SensorName != "Label"); // Assert Assert.NotNull(result); @@ -149,6 +154,7 @@ public void PreprocessAsync_ShouldHandleLargeDataset_WhenManyMeasurementsProvide // Act var result = _featureExtractor.PreprocessAsync(measurements, config); + result = result.Where(f => f.SensorName != "Label"); // Assert Assert.NotNull(result); diff --git a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs index c937a27..845f080 100644 --- a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs +++ b/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using DataAggregator.Collector.Shared.Models; using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services.DataStorage; @@ -130,29 +129,12 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe MachinePredictionConfig config = CreateValidMachineConfig(); CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); List measurements = CreateTestMeasurements(); - var now = DateTime.UtcNow; - var preprocessedData = new List - { - new MeasurementData(now, "GlobalActivityRatio", 1.0f), - new MeasurementData(now, "GlobalChangeDensity", 2.0f), - new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), - new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), - new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), - new MeasurementData(now, "AxisSynchronization", 6.0f), - new MeasurementData(now, "AxisLoadBalance", 7.0f), - new MeasurementData(now, "TemporalStability", 8.0f), - new MeasurementData(now, "GlobalSkewness", 9.0f), - new MeasurementData(now, "GlobalKurtosis", 10.0f), - new MeasurementData(now, "GlobalTrendSlope", 11.0f), - new MeasurementData(now, "CoefficientOfVariation", 12.0f), - new MeasurementData(now, "NormalizedIqrMedian", 13.0f), - new MeasurementData(now, "NormalizedIqrMean", 14.0f), - }; + var preprocessedData = ProcessorTestHelper.GetValidTestData(); IEnumerable results = new List { - new MeasurementData(now, "Prediction", 0.85f) + new MeasurementData(DateTime.Now, "Prediction", 0.85f) }; float[] predictions = [0.85f]; @@ -176,13 +158,15 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe x => x.InitializeAsync( collectorInfo.AssignedInfluxEndpoint.Endpoint, collectorInfo.AssignedInfluxEndpoint.Token, - "Dataggregator"), Times.Once); + "Dataggregator"), + Times.Once); _mockInfluxRepository.Verify( x => x.QueryMeasurementsAsync( config.MachineName, It.IsAny(), It.IsAny(), - It.IsAny>()), Times.Once); + It.IsAny>()), + Times.Once); _mockPredictionEngine.Verify(x => x.PredictAsync(config.ModelPath, preprocessedData), Times.Once); _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(config.MachineName, It.IsAny>()), Times.Once); } @@ -197,23 +181,7 @@ public async Task ProcessAsync_ShouldReinitializeInfluxConnection_WhenEndpointCh List measurements = CreateTestMeasurements(); var now = DateTime.UtcNow; - var preprocessedData = new List - { - new MeasurementData(now, "GlobalActivityRatio", 1.0f), - new MeasurementData(now, "GlobalChangeDensity", 2.0f), - new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), - new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), - new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), - new MeasurementData(now, "AxisSynchronization", 6.0f), - new MeasurementData(now, "AxisLoadBalance", 7.0f), - new MeasurementData(now, "TemporalStability", 8.0f), - new MeasurementData(now, "GlobalSkewness", 9.0f), - new MeasurementData(now, "GlobalKurtosis", 10.0f), - new MeasurementData(now, "GlobalTrendSlope", 11.0f), - new MeasurementData(now, "CoefficientOfVariation", 12.0f), - new MeasurementData(now, "NormalizedIqrMedian", 13.0f), - new MeasurementData(now, "NormalizedIqrMean", 14.0f), - }; + var preprocessedData = ProcessorTestHelper.GetValidTestData(); var predictions = new List { @@ -258,23 +226,7 @@ public async Task ProcessAsync_ShouldNotReinitializeInfluxConnection_WhenEndpoin List measurements = CreateTestMeasurements(); var now = DateTime.UtcNow; - var preprocessedData = new List - { - new MeasurementData(now, "GlobalActivityRatio", 1.0f), - new MeasurementData(now, "GlobalChangeDensity", 2.0f), - new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), - new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), - new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), - new MeasurementData(now, "AxisSynchronization", 6.0f), - new MeasurementData(now, "AxisLoadBalance", 7.0f), - new MeasurementData(now, "TemporalStability", 8.0f), - new MeasurementData(now, "GlobalSkewness", 9.0f), - new MeasurementData(now, "GlobalKurtosis", 10.0f), - new MeasurementData(now, "GlobalTrendSlope", 11.0f), - new MeasurementData(now, "CoefficientOfVariation", 12.0f), - new MeasurementData(now, "NormalizedIqrMedian", 13.0f), - new MeasurementData(now, "NormalizedIqrMean", 14.0f), - }; + var preprocessedData = ProcessorTestHelper.GetValidTestData(); var predictions = new List { new MeasurementData(now, "Prediction", 0.85f) }; @@ -321,24 +273,7 @@ public async Task ProcessAsync_ShouldThrowException_WhenPredictionEngineThrows() CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); List measurements = CreateTestMeasurements(); - var now = DateTime.UtcNow; - var preprocessedData = new List - { - new MeasurementData(now, "GlobalActivityRatio", 1.0f), - new MeasurementData(now, "GlobalChangeDensity", 2.0f), - new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), - new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), - new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), - new MeasurementData(now, "AxisSynchronization", 6.0f), - new MeasurementData(now, "AxisLoadBalance", 7.0f), - new MeasurementData(now, "TemporalStability", 8.0f), - new MeasurementData(now, "GlobalSkewness", 9.0f), - new MeasurementData(now, "GlobalKurtosis", 10.0f), - new MeasurementData(now, "GlobalTrendSlope", 11.0f), - new MeasurementData(now, "CoefficientOfVariation", 12.0f), - new MeasurementData(now, "NormalizedIqrMedian", 13.0f), - new MeasurementData(now, "NormalizedIqrMean", 14.0f), - }; + var preprocessedData = ProcessorTestHelper.GetValidTestData(); _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo); diff --git a/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs b/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs index 891bbe9..5c1edd5 100644 --- a/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs +++ b/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs @@ -44,14 +44,9 @@ public async Task PredictAsync_ShouldCacheModel_WhenSameModelPathIsUsedMultipleT // Arrange string modelPath = _testModelPath; string copyPath = Path.Combine("resources", "opencn_model_copy.onnx"); - File.Copy(modelPath, copyPath); + File.Copy(modelPath, copyPath, true); - var inputData = new List - { - new MeasurementData(DateTime.UtcNow, "GlobalActivityRatio", 1.0f), - new MeasurementData(DateTime.UtcNow, "GlobalChangeDensity", 2.0f), - new MeasurementData(DateTime.UtcNow, "InterAxisMeanCorrelation", 3.0f), - }; + var inputData = ProcessorTestHelper.GetValidTestData(); // Act IEnumerable result1 = await _predictionEngine.PredictAsync(copyPath, inputData); @@ -70,42 +65,38 @@ public async Task PredictAsync_ShouldReturnCorrectOutputShape_WhenValidInputProv // Arrange string modelPath = _testModelPath; var now = DateTime.UtcNow; - var inputData = new List - { - new MeasurementData(now, "GlobalActivityRatio", 1.0f), - new MeasurementData(now, "GlobalChangeDensity", 2.0f), - new MeasurementData(now, "InterAxisMeanCorrelation", 3.0f), - new MeasurementData(now, "InterAxisMaxCorrelation", 4.0f), - new MeasurementData(now, "InterAxisCorrelationVariance", 5.0f), - new MeasurementData(now, "AxisSynchronization", 6.0f), - new MeasurementData(now, "AxisLoadBalance", 7.0f), - new MeasurementData(now, "TemporalStability", 8.0f), - new MeasurementData(now, "GlobalSkewness", 9.0f), - new MeasurementData(now, "GlobalKurtosis", 10.0f), - new MeasurementData(now, "GlobalTrendSlope", 11.0f), - new MeasurementData(now, "CoefficientOfVariation", 12.0f), - new MeasurementData(now, "NormalizedIqrMedian", 13.0f), - new MeasurementData(now, "NormalizedIqrMean", 14.0f), - }; + var inputData = ProcessorTestHelper.GetValidTestData(); - try - { - // Act - IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); + // Act + IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); + + // Assert + Assert.NotNull(result); + Assert.True(result.Count() > 0); + } + + [Fact] + public async Task PredictAsync_ShouldReturnGoodResult_DifferentStateDataProvided() + { + // Arrange + string modelPath = _testModelPath; + var inputDataShutdown = ProcessorTestHelper.GetValidShutdownStateData(); + var inputDataProduction = ProcessorTestHelper.GetValidProductionStateData(); + var inputDataIdle = ProcessorTestHelper.GetValidIdleStateData(); + + // Act + IEnumerable resultShutdown = await _predictionEngine.PredictAsync(modelPath, inputDataShutdown); + IEnumerable resultProduction = await _predictionEngine.PredictAsync(modelPath, inputDataProduction); + IEnumerable resultIdle = await _predictionEngine.PredictAsync(modelPath, inputDataIdle); + + // Assert + Assert.NotNull(resultShutdown); + Assert.NotNull(resultProduction); + Assert.NotNull(resultIdle); + Assert.Equal("disable", resultShutdown.First(x => x.SensorName == "PredictedLabel.output_0").GetRawValue().ToString()); + Assert.Equal("production", resultProduction.First(x => x.SensorName == "PredictedLabel.output_0").GetRawValue().ToString()); + Assert.Equal("enable", resultIdle.First(static x => x.SensorName == "PredictedLabel.output_0").GetRawValue().ToString()); - // Assert - Assert.NotNull(result); - Assert.True(result.Count() > 0); - // TODO ADD RESULT - } - finally - { - // Cleanup - if (File.Exists(modelPath)) - { - File.Delete(modelPath); - } - } } [Fact] @@ -115,50 +106,23 @@ public async Task PredictAsync_ShouldHandleEmptyInputArray_WhenProvided() string modelPath = _testModelPath; var inputData = new List(); - try - { - // Act - IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); + // Act + IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); - // Assert - Assert.NotNull(result); - } - finally - { - // Cleanup - if (File.Exists(modelPath)) - { - File.Delete(modelPath); - } - } + // Assert + Assert.NotNull(result); } #endregion #region Dispose tests - [Fact] - public void Dispose_ShouldNotThrowException_WhenCalledMultipleTimes() - { - // Act & Assert - Exception exception = Record.Exception(() => - { - _predictionEngine.Dispose(); - _predictionEngine.Dispose(); - }); - - Assert.Null(exception); - } - [Fact] public void Dispose_ShouldClearModelCache_WhenCalled() { // Arrange string modelPath = _testModelPath; - var inputData = new List - { - new MeasurementData(DateTime.UtcNow, "GlobalActivityRatio", 1.0f), - }; + IEnumerable inputData = ProcessorTestHelper.GetValidTestData(); try { @@ -173,11 +137,7 @@ public void Dispose_ShouldClearModelCache_WhenCalled() } finally { - // Cleanup - if (File.Exists(modelPath)) - { - File.Delete(modelPath); - } + // do nothing } } @@ -187,10 +147,6 @@ public void Dispose_ShouldClearModelCache_WhenCalled() private static string _testModelPath = Path.Combine("resources", "opencn_model.onnx"); - // Simple test model bytes (minimal ONNX model) - private static readonly byte[] TestModelBytes = Convert.FromBase64String( - "T05OWA=="); // This is just a placeholder - in real tests you'd use a proper minimal ONNX model - #endregion /// From aac63dc78e8eb462b6bc0f420083ef6b63d39eb4 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 11:57:44 +0200 Subject: [PATCH 39/70] fix: prediction engine behavior --- .../Prediction/OnnxPredictionEngine.cs | 83 +++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs index 6abc7bf..57e3dc1 100644 --- a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs @@ -25,34 +25,51 @@ public async Task> PredictAsync( { try { + // Load the model or get the existing session InferenceSession session = LoadOrGetModel(modelPath); + + if (!inputData.Any()) + { + Log.Information($"No inputs data provided to model for model {modelPath}"); + return Array.Empty(); + } + ValidateInputData(session, inputData); + // Prepare the inputs for the inference session var inputs = new List(); - foreach (IMeasurementData data in inputData) { inputs.Add(CreateNamedOnnxValue(data)); } + // Run the model to get results using IDisposableReadOnlyCollection results = session.Run(inputs); + var inputsNamesAsOutput = session.InputMetadata.Keys + .Select(name => name + ".output") + .ToList(); + + // Filter out results containing input names or the terms "unused" or "__Features__" + var filteredResults = results.Where(x => + !inputsNamesAsOutput.Any(input => input.Equals(x.Name, StringComparison.InvariantCultureIgnoreCase)) && // Remove input names + !x.Name.Contains("unused", StringComparison.InvariantCultureIgnoreCase) && // Remove "unused" term + !x.Name.Contains("__Features__", StringComparison.InvariantCultureIgnoreCase)) // Remove "__Features__" term + .ToList(); + + // Prepare the list to store output measurements var outputMeasurements = new List(); DateTime now = DateTime.UtcNow; - foreach (DisposableNamedOnnxValue? result in results) + // Process each filtered result to convert it into measurement data + foreach (DisposableNamedOnnxValue? result in filteredResults) { - string name = result.Name; - Tensor tensor = result.AsTensor(); - float[] values = [.. tensor]; - - for (int i = 0; i < values.Length; i++) - { - outputMeasurements.Add( - new MeasurementData(now, $"{name}_{i}", values[i])); - } + // Process the result and add it to the output list + IEnumerable measurementData = ProcessResultToMeasurementData(result, now); + outputMeasurements.AddRange(measurementData); } + // Log the completion of the prediction Log.Debug("Prediction completed for model {ModelPath} with {OutputCount} outputs", modelPath, outputMeasurements.Count); return await Task.FromResult(outputMeasurements); } @@ -63,6 +80,39 @@ public async Task> PredictAsync( } } + /// + /// Processes the ONNX result into a list of IMeasurementData objects. + /// + /// The result from the ONNX model run. + /// The timestamp for the measurement data. + /// A list of IMeasurementData containing the processed result. + private List ProcessResultToMeasurementData(DisposableNamedOnnxValue result, DateTime timestamp) + { + var measurementDataList = new List(); + + // Check if the result is of type float (tensor of floats) + if (result.AsTensor() != null) + { + float[] values = [.. result.AsTensor()]; + + // Add each float value as MeasurementData + measurementDataList.AddRange(values.Select((value, index) => + new MeasurementData(timestamp, $"{result.Name}_{index}", value))); + } + + // Check if the result is of type string (tensor of strings) + if (result.AsTensor() != null) + { + string[] values = [.. result.AsTensor()]; + + // Add each string value as MeasurementData + measurementDataList.AddRange(values.Select((value, index) => + new MeasurementData(timestamp, $"{result.Name}_{index}", value))); + } + + return measurementDataList; + } + /// /// Disposes the cached models. /// @@ -112,13 +162,13 @@ private void ValidateInputData(InferenceSession session, IEnumerable modelInputs = session.InputMetadata; - // Check if all required model inputs are provided - foreach (KeyValuePair modelInput in modelInputs) + // Check if all inputData sensors exist in the model inputs + foreach (IMeasurementData input in inputData) { - if (!inputData.Any(x => modelInput.Key == x.SensorName)) + if (!modelInputs.ContainsKey(input.SensorName)) { throw new ArgumentException( - $"Model requires input '{modelInput.Key}' but it was not provided. "); + $"Model does not have an input for '{input.SensorName}' which was provided in input data."); } } @@ -141,6 +191,9 @@ private NamedOnnxValue CreateNamedOnnxValue(IMeasurementData data) double[] dArray => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(dArray, [1, dArray.Length])), double d => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new[] { d }, [1, 1])), + string[] sArray => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(sArray, [1, sArray.Length])), + string s => NamedOnnxValue.CreateFromTensor(name, new DenseTensor(new[] { s }, [1, 1])), + _ => throw new NotSupportedException($"Unsupported data type {data.ValueType} for sensor {name}"), }; } From 3bfa1fc249a45d727a2d1d574d7ff8f0f8908820 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 11:57:55 +0200 Subject: [PATCH 40/70] feat: add label to feature extractor to match model input scheme --- .../ActuatorCurrentFeatureExtractor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index 3c2d367..e0b156c 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -48,6 +48,7 @@ public IEnumerable PreprocessAsync(IEnumerable(now, "CoefficientOfVariation", normalizedFeatures[11]), new MeasurementData(now, "NormalizedIqrMedian", normalizedFeatures[12]), new MeasurementData(now, "NormalizedIqrMean", normalizedFeatures[13]), + new MeasurementData(now, "Label", string.Empty), }; Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); From fa34716e4b55ad0ac3847e4ea8e93bbf1648dde1 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 12:59:55 +0200 Subject: [PATCH 41/70] feat: create interface for MachinePredictionProcessor --- .../Prediction/IMachinePredictionProcessor.cs | 16 ++++++++++++++++ .../Prediction/MachinePredictionProcessor.cs | 8 ++------ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 src/DataAggregator.Processor/Services/Prediction/IMachinePredictionProcessor.cs diff --git a/src/DataAggregator.Processor/Services/Prediction/IMachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/IMachinePredictionProcessor.cs new file mode 100644 index 0000000..5f98e59 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Prediction/IMachinePredictionProcessor.cs @@ -0,0 +1,16 @@ +using DataAggregator.Processor.Configuration; + +namespace DataAggregator.Processor.Services.Prediction; + +/// +/// Interface for processing machine predictions. +/// +public interface IMachinePredictionProcessor +{ + /// + /// Processes prediction for a specific machine. + /// + /// The machine prediction configuration. + /// A task representing the asynchronous operation. + public Task ProcessAsync(MachinePredictionConfig config); +} diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index fda7733..58d8032 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -22,7 +22,7 @@ public class MachinePredictionProcessor( IDataRepository influxRepository, IRegistrationServiceClient registrationClient, IOnnxPredictionEngine predictionEngine, - IPreprocessingStrategyFactory strategyFactory) + IPreprocessingStrategyFactory strategyFactory) : IMachinePredictionProcessor { #region Private fields @@ -33,11 +33,7 @@ public class MachinePredictionProcessor( #region Public methods - /// - /// Processes prediction for a specific machine. - /// - /// The machine prediction configuration. - /// A task representing the asynchronous operation. + /// public async Task ProcessAsync(MachinePredictionConfig config) { try From 1fadd71956d49710c368c0f1c632419ee3f46c13 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 13:00:12 +0200 Subject: [PATCH 42/70] feat: use new interface for PredictionBackgroundService --- .../PredictionBackgroundServiceTests.cs | 111 +----------------- .../Services/PredictionBackgroundService.cs | 2 +- 2 files changed, 6 insertions(+), 107 deletions(-) diff --git a/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs index b949567..abb0eac 100644 --- a/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs +++ b/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs @@ -12,7 +12,7 @@ namespace DataAggregator.Processor.Tests.Services; public class PredictionBackgroundServiceTests : IDisposable { private readonly Mock> _mockConfiguration; - private readonly Mock _mockPredictionProcessor; + private readonly Mock _mockPredictionProcessor; private readonly PredictionBackgroundService _backgroundService; private readonly CancellationTokenSource _cancellationTokenSource; @@ -22,7 +22,7 @@ public class PredictionBackgroundServiceTests : IDisposable public PredictionBackgroundServiceTests() { _mockConfiguration = new Mock>(); - _mockPredictionProcessor = new Mock(); + _mockPredictionProcessor = new Mock(); _cancellationTokenSource = new CancellationTokenSource(); _backgroundService = new PredictionBackgroundService( @@ -62,25 +62,9 @@ public async Task ExecuteAsync_ShouldScheduleEnabledMachines_WhenConfigurationCo // Assert // The service should have started without throwing exceptions - Assert.True(true); - } - - [Fact] - public async Task ExecuteAsync_ShouldNotScheduleDisabledMachines_WhenConfigurationContainsDisabledMachines() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - config.Machines[0].Enabled = false; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act - Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start - await _backgroundService.StopAsync(_cancellationTokenSource.Token); - - // Assert - // The service should have started without throwing exceptions - Assert.True(true); + _mockPredictionProcessor.Verify( + x => x.ProcessAsync(It.IsAny()), + Times.Exactly(config.Machines.Count(m => m.Enabled))); } [Fact] @@ -103,28 +87,6 @@ public async Task ExecuteAsync_ShouldHandleEmptyMachineList_WhenConfigurationCon Assert.True(true); } - [Fact] - public async Task ExecuteAsync_ShouldHandleAllDisabledMachines_WhenConfigurationContainsOnlyDisabledMachines() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - foreach (MachinePredictionConfig machine in config.Machines) - { - machine.Enabled = false; - } - - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act - Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start - await _backgroundService.StopAsync(_cancellationTokenSource.Token); - - // Assert - // The service should have started without throwing exceptions - Assert.True(true); - } - [Fact] public async Task ExecuteAsync_ShouldThrowFileNotFoundException_WhenModelFileDoesNotExist() { @@ -149,18 +111,6 @@ public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenMachineN await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); } - [Fact] - public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenMachineNameIsNull() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - config.Machines[0].MachineName = null!; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act & Assert - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - [Fact] public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenNoInputSensorsConfigured() { @@ -185,18 +135,6 @@ public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPreproce await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); } - [Fact] - public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPreprocessingStrategyIsNull() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - config.Machines[0].PreprocessingStrategy = null!; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act & Assert - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - [Fact] public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() { @@ -217,45 +155,6 @@ public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() #endregion - #region StopAsync tests - - [Fact] - public async Task StopAsync_ShouldDisposeAllTimers_WhenCalled() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act - await _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start - await _backgroundService.StopAsync(_cancellationTokenSource.Token); - - // Assert - // The service should have stopped without throwing exceptions - Assert.True(true); - } - - [Fact] - public async Task StopAsync_ShouldNotThrowException_WhenCalledMultipleTimes() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act - await _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start - await _backgroundService.StopAsync(_cancellationTokenSource.Token); - await _backgroundService.StopAsync(_cancellationTokenSource.Token); - - // Assert - // The service should have stopped without throwing exceptions - Assert.True(true); - } - - #endregion - #region Helper methods private static PredictionServiceConfiguration CreateValidConfiguration() diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs index c65c278..6a553b2 100644 --- a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -15,7 +15,7 @@ namespace DataAggregator.Processor.Services; /// The machine prediction processor. public class PredictionBackgroundService( IOptions configuration, - MachinePredictionProcessor predictionProcessor) : BackgroundService + IMachinePredictionProcessor predictionProcessor) : BackgroundService { #region Private fields From 94013c3779f2286a5ed8df06bd22732e6f6fb8dc Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 13:05:12 +0200 Subject: [PATCH 43/70] feat: enhance error message on registration service unavailable --- .../Registration/RegistrationService.cs | 7 ++++++- src/DataAggregator.Processor/Program.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs b/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs index 5e7ce56..0bf5233 100644 --- a/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs +++ b/src/DataAggregator.Collector.Shared/Registration/RegistrationService.cs @@ -48,9 +48,14 @@ public async Task RegisterCollectorAsync(CollectorCo Log.Information("Collector registration result: {IsSuccess}", result.IsSuccess); return result; } + catch (HttpRequestException ex) + { + Log.Error(ex, "Error registering collector, registration service unavailable"); + return new DeviceRegistrationResponse(false, string.Empty, string.Empty); + } catch (Exception ex) { - Log.Error(ex, "Error registering collector"); + Log.Error(ex, "Unexpected error during collector registration"); return new DeviceRegistrationResponse(false, string.Empty, string.Empty); } } diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index 170367d..5407ddd 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -42,7 +42,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); // Register background service builder.Services.AddHostedService(); From abcfd371e1383af7516d2ac181ecc46621fcbbf8 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 13:27:52 +0200 Subject: [PATCH 44/70] feat: set appsettings to match micro5 case --- src/DataAggregator.Collector/appsettings.json | 78 ++++++++++++++----- src/DataAggregator.Processor/appsettings.json | 44 ++++++----- 2 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/DataAggregator.Collector/appsettings.json b/src/DataAggregator.Collector/appsettings.json index eeccf9d..efab06c 100644 --- a/src/DataAggregator.Collector/appsettings.json +++ b/src/DataAggregator.Collector/appsettings.json @@ -17,11 +17,10 @@ "AllowedHosts": "*", "CollectorType": "OpenCN", "Collector": { - "DeviceId": "Machine-001", - "DeviceName": "OpenCN-Machine-001", + "DeviceName": "Micro5_1", "Location": "Manufacturing Floor - Section A", "HealthCheckEndpoint": "http://localhost:5000/health", - "SamplingRate": 500, + "SamplingRate": 100, "CapnProto": { "ServerAddress": "192.168.53.15", "Port": 7002, @@ -35,29 +34,70 @@ }, "Sensors": [ { - "Name": "V1", - "Type": "V1", - "Unit": "U1", + "Name": "current-amp-x", + "Type": "Current", + "Unit": "A", "Metadata": { - "MinValue": "-10", - "MaxValue": "100" + "MinValue": "-20", + "MaxValue": "20" }, "DataType": "float", - "PinName": "streamer.0.pin.0" + "PinName": "streamer.0.pin.0" }, { - "Name": "V2", - "Type": "V2", - "Unit": "Bar", - "PinName": "streamer.0.pin.1", - "DataType": "float" + "Name": "current-amp-y", + "Type": "Current", + "Unit": "A", + "Metadata": { + "MinValue": "-20", + "MaxValue": "20" + }, + "DataType": "float", + "PinName": "streamer.0.pin.1" }, { - "Name": "V3", - "Type": "V3", - "Unit": "Hz", - "PinName": "streamer.0.pin.2", - "DataType": "float" + "Name": "current-amp-z", + "Type": "Current", + "Unit": "A", + "Metadata": { + "MinValue": "-20", + "MaxValue": "20" + }, + "DataType": "float", + "PinName": "streamer.0.pin.2" + }, + { + "Name": "current-amp-b", + "Type": "Current", + "Unit": "A", + "Metadata": { + "MinValue": "-20", + "MaxValue": "20" + }, + "DataType": "float", + "PinName": "streamer.0.pin.3" + }, + { + "Name": "current-amp-c", + "Type": "Current", + "Unit": "A", + "Metadata": { + "MinValue": "-20", + "MaxValue": "20" + }, + "DataType": "float", + "PinName": "streamer.0.pin.4" + }, + { + "Name": "current-amp-s", + "Type": "Current", + "Unit": "A", + "Metadata": { + "MinValue": "-20", + "MaxValue": "20" + }, + "DataType": "float", + "PinName": "streamer.0.pin.5" } ] } diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index 01d5bea..f41c133 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -5,7 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, - + "Serilog": { "MinimumLevel": { "Default": "Information", @@ -26,35 +26,37 @@ "GlobalCycleIntervalSeconds": 1, "Machines": [ { - "MachineName": "OpenCN-Machine-001", + "MachineName": "Micro5_1", "Enabled": true, - "ModelPath": "/app/models/opencn-machine-001.onnx", + "ModelPath": "/resources/opencn_model.onnx", "PreprocessingStrategy": "ActuatorCurrentFeatureExtractor", "InputSensors": [ - "V1", - "V2", - "V3" + "current-amp-x", + "current-amp-y", + "current-amp-z", + "current-amp-b", + "current-amp-c", + "current-amp-s" ], - "PredictionSensorName": "PredictedCurrentState", "WindowSizeSeconds": 1, "CycleIntervalSeconds": 1, "Preprocessing": { "EnableZScoreNormalization": true, "NormalizationParameters": { - "GlobalActivityRatio": [0.15, 0.12], - "GlobalChangeDensity": [0.08, 0.06], - "InterAxisMeanCorrelation": [0.25, 0.18], - "InterAxisMaxCorrelation": [0.45, 0.22], - "InterAxisCorrelationVariance": [0.12, 0.08], - "AxisSynchronization": [0.75, 0.15], - "AxisLoadBalance": [0.82, 0.12], - "TemporalStability": [0.68, 0.18], - "GlobalSkewness": [0.05, 0.85], - "GlobalKurtosis": [2.1, 1.2], - "GlobalTrendSlope": [0.002, 0.008], - "CoefficientOfVariation": [0.45, 0.25], - "NormalizedIqrMedian": [0.35, 0.22], - "NormalizedIqrMean": [0.38, 0.24] + "GlobalActivityRatio": [ 0.004157, 0.017644 ], + "GlobalChangeDensity": [ 0.432407, 0.143321 ], + "InterAxisMeanCorrelation": [ -0.001363, 0.072809 ], + "InterAxisMaxCorrelation": [ 0.439072, 0.267887 ], + "InterAxisCorrelationVariance": [ 0.225831, 0.129697 ], + "AxisSynchronization": [ -38.172882, 221.177505 ], + "AxisLoadBalance": [ -0.045936, 0.327285 ], + "TemporalStability": [ 0.974642, 0.046761 ], + "GlobalSkewness": [ -0.129752, 0.362594 ], + "GlobalKurtosis": [ -0.570948, 0.396184 ], + "GlobalTrendSlope": [ -0.000001, 0.000179 ], + "CoefficientOfVariation": [ -17.267527, 234.962006 ], + "NormalizedIqrMedian": [ 0.633865, 107.640205 ], + "NormalizedIqrMean": [ -14.154306, 267.521179 ] } } } From 5f000f652c384a385346b64dbcdc4ed6f24cfa0d Mon Sep 17 00:00:00 2001 From: CoJaques Date: Fri, 18 Jul 2025 15:43:02 +0200 Subject: [PATCH 45/70] feat: add datas for test --- .../data_test_prod_enable_disable.txt | 5517 +++++++++++++++++ 1 file changed, 5517 insertions(+) create mode 100644 tests/Test_data/data_test_prod_enable_disable.txt diff --git a/tests/Test_data/data_test_prod_enable_disable.txt b/tests/Test_data/data_test_prod_enable_disable.txt new file mode 100644 index 0000000..56d7b27 --- /dev/null +++ b/tests/Test_data/data_test_prod_enable_disable.txt @@ -0,0 +1,5517 @@ +-0.114043 -0.367743 1.064101 -1.13329 -0.362408 0.928562 +-0.107564 -0.37385 1.06562 -1.135637 -0.361226 0.926672 +-0.099499 -0.357121 1.070166 -1.143614 -0.359908 0.921025 +-0.101629 -0.358265 1.068709 -1.147903 -0.35847 0.922318 +-0.105272 -0.359155 1.069692 -1.162446 -0.36763 0.927136 +-0.111129 -0.359621 1.065671 -1.161633 -0.355906 0.926529 +-0.11177 -0.358262 1.065608 -1.149412 -0.362034 0.925613 +-0.108176 -0.361639 1.065562 -1.149494 -0.36747 0.929252 +-0.110184 -0.353449 1.069435 -1.154847 -0.361264 0.931631 +-0.112168 -0.354176 1.063073 -1.153881 -0.367079 0.931428 +-0.109536 -0.345769 1.06679 -1.15405 -0.367692 0.930251 +-0.119862 -0.360696 1.0647 -1.155139 -0.360868 0.928331 +-0.126749 -0.343815 1.066916 -1.14166 -0.352633 0.928676 +-0.126927 -0.357417 1.069686 -1.123999 -0.36294 0.931053 +-0.123174 -0.359457 1.062918 -1.120956 -0.361204 0.929306 +-0.11461 -0.355384 1.06546 -1.132661 -0.35376 0.934578 +-0.115225 -0.339648 1.065711 -1.124699 -0.361857 0.932659 +-0.11109 -0.358544 1.070594 -1.136363 -0.358269 0.929018 +-0.112469 -0.341583 1.062479 -1.134042 -0.356637 0.930595 +-0.107263 -0.344959 1.068864 -1.13414 -0.354803 0.937473 +-0.102348 -0.358661 1.07064 -1.136895 -0.363412 0.932572 +-0.099113 -0.338984 1.066157 -1.145291 -0.362132 0.92492 +-0.100075 -0.361025 1.069943 -1.151673 -0.364888 0.928847 +-0.101933 -0.346202 1.066808 -1.161592 -0.353404 0.924148 +-0.104893 -0.352553 1.068144 -1.160993 -0.357836 0.930657 +-0.112023 -0.349128 1.064272 -1.156689 -0.359275 0.930311 +-0.110789 -0.353696 1.06823 -1.154259 -0.356456 0.927186 +-0.114092 -0.34749 1.072901 -1.154678 -0.362114 0.931227 +-0.112932 -0.350816 1.060508 -1.148435 -0.366309 0.930968 +-0.111867 -0.360182 1.06064 -1.15676 -0.355568 0.929793 +-0.123126 -0.36005 1.070177 -1.147177 -0.353344 0.935152 +-0.125023 -0.342014 1.069497 -1.138224 -0.365461 0.930967 +-0.126088 -0.359007 1.07068 -1.131991 -0.364654 0.931426 +-0.12807 -0.345753 1.07233 -1.132103 -0.358231 0.933145 +-0.117659 -0.361326 1.073255 -1.119377 -0.367589 0.931312 +-0.11313 -0.35424 1.06787 -1.130314 -0.370189 0.933146 +-0.114441 -0.348832 1.065077 -1.145991 -0.358231 0.928961 +-0.105942 -0.361293 1.067699 -1.147203 -0.361855 0.930022 +-0.111488 -0.358131 1.074586 -1.141349 -0.367074 0.927414 +-0.108166 -0.34648 1.065266 -1.14142 -0.358606 0.933175 +-0.101085 -0.351662 1.063855 -1.143572 -0.360772 0.935467 +-0.099036 -0.359124 1.067955 -1.152695 -0.366781 0.937903 +-0.100026 -0.357716 1.066088 -1.159736 -0.360953 0.935782 +-0.104971 -0.360397 1.06482 -1.163995 -0.367828 0.931626 +-0.107564 -0.342574 1.067573 -1.161371 -0.36211 0.933317 +-0.119939 -0.355134 1.070611 -1.156662 -0.359197 0.934663 +-0.123268 -0.35181 1.06438 -1.149202 -0.364931 0.932255 +-0.115782 -0.357302 1.066431 -1.153058 -0.364793 0.932056 +-0.114072 -0.358809 1.065477 -1.151995 -0.364161 0.935409 +-0.115345 -0.370441 1.063347 -1.142023 -0.364161 0.931683 +-0.120639 -0.336869 1.064255 -1.137497 -0.366781 0.932342 +-0.122251 -0.35747 1.062182 -1.133301 -0.357915 0.934347 +-0.123912 -0.356823 0.997179 -1.121698 -0.362114 0.934405 +-0.119686 -0.342198 0.863597 -1.109483 -0.358779 0.934091 +-0.111197 -0.340192 0.650456 -1.129828 -0.358112 0.935035 +-0.104319 -0.351214 0.377624 -1.13283 -0.354407 0.936383 +-0.100075 -0.355665 0.208884 -1.140668 -0.369773 0.930021 +-0.103659 -0.361407 0.199749 -1.15 -0.357483 0.927557 +-0.10181 -0.350501 0.210014 -1.156284 -0.365029 0.927873 +-0.102422 -0.343024 0.225576 -1.146758 -0.366032 0.929048 +-0.097734 -0.35186 0.240586 -1.148308 -0.364199 0.929192 +-0.095441 -0.343272 0.246096 -1.15606 -0.35975 0.93203 +-0.100499 -0.354441 0.260128 -1.164834 -0.36824 0.93564 +-0.100735 -0.346996 0.247016 -1.171373 -0.356201 0.93349 +-0.108837 -0.345454 0.239489 -1.155042 -0.367372 0.927786 +-0.114279 -0.344661 0.236301 -1.143572 -0.365896 0.933548 +-0.111168 -0.338986 0.259347 -1.130846 -0.357879 0.932658 +-0.115694 -0.344496 0.197698 -1.142023 -0.360141 0.929966 +-0.116307 -0.330514 0.173326 -1.141113 -0.365913 0.928103 +-0.118673 -0.329172 0.160829 -1.151171 -0.35553 0.930453 +-0.117297 -0.33955 0.161365 -1.139436 -0.365698 0.931628 +-0.12261 -0.323315 0.248001 -1.125523 -0.367177 0.930539 +-0.116789 -0.322966 0.218869 -1.12653 -0.355631 0.93369 +-0.105893 -0.316315 0.169799 -1.130943 -0.362074 0.930195 +-0.100402 -0.3184 0.114951 -1.13012 -0.366092 0.929277 +-0.09582 -0.304498 -0.081049 -1.144969 -0.361226 0.931485 +-0.100327 -0.311397 -0.298514 -1.152358 -0.352029 0.929193 +-0.101376 -0.308969 -0.052826 -1.143696 -0.365441 0.929365 +-0.102999 -0.282111 0.066155 -1.159036 -0.363076 0.928965 +-0.104941 -0.306967 -0.017464 -1.162348 -0.353762 0.932691 +-0.095159 -0.269899 0.03066 -1.175872 -0.35642 0.932001 +-0.096225 -0.282228 -0.104379 -1.177714 -0.355277 0.933231 +-0.101697 -0.288661 -0.112925 -1.172462 -0.36166 0.930682 +-0.106224 -0.284296 -0.065777 -1.166148 -0.360141 0.933663 +-0.110837 -0.281466 -0.150331 -1.176288 -0.347117 0.931943 +-0.114752 -0.277744 -0.108644 -1.156116 -0.3484 0.932258 +-0.112459 -0.293578 -0.093661 -1.157681 -0.357716 0.93157 +-0.109717 -0.285836 -0.043773 -1.147275 -0.355199 0.930796 +-0.106932 -0.266972 -0.002208 -1.14568 -0.351277 0.936355 +-0.109147 -0.293232 -0.037318 -1.170965 -0.356555 0.931312 +-0.11096 -0.271884 0.038458 -1.151018 -0.340341 0.937531 +-0.114412 -0.295612 -0.125066 -1.166009 -0.345424 0.932629 +-0.104621 -0.296405 -0.080547 -1.147009 -0.351554 0.932287 +-0.091313 -0.278026 0.005944 -1.145122 -0.347652 0.930338 +-0.092508 -0.28658 -0.118447 -1.181195 -0.337956 0.927932 +-0.092964 -0.28845 -0.034267 -1.167671 -0.34448 0.92925 +-0.093586 -0.301819 -0.061987 -1.177055 -0.349954 0.930253 +-0.097095 -0.298097 0.437893 -1.182045 -0.341486 0.929767 +-0.091983 -0.279877 0.31382 -1.174319 -0.3431 0.933807 +-0.099822 -0.308026 0.186114 -1.168929 -0.344381 0.929651 +-0.094936 -0.308223 0.147246 -1.178051 -0.340894 0.931428 +-0.092708 -0.307177 0.118567 -1.182434 -0.341111 0.929392 +-0.097724 -0.303457 0.124282 -1.186499 -0.335812 0.932946 +-0.11178 -0.326028 0.124096 -1.178972 -0.339338 0.934121 +-0.113168 -0.309316 0.131606 -1.163718 -0.337208 0.92776 +-0.109714 -0.301077 0.134409 -1.161719 -0.335258 0.929107 +-0.113761 -0.312986 0.152035 -1.154596 -0.335475 0.930914 +-0.110857 -0.312145 0.164653 -1.151171 -0.34736 0.929825 +-0.110895 -0.311847 0.183558 -1.165099 -0.337151 0.936931 +-0.109769 -0.296806 0.189531 -1.171373 -0.329584 0.935124 +-0.103902 -0.301456 0.199696 -1.163243 -0.338372 0.936125 +-0.105119 -0.309564 0.193082 -1.15792 -0.338942 0.931138 +-0.092915 -0.307778 0.188642 -1.170407 -0.336717 0.934033 +-0.095888 -0.315969 0.181745 -1.161693 -0.338372 0.933719 +-0.093029 -0.313434 0.175374 -1.171594 -0.337602 0.939162 +-0.090811 -0.311699 0.17873 -1.186821 -0.331517 0.944116 +-0.096801 -0.298099 0.173877 -1.186432 -0.336815 0.939764 +-0.100036 -0.320535 0.176772 -1.177403 -0.33979 0.943259 +-0.098802 -0.310505 0.180204 -1.175925 -0.333882 0.935548 +-0.099366 -0.304797 0.180663 -1.178189 -0.342448 0.933972 +-0.09367 -0.309168 0.172829 -1.178163 -0.338551 0.930421 +-0.102357 -0.305742 0.158933 -1.181319 -0.34097 0.930107 +-0.10939 -0.30073 0.14863 -1.180132 -0.339414 0.933461 +-0.114043 -0.31676 0.133205 -1.171568 -0.345033 0.93478 +-0.116251 -0.305591 0.122149 -1.156423 -0.338175 0.939766 +-0.107884 -0.319739 0.118111 -1.147664 -0.340656 0.939478 +-0.106858 -0.315108 0.121032 -1.15996 -0.340894 0.93965 +-0.104556 -0.314878 0.118553 -1.168495 -0.334944 0.931511 +-0.10329 -0.316813 0.127313 -1.169973 -0.340775 0.933747 +-0.103319 -0.293828 0.1321 -1.168802 -0.341779 0.936154 +-0.104242 -0.293995 0.131729 -1.166148 -0.336148 0.935409 +-0.101366 -0.303954 0.136724 -1.170044 -0.336262 0.933576 +-0.09728 -0.314263 0.143364 -1.16179 -0.336717 0.930509 +-0.098378 -0.313885 0.12675 -1.174416 -0.332248 0.930022 +-0.09683 -0.300315 0.114922 -1.180742 -0.332991 0.929507 +-0.101444 -0.315025 0.106312 -1.182842 -0.34058 0.928304 +-0.100055 -0.318596 0.108934 -1.177448 -0.333132 0.929221 +-0.107515 -0.311498 0.113277 -1.182479 -0.333879 0.931686 +-0.10464 -0.315586 0.135218 -1.179335 -0.333544 0.926213 +-0.106 -0.322669 0.156199 -1.18252 -0.340048 0.927849 +-0.102989 -0.310607 0.174936 -1.176164 -0.340618 0.928136 +-0.107506 -0.311615 0.188668 -1.184826 -0.336067 0.929543 +-0.111886 -0.31299 0.197908 -1.174667 -0.337466 0.926586 +-0.111391 -0.311696 0.203085 -1.167211 -0.341227 0.926442 +-0.107981 -0.301275 0.200181 -1.161214 -0.343455 0.931974 +-0.103138 -0.302729 0.200577 -1.166035 -0.333879 0.927387 +-0.098812 -0.302727 0.200894 -1.161858 -0.335614 0.934182 +-0.096228 -0.315569 0.189222 -1.17165 -0.342294 0.934666 +-0.100104 -0.30579 0.192803 -1.180843 -0.336148 0.937703 +-0.098491 -0.311749 0.188693 -1.182213 -0.335199 0.933118 +-0.09931 -0.306042 0.192883 -1.169752 -0.340638 0.933261 +-0.09817 -0.304152 0.199145 -1.165013 -0.336717 0.930911 +-0.098064 -0.301026 0.203628 -1.178848 -0.330354 0.932143 +-0.091002 -0.300526 0.195364 -1.171542 -0.336282 0.92816 +-0.093404 -0.30197 0.194922 -1.182311 -0.334938 0.928475 +-0.098112 -0.292042 0.181689 -1.187701 -0.334428 0.930109 +-0.103668 -0.319541 0.171772 -1.184811 -0.330354 0.93415 +-0.104922 -0.30086 0.15072 -1.171762 -0.338117 0.930711 +-0.095123 -0.307378 0.152553 -1.174805 -0.338195 0.930997 +-0.100123 -0.309862 0.144419 -1.178006 -0.335437 0.928648 +-0.094654 -0.305293 0.146772 -1.180578 -0.339869 0.931084 +-0.099055 -0.303875 0.140151 -1.180592 -0.337428 0.935698 +-0.1016 -0.302863 0.141979 -1.175157 -0.339397 0.935812 +-0.10429 -0.312427 0.145873 -1.161689 -0.341187 0.937301 +-0.106281 -0.296856 0.138197 -1.160073 -0.329937 0.941369 +-0.099142 -0.292437 0.137376 -1.154705 -0.341898 0.941081 +-0.094858 -0.29219 0.132226 -1.176651 -0.336896 0.934779 +-0.102121 -0.304815 0.125227 -1.178957 -0.331217 0.931368 +-0.104109 -0.303904 0.132509 -1.174012 -0.342885 0.930679 +-0.098715 -0.308652 0.124354 -1.168955 -0.337683 0.93114 +-0.106544 -0.310063 0.14038 -1.173649 -0.332622 0.928391 +-0.096811 -0.295464 0.146721 -1.1669 -0.336067 0.926213 +-0.099518 -0.298777 0.150691 -1.166776 -0.344458 0.927906 +-0.097319 -0.309911 0.160434 -1.182116 -0.33534 0.933692 +-0.102348 -0.302314 0.177377 -1.182449 -0.334336 0.931628 +-0.097112 -0.307528 0.185973 -1.175828 -0.340048 0.933719 +-0.107807 -0.309662 0.186899 -1.183011 -0.33713 0.930224 +-0.105563 -0.309266 0.1893 -1.170576 -0.340341 0.928735 +-0.107224 -0.308769 0.191573 -1.167529 -0.339218 0.931543 +-0.104471 -0.314778 0.181528 -1.166537 -0.341052 0.925756 +-0.105893 -0.30091 0.1755 -1.179069 -0.340817 0.925299 +-0.112129 -0.297304 0.16845 -1.178904 -0.333663 0.92802 +-0.102056 -0.310553 0.167086 -1.167405 -0.336048 0.926188 +-0.102953 -0.296061 0.165974 -1.165182 -0.337406 0.929025 +-0.107622 -0.308671 0.173992 -1.168423 -0.34231 0.928536 +-0.098083 -0.310305 0.176846 -1.179601 -0.340027 0.929654 +-0.100658 -0.304006 0.175614 -1.176262 -0.340518 0.933149 +-0.100599 -0.303756 0.168391 -1.176651 -0.343865 0.933291 +-0.105669 -0.293332 0.158594 -1.192425 -0.337014 0.929364 +-0.105216 -0.299289 0.139978 -1.173802 -0.335179 0.930139 +-0.098365 -0.305197 0.124177 -1.169052 -0.344164 0.930253 +-0.102979 -0.299637 0.108037 -1.173956 -0.33526 0.936213 +-0.099793 -0.30981 0.103784 -1.170841 -0.336658 0.932314 +-0.101036 -0.303061 0.101198 -1.169247 -0.342372 0.928761 +-0.112307 -0.317755 0.10849 -1.171373 -0.343927 0.934493 +-0.107185 -0.29899 0.100876 -1.179866 -0.340815 0.931857 +-0.110818 -0.301319 0.109156 -1.170714 -0.343515 0.927787 +-0.099828 -0.298346 0.112998 -1.165769 -0.340103 0.931227 +-0.104912 -0.310259 0.10685 -1.177422 -0.339457 0.930166 +-0.092896 -0.289111 0.10099 -1.178006 -0.338945 0.93243 +-0.102046 -0.291148 0.102394 -1.179432 -0.333172 0.935639 +-0.102726 -0.311383 0.112337 -1.181962 -0.338627 0.932631 +-0.105242 -0.327583 0.113365 -1.175045 -0.345169 0.935322 +-0.103989 -0.298644 0.121127 -1.172028 -0.33579 0.930709 +-0.102273 -0.311945 0.126167 -1.174237 -0.341643 0.936096 +-0.101583 -0.298097 0.138955 -1.174641 -0.338611 0.938617 +-0.101735 -0.315521 0.155478 -1.178803 -0.339791 0.9326 +-0.101376 -0.315719 0.16512 -1.185634 -0.343102 0.927698 +-0.106375 -0.298346 0.173465 -1.176179 -0.340601 0.927786 +-0.107564 -0.307778 0.18059 -1.173731 -0.341448 0.929048 +-0.103782 -0.298591 0.171273 -1.17256 -0.333365 0.932144 +-0.102276 -0.302714 0.172278 -1.161259 -0.342945 0.929364 +-0.102396 -0.296409 0.168163 -1.168719 -0.344165 0.929106 +-0.095888 -0.300563 0.158272 -1.184294 -0.332638 0.929336 +-0.101998 -0.305788 0.149874 -1.172223 -0.342609 0.92882 +-0.099055 -0.314181 0.143434 -1.174222 -0.34314 0.927789 +-0.10214 -0.305443 0.146196 -1.169262 -0.337978 0.924121 +-0.099424 -0.300134 0.150042 -1.178803 -0.34452 0.92845 +-0.096234 -0.30483 0.151379 -1.18495 -0.341761 0.927904 +-0.096331 -0.298226 0.149377 -1.186739 -0.346941 0.932691 +-0.100149 -0.308719 0.15335 -1.19153 -0.345169 0.927847 +-0.105385 -0.30222 0.145217 -1.188345 -0.339517 0.931171 +-0.107952 -0.299174 0.137392 -1.175745 -0.337721 0.932488 +-0.106855 -0.301521 0.118397 -1.165084 -0.343397 0.933461 +-0.101914 -0.317488 0.110675 -1.1771 -0.341898 0.937645 +-0.10497 -0.296111 0.102358 -1.188386 -0.339043 0.93349 +-0.09829 -0.320057 0.106187 -1.17576 -0.338354 0.934434 +-0.108185 -0.292935 0.111495 -1.175423 -0.344339 0.93174 +-0.109449 -0.298592 0.114984 -1.175438 -0.33707 0.934922 +-0.108836 -0.304684 0.125319 -1.155349 -0.341979 0.933746 +-0.10397 -0.307134 0.132196 -1.153267 -0.339593 0.927585 +-0.101667 -0.291446 0.132314 -1.171077 -0.341703 0.929849 +-0.103377 -0.308271 0.129863 -1.178874 -0.337998 0.9275 +-0.09549 -0.298295 0.119947 -1.170115 -0.337721 0.928102 +-0.102555 -0.283319 0.117055 -1.17865 -0.339121 0.933032 +-0.10429 -0.286265 0.120369 -1.176528 -0.342549 0.931828 +-0.098423 -0.315074 0.125907 -1.178006 -0.336853 0.931283 +-0.098151 -0.299041 0.132043 -1.177014 -0.340186 0.932172 +-0.09966 -0.291594 0.135203 -1.186432 -0.342786 0.937385 +-0.09819 -0.311749 0.147223 -1.19156 -0.339772 0.934492 +-0.101065 -0.300513 0.160742 -1.19378 -0.338529 0.937185 +-0.105883 -0.303411 0.171597 -1.184182 -0.34324 0.933433 +-0.109063 -0.308272 0.186817 -1.181165 -0.34448 0.929904 +-0.108827 -0.311252 0.186876 -1.175464 -0.33256 0.931369 +-0.111847 -0.303904 0.185214 -1.16806 -0.335593 0.933605 +-0.105262 -0.294588 0.183848 -1.165171 -0.34448 0.933919 +-0.110167 -0.309364 0.175689 -1.174835 -0.332975 0.935038 +-0.115921 -0.301273 0.170581 -1.172631 -0.34182 0.93071 +-0.108215 -0.290797 0.165366 -1.15807 -0.338291 0.929076 +-0.106139 -0.301623 0.164022 -1.160589 -0.341996 0.931828 +-0.102649 -0.297551 0.161258 -1.163898 -0.33796 0.92836 +-0.097131 -0.316168 0.159443 -1.165324 -0.339359 0.927128 +-0.09414 -0.3042 0.161501 -1.178526 -0.345663 0.929308 +-0.094696 -0.300394 0.156976 -1.182045 -0.333266 0.934408 +-0.094489 -0.311601 0.149154 -1.180158 -0.338804 0.928762 +-0.090762 -0.313485 0.134531 -1.179166 -0.340873 0.93157 +-0.099433 -0.309017 0.120301 -1.180158 -0.335891 0.928877 +-0.101415 -0.307084 0.109067 -1.189617 -0.344594 0.929334 +-0.099744 -0.303508 0.10561 -1.187128 -0.335378 0.93286 +-0.101046 -0.307002 0.094082 -1.188528 -0.335455 0.930252 +-0.107826 -0.316612 0.107685 -1.196392 -0.343949 0.926728 +-0.115034 -0.293678 0.107415 -1.185552 -0.333604 0.926414 +-0.114538 -0.292883 0.109474 -1.17101 -0.337092 0.925383 +-0.114072 -0.314328 0.096083 -1.160477 -0.340791 0.927618 +-0.108461 -0.298246 0.104298 -1.174544 -0.342234 0.92716 +-0.104695 -0.305642 0.098369 -1.171568 -0.341703 0.927561 +-0.107185 -0.304436 0.094885 -1.164403 -0.344203 0.930511 +-0.111478 -0.31259 0.08948 -1.164624 -0.345131 0.926271 +-0.114431 -0.300628 0.091004 -1.165784 -0.339808 0.927762 +-0.096367 -0.302514 0.090424 -1.154203 -0.336674 0.931657 +-0.098358 -0.300349 0.102218 -1.161914 -0.348163 0.934436 +-0.092698 -0.298296 0.107131 -1.178051 -0.330767 0.932858 +-0.09548 -0.295416 0.116579 -1.17868 -0.343081 0.926812 +-0.098122 -0.299936 0.124252 -1.182838 -0.33924 0.926269 +-0.1016 -0.288068 0.131369 -1.183048 -0.336164 0.926958 +-0.105057 -0.302944 0.134146 -1.182771 -0.342489 0.925755 +-0.09832 -0.295068 0.144177 -1.181513 -0.335869 0.926759 +-0.098103 -0.290472 0.13433 -1.189688 -0.337108 0.923979 +-0.105281 -0.308669 0.137041 -1.198137 -0.344146 0.923495 +-0.106463 -0.302829 0.131461 -1.194045 -0.34237 0.929025 +-0.114383 -0.313303 0.124368 -1.183205 -0.338669 0.928365 +-0.114694 -0.306781 0.12254 -1.170448 -0.335533 0.931314 +-0.117287 -0.3113 0.121758 -1.162614 -0.337781 0.935754 +-0.118997 -0.294125 0.125854 -1.166915 -0.335752 0.934063 +-0.115675 -0.303013 0.134598 -1.162446 -0.341209 0.928761 +-0.11244 -0.302862 0.130835 -1.167937 -0.34243 0.923201 +-0.108836 -0.304997 0.132986 -1.17147 -0.342234 0.92561 +-0.113402 -0.31496 0.12905 -1.15947 -0.337249 0.930196 +-0.107243 -0.306983 0.118193 -1.160799 -0.340379 0.929479 +-0.103659 -0.294523 0.098395 -1.174038 -0.347849 0.926641 +-0.097368 -0.308056 0.080687 -1.173469 -0.344323 0.926154 +-0.096328 -0.304548 0.069062 -1.178414 -0.343791 0.928389 +-0.096591 -0.300878 0.058262 -1.195191 -0.341507 0.926069 +-0.102717 -0.326143 0.06023 -1.189838 -0.338649 0.927874 +-0.10599 -0.318845 0.054147 -1.18727 -0.33981 0.92816 +-0.104773 -0.314361 0.062758 -1.180061 -0.334472 0.926871 +-0.102225 -0.309516 0.064186 -1.181708 -0.340381 0.931255 +-0.100812 -0.315421 0.063581 -1.192159 -0.338589 0.925064 +-0.107612 -0.31193 0.066055 -1.190706 -0.33522 0.931743 +-0.112809 -0.310146 0.061558 -1.185986 -0.33975 0.92581 +-0.121289 -0.300329 0.052049 -1.175842 -0.338687 0.92859 +-0.121862 -0.298298 0.04865 -1.168495 -0.336934 0.926899 +-0.116562 -0.293613 0.06685 -1.153155 -0.331239 0.930081 +-0.114528 -0.323712 0.071317 -1.161454 -0.345997 0.928704 +-0.106045 -0.323332 0.090612 -1.171762 -0.334569 0.931082 +-0.108817 -0.300461 0.09884 -1.168831 -0.334591 0.929019 +-0.107518 -0.314578 0.113865 -1.169277 -0.341621 0.929848 +-0.106835 -0.300282 0.130284 -1.166466 -0.338884 0.929478 +-0.101998 -0.320219 0.1382 -1.177811 -0.330924 0.930908 +-0.087 -0.316714 0.1458 -1.172197 -0.338117 0.933974 +-0.090982 -0.307083 0.147464 -1.187536 -0.340773 0.931137 +-0.095292 -0.319195 0.155427 -1.187828 -0.336913 0.93005 +-0.095791 -0.322869 0.146853 -1.198837 -0.341643 0.928587 +-0.102668 -0.310557 0.137774 -1.199855 -0.340601 0.927356 +-0.104659 -0.302483 0.13485 -1.189935 -0.34314 0.927099 +-0.10758 -0.309464 0.140293 -1.185665 -0.341898 0.933231 +-0.109817 -0.312594 0.135836 -1.187899 -0.332975 0.934347 +-0.116054 -0.315321 0.136949 -1.19199 -0.33841 0.933546 +-0.111628 -0.311431 0.141002 -1.184377 -0.339083 0.929075 +-0.112751 -0.305892 0.138303 -1.186473 -0.338155 0.93303 +-0.118657 -0.307311 0.130075 -1.176011 -0.336896 0.933573 +-0.117109 -0.311649 0.123445 -1.157052 -0.343732 0.930279 +-0.110242 -0.316744 0.109583 -1.157123 -0.344675 0.92787 +-0.106346 -0.319921 0.090721 -1.163606 -0.338117 0.928244 +-0.101619 -0.309416 0.077348 -1.164695 -0.337938 0.929305 +-0.104423 -0.291777 0.069834 -1.181042 -0.335101 0.932628 +-0.105262 -0.311682 0.066198 -1.174252 -0.339435 0.936898 +-0.101774 -0.314528 0.069728 -1.16489 -0.344126 0.939966 +-0.099009 -0.322024 0.08199 -1.17302 -0.337504 0.936753 +-0.100074 -0.324538 0.091059 -1.186155 -0.338133 0.935833 +-0.089866 -0.313038 0.103603 -1.190707 -0.341426 0.932481 +-0.092954 -0.327733 0.103914 -1.193836 -0.338942 0.934088 +-0.103387 -0.315437 0.109223 -1.199297 -0.344697 0.931453 +-0.11246 -0.314132 0.103701 -1.190748 -0.338292 0.932942 +-0.111478 -0.324108 0.099996 -1.189378 -0.335495 0.930621 +-0.108176 -0.311417 0.101091 -1.180256 -0.343496 0.928815 +-0.107506 -0.316068 0.10906 -1.170841 -0.341448 0.927785 +-0.109507 -0.314015 0.116916 -1.178537 -0.340677 0.93028 +-0.1216 -0.316562 0.132875 -1.182674 -0.338882 0.93048 +-0.122834 -0.321325 0.144747 -1.174349 -0.346726 0.930392 +-0.118796 -0.306685 0.155489 -1.162446 -0.336305 0.933287 +-0.1094 -0.319935 0.163116 -1.157905 -0.334924 0.934807 +-0.101658 -0.324221 0.16899 -1.156273 -0.342587 0.930909 +-0.095726 -0.318431 0.171774 -1.168397 -0.341426 0.927729 +-0.098151 -0.3253 0.174764 -1.176359 -0.33187 0.929878 +-0.101735 -0.298446 0.165173 -1.178062 -0.331239 0.931053 +-0.101046 -0.324006 0.159425 -1.179432 -0.344853 0.928933 +-0.102085 -0.313238 0.157836 -1.178537 -0.338388 0.92899 +-0.095612 -0.316166 0.161844 -1.180787 -0.332107 0.929792 +-0.096606 -0.302168 0.164182 -1.185159 -0.343748 0.935837 +-0.09683 -0.315617 0.157846 -1.192466 -0.338135 0.929075 +-0.098112 -0.304452 0.161687 -1.198852 -0.341052 0.930709 +-0.107205 -0.321774 0.16711 -1.1947 -0.334727 0.929821 +-0.112657 -0.320186 0.15661 -1.18751 -0.342055 0.931653 +-0.114684 -0.314643 0.147332 -1.181221 -0.340482 0.932256 +-0.111252 -0.316581 0.133183 -1.175547 -0.342451 0.929734 +-0.109468 -0.316812 0.117666 -1.188165 -0.339515 0.931051 +-0.111148 -0.312047 0.111168 -1.180746 -0.339294 0.933887 +-0.111731 -0.305298 0.104057 -1.176819 -0.342017 0.931653 +-0.114033 -0.309566 0.1012 -1.173802 -0.336302 0.931451 +-0.111789 -0.322139 0.103053 -1.160559 -0.335513 0.927183 +-0.101318 -0.329471 0.108234 -1.155936 -0.338963 0.930394 +-0.106894 -0.316314 0.110363 -1.164066 -0.340482 0.932915 +-0.098413 -0.31696 0.108241 -1.171736 -0.330036 0.9322 +-0.098951 -0.310887 0.095907 -1.180858 -0.335752 0.929592 +-0.099113 -0.316512 0.098616 -1.18069 -0.344122 0.926784 +-0.100725 -0.310508 0.094685 -1.184684 -0.330884 0.923604 +-0.093139 -0.311603 0.102629 -1.180634 -0.340954 0.925868 +-0.09581 -0.307976 0.108165 -1.183748 -0.34237 0.921284 +-0.099226 -0.310607 0.114645 -1.191627 -0.340363 0.925239 +-0.094755 -0.313731 0.111554 -1.201071 -0.333013 0.926014 +-0.104076 -0.298792 0.115116 -1.18587 -0.338866 0.92458 +-0.11111 -0.315358 0.119611 -1.192956 -0.341565 0.931028 +-0.111139 -0.308421 0.124435 -1.182045 -0.335399 0.929737 +-0.108244 -0.310507 0.138245 -1.172825 -0.33713 0.930481 +-0.108017 -0.318846 0.138365 -1.172728 -0.341524 0.929277 +-0.108234 -0.31284 0.14935 -1.178219 -0.342233 0.927157 +-0.110533 -0.327733 0.15611 -1.183415 -0.34363 0.935095 +-0.115082 -0.309748 0.162434 -1.170576 -0.333034 0.933919 +-0.107486 -0.307564 0.159011 -1.169711 -0.341703 0.933688 +-0.104591 -0.308969 0.152958 -1.170187 -0.342236 0.932744 +-0.098534 -0.31155 0.143842 -1.173694 -0.337725 0.929932 +-0.094839 -0.310391 0.144758 -1.173971 -0.337032 0.9285 +-0.096196 -0.329869 0.13829 -1.179821 -0.341681 0.927355 +-0.099132 -0.31155 0.143856 -1.190733 -0.335041 0.927757 +-0.101007 -0.310359 0.142958 -1.176584 -0.341191 0.930939 +-0.104724 -0.310491 0.143741 -1.17844 -0.349541 0.931425 +-0.101697 -0.319145 0.133943 -1.188569 -0.33841 0.935323 +-0.097471 -0.308077 0.126916 -1.195007 -0.34275 0.933116 +-0.101123 -0.32212 0.116521 -1.19494 -0.340855 0.931106 +-0.102007 -0.310438 0.103571 -1.197272 -0.342489 0.932743 +-0.115053 -0.306635 0.082122 -1.19019 -0.341882 0.935178 +-0.113761 -0.321048 0.082912 -1.185466 -0.342175 0.934661 +-0.105942 -0.306436 0.072409 -1.172852 -0.339061 0.93314 +-0.111537 -0.31284 0.074091 -1.172534 -0.340558 0.927238 +-0.112392 -0.325698 0.070276 -1.174794 -0.334591 0.932371 +-0.178488 -0.291642 0.072772 -1.186642 -0.334472 0.938014 +-0.280779 -0.18367 0.088041 -1.196198 -0.280884 0.944377 +-0.426959 -0.006793 0.09961 -0.999169 -0.327577 0.970579 +-0.522271 0.194956 0.103061 -1.011098 -0.320007 0.987559 +-0.470498 0.38556 0.121469 -1.035013 -0.323456 0.980579 +-0.441674 0.486829 0.130581 -1.073347 -0.319005 0.97137 +-0.40526 0.466479 0.151342 -1.118093 -0.302376 0.948741 +-0.372792 0.453574 0.170978 -1.154076 -0.284996 0.9388 +-0.356707 0.462568 0.193653 -1.157389 -0.279662 0.935521 +-0.370957 0.456616 0.214923 -1.160477 -0.27645 0.934582 +-0.499902 0.498694 0.236169 -1.170699 -0.263133 0.934803 +-0.66503 0.561533 0.251355 -1.202902 -0.25198 0.935369 +-1.440341 0.707567 0.274948 -1.265641 -0.297883 0.94776 +-0.627971 0.537214 0.29634 -1.296822 -0.252057 0.993975 +-0.541368 0.56721 0.317208 -1.123973 -0.293771 1.049187 +-0.491851 0.72486 0.316844 -1.026872 -0.269475 1.079709 +-0.640329 0.673135 0.308392 -0.821323 -0.252458 1.090779 +-0.683822 0.689044 0.306892 -0.717427 -0.233562 1.100099 +-0.688221 0.495138 0.32075 -0.784303 -0.178042 1.100394 +-0.595575 0.72895 0.332728 -0.831561 -0.187146 1.101951 +-0.573106 0.876429 0.296196 -0.814016 -0.189294 1.092817 +-0.569969 0.605165 0.288443 -0.842012 -0.232126 1.080966 +-0.53184 0.710904 0.286665 -0.894259 -0.158656 1.078704 +-0.646343 0.825323 0.292927 -0.818445 -0.202079 1.078954 +-0.452382 0.57967 0.328575 -0.94149 -0.181884 1.078023 +-0.544038 0.887538 0.304535 -0.775611 -0.237502 1.080117 +-0.576202 0.64235 0.341432 -0.825991 -0.109578 1.076003 +-0.569013 0.677153 0.315839 -0.805718 -0.088104 1.07317 +-0.578957 0.781644 0.324699 -0.798677 -0.057981 1.075166 +-0.538122 0.737667 0.336954 -0.752984 -0.288135 1.070355 +-0.598215 0.404045 0.308728 -0.84602 0.042609 1.072445 +-0.512254 0.802807 0.297813 -0.992326 0.197524 1.0776 +-0.480994 0.40122 0.304895 -0.797127 0.176857 1.076223 +-0.42869 0.791956 0.307869 -0.745565 -0.354843 1.071434 +-0.423714 1.056365 0.317078 -1.099531 0.015621 1.068167 +-0.608119 1.01068 0.321693 -0.900031 -0.056701 1.070051 +-0.397674 0.844069 0.329814 -0.861458 -0.199834 1.072861 +-0.487807 0.596833 0.350491 -0.605338 -0.014855 1.069015 +-0.491521 0.693627 0.298498 -0.885638 -0.068797 1.070777 +-0.513705 0.73531 0.324758 -0.893645 -0.105959 1.071551 +-0.403289 0.77893 0.320109 -0.73949 -0.029257 1.075649 +-0.445372 0.585979 0.34102 -0.688051 -0.078155 1.076711 +-0.389813 0.957921 0.311759 -0.768769 0.025236 1.078623 +-0.488338 0.741754 0.305362 -0.892148 0.080618 1.070961 +-0.484087 0.471855 0.319225 -0.809368 -0.026637 1.066604 +-0.426504 0.604768 0.319949 -0.761268 0.008824 1.066254 +-0.424095 0.572213 0.314144 -0.79819 0.017318 1.071368 +-0.447927 0.743939 0.31632 -0.792505 0.05493 1.073621 +-0.486258 0.931526 0.316087 -0.798482 0.104063 1.07364 +-0.402758 0.625482 0.315155 -0.761631 -0.112161 1.070667 +-0.274716 0.256752 0.310681 -0.705841 0.006838 1.081311 +-1.686872 -1.792119 0.32381 -0.833739 -0.404135 1.041175 +-0.646539 -0.591889 0.314542 -0.384862 -0.263744 0.98287 +-0.71786 -0.696165 0.268669 -0.519655 0.067832 1.018449 +-0.65042 -0.683556 0.270592 -0.500507 -0.057863 1.075352 +-0.343065 -0.313887 0.246461 -0.498866 0.032056 1.09463 +-0.6568 -0.694589 0.258983 -0.518152 -0.316544 1.07 +0.100745 1.81974 0.288589 -0.806347 -0.166732 1.044884 +-0.10708 0.938786 0.30233 -1.255957 0.055876 0.977193 +-0.135265 1.028611 0.287787 -0.898283 0.123762 0.981728 +-0.145927 0.707392 0.317821 -0.671681 -0.103037 1.043636 +-0.125757 0.681796 0.316232 -0.714124 0.024273 1.081515 +-0.171848 0.798302 0.282644 -0.746738 -0.009496 1.093786 +-0.20735 1.116543 0.337479 -0.577905 0.067105 1.082976 +-0.189471 0.877938 0.288123 -0.734614 0.104538 1.081625 +-0.205764 0.526113 0.308029 -0.642234 -0.052247 1.087829 +-0.225881 0.58588 0.323703 -0.59734 0.057054 1.091588 +-0.22496 0.869564 0.291315 -0.678592 0.062103 1.093606 +-0.233225 1.252891 0.341851 -0.561462 0.087476 1.09029 +-0.215606 0.962483 0.29452 -0.700292 0.134152 1.090491 +-0.199301 0.62465 0.312046 -0.630806 -0.039754 1.088793 +-0.167153 -0.032496 0.337581 -0.571437 0.052546 1.093348 +1.101387 -0.107988 0.263248 -0.788129 -0.199654 1.062949 +1.013864 -0.362648 0.243547 -0.858564 -0.31863 0.986313 +0.335618 -0.378755 0.223068 -0.803202 -0.27304 1.010551 +0.514527 -0.607385 0.236144 -0.687478 -0.329939 1.087899 +0.508869 -0.438046 0.254651 -0.634198 -0.372179 1.124373 +0.620074 -0.436888 0.244946 -0.589584 -0.361243 1.132282 +0.493417 -0.44541 0.243939 -0.634939 -0.332069 1.124897 +0.574784 -0.467073 0.244174 -0.623807 -0.349289 1.122866 +0.584105 -0.629418 0.257988 -0.45116 -0.365463 1.127076 +0.657374 -0.424713 0.261286 -0.598728 -0.376828 1.130694 +0.463006 -0.423479 0.252846 -0.541832 -0.330925 1.137579 +0.097086 -0.407335 0.238651 -0.495982 -0.372357 1.139727 +0.078118 -0.281366 0.265235 -0.663283 -0.321469 1.126998 +-1.72481 -1.003327 0.310929 -0.856872 -0.394029 1.102576 +-0.45354 -0.539438 0.32059 -0.450782 -0.270679 1.002061 +-0.55552 -0.442552 0.285184 -0.283398 -0.19914 1.008633 +-0.619726 -0.475174 0.342565 -0.234633 -0.101423 1.087018 +-0.083002 -0.16788 0.300101 -0.327518 -0.069052 1.13192 +0.075334 -0.60105 0.322412 -0.565717 -0.2493 1.106861 +0.37673 -1.114772 0.288065 -0.925978 -0.307696 1.01679 +0.320553 -0.562727 0.260887 -0.71725 -0.077623 0.987406 +0.30334 -0.781062 0.265594 -0.532213 -0.225582 1.058032 +0.471081 -0.987015 0.263963 -0.556098 -0.319419 1.115913 +0.391442 -0.432615 0.275965 -0.444748 -0.321507 1.128478 +0.412373 -0.409856 0.27429 -0.432017 -0.361951 1.118135 +0.423129 -0.678006 0.267168 -0.457699 -0.268627 1.108229 +0.429719 -1.052066 0.250789 -0.538509 -0.329172 1.100423 +0.273319 -0.502289 0.271744 -0.437763 -0.243232 1.096831 +0.457979 -0.49779 0.265595 -0.456764 -0.388767 1.098045 +0.441758 -0.426045 0.246461 -0.519876 -0.200817 1.099388 +0.410104 -0.823001 0.276627 -0.49499 -0.291308 1.105097 +0.415225 -0.567979 0.26029 -0.48446 -0.382859 1.101346 +0.557696 -0.447957 0.252456 -0.595992 -0.355904 1.10428 +0.500526 -0.755112 0.264604 -0.612537 -0.24197 1.100199 +0.406325 -0.619974 0.227169 -0.552519 -0.285136 1.096608 +0.508331 -0.7764 0.25076 -0.682073 -0.455066 1.106326 +0.619052 -0.664747 0.229466 -0.397128 -0.273732 1.109577 +0.491773 -0.651441 0.236349 -0.66661 -0.263605 1.108125 +0.561841 -0.577026 0.247249 -0.62685 -0.349975 1.112467 +0.674384 -0.429159 0.283747 -0.601258 -0.282813 1.112688 +0.422231 -0.649524 0.234848 -0.466233 -0.319145 1.1159 +0.627976 -0.650271 0.296031 -0.584235 -0.24138 1.113715 +0.54116 -0.564539 0.288998 -0.47981 -0.301906 1.111449 +0.590999 -0.397304 0.261063 -0.540406 -0.303774 1.118133 +0.600569 -0.636769 0.231248 -0.506024 -0.318551 1.121803 +0.494408 -0.545859 0.247569 -0.532301 -0.347453 1.129956 +0.626628 -0.485782 0.233142 -0.487745 -0.320932 1.128335 +-0.180495 -0.395115 0.239614 -0.497168 -0.304522 1.132238 +-0.30445 1.423035 0.26472 -0.776031 -0.238109 1.091482 +0.040323 0.949997 0.26388 -0.868386 0.114701 0.996683 +0.072843 0.96861 0.315098 -0.539146 -0.0542 1.025066 +0.029069 0.302995 0.316568 -0.341061 -0.071596 1.1146 +0.005393 0.602432 0.316404 -0.386165 -0.144432 1.167451 +0.043372 1.121786 0.35311 -0.267554 0.011743 1.159286 +0.045769 0.971134 0.298076 -0.444767 0.013831 1.154605 +0.014625 0.953866 0.339666 -0.331168 -0.041333 1.146733 +0.058691 0.351306 0.327512 -0.358508 -0.007664 1.146604 +0.06952 0.605573 0.31756 -0.366945 -0.100538 1.153772 +0.044621 0.956979 0.354825 -0.236767 0.056151 1.158796 +0.058415 0.886417 0.301938 -0.437026 0.041156 1.157874 +0.069426 0.875893 0.336805 -0.33131 -0.026495 1.156856 +0.055155 0.395806 0.333457 -0.338142 0.02463 1.166223 +0.029801 0.850781 0.314388 -0.373199 -0.029987 1.169315 +0.014548 1.217948 0.363593 -0.173722 0.094395 1.169758 +0.016677 1.086465 0.305115 -0.378219 0.100045 1.173589 +0.011778 0.882078 0.349094 -0.291903 0.040019 1.174331 +0.018679 0.498894 0.341783 -0.297783 0.026398 1.174194 +0.020984 0.77267 0.31226 -0.291776 0.051147 1.177636 +0.002497 1.088599 0.36285 -0.19867 0.166636 1.177713 +0.02138 0.901234 0.310215 -0.354203 0.189353 1.179964 +0.04074 0.792825 0.333623 -0.306681 0.027325 1.177538 +0.050827 0.462879 0.33882 -0.237089 0.15196 1.176698 +0.058059 0.906107 0.316574 -0.348491 0.11106 1.1793 +0.041807 1.230912 0.36046 -0.183637 0.187835 1.18065 +0.044488 1.014415 0.310041 -0.328121 0.222373 1.186687 +0.050817 0.762233 0.34039 -0.213784 0.112124 1.188805 +0.051273 0.49935 0.338893 -0.200377 0.188822 1.187925 +0.055879 0.856761 0.307621 -0.294777 0.143804 1.194652 +0.035222 1.026115 0.352591 -0.136995 0.216797 1.195083 +0.04353 0.938531 0.31313 -0.338909 0.261696 1.201187 +0.0379 0.723526 0.33102 -0.265259 0.144612 1.198056 +0.038648 0.418088 0.343323 -0.249745 0.209881 1.198154 +0.026194 0.311722 0.335449 -0.30412 -0.080437 1.19348 +-1.124863 0.023846 0.342186 -0.678832 -0.121418 1.109265 +-1.171329 0.286037 0.324048 -0.703028 -0.112989 1.002152 +-0.633835 0.194142 0.347679 -0.430457 0.237856 1.047858 +-0.776527 0.185426 0.351322 -0.250743 0.083457 1.181147 +-0.600725 0.191085 0.348015 -0.281811 0.085307 1.242605 +-0.772069 0.225659 0.343662 -0.309974 0.14837 1.230737 +-0.719022 0.220981 0.34816 -0.365432 0.170219 1.201432 +-0.605212 0.239191 0.342506 -0.361909 0.20296 1.18573 +-0.648479 0.236293 0.338513 -0.36173 0.218171 1.17939 +-0.570924 0.233331 0.345086 -0.39626 0.196618 1.173205 +-0.680272 0.212284 0.349938 -0.405371 0.209641 1.169536 +-0.826426 0.236187 0.350871 -0.473433 0.23748 1.162166 +-0.647404 0.197533 0.33028 -0.536438 0.259803 1.163532 +0.131785 0.210172 0.350171 -0.404768 0.240731 1.158121 +0.227976 0.911933 0.342623 -0.713752 -0.066393 1.117759 +-0.144046 1.410776 0.32452 -1.137497 0.051759 1.008343 +-0.058462 0.88846 0.300045 -0.673224 0.503513 0.99724 +-0.061721 1.22445 0.363375 -0.401654 0.391627 1.11853 +-0.06898 0.675483 0.340962 -0.505619 0.402958 1.189783 +-0.095959 0.579501 0.326843 -0.399086 0.205135 1.197884 +-0.107544 1.076563 0.359673 -0.341645 0.456664 1.194836 +-0.088862 0.096576 0.325128 -0.428922 0.175387 1.187113 +0.914659 0.083565 0.298777 -0.67703 -0.192677 1.121793 +0.958307 0.354043 0.288479 -0.647063 -0.239313 1.000419 +0.546437 0.189111 0.236603 -0.468211 -0.174967 1.043059 +0.445802 0.15306 0.253239 -0.422199 -0.281219 1.166461 +0.602169 0.165599 0.238047 -0.366016 -0.306416 1.22319 +0.652484 0.190847 0.242469 -0.350845 -0.249498 1.231197 +0.641631 0.181944 0.238753 -0.230751 -0.317786 1.223106 +0.661443 0.209511 0.236292 -0.359506 -0.299266 1.216277 +0.600979 0.208673 0.228014 -0.332141 -0.294417 1.213965 +0.639028 0.222758 0.23291 -0.275159 -0.287442 1.206945 +0.535053 0.239915 0.217012 -0.248209 -0.267386 1.206598 +0.545788 0.24272 0.219664 -0.179021 -0.302553 1.212708 +0.740886 0.236817 0.217652 -0.165839 -0.269219 1.21791 +-0.080102 0.208987 0.230768 -0.331021 -0.335198 1.218887 +-0.305677 1.045646 0.2644 -0.741915 -0.298181 1.16876 +0.140118 1.081448 0.298241 -0.863232 0.008478 1.035293 +0.081082 0.702739 0.318667 -0.357939 0.506585 1.022229 +0.041278 0.775973 0.311571 -0.251515 0.311641 1.168088 +0.001833 1.231509 0.359149 -0.065251 0.475184 1.254853 +0.052221 1.092984 0.302594 -0.279464 0.356799 1.274217 +0.063076 1.11936 0.341546 -0.18604 0.350531 1.255519 +0.039337 -0.154976 0.341545 -0.148546 0.185337 1.237045 +-1.166528 0.482159 0.332364 -0.706295 -0.026416 1.143788 +-0.765522 0.980475 0.317194 -0.782708 0.148589 1.032042 +-0.567977 1.037069 0.357642 -0.571845 0.526284 1.047427 +-0.410974 1.186697 0.349473 -0.621377 0.509717 1.179232 +-0.380186 1.310302 0.33525 -0.50292 0.688765 1.260276 +-0.26675 1.105466 0.381978 -0.306804 0.651271 1.269872 +-0.209703 0.732276 0.348788 -0.37668 0.570103 1.247382 +-0.228729 1.109871 0.361029 -0.234652 0.643789 1.230791 +-0.193732 0.412953 0.333298 -0.24681 0.51711 1.222509 +0.588179 0.736586 0.307919 -0.226522 0.440254 1.226985 +0.580582 1.224026 0.347389 -0.05882 0.419942 1.234417 +0.625771 0.698301 0.308277 -0.047269 0.323268 1.232819 +0.701965 0.99108 0.301662 -0.112088 0.281166 1.236158 +0.563152 0.524848 0.301036 -0.051008 0.130669 1.242956 +0.761292 0.407344 0.29576 0.303153 0.205399 1.242201 +0.707787 0.903354 0.244064 0.049697 -0.024405 1.250747 +0.584159 0.547586 0.256794 0.086839 -0.185857 1.258944 +0.598995 0.689386 0.214756 0.185171 -0.239311 1.260593 +0.618401 0.574592 0.207468 -0.021392 -0.22964 1.262652 +0.629839 0.491559 0.205953 0.073361 -0.269217 1.269034 +0.682152 0.491968 0.212307 -0.117418 -0.267541 1.274444 +0.43069 -0.555108 0.211786 -0.124177 -0.290493 1.278426 +0.564848 -0.57823 0.210751 -0.132671 -0.31786 1.287203 +0.475975 -0.33477 0.216123 -0.173478 -0.335138 1.282954 +0.449631 -0.694094 0.224182 -0.208667 -0.340814 1.288689 +0.33157 -0.754253 0.228305 -0.214813 -0.286615 1.287646 +0.349019 -0.506802 0.217011 -0.193399 -0.332144 1.285751 +0.353448 -0.457217 0.219459 -0.307822 -0.472913 1.272956 +0.318011 -0.868936 0.236946 -0.163544 -0.42435 1.274074 +0.264256 -0.90461 0.238587 0.153582 -0.389336 1.271696 +0.210124 -0.674763 0.232491 0.141608 -0.457367 1.273763 +0.26683 -0.636129 0.256471 0.270909 -0.335139 1.271909 +0.164836 -0.906155 0.246305 0.2845 -0.317211 1.270659 +-0.482369 -0.6765 0.243809 0.422632 -0.509518 1.269059 +-0.525596 -0.343713 0.288428 0.46926 -0.287207 1.272908 +-0.610666 -0.598304 0.272097 0.521619 -0.295322 1.270406 +-0.603794 -0.262443 0.293115 0.576586 -0.325152 1.262789 +-0.59678 -0.361581 0.304428 0.593868 -0.466587 1.260477 +-0.605946 -0.337822 0.289992 0.47492 -0.49319 1.267216 +-0.40484 -0.249254 0.294782 0.251831 -0.451614 1.259881 +-0.083018 -0.202607 0.306192 0.055687 -0.296527 1.237286 +0.342278 -0.542414 0.321843 -0.588415 -0.244575 1.134002 +-0.050103 -1.36258 0.285456 -0.564325 -0.173865 1.022086 +-0.052864 -0.821321 0.268625 0.248555 -0.266402 1.074411 +0.006649 -0.873826 0.249627 0.235286 -0.295974 1.232725 +0.005177 -0.729297 0.253499 0.335856 -0.465996 1.296136 +-0.047076 -0.343543 0.269139 0.232468 -0.227651 1.284323 +-0.027014 -0.564612 0.230679 0.203302 -0.312643 1.263955 +0.013848 0.207776 0.284358 0.122841 -0.389792 1.239228 +0.853776 -0.111489 0.27238 -0.583455 -0.261082 1.136775 +0.951666 -0.06252 0.247627 -0.645611 -0.131367 1.013277 +0.487817 0.029859 0.210382 -0.135666 -0.109046 1.09532 +0.581383 0.0392 0.223059 -0.04573 -0.286436 1.248316 +0.788624 0.024054 0.209449 0.036106 -0.228611 1.312886 +0.726737 -0.016629 0.217478 -0.022339 -0.243997 1.306636 +0.741414 -0.000657 0.203784 -0.057082 -0.23283 1.285687 +0.74525 -0.026554 0.212173 -0.056903 -0.227902 1.274306 +0.706262 -0.034402 0.208414 0.033314 -0.244351 1.268815 +0.76613 -0.029018 0.210569 -0.022747 -0.229813 1.269912 +0.623611 -0.01564 0.207174 0.042312 -0.263641 1.276024 +0.851972 -0.029279 0.199962 0.095617 -0.262892 1.276342 +0.644008 -0.04324 0.205975 0.046183 -0.228929 1.279835 +-0.01628 -0.018477 0.207262 -0.03751 -0.262638 1.273419 +-0.190415 -1.126332 0.25979 -0.575299 -0.279302 1.192652 +0.152026 -1.591033 0.265637 -0.742575 -0.105516 1.032581 +0.120511 -0.469326 0.203196 0.388517 -0.260632 1.068053 +0.096681 -0.142484 0.242744 0.355874 -0.237876 1.243396 +0.059838 -0.951708 0.215081 0.28721 -0.393198 1.326606 +0.094336 -0.888999 0.270635 0.417673 -0.455928 1.326358 +0.095147 -0.938326 0.239125 0.36915 -0.330094 1.306205 +0.105197 -0.351985 0.25564 0.25355 -0.178791 1.282575 +-0.743768 -0.045797 0.326534 -0.58374 -0.354131 1.144227 +-1.240508 -0.035515 0.305113 -0.675927 -0.238228 1.00322 +-0.580077 0.083943 0.328924 -0.132567 -0.140863 1.09972 +-0.4765 0.095329 0.323707 -0.048466 -0.224278 1.257203 +-0.582306 0.073321 0.324305 -0.016691 -0.183364 1.310672 +-0.807227 0.078258 0.326403 0.050981 -0.201938 1.296781 +-0.705 0.053394 0.329566 -0.07128 -0.146325 1.270489 +-0.644163 0.055529 0.32907 -0.149522 -0.146873 1.250256 +-0.673511 0.034197 0.320429 -0.040684 -0.198298 1.237471 +-0.438548 0.043315 0.330381 -0.158015 -0.093365 1.241789 +-0.591919 0.029503 0.334783 -0.149144 -0.120217 1.239517 +-0.468301 0.008194 0.335657 -0.119933 -0.090609 1.238876 +-0.50978 0.029366 0.332635 -0.231728 -0.028135 1.226944 +0.083026 0.005284 0.334302 -0.34654 -0.022894 1.221471 +0.497834 -1.096463 0.317354 -0.682826 -0.167048 1.144952 +-0.072504 -1.034864 0.283552 -0.710361 -0.183891 1.009947 +-0.07946 -0.528294 0.242147 0.012355 -0.169196 1.034315 +-0.022965 -0.760708 0.272676 0.289771 -0.264592 1.181801 +-0.018082 -1.066551 0.231917 0.109271 -0.276131 1.266758 +-0.04683 -0.856559 0.272414 0.255181 -0.384291 1.270616 +-0.049529 -0.673605 0.270951 0.166492 -0.187263 1.249966 +-0.033921 -0.579791 0.235153 0.089718 -0.221011 1.225679 +-0.056115 -0.848787 0.272574 0.207603 -0.280725 1.215762 +-0.057687 -1.06935 0.240879 0.069348 -0.245541 1.201866 +-0.032333 -0.683834 0.267634 0.270633 -0.354941 1.208604 +-0.0469 -0.589338 0.261513 0.142271 -0.175461 1.208833 +-0.036957 -0.480849 0.233579 0.123608 -0.191123 1.200724 +-0.031651 -0.817376 0.274279 0.213412 -0.280587 1.204162 +-0.034465 -1.109802 0.237168 0.034481 -0.234524 1.203876 +-0.037931 -0.844364 0.265754 0.200996 -0.344166 1.196368 +-0.043574 -0.561504 0.268362 0.142731 -0.119154 1.195311 +-0.026455 -0.552579 0.236115 0.039583 -0.191673 1.195135 +-0.035431 -0.880284 0.277003 0.215692 -0.227884 1.192393 +-0.030931 -1.075806 0.241209 0.057587 -0.223274 1.194032 +-0.033353 -0.705788 0.266784 0.126763 -0.328282 1.180862 +-0.055801 -0.521101 0.263509 0.073607 -0.104713 1.179602 +-0.032809 -0.583329 0.232748 0.033253 -0.185827 1.179171 +-0.024859 -0.929433 0.282745 0.197826 -0.259818 1.180703 +-0.015123 -1.082949 0.236653 -0.030503 -0.165941 1.179821 +-0.008568 -0.919393 0.262917 0.087079 -0.308363 1.181455 +-0.031467 -0.526295 0.273564 0.025864 -0.082191 1.172982 +-0.021916 -0.778945 0.235823 -0.081855 -0.172722 1.166628 +-0.028822 -0.991452 0.286505 0.100655 -0.255642 1.1689 +-0.024561 -1.043844 0.244362 -0.079845 -0.13322 1.162082 +-0.017967 -0.736559 0.261776 0.024177 -0.337797 1.162573 +-0.03333 -0.406781 0.267497 -0.033801 -0.047224 1.162268 +-0.021861 -0.695694 0.236041 -0.108689 -0.157728 1.162254 +-0.028454 -0.941585 0.283823 0.014713 -0.230797 1.154542 +-0.0331 -0.37348 0.265161 -0.059475 -0.080791 1.153571 +1.007864 0.14447 0.299314 -0.455065 -0.261591 1.11672 +1.086216 0.576876 0.274716 -0.717401 -0.203556 1.010214 +0.596227 0.56768 0.260698 -0.296256 -0.079022 1.02509 +0.751296 0.663725 0.267576 -0.14128 -0.167087 1.139891 +0.679394 0.63214 0.254428 -0.160056 -0.142951 1.200416 +0.707915 0.509008 0.242979 -0.087679 -0.194848 1.208949 +0.558536 0.73382 0.276218 -0.022107 -0.17603 1.198716 +0.627809 0.77699 0.238909 0.038662 -0.211134 1.195336 +0.857043 0.750962 0.238944 0.094217 -0.099022 1.198188 +0.634403 0.57117 0.262405 0.067634 -0.197132 1.191868 +0.621499 0.794411 0.277413 0.072271 -0.151818 1.196191 +0.74678 0.736737 0.282494 0.160627 -0.080047 1.197161 +0.747987 0.517677 0.268417 0.194416 -0.106861 1.206303 +0.642749 0.753856 0.289099 0.342759 -0.232337 1.212067 +0.571844 0.939132 0.259547 0.192043 -0.172109 1.210069 +0.58773 0.696763 0.291389 0.325317 -0.356891 1.212475 +0.496678 1.066547 0.308189 0.28653 0.009496 1.22161 +0.620739 0.797127 0.264137 0.242638 0.040313 1.2238 +0.528243 0.966982 0.242823 0.189119 -0.080519 1.226414 +0.504082 0.549129 0.283596 0.477163 -0.044896 1.231838 +0.445993 0.72388 0.251941 0.294858 -0.029788 1.236808 +0.52922 0.670173 0.303163 0.411403 -0.078131 1.236596 +0.385964 0.906118 0.256051 0.319678 -0.072264 1.2451 +0.443382 0.823133 0.275504 0.374073 0.058496 1.242258 +0.470266 0.662346 0.280414 0.353119 -0.007445 1.241794 +0.460112 0.779163 0.268839 0.34683 -0.00252 1.244722 +0.457007 1.085934 0.262899 0.320221 0.005205 1.255046 +0.448823 0.87558 0.273989 0.321587 -0.176938 1.25364 +0.400286 0.3084 0.302506 0.351202 -0.113656 1.258877 +0.351631 0.310547 0.290731 -0.016938 -0.339592 1.226152 +-0.544112 0.520813 0.29353 -0.694839 -0.363451 1.1025 +-0.876041 1.067718 0.33433 -0.766554 -0.319733 1.029661 +-0.40075 0.76162 0.372831 -0.311383 -0.278144 1.14244 +-0.381348 0.771303 0.302812 -0.10723 -0.367687 1.27281 +-0.438766 0.813786 0.364743 -0.258938 -0.24412 1.303402 +1.34809 0.332112 0.276024 -0.101371 -0.22247 1.278384 +0.720669 0.682377 0.288576 -0.60179 -0.067002 1.118469 +0.757732 0.730277 0.255682 0.245705 -0.119644 1.041762 +0.604384 0.623511 0.234163 -0.058898 -0.13899 1.133044 +0.824585 0.508173 0.218833 0.252259 -0.20326 1.265066 +0.613585 0.591898 0.209031 0.318276 -0.214527 1.311275 +0.677006 0.776874 0.182865 0.327783 -0.234484 1.305651 +0.4951 0.631502 0.204868 0.249458 -0.241341 1.288917 +0.675356 0.6622 0.204072 0.352166 -0.235547 1.285773 +0.706882 0.618406 0.192968 0.389334 -0.225439 1.281943 +0.896887 0.639511 0.189076 0.485319 -0.261259 1.286955 +0.759624 0.774534 0.177972 0.391921 -0.259501 1.291859 +0.021462 0.499778 0.193754 0.298898 -0.23476 1.286955 +-0.150643 -1.135683 0.240209 -0.487123 -0.35076 1.227257 +0.26313 -1.052598 0.24357 -0.723627 -0.166044 1.057028 +0.16672 -0.460281 0.202043 0.444533 0.012254 1.073795 +0.17646 -0.47978 0.159536 0.418781 -0.257557 1.244198 +0.184634 -0.639051 0.209883 0.566042 -0.272332 1.32811 +0.263969 -0.909545 0.178479 0.326188 -0.208891 1.334071 +0.278911 -0.785924 0.200586 0.475758 -0.346075 1.308669 +0.255526 -0.399638 0.216847 0.441382 -0.099863 1.288692 +0.259064 -0.728847 0.176235 0.385704 -0.214487 1.277935 +0.256777 -0.890084 0.223988 0.479977 -0.288151 1.266673 +0.241292 -1.065594 0.181787 0.362368 -0.14772 1.267153 +0.217833 -0.832283 0.199857 0.499426 -0.322312 1.272219 +0.203804 -0.475429 0.218072 0.448157 -0.054866 1.270306 +0.184733 -0.846751 0.178295 0.337369 -0.185371 1.259705 +0.157822 -0.900833 0.223201 0.43406 -0.278419 1.25375 +-0.791638 0.741107 0.2311 0.232012 -0.230817 1.247099 +-0.115474 0.649172 0.306233 -1.073264 -1.093407 1.090137 +-0.7771 0.905702 0.350825 -0.79746 -0.467494 1.025753 +-0.397537 0.477374 0.379805 -0.38562 -0.52985 1.115181 +-0.408095 0.853146 0.338687 -0.10777 -0.462553 1.251767 +-0.342404 0.758241 0.335009 -0.229383 -0.529906 1.307089 +0.579917 -0.428804 0.316416 -0.194482 -0.32218 1.285426 +-0.375962 -1.085368 0.312106 -0.555731 -0.404052 1.155404 +-0.477912 -0.965913 0.262707 -0.040258 -0.024294 1.044456 +-0.343792 -0.74791 0.238891 0.304119 -0.194431 1.152524 +-0.296614 -0.521406 0.257606 0.479879 -0.337482 1.261469 +-0.424107 -0.761464 0.270168 0.491711 -0.305352 1.285703 +-0.349072 -0.835448 0.282379 0.471315 -0.302277 1.278434 +-0.35919 -0.819114 0.26977 0.42069 -0.305314 1.259237 +-0.391794 -0.582023 0.263766 0.456624 -0.282298 1.248961 +-0.498155 -0.93953 0.254236 0.311073 -0.229343 1.239565 +-0.484845 -0.478769 0.272995 0.313027 -0.169212 1.233702 +-0.488743 -0.825895 0.295187 0.425301 -0.32263 1.225579 +-0.368712 -0.772399 0.261993 0.251371 -0.290793 1.218526 +-0.460758 -0.971428 0.299997 0.291587 -0.347176 1.221991 +-0.419168 -0.348729 0.258131 0.139568 -0.193959 1.215277 +-0.50923 -0.684516 0.292478 0.325462 -0.231074 1.21293 +-0.402905 -0.720304 0.295144 0.25036 -0.276899 1.204245 +-0.545955 -0.683219 0.290763 0.136955 -0.288309 1.203185 +-0.526507 -0.683314 0.309726 0.265628 -0.232892 1.200322 +-0.4772 -0.328799 0.272427 0.121358 -0.353994 1.202888 +-0.435013 -1.010099 0.3445 0.362338 -0.369693 1.201771 +-0.533631 -0.581511 0.329898 0.331705 -0.175124 1.19985 +-0.561694 -0.681233 0.335677 0.20974 -0.369241 1.19553 +-0.469536 -0.667003 0.278765 0.092383 -0.112847 1.18568 +-0.492296 -0.615955 0.276638 0.03496 -0.174908 1.186308 +-0.555326 -0.821866 0.28065 -0.026228 -0.13513 1.176783 +-0.667727 -0.563708 0.284915 -0.053717 -0.277671 1.169203 +-0.703832 -0.479114 0.325601 0.030139 -0.226566 1.158923 +-0.511625 -0.609862 0.277499 0.092888 -0.21122 1.156291 +-0.569136 -0.753652 0.332084 -0.117642 -0.191437 1.156475 +-0.553402 -0.616225 0.312049 -0.076113 -0.255841 1.15513 +-0.554889 -0.585746 0.288947 -0.097512 -0.238405 1.1499 +-0.540421 -0.709461 0.317149 -0.158779 -0.110621 1.148392 +-0.472327 -0.290747 0.325892 -0.30659 -0.139996 1.13952 +-0.463016 -0.670877 0.318148 -0.320795 -0.117537 1.130193 +-0.531428 -0.651185 0.319159 -0.317722 -0.172957 1.1272 +-0.499901 -0.45589 0.338555 -0.682994 -0.230428 1.116001 +0.00862 -0.37674 0.332042 -0.991079 -0.187991 1.065314 +-0.023835 -0.469166 0.311899 -1.047321 -0.15105 1.007843 +-0.289818 -0.607525 0.311141 -1.114806 -0.146261 0.947366 +-0.264951 -0.649789 0.314206 -1.127967 -0.173075 0.92066 +-0.272919 -0.673845 0.303919 -1.090726 -0.16224 0.915808 +-0.300575 -0.670276 0.304953 -1.146814 -0.1517 0.922937 +-0.280418 -0.697709 0.303938 -1.093545 -0.150241 0.927587 +-0.253408 -0.681955 0.307207 -1.095181 -0.155384 0.93124 +-0.286416 -0.666472 0.304414 -1.086631 -0.1637 0.940903 +-0.326288 -0.66014 0.306207 -1.09426 -0.16677 0.942481 +-0.320415 -0.667809 0.308349 -1.080807 -0.172113 0.945963 +-0.351112 -0.650921 0.311637 -1.083349 -0.168229 0.940236 +-0.354281 -0.671195 0.309689 -1.091018 -0.168842 0.938817 +-0.328932 -0.667503 0.307285 -1.096536 -0.164405 0.939573 +-0.305325 -0.666785 0.310578 -1.106066 -0.165414 0.937323 +-0.291982 -0.65165 0.307445 -1.114754 -0.167578 0.939012 +-0.275776 -0.662108 0.312949 -1.130651 -0.169786 0.939065 +-0.270658 -0.673765 0.314119 -1.133807 -0.160014 0.944296 +-0.279209 -0.665367 0.31188 -1.130554 -0.159561 0.94168 +-0.288629 -0.668715 0.306279 -1.125452 -0.169569 0.944432 +-0.279595 -0.644401 0.312254 -1.113691 -0.162674 0.94336 +-0.279929 -0.647998 0.310219 -1.106818 -0.163184 0.935353 +-0.279714 -0.628027 0.3153 -1.100534 -0.171598 0.937674 +-0.261849 -0.595313 0.311462 -1.099568 -0.163363 0.938718 +-0.238544 -0.569301 0.313843 -1.092399 -0.158908 0.941889 +-0.005609 -0.107728 0.601193 -0.545371 0.005348 0.0 +-0.004511 -0.105685 0.59773 -0.544927 0.009272 0.0 +-0.000494 -0.097057 0.603354 -0.543101 0.003389 0.0 +-0.006931 -0.092577 0.598497 -0.545565 0.004023 0.0 +-0.008479 -0.101852 0.602645 -0.541008 0.009277 0.0 +-0.002993 -0.083314 0.602297 -0.541677 0.006728 0.0 +-0.001161 -0.091099 0.604173 -0.542156 0.014745 0.0 +-0.001478 -0.104447 0.601381 -0.543606 0.010455 0.0 +-0.003407 -0.095302 0.601007 -0.540504 0.010706 0.0 +-0.002543 -0.120113 0.597021 -0.545229 0.014845 0.0 +-0.006397 -0.095242 0.600548 -0.543943 0.013397 0.0 +-0.008297 -0.118454 0.601367 -0.549375 0.006435 0.0 +-0.002695 -0.098859 0.602124 -0.540171 0.013561 0.0 +-0.001811 -0.078958 0.602448 -0.548443 0.008318 0.0 +-0.00778 -0.104006 0.599779 -0.542523 0.004188 0.0 +-0.000942 -0.093448 0.60153 -0.54503 -0.001124 0.0 +-0.001028 -0.096969 0.600037 -0.544348 0.012538 0.0 +0.000392 -0.091649 0.599368 -0.542135 0.011364 0.0 +-0.00256 -0.110877 0.600013 -0.544495 0.013716 0.0 +-0.00401 -0.103421 0.599676 -0.542562 0.009117 0.0 +-0.004128 -0.100962 0.60251 -0.539291 0.008947 0.0 +-0.005227 -0.08488 0.601106 -0.540948 0.011104 0.0 +-0.005041 -0.105685 0.600136 -0.54368 0.01029 0.0 +-0.006178 -0.098309 0.601652 -0.542225 0.012433 0.0 +-0.003926 -0.088322 0.5962 -0.537699 0.006385 0.0 +0.001879 -0.099724 0.604993 -0.540594 0.012538 0.0 +-0.00174 -0.08479 0.605516 -0.541483 0.017955 0.0 +-0.004378 -0.097739 0.598115 -0.545358 0.006906 0.0 +-0.003509 -0.111835 0.605328 -0.543679 0.011652 0.0 +-0.004609 -0.095371 0.600002 -0.540163 0.014251 0.0 +-0.000608 -0.101074 0.602337 -0.549173 -0.001061 0.0 +-0.002461 -0.096531 0.600871 -0.543472 0.020033 0.0 +-0.000876 -0.103957 0.603651 -0.542264 0.003604 0.0 +0.000709 -0.093697 0.60225 -0.53939 0.011684 0.0 +0.000594 -0.102991 0.60225 -0.540102 0.002105 0.0 +-0.001625 -0.100064 0.60257 -0.5461 0.001411 0.0 +-0.001292 -0.094442 0.596249 -0.540038 0.01087 0.0 +-0.004178 -0.100773 0.602347 -0.542225 0.013397 0.0 +0.00039 -0.107903 0.599978 -0.547861 -0.002431 0.0 +-0.005495 -0.111887 0.602907 -0.543783 0.005846 0.0 +-0.000308 -0.103426 0.601925 -0.542954 0.012852 0.0 +-0.003795 -0.097427 0.592774 -0.536987 0.018138 0.0 +-0.00421 -0.098393 0.599479 -0.544081 0.009989 0.0 +-0.00393 -0.091258 0.595443 -0.543576 0.008258 0.0 +0.001091 -0.103451 0.593891 -0.549988 0.005636 0.0 +-0.00166 -0.098733 0.600571 -0.548249 0.003114 0.0 +0.000189 -0.106367 0.597691 -0.546204 0.003654 0.0 +-0.005845 -0.099826 0.596225 -0.540473 0.00697 0.0 +-0.005675 -0.108122 0.597244 -0.543783 0.01034 0.0 +-0.003844 -0.099814 0.610148 -0.54172 0.009267 0.0 +-0.001776 -0.118497 0.602223 -0.541988 0.006175 0.0 +-0.00638 -0.113713 0.6039 -0.546471 0.014662 0.0 +-0.00266 -0.119954 0.596376 -0.542165 0.009162 0.0 +0.004358 -0.091559 0.59927 -0.546502 0.012492 0.0 +-0.006544 -0.092024 0.601094 -0.543606 0.004353 0.0 +0.003327 -0.098199 0.602971 -0.548102 -0.001467 0.0 +-0.002042 -0.090502 0.602558 -0.542696 0.008706 0.0 +0.001124 -0.097284 0.597056 -0.547964 0.011679 0.0 +-0.004861 -0.088752 0.602434 -0.542329 0.005796 0.0 +-0.003779 -0.09082 0.59974 -0.5484 0.016877 0.0 +-0.005626 -0.092292 0.598473 -0.546217 0.006535 0.0 +-0.007747 -0.098818 0.600548 -0.544927 0.001671 0.0 +-0.00221 -0.099656 0.601753 -0.545703 0.007998 0.0 +-0.001461 -0.100839 0.60164 -0.54736 0.000442 0.0 +-0.005079 -0.087635 0.598648 -0.541677 0.019559 0.0 +-0.00286 -0.089393 0.605727 -0.545194 0.001246 0.0 +-0.00156 -0.105337 0.603468 -0.544215 0.001891 0.0 +-0.005828 -0.101362 0.601292 -0.544288 0.014122 0.0 +-0.000993 -0.116777 0.601652 -0.542566 0.012593 0.0 +-0.004227 -0.095513 0.599316 -0.542558 0.001401 0.0 +-0.004828 -0.09057 0.600472 -0.547938 0.008527 0.0 +-0.00266 -0.099776 0.599095 -0.54229 0.01199 0.0 +-0.005544 -0.098204 0.597317 -0.543446 0.011259 0.0 +0.002257 -0.0899 0.599903 -0.545362 -0.00137 0.0 +-0.003247 -0.096729 0.598749 -0.544258 0.00644 0.0 +-0.003544 -0.100253 0.602384 -0.541543 0.00639 0.0 +-0.003194 -0.091348 0.597765 -0.544111 0.007349 0.0 +-0.002762 -0.105036 0.598113 -0.547731 0.007102 0.0 +-0.004227 -0.088971 0.595022 -0.544245 0.009536 0.0 +-0.005145 -0.081644 0.602694 -0.538842 0.006097 0.0 +-0.003893 -0.125055 0.600734 -0.546208 0.00775 0.0 +-0.006014 -0.103467 0.600188 -0.541854 0.007317 0.0 +-0.008348 -0.102043 0.603306 -0.542463 0.0105 0.0 +-0.001625 -0.10235 0.603964 -0.550493 0.003699 0.0 +-0.003877 -0.107511 0.600497 -0.540236 0.00628 0.0 +-0.002592 -0.094552 0.603999 -0.544413 0.007774 0.0 +-0.002623 -0.104669 0.600819 -0.547805 0.003014 0.0 +-0.004358 -0.10318 0.596487 -0.542001 0.005746 0.0 +-0.001529 -0.114362 0.600461 -0.548098 0.005421 0.0 +-0.001707 -0.096107 0.601181 -0.544715 0.006061 0.0 +4e-05 -0.103919 0.601307 -0.543032 0.001726 0.0 +-0.000975 -0.097777 0.602273 -0.545876 0.004644 0.0 +-0.004762 -0.091559 0.598485 -0.546985 -0.009136 0.0 +-0.00104 -0.105157 0.600931 -0.541923 0.013292 0.0 +-0.00781 -0.09772 0.603217 -0.546929 0.00216 0.0 +0.001024 -0.088413 0.597383 -0.548642 0.01304 0.0 +0.000676 -0.092178 0.60734 -0.542389 0.00676 0.0 +7.5e-05 -0.096693 0.59748 -0.545664 -0.002654 0.0 +-0.003478 -0.10339 0.60164 -0.54374 0.00676 0.0 +-0.007698 -0.099256 0.596771 -0.542389 0.01864 0.0 +-0.007663 -0.093566 0.603951 -0.541401 0.016662 0.0 +-0.00356 -0.104699 0.598212 -0.547257 0.012702 0.0 +0.002245 -0.080836 0.60097 -0.547554 0.013401 0.0 +-0.003779 -0.092337 0.599031 -0.543339 0.024153 0.0 +-0.001144 -0.111517 0.598834 -0.548698 0.011739 0.0 +-0.000242 -0.097777 0.604272 -0.545595 0.003708 0.0 +-0.00074 -0.104436 0.600151 -0.549155 0.003444 0.0 +-0.002795 -0.120211 0.600931 -0.546234 0.007344 0.0 +-0.003478 -0.101701 0.604607 -0.545086 0.012488 0.0 +-0.001144 -0.107701 0.598881 -0.544763 0.009971 0.0 +-0.001942 -0.10315 0.601466 -0.552616 0.00828 0.0 +0.000993 -0.108007 0.60102 -0.544115 0.011619 0.0 +-0.001226 -0.104938 0.600374 -0.545306 -0.002184 0.0 +-0.003413 -0.105584 0.59877 -0.542627 0.007399 0.0 +0.002977 -0.099166 0.601925 -0.544659 -0.001074 0.0 +-0.00139 -0.112024 0.601082 -0.551783 0.00681 0.0 +-0.000909 -0.102032 0.598832 -0.545267 0.000136 0.0 +-0.003811 -0.092178 0.600583 -0.546208 0.008792 0.0 +-0.002243 -0.094344 0.598462 -0.551132 0.013232 0.0 +-0.00121 -0.095272 0.604011 -0.54708 0.003923 0.0 +0.001443 -0.102564 0.597567 -0.544586 0.007321 0.0 +-0.001674 -0.095551 0.602047 -0.54374 0.010702 0.0 +-0.001543 -0.089341 0.599975 -0.545639 0.009222 0.0 +-0.00156 -0.096759 0.597939 -0.544694 0.012158 0.0 +-0.003478 -0.106274 0.598931 -0.545544 0.005521 0.0 +-0.007597 -0.108979 0.598648 -0.544888 0.00231 0.0 +-0.000557 -0.113013 0.599107 -0.547188 0.00517 0.0 +-0.003893 -0.098139 0.606468 -0.545229 0.013077 0.0 +-0.002427 -0.104886 0.601565 -0.550195 0.000712 0.0 +9e-06 -0.103924 0.601466 -0.548331 0.002635 0.0 +5.8e-05 -0.089393 0.603228 -0.545267 0.008043 0.0 +0.002409 -0.097492 0.60354 -0.549138 0.014995 0.0 +-0.002527 -0.093571 0.596723 -0.542665 0.005531 0.0 +-0.003728 -0.097588 0.597852 -0.543028 0.01484 0.0 +0.000508 -0.106364 0.601838 -0.552586 0.004955 0.0 +0.003441 -0.092427 0.602918 -0.54169 0.012442 0.0 +-0.004228 -0.10367 0.601826 -0.545733 -0.000846 0.0 +-0.003075 -0.090258 0.60427 -0.541341 0.008957 0.0 +-0.00691 -0.118574 0.602698 -0.547628 0.000634 0.0 +-0.000827 -0.089431 0.602907 -0.544586 0.008258 0.0 +0.001392 -0.105436 0.602198 -0.546234 0.004439 0.0 +-0.002642 -0.101052 0.60139 -0.545863 0.004887 0.0 +0.002409 -0.092682 0.600408 -0.541082 0.010071 0.0 +-0.003347 -0.107413 0.601282 -0.544914 0.004937 0.0 +0.005562 -0.093935 0.601479 -0.547257 0.012697 0.0 +-0.000592 -0.107035 0.599926 -0.54834 0.004078 0.0 +-0.003412 -0.090748 0.601814 -0.546937 0.015055 0.0 +-0.006109 -0.111405 0.602136 -0.544763 0.002959 0.0 +-0.002893 -0.089393 0.598946 -0.545833 0.015155 0.0 +-0.002226 -0.082693 0.603416 -0.548163 0.004508 0.0 +0.00131 -0.105685 0.598063 -0.544279 0.006705 0.0 +0.003043 -0.101104 0.600211 -0.542286 0.011739 0.0 +-0.001193 -0.094133 0.597405 -0.550195 0.004138 0.0 +-0.000825 -0.115751 0.600408 -0.544616 0.00368 0.0 +-0.008649 -0.091688 0.601814 -0.540504 0.006741 0.0 +-0.004795 -0.099166 0.598969 -0.543028 0.00237 0.0 +0.002141 -0.097219 0.598695 -0.545634 0.003238 0.0 +-0.000525 -0.099445 0.602012 -0.54613 0.007354 0.0 +-0.005777 -0.104847 0.597902 -0.546083 0.010286 0.0 +-0.003611 -0.091718 0.596684 -0.544353 0.010236 0.0 +-0.002609 -0.096474 0.601466 -0.543977 0.001471 0.0 +-0.000226 -0.105036 0.597294 -0.547416 0.01183 0.0 +-0.004047 -0.087605 0.595295 -0.541138 0.008829 0.0 +-0.002194 -0.086526 0.598288 -0.543442 0.011309 0.0 +-0.004779 -0.083582 0.59773 -0.543464 0.015749 0.0 +-0.002408 -0.089333 0.602301 -0.547291 0.008423 0.0 +-9.3e-05 -0.098629 0.600722 -0.546204 0.000767 0.0 +-0.004713 -0.096471 0.600623 -0.544452 0.005828 0.0 +-0.00233 -0.09815 0.600658 -0.544957 0.007239 0.0 +-0.000827 -0.095543 0.601989 -0.542726 0.010875 0.0 +0.000408 -0.095001 0.597478 -0.543343 0.008043 0.0 +-0.000794 -0.094193 0.600461 -0.541241 0.011254 0.0 +-0.003861 -0.093476 0.604731 -0.546653 0.010487 0.0 +0.000891 -0.112424 0.602099 -0.547429 -0.003623 0.0 +-0.003662 -0.095452 0.598038 -0.548905 -0.000896 0.0 +-0.00051 -0.099256 0.602398 -0.545669 0.003247 0.0 +0.001891 -0.096909 0.599827 -0.54705 0.007883 0.0 +0.000474 -0.093016 0.600449 -0.544111 0.004193 0.0 +-0.002111 -0.101444 0.597207 -0.546588 0.004357 0.0 +-0.001128 -0.087514 0.60025 -0.543813 0.008235 0.0 +0.000878 -0.099905 0.598387 -0.544288 0.01045 0.0 +-0.003977 -0.112703 0.596795 -0.548266 0.001233 0.0 +-0.001844 -0.106205 0.605004 -0.546916 0.012647 0.0 +-0.006914 -0.093634 0.600461 -0.545134 0.000388 0.0 +-0.000576 -0.107722 0.602088 -0.543887 0.002475 0.0 +-0.005145 -0.086685 0.598845 -0.544284 0.00654 0.0 +0.000725 -0.10723 0.605403 -0.545535 0.011427 0.0 +-0.002177 -0.073057 0.59804 -0.545449 0.005207 0.0 +-0.003593 -0.094187 0.596597 -0.547093 0.005906 0.0 +0.001326 -0.10991 0.600798 -0.545224 -0.000361 0.0 +-0.00116 -0.093905 0.600013 -0.546976 0.004353 0.0 +0.000439 -0.101873 0.601195 -0.538842 0.008837 0.0 +0.003642 -0.096351 0.597207 -0.545354 0.013346 0.0 +-0.005178 -0.099535 0.600002 -0.543606 0.018206 0.0 +-0.002161 -0.107473 0.599467 -0.543131 0.004143 0.0 +0.004611 -0.100055 0.597195 -0.548072 0.014575 0.0 +-0.002527 -0.098848 0.600091 -0.547524 0.002438 0.0 +0.002427 -0.091348 0.603738 -0.544115 0.005906 0.0 +-0.005278 -0.086118 0.601739 -0.54506 0.000662 0.0 +0.001507 -0.102772 0.601801 -0.544141 0.009706 0.0 +-8.9e-05 -0.106909 0.599467 -0.545876 0.002749 0.0 +0.004076 -0.103919 0.598534 -0.544521 0.005801 0.0 +-0.002026 -0.110343 0.600707 -0.541682 0.006042 0.0 +-0.00221 -0.089023 0.60287 -0.544763 0.00237 0.0 +-0.002543 -0.097158 0.600792 -0.541483 0.005152 0.0 +-0.00526 -0.102254 0.600025 -0.545401 0.004288 0.0 +-0.006194 -0.107941 0.599554 -0.547421 0.003439 0.0 +-0.006162 -0.100274 0.600434 -0.544957 0.01431 0.0 +-0.002543 -0.108462 0.599293 -0.54046 0.013332 0.0 +-0.004609 -0.095861 0.602372 -0.544927 0.011254 0.0 +-0.003926 -0.10318 0.599839 -0.542804 0.007805 0.0 +-0.008113 -0.090724 0.598931 -0.54034 0.004512 0.0 +-0.002259 -0.104664 0.600943 -0.547701 -0.00127 0.0 +-0.003427 -0.093205 0.602149 -0.544081 0.008911 0.0 +0.000643 -0.105096 0.601902 -0.538648 0.00269 0.0 +-0.005992 -0.102251 0.600757 -0.547787 0.01024 0.0 +-0.006714 -0.102681 0.597641 -0.539049 0.014835 0.0 +-0.002511 -0.094563 0.602831 -0.542601 0.005841 0.0 +-0.002593 -0.099278 0.601801 -0.542286 0.009536 0.0 +-0.00446 -0.106947 0.597405 -0.548474 0.005207 0.0 +-0.000609 -0.093256 0.603228 -0.5427 0.010204 0.0 +-0.001357 -0.110069 0.598747 -0.547287 0.011807 0.0 +0.000124 -0.102002 0.601913 -0.543308 0.004326 0.0 +-0.004127 -0.11347 0.596969 -0.542027 0.018732 0.0 +-0.004327 -0.094343 0.600947 -0.545971 0.007436 0.0 +-0.00256 -0.10281 0.602175 -0.543071 -0.001965 0.0 +-0.008446 -0.098922 0.597405 -0.542079 0.003832 0.0 +0.000725 -0.108483 0.59994 -0.544007 0.007171 0.0 +0.002343 -0.09654 0.599467 -0.546709 0.00258 0.0 +0.000124 -0.107542 0.601987 -0.544883 0.005526 0.0 +-0.004462 -0.113464 0.597492 -0.54415 0.011314 0.0 +-0.003227 -0.095233 0.599876 -0.543308 0.010565 0.0 +0.00313 -0.096939 0.599506 -0.544452 0.01098 0.0 +0.002392 -0.085754 0.599951 -0.542972 0.00628 0.0 +-0.002175 -0.097777 0.599194 -0.545975 0.004887 0.0 +0.003627 -0.108495 0.601292 -0.542225 0.008682 0.0 +-0.003511 -0.097339 0.600647 -0.548443 0.005673 0.0 +-0.000293 -0.101641 0.597813 -0.547792 0.003462 0.0 +-0.004112 -0.107008 0.596349 -0.546704 0.002535 0.0 +-0.00491 -0.102848 0.594486 -0.545595 0.010181 0.0 +-0.003259 -0.10149 0.602163 -0.546113 0.001068 0.0 +-0.001075 -0.105625 0.598636 -0.544853 0.00205 0.0 +-0.005746 -0.106093 0.600013 -0.544763 0.001932 0.0 +-0.001179 -0.100795 0.598648 -0.544991 0.00681 0.0 +-0.003762 -0.109699 0.593854 -0.545561 0.004942 0.0 +-0.001909 -0.099165 0.60035 -0.547063 -0.00302 0.0 +-0.002991 -0.099135 0.598148 -0.54475 0.00617 0.0 +-0.00356 -0.093949 0.600013 -0.548547 0.008952 0.0 +-0.001762 -0.104478 0.60226 -0.543339 0.006175 0.0 +0.001691 -0.082701 0.597269 -0.545052 0.009427 0.0 +-0.003276 -0.103766 0.602622 -0.542864 0.004028 0.0 +-0.009097 -0.102095 0.60071 -0.542903 0.001375 0.0 +-0.007479 -0.100094 0.59974 -0.538441 0.017571 0.0 +-0.00151 -0.097498 0.599851 -0.548577 0.000534 0.0 +-0.002828 -0.098298 0.597306 -0.541613 0.006655 0.0 +-0.006495 -0.10241 0.600199 -0.54689 0.000662 0.0 +-0.003844 -0.111955 0.60117 -0.54708 0.003795 0.0 +-0.004194 -0.099754 0.602808 -0.542566 0.008582 0.0 +-0.000745 -0.098706 0.60123 -0.540163 0.012328 0.0 +-0.00526 -0.093172 0.600571 -0.548465 0.00232 0.0 +-0.002576 -0.093793 0.599031 -0.549781 -0.00165 0.0 +-0.004527 -0.111006 0.603889 -0.549721 0.009112 0.0 +-0.001226 -0.096811 0.60153 -0.543162 0.008075 0.0 +-0.004012 -0.094034 0.599345 -0.544051 0.003169 0.0 +-0.00039 -0.097868 0.598782 -0.546057 0.003334 0.0 +-7e-06 -0.081055 0.594101 -0.542256 0.012538 0.0 +-0.00843 -0.097558 0.598584 -0.543843 0.007956 0.0 +-0.003259 -0.099716 0.601489 -0.545431 0.013941 0.0 +-0.001926 -0.098237 0.600945 -0.547524 0.01019 0.0 +-0.003374 -0.097739 0.600025 -0.545759 0.005207 0.0 +-0.000226 -0.106764 0.597009 -0.545358 0.005421 0.0 +-0.003128 -0.106145 0.609478 -0.54506 -0.000466 0.0 +0.002141 -0.091739 0.604371 -0.547347 0.00617 0.0 +-0.006577 -0.106674 0.600188 -0.548133 0.006124 0.0 +-0.00603 -0.0962 0.597939 -0.543606 -0.000791 0.0 +-0.002276 -0.097936 0.601332 -0.54424 0.013506 0.0 +-0.00286 -0.099226 0.601553 -0.542079 0.015639 0.0 +-0.00421 -0.104228 0.603616 -0.552495 0.003553 0.0 +-0.006211 -0.102282 0.60164 -0.548741 0.001351 0.0 +-0.000925 -0.094491 0.599479 -0.544141 0.012702 0.0 +-0.003145 -0.090417 0.601975 -0.545906 0.008578 0.0 +-0.00409 -0.092418 0.600275 -0.549751 0.002713 0.0 +-0.001159 -0.10433 0.60035 -0.547192 0.004298 0.0 +-0.00556 -0.092736 0.598933 -0.547347 0.00786 0.0 +0.001594 -0.104636 0.603976 -0.546976 0.00258 0.0 +0.003094 -0.090784 0.597939 -0.544081 0.006924 0.0 +-0.004413 -0.097859 0.597492 -0.545733 0.012323 0.0 +-0.001827 -0.107413 0.600149 -0.544456 0.00269 0.0 +-0.005397 -0.099037 0.607078 -0.546782 0.012597 0.0 +-0.003112 -0.107194 0.605516 -0.54563 0.015319 0.0 +-0.002928 -0.093815 0.598073 -0.546264 0.001091 0.0 +-0.002379 -0.099749 0.59805 -0.539835 0.012734 0.0 +-0.000893 -0.098018 0.603615 -0.547244 0.01024 0.0 +-0.004358 -0.10287 0.598834 -0.543334 -0.006089 0.0 +-0.007063 -0.088103 0.598584 -0.541556 0.015484 0.0 +-0.000527 -0.089801 0.60225 -0.540564 0.000872 0.0 +-0.004844 -0.113418 0.601727 -0.544849 0.009172 0.0 +0.001026 -0.10519 0.597317 -0.548814 0.008879 0.0 +-0.000594 -0.107347 0.597467 -0.544314 0.001406 0.0 +-0.000543 -0.089245 0.6039 -0.543274 0.006723 0.0 +-0.00291 -0.105677 0.60354 -0.540469 0.013671 0.0 +-0.004828 -0.108681 0.601902 -0.543369 0.014246 0.0 +-0.000494 -0.099187 0.597006 -0.547999 0.004772 0.0 +-0.005794 -0.105866 0.602372 -0.540905 0.015411 0.0 +-0.002893 -0.089672 0.599926 -0.543175 0.013232 0.0 +-0.002707 -0.107013 0.605341 -0.548577 -0.000252 0.0 +-0.005194 -0.102599 0.599676 -0.54604 0.007166 0.0 +0.004575 -0.103675 0.599665 -0.544038 0.006225 0.0 +-0.003877 -0.095513 0.598921 -0.542152 0.004672 0.0 +0.000474 -0.098117 0.603478 -0.540504 0.010044 0.0 +-0.001324 -0.094434 0.600048 -0.541315 0.005312 0.0 +-0.000745 -0.105775 0.604931 -0.542963 0.013552 0.0 +-0.005364 -0.101082 0.600968 -0.551416 0.004243 0.0 +-0.002429 -0.100395 0.601106 -0.546234 0.008153 0.0 +-0.006096 -0.092019 0.599479 -0.548776 0.004407 0.0 +-0.004795 -0.083092 0.601094 -0.542553 0.015594 0.0 +-0.00751 -0.100923 0.596847 -0.544853 0.007856 0.0 +-0.001893 -0.105014 0.602944 -0.544922 -0.002823 0.0 +-0.002144 -0.106578 0.595383 -0.549203 0.008902 0.0 +0.000259 -0.099437 0.600211 -0.546976 0.009486 0.0 +0.00201 -0.095551 0.603552 -0.543533 0.006011 0.0 +0.00239 -0.11109 0.600807 -0.545729 0.009277 0.0 +-0.001993 -0.098788 0.60709 -0.547494 0.007394 0.0 +-0.002578 -0.092449 0.601257 -0.541958 0.010935 0.0 +-0.002893 -0.101602 0.603476 -0.543446 0.009696 0.0 +-0.003112 -0.104324 0.605215 -0.543037 0.007404 0.0 +-0.004112 -0.094371 0.601803 -0.543377 0.011414 0.0 +-0.003777 -0.101972 0.605887 -0.539257 0.007655 0.0 +-0.002727 -0.098577 0.596934 -0.541142 0.007148 0.0 +0.004475 -0.104541 0.598224 -0.548163 0.0048 0.0 +-0.003642 -0.100003 0.599293 -0.544728 0.009651 0.0 +-0.009348 -0.104039 0.600647 -0.548638 -0.000946 0.0 +-0.001707 -0.093325 0.597988 -0.542458 0.012483 0.0 +-0.003779 -0.094494 0.606161 -0.544215 0.004453 0.0 +-0.007996 -0.096657 0.597753 -0.546407 0.005636 0.0 +-0.000876 -0.088842 0.597654 -0.539658 0.011537 0.0 +0.004161 -0.112454 0.60354 -0.545535 0.009751 0.0 +-0.004495 -0.10247 0.599926 -0.547434 0.005047 0.0 +-0.006597 -0.104168 0.596574 -0.540767 0.013936 0.0 +-0.00673 -0.097868 0.597255 -0.544931 0.004567 0.0 +-0.001161 -0.097068 0.602883 -0.544098 -9.2e-05 0.0 +-0.003243 -0.085809 0.600211 -0.540771 0.010395 0.0 +-0.004095 -0.108377 0.602297 -0.544305 0.015319 0.0 +-0.004828 -0.095666 0.600972 -0.545427 0.010081 0.0 +0.000424 -0.11959 0.601082 -0.546678 0.013926 0.0 +-0.001527 -0.103661 0.598857 -0.541509 0.009441 0.0 +-0.000909 -0.089492 0.600151 -0.541241 0.010395 0.0 +-0.004012 -0.106668 0.603912 -0.543947 0.000922 0.0 +-0.003292 -0.088254 0.600821 -0.542018 0.009052 0.0 +-0.002527 -0.095006 0.602918 -0.540305 0.019179 0.0 +-0.001592 -0.088446 0.600002 -0.540875 0.00412 0.0 +-0.005431 -0.108996 0.600152 -0.540322 0.010505 0.0 +-0.004347 -0.091625 0.597666 -0.541617 0.013739 0.0 +9e-06 -0.095762 0.600374 -0.543813 0.007582 0.0 +-0.002811 -0.109083 0.601739 -0.540845 0.006723 0.0 +0.002611 -0.100274 0.601902 -0.546307 0.007362 0.0 +-0.00456 -0.07689 0.597591 -0.544025 0.020138 0.0 +0.000357 -0.107632 0.602397 -0.542864 -0.000896 0.0 +-0.001243 -0.086586 0.595904 -0.538989 0.01863 0.0 +-0.004194 -0.100335 0.601689 -0.548072 0.013575 0.0 +0.000357 -0.10284 0.604359 -0.542596 0.005293 0.0 +0.000173 -0.087257 0.60133 -0.540693 0.019567 0.0 +-0.00656 -0.120173 0.600472 -0.544314 0.012438 0.0 +-0.004112 -0.09672 0.601948 -0.544586 0.009761 0.0 +-0.002658 -0.098599 0.601727 -0.543412 0.014059 0.0 +-0.004677 -0.120608 0.604261 -0.545872 0.003604 0.0 +-0.004861 -0.085379 0.60071 -0.541444 0.004206 0.0 +-7e-06 -0.096699 0.603069 -0.545975 0.000183 0.0 +-0.005462 -0.097051 0.598822 -0.541345 0.015617 0.0 +-0.000942 -0.114513 0.601698 -0.545328 0.00231 0.0 +-0.00321 -0.103369 0.601355 -0.54607 0.008527 0.0 +-0.002827 -0.10807 0.602372 -0.544076 -0.003308 0.0 +-0.00151 -0.103459 0.601989 -0.542687 0.00205 0.0 +-0.003343 -0.102678 0.598485 -0.542929 0.012583 0.0 +-0.002942 -0.102492 0.604671 -0.54232 0.017347 0.0 +-0.000275 -0.094182 0.600596 -0.544689 0.01056 0.0 +-0.00166 -0.086427 0.599653 -0.541923 0.008454 0.0 +-0.003161 -0.100274 0.601739 -0.541138 0.019015 0.0 +-0.003358 -0.102969 0.602808 -0.544853 0.006385 0.0 +0.000126 -0.093037 0.604235 -0.545267 0.009217 0.0 +-0.002061 -0.114581 0.599467 -0.551576 0.004942 0.0 +0.001075 -0.095195 0.596004 -0.540435 0.011044 0.0 +0.000107 -0.101948 0.602012 -0.541742 0.012812 0.0 +0.002409 -0.101791 0.601627 -0.543977 0.006815 0.0 +0.00021 -0.104138 0.604036 -0.54865 0.004992 0.0 +-0.00276 -0.111547 0.600147 -0.543205 0.008824 0.0 +-0.002478 -0.132193 0.600149 -0.547287 0.010788 0.0 +0.000825 -0.105806 0.60313 -0.548638 0.003119 0.0 +-5.8e-05 -0.098725 0.602088 -0.538769 0.011679 0.0 +-0.006747 -0.113284 0.602349 -0.549393 0.010916 0.0 +-0.002543 -0.10382 0.602396 -0.545254 0.009377 0.0 +-0.007045 -0.113092 0.604046 -0.538778 0.01051 0.0 +-0.003008 -0.086307 0.601466 -0.546351 0.004138 0.0 +0.001507 -0.09151 0.601937 -0.543649 -1.9e-05 0.0 +-0.000324 -0.114732 0.598375 -0.541884 0.003918 0.0 +-0.001259 -0.101942 0.601007 -0.544288 0.004937 0.0 +-0.00104 -0.117462 0.604348 -0.546575 0.003229 0.0 +-0.007528 -0.096323 0.603939 -0.543705 0.010961 0.0 +-0.003675 -0.112974 0.603255 -0.546834 0.007828 0.0 +0.002042 -0.111345 0.593593 -0.541647 0.007619 0.0 +-0.003593 -0.104228 0.599403 -0.540711 0.010665 0.0 +0.001576 -0.107933 0.602808 -0.544689 0.0142 0.0 +-0.003128 -0.10784 0.601193 -0.540296 0.010081 0.0 +-0.002625 -0.096879 0.605589 -0.5427 0.011665 0.0 +-0.005245 -0.089719 0.600682 -0.541686 0.013556 0.0 +-0.006178 -0.111277 0.600197 -0.542627 0.007335 0.0 +0.002556 -0.095431 0.599417 -0.54509 0.010345 0.0 +-0.002226 -0.106093 0.600062 -0.544827 0.005796 0.0 +-0.004629 -0.100872 0.602361 -0.541988 0.00353 0.0 +-0.002942 -0.100915 0.59723 -0.547999 0.00644 0.0 +0.001507 -0.085379 0.603615 -0.543472 0.016539 0.0 +-0.00818 -0.099959 0.602882 -0.547731 0.006815 0.0 +-0.007064 -0.1058 0.598189 -0.546204 0.007888 0.0 +-0.003576 -0.103141 0.59707 -0.540327 0.003923 0.0 +-0.006861 -0.104018 0.595568 -0.545997 0.007024 0.0 +-0.004697 -0.092126 0.601266 -0.546143 0.011793 0.0 +-0.001529 -0.094352 0.597829 -0.546946 0.002795 0.0 +-0.002592 -0.105814 0.598264 -0.541172 0.008915 0.0 +-0.003544 -0.110069 0.60189 -0.545164 0.008518 0.0 +-0.005943 -0.091655 0.603377 -0.544616 0.00106 0.0 +-0.004795 -0.105647 0.602309 -0.548253 0.001283 0.0 +-0.002543 -0.096063 0.605144 -0.547144 0.005805 0.0 +-0.006845 -0.096591 0.604809 -0.546143 0.004832 0.0 +-0.006194 -0.099037 0.598584 -0.54371 -0.003518 0.0 +-0.002844 -0.112914 0.603343 -0.536452 0.008404 0.0 +-0.002543 -0.087876 0.600223 -0.547731 -0.004536 0.0 +-0.003893 -0.108506 0.601478 -0.539524 0.007645 0.0 +-0.005577 -0.101271 0.598933 -0.541988 0.004293 0.0 +-0.005112 -0.096849 0.60261 -0.546924 0.009811 0.0 +-0.002511 -0.087643 0.601838 -0.543235 0.012378 0.0 +-0.000559 -0.102711 0.597989 -0.537699 0.008577 0.0 +-0.007162 -0.120732 0.599914 -0.546782 0.015406 0.0 +0.001343 -0.100055 0.600286 -0.542519 0.00956 0.0 +0.001758 -0.091649 0.598026 -0.548564 0.003129 0.0 +-0.007914 -0.108771 0.599903 -0.547317 0.004777 0.0 +-0.001727 -0.088125 0.602583 -0.540771 0.006874 0.0 +-0.003576 -0.082254 0.600412 -0.546722 0.011793 0.0 +-0.000625 -0.10327 0.598373 -0.543805 0.009779 0.0 +-0.007096 -0.095354 0.597928 -0.543947 0.011912 0.0 +-0.004112 -0.099971 0.601489 -0.544646 0.013182 0.0 +-0.008816 -0.095647 0.598735 -0.546381 0.005791 0.0 +-0.000925 -0.104417 0.603006 -0.543874 0.014739 0.0 +-0.000876 -0.097158 0.59866 -0.543032 0.021377 0.0 +-0.006896 -0.081458 0.596575 -0.545323 0.015909 0.0 +-0.004495 -0.097654 0.597951 -0.547006 0.011739 0.0 +-0.000625 -0.108982 0.603104 -0.545699 0.008098 0.0 +-0.004763 -0.101542 0.592524 -0.54276 0.012542 0.0 +-0.007894 -0.091838 0.602698 -0.545535 0.004727 0.0 +-0.005227 -0.104409 0.60282 -0.54632 0.004316 0.0 +-0.005112 -0.09149 0.60092 -0.544076 0.009971 0.0 +-0.004108 -0.080779 0.601803 -0.543602 0.013346 0.0 +-0.003161 -0.098451 0.60092 -0.543736 0.019289 0.0 +-0.00538 -0.105685 0.599134 -0.541518 0.000762 0.0 +-0.000494 -0.109979 0.599095 -0.543649 0.01067 0.0 +-0.00356 -0.105414 0.600807 -0.545436 0.002708 0.0 +-0.004427 -0.113481 0.59676 -0.546622 0.003937 0.0 +-0.003462 -0.091627 0.605428 -0.544081 0.010432 0.0 +-0.003292 -0.093205 0.604086 -0.543472 0.004549 0.0 +-0.00573 -0.089062 0.599403 -0.545431 -0.000202 0.0 +-0.004462 -0.115329 0.599915 -0.544348 0.011144 0.0 +-0.008446 -0.099817 0.600722 -0.543101 0.01067 0.0 +-0.004194 -0.108779 0.600757 -0.544046 0.003119 0.0 +-0.001374 -0.098013 0.605577 -0.539628 0.001626 0.0 +-0.002795 -0.086778 0.598003 -0.54169 0.008376 0.0 +0.000107 -0.091589 0.603552 -0.544072 -0.000613 0.0 +0.002693 -0.099595 0.598363 -0.542627 0.006422 0.0 +-0.002928 -0.089861 0.599764 -0.542821 0.009441 0.0 +0.002675 -0.101422 0.597604 -0.541846 0.005906 0.0 +-0.003742 -0.075154 0.598882 -0.541781 0.007673 0.0 +-0.002292 -0.101887 0.599864 -0.546989 0.004293 0.0 +-0.001007 -0.098388 0.602669 -0.549186 0.00232 0.0 +0.000293 -0.099478 0.599589 -0.543705 0.003064 0.0 +-0.002361 -0.107112 0.601379 -0.546135 0.00358 0.0 +0.001441 -0.10361 0.599144 -0.542152 0.000913 0.0 +-0.001357 -0.104237 0.598909 -0.546584 0.00507 0.0 +-0.004112 -0.104417 0.601255 -0.545876 0.005316 0.0 +-0.002396 -0.108383 0.598571 -0.543205 0.011199 0.0 +-0.007293 -0.103982 0.602818 -0.546113 0.014885 0.0 +-0.003343 -0.098851 0.599132 -0.545699 0.008258 0.0 +-0.001893 -0.096682 0.59974 -0.540223 0.009546 0.0 +-0.002445 -0.087419 0.599566 -0.544124 0.007828 0.0 +-0.00643 -0.117895 0.601541 -0.545358 0.001082 0.0 +-0.008414 -0.105157 0.598545 -0.542553 0.002247 0.0 +0.003308 -0.094932 0.596487 -0.54276 0.010674 0.0 +-0.007462 -0.088941 0.601826 -0.547395 0.010076 0.0 +-0.001343 -0.102629 0.599392 -0.545595 0.006865 0.0 +-0.005845 -0.098826 0.599914 -0.546247 0.000392 0.0 +-0.001226 -0.101512 0.602273 -0.542687 0.005471 0.0 +-0.003276 -0.096071 0.601838 -0.54377 0.004562 0.0 +-0.003227 -0.098139 0.600075 -0.541647 0.012606 0.0 +0.000375 -0.115792 0.599093 -0.546678 0.002425 0.0 +-0.002194 -0.102681 0.600135 -0.546812 0.005033 0.0 +-0.005881 -0.099196 0.602051 -0.545263 0.007504 0.0 +-0.005861 -0.103894 0.598735 -0.545427 0.004448 0.0 +-0.000592 -0.084382 0.601704 -0.539093 0.015269 0.0 +-0.002625 -0.087854 0.602299 -0.541513 0.015854 0.0 +-0.002644 -0.09611 0.595568 -0.543649 0.011852 0.0 +-0.005845 -0.098878 0.595727 -0.545639 0.012113 0.0 +-0.002478 -0.094026 0.598648 -0.544452 0.007559 0.0 +-0.008096 -0.100713 0.598723 -0.544823 0.011684 0.0 +-0.006113 -0.100094 0.602982 -0.543131 0.008837 0.0 +-0.004662 -0.08511 0.599554 -0.538812 0.012049 0.0 +-0.005063 -0.104667 0.598807 -0.54402 0.00628 0.0 +-0.00286 -0.098303 0.600957 -0.546622 0.006558 0.0 +-0.01088 -0.09623 0.599467 -0.543951 0.002941 0.0 +-0.007162 -0.098727 0.599467 -0.546855 0.013926 0.0 +-0.002576 -0.115419 0.599566 -0.543101 0.007719 0.0 +-0.003877 -0.100674 0.598189 -0.546514 0.01035 0.0 +-0.001625 -0.111559 0.598386 -0.542834 0.008418 0.0 +-0.001077 -0.081833 0.599529 -0.549479 0.01014 0.0 +-0.003394 -0.093388 0.596845 -0.543679 0.004887 0.0 +-0.004263 -0.101353 0.598549 -0.541276 0.009541 0.0 +-0.00573 -0.100677 0.599616 -0.541099 0.015529 0.0 +-0.003626 -0.089368 0.600362 -0.545431 0.012757 0.0 +-0.005413 -0.0914 0.602041 -0.544888 0.00665 0.0 +-0.002926 -0.100773 0.600734 -0.541112 0.010889 0.0 +-0.008332 -0.093467 0.602448 -0.545846 0.004033 0.0 +-0.007446 -0.110378 0.600995 -0.545936 0.013552 0.0 +-2.4e-05 -0.106235 0.604261 -0.540944 0.015589 0.0 +-0.004894 -0.095888 0.603244 -0.544788 0.007833 0.0 +0.002409 -0.095543 0.603244 -0.538277 0.004727 0.0 +-0.004544 -0.121161 0.600559 -0.544866 0.003064 0.0 +0.001541 -0.10643 0.598572 -0.550182 0.002585 0.0 +-0.002008 -0.103374 0.600112 -0.541172 0.013506 0.0 +-0.005495 -0.107752 0.599568 -0.543606 0.005586 0.0 +-0.006577 -0.116838 0.599717 -0.544288 0.012008 0.0 +-0.001177 -0.119283 0.603602 -0.544245 0.011629 0.0 +-0.004593 -0.108771 0.598522 -0.548059 0.00665 0.0 +-0.002642 -0.102884 0.599676 -0.543339 0.009381 0.0 +-0.001478 -0.107013 0.598944 -0.53804 0.006815 0.0 +-0.001909 -0.114491 0.600672 -0.545708 0.000497 0.0 +-0.002777 -0.096321 0.604894 -0.545 0.011474 0.0 +-0.003276 -0.095642 0.59974 -0.542778 -0.002344 0.0 +-0.003061 -0.097588 0.605591 -0.540206 0.006262 0.0 +-0.006747 -0.100373 0.598946 -0.541587 0.017092 0.0 +0.000758 -0.101293 0.60124 -0.544245 0.00612 0.0 +-0.001494 -0.093632 0.59533 -0.541418 0.008048 0.0 +0.000189 -0.103339 0.597937 -0.542907 0.007874 0.0 +-0.002893 -0.104511 0.601007 -0.547244 0.010295 0.0 +-0.003844 -0.099905 0.598857 -0.545837 0.004343 0.0 +-0.002926 -0.099445 0.601442 -0.545639 0.015009 0.0 +-0.003893 -0.103481 0.598299 -0.541915 0.010437 0.0 +-0.003075 -0.090628 0.597345 -0.543131 0.009971 0.0 +-0.004278 -0.092887 0.601367 -0.538678 0.01996 0.0 +-0.003462 -0.08387 0.60313 -0.545298 0.002265 0.0 +-0.006796 -0.099094 0.599764 -0.547252 0.001721 0.0 +-0.00538 -0.11526 0.599415 -0.548098 0.009217 0.0 +-0.002079 -0.095647 0.598497 -0.545759 0.000306 0.0 +-0.006626 -0.105195 0.600197 -0.547317 0.021723 0.0 +-0.004128 -0.102002 0.599827 -0.543339 0.005626 0.0 +-0.003758 -0.092706 0.601466 -0.54506 0.011419 0.0 +-0.00473 -0.10284 0.604545 -0.546605 0.010884 0.0 +-0.001693 -0.108711 0.597904 -0.543235 0.003818 0.0 +-0.007994 -0.09088 0.599368 -0.544392 0.006755 0.0 +0.000239 -0.099135 0.600858 -0.54796 0.003169 0.0 +-0.00538 -0.078429 0.601727 -0.547019 0.004083 0.0 +0.004677 -0.107972 0.598387 -0.546877 0.005801 0.0 +-0.001691 -0.103766 0.601687 -0.542596 0.006312 0.0 +-0.001275 -0.112235 0.598538 -0.543468 0.01209 0.0 +-0.00021 -0.101014 0.603877 -0.537669 0.007856 0.0 +0.000709 -0.101362 0.600188 -0.544111 0.003941 0.0 +-0.004976 -0.111734 0.602198 -0.548776 0.004764 0.0 +-0.002543 -0.103248 0.601266 -0.548443 0.01886 0.0 +0.001277 -0.094305 0.598224 -0.541077 0.005659 0.0 +0.000541 -0.090948 0.594908 -0.542493 0.005471 0.0 +-0.001111 -0.094932 0.600385 -0.545401 0.009746 0.0 +-0.001161 -0.08304 0.595743 -0.549116 0.000328 0.0 +0.001376 -0.098645 0.599754 -0.538644 0.006111 0.0 +-0.003613 -0.095882 0.599033 -0.548128 0.011775 0.0 +-0.000226 -0.106575 0.602721 -0.541884 0.011624 0.0 +-0.00286 -0.093536 0.60379 -0.545298 0.00285 0.0 +0.000909 -0.102284 0.598846 -0.54276 0.009756 0.0 +-0.000576 -0.097468 0.59963 -0.544525 0.004298 0.0 +0.002042 -0.111739 0.600002 -0.545194 0.007728 0.0 +0.001476 -0.112914 0.603726 -0.542631 0.013871 0.0 +-0.000428 -0.109113 0.600164 -0.540974 0.008477 0.0 +-0.001592 -0.089248 0.601367 -0.539524 0.008216 0.0 +-0.00016 -0.09106 0.604745 -0.546441 0.007509 0.0 +-0.003227 -0.109817 0.597904 -0.539701 0.015219 0.0 +-0.00356 -0.102287 0.606695 -0.541988 0.009272 0.0 +-0.00473 -0.09666 0.599676 -0.540132 0.008007 0.0 +-0.002678 -0.085688 0.60379 -0.54645 0.007189 0.0 +-0.006779 -0.085601 0.6039 -0.546678 0.009432 0.0 +-0.00286 -0.108968 0.605068 -0.542596 0.00913 0.0 +-0.004128 -0.103924 0.601714 -0.545595 0.007134 0.0 +-0.003511 -0.090652 0.600856 -0.544927 0.004421 0.0 +0.001627 -0.110778 0.603939 -0.545664 0.018535 0.0 +-0.006761 -0.09008 0.599816 -0.545565 0.002955 0.0 +0.000643 -0.097588 0.602831 -0.5461 0.004782 0.0 +0.001693 -0.109735 0.600385 -0.539153 0.003973 0.0 +-0.000259 -0.091994 0.599281 -0.544542 0.007207 0.0 +0.005961 -0.10238 0.60102 -0.541988 0.019572 0.0 +-0.004409 -0.106296 0.600498 -0.550325 0.001297 0.0 +-0.004478 -0.092895 0.601925 -0.545328 0.002205 0.0 +0.003292 -0.101052 0.596783 -0.545134 0.001351 0.0 +0.001693 -0.121627 0.599851 -0.544832 0.012383 0.0 +-0.001926 -0.111975 0.599914 -0.542967 0.013616 0.0 +0.000658 -0.097985 0.600236 -0.544392 0.001091 0.0 +-0.000408 -0.105096 0.601268 -0.542924 0.0029 0.0 +0.000873 -0.100551 0.603093 -0.545556 0.015031 0.0 +-0.003861 -0.090261 0.600275 -0.549082 0.008111 0.0 +-0.002893 -0.095576 0.599641 -0.541319 0.011684 0.0 +-0.00251 -0.110039 0.59841 -0.547183 0.005485 0.0 +-0.00341 -0.096655 0.597219 -0.547287 0.008732 0.0 +-0.001226 -0.101822 0.598574 -0.543576 -0.000676 0.0 +-0.006162 -0.104595 0.599591 -0.547731 0.008436 0.0 +0.001793 -0.110748 0.602273 -0.540978 0.013986 0.0 +-0.00041 -0.099935 0.59748 -0.539287 0.011437 0.0 +0.002026 -0.110778 0.602707 -0.543546 0.005138 0.0 +-0.000958 -0.10841 0.599217 -0.53936 0.012679 0.0 +-0.000355 -0.104667 0.600771 -0.542497 0.003708 0.0 +-0.000909 -0.100132 0.600734 -0.54453 0.012273 0.0 +-0.007594 -0.097988 0.6016 -0.540098 0.011364 0.0 +7.5e-05 -0.11 0.598636 -0.539183 0.013991 0.0 +7.5e-05 -0.099527 0.600397 -0.540771 0.011364 0.0 +-0.006315 -0.10281 0.603056 -0.543265 0.009007 0.0 +-0.002574 -0.115261 0.602523 -0.54708 0.001941 0.0 +0.002157 -0.105096 0.600013 -0.545669 0.008116 0.0 +-0.004462 -0.107104 0.601665 -0.538173 0.011948 0.0 +-0.001576 -0.095702 0.600747 -0.538916 0.016791 0.0 +-0.001993 -0.103574 0.604307 -0.545134 0.015069 0.0 +-0.001392 -0.089125 0.603342 -0.54591 0.005545 0.0 +-0.003243 -0.102101 0.600647 -0.546342 0.008719 0.0 +-0.002795 -0.093571 0.601925 -0.543442 0.007221 0.0 +-0.001024 -0.097747 0.599938 -0.542804 -0.002719 0.0 +-0.004331 -0.097501 0.605653 -0.54383 0.004567 0.0 +-0.004495 -0.10235 0.601553 -0.549859 0.001959 0.0 +-0.004511 -0.090759 0.599911 -0.541086 0.008318 0.0 +-0.004423 -0.099965 0.598933 -0.548771 0.004453 0.0 +-0.003429 -0.10321 0.598921 -0.542048 -0.000348 0.0 +-0.002811 -0.088059 0.599467 -0.544555 0.009893 0.0 +-0.005646 -0.118388 0.603989 -0.544525 0.010665 0.0 +-0.001478 -0.112626 0.59938 -0.538588 0.008043 0.0 +-0.002061 -0.104297 0.600682 -0.541276 0.008148 0.0 +0.000574 -0.100431 0.597108 -0.54333 0.006116 0.0 +-0.001778 -0.091288 0.598758 -0.54412 0.012309 0.0 +-0.005276 -0.105556 0.601168 -0.543041 0.00623 0.0 +-0.003627 -0.103141 0.60282 -0.542079 0.008318 0.0 +-0.006129 -0.089894 0.59974 -0.538648 0.013886 0.0 +-0.004194 -0.094902 0.600745 -0.542696 0.011259 0.0 +-0.000275 -0.107446 0.601902 -0.542834 0.012017 0.0 +-0.003008 -0.114803 0.603378 -0.542092 0.010583 0.0 +-0.002325 -0.100795 0.599231 -0.543783 0.010167 0.0 +-0.003893 -0.086526 0.602535 -0.545462 0.012907 0.0 +-0.004161 -0.099779 0.599827 -0.54374 0.012624 0.0 +-0.003194 -0.099316 0.600025 -0.545876 0.004937 0.0 +-0.005096 -0.089423 0.60103 -0.546471 0.006865 0.0 +-0.001541 -0.100395 0.597829 -0.544754 0.007404 0.0 +-0.003611 -0.099782 0.601937 -0.541716 0.009701 0.0 +-0.003877 -0.084883 0.602732 -0.544141 0.01463 0.0 +-0.008831 -0.091471 0.599107 -0.546924 0.013301 0.0 +-0.003077 -0.092164 0.597927 -0.546174 0.006116 0.0 +-0.003626 -0.105247 0.601082 -0.54245 0.011994 0.0 +-0.001226 -0.107435 0.599182 -0.544927 0.004531 0.0 +-0.006397 -0.096811 0.602622 -0.543308 0.014835 0.0 +-0.001161 -0.093853 0.604049 -0.545634 0.004586 0.0 +-0.00608 -0.105504 0.598607 -0.545462 0.016558 0.0 +-0.008747 -0.09672 0.600472 -0.542225 0.007367 0.0 +0.001611 -0.099782 0.599678 -0.544512 0.004407 0.0 +-0.003746 -0.108462 0.602087 -0.53942 0.009578 0.0 +-0.004194 -0.090417 0.597577 -0.541323 0.01035 0.0 +0.00139 -0.090789 0.600112 -0.547019 0.002046 0.0 +-0.005894 -0.101731 0.602012 -0.544288 0.008696 0.0 +-0.002926 -0.098766 0.596672 -0.543442 0.011364 0.0 +-0.004744 -0.095981 0.601355 -0.543546 0.016128 0.0 +-0.00556 -0.105956 0.598164 -0.540668 0.014945 0.0 +-0.003943 -0.104259 0.600943 -0.54632 0.004562 0.0 +-0.004763 -0.110417 0.597614 -0.545496 0.001566 0.0 +-0.001926 -0.08927 0.598398 -0.540133 0.009774 0.0 +-0.007012 -0.10108 0.599467 -0.541988 0.012977 0.0 +-0.001559 -0.090858 0.600385 -0.547347 0.005841 0.0 +-0.004178 -0.073848 0.598375 -0.542329 0.004407 0.0 +-0.005976 -0.084111 0.600821 -0.542329 0.001118 0.0 +-0.005176 -0.096 0.599754 -0.541423 0.013077 0.0 +-0.00691 -0.113013 0.596411 -0.540978 0.012643 0.0 +0.001359 -0.089461 0.602198 -0.542161 0.012762 0.0 +-0.001077 -0.093536 0.600211 -0.543041 0.012383 0.0 +-0.000111 -0.099694 0.598061 -0.546851 0.009669 0.0 +-0.001292 -0.106247 0.602744 -0.54371 0.009633 0.0 +-0.002177 -0.093596 0.59805 -0.544927 0.01457 0.0 +0.000177 -0.103248 0.601255 -0.540193 0.006677 0.0 +-0.00031 -0.088654 0.599764 -0.542329 0.013342 0.0 +-0.003259 -0.097616 0.604272 -0.548702 0.007993 0.0 +-0.004877 -0.106665 0.600499 -0.545867 0.00205 0.0 +-0.002063 -0.097739 0.601303 -0.541039 0.006865 0.0 +-0.003979 -0.096918 0.600844 -0.546812 0.00511 0.0 +0.001394 -0.098596 0.59938 -0.544215 0.014041 0.0 +-0.002275 -0.083678 0.599754 -0.543205 -0.004217 0.0 +0.001742 -0.117238 0.601466 -0.542627 0.013497 0.0 +0.000124 -0.114831 0.600162 -0.546502 0.00617 0.0 +-0.004376 -0.106953 0.600025 -0.543382 0.001781 0.0 +0.000676 -0.101882 0.602645 -0.54289 0.014465 0.0 +-0.00061 -0.099656 0.600101 -0.546484 0.009583 0.0 +-0.002144 -0.099256 0.600794 -0.542523 0.002548 0.0 +-0.002144 -0.104039 0.600188 -0.543843 0.013182 0.0 +-0.010447 -0.100455 0.601565 -0.543446 0.009368 0.0 +-0.00386 -0.101268 0.6 -0.543576 0.004882 0.0 +0.001576 -0.104499 0.601106 -0.540879 0.007669 0.0 +0.000539 -0.100305 0.600821 -0.542048 0.001511 0.0 +-0.000177 -0.108125 0.602111 -0.541759 0.009172 0.0 +-0.000727 -0.104387 0.600809 -0.544853 0.003169 0.0 +-0.001745 -0.089678 0.599566 -0.544927 0.004727 0.0 +-0.000991 -0.107164 0.600548 -0.546281 -0.00058 0.0 +0.00061 -0.110688 0.599316 -0.543977 -0.002124 0.0 +-0.00256 -0.096849 0.602971 -0.54648 0.002438 0.0 +0.003726 -0.099385 0.601803 -0.536957 0.006974 0.0 +0.002893 -0.093514 0.597267 -0.539761 0.018316 0.0 +0.002343 -0.093695 0.597767 -0.541302 0.003494 0.0 +-0.003144 -0.086466 0.60411 -0.543874 0.011199 0.0 +-0.00211 -0.100743 0.603465 -0.542967 0.003708 0.0 +-0.007861 -0.103248 0.598846 -0.544348 0.00406 0.0 +-0.004777 -0.087553 0.598549 -0.54374 0.009162 0.0 +-0.002828 -0.099097 0.599554 -0.539321 0.004586 0.0 +0.000942 -0.095023 0.602186 -0.543472 0.013017 0.0 +-0.002259 -0.089732 0.604075 -0.543438 0.003818 0.0 +-0.003194 -0.095521 0.598671 -0.544413 0.008111 0.0 +0.000244 -0.101148 0.602436 -0.544228 0.008116 0.0 +-0.000124 -0.106789 0.601541 -0.54163 0.01521 0.0 +-0.002195 -0.107103 0.601838 -0.545134 0.003284 0.0 +-0.001226 -0.113503 0.603342 -0.541513 0.010455 0.0 +0.002775 -0.092057 0.600484 -0.541483 0.016461 0.0 +-0.007697 -0.107941 0.600658 -0.541992 0.014251 0.0 +-0.006194 -0.107323 0.600931 -0.537474 0.013451 0.0 +-0.00291 -0.107662 0.598201 -0.545699 0.015822 0.0 +-0.003811 -0.094215 0.598485 -0.545729 0.004206 0.0 +-0.003462 -0.096468 0.601466 -0.544784 0.013246 0.0 +-0.004812 -0.103588 0.600013 -0.544616 0.010505 0.0 +-0.002259 -0.093076 0.601778 -0.538575 0.006495 0.0 +-0.000793 -0.102999 0.599181 -0.547598 0.007878 0.0 +-0.003194 -0.099437 0.595743 -0.541453 0.012213 0.0 +-0.004828 -0.099127 0.60092 -0.546614 0.013022 0.0 +-0.002674 -0.10315 0.596324 -0.549794 0.011843 0.0 +0.001523 -0.09746 0.596498 -0.546407 0.000712 0.0 +-0.003959 -0.100053 0.597271 -0.54267 0.01225 0.0 +-0.000494 -0.113812 0.600548 -0.543308 0.010236 0.0 +-0.004446 -0.109119 0.600658 -0.539925 0.008989 0.0 +-0.002494 -0.091846 0.598038 -0.538204 0.014096 0.0 +-0.004762 -0.098757 0.599554 -0.539925 0.008399 0.0 +-0.000144 -0.10951 0.597207 -0.549897 0.00231 0.0 +-0.003944 -0.106307 0.599851 -0.544655 0.002567 0.0 +-0.001177 -0.100833 0.598224 -0.543205 0.005047 0.0 +-0.001926 -0.091808 0.599217 -0.542195 0.013721 0.0 +-0.000291 -0.094374 0.599938 -0.544288 0.004869 0.0 +-0.002642 -0.101792 0.599978 -0.549617 0.011359 0.0 +-0.005397 -0.09746 0.595441 -0.54141 0.011099 0.0 +-0.003893 -0.087605 0.598125 -0.540741 0.007244 0.0 +-0.005761 -0.117826 0.602473 -0.54267 0.001836 0.0 +-0.005812 -0.092577 0.600288 -0.543399 0.003658 0.0 +-0.000494 -0.106063 0.600461 -0.545664 0.005563 0.0 +-0.001609 -0.112355 0.600286 -0.544184 0.012506 0.0 +-0.001478 -0.086986 0.597317 -0.54503 0.005531 0.0 +0.001091 -0.105463 0.600012 -0.542523 0.010825 0.0 +-0.002243 -0.093755 0.600362 -0.54604 0.001941 0.0 +0.002875 -0.097498 0.59938 -0.54368 0.01024 0.0 +-0.005129 -0.110628 0.604309 -0.544085 0.007189 0.0 +-0.009796 -0.094855 0.600885 -0.544378 0.007737 0.0 +-0.003762 -0.095302 0.602349 -0.544422 0.00671 0.0 +-0.000791 -0.098267 0.600658 -0.546575 0.008884 0.0 +-0.00653 -0.100636 0.600399 -0.545401 0.006285 0.0 +-0.00156 -0.108393 0.599018 -0.544314 0.011898 0.0 +-0.001242 -0.084639 0.599018 -0.544512 0.005102 0.0 +-0.002095 -0.095869 0.599206 -0.545263 0.004987 0.0 +-0.005495 -0.105625 0.599715 -0.542696 0.007234 0.0 +-0.005961 -0.094502 0.595702 -0.543205 0.003649 0.0 +0.002075 -0.114483 0.598906 -0.543339 0.012807 0.0 +-0.008381 -0.098892 0.597765 -0.539865 0.002959 0.0 +-0.001811 -0.091909 0.60251 -0.541613 0.01003 0.0 +0.000993 -0.088322 0.605055 -0.542342 0.004887 0.0 +-0.006162 -0.094245 0.602186 -0.544823 0.009756 0.0 +-0.000275 -0.104817 0.599764 -0.541587 0.0067 0.0 +0.000375 -0.097933 0.594337 -0.544215 0.004504 0.0 +-0.003544 -0.108182 0.598671 -0.536689 0.013976 0.0 +0.000175 -0.102473 0.599107 -0.539955 0.010578 0.0 +-0.004646 -0.085272 0.602535 -0.547183 0.017786 0.0 +-0.003024 -0.093205 0.59676 -0.540607 0.008787 0.0 +-0.003243 -0.107991 0.60189 -0.543131 0.012597 0.0 +-0.001811 -0.08996 0.601964 -0.542864 0.005801 0.0 +-0.006112 -0.097829 0.603255 -0.543442 0.013871 0.0 +-0.006796 -0.096454 0.600176 -0.540836 0.003549 0.0 +0.000312 -0.099806 0.599417 -0.540301 0.007079 0.0 +-0.003374 -0.099505 0.607055 -0.549349 0.014675 0.0 +-0.00556 -0.105337 0.599578 -0.546307 0.007289 0.0 +-0.001024 -0.090209 0.601702 -0.544689 0.007677 0.0 +-0.001893 -0.096342 0.598572 -0.545129 0.018695 0.0 +-0.004161 -0.103111 0.600224 -0.543645 0.008121 0.0 +0.001993 -0.100307 0.603988 -0.543412 0.013611 0.0 +-0.000111 -0.101293 0.595741 -0.540702 0.003384 0.0 +0.002277 -0.10445 0.600571 -0.545254 0.013826 0.0 +0.004961 -0.104976 0.605963 -0.543041 0.01441 0.0 +-0.002325 -0.09517 0.602982 -0.545224 0.000324 0.0 +0.001725 -0.102583 0.601565 -0.542864 0.001425 0.0 +-0.001713 -0.101873 0.602299 -0.544551 0.004115 0.0 +-0.002829 -0.098508 0.599229 -0.550256 0.008682 0.0 +-0.00051 -0.088473 0.603267 -0.542657 0.007194 0.0 +-0.002543 -0.099842 0.603976 -0.54273 0.0105 0.0 +-0.005577 -0.096969 0.599043 -0.541859 0.010181 0.0 +-0.000423 -0.097868 0.604807 -0.541828 0.009172 0.0 +-0.001095 -0.101701 0.601007 -0.544655 0.003347 0.0 +0.000124 -0.095891 0.596248 -0.543649 0.007778 0.0 +-0.005112 -0.10695 0.599107 -0.541988 0.005312 0.0 +-0.001576 -0.091597 0.600087 -0.541181 0.010961 0.0 +0.002327 -0.096564 0.598547 -0.540698 0.009272 0.0 +-0.004544 -0.09583 0.603354 -0.543615 0.020787 0.0 +-0.004227 -0.084634 0.599107 -0.541349 0.011843 0.0 +0.002175 -0.105348 0.601367 -0.544793 0.000342 0.0 +-0.00411 -0.093018 0.60195 -0.54264 0.01452 0.0 +-0.004763 -0.102251 0.598398 -0.541306 0.016603 0.0 +-0.007213 -0.105447 0.602547 -0.544081 0.010231 0.0 +-0.005478 -0.104998 0.603157 -0.542967 0.008957 0.0 +0.003376 -0.102501 0.599641 -0.545897 0.009331 0.0 +-0.003041 -0.110447 0.601067 -0.549617 0.003868 0.0 +-0.005511 -0.091658 0.603627 -0.549052 0.0066 0.0 +0.001562 -0.094719 0.600908 -0.541289 0.012917 0.0 +0.001895 -0.090209 0.600298 -0.548072 0.00148 0.0 +-0.002061 -0.094464 0.603788 -0.542294 0.017412 0.0 +-0.002161 -0.10022 0.598722 -0.544007 0.002959 0.0 +-0.004061 -0.095181 0.604173 -0.544392 0.015534 0.0 +-0.00356 -0.096351 0.605752 -0.546174 0.00623 0.0 +0.00301 -0.103768 0.603962 -0.543947 0.009541 0.0 +-2.5e-05 -0.104749 0.598809 -0.542091 0.010345 0.0 +-0.007446 -0.110855 0.600649 -0.5404 0.022627 0.0 +-0.001375 -0.090231 0.596895 -0.534937 0.008313 0.0 +-0.006045 -0.098982 0.601416 -0.545565 0.007719 0.0 +-0.001827 -0.107104 0.601379 -0.542432 0.007079 0.0 +0.00401 -0.099716 0.603999 -0.539287 0.016507 0.0 +-0.004077 -0.108037 0.601865 -0.542562 0.011469 0.0 +0.001294 -0.089612 0.597803 -0.544081 0.013611 0.0 +-0.001942 -0.095891 0.600461 -0.541121 0.001242 0.0 +-7.5e-05 -0.105157 0.599182 -0.547598 0.010126 0.0 +-0.001926 -0.093746 0.600362 -0.541319 0.013342 0.0 +-0.000105 -0.09002 0.601117 -0.545431 0.007234 0.0 +-0.000925 -0.09614 0.595702 -0.54371 0.015804 0.0 +-0.001576 -0.092309 0.601055 -0.540672 0.009162 0.0 +-0.001625 -0.097468 0.605403 -0.541315 0.00654 0.0 +-0.00094 -0.097597 0.599676 -0.547598 0.00654 0.0 +-0.001958 -0.096789 0.600286 -0.543684 0.008847 0.0 +0.000124 -0.094494 0.604297 -0.543265 0.007449 0.0 +-0.000106 -0.092336 0.601716 -0.546044 0.003444 0.0 +-0.004495 -0.093385 0.597244 -0.541444 0.000767 0.0 +0.000457 -0.102755 0.596911 -0.544141 0.016393 0.0 +-0.003811 -0.10856 0.599217 -0.546575 0.012218 0.0 +-0.009731 -0.1054 0.605849 -0.541203 0.010834 0.0 +-0.00256 -0.100181 0.598038 -0.549483 0.005366 0.0 +-0.006329 -0.098297 0.600486 -0.544184 0.011739 0.0 +0.005728 -0.101422 0.602349 -0.543943 0.010637 0.0 +-0.00663 -0.104976 0.599518 -0.539628 0.002635 0.0 +-0.003243 -0.109888 0.603478 -0.542894 0.009948 0.0 +-0.002425 -0.098878 0.599182 -0.543779 0.004622 0.0 +-0.00139 -0.09755 0.598125 -0.546247 0.009856 0.0 +-0.004709 -0.103089 0.603592 -0.545729 0.004133 0.0 +-0.005861 -0.103919 0.603327 -0.543136 0.012488 0.0 +-0.005161 -0.111482 0.604284 -0.541039 0.009272 0.0 +-0.000559 -0.093199 0.599903 -0.544275 0.005033 0.0 +9.1e-05 -0.113623 0.605142 -0.543131 0.013616 0.0 +0.005178 -0.097865 0.60092 -0.541988 0.001626 0.0 +-0.002478 -0.105529 0.600286 -0.544107 0.012268 0.0 +0.002026 -0.095663 0.600286 -0.544387 0.009436 0.0 +0.001594 -0.116257 0.604446 -0.543887 0.008413 0.0 +-0.001275 -0.099905 0.600734 -0.544081 0.012647 0.0 +-0.001191 -0.117177 0.600286 -0.540504 0.011524 0.0 +0.001458 -0.095212 0.600943 -0.544719 0.008473 0.0 +-0.000291 -0.101164 0.59594 -0.543308 0.006791 0.0 +-0.001057 -0.106266 0.601255 -0.540978 0.012135 0.0 +-0.00151 -0.090579 0.603192 -0.545966 0.009313 0.0 +-0.001609 -0.095513 0.601257 -0.548978 0.008363 0.0 +-0.001877 -0.096178 0.601478 -0.543235 0.011341 0.0 +0.000844 -0.111153 0.602436 -0.544051 0.013077 0.0 +-0.003809 -0.109081 0.599566 -0.545371 0.010935 0.0 +-0.007162 -0.100403 0.598125 -0.543399 0.011684 0.0 +-0.002946 -0.097128 0.600933 -0.546234 0.007399 0.0 +0.005863 -0.094782 0.599566 -0.5404 0.01088 0.0 +0.000222 -0.097369 0.598214 -0.544823 0.011629 0.0 +-7.3e-05 -0.09109 0.603935 -0.544525 0.016558 0.0 +0.001392 -0.102509 0.599827 -0.546441 0.008523 0.0 +-0.002975 -0.09623 0.603703 -0.540266 0.008454 0.0 +-0.005446 -0.10899 0.602347 -0.541988 0.005641 0.0 +9.1e-05 -0.1022 0.598509 -0.545733 0.015785 0.0 +-0.003511 -0.112544 0.601675 -0.539792 0.010651 0.0 +0.004442 -0.091288 0.597393 -0.543606 0.009172 0.0 +-0.006665 -0.111457 0.599169 -0.540905 0.010675 0.0 +0.001294 -0.104946 0.595941 -0.544215 0.008468 0.0 +-0.003828 -0.099469 0.600164 -0.543576 0.004937 0.0 +-0.004161 -0.113103 0.604557 -0.543986 0.011793 0.0 +-0.003824 -0.095617 0.59744 -0.540266 0.007774 0.0 +-0.002008 -0.103188 0.597418 -0.539865 0.01901 0.0 +-0.000842 -0.094218 0.603428 -0.545328 0.008742 0.0 +-0.002642 -0.103678 0.599055 -0.545936 0.011432 0.0 +0.001042 -0.11517 0.601652 -0.542363 0.007084 0.0 +-0.002611 -0.097186 0.605167 -0.539925 0.009222 0.0 +-0.004642 -0.090222 0.59809 -0.543071 0.016713 0.0 +-0.006031 -0.101143 0.598038 -0.542018 0.003398 0.0 +-0.007146 -0.098818 0.598439 -0.538842 0.00792 0.0 +-0.003646 -0.100751 0.603203 -0.545164 0.005079 0.0 +-0.003462 -0.104628 0.597788 -0.544594 0.007139 0.0 +-0.002194 -0.089239 0.601117 -0.545664 0.006166 0.0 +-0.002292 -0.11612 0.599903 -0.544219 0.011715 0.0 +0.005626 -0.107525 0.601193 -0.549091 0.019664 0.0 +-0.003161 -0.093226 0.600124 -0.542493 0.014054 0.0 +-0.00464 -0.100003 0.596771 -0.54733 0.009322 0.0 +-0.009982 -0.079637 0.600594 -0.54172 0.003384 0.0 +-0.007462 -0.097068 0.595493 -0.545341 0.012757 0.0 +-0.003014 -0.095951 0.600037 -0.540857 0.01764 0.0 +-0.007047 -0.092736 0.59866 -0.544551 0.003284 0.0 +-0.003697 -0.099971 0.600389 -0.540948 0.000712 0.0 +-0.005413 -0.087394 0.603093 -0.540102 0.005476 0.0 +-0.00646 -0.108798 0.599653 -0.539999 0.005052 0.0 +0.000977 -0.104289 0.600186 -0.543369 0.011547 0.0 +-0.003511 -0.091189 0.600362 -0.542998 0.005731 0.0 +-0.001975 -0.087794 0.605242 -0.544258 0.002155 0.0 +-0.004544 -0.086986 0.5926 -0.542804 0.003873 0.0 +-0.009714 -0.09425 0.598747 -0.539494 0.011953 0.0 +-0.008446 -0.098051 0.599926 -0.543572 0.012309 0.0 +-0.00491 -0.09626 0.59938 -0.548638 0.012807 0.0 +0.000676 -0.10994 0.599107 -0.547494 0.006815 0.0 +-0.008246 -0.088843 0.598571 -0.538376 0.010081 0.0 +9e-06 -0.091783 0.601938 -0.538143 0.022281 0.0 +-0.005746 -0.115852 0.599095 -0.546277 0.005476 0.0 +-0.005327 -0.103459 0.597614 -0.539986 0.008253 0.0 +-0.005794 -0.096356 0.600002 -0.546678 0.014022 0.0 +-0.002991 -0.102711 0.60103 -0.544525 0.010775 0.0 +0.001693 -0.096383 0.600524 -0.542286 0.006924 0.0 +-0.007012 -0.108333 0.603058 -0.544836 0.002425 0.0 +-0.00738 -0.094713 0.601751 -0.538713 0.012972 0.0 +-0.003059 -0.110469 0.600102 -0.545906 0.01336 0.0 +-0.003642 -0.094313 0.598386 -0.541073 0.001626 0.0 +-0.006761 -0.094686 0.599479 -0.546484 0.004937 0.0 +-0.001827 -0.10278 0.597939 -0.54185 0.010565 0.0 +-0.001826 -0.094497 0.6039 -0.543917 0.006092 0.0 +-0.001527 -0.109639 0.599229 -0.539524 0.002412 0.0 +-0.002558 -0.113034 0.602622 -0.544288 0.003005 0.0 +0.002727 -0.099746 0.59974 -0.546312 0.005458 0.0 +-0.004028 -0.104779 0.598075 -0.542592 0.014146 0.0 +-0.000275 -0.09407 0.594798 -0.541483 0.011606 0.0 +-0.001778 -0.093262 0.599194 -0.542726 0.007774 0.0 +-0.004241 -0.099749 0.59912 -0.547835 0.007614 0.0 +-0.00573 -0.094434 0.601479 -0.5461 0.010172 0.0 +-0.000328 -0.105463 0.602386 -0.542791 0.007545 0.0 +0.004212 -0.110469 0.599154 -0.543874 0.009866 0.0 +-0.00061 -0.075034 0.599554 -0.542389 0.010985 0.0 +-0.003511 -0.107939 0.60257 -0.549751 0.0066 0.0 +-0.004411 -0.091255 0.603801 -0.542432 0.017671 0.0 +0.00096 -0.080685 0.603616 -0.541513 0.008527 0.0 +-0.004577 -0.101425 0.60385 -0.544223 0.006956 0.0 +-0.006495 -0.114152 0.601602 -0.546113 0.007289 0.0 +-0.00166 -0.104793 0.595569 -0.544655 0.003978 0.0 +0.000124 -0.095195 0.592873 -0.546618 0.011369 0.0 +-0.004125 -0.092986 0.596696 -0.543433 0.004023 0.0 +-0.004746 -0.096293 0.600472 -0.544931 0.008408 0.0 +-0.002527 -0.102583 0.605167 -0.543472 0.013401 0.0 +-0.002942 -0.092736 0.599754 -0.540978 0.014515 0.0 +-0.002161 -0.093054 0.600982 -0.546411 0.004987 0.0 +-7.3e-05 -0.10997 0.608804 -0.542018 0.012492 0.0 +-0.000874 -0.113533 0.601169 -0.541444 0.002215 0.0 +-0.00311 -0.124226 0.600286 -0.544927 -0.000581 0.0 +-0.001962 -0.090299 0.600374 -0.544689 0.012862 0.0 +-0.004227 -0.099226 0.601791 -0.539658 0.004398 0.0 +-0.007998 -0.109538 0.600472 -0.544007 0.01029 0.0 +-0.000625 -0.093733 0.602758 -0.54604 0.010678 0.0 +-0.001958 -0.093574 0.599491 -0.543705 0.009144 0.0 +-0.008129 -0.096441 0.599692 -0.543472 0.016398 0.0 +-0.000909 -0.095606 0.601888 -0.549393 0.008048 0.0 +-0.002729 -0.102161 0.600809 -0.542937 0.00634 0.0 +-0.000357 -0.099694 0.600848 -0.541142 0.008769 0.0 +0.000344 -0.105896 0.600931 -0.543679 0.00517 0.0 +-0.005397 -0.101821 0.60225 -0.54141 0.003489 0.0 +-0.004877 -0.104576 0.599839 -0.542735 0.012917 0.0 +-0.010015 -0.106764 0.599578 -0.543304 0.012163 0.0 +-0.000876 -0.09752 0.598299 -0.545462 0.011249 0.0 +-0.009414 -0.101852 0.602273 -0.54138 0.011519 0.0 +-0.007945 -0.092186 0.599392 -0.545 0.00686 0.0 +-0.003194 -0.095014 0.596264 -0.545966 0.007244 0.0 +0.000823 -0.11517 0.598921 -0.547731 0.019444 0.0 +0.000944 -0.097742 0.601553 -0.541677 0.008797 0.0 +-0.001891 -0.099535 0.601553 -0.538885 0.010405 0.0 +-0.002811 -0.113034 0.601615 -0.546575 0.009541 0.0 +0.00501 -0.097498 0.597602 -0.542696 0.012318 0.0 +-0.001527 -0.112388 0.603168 -0.545565 0.007198 0.0 +-0.005677 -0.094124 0.599903 -0.54623 0.024144 0.0 +-0.002844 -0.098547 0.600931 -0.539658 0.008724 0.0 +-0.003895 -0.098229 0.599543 -0.538747 0.013931 0.0 +-0.008146 -0.110789 0.599217 -0.546579 0.002357 0.0 +-0.009649 -0.097429 0.598078 -0.544724 0.012757 0.0 +-0.006096 -0.118971 0.600374 -0.545833 0.011519 0.0 +-0.003358 -0.092925 0.600286 -0.546514 0.010076 0.0 +-0.004292 -0.10847 0.595493 -0.546924 0.005476 0.0 +-0.001161 -0.104628 0.598201 -0.54226 0.007509 0.0 +0.001676 -0.107293 0.598882 -0.538782 0.006458 0.0 +0.000856 -0.08916 0.599004 -0.544215 0.013881 0.0 +0.00014 -0.107391 0.5997 -0.543917 0.013986 0.0 +-0.002959 -0.094993 0.599839 -0.541336 0.013501 0.0 +0.002173 -0.122407 0.600224 -0.546752 0.004736 0.0 +-0.001827 -0.096942 0.603863 -0.537863 0.009747 0.0 +0.004192 -0.085447 0.602769 -0.54037 0.011843 0.0 +-0.000341 -0.097213 0.602372 -0.537263 0.011784 0.0 +-0.002877 -0.104508 0.600757 -0.545056 0.009067 0.0 +-0.000144 -0.099226 0.596847 -0.547688 0.011592 0.0 +-0.001725 -0.107073 0.601367 -0.543606 0.005563 0.0 +0.002611 -0.089831 0.599095 -0.544482 0.007619 0.0 +0.001124 -0.106055 0.599403 -0.545595 0.011104 0.0 +-0.005845 -0.104877 0.598003 -0.543774 -0.000426 0.0 +-0.003544 -0.088079 0.595057 -0.545979 0.006225 0.0 +-0.00151 -0.106016 0.600797 -0.544957 0.011793 0.0 +0.00249 -0.103281 0.600672 -0.546851 0.00622 0.0 +-0.007129 -0.105893 0.599775 -0.542773 0.016987 0.0 +-0.002062 -0.10373 0.598584 -0.543753 0.00681 0.0 +-0.001674 -0.082972 0.602918 -0.54604 0.003549 0.0 +-0.004161 -0.100455 0.602982 -0.550506 0.011304 0.0 +0.000476 -0.085787 0.600201 -0.544348 0.012213 0.0 +-0.002089 -0.096718 0.598189 -0.543813 0.008679 0.0 +9.1e-05 -0.098081 0.603254 -0.542631 0.010131 0.0 +-0.006513 -0.104417 0.605068 -0.547024 0.00697 0.0 +-0.004495 -0.108221 0.598323 -0.542256 0.009162 0.0 +-0.003028 -0.096381 0.601803 -0.540624 0.0104 0.0 +-0.00286 -0.105926 0.599217 -0.544378 0.005042 0.0 +-0.007976 -0.095861 0.606381 -0.545341 0.006815 0.0 +-0.00863 -0.108431 0.601305 -0.544081 0.011099 0.0 +-0.005495 -0.104751 0.605353 -0.547416 0.007349 0.0 +-0.005528 -0.106446 0.597974 -0.53473 0.007121 0.0 +-0.003944 -0.088413 0.602682 -0.547831 0.012378 0.0 +-0.005411 -0.096222 0.594633 -0.548771 0.005846 0.0 +-0.00221 -0.095633 0.601315 -0.546411 0.000392 0.0 +-0.003779 -0.093533 0.598933 -0.540875 0.01062 0.0 +-0.006779 -0.112977 0.598933 -0.541859 0.009924 0.0 +-0.002711 -0.110231 0.601094 -0.546549 0.01041 0.0 +-0.004628 -0.098358 0.598274 -0.538782 0.014748 0.0 +0.000742 -0.102419 0.598323 -0.543071 0.01034 0.0 +-0.003028 -0.097027 0.602024 -0.538747 0.011843 0.0 +-0.001979 -0.087643 0.594064 -0.546104 0.012273 0.0 +-0.004877 -0.101707 0.60287 -0.544051 0.005983 0.0 +-0.006047 -0.10031 0.598497 -0.540607 0.008947 0.0 +-0.00096 -0.102221 0.599467 -0.544154 0.012647 0.0 +-0.002709 -0.102668 0.59969 -0.534924 0.009884 0.0 +-0.001576 -0.090943 0.600623 -0.542355 0.009733 0.0 +-0.005462 -0.099557 0.599479 -0.547196 0.004782 0.0 +-0.004812 -0.09913 0.601082 -0.545457 0.002535 0.0 +-0.001492 -0.101323 0.600821 -0.544659 0.007509 0.0 +-0.000893 -0.10321 0.598869 -0.5461 0.00676 0.0 +-0.005243 -0.08953 0.602273 -0.542463 0.009756 0.0 +0.000175 -0.10367 0.602907 -0.539123 0.013342 0.0 +-0.00311 -0.100743 0.598363 -0.546916 0.016005 0.0 +0.000856 -0.103434 0.603029 -0.541984 0.004275 0.0 +-0.005828 -0.085168 0.598845 -0.54654 0.009861 0.0 +-0.004992 -0.091657 0.602163 -0.538027 0.014954 0.0 +-0.00761 -0.103988 0.602372 -0.542864 0.011309 0.0 +-0.005395 -0.104847 0.599554 -0.541039 0.010395 0.0 +-0.005063 -0.099513 0.603813 -0.543369 0.000867 0.0 +-0.005861 -0.12086 0.599876 -0.545669 0.001731 0.0 +-0.004844 -0.087665 0.602337 -0.542562 0.010678 0.0 +-0.000578 -0.10244 0.60164 -0.544866 0.00649 0.0 +-0.004712 -0.093235 0.603615 -0.545871 0.003384 0.0 +-0.005927 -0.100513 0.59815 -0.553035 -0.000676 0.0 +0.002174 -0.096041 0.602608 -0.54333 0.003923 0.0 +-0.001259 -0.095861 0.602508 -0.541988 0.010935 0.0 +-0.008413 -0.102342 0.602163 -0.544763 0.009067 0.0 +-0.001007 -0.107722 0.602697 -0.538307 0.003955 0.0 +-0.00106 -0.102309 0.6001 -0.543295 0.009062 0.0 +-0.003828 -0.104108 0.599789 -0.542359 0.013812 0.0 +-0.000895 -0.118421 0.602037 -0.544952 0.004882 0.0 +-0.002445 -0.106772 0.605289 -0.54607 0.006796 0.0 +-0.002844 -0.109609 0.600908 -0.53807 0.017731 0.0 +-0.002975 -0.098388 0.601553 -0.540668 0.010231 0.0 +0.00021 -0.10936 0.602361 -0.546575 0.00671 0.0 +-0.000175 -0.112388 0.601094 -0.544806 0.0039 0.0 +-0.00286 -0.10241 0.596425 -0.541203 0.009546 0.0 +-0.00808 -0.087575 0.597242 -0.545155 0.005791 0.0 +0.00031 -0.106175 0.5982 -0.543576 0.009117 0.0 +-0.004576 -0.096381 0.600894 -0.541319 0.015073 0.0 +-0.003746 -0.082786 0.602186 -0.541142 -0.006619 0.0 +0.003292 -0.093884 0.606695 -0.544245 0.000502 0.0 +-0.001193 -0.092994 0.599405 -0.542256 0.008203 0.0 +-0.002695 -0.098985 0.602 -0.549751 0.008413 0.0 +-0.003859 -0.105992 0.600224 -0.54503 0.005335 0.0 +0.001009 -0.101756 0.598462 -0.539938 0.005901 0.0 +0.001427 -0.10327 0.599827 -0.539731 0.01066 0.0 +-0.004161 -0.088142 0.599588 -0.543947 0.003334 0.0 +-0.004292 -0.106454 0.60128 -0.543485 0.009866 0.0 +-0.004245 -0.105006 0.599579 -0.542864 0.013776 0.0 +-0.00708 -0.107076 0.602583 -0.542627 0.012798 0.0 +-0.001243 -0.088076 0.598201 -0.546307 0.014246 0.0 +-0.004877 -0.108563 0.605539 -0.547183 0.004467 0.0 +-0.004593 -0.10813 0.600188 -0.542122 0.009441 0.0 +-0.001959 -0.094908 0.602645 -0.545535 0.007988 0.0 +-0.003227 -0.113933 0.599105 -0.547999 0.001192 0.0 +-0.000709 -0.09571 0.597827 -0.550627 0.00231 0.0 +-0.007397 -0.101137 0.603664 -0.545401 0.008235 0.0 +-0.000527 -0.092517 0.599936 -0.543679 0.017996 0.0 +-0.003625 -0.100734 0.597567 -0.543546 0.017516 0.0 +-0.004128 -0.106644 0.600635 -0.545194 0.002695 0.0 +-0.005845 -0.101104 0.602163 -0.544586 0.00612 0.0 +0.00014 -0.088881 0.601987 -0.539554 0.012593 0.0 +-0.002975 -0.08993 0.601454 -0.544495 0.01827 0.0 +6e-05 -0.102509 0.599876 -0.547049 0.005526 0.0 +-0.006063 -0.101701 0.599676 -0.546709 0.013282 0.0 +-0.002259 -0.095401 0.602088 -0.541854 0.004133 0.0 +-0.002361 -0.087764 0.604208 -0.551101 0.002046 0.0 +-0.003292 -0.09577 0.599269 -0.546782 0.007815 0.0 +-0.004145 -0.094097 0.599818 -0.544616 0.003009 0.0 +-0.007195 -0.106257 0.600536 -0.541259 0.010418 0.0 +0.003096 -0.100162 0.596672 -0.542765 0.006011 0.0 +-0.006479 -0.090642 0.60189 -0.546407 0.009441 0.0 +-0.003276 -0.103829 0.603528 -0.541039 0.012547 0.0 +-0.000641 -0.089861 0.60282 -0.547287 0.01161 0.0 +-0.002161 -0.108494 0.602907 -0.545237 0.007504 0.0 +-0.005161 -0.104628 0.605091 -0.542225 0.009756 0.0 +-0.004877 -0.094344 0.60031 -0.545772 0.009952 0.0 +-0.001746 -0.107911 0.598561 -0.543615 0.004987 0.0 +-0.004894 -0.104448 0.598026 -0.544732 0.015584 0.0 +0.001693 -0.102005 0.600286 -0.542627 0.005691 0.0 +-0.002944 -0.103642 0.599031 -0.545457 0.012177 0.0 +-0.000142 -0.103588 0.603378 -0.543442 0.007874 0.0 +-0.002828 -0.10284 0.597143 -0.538471 0.011236 0.0 +-0.004128 -0.097309 0.599777 -0.543736 0.015059 0.0 +-0.002959 -0.093905 0.602918 -0.544797 0.021956 0.0 +-0.001979 -0.09085 0.598363 -0.545129 0.024478 0.0 +-0.009512 -0.100779 0.600013 -0.547153 0.014575 0.0 +-0.004145 -0.104806 0.600856 -0.544081 0.021408 0.0 +-0.004429 -0.108185 0.600112 -0.543511 0.013447 0.0 +-0.004895 -0.10094 0.599467 -0.543231 0.004882 0.0 +-0.002511 -0.111055 0.597207 -0.539615 0.016453 0.0 +-0.004045 -0.105896 0.596138 -0.5461 0.016868 0.0 +-0.001942 -0.09663 0.600263 -0.538721 0.010021 0.0 +0.00014 -0.079117 0.601007 -0.547421 0.009846 0.0 +-0.003678 -0.106824 0.604249 -0.551278 0.008628 0.0 +-0.004243 -0.094365 0.597567 -0.544154 0.010884 0.0 +-0.006096 -0.085069 0.598807 -0.537461 0.013205 0.0 +-0.008712 -0.092238 0.603366 -0.544124 0.012273 0.0 +-0.007397 -0.098358 0.601803 -0.544141 0.001251 0.0 +-0.00159 -0.105096 0.60031 -0.549552 0.003823 0.0 +-0.001942 -0.09508 0.603488 -0.543636 0.011364 0.0 +-0.005845 -0.077621 0.602086 -0.542627 0.004832 0.0 +-0.00256 -0.095395 0.601927 -0.547554 0.01204 0.0 +0.002274 -0.090918 0.601216 -0.543654 0.008861 0.0 +0.000173 -0.098766 0.599764 -0.544689 0.012218 0.0 +-0.000226 -0.093166 0.598474 -0.539093 0.01489 0.0 +-0.001343 -0.08522 0.604297 -0.542998 0.008436 0.0 +-0.004446 -0.089886 0.601292 -0.547999 0.011793 0.0 +-0.004977 -0.122958 0.601144 -0.543805 0.013027 0.0 +-0.002709 -0.108459 0.594163 -0.547969 0.009806 0.0 +-0.006894 -0.098826 0.600559 -0.545298 0.008587 0.0 +-0.003261 -0.088848 0.596411 -0.542553 0.012538 0.0 +0.001326 -0.119362 0.602062 -0.542627 0.013012 0.0 +-0.003227 -0.097339 0.599765 -0.54314 0.012949 0.0 +0.000592 -0.089092 0.603182 -0.547494 0.021463 0.0 +-0.000991 -0.104267 0.600263 -0.54547 0.01188 0.0 +-0.001793 -0.095669 0.599864 -0.54371 0.016278 0.0 +-0.002325 -0.105466 0.596324 -0.545966 0.007925 0.0 +-0.000791 -0.087977 0.600252 -0.54402 0.00692 0.0 +-0.001461 -0.098366 0.601106 -0.546855 0.013566 0.0 +-0.00156 -0.099001 0.603017 -0.54374 0.010966 0.0 +-0.001324 -0.093355 0.600943 -0.548474 0.010875 0.0 +-0.002877 -0.108182 0.596748 -0.543977 0.01467 0.0 +0.000441 -0.102933 0.602907 -0.540132 0.016817 0.0 +-0.00321 -0.098788 0.598685 -0.542864 0.011524 0.0 +-0.002008 -0.100132 0.600697 -0.543874 0.011894 0.0 +0.000692 -0.097002 0.602634 -0.548137 0.00612 0.0 +-0.00356 -0.106142 0.599729 -0.538208 0.015489 0.0 +0.002245 -0.096449 0.599467 -0.546307 0.010286 0.0 +-0.001427 -0.096918 0.600188 -0.544081 0.019065 0.0 +-0.000175 -0.110937 0.600734 -0.546976 0.001881 0.0 +-0.000608 -0.080866 0.600833 -0.544228 0.003064 0.0 +-0.001576 -0.109601 0.602994 -0.54084 0.011953 0.0 +-0.00511 -0.089242 0.600523 -0.545569 0.018247 0.0 +-0.006544 -0.106737 0.598534 -0.541587 0.009277 0.0 +-0.003893 -0.089431 0.599903 -0.540814 0.008934 0.0 +-0.007342 -0.100493 0.598485 -0.547628 0.006225 0.0 +0.000725 -0.110868 0.603006 -0.545608 0.015169 0.0 +-0.003627 -0.08999 0.601739 -0.547559 0.001301 0.0 +-0.002877 -0.096381 0.60164 -0.539446 0.018915 0.0 +-0.005178 -0.108891 0.603366 -0.540948 0.014255 0.0 +-0.001093 -0.093136 0.603255 -0.545777 0.006216 0.0 +-0.002509 -0.091219 0.600484 -0.547464 0.006545 0.0 +-0.000876 -0.091808 0.600124 -0.543118 0.008399 0.0 +-0.000844 -0.096479 0.603726 -0.549721 -0.00017 0.0 +-0.000527 -0.10269 0.603352 -0.545393 0.007249 0.0 +-0.001275 -0.090587 0.603652 -0.542523 0.003553 0.0 +-0.007129 -0.103793 0.597777 -0.538605 0.005243 0.0 +-0.005478 -0.098267 0.60318 -0.542419 0.009117 0.0 +-0.005362 -0.089741 0.60071 -0.539244 0.015808 0.0 +0.001042 -0.107071 0.601479 -0.546238 0.004947 0.0 +-0.00186 -0.112416 0.599938 -0.540901 0.006705 0.0 +-0.003194 -0.097238 0.60164 -0.541884 0.004508 0.0 +-0.003544 -0.10738 0.599752 -0.547598 0.011309 0.0 +-0.001776 -0.091156 0.600164 -0.544417 0.018111 0.0 +-0.002392 -0.097739 0.601826 -0.543977 0.012433 0.0 +0.004691 -0.100006 0.597866 -0.543917 0.007024 0.0 +-0.002511 -0.099037 0.597939 -0.544219 0.006116 0.0 +-0.001559 -0.094872 0.600722 -0.547688 0.003284 0.0 +-0.001292 -0.105625 0.596574 -0.543882 0.01045 0.0 +-0.003711 -0.098478 0.598038 -0.549824 0.005476 0.0 +-0.001478 -0.09258 0.603403 -0.54377 0.003905 0.0 +-0.002227 -0.104817 0.603616 -0.548879 0.010185 0.0 +-0.006796 -0.097399 0.598125 -0.543412 0.019764 0.0 +-0.000592 -0.097889 0.598348 -0.543442 0.003439 0.0 +-0.001778 -0.108546 0.602273 -0.542182 0.015644 0.0 +-0.000576 -0.08999 0.601938 -0.545949 0.007929 0.0 +-0.004777 -0.114206 0.601541 -0.545997 0.01083 0.0 +0.00014 -0.097093 0.59748 -0.546907 0.013936 0.0 +-0.001942 -0.091279 0.601228 -0.544461 0.008473 0.0 +0.001539 -0.08436 0.601861 -0.547006 0.012259 0.0 +-0.000975 -0.098489 0.601466 -0.544348 0.007545 0.0 +-0.004495 -0.106515 0.602872 -0.542795 0.019919 0.0 +-0.004711 -0.090639 0.605911 -0.546653 0.012593 0.0 +-0.001193 -0.105655 0.603488 -0.54613 0.012158 0.0 +0.000523 -0.097558 0.600647 -0.546217 0.010825 0.0 +-0.002224 -0.085537 0.60282 -0.546169 0.001246 0.0 +-0.00016 -0.098021 0.600559 -0.545237 0.003439 0.0 +-0.000576 -0.093024 0.596636 -0.54317 0.000602 0.0 +-0.002977 -0.099754 0.599676 -0.541216 0.017836 0.0 +-0.003462 -0.110636 0.599206 -0.542976 0.009327 0.0 +-0.005495 -0.083498 0.597096 -0.54604 0.013926 0.0 +-0.002576 -0.088383 0.597108 -0.54012 0.012702 0.0 +0.001725 -0.093196 0.599368 -0.544724 0.01495 0.0 +-0.002243 -0.094223 0.601925 -0.544719 0.017197 0.0 +-0.001375 -0.097799 0.600995 -0.542924 0.007179 0.0 +-0.000975 -0.098366 0.600199 -0.546312 0.015699 0.0 +-0.000876 -0.098796 0.602434 -0.545626 0.011299 0.0 +-0.002511 -0.099913 0.60533 -0.542419 0.009861 0.0 +-0.004278 -0.090351 0.600771 -0.544957 0.015963 0.0 +-0.005644 -0.098667 0.599316 -0.543278 0.013986 0.0 +-0.001077 -0.095767 0.604061 -0.543037 0.012885 0.0 +-0.00448 -0.108642 0.601628 -0.542523 0.015155 0.0 +0.002277 -0.105337 0.59307 -0.543308 0.013986 0.0 +-0.002161 -0.108002 0.595047 -0.544987 0.009172 0.0 +-0.003544 -0.098848 0.600275 -0.544115 0.010935 0.0 +0.001561 -0.09715 0.597951 -0.540944 0.013492 0.0 +-0.00256 -0.096351 0.600647 -0.545604 0.01057 0.0 +-0.000893 -0.109886 0.600687 -0.543006 0.01051 0.0 +-0.004046 -0.097128 0.600645 -0.547835 0.008308 0.0 +-0.005947 -0.105466 0.598907 -0.54371 0.013981 0.0 +0.000326 -0.102073 0.601565 -0.54276 0.016717 0.0 +0.002392 -0.092586 0.602386 -0.542834 0.009152 0.0 +-0.006329 -0.10247 0.60329 -0.546351 0.01611 0.0 +-0.004544 -0.097988 0.600275 -0.544245 0.009706 0.0 +-0.005096 -0.083624 0.607326 -0.544258 0.005321 0.0 +0.004579 -0.080926 0.600745 -0.542316 0.009756 0.0 +-0.004746 -0.093821 0.605192 -0.544314 0.017713 0.0 +-0.000825 -0.093046 0.598261 -0.546847 0.018188 0.0 +-0.001226 -0.096197 0.597505 -0.550834 0.012378 0.0 +-0.005478 -0.101534 0.598485 -0.543308 0.013976 0.0 +-0.000177 -0.093665 0.598026 -0.545949 0.00617 0.0 +-0.00681 -0.099836 0.604842 -0.544599 0.014625 0.0 +-0.002609 -0.101028 0.599554 -0.543576 0.006865 0.0 +-0.002779 -0.085847 0.601553 -0.546187 0.004458 0.0 +-0.003795 -0.106326 0.599455 -0.544413 0.023244 0.0 +-0.004779 -0.090546 0.597829 -0.546204 0.016068 0.0 +-0.005728 -0.0935 0.600658 -0.547317 0.013415 0.0 +-0.00221 -0.112136 0.603343 -0.537833 0.017786 0.0 +-0.006113 -0.103393 0.59656 -0.546139 0.011739 0.0 +-0.005243 -0.105324 0.603104 -0.548978 0.008258 0.0 +-0.003576 -0.09252 0.598933 -0.547287 0.009871 0.0 +-0.00086 -0.072988 0.601292 -0.54774 0.005312 0.0 +-0.003292 -0.09177 0.599926 -0.540668 0.010989 0.0 +-0.005413 -0.08378 0.600782 -0.540607 0.008033 0.0 +-0.005079 -0.099354 0.604446 -0.544952 0.017786 0.0 +-0.005162 -0.110348 0.598882 -0.548133 0.012679 0.0 +-0.009447 -0.100737 0.603465 -0.540227 0.017617 0.0 +-0.006296 -0.106424 0.59932 -0.546536 0.0077 0.0 +-0.003063 -0.086277 0.596771 -0.543433 0.008468 0.0 +-0.002443 -0.098848 0.602076 -0.545492 0.006074 0.0 +-0.001243 -0.101394 0.601181 -0.53911 0.001836 0.0 +-0.006012 -0.114798 0.600124 -0.546847 0.011401 0.0 +-0.001494 -0.104727 0.598026 -0.540499 0.010021 0.0 +-0.002243 -0.086397 0.602459 -0.546812 0.013771 0.0 +0.000375 -0.103339 0.598449 -0.545237 0.013346 0.0 +-0.003713 -0.092607 0.601704 -0.543606 0.011697 0.0 +-0.004296 -0.096772 0.598398 -0.550329 0.013456 0.0 +-0.003527 -0.087673 0.601791 -0.542877 0.010555 0.0 +-0.005396 -0.104138 0.601193 -0.536279 0.01304 0.0 +-0.006746 -0.101581 0.596151 -0.539666 0.01436 0.0 +-0.008414 -0.097366 0.601466 -0.547861 0.012328 0.0 +-0.00798 -0.095121 0.596895 -0.543235 0.00897 0.0 +-0.007528 -0.092093 0.598214 -0.542355 0.014209 0.0 +0.00021 -0.092986 0.60282 -0.543162 0.016183 0.0 +-0.00086 -0.098968 0.600995 -0.542799 0.020892 0.0 +-0.008545 -0.101663 0.600846 -0.542493 0.011437 0.0 +-0.005828 -0.099497 0.600647 -0.546169 0.017407 0.0 +-0.006914 -0.09724 0.597081 -0.544659 0.015754 0.0 +-0.002959 -0.100532 0.598782 -0.544184 0.013177 0.0 +-0.008463 -0.100524 0.598898 -0.544007 0.012364 0.0 +-0.000508 -0.100214 0.602436 -0.545401 0.005901 0.0 +-0.007381 -0.086715 0.601478 -0.547244 0.009738 0.0 +-0.011081 -0.090699 0.595046 -0.542976 0.005906 0.0 +-0.004364 -0.100959 0.604832 -0.54749 0.010345 0.0 +-0.006681 -0.090699 0.598956 -0.547153 0.004243 0.0 +-0.002893 -0.109335 0.602994 -0.545164 0.010053 0.0 +-0.008747 -0.099995 0.600734 -0.546277 0.015547 0.0 +-0.003879 -0.098755 0.597592 -0.538989 0.019654 0.0 +-0.004894 -0.095581 0.602783 -0.545729 0.009806 0.0 +-0.004861 -0.092646 0.598509 -0.542834 0.00617 0.0 +-0.004343 -0.092418 0.600199 -0.545328 0.011688 0.0 +-0.009447 -0.102993 0.601094 -0.541958 0.004727 0.0 +-0.005579 -0.088752 0.601082 -0.545708 0.01907 0.0 +-0.00309 -0.119004 0.598782 -0.543175 0.008038 0.0 +-0.00608 -0.100154 0.599095 -0.542152 0.007454 0.0 +0.003294 -0.095633 0.604772 -0.54509 0.001137 0.0 +-0.000126 -0.110047 0.597829 -0.537936 0.016438 0.0 +-0.005178 -0.100154 0.597728 -0.546441 0.009272 0.0 +-0.000641 -0.101052 0.599804 -0.545367 0.00692 0.0 +-0.004161 -0.074385 0.603949 -0.545462 0.007349 0.0 +-0.004012 -0.090691 0.600623 -0.54695 0.012483 0.0 +-0.000876 -0.085645 0.601727 -0.543131 0.0189 0.0 +-0.000956 -0.0993 0.598311 -0.547792 0.007134 0.0 +0.000723 -0.084979 0.600509 -0.543028 0.006869 0.0 +-0.000242 -0.107347 0.601716 -0.546217 0.018165 0.0 +0.001827 -0.09292 0.600211 -0.542419 0.010725 0.0 +-0.004445 -0.109445 0.595952 -0.545708 0.01606 0.0 +0.000676 -0.112982 0.595615 -0.543934 0.010935 0.0 +-0.001809 -0.101638 0.594662 -0.538139 0.011765 0.0 +0.001529 -0.091321 0.599653 -0.54503 0.010026 0.0 +-0.001359 -0.105337 0.601478 -0.544076 0.00908 0.0 +0.002308 -0.095017 0.599008 -0.540365 0.014031 0.0 +0.002607 -0.088897 0.599159 -0.545608 0.006641 0.0 +0.000208 -0.099784 0.600958 -0.548331 0.011309 0.0 +-0.002093 -0.085817 0.59651 -0.544896 0.008148 0.0 +0.00249 -0.10899 0.603319 -0.544094 0.004133 0.0 +-0.005145 -0.106515 0.600995 -0.544387 0.015749 0.0 +0.002075 -0.100266 0.598447 -0.54273 0.008153 0.0 +0.003278 -0.088717 0.59913 -0.539036 0.011204 0.0 +0.000108 -0.106175 0.600385 -0.546208 0.00617 0.0 +-0.006029 -0.110387 0.603366 -0.548068 0.019015 0.0 +0.001643 -0.099595 0.600548 -0.544378 0.014858 0.0 +-0.005861 -0.102071 0.603267 -0.538277 0.018535 0.0 +0.000725 -0.102251 0.598299 -0.545371 0.003549 0.0 +0.00201 -0.104198 0.59712 -0.541789 0.013602 0.0 +0.000392 -0.097714 0.594685 -0.547925 0.006225 0.0 +0.00014 -0.088322 0.603924 -0.540624 0.008911 0.0 +0.007125 -0.1029 0.600647 -0.545254 0.011519 0.0 +0.001195 -0.09657 0.599874 -0.54327 0.012702 0.0 +-0.000574 -0.103831 0.599804 -0.542001 0.001401 0.0 +-0.000893 -0.10609 0.600571 -0.541306 0.004458 0.0 +9e-06 -0.098388 0.59718 -0.541587 0.006495 0.0 +-0.002292 -0.092208 0.602175 -0.544051 0.000662 0.0 +0.002511 -0.097123 0.601292 -0.540668 0.015319 0.0 +-0.002093 -0.103056 0.600821 -0.540271 0.002904 0.0 +-0.00186 -0.093355 0.6 -0.547559 0.012414 0.0 +-0.003824 -0.090798 0.597207 -0.544245 0.009756 0.0 +0.00788 -0.112385 0.599717 -0.544184 0.008847 0.0 +-0.001109 -0.098757 0.599839 -0.54371 0.01415 0.0 +-0.001559 -0.102443 0.596847 -0.538618 0.012464 0.0 +0.003442 -0.088692 0.596994 -0.544883 0.012163 0.0 +0.003177 -0.097498 0.598065 -0.542804 0.01943 0.0 +0.002992 -0.102909 0.597654 -0.54601 0.010985 0.0 +-0.00121 -0.109269 0.598449 -0.539213 0.01832 0.0 +-0.000975 -0.101474 0.598696 -0.5404 0.005778 0.0 +0.002042 -0.103396 0.600745 -0.546769 0.021481 0.0 +-0.001977 -0.092616 0.59368 -0.543295 0.020376 0.0 +-0.003161 -0.095483 0.602558 -0.546441 0.011291 0.0 +-0.00121 -0.098298 0.607177 -0.541617 0.012748 0.0 +-0.000925 -0.105614 0.59871 -0.547701 0.011309 0.0 +-0.000412 -0.110682 0.603801 -0.54506 0.010395 0.0 +-2.4e-05 -0.095031 0.604569 -0.541587 0.016658 0.0 +-0.000477 -0.111769 0.601106 -0.54604 0.007988 0.0 +0.006442 -0.104072 0.601675 -0.546514 0.007184 0.0 +0.00671 -0.107851 0.600658 -0.54601 0.0067 0.0 +-0.00151 -0.095113 0.595615 -0.542873 0.005476 0.0 +0.001725 -0.103369 0.599045 -0.538678 0.010057 0.0 +0.000107 -0.085907 0.603999 -0.544219 0.009432 0.0 +0.002912 -0.094245 0.6001 -0.54418 0.004727 0.0 +0.006094 -0.094795 0.600745 -0.545164 0.014255 0.0 +0.003327 -0.107405 0.602299 -0.547455 0.003654 0.0 +-0.000557 -0.09583 0.597951 -0.540944 0.013227 0.0 +-0.002308 -0.100343 0.599142 -0.548102 0.01246 0.0 +0.007246 -0.096479 0.601663 -0.548698 0.010925 0.0 +-0.000776 -0.093166 0.599764 -0.546368 0.011299 0.0 +0.001995 -0.098232 0.598386 -0.543239 0.015105 0.0 +0.005344 -0.097095 0.595667 -0.550131 0.012962 0.0 +0.002844 -0.089982 0.598857 -0.54509 0.021687 0.0 +0.003844 -0.086995 0.604917 -0.543541 0.013342 0.0 +-0.001292 -0.107013 0.598933 -0.547054 0.006527 0.0 +0.003075 -0.094864 0.595667 -0.548176 0.023244 0.0 +0.003223 -0.103552 0.600583 -0.546605 0.018179 0.0 +0.006229 -0.100124 0.596748 -0.547835 0.008258 0.0 +0.00331 -0.096759 0.600472 -0.545906 0.003284 0.0 +0.005163 -0.099875 0.599403 -0.548672 0.013716 0.0 +0.006327 -0.092646 0.596884 -0.545941 0.017841 0.0 +0.004693 -0.100587 0.600188 -0.543843 0.026579 0.0 +0.000911 -0.111668 0.600176 -0.54314 0.01045 0.0 +0.001108 -0.101334 0.599905 -0.5438 0.009277 0.0 +-0.000893 -0.094344 0.602523 -0.542657 0.017836 0.0 +-0.002308 -0.095978 0.600472 -0.54141 0.007687 0.0 +0.001562 -0.1068 0.598024 -0.546441 0.01431 0.0 +-0.002243 -0.09036 0.599504 -0.544422 0.016188 0.0 +0.000594 -0.101104 0.601268 -0.543248 0.021563 0.0 +-0.001609 -0.10376 0.599978 -0.54938 0.009222 0.0 +0.002676 -0.083582 0.598782 -0.546545 0.013926 0.0 +-0.00156 -0.106175 0.595046 -0.539252 0.017736 0.0 +0.003611 -0.104658 0.602498 -0.54516 0.013232 0.0 +0.001627 -0.091222 0.598125 -0.542799 0.013936 0.0 +0.004176 -0.098322 0.594871 -0.545164 0.013501 0.0 +-0.000508 -0.106545 0.605601 -0.54267 0.013401 0.0 +0.005294 -0.111087 0.604534 -0.540309 0.006765 0.0 +0.00066 -0.087326 0.599804 -0.545936 0.011414 0.0 +0.000261 -0.094283 0.60139 -0.541543 0.012433 0.0 +-0.003124 -0.096353 0.599194 -0.544055 0.00951 0.0 +0.004261 -0.105956 0.598063 -0.544275 0.012488 0.0 +-0.001958 -0.097438 0.603714 -0.544655 0.009112 0.0 +-0.003194 -0.087824 0.596649 -0.544659 0.011464 0.0 +0.00348 -0.097958 0.601489 -0.542389 0.007627 0.0 +0.00266 -0.093016 0.596736 -0.546648 0.016868 0.0 +-0.003276 -0.117366 0.599676 -0.538989 0.009062 0.0 +-0.001893 -0.090932 0.600199 -0.548978 0.010117 0.0 +2.6e-05 -0.105096 0.604905 -0.547015 0.013588 0.0 +-0.001543 -0.108552 0.602198 -0.535412 0.013771 0.0 +0.003092 -0.110786 0.602459 -0.545462 0.011419 0.0 +-0.002942 -0.094004 0.601069 -0.549319 0.012373 0.0 +-0.00059 -0.095521 0.59717 -0.542976 0.004827 0.0 +-0.005241 -0.086156 0.594488 -0.545401 0.020024 0.0 +0.000408 -0.097868 0.597478 -0.545328 0.008198 0.0 +0.004174 -0.0935 0.598834 -0.544378 0.008957 0.0 +0.000124 -0.10982 0.598623 -0.54506 0.010857 0.0 +-2.4e-05 -0.100244 0.596971 -0.543442 0.009555 0.0 +0.00049 -0.099412 0.596089 -0.544081 0.010121 0.0 +0.003977 -0.097709 0.599839 -0.546678 0.005992 0.0 +0.000392 -0.101482 0.600658 -0.543516 0.006097 0.0 +-0.000576 -0.105028 0.597527 -0.545371 0.011414 0.0 +0.001856 -0.115441 0.599357 -0.544555 0.005366 0.0 +-0.002226 -0.097961 0.599653 -0.546821 0.023226 0.0 +-0.000973 -0.087725 0.598969 -0.544318 0.0161 0.0 +-0.001625 -0.091748 0.595057 -0.546005 0.008043 0.0 +0.002042 -0.106301 0.60225 -0.540607 0.009528 0.0 +-0.005096 -0.109519 0.59877 -0.546886 0.004772 0.0 +-0.000709 -0.105398 0.597242 -0.541384 0.009811 0.0 +-0.00491 -0.11477 0.598933 -0.547623 0.013117 0.0 +0.001075 -0.100639 0.597033 -0.547999 0.010002 0.0 +-0.003462 -0.094971 0.599729 -0.546782 0.008578 0.0 +0.003693 -0.092517 0.59902 -0.54475 0.010469 0.0 +-0.003494 -0.091356 0.604396 -0.547123 0.014196 0.0 +-0.003562 -0.090231 0.601355 -0.54503 0.011807 0.0 +-0.006779 -0.095181 0.600323 -0.542251 0.010345 0.0 +-0.001827 -0.080067 0.599517 -0.548193 0.016288 0.0 +-0.000341 -0.099015 0.596672 -0.545833 0.010605 0.0 +0.002277 -0.099716 0.598636 -0.542773 0.013881 0.0 +0.000774 -0.095422 0.599392 -0.53908 0.007308 0.0 +0.001108 -0.104546 0.598497 -0.545237 0.004672 0.0 +0.00201 -0.088007 0.598276 -0.542553 0.009167 0.0 +-0.00266 -0.091956 0.59774 -0.548163 0.007723 0.0 +0.00049 -0.102041 0.59779 -0.550256 0.012702 0.0 +-0.000741 -0.107602 0.598212 -0.549483 0.011204 0.0 +0.00201 -0.099324 0.602012 -0.54604 0.005312 0.0 +-0.006828 -0.09669 0.600025 -0.546143 0.012734 0.0 +-0.001128 -0.102591 0.602 -0.546605 0.016453 0.0 +0.002726 -0.102161 0.598449 -0.549557 0.016118 0.0 +0.000359 -0.101822 0.604272 -0.551951 0.008897 0.0 +-0.000678 -0.094929 0.599516 -0.545431 0.00274 0.0 +0.003928 -0.089431 0.601826 -0.545966 0.009331 0.0 +-0.004795 -0.107065 0.602932 -0.545224 0.010665 0.0 +0.001441 -0.098013 0.596138 -0.548163 0.020316 0.0 +-0.002609 -0.106025 0.596835 -0.548888 0.018046 0.0 +-0.001259 -0.100025 0.601355 -0.54415 0.019659 0.0 +9e-06 -0.092643 0.600759 -0.540223 0.008687 0.0 +-0.005462 -0.087974 0.596161 -0.543563 0.016631 0.0 +-0.000559 -0.103278 0.598313 -0.541069 0.011898 0.0 +0.000825 -0.111285 0.600013 -0.548763 0.016584 0.0 +-0.003942 -0.0874 0.59969 -0.546946 0.014785 0.0 +-0.001958 -0.083706 0.599095 -0.547861 0.006207 0.0 +-0.006511 -0.103661 0.597021 -0.550092 -0.000137 0.0 +0.001376 -0.111276 0.603354 -0.547183 0.001196 0.0 +-0.000925 -0.085811 0.599578 -0.546842 0.001521 0.0 +-0.002106 -0.106055 0.599554 -0.541216 0.009432 0.0 +0.001324 -0.103497 0.598202 -0.539714 0.011373 0.0 +-0.000842 -0.107232 0.601803 -0.543101 0.01061 0.0 +-0.00204 -0.109294 0.602918 -0.5461 0.006705 0.0 +0.003973 -0.110565 0.598921 -0.544215 0.009971 0.0 +-0.002844 -0.101663 0.604482 -0.540978 0.005951 0.0 +-0.001592 -0.094064 0.605277 -0.545065 0.012268 0.0 +-0.005243 -0.100825 0.602547 -0.544391 0.01241 0.0 +0.003726 -0.091405 0.598224 -0.543645 0.02192 0.0 +0.003092 -0.101572 0.599107 -0.544012 0.009564 0.0 +-0.003456 -0.105337 0.601268 -0.545341 0.012506 0.0 +-0.005313 -0.106205 0.599914 -0.547688 0.012643 0.0 +-0.003795 -0.088903 0.602944 -0.545626 0.00612 0.0 +-0.000273 -0.101641 0.596599 -0.545846 0.013397 0.0 +-0.001443 -0.098366 0.598561 -0.545121 0.012428 0.0 +-0.004309 -0.099505 0.601158 -0.546143 0.006865 0.0 +-0.003112 -0.088103 0.600709 -0.545151 0.00665 0.0 +-0.00121 -0.101014 0.599827 -0.544215 0.007289 0.0 +-0.003024 -0.088911 0.60159 -0.546812 0.015539 0.0 +-0.000844 -0.102848 0.600374 -0.548508 0.015055 0.0 +-0.004159 -0.101077 0.598561 -0.547446 0.017179 0.0 +-0.003828 -0.096622 0.597416 -0.542596 0.018425 0.0 +-0.001926 -0.092421 0.598793 -0.542696 0.015882 0.0 +-0.002177 -0.092607 0.604396 -0.546277 0.010131 0.0 +0.001425 -0.09657 0.598474 -0.546312 0.010857 0.0 +-0.003543 -0.090882 0.597068 -0.544866 0.014109 0.0 +-0.004677 -0.103768 0.60067 -0.544378 0.012812 0.0 +-0.002128 -0.09764 0.6001 -0.545971 0.003251 0.0 +-0.005161 -0.109179 0.601404 -0.540698 0.0066 0.0 +-0.002063 -0.086627 0.601541 -0.543606 0.005801 0.0 +0.003693 -0.098336 0.598212 -0.548905 0.016813 0.0 +-0.002494 -0.090628 0.597132 -0.544409 0.017854 0.0 +-0.003779 -0.105934 0.602087 -0.544426 0.01447 0.0 +-0.000958 -0.089423 0.599342 -0.547969 0.008578 0.0 +-0.001827 -0.086551 0.603354 -0.539524 0.017288 0.0 +0.001009 -0.095272 0.596672 -0.544184 0.011848 0.0 +-0.003026 -0.089921 0.605091 -0.545669 0.015968 0.0 +-0.001543 -0.086866 0.601193 -0.539684 0.011254 0.0 +0.000189 -0.099469 0.6001 -0.543908 0.008153 0.0 +-0.001592 -0.101479 0.602796 -0.544322 0.007833 0.0 +-0.005276 -0.107008 0.598026 -0.542834 0.013721 0.0 +4.2e-05 -0.10683 0.59394 -0.543779 0.007239 0.0 +-0.00051 -0.098517 0.597044 -0.542152 0.011532 0.0 +-0.001007 -0.098706 0.600449 -0.545669 0.016872 0.0 +0.001294 -0.100622 0.601553 -0.545919 0.00527 0.0 +-0.003073 -0.101758 0.598584 -0.545492 0.011514 0.0 +-0.001308 -0.100247 0.599531 -0.544215 0.003704 0.0 +0.001275 -0.088963 0.600757 -0.544957 0.012168 0.0 +0.001474 -0.102969 0.598164 -0.546886 0.010277 0.0 +-0.000975 -0.111096 0.600374 -0.540581 0.009866 0.0 +-0.004112 -0.10152 0.594263 -0.548637 0.008038 0.0 +-0.003079 -0.096252 0.595121 -0.544586 0.019554 0.0 +-0.003429 -0.095083 0.603714 -0.54503 0.006011 0.0 +-0.00204 -0.095581 0.596893 -0.546916 0.010099 0.0 +-0.000658 -0.093823 0.600484 -0.54141 0.004663 0.0 +0.002327 -0.099415 0.603627 -0.541854 0.006175 0.0 +0.000506 -0.087463 0.600699 -0.544793 0.007805 0.0 +0.001725 -0.104417 0.597405 -0.547628 0.010861 0.0 +-0.004392 -0.104869 0.597515 -0.543028 0.014255 0.0 +-0.000608 -0.105707 0.598375 -0.547865 0.008454 0.0 +-0.003711 -0.103549 0.600188 -0.547822 0.01051 0.0 +-0.00021 -0.087767 0.601551 -0.542937 0.010825 0.0 +-0.003611 -0.107172 0.599688 -0.545802 0.01088 0.0 +0.003595 -0.108092 0.601344 -0.545634 0.01415 0.0 +-0.006796 -0.096159 0.599341 -0.545341 0.014041 0.0 +0.000124 -0.093875 0.596039 -0.545181 0.016895 0.0 +7.5e-05 -0.104294 0.600658 -0.552318 0.011885 0.0 +-0.000993 -0.083462 0.597776 -0.545358 0.004384 0.0 +-0.001827 -0.093536 0.59845 -0.548007 0.011574 0.0 +-0.004844 -0.100545 0.602049 -0.542316 0.012894 0.0 +0.001042 -0.097038 0.60354 -0.544111 0.012588 0.0 +-0.005411 -0.110189 0.600209 -0.543162 0.009336 0.0 +-0.002376 -0.087213 0.601017 -0.54377 0.007129 0.0 +-0.006227 -0.105655 0.599465 -0.548059 0.004937 0.0 +-0.002128 -0.098169 0.597962 -0.541513 0.005385 0.0 +0.00111 -0.092577 0.596347 -0.544896 0.000717 0.0 +0.001991 -0.110529 0.602347 -0.541552 0.005042 0.0 +-0.002877 -0.103174 0.600883 -0.543503 0.013401 0.0 +-0.00186 -0.084648 0.602647 -0.548814 0.008724 0.0 +-0.000343 -0.091937 0.601691 -0.546989 0.001401 0.0 +-0.002175 -0.096318 0.598795 -0.544745 0.018695 0.0 +9e-06 -0.084889 0.598474 -0.541172 0.026126 0.0 +0.004759 -0.097799 0.601404 -0.548905 2.3e-05 0.0 +0.003392 -0.094343 0.597591 -0.543442 0.011464 0.0 +-0.001494 -0.099595 0.597877 -0.540102 0.020481 0.0 +-0.005311 -0.089672 0.601204 -0.546773 0.014735 0.0 +-0.003593 -0.099376 0.603974 -0.544853 0.015374 0.0 +-0.002478 -0.105929 0.601204 -0.543412 0.015484 0.0 +0.001359 -0.099565 0.599752 -0.543028 0.009432 0.0 +-0.000625 -0.094494 0.602198 -0.543947 0.005901 0.0 +0.003844 -0.108711 0.603801 -0.544599 0.010236 0.0 +-0.000658 -0.094064 0.603104 -0.543472 0.013876 0.0 +-0.000608 -0.093109 0.601268 -0.546411 0.013826 0.0 +-0.003779 -0.100274 0.598235 -0.546174 0.007271 0.0 +0.002028 -0.100584 0.600374 -0.547895 0.015159 0.0 +-0.005194 -0.094193 0.600571 -0.547213 0.009277 0.0 +-0.006178 -0.095173 0.602186 -0.544793 0.015314 0.0 +0.002192 -0.099694 0.602645 -0.541423 0.011798 0.0 +0.00276 -0.086028 0.598572 -0.543196 0.010423 0.0 +-0.002114 -0.086337 0.598995 -0.546747 0.012967 0.0 +-0.000676 -0.0966 0.595853 -0.545129 0.020476 0.0 +-0.000394 -0.098544 0.600374 -0.545802 0.006385 0.0 +-0.001259 -0.107821 0.598326 -0.543429 0.016767 0.0 +-0.001844 -0.098949 0.601826 -0.544245 0.026921 0.0 +-0.002893 -0.101693 0.598572 -0.546342 0.009596 0.0 +-0.001394 -0.103949 0.596016 -0.546678 0.013781 0.0 +-0.002826 -0.101263 0.599914 -0.543982 0.005486 0.0 +-0.001341 -0.086835 0.599903 -0.539584 0.013342 0.0 +-0.001942 -0.097369 0.598386 -0.545362 0.005262 0.0 +-0.004462 -0.098766 0.604818 -0.545975 0.007121 0.0 +-0.003777 -0.085173 0.599268 -0.542217 0.013218 0.0 +0.000293 -0.090245 0.602024 -0.544616 0.020709 0.0 +-0.001523 -0.102813 0.601716 -0.545906 0.021167 0.0 +-0.006561 -0.101761 0.599814 -0.5455 0.007774 0.0 +-0.003625 -0.092547 0.599467 -0.551339 0.017731 0.0 +-0.000793 -0.089522 0.600275 -0.543714 0.018435 0.0 +0.00021 -0.102591 0.595865 -0.540577 0.0142 0.0 +0.002674 -0.111986 0.597008 -0.549276 0.009277 0.0 +0.003261 -0.095324 0.600323 -0.538946 0.017663 0.0 +-0.002911 -0.098078 0.60153 -0.544758 0.014018 0.0 +-0.000658 -0.092117 0.604598 -0.544361 0.007833 0.0 +-0.003795 -0.104508 0.599876 -0.545677 0.013506 0.0 +-0.001059 -0.091726 0.599951 -0.545134 0.01611 0.0 +0.002376 -0.088533 0.599554 -0.551373 0.013273 0.0 +0.004076 -0.094683 0.603775 -0.545802 0.011304 0.0 +-0.000909 -0.09014 0.600807 -0.543235 0.028698 0.0 +-0.000745 -0.090609 0.597195 -0.543503 0.015324 0.0 +-0.001911 -0.095819 0.601067 -0.541151 0.01907 0.0 +-0.002243 -0.094133 0.600013 -0.542804 0.007239 0.0 +-0.003527 -0.087824 0.599403 -0.545604 0.017791 0.0 +-0.000827 -0.106334 0.597341 -0.544482 0.012438 0.0 +-0.003711 -0.097832 0.59686 -0.541781 0.007089 0.0 +-0.004544 -0.108251 0.600397 -0.544586 0.014922 0.0 +-0.000876 -0.113103 0.59517 -0.546851 0.006965 0.0 +0.002742 -0.102714 0.60056 -0.54604 0.019193 0.0 +0.001274 -0.102621 0.602163 -0.546877 0.010834 0.0 +-0.001527 -0.101118 0.599653 -0.54276 0.008235 0.0 +-0.005395 -0.086745 0.599827 -0.547835 0.014415 0.0 +0.001993 -0.095176 0.599543 -0.544646 0.007719 0.0 +-0.000576 -0.092328 0.598375 -0.544823 0.007312 0.0 +-0.001292 -0.104384 0.597056 -0.541617 0.017142 0.0 +0.001625 -0.084897 0.601007 -0.545919 0.003928 0.0 +-0.005478 -0.077925 0.601443 -0.543339 0.022984 0.0 +-0.005528 -0.113563 0.601553 -0.544525 0.008792 0.0 +0.002376 -0.100034 0.592375 -0.547563 0.019065 0.0 +0.002124 -0.100053 0.599206 -0.543813 0.015141 0.0 +0.001075 -0.107128 0.601999 -0.544081 0.016128 0.0 +-0.002292 -0.105745 0.598921 -0.541039 0.017576 0.0 +-0.003811 -0.088257 0.600275 -0.550864 0.014625 0.0 +0.000261 -0.098547 0.602756 -0.542329 0.011309 0.0 +0.001026 -0.106635 0.601293 -0.546812 0.017466 0.0 +-0.008812 -0.10201 0.59845 -0.547434 0.00655 0.0 +-0.000991 -0.096282 0.59809 -0.546411 0.008952 0.0 +-0.003906 -0.102041 0.599479 -0.542743 0.017663 0.0 +-0.000526 -0.101151 0.596237 -0.547153 0.0068 0.0 +-0.004511 -0.087145 0.598323 -0.549082 0.008368 0.0 +-0.00076 -0.114089 0.598648 -0.54264 0.013177 0.0 +-0.001658 -0.097525 0.598909 -0.546916 0.008902 0.0 +-0.002161 -0.085078 0.597805 -0.547718 0.010372 0.0 +-0.004161 -0.0962 0.601257 -0.543869 0.012697 0.0 +-0.000193 -0.090784 0.602721 -0.543723 0.01431 0.0 +-0.001893 -0.08993 0.598474 -0.543364 0.006061 0.0 +-0.000807 -0.088133 0.599479 -0.54264 0.007614 0.0 +0.002092 -0.105367 0.600809 -0.55089 0.010085 0.0 +-0.001559 -0.100055 0.600484 -0.542696 0.015639 0.0 +-0.002795 -0.088593 0.598387 -0.540698 0.017247 0.0 +-0.00256 -0.106487 0.600844 -0.546916 0.010473 0.0 +9e-06 -0.089675 0.599043 -0.550329 0.005042 0.0 +-0.001576 -0.090617 0.605452 -0.547451 0.008902 0.0 +-0.001357 -0.09568 0.603093 -0.548064 0.012802 0.0 +0.002392 -0.096044 0.602163 -0.542256 0.012268 0.0 +-0.00526 -0.097243 0.601542 -0.544646 0.020577 0.0 +0.003513 -0.101485 0.601865 -0.542536 0.017786 0.0 +0.001042 -0.105466 0.594994 -0.540668 0.013575 0.0 +-0.002609 -0.100124 0.599928 -0.544417 0.015534 0.0 +0.003075 -0.096501 0.599032 -0.542968 0.008413 0.0 +0.004192 -0.102909 0.599417 -0.54982 0.008368 0.0 +0.00201 -0.103399 0.598636 -0.549203 0.021107 0.0 +-0.003227 -0.096041 0.60103 -0.541039 0.012323 0.0 +-0.000741 -0.101633 0.597131 -0.544689 0.013232 0.0 +-0.002713 -0.107013 0.602994 -0.543947 0.006655 0.0 +0.002993 -0.103278 0.601268 -0.544465 0.010469 0.0 +-0.003294 -0.093938 0.600223 -0.535947 0.020248 0.0 +-0.001926 -0.090937 0.599566 -0.542963 0.012145 0.0 +-0.003762 -0.096841 0.598162 -0.545134 0.01658 0.0 +-0.001658 -0.103708 0.59866 -0.546376 0.015813 0.0 +-0.004194 -0.08746 0.600136 -0.546381 0.010825 0.0 +-7e-06 -0.105874 0.597556 -0.545431 0.012401 0.0 +-0.003824 -0.082112 0.599031 -0.546515 0.012917 0.0 +-0.004945 -0.109488 0.601158 -0.539494 0.004142 0.0 +-0.00016 -0.10413 0.603552 -0.543248 0.015594 0.0 +0.001676 -0.10976 0.599566 -0.545768 0.016375 0.0 +-0.004178 -0.095483 0.601506 -0.542998 0.00638 0.0 +-0.00106 -0.096561 0.60257 -0.545462 0.008318 0.0 +-0.008364 -0.107605 0.599914 -0.547494 0.011359 0.0 +-0.00151 -0.10376 0.598088 -0.545224 0.008897 0.0 +-0.001044 -0.100184 0.603192 -0.54459 0.01721 0.0 +-0.001744 -0.110748 0.601443 -0.541216 0.014593 0.0 +0.000895 -0.082783 0.602285 -0.551063 0.011469 0.0 +-0.002345 -0.100644 0.59805 -0.544378 0.008098 0.0 +0.003276 -0.070001 0.600984 -0.547153 0.013237 0.0 +0.002591 -0.080217 0.599045 -0.543744 0.016809 0.0 +-0.003576 -0.091893 0.59871 -0.546752 0.003069 0.0 +-0.001161 -0.108651 0.603765 -0.542419 0.012647 0.0 +-0.00176 -0.099653 0.600025 -0.548206 0.009966 0.0 +-0.000576 -0.095954 0.599343 -0.54289 0.01436 0.0 +-0.003194 -0.090976 0.597443 -0.545298 0.013017 0.0 +0.007311 -0.103339 0.601826 -0.543339 0.01881 0.0 +0.001977 -0.102802 0.595156 -0.542661 0.015109 0.0 +-0.006544 -0.09051 0.601664 -0.546385 0.01848 0.0 +0.002126 -0.096131 0.600672 -0.546722 0.015584 0.0 +-0.004711 -0.098736 0.59974 -0.545556 0.015699 0.0 +7.5e-05 -0.109269 0.601169 -0.541617 0.008088 0.0 +-0.000206 -0.102191 0.59934 -0.549626 0.01061 0.0 +-0.005462 -0.107232 0.598249 -0.547585 0.012168 0.0 +0.000743 -0.100784 0.598933 -0.543912 0.010989 0.0 +-0.002059 -0.094683 0.598026 -0.543308 0.013666 0.0 +-0.002875 -0.097799 0.598747 -0.542553 0.019659 0.0 +-0.002478 -0.092646 0.599206 -0.54516 -0.000558 0.0 +0.004127 -0.089083 0.600112 -0.546782 0.00655 0.0 +-0.001895 -0.094404 0.594151 -0.551175 0.005157 0.0 +-0.001275 -0.110627 0.600745 -0.548551 0.010573 0.0 +-0.00086 -0.08241 0.598758 -0.542212 0.011537 0.0 +7.5e-05 -0.086337 0.600002 -0.541263 0.008632 0.0 +-0.002746 -0.102128 0.596475 -0.544046 0.021015 0.0 +-0.004309 -0.087137 0.597741 -0.545462 0.006974 0.0 +0.002042 -0.076083 0.60128 -0.547153 0.006828 0.0 +-0.000793 -0.105359 0.595667 -0.542998 0.014205 0.0 +-0.000377 -0.092109 0.602274 -0.547792 0.016932 0.0 +-0.001377 -0.098484 0.597904 -0.54563 0.00665 0.0 +-0.000527 -0.085477 0.596736 -0.545354 0.01035 0.0 +-0.00291 -0.100562 0.600571 -0.543904 0.014515 0.0 +-0.00421 -0.094524 0.60533 -0.5484 0.014415 0.0 +0.001476 -0.10764 0.601044 -0.543442 0.010281 0.0 +9.1e-05 -0.097104 0.604049 -0.545699 0.008258 0.0 +0.002257 -0.089061 0.604135 -0.548918 0.01462 0.0 +0.003059 -0.110778 0.599479 -0.542061 0.016343 0.0 +-0.001607 -0.09338 0.599965 -0.541293 0.013931 0.0 +0.001108 -0.097648 0.600647 -0.546976 0.006765 0.0 +-0.004276 -0.094951 0.598251 -0.544715 0.011953 0.0 +-0.000676 -0.102062 0.597939 -0.54519 0.014392 0.0 +0.001175 -0.107752 0.603378 -0.544124 0.011359 0.0 +0.001326 -0.106682 0.598572 -0.545505 0.010555 0.0 +-0.004462 -0.098139 0.600722 -0.543666 0.015159 0.0 +0.000359 -0.094343 0.59938 -0.544957 0.008682 0.0 +0.000643 -0.087052 0.60066 -0.542696 0.019389 0.0 +7.5e-05 -0.088593 0.596847 -0.544482 0.013506 0.0 +-7.3e-05 -0.103678 0.598038 -0.542877 0.015963 0.0 +-0.000794 -0.093476 0.602024 -0.545936 0.01854 0.0 +-0.005047 -0.085288 0.600321 -0.539019 0.013876 0.0 +-0.00221 -0.092947 0.600112 -0.548266 0.010642 0.0 +-0.003726 -0.100365 0.596027 -0.547598 0.015808 0.0 +0.001376 -0.09724 0.602198 -0.545526 0.006225 0.0 +-0.005462 -0.101753 0.602026 -0.545708 0.01341 0.0 +-0.002593 -0.09712 0.600213 -0.545802 0.017457 0.0 +-0.003811 -0.091477 0.602273 -0.544853 0.018713 0.0 +-0.00059 -0.100455 0.601576 -0.540784 0.018613 0.0 +-0.000942 -0.09703 0.603801 -0.542523 0.013684 0.0 +-0.000674 -0.107687 0.595158 -0.540836 0.007938 0.0 +-0.001308 -0.098547 0.595505 -0.547792 0.018206 0.0 +-0.001275 -0.095858 0.604483 -0.544689 0.013132 0.0 +-0.005278 -0.094962 0.602074 -0.549884 0.010012 0.0 +-0.010179 -0.102884 0.598968 -0.546411 0.014717 0.0 +-0.001543 -0.0962 0.604522 -0.543278 0.013721 0.0 +-0.002226 -0.087862 0.598474 -0.54623 0.011684 0.0 +-0.003378 -0.1093 0.599008 -0.541203 0.01901 0.0 +-0.006495 -0.086217 0.603664 -0.541487 0.017297 0.0 +0.002726 -0.09169 0.601268 -0.545897 0.01447 0.0 +-0.007397 -0.093106 0.602883 -0.547792 0.009856 0.0 +-0.007612 -0.085318 0.598348 -0.54503 0.013022 0.0 +-0.001776 -0.09663 0.600968 -0.552102 0.008025 0.0 +-0.003611 -0.093144 0.602262 -0.539718 0.00565 0.0 +-0.002959 -0.09051 0.605515 -0.546454 0.015863 0.0 +-0.002128 -0.100326 0.601231 -0.547585 0.008353 0.0 +-0.001691 -0.094012 0.597875 -0.545738 0.0104 0.0 +0.00111 -0.111435 0.601826 -0.547895 0.011304 0.0 +-0.000576 -0.096474 0.599355 -0.538842 0.007075 0.0 +-0.001625 -0.097345 0.599479 -0.543205 0.01045 0.0 +-0.001543 -0.094705 0.600013 -0.543278 0.012492 0.0 +-0.000827 -0.104138 0.598882 -0.54459 0.006485 0.0 +-0.005646 -0.099409 0.59912 -0.545254 0.012003 0.0 +0.003709 -0.096471 0.600745 -0.544883 0.016863 0.0 +-0.002758 -0.104598 0.600385 -0.551559 0.005252 0.0 +-0.002713 -0.083719 0.599479 -0.541552 0.009856 0.0 +-0.007982 -0.104697 0.595156 -0.545742 0.013776 0.0 +-0.001592 -0.096318 0.600955 -0.5427 0.016142 0.0 +-0.000958 -0.092646 0.597393 -0.549854 0.009112 0.0 +-0.005096 -0.10281 0.598857 -0.543904 0.015306 0.0 +-0.004096 -0.087046 0.604621 -0.545936 0.015114 0.0 +-0.003259 -0.088662 0.598334 -0.545388 0.013661 0.0 +-0.004861 -0.101452 0.599653 -0.547999 0.022422 0.0 +-0.003863 -0.095973 0.599729 -0.543675 0.026191 0.0 +-0.004511 -0.101756 0.601478 -0.543192 0.013182 0.0 +-0.004445 -0.109858 0.599837 -0.546234 0.024363 0.0 +-0.001343 -0.086866 0.598224 -0.544245 0.013766 0.0 +-0.006812 -0.103558 0.595418 -0.543606 0.012373 0.0 +-0.00456 -0.10899 0.597852 -0.541915 0.019399 0.0 +-0.003544 -0.103738 0.598834 -0.541039 0.011903 0.0 +-0.003308 -0.093295 0.596027 -0.548029 0.010642 0.0 +-0.003359 -0.101263 0.604063 -0.543636 0.009756 0.0 +-0.005276 -0.090699 0.599752 -0.545462 0.010655 0.0 +-0.00573 -0.097468 0.600809 -0.546368 0.016653 0.0 +-0.000991 -0.09252 0.602523 -0.544327 0.018763 0.0 +-0.005362 -0.077909 0.600124 -0.546752 0.018046 0.0 +0.001075 -0.112643 0.602796 -0.544007 0.01098 0.0 +-0.001161 -0.088163 0.601379 -0.545328 0.006723 0.0 +-0.002161 -0.104357 0.598758 -0.540637 0.01489 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 0.0 0.0 0.0 0.0 \ No newline at end of file From b61885644c9140f18527f56ca316a4771c55c7d2 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 20 Jul 2025 22:12:21 +0200 Subject: [PATCH 46/70] fix: tests position --- .../DataAggregator.Processor.Tests.csproj | 0 .../ProcessorTestHelper.cs | 0 .../ActuatorCurrentFeatureExtractorTests.cs | 0 .../Services/PreProcessing/MathUtilsTests.cs | 0 .../PreprocessingStrategyFactoryTests.cs | 0 .../Prediction/MachinePredictionProcessorTests.cs | 0 .../Prediction/OnnxPredictionEngineTests.cs | 0 .../Services/PredictionBackgroundServiceTests.cs | 0 .../Registration/RegistrationServiceClientTests.cs | 0 .../resources/opencn_model.onnx | Bin 10 files changed, 0 insertions(+), 0 deletions(-) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/DataAggregator.Processor.Tests.csproj (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/ProcessorTestHelper.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/Services/PreProcessing/MathUtilsTests.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/Services/Prediction/MachinePredictionProcessorTests.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/Services/Prediction/OnnxPredictionEngineTests.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/Services/PredictionBackgroundServiceTests.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/Services/Registration/RegistrationServiceClientTests.cs (100%) rename {DataAggregator.Processor.Tests => tests/DataAggregator.Processor.Tests}/resources/opencn_model.onnx (100%) diff --git a/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj b/tests/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj similarity index 100% rename from DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj rename to tests/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj diff --git a/DataAggregator.Processor.Tests/ProcessorTestHelper.cs b/tests/DataAggregator.Processor.Tests/ProcessorTestHelper.cs similarity index 100% rename from DataAggregator.Processor.Tests/ProcessorTestHelper.cs rename to tests/DataAggregator.Processor.Tests/ProcessorTestHelper.cs diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs similarity index 100% rename from DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs rename to tests/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs similarity index 100% rename from DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs rename to tests/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs diff --git a/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs similarity index 100% rename from DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs rename to tests/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs diff --git a/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs similarity index 100% rename from DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs rename to tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs diff --git a/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs b/tests/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs similarity index 100% rename from DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs rename to tests/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs diff --git a/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs similarity index 100% rename from DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs rename to tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs diff --git a/DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs b/tests/DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs similarity index 100% rename from DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs rename to tests/DataAggregator.Processor.Tests/Services/Registration/RegistrationServiceClientTests.cs diff --git a/DataAggregator.Processor.Tests/resources/opencn_model.onnx b/tests/DataAggregator.Processor.Tests/resources/opencn_model.onnx similarity index 100% rename from DataAggregator.Processor.Tests/resources/opencn_model.onnx rename to tests/DataAggregator.Processor.Tests/resources/opencn_model.onnx From 50fec2d3b10eee7128b049b657899e3f5ea25064 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 20 Jul 2025 22:12:28 +0200 Subject: [PATCH 47/70] feat: add influx ui --- docker-compose.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 553782a..4bd748c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,6 +55,17 @@ services: volumes: - influx_data:/var/lib/influxdb3 + influxdb-ui: + image: influxdata/influxdb3-ui:1.0.0 + container_name: influxdb3-explorer + ports: + - "8888:80" + environment: + INFLUXDB_URL: http://influxdb:8181 + command: ["--mode=admin"] + depends_on: + - influxdb + volumes: postgres_data: influx_data: From afb95c6c8ee89544d66d1807bcc0c34a10f447b9 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 20 Jul 2025 22:13:07 +0200 Subject: [PATCH 48/70] fix: fix processor --- .../CapnProto/CapnProtoConfig.cs | 5 --- src/DataAggregator.Collector/appsettings.json | 7 ++- .../PredictionServiceConfiguration.cs | 5 --- src/DataAggregator.Processor/Program.cs | 22 +++++----- .../Properties/launchSettings.json | 4 +- .../DataStorage/InfluxV3Repository.cs | 15 ++++--- .../Prediction/MachinePredictionProcessor.cs | 3 +- .../Services/PredictionBackgroundService.cs | 43 +++++++++++-------- .../Registration/RegistrationServiceClient.cs | 2 +- src/DataAggregator.Processor/appsettings.json | 16 +++---- 10 files changed, 56 insertions(+), 66 deletions(-) diff --git a/src/DataAggregator.Collector.OpenCNCapnProtoConnector/CapnProto/CapnProtoConfig.cs b/src/DataAggregator.Collector.OpenCNCapnProtoConnector/CapnProto/CapnProtoConfig.cs index fff0b29..9cadd28 100644 --- a/src/DataAggregator.Collector.OpenCNCapnProtoConnector/CapnProto/CapnProtoConfig.cs +++ b/src/DataAggregator.Collector.OpenCNCapnProtoConnector/CapnProto/CapnProtoConfig.cs @@ -14,9 +14,4 @@ public class CapnProtoConfig /// Gets or sets the server port. /// public int Port { get; set; } = 7001; - - /// - /// Gets or sets the connection timeout in milliseconds. - /// - public int TimeoutMs { get; set; } = 5000; } diff --git a/src/DataAggregator.Collector/appsettings.json b/src/DataAggregator.Collector/appsettings.json index efab06c..e7cb0b7 100644 --- a/src/DataAggregator.Collector/appsettings.json +++ b/src/DataAggregator.Collector/appsettings.json @@ -17,14 +17,13 @@ "AllowedHosts": "*", "CollectorType": "OpenCN", "Collector": { - "DeviceName": "Micro5_1", - "Location": "Manufacturing Floor - Section A", + "DeviceName": "Micro5", + "Location": "Test", "HealthCheckEndpoint": "http://localhost:5000/health", "SamplingRate": 100, "CapnProto": { "ServerAddress": "192.168.53.15", - "Port": 7002, - "TimeoutMs": 5000 + "Port": 7002 }, "RegistrationService": { "Endpoint": "http://localhost:5137/api/DeviceRegistration/register" diff --git a/src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs b/src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs index 826000b..f082bec 100644 --- a/src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs +++ b/src/DataAggregator.Processor/Configuration/PredictionServiceConfiguration.cs @@ -10,11 +10,6 @@ public class PredictionServiceConfiguration /// public string RegistrationServiceUrl { get; set; } = "http://localhost:5001"; - /// - /// Gets or sets the global cycle interval in seconds. - /// - public int GlobalCycleIntervalSeconds { get; set; } = 1; - /// /// Gets or sets the list of machine prediction configurations. /// diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index 5407ddd..d71ea41 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -22,26 +22,24 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new() { Title = "DataAggregator Processor API", Version = "v1" })); -// Configure HTTP clients -builder.Services.AddHttpClient(); -builder.Services.AddHttpClient("RegistrationClient", client => -{ - string registrationEndpoint = builder.Configuration["RegistrationService:Endpoint"] ?? "http://localhost:5001"; - client.BaseAddress = new Uri(registrationEndpoint); - client.DefaultRequestHeaders.Add("Accept", "application/json"); -}); - // Register health checks builder.Services.AddHealthChecks(); // Configure prediction service builder.Services.Configure(builder.Configuration.GetSection("PredictionService")); +// Configure HTTP clients +builder.Services.AddHttpClient("RegistrationClient", client => +{ + string registrationEndpoint = builder.Configuration["PredictionService:RegistrationServiceUrl"] ?? "http://localhost:5001"; + client.BaseAddress = new Uri(registrationEndpoint); + client.DefaultRequestHeaders.Add("Accept", "application/json"); +}); + // Register services builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); // Register background service diff --git a/src/DataAggregator.Processor/Properties/launchSettings.json b/src/DataAggregator.Processor/Properties/launchSettings.json index 23512d3..6a3bac3 100644 --- a/src/DataAggregator.Processor/Properties/launchSettings.json +++ b/src/DataAggregator.Processor/Properties/launchSettings.json @@ -4,7 +4,7 @@ "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "http://localhost:5148", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -13,7 +13,7 @@ "https": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:7173;http://localhost:5148", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index 0128799..4414e1b 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -52,13 +52,14 @@ public async Task> QueryMeasurementsAsync(string table, D try { - // Build the Flux query - no pivot needed since we want the original structure - string sensorFilter = string.Join(" or ", sensors.Select(s => $"r[\"_field\"] == \"{s.SensorName}\"")); - string query = $@" - from(bucket: ""{_database}"") - |> range(start: {startTime:yyyy-MM-ddTHH:mm:ssZ}, stop: {endTime:yyyy-MM-ddTHH:mm:ssZ}) - |> filter(fn: (r) => r[""_measurement""] == ""{table}"") - |> filter(fn: (r) => {sensorFilter})"; + string sensorColumns = string.Join(", ", sensors.Select(s => $"\"{s.SensorName}\"")); + + string query = $""" + SELECT time, {sensorColumns} + FROM "{table}" + WHERE time >= '{startTime:yyyy-MM-ddTHH:mm:ssZ}' + AND time < '{endTime:yyyy-MM-ddTHH:mm:ssZ}' + """; var measurements = new List(); var sensorDict = sensors.ToDictionary(s => s.SensorName, s => s); diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index 58d8032..b69792d 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -104,7 +104,8 @@ public async Task ProcessAsync(MachinePredictionConfig config) } // Perform prediction - IEnumerable predictions = await predictionEngine.PredictAsync(config.ModelPath, preprocessedData); + string fullPath = Path.Combine(AppContext.BaseDirectory, config.ModelPath); + IEnumerable predictions = await predictionEngine.PredictAsync(fullPath, preprocessedData); if (predictions == null || !predictions.Any()) { diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs index 6a553b2..796456a 100644 --- a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -12,15 +12,15 @@ namespace DataAggregator.Processor.Services; /// Initializes a new instance of the class. /// /// The prediction service configuration. -/// The machine prediction processor. +/// The service provider. public class PredictionBackgroundService( IOptions configuration, - IMachinePredictionProcessor predictionProcessor) : BackgroundService + IServiceProvider serviceProvider) : BackgroundService { #region Private fields private readonly Dictionary _machineTimers = []; - private readonly Dictionary _machineErrors = []; + private readonly Dictionary _machineLocks = []; #endregion @@ -97,7 +97,8 @@ private void ValidateConfigurationAsync() try { // Check if ONNX model file exists - if (!File.Exists(machineConfig.ModelPath)) + string fullPath = Path.Combine(AppContext.BaseDirectory, machineConfig.ModelPath); + if (!File.Exists(fullPath)) { Log.Error( "ONNX model file not found for machine {MachineName}: {ModelPath}", @@ -151,10 +152,18 @@ private void ScheduleMachine(MachinePredictionConfig machineConfig) { var interval = TimeSpan.FromSeconds(machineConfig.CycleIntervalSeconds); - var timer = new Timer(async _ => await ProcessMachineAsync(machineConfig), null, TimeSpan.Zero, interval); + if (!_machineLocks.ContainsKey(machineConfig.MachineName)) + { + _machineLocks[machineConfig.MachineName] = new SemaphoreSlim(1, 1); + } + + using IServiceScope scope = serviceProvider.CreateScope(); + IMachinePredictionProcessor predictionProcessor + = scope.ServiceProvider.GetRequiredService(); + + var timer = new Timer(async _ => await ProcessMachineAsync(machineConfig, predictionProcessor), null, TimeSpan.Zero, interval); _machineTimers[machineConfig.MachineName] = timer; - _machineErrors[machineConfig.MachineName] = false; Log.Information( "Scheduled prediction processing for machine {MachineName} with interval {Interval}", @@ -168,31 +177,27 @@ private void ScheduleMachine(MachinePredictionConfig machineConfig) } } - private async Task ProcessMachineAsync(MachinePredictionConfig machineConfig) + private async Task ProcessMachineAsync(MachinePredictionConfig machineConfig, IMachinePredictionProcessor predictionProcessor) { - // Skip if machine has errors - if (_machineErrors.TryGetValue(machineConfig.MachineName, out bool hasError) && hasError) + SemaphoreSlim machineLock = _machineLocks[machineConfig.MachineName]; + + if (!await machineLock.WaitAsync(0)) { - Log.Debug("Skipping prediction for machine {MachineName} due to previous errors", machineConfig.MachineName); + Log.Warning("Prediction already running for machine {MachineName}, skipping this cycle", machineConfig.MachineName); return; } try { await predictionProcessor.ProcessAsync(machineConfig); - - // Clear error flag if processing succeeds - if (_machineErrors.ContainsKey(machineConfig.MachineName)) - { - _machineErrors[machineConfig.MachineName] = false; - } } catch (Exception ex) { Log.Error(ex, "Error processing prediction for machine {MachineName}", machineConfig.MachineName); - - // Set error flag to stop processing for this machine - _machineErrors[machineConfig.MachineName] = true; + } + finally + { + machineLock.Release(); } } diff --git a/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs index c541e4e..b9b3808 100644 --- a/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs +++ b/src/DataAggregator.Processor/Services/Registration/RegistrationServiceClient.cs @@ -23,7 +23,7 @@ public class RegistrationServiceClient(HttpClient httpClient) : IRegistrationSer try { - HttpResponseMessage response = await httpClient.GetAsync($"/api/DeviceRegistration/collector/{deviceName}"); + HttpResponseMessage response = await httpClient.GetAsync($"api/DeviceRegistration/{deviceName}"); if (response.IsSuccessStatusCode) { diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index f41c133..3f0cc8d 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -17,19 +17,15 @@ }, "AllowedHosts": "*", - "RegistrationService": { - "Endpoint": "http://localhost:5001" - }, "PredictionService": { - "RegistrationServiceUrl": "http://localhost:5001", - "GlobalCycleIntervalSeconds": 1, + "RegistrationServiceUrl": "http://localhost:5137", "Machines": [ { - "MachineName": "Micro5_1", + "MachineName": "Micro5", "Enabled": true, - "ModelPath": "/resources/opencn_model.onnx", - "PreprocessingStrategy": "ActuatorCurrentFeatureExtractor", + "ModelPath": "resources/opencn_model.onnx", + "PreprocessingStrategy": "actuatorcurrent", "InputSensors": [ "current-amp-x", "current-amp-y", @@ -38,8 +34,8 @@ "current-amp-c", "current-amp-s" ], - "WindowSizeSeconds": 1, - "CycleIntervalSeconds": 1, + "WindowSizeSeconds": 2, + "CycleIntervalSeconds": 2, "Preprocessing": { "EnableZScoreNormalization": true, "NormalizationParameters": { From b9170fa1a220434bb8aadda7db62ef861cc7de8a Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 24 Jul 2025 10:38:13 +0200 Subject: [PATCH 49/70] feat: fix minors and rework influx scheme --- DataAggregator.sln | 28 ++++++------- .../DataStorage/Influx/InfluxDbRepository.cs | 4 +- .../Services/DataStorage/IDataRepository.cs | 7 ++-- .../DataStorage/InfluxV3Repository.cs | 19 ++++----- .../Prediction/MachinePredictionProcessor.cs | 3 +- .../appsettings.json | 2 +- .../Configuration/TimeSeries/InfluxHelper.cs | 22 +++++++++++ .../DataAggregator.Processor.Tests.csproj | 4 +- .../MachinePredictionProcessorTests.cs | 39 +++---------------- .../PredictionBackgroundServiceTests.cs | 31 ++++++++++++--- 10 files changed, 86 insertions(+), 73 deletions(-) create mode 100644 src/DataAggregator.Shared/Configuration/TimeSeries/InfluxHelper.cs diff --git a/DataAggregator.sln b/DataAggregator.sln index 9aeecf7..cddc1c4 100644 --- a/DataAggregator.sln +++ b/DataAggregator.sln @@ -42,7 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{EB9A5576-3E00-4007-8C72-2235E0A1546D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Processor.Tests", "DataAggregator.Processor.Tests\DataAggregator.Processor.Tests.csproj", "{C4C90E68-F473-4672-A3C8-E0F1713E5372}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Processor.Tests", "tests\DataAggregator.Processor.Tests\DataAggregator.Processor.Tests.csproj", "{EFE47FE6-F41E-CDD6-0991-472080AD88B0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -162,18 +162,18 @@ Global {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x64.Build.0 = Release|x64 {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x86.ActiveCfg = Release|x86 {039EC00D-5EDF-4C48-B449-29CFB6750232}.Release|x86.Build.0 = Release|x86 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x64.ActiveCfg = Debug|x64 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x64.Build.0 = Debug|x64 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x86.ActiveCfg = Debug|x86 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Debug|x86.Build.0 = Debug|x86 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|Any CPU.Build.0 = Release|Any CPU - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x64.ActiveCfg = Release|x64 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x64.Build.0 = Release|x64 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x86.ActiveCfg = Release|x86 - {C4C90E68-F473-4672-A3C8-E0F1713E5372}.Release|x86.Build.0 = Release|x86 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Debug|x64.ActiveCfg = Debug|x64 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Debug|x64.Build.0 = Debug|x64 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Debug|x86.ActiveCfg = Debug|x86 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Debug|x86.Build.0 = Debug|x86 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Release|Any CPU.Build.0 = Release|Any CPU + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Release|x64.ActiveCfg = Release|x64 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Release|x64.Build.0 = Release|x64 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Release|x86.ActiveCfg = Release|x86 + {EFE47FE6-F41E-CDD6-0991-472080AD88B0}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -192,7 +192,7 @@ Global {2C10378E-0937-40E3-940E-F236F6E8B95B} = {F5250AC4-9CCC-432B-8725-DAC6AE01CCF0} {039EC00D-5EDF-4C48-B449-29CFB6750232} = {EB9A5576-3E00-4007-8C72-2235E0A1546D} {EB9A5576-3E00-4007-8C72-2235E0A1546D} = {E1AD9667-4C40-4CAF-8096-5FA749EBB2B1} - {C4C90E68-F473-4672-A3C8-E0F1713E5372} = {247EF7A2-1DFD-4B51-AC7D-0FD13827CAC2} + {EFE47FE6-F41E-CDD6-0991-472080AD88B0} = {247EF7A2-1DFD-4B51-AC7D-0FD13827CAC2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9C8E6DBA-9F77-4FD0-9A1D-25150F7806FF} diff --git a/src/DataAggregator.Collector.Shared/DataStorage/Influx/InfluxDbRepository.cs b/src/DataAggregator.Collector.Shared/DataStorage/Influx/InfluxDbRepository.cs index 9139ae8..3bf4a11 100644 --- a/src/DataAggregator.Collector.Shared/DataStorage/Influx/InfluxDbRepository.cs +++ b/src/DataAggregator.Collector.Shared/DataStorage/Influx/InfluxDbRepository.cs @@ -79,8 +79,8 @@ private void InitializeClient() { Token = _config.Token, Host = _config.Endpoint, - Organization = "Dataggregator", - Database = "Dataggregator", + Organization = InfluxHelper.DatabaseName, + Database = InfluxHelper.DatabaseName, }; _client = new InfluxDBClient(clientConfig); diff --git a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs index 169a2fe..83a5eaa 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs @@ -13,8 +13,7 @@ public interface IDataRepository /// /// The InfluxDB endpoint. /// The authentication token. - /// The organization name. - public void InitializeAsync(string endpoint, string token, string org); + public void InitializeAsync(string endpoint, string token); /// /// Queries measurements from InfluxDB for a specific time range and sensors with type information. @@ -29,8 +28,8 @@ public interface IDataRepository /// /// Writes a single measurement to InfluxDB. /// - /// The table name (machine name). + /// The tag name (machine name). /// The measurement data to write. /// A task representing the asynchronous operation. - public Task WriteMeasurementAsync(string table, IEnumerable measurement); + public Task WriteMeasurementAsync(string tag, IEnumerable measurement); } diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index 4414e1b..f8cae55 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -1,4 +1,5 @@ using DataAggregator.Collector.Shared.Models; +using DataAggregator.Shared.Configuration.TimeSeries; using DataAggregator.Shared.Domain.DataType; using DataAggregator.Shared.DTOs; using InfluxDB3.Client; @@ -13,11 +14,10 @@ namespace DataAggregator.Processor.Services.DataStorage; /// public class InfluxV3Repository : IDataRepository, IDisposable { - private readonly string _database = "Dataggregator"; private InfluxDBClient? _client; /// - public void InitializeAsync(string endpoint, string token, string org) + public void InitializeAsync(string endpoint, string token) { _client?.Dispose(); @@ -27,8 +27,8 @@ public void InitializeAsync(string endpoint, string token, string org) { Token = token, Host = endpoint, - Organization = org, - Database = _database, + Organization = InfluxHelper.DatabaseName, + Database = InfluxHelper.DatabaseName, }; _client = new InfluxDBClient(clientConfig); @@ -59,7 +59,7 @@ public async Task> QueryMeasurementsAsync(string table, D FROM "{table}" WHERE time >= '{startTime:yyyy-MM-ddTHH:mm:ssZ}' AND time < '{endTime:yyyy-MM-ddTHH:mm:ssZ}' - """; + """; var measurements = new List(); var sensorDict = sensors.ToDictionary(s => s.SensorName, s => s); @@ -137,7 +137,7 @@ SensorDataType.Double or SensorDataType.Float when double.TryParse(value.ToStrin } /// - public async Task WriteMeasurementAsync(string table, IEnumerable measurements) + public async Task WriteMeasurementAsync(string tag, IEnumerable measurements) { if (_client == null) { @@ -155,9 +155,10 @@ public async Task WriteMeasurementAsync(string table, IEnumerable m.GetRawValue()); return PointData - .Measurement(table) + .Measurement(InfluxHelper.PredictionTableName) .SetTimestamp(DateTime.SpecifyKind(group.Key, DateTimeKind.Utc)) - .SetFields(fields); + .SetFields(fields) + .SetTag(InfluxHelper.MachineNameTag, tag); }); await _client.WritePointsAsync(groupedPoints, null, WritePrecision.Ms); @@ -166,7 +167,7 @@ public async Task WriteMeasurementAsync(string table, IEnumerable +/// Static containing helper informations for InfluxDB operations. +/// +public static class InfluxHelper +{ + /// + /// Constants for InfluxDB database name. + /// + public const string DatabaseName = "Dataaggregator"; + + /// + /// Constant for influxDb measurement table name for predictions. + /// + public const string PredictionTableName = "Predictions"; + + /// + /// Constant for influxDb tag for machine name. + /// + public const string MachineNameTag = "MachineName"; +} diff --git a/tests/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj b/tests/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj index d188ace..a95a1a6 100644 --- a/tests/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj +++ b/tests/DataAggregator.Processor.Tests/DataAggregator.Processor.Tests.csproj @@ -20,11 +20,11 @@ - + - + diff --git a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs index 845f080..b8aa884 100644 --- a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -147,7 +147,7 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe .Returns(_mockPreprocessingStrategy.Object); _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) .Returns(preprocessedData); - _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) + _mockPredictionEngine.Setup(x => x.PredictAsync(It.IsAny(), It.IsAny>())) .ReturnsAsync(results); // Act @@ -157,8 +157,7 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe _mockInfluxRepository.Verify( x => x.InitializeAsync( collectorInfo.AssignedInfluxEndpoint.Endpoint, - collectorInfo.AssignedInfluxEndpoint.Token, - "Dataggregator"), + collectorInfo.AssignedInfluxEndpoint.Token), Times.Once); _mockInfluxRepository.Verify( x => x.QueryMeasurementsAsync( @@ -167,7 +166,7 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe It.IsAny(), It.IsAny>()), Times.Once); - _mockPredictionEngine.Verify(x => x.PredictAsync(config.ModelPath, preprocessedData), Times.Once); + _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Once); _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(config.MachineName, It.IsAny>()), Times.Once); } @@ -208,13 +207,11 @@ public async Task ProcessAsync_ShouldReinitializeInfluxConnection_WhenEndpointCh _mockInfluxRepository.Verify( x => x.InitializeAsync( collectorInfo1.AssignedInfluxEndpoint.Endpoint, - collectorInfo1.AssignedInfluxEndpoint.Token, - "Dataggregator"), Times.Once); + collectorInfo1.AssignedInfluxEndpoint.Token), Times.Once); _mockInfluxRepository.Verify( x => x.InitializeAsync( collectorInfo2.AssignedInfluxEndpoint.Endpoint, - collectorInfo2.AssignedInfluxEndpoint.Token, - "Dataggregator"), Times.Once); + collectorInfo2.AssignedInfluxEndpoint.Token), Times.Once); } [Fact] @@ -249,7 +246,6 @@ public async Task ProcessAsync_ShouldNotReinitializeInfluxConnection_WhenEndpoin _mockInfluxRepository.Verify( x => x.InitializeAsync( It.IsAny(), - It.IsAny(), It.IsAny()), Times.Once); } @@ -265,31 +261,6 @@ public async Task ProcessAsync_ShouldThrowException_WhenRegistrationClientThrows await Assert.ThrowsAsync(() => _processor.ProcessAsync(config)); } - [Fact] - public async Task ProcessAsync_ShouldThrowException_WhenPredictionEngineThrows() - { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); - CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); - List measurements = CreateTestMeasurements(); - - var preprocessedData = ProcessorTestHelper.GetValidTestData(); - - _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) - .ReturnsAsync(collectorInfo); - _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) - .ReturnsAsync(measurements); - _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) - .Returns(_mockPreprocessingStrategy.Object); - _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) - .Returns(preprocessedData); - _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) - .ThrowsAsync(new InvalidOperationException("Prediction error")); - - // Act & Assert - await Assert.ThrowsAsync(() => _processor.ProcessAsync(config)); - } - #endregion #region Helper methods diff --git a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs index abb0eac..01cc656 100644 --- a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs @@ -1,6 +1,7 @@ using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services; using DataAggregator.Processor.Services.Prediction; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; @@ -13,21 +14,41 @@ public class PredictionBackgroundServiceTests : IDisposable { private readonly Mock> _mockConfiguration; private readonly Mock _mockPredictionProcessor; - private readonly PredictionBackgroundService _backgroundService; + private readonly Mock _serviceProvider; + private readonly Mock _mockScope; + private readonly Mock _mockScopeFactory; private readonly CancellationTokenSource _cancellationTokenSource; + private readonly PredictionBackgroundService _backgroundService; - /// - /// Initializes a new instance of the class. - /// public PredictionBackgroundServiceTests() { _mockConfiguration = new Mock>(); _mockPredictionProcessor = new Mock(); _cancellationTokenSource = new CancellationTokenSource(); + _serviceProvider = new Mock(); + _mockScope = new Mock(); + _mockScopeFactory = new Mock(); + + _serviceProvider + .Setup(x => x.GetService(typeof(IMachinePredictionProcessor))) + .Returns(_mockPredictionProcessor.Object); + + _serviceProvider + .Setup(x => x.GetService(typeof(IServiceScopeFactory))) + .Returns(_mockScopeFactory.Object); + + _mockScope + .Setup(x => x.ServiceProvider) + .Returns(_serviceProvider.Object); + + _mockScopeFactory + .Setup(x => x.CreateScope()) + .Returns(_mockScope.Object); + _backgroundService = new PredictionBackgroundService( _mockConfiguration.Object, - _mockPredictionProcessor.Object); + _serviceProvider.Object); } #region ExecuteAsync tests From e4e94a6427f11f55a1b685d113cff8c163d43c54 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 24 Jul 2025 15:34:03 +0200 Subject: [PATCH 50/70] fix: timing results --- .../ActuatorCurrentFeatureExtractor.cs | 38 +++++++++++-------- .../Prediction/OnnxPredictionEngine.cs | 4 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index e0b156c..b47f9a0 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -31,24 +31,30 @@ public IEnumerable PreprocessAsync(IEnumerable { - new MeasurementData(now, "GlobalActivityRatio", normalizedFeatures[0]), - new MeasurementData(now, "GlobalChangeDensity", normalizedFeatures[1]), - new MeasurementData(now, "InterAxisMeanCorrelation", normalizedFeatures[2]), - new MeasurementData(now, "InterAxisMaxCorrelation", normalizedFeatures[3]), - new MeasurementData(now, "InterAxisCorrelationVariance", normalizedFeatures[4]), - new MeasurementData(now, "AxisSynchronization", normalizedFeatures[5]), - new MeasurementData(now, "AxisLoadBalance", normalizedFeatures[6]), - new MeasurementData(now, "TemporalStability", normalizedFeatures[7]), - new MeasurementData(now, "GlobalSkewness", normalizedFeatures[8]), - new MeasurementData(now, "GlobalKurtosis", normalizedFeatures[9]), - new MeasurementData(now, "GlobalTrendSlope", normalizedFeatures[10]), - new MeasurementData(now, "CoefficientOfVariation", normalizedFeatures[11]), - new MeasurementData(now, "NormalizedIqrMedian", normalizedFeatures[12]), - new MeasurementData(now, "NormalizedIqrMean", normalizedFeatures[13]), - new MeasurementData(now, "Label", string.Empty), + new MeasurementData(meanTime, "GlobalActivityRatio", normalizedFeatures[0]), + new MeasurementData(meanTime, "GlobalChangeDensity", normalizedFeatures[1]), + new MeasurementData(meanTime, "InterAxisMeanCorrelation", normalizedFeatures[2]), + new MeasurementData(meanTime, "InterAxisMaxCorrelation", normalizedFeatures[3]), + new MeasurementData(meanTime, "InterAxisCorrelationVariance", normalizedFeatures[4]), + new MeasurementData(meanTime, "AxisSynchronization", normalizedFeatures[5]), + new MeasurementData(meanTime, "AxisLoadBalance", normalizedFeatures[6]), + new MeasurementData(meanTime, "TemporalStability", normalizedFeatures[7]), + new MeasurementData(meanTime, "GlobalSkewness", normalizedFeatures[8]), + new MeasurementData(meanTime, "GlobalKurtosis", normalizedFeatures[9]), + new MeasurementData(meanTime, "GlobalTrendSlope", normalizedFeatures[10]), + new MeasurementData(meanTime, "CoefficientOfVariation", normalizedFeatures[11]), + new MeasurementData(meanTime, "NormalizedIqrMedian", normalizedFeatures[12]), + new MeasurementData(meanTime, "NormalizedIqrMean", normalizedFeatures[13]), + new MeasurementData(meanTime, "Label", string.Empty), }; Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs index 57e3dc1..d19e5e7 100644 --- a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs @@ -59,13 +59,13 @@ public async Task> PredictAsync( // Prepare the list to store output measurements var outputMeasurements = new List(); - DateTime now = DateTime.UtcNow; + DateTime processedDataTime = inputData.First().TimeStamp; // Process each filtered result to convert it into measurement data foreach (DisposableNamedOnnxValue? result in filteredResults) { // Process the result and add it to the output list - IEnumerable measurementData = ProcessResultToMeasurementData(result, now); + IEnumerable measurementData = ProcessResultToMeasurementData(result, processedDataTime); outputMeasurements.AddRange(measurementData); } From 4ab37e19a7d40429ee36035bb31dbaf2c06fa7f4 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 27 Jul 2025 13:54:45 +0200 Subject: [PATCH 51/70] fix: influx helper --- .../Configuration/TimeSeries/InfluxHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataAggregator.Shared/Configuration/TimeSeries/InfluxHelper.cs b/src/DataAggregator.Shared/Configuration/TimeSeries/InfluxHelper.cs index 2ca5a7e..415221d 100644 --- a/src/DataAggregator.Shared/Configuration/TimeSeries/InfluxHelper.cs +++ b/src/DataAggregator.Shared/Configuration/TimeSeries/InfluxHelper.cs @@ -8,7 +8,7 @@ public static class InfluxHelper /// /// Constants for InfluxDB database name. /// - public const string DatabaseName = "Dataaggregator"; + public const string DatabaseName = "Dataggregator"; /// /// Constant for influxDb measurement table name for predictions. From 7fc5a694529855ad9139e4342d03a03742228cea Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 27 Jul 2025 18:09:58 +0200 Subject: [PATCH 52/70] feat: implement list of data processor --- .../Configuration/MachinePredictionConfig.cs | 14 +- src/DataAggregator.Processor/Program.cs | 5 +- .../Services/DataStorage/IDataRepository.cs | 2 +- .../DataStorage/InfluxV3Repository.cs | 2 +- .../PreProcessing/IPreprocessingStrategy.cs | 18 -- .../IPreprocessingStrategyFactory.cs | 14 -- .../PreprocessingStrategyFactory.cs | 26 --- .../Prediction/IOnnxPredictionEngine.cs | 17 -- .../Prediction/MachinePredictionProcessor.cs | 213 ++++++++---------- .../Services/PredictionBackgroundService.cs | 21 -- .../Processing/Abstraction/IDataProcessor.cs | 16 ++ .../Factory/DataProcessorFactory.cs | 53 +++++ .../Factory/IDataProcessorFactory.cs | 17 ++ .../Processing/Onnx/OnnxPredictionConfig.cs | 12 + .../Onnx}/OnnxPredictionEngine.cs | 28 +-- .../StateDeductionPostProcessor.cs | 74 ++++++ .../StateDeductionPostProcessorConfig.cs | 12 + .../ActuatorCurrentFeatureExtractor.cs | 75 +++--- .../MathUtils.cs | 2 +- .../PreprocessingConfig.cs | 2 +- src/DataAggregator.Processor/appsettings.json | 53 +++-- 21 files changed, 368 insertions(+), 308 deletions(-) delete mode 100644 src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs delete mode 100644 src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs delete mode 100644 src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs delete mode 100644 src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs create mode 100644 src/DataAggregator.Processor/Services/Processing/Abstraction/IDataProcessor.cs create mode 100644 src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs create mode 100644 src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs create mode 100644 src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs rename src/DataAggregator.Processor/Services/{Prediction => Processing/Onnx}/OnnxPredictionEngine.cs (89%) create mode 100644 src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs create mode 100644 src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs rename src/DataAggregator.Processor/Services/{ => Processing}/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs (83%) rename src/DataAggregator.Processor/Services/{ => Processing}/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs (97%) rename src/DataAggregator.Processor/{Configuration => Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing}/PreprocessingConfig.cs (83%) diff --git a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs index 5f94703..1878a4a 100644 --- a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs +++ b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs @@ -15,16 +15,6 @@ public class MachinePredictionConfig /// public bool Enabled { get; set; } = true; - /// - /// Gets or sets the path to the ONNX model file. - /// - public string ModelPath { get; set; } = string.Empty; - - /// - /// Gets or sets the preprocessing strategy name. - /// - public string PreprocessingStrategy { get; set; } = string.Empty; - /// /// Gets or sets the list of input sensor names. /// @@ -41,7 +31,7 @@ public class MachinePredictionConfig public int CycleIntervalSeconds { get; set; } = 1; /// - /// Gets or sets the preprocessing configuration for Z-score normalization. + /// Gets or sets the processing pipeline for this machine. /// - public PreprocessingConfig Preprocessing { get; set; } = new(); + public List? ProcessingPipeline { get; set; } } diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index d71ea41..acf3413 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -2,7 +2,7 @@ using DataAggregator.Processor.Services; using DataAggregator.Processor.Services.DataStorage; using DataAggregator.Processor.Services.Prediction; -using DataAggregator.Processor.Services.PreProcessing; +using DataAggregator.Processor.Services.Processing.Factory; using DataAggregator.Processor.Services.Registration; using Serilog; @@ -38,8 +38,7 @@ // Register services builder.Services.AddScoped(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); // Register background service diff --git a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs index 83a5eaa..d17cd27 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs @@ -13,7 +13,7 @@ public interface IDataRepository /// /// The InfluxDB endpoint. /// The authentication token. - public void InitializeAsync(string endpoint, string token); + public void Initialize(string endpoint, string token); /// /// Queries measurements from InfluxDB for a specific time range and sensors with type information. diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index f8cae55..52a6281 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -17,7 +17,7 @@ public class InfluxV3Repository : IDataRepository, IDisposable private InfluxDBClient? _client; /// - public void InitializeAsync(string endpoint, string token) + public void Initialize(string endpoint, string token) { _client?.Dispose(); diff --git a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs deleted file mode 100644 index 1f2bb74..0000000 --- a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategy.cs +++ /dev/null @@ -1,18 +0,0 @@ -using DataAggregator.Collector.Shared.Models; -using DataAggregator.Processor.Configuration; - -namespace DataAggregator.Processor.Services.PreProcessing; - -/// -/// Interface for preprocessing strategies that convert raw measurement data into feature vectors for ML models. -/// -public interface IPreprocessingStrategy -{ - /// - /// Preprocesses a list of measurements into a feature vector for a single prediction sample. - /// - /// List of raw measurements from the data window. - /// Configuration for the machine prediction. - /// Feature vector as dictionary mapping input names to values for a single sample. - public IEnumerable PreprocessAsync(IEnumerable measurements, MachinePredictionConfig config); -} diff --git a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs b/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs deleted file mode 100644 index 71715c8..0000000 --- a/src/DataAggregator.Processor/Services/PreProcessing/IPreprocessingStrategyFactory.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace DataAggregator.Processor.Services.PreProcessing; - -/// -/// Factory interface for creating preprocessing strategies based on strategy name. -/// -public interface IPreprocessingStrategyFactory -{ - /// - /// Creates a preprocessing strategy based on the strategy name. - /// - /// Name of the strategy to create. - /// Configured preprocessing strategy. - public IPreprocessingStrategy CreateStrategy(string strategyName); -} diff --git a/src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs b/src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs deleted file mode 100644 index f7c7689..0000000 --- a/src/DataAggregator.Processor/Services/PreProcessing/PreprocessingStrategyFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; -using Serilog; - -namespace DataAggregator.Processor.Services.PreProcessing; - -/// -/// Factory implementation for creating preprocessing strategies. -/// -public class PreprocessingStrategyFactory : IPreprocessingStrategyFactory -{ - /// - /// Creates a preprocessing strategy based on the strategy name. - /// - /// Name of the strategy to create. - /// Configured preprocessing strategy. - public IPreprocessingStrategy CreateStrategy(string strategyName) - { - Log.Information("Creating preprocessing strategy: {StrategyName}", strategyName); - - return strategyName.ToLowerInvariant() switch - { - "actuatorcurrent" => new ActuatorCurrentFeatureExtractor(), - _ => throw new ArgumentException($"Unknown preprocessing strategy: {strategyName}", nameof(strategyName)), - }; - } -} diff --git a/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs deleted file mode 100644 index 4e99b7c..0000000 --- a/src/DataAggregator.Processor/Services/Prediction/IOnnxPredictionEngine.cs +++ /dev/null @@ -1,17 +0,0 @@ -using DataAggregator.Collector.Shared.Models; - -namespace DataAggregator.Processor.Services.Prediction; - -/// -/// Interface for ONNX prediction engine. -/// -public interface IOnnxPredictionEngine -{ - /// - /// Performs prediction using an ONNX model. - /// - /// The path to the ONNX model file. - /// The input data for prediction as a dictionary mapping input names to values. - /// The prediction results as a dictionary mapping output names to values. - public Task> PredictAsync(string modelPath, IEnumerable inputData); -} diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index c98b051..c2f57b0 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -1,123 +1,54 @@ +using System.Text.Json; using DataAggregator.Collector.Shared.Models; using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services.DataStorage; -using DataAggregator.Processor.Services.PreProcessing; +using DataAggregator.Processor.Services.Processing.Abstraction; +using DataAggregator.Processor.Services.Processing.Factory; using DataAggregator.Processor.Services.Registration; using DataAggregator.Shared.DTOs; using Serilog; namespace DataAggregator.Processor.Services.Prediction; -/// -/// Processor for machine prediction operations. -/// -/// -/// Initializes a new instance of the class. -/// -/// The InfluxDB repository. -/// The registration service client. -/// The ONNX prediction engine. -/// The preprocessing strategy factory. +/// public class MachinePredictionProcessor( IDataRepository influxRepository, IRegistrationServiceClient registrationClient, - IOnnxPredictionEngine predictionEngine, - IPreprocessingStrategyFactory strategyFactory) : IMachinePredictionProcessor + IDataProcessorFactory processorFactory) : IMachinePredictionProcessor { - #region Private fields - - // Track the last endpoint used to avoid unnecessary reinitializations - private string? _lastEndpoint; - - #endregion - - #region Public methods - - /// + /// public async Task ProcessAsync(MachinePredictionConfig config) { try { Log.Debug("Starting prediction process for machine {MachineName}", config.MachineName); - // Get collector info from registration service - CollectorInfoDto? collectorInfo = await registrationClient.GetCollectorInfoAsync(config.MachineName); - - if (collectorInfo == null) - { - Log.Warning("Collector info not found for machine {MachineName}", config.MachineName); - return; - } - - // Validate that all required sensors are available - var availableSensors = collectorInfo.Sensors.ToDictionary(s => s.SensorName, s => s); - var requestedSensors = config.InputSensors.Where(s => availableSensors.ContainsKey(s)).ToList(); - - if (requestedSensors.Count != config.InputSensors.Count) - { - IEnumerable missingSensors = config.InputSensors.Except(requestedSensors); - Log.Warning( - "Missing sensors for machine {MachineName}: {MissingSensors}", - config.MachineName, - string.Join(", ", missingSensors)); - - if (requestedSensors.Count == 0) - { - Log.Error("No valid sensors found for machine {MachineName}", config.MachineName); - return; - } - } - - // Initialize InfluxDB repository only if endpoint changed - if (_lastEndpoint != collectorInfo.AssignedInfluxEndpoint.Endpoint) - { - influxRepository.InitializeAsync( - collectorInfo.AssignedInfluxEndpoint.Endpoint, - collectorInfo.AssignedInfluxEndpoint.Token); - - _lastEndpoint = collectorInfo.AssignedInfluxEndpoint.Endpoint; - Log.Debug("Reinitialized InfluxDB connection with new endpoint: {Endpoint}", _lastEndpoint); - } + CollectorInfoDto? collectorInfo = await FetchCollectorInfo(config.MachineName); + if (collectorInfo == null) return; - // Get sensor info for requested sensors - var requestedSensorInfos = requestedSensors - .Select(sensorName => availableSensors[sensorName]) - .ToList(); + List? requestedSensors = ValidateAndGetRequestedSensors(config, collectorInfo); + if (requestedSensors == null) return; - // Fetch data window with sensor type information - List measurements = await FetchDataWindowAsync(config, requestedSensorInfos); + InitializeRepositoryIfNeeded(collectorInfo); + List measurements = await FetchDataWindowAsync(config, requestedSensors); if (measurements.Count == 0) { Log.Warning("No measurements found for machine {MachineName} in the specified time window", config.MachineName); return; } - // Preprocess data using strategy - IEnumerable preprocessedData = PreprocessDataAsync(measurements, config); - - if (preprocessedData == null || !preprocessedData.Any()) - { - Log.Warning("Data preprocessing failed for machine {MachineName}", config.MachineName); - return; - } - - // Perform prediction - string fullPath = Path.Combine(AppContext.BaseDirectory, config.ModelPath); - IEnumerable predictions = await predictionEngine.PredictAsync(fullPath, preprocessedData); - - if (predictions == null || !predictions.Any()) + if (!BuildPipelineIfNeeded(config)) { - Log.Warning("No predictions returned for machine {MachineName}", config.MachineName); + Log.Error("Failed to build processing pipeline for machine {MachineName}", config.MachineName); return; } - // Write prediction to InfluxDB - await influxRepository.WriteMeasurementAsync(config.MachineName, predictions); + IEnumerable? processedData = await RunPipelineAsync(measurements, config.MachineName); + if (processedData == null) return; - Log.Information( - "Prediction completed for machine {MachineName}", - config.MachineName); + await influxRepository.WriteMeasurementAsync(config.MachineName, processedData); + Log.Information("Prediction pipeline completed for machine {MachineName}", config.MachineName); } catch (Exception ex) { @@ -126,49 +57,101 @@ public async Task ProcessAsync(MachinePredictionConfig config) } } - #endregion - #region Private methods - - private async Task> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) + private async Task FetchCollectorInfo(string machineName) { - DateTime endTime = DateTime.UtcNow; - DateTime startTime = endTime.AddSeconds(-config.WindowSizeSeconds); - - return await influxRepository.QueryMeasurementsAsync( - config.MachineName, - startTime, - endTime, - sensors); + CollectorInfoDto? collectorInfo = await registrationClient.GetCollectorInfoAsync(machineName); + if (collectorInfo == null) + Log.Warning("Collector info not found for machine {MachineName}", machineName); + return collectorInfo; } - private IEnumerable PreprocessDataAsync(IEnumerable measurements, MachinePredictionConfig config) + private List? ValidateAndGetRequestedSensors(MachinePredictionConfig config, CollectorInfoDto collectorInfo) { - try + var availableSensors = collectorInfo.Sensors.ToDictionary(s => s.SensorName); + var requestedSensors = config.InputSensors.Where(availableSensors.ContainsKey).ToList(); + + if (requestedSensors.Count != config.InputSensors.Count) { - if (string.IsNullOrEmpty(config.PreprocessingStrategy)) + IEnumerable missing = config.InputSensors.Except(requestedSensors); + Log.Warning("Missing sensors for machine {MachineName}: {MissingSensors}", config.MachineName, string.Join(", ", missing)); + if (requestedSensors.Count == 0) { - Log.Error("No preprocessing strategy configured for machine {MachineName}", config.MachineName); - return Array.Empty(); + Log.Error("No valid sensors found for machine {MachineName}", config.MachineName); + return null; } + } - IPreprocessingStrategy strategy = strategyFactory.CreateStrategy(config.PreprocessingStrategy); - IEnumerable preprocessedData = strategy.PreprocessAsync(measurements, config); + return requestedSensors.Select(s => availableSensors[s]).ToList(); + } - Log.Debug( - "Preprocessed data for machine {MachineName} using strategy {Strategy}: {InputCount} inputs", - config.MachineName, - config.PreprocessingStrategy, - preprocessedData.Count()); + private void InitializeRepositoryIfNeeded(CollectorInfoDto collectorInfo) + { + if (_lastEndpoint != collectorInfo.AssignedInfluxEndpoint.Endpoint) + { + influxRepository.Initialize( + collectorInfo.AssignedInfluxEndpoint.Endpoint, + collectorInfo.AssignedInfluxEndpoint.Token); + _lastEndpoint = collectorInfo.AssignedInfluxEndpoint.Endpoint; + Log.Debug("Reinitialized InfluxDB connection with new endpoint: {Endpoint}", _lastEndpoint); + } + } - return preprocessedData; + private async Task?> RunPipelineAsync(IEnumerable data, string machineName) + { + foreach (IDataProcessor processor in _pipelineProcessors!) + { + try + { + data = await processor.ProcessAsync(data); + if (data is null || !data.Any()) + { + Log.Warning("Processor {Processor} returned no data for machine {MachineName}", processor.GetType().Name, machineName); + return null; + } + } + catch (Exception ex) + { + Log.Error(ex, "Error in processor {Processor} for machine {MachineName}", processor.GetType().Name, machineName); + return null; + } } - catch (Exception ex) + + return data; + } + + private bool BuildPipelineIfNeeded(MachinePredictionConfig config) + { + if (_pipelineProcessors == null) { - Log.Error(ex, "Error preprocessing data for machine {MachineName}", config.MachineName); - return Array.Empty(); + if (config.ProcessingPipeline == null) + { + Log.Error("No processing pipeline configured for machine {MachineName}", config.MachineName); + return false; + } + + string pipelineJson = JsonSerializer.Serialize(config.ProcessingPipeline); + var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + _pipelineProcessors = processorFactory.CreateProcessors(pipelineElements); } + + return true; } + private async Task> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) + { + DateTime endTime = DateTime.UtcNow; + DateTime startTime = endTime.AddSeconds(-config.WindowSizeSeconds); + return await influxRepository.QueryMeasurementsAsync( + config.MachineName, + startTime, + endTime, + sensors); + } + #endregion + + #region Private fields + private List? _pipelineProcessors; + private string? _lastEndpoint; #endregion } diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs index 796456a..3e7c7c5 100644 --- a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -96,18 +96,6 @@ private void ValidateConfigurationAsync() { try { - // Check if ONNX model file exists - string fullPath = Path.Combine(AppContext.BaseDirectory, machineConfig.ModelPath); - if (!File.Exists(fullPath)) - { - Log.Error( - "ONNX model file not found for machine {MachineName}: {ModelPath}", - machineConfig.MachineName, - machineConfig.ModelPath); - - throw new FileNotFoundException($"ONNX model file not found: {machineConfig.ModelPath}"); - } - // Validate configuration if (string.IsNullOrEmpty(machineConfig.MachineName)) { @@ -127,15 +115,6 @@ private void ValidateConfigurationAsync() throw new InvalidOperationException($"No input sensors configured for machine {machineConfig.MachineName}"); } - if (string.IsNullOrEmpty(machineConfig.PreprocessingStrategy)) - { - Log.Error( - "Preprocessing strategy is not configured for machine {MachineName}", - machineConfig.MachineName); - - throw new InvalidOperationException($"Preprocessing strategy is not configured for machine {machineConfig.MachineName}"); - } - Log.Information("Configuration validated for machine {MachineName}", machineConfig.MachineName); } catch (Exception ex) diff --git a/src/DataAggregator.Processor/Services/Processing/Abstraction/IDataProcessor.cs b/src/DataAggregator.Processor/Services/Processing/Abstraction/IDataProcessor.cs new file mode 100644 index 0000000..3669d5a --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/Abstraction/IDataProcessor.cs @@ -0,0 +1,16 @@ +using DataAggregator.Collector.Shared.Models; + +namespace DataAggregator.Processor.Services.Processing.Abstraction; + +/// +/// Base interface for data processors that handle measurement data. +/// +public interface IDataProcessor +{ + /// + /// Processes a collection of measurement data asynchronously and return a new processed list of measurements. + /// + /// The input measurements. + /// A new IEnumerable of IMeasurementData which is processed. + public Task> ProcessAsync(IEnumerable input); +} diff --git a/src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs b/src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs new file mode 100644 index 0000000..0e6420c --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using DataAggregator.Processor.Services.Prediction; +using DataAggregator.Processor.Services.Processing.Abstraction; +using DataAggregator.Processor.Services.Processing.Onnx; +using DataAggregator.Processor.Services.Processing.PostProcessing.StateDeductionPostProcess; +using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; + +namespace DataAggregator.Processor.Services.Processing.Factory; + +/// +/// Factory implementation for creating processor strategies. +/// +public class DataProcessorFactory : IDataProcessorFactory +{ + /// + public List CreateProcessors(IEnumerable pipelineConfig) + { + var processors = new List(); + foreach (JsonElement element in pipelineConfig) + { + if (!element.TryGetProperty("Strategy", out JsonElement strategyProp)) + throw new ArgumentException("Each processor config must have a 'Strategy' property."); + + string? strategy = strategyProp.GetString()?.ToLowerInvariant(); + + switch (strategy) + { + case "actuatorcurrent": + PreprocessingConfig? preConfig = element.Deserialize(); + if (preConfig == null) + throw new ArgumentException("Preprocessing configuration is required for ActuatorCurrentFeatureExtractor."); + processors.Add(new ActuatorCurrentFeatureExtractor(preConfig)); + break; + case "onnxprediction": + OnnxPredictionConfig? onnxConfig = element.Deserialize(); + if (onnxConfig == null) + throw new ArgumentException("ONNX prediction configuration is required."); + processors.Add(new OnnxPredictionEngine(onnxConfig)); + break; + case "statedeductionpostprocessor": + StateDeductionPostProcessorConfig? postConfig = element.Deserialize(); + if (postConfig == null) + throw new ArgumentException("Post-processing configuration is required for MyCustomPostProcessor."); + processors.Add(new StateDeductionPostProcessor(postConfig)); + break; + default: + throw new ArgumentException($"Unknown processor strategy: {strategy}"); + } + } + + return processors; + } +} diff --git a/src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs b/src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs new file mode 100644 index 0000000..2aee1bb --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs @@ -0,0 +1,17 @@ +using System.Text.Json; +using DataAggregator.Processor.Services.Processing.Abstraction; + +namespace DataAggregator.Processor.Services.Processing.Factory; + +/// +/// Factory interface for creating processing strategies based on strategy name. +/// +public interface IDataProcessorFactory +{ + /// + /// Create multiple data processor which correspond to the pipeline configuration. + /// + /// The pipeline configuration. + /// Configured pipeline strategy. + public List CreateProcessors(IEnumerable pipelineConfig); +} diff --git a/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs new file mode 100644 index 0000000..d76903a --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs @@ -0,0 +1,12 @@ +namespace DataAggregator.Processor.Services.Processing.Onnx; + +/// +/// Configuration for ONNX prediction. +/// +public class OnnxPredictionConfig +{ + /// + /// Gets or sets the path of the model. + /// + public string ModelPath { get; set; } = string.Empty; +} diff --git a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionEngine.cs similarity index 89% rename from src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs rename to src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionEngine.cs index d19e5e7..ef93280 100644 --- a/src/DataAggregator.Processor/Services/Prediction/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionEngine.cs @@ -1,4 +1,6 @@ using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Services.Processing.Abstraction; +using DataAggregator.Processor.Services.Processing.Onnx; using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using Serilog; @@ -8,37 +10,36 @@ namespace DataAggregator.Processor.Services.Prediction; /// /// Implementation of ONNX prediction engine. /// -public class OnnxPredictionEngine : IOnnxPredictionEngine, IDisposable +public class OnnxPredictionEngine(OnnxPredictionConfig config) : IDataProcessor, IDisposable { #region Private fields - private readonly Dictionary _modelCache = []; + private static readonly Dictionary _modelCache = []; #endregion #region Public methods /// - public async Task> PredictAsync( - string modelPath, - IEnumerable inputData) + public async Task> ProcessAsync( + IEnumerable input) { try { // Load the model or get the existing session - InferenceSession session = LoadOrGetModel(modelPath); + InferenceSession session = LoadOrGetModel(config.ModelPath); - if (!inputData.Any()) + if (!input.Any()) { - Log.Information($"No inputs data provided to model for model {modelPath}"); + Log.Information($"No inputs data provided to model for model {config.ModelPath}"); return Array.Empty(); } - ValidateInputData(session, inputData); + ValidateInputData(session, input); // Prepare the inputs for the inference session var inputs = new List(); - foreach (IMeasurementData data in inputData) + foreach (IMeasurementData data in input) { inputs.Add(CreateNamedOnnxValue(data)); } @@ -59,7 +60,7 @@ public async Task> PredictAsync( // Prepare the list to store output measurements var outputMeasurements = new List(); - DateTime processedDataTime = inputData.First().TimeStamp; + DateTime processedDataTime = input.First().TimeStamp; // Process each filtered result to convert it into measurement data foreach (DisposableNamedOnnxValue? result in filteredResults) @@ -69,13 +70,12 @@ public async Task> PredictAsync( outputMeasurements.AddRange(measurementData); } - // Log the completion of the prediction - Log.Debug("Prediction completed for model {ModelPath} with {OutputCount} outputs", modelPath, outputMeasurements.Count); + Log.Debug("Prediction completed for model {ModelPath} with {OutputCount} outputs", config.ModelPath, outputMeasurements.Count); return await Task.FromResult(outputMeasurements); } catch (Exception ex) { - Log.Error(ex, "Error during prediction with model {ModelPath}", modelPath); + Log.Error(ex, "Error during prediction with model {ModelPath}", config.ModelPath); throw; } } diff --git a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs new file mode 100644 index 0000000..cc0090c --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs @@ -0,0 +1,74 @@ +using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Services.Processing.Abstraction; + +namespace DataAggregator.Processor.Services.Processing.PostProcessing.StateDeductionPostProcess; + +/// +/// Post processor for state deduction which avoids bad transitions recognition +/// by not allowing state change if the change duration is less than a threshold. +/// +public class StateDeductionPostProcessor(StateDeductionPostProcessorConfig config) : IDataProcessor +{ + private readonly string _resultOutputName = "PredictedLabel.output_0"; + private string? _lastState = null; + private int _stableCount = 0; + + /// + public Task> ProcessAsync(IEnumerable input) + { + var inputList = input.ToList(); + IMeasurementData? prediction = inputList.FirstOrDefault(x => x.SensorName == _resultOutputName); + string currentPredictedState = prediction?.GetRawValue() as string ?? string.Empty; + + if (_lastState == null) + { + // first run, initialize the last state + _lastState = currentPredictedState; + _stableCount = 1; + return Task.FromResult>(inputList); + } + + if (currentPredictedState == _lastState) + { + // stable state, increment the stable count + _stableCount++; + return Task.FromResult>(inputList); + } + else + { + // state change detected + _stableCount = 1; + + // Accept the state change only if the stable count reaches the threshold + if (_stableCount >= config.Threshold) + { + _lastState = currentPredictedState; + return Task.FromResult>(inputList); + } + else + { + if (prediction != null) + { + var forced = new MeasurementDataWrapper(prediction, _lastState!); + var output = inputList.Select(x => x.SensorName == _resultOutputName ? forced : x).ToList(); + return Task.FromResult>(output); + } + else + { + return Task.FromResult>(inputList); + } + } + } + } + + private class MeasurementDataWrapper(IMeasurementData original, string forcedValue) : IMeasurementData + { + public DateTime TimeStamp => original.TimeStamp; + + public string SensorName => original.SensorName; + + public Type ValueType => typeof(string); + + public object GetRawValue() => forcedValue; + } +} diff --git a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs new file mode 100644 index 0000000..efd57b6 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs @@ -0,0 +1,12 @@ +namespace DataAggregator.Processor.Services.Processing.PostProcessing.StateDeductionPostProcess; + +/// +/// Configuration for . +/// +public class StateDeductionPostProcessorConfig +{ + /// + /// Gets or sets the threshold for minimum number of consecutive cycles required to deduce a state change. + /// + public int Threshold { get; set; } +} diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs similarity index 83% rename from src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs rename to src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index b47f9a0..10ceb2b 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -1,40 +1,38 @@ using DataAggregator.Collector.Shared.Models; -using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services.Processing.Abstraction; using Serilog; -namespace DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; +namespace DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; /// /// Feature extractor for actuator current data based on ML.NET approach. /// Extracts 14 agnostic features with Z-score normalization. /// -public class ActuatorCurrentFeatureExtractor : IPreprocessingStrategy +/// +/// Initializes a new instance of the class. +/// +/// The configuration of the processor. +public class ActuatorCurrentFeatureExtractor(PreprocessingConfig config) : IDataProcessor { #region Public methods /// /// Preprocesses actuator current measurements into a feature vector. /// - /// List of raw measurements from the data window. - /// Configuration for the machine prediction. + /// List of raw measurements from the data window. /// Feature vector as dictionary mapping feature names to values for a single sample. - public IEnumerable PreprocessAsync(IEnumerable measurements, MachinePredictionConfig config) + public Task> ProcessAsync(IEnumerable input) { - Log.Debug( - "Preprocessing {Count} measurements for machine {MachineName}", - measurements.Count(), - config.MachineName); - // Extract the 14 features from measurements - float[] features = ExtractFeatures(measurements, config.InputSensors); + float[] features = ExtractFeatures(input); // Apply Z-score normalization if enabled - float[] normalizedFeatures = NormalizeFeaturesAsync(features, config.Preprocessing); + float[] normalizedFeatures = NormalizeFeaturesAsync(features, config); DateTime meanTime; - if (measurements.Count() != 0) - meanTime = measurements.ElementAt(measurements.Count() / 2).TimeStamp; + if (input.Count() != 0) + meanTime = input.ElementAt(input.Count() / 2).TimeStamp; else meanTime = DateTime.UtcNow; @@ -57,46 +55,37 @@ public IEnumerable PreprocessAsync(IEnumerable(meanTime, "Label", string.Empty), }; - Log.Debug("Preprocessing completed for machine {MachineName}", config.MachineName); - return result; + return Task.FromResult(result.AsEnumerable()); } #endregion #region Private methods - private float[] ExtractFeatures(IEnumerable measurements, List sensors) + private float[] ExtractFeatures(IEnumerable measurements) { - if (measurements == null || sensors == null || sensors.Count == 0) + if (measurements == null) { - Log.Warning("No measurements or sensors provided for feature extraction."); + Log.Warning("No measurements provided for feature extraction."); return new float[14]; } - Log.Debug("Extracting features for {SensorCount} sensors", sensors.Count); - - // Concatenate all currents from all actuators (like in the notebook) + // Concatenate all currents from all actuators var allCurrents = new List(); foreach (IMeasurementData measurement in measurements) { - foreach (string sensor in sensors) + object value = measurement.GetRawValue(); + if (value is float floatValue) { - if (measurement.SensorName == sensor) - { - object value = measurement.GetRawValue(); - if (value is float floatValue) - { - allCurrents.Add(floatValue); - } - else if (value is double doubleValue) - { - allCurrents.Add((float)doubleValue); - } - else if (value is int intValue) - { - allCurrents.Add(intValue); - } - } + allCurrents.Add(floatValue); + } + else if (value is double doubleValue) + { + allCurrents.Add((float)doubleValue); + } + else if (value is int intValue) + { + allCurrents.Add(intValue); } } @@ -127,7 +116,7 @@ private float[] ExtractFeatures(IEnumerable measurements, List float changeDensity = significantChanges / (float)allCurrents.Count; // Extract actuator currents for correlation analysis - List> actuatorsCurrents = ExtractActuatorCurrents(measurements, sensors); + List> actuatorsCurrents = ExtractActuatorCurrents(measurements); // Features 3-5: Inter-actuator correlations List correlations = CalculateInterActuatorCorrelations(actuatorsCurrents); @@ -179,7 +168,7 @@ private float[] ExtractFeatures(IEnumerable measurements, List ]; } - private List> ExtractActuatorCurrents(IEnumerable measurements, List sensors) + private List> ExtractActuatorCurrents(IEnumerable measurements) { var actuatorsCurrents = new List>(); @@ -188,6 +177,8 @@ private List> ExtractActuatorCurrents(IEnumerable .GroupBy(m => m.SensorName) .ToDictionary(g => g.Key, g => g.ToList()); + var sensors = measurementsBySensor.Keys.ToList(); + // Extract currents for each sensor/Actuator foreach (string sensor in sensors) { diff --git a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs similarity index 97% rename from src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs rename to src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs index 8663cbc..cbe9ce8 100644 --- a/src/DataAggregator.Processor/Services/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs +++ b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/MathUtils.cs @@ -1,4 +1,4 @@ -namespace DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; +namespace DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; /// /// Utility class for mathematical operations used in feature extraction. diff --git a/src/DataAggregator.Processor/Configuration/PreprocessingConfig.cs b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/PreprocessingConfig.cs similarity index 83% rename from src/DataAggregator.Processor/Configuration/PreprocessingConfig.cs rename to src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/PreprocessingConfig.cs index 3b1a9ce..5825ccb 100644 --- a/src/DataAggregator.Processor/Configuration/PreprocessingConfig.cs +++ b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/PreprocessingConfig.cs @@ -1,4 +1,4 @@ -namespace DataAggregator.Processor.Configuration; +namespace DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; /// /// Configuration for preprocessing operations including Z-score normalization. diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index 3f0cc8d..e5f7af0 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -24,8 +24,6 @@ { "MachineName": "Micro5", "Enabled": true, - "ModelPath": "resources/opencn_model.onnx", - "PreprocessingStrategy": "actuatorcurrent", "InputSensors": [ "current-amp-x", "current-amp-y", @@ -34,27 +32,38 @@ "current-amp-c", "current-amp-s" ], - "WindowSizeSeconds": 2, - "CycleIntervalSeconds": 2, - "Preprocessing": { - "EnableZScoreNormalization": true, - "NormalizationParameters": { - "GlobalActivityRatio": [ 0.004157, 0.017644 ], - "GlobalChangeDensity": [ 0.432407, 0.143321 ], - "InterAxisMeanCorrelation": [ -0.001363, 0.072809 ], - "InterAxisMaxCorrelation": [ 0.439072, 0.267887 ], - "InterAxisCorrelationVariance": [ 0.225831, 0.129697 ], - "AxisSynchronization": [ -38.172882, 221.177505 ], - "AxisLoadBalance": [ -0.045936, 0.327285 ], - "TemporalStability": [ 0.974642, 0.046761 ], - "GlobalSkewness": [ -0.129752, 0.362594 ], - "GlobalKurtosis": [ -0.570948, 0.396184 ], - "GlobalTrendSlope": [ -0.000001, 0.000179 ], - "CoefficientOfVariation": [ -17.267527, 234.962006 ], - "NormalizedIqrMedian": [ 0.633865, 107.640205 ], - "NormalizedIqrMean": [ -14.154306, 267.521179 ] + "WindowSizeSeconds": 3, + "CycleIntervalSeconds": 1, + "ProcessingPipeline": [ + { + "Strategy": "actuatorcurrent", + "EnableZScoreNormalization": true, + "NormalizationParameters": { + "GlobalActivityRatio": [0.004157, 0.017644], + "GlobalChangeDensity": [0.432407, 0.143321], + "InterAxisMeanCorrelation": [-0.001363, 0.072809], + "InterAxisMaxCorrelation": [0.439072, 0.267887], + "InterAxisCorrelationVariance": [0.225831, 0.129697], + "AxisSynchronization": [-38.172882, 221.177505], + "AxisLoadBalance": [-0.045936, 0.327285], + "TemporalStability": [0.974642, 0.046761], + "GlobalSkewness": [-0.129752, 0.362594], + "GlobalKurtosis": [-0.570948, 0.396184], + "GlobalTrendSlope": [-0.000001, 0.000179], + "CoefficientOfVariation": [-17.267527, 234.962006], + "NormalizedIqrMedian": [0.633865, 107.640205], + "NormalizedIqrMean": [-14.154306, 267.521179] + } + }, + { + "Strategy": "onnxprediction", + "ModelPath": "resources/opencn_model.onnx" + }, + { + "Strategy": "myCustomPostProcess", + "Threshold": 0.5 } - } + ] } ] } From 90f4d8cbd9fd88a499adbdf80841062b9f01b5c0 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Sun, 27 Jul 2025 18:10:09 +0200 Subject: [PATCH 53/70] feat: adapt tests --- .../ActuatorCurrentFeatureExtractorTests.cs | 121 ++--------- .../DataProcessorFactoryTests.cs | 76 +++++++ .../Services/PreProcessing/MathUtilsTests.cs | 2 +- .../PreprocessingStrategyFactoryTests.cs | 48 ----- .../MachinePredictionProcessorTests.cs | 198 ++++-------------- .../Prediction/OnnxPredictionEngineTests.cs | 121 +++-------- .../PredictionBackgroundServiceTests.cs | 88 ++------ 7 files changed, 191 insertions(+), 463 deletions(-) create mode 100644 tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs delete mode 100644 tests/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs diff --git a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs index 5671bbc..ceb8a75 100644 --- a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/ActuatorCurrentFeatureExtractorTests.cs @@ -1,143 +1,83 @@ using DataAggregator.Collector.Shared.Models; -using DataAggregator.Processor.Configuration; -using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; +using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; namespace DataAggregator.Processor.Tests.Services.PreProcessing; -/// -/// Tests for the class. -/// public class ActuatorCurrentFeatureExtractorTests { private readonly ActuatorCurrentFeatureExtractor _featureExtractor; - /// - /// Initializes a new instance of the class. - /// public ActuatorCurrentFeatureExtractorTests() - => _featureExtractor = new ActuatorCurrentFeatureExtractor(); - - #region PreprocessAsync tests + => _featureExtractor = new ActuatorCurrentFeatureExtractor(CreateValidPreprocessingConfig()); [Fact] - public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenValidDataProvided() + public async Task ProcessAsync_ShouldReturnFifteenFeatures_WhenValidDataProvided() { - // Arrange - List measurements = CreateTestMeasurements(); - MachinePredictionConfig config = CreateValidConfig(); - - // Act - var result = _featureExtractor.PreprocessAsync(measurements, config); - - // Assert + var measurements = CreateTestMeasurements(); + var result = await _featureExtractor.ProcessAsync(measurements); Assert.NotNull(result); Assert.Equal(15, result.Count()); } [Fact] - public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptyMeasurementsProvided() + public async Task ProcessAsync_ShouldReturnFourteenFeatures_WhenEmptyMeasurementsProvided() { - // Arrange var measurements = new List(); - MachinePredictionConfig config = CreateValidConfig(); - - // Act - var result = _featureExtractor.PreprocessAsync(measurements, config); + var result = await _featureExtractor.ProcessAsync(measurements); result = result.Where(f => f.SensorName != "Label"); - - // Assert - Assert.NotNull(result); - Assert.Equal(14, result.Count()); - Assert.All(result, feature => Assert.Equal(0.0f, (float)feature.GetRawValue())); - } - - [Fact] - public void PreprocessAsync_ShouldReturnFourteenFeatures_WhenEmptySensorsListProvided() - { - // Arrange - List measurements = CreateTestMeasurements(); - MachinePredictionConfig config = CreateValidConfig(); - config.InputSensors.Clear(); - - // Act - var result = _featureExtractor.PreprocessAsync(measurements, config); - result = result.Where(f => f.SensorName != "Label"); - - // Assert Assert.NotNull(result); Assert.Equal(14, result.Count()); Assert.All(result, feature => Assert.Equal(0.0f, (float)feature.GetRawValue())); } + [Fact] - public void PreprocessAsync_ShouldReturnValidFeatures_WhenValidDataProvided() + public async Task ProcessAsync_ShouldReturnValidFeatures_WhenValidDataProvided() { - // Arrange - List measurements = CreateTestMeasurements(); - MachinePredictionConfig config = CreateValidConfig(); - - // Act - var result = _featureExtractor.PreprocessAsync(measurements, config); + var measurements = CreateTestMeasurements(); + var result = await _featureExtractor.ProcessAsync(measurements); result = result.Where(f => f.SensorName != "Label"); - - // Assert Assert.NotNull(result); Assert.Equal(14, result.Count()); - - // Check that features are within reasonable bounds Assert.All(result, feature => Assert.False(float.IsNaN((float)feature.GetRawValue()))); Assert.All(result, feature => Assert.False(float.IsInfinity((float)feature.GetRawValue()))); } [Fact] - public void PreprocessAsync_ShouldReturnZeroFeatures_WhenNoValidValuesFound() + public async Task ProcessAsync_ShouldReturnZeroFeatures_WhenNoValidValuesFound() { - // Arrange var measurements = new List { new MeasurementData(DateTime.UtcNow, "sensor1", float.NaN), new MeasurementData(DateTime.UtcNow, "sensor2", float.PositiveInfinity), new MeasurementData(DateTime.UtcNow, "sensor1", float.NegativeInfinity), }; - MachinePredictionConfig config = CreateValidConfig(); - - // Act - var result = _featureExtractor.PreprocessAsync(measurements, config); + var result = await _featureExtractor.ProcessAsync(measurements); result = result.Where(f => f.SensorName != "Label"); - - // Assert Assert.NotNull(result); Assert.Equal(14, result.Count()); Assert.All(result, feature => Assert.Equal(0.0f, (float)feature.GetRawValue())); } [Fact] - public void PreprocessAsync_ShouldHandleSingleValue_WhenOnlyOneValidMeasurementProvided() + public async Task ProcessAsync_ShouldHandleSingleValue_WhenOnlyOneValidMeasurementProvided() { - // Arrange var measurements = new List { new MeasurementData(DateTime.UtcNow, "sensor1", 10.5f), }; - MachinePredictionConfig config = CreateValidConfig(); - - // Act - var result = _featureExtractor.PreprocessAsync(measurements, config); + var result = await _featureExtractor.ProcessAsync(measurements); result = result.Where(f => f.SensorName != "Label"); - - // Assert Assert.NotNull(result); Assert.Equal(14, result.Count()); Assert.All(result, feature => Assert.False(float.IsNaN((float)feature.GetRawValue()))); } [Fact] - public void PreprocessAsync_ShouldHandleLargeDataset_WhenManyMeasurementsProvided() + public async Task ProcessAsync_ShouldHandleLargeDataset_WhenManyMeasurementsProvided() { - // Arrange var measurements = new List(); var random = new Random(42); - for (int i = 0; i < 1000; i++) { measurements.Add(new MeasurementData( @@ -149,24 +89,14 @@ public void PreprocessAsync_ShouldHandleLargeDataset_WhenManyMeasurementsProvide "sensor2", (float)random.NextDouble() * 100)); } - - MachinePredictionConfig config = CreateValidConfig(); - - // Act - var result = _featureExtractor.PreprocessAsync(measurements, config); + var result = await _featureExtractor.ProcessAsync(measurements); result = result.Where(f => f.SensorName != "Label"); - - // Assert Assert.NotNull(result); Assert.Equal(14, result.Count()); Assert.All(result, feature => Assert.False(float.IsNaN((float)feature.GetRawValue()))); Assert.All(result, feature => Assert.False(float.IsInfinity((float)feature.GetRawValue()))); } - #endregion - - #region Helper methods - private static List CreateTestMeasurements() => [ new MeasurementData(DateTime.UtcNow, "sensor1", 10.5f), new MeasurementData(DateTime.UtcNow, "sensor2", 20.3f), @@ -176,20 +106,9 @@ private static List CreateTestMeasurements() => [ new MeasurementData(DateTime.UtcNow, "sensor2", 22.5f) ]; - private static MachinePredictionConfig CreateValidConfig() => new() + private static PreprocessingConfig CreateValidPreprocessingConfig() => new() { - MachineName = "test_machine", - ModelPath = "test_model.onnx", - InputSensors = ["sensor1", "sensor2"], - PreprocessingStrategy = "ActuatorMergingCurrent", - WindowSizeSeconds = 1, - CycleIntervalSeconds = 1, - Enabled = true, - Preprocessing = new PreprocessingConfig - { - EnableZScoreNormalization = true, - }, + EnableZScoreNormalization = true, + NormalizationParameters = new Dictionary() }; - - #endregion } diff --git a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs new file mode 100644 index 0000000..b84065b --- /dev/null +++ b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs @@ -0,0 +1,76 @@ +using DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services.Processing.Factory; +using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; +using DataAggregator.Processor.Services.Prediction; +using DataAggregator.Processor.Services.Processing.PostProcessing.StateDeductionPostProcess; +using System.Text.Json; + +namespace DataAggregator.Processor.Tests.Services.PreProcessing; + +public class DataProcessorFactoryTests +{ + private readonly DataProcessorFactory _factory; + + public DataProcessorFactoryTests() => _factory = new DataProcessorFactory(); + + [Fact] + public void CreateProcessors_ShouldReturnCorrectProcessors_ForValidPipeline() + { + // Arrange + string pipelineJson = @"[ + { ""Strategy"": ""actuatorcurrent"", ""EnableZScoreNormalization"": true, ""NormalizationParameters"": {} }, + { ""Strategy"": ""onnxprediction"", ""ModelPath"": ""model.onnx"" }, + { ""Strategy"": ""statedeductionpostprocessor"", ""Threshold"": 2 } + ]"; + var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + + // Act + var processors = _factory.CreateProcessors(pipelineElements); + + // Assert + Assert.Equal(3, processors.Count); + Assert.IsType(processors[0]); + Assert.IsType(processors[1]); + Assert.IsType(processors[2]); + } + + [Fact] + public void CreateProcessors_ShouldThrowArgumentException_ForUnknownStrategy() + { + // Arrange + string pipelineJson = @"[ + { ""Strategy"": ""unknownstrategy"" } + ]"; + var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + + // Act & Assert + Assert.Throws(() => _factory.CreateProcessors(pipelineElements)); + } + + [Fact] + public void CreateProcessors_ShouldReturnEmptyList_ForEmptyPipeline() + { + // Arrange + string pipelineJson = "[]"; + var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + + // Act + var processors = _factory.CreateProcessors(pipelineElements); + + // Assert + Assert.Empty(processors); + } + + [Fact] + public void CreateProcessors_ShouldThrowArgumentException_WhenStrategyMissing() + { + // Arrange + string pipelineJson = @"[ + { ""EnableZScoreNormalization"": true } + ]"; + var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + + // Act & Assert + Assert.Throws(() => _factory.CreateProcessors(pipelineElements)); + } +} diff --git a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs index 53b30cd..6d86095 100644 --- a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/MathUtilsTests.cs @@ -1,4 +1,4 @@ -using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; +using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; namespace DataAggregator.Processor.Tests.Services.PreProcessing; diff --git a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs deleted file mode 100644 index 9f4e41b..0000000 --- a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/PreprocessingStrategyFactoryTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -using DataAggregator.Processor.Services.PreProcessing; -using DataAggregator.Processor.Services.PreProcessing.ActuatorMergingCurrentPreprocessing; - -namespace DataAggregator.Processor.Tests.Services.PreProcessing; - -/// -/// Tests for the class. -/// -public class PreprocessingStrategyFactoryTests -{ - private readonly PreprocessingStrategyFactory _factory; - - /// - /// Initializes a new instance of the class. - /// - public PreprocessingStrategyFactoryTests() => _factory = new PreprocessingStrategyFactory(); - - #region CreateStrategy tests - - [Fact] - public void CreateStrategy_ShouldReturnGoodStrategy() - { - string strategyName = "actuatorcurrent"; - - IPreprocessingStrategy strategy = _factory.CreateStrategy(strategyName); - - Assert.NotNull(strategy); - Assert.IsType(strategy); - } - - [Fact] - public void CreateStrategy_ShouldThrowArgumentException_WhenStrategyNameIsEmpty() - { - string strategyName = string.Empty; - - ArgumentException exception = Assert.Throws(() => _factory.CreateStrategy(strategyName)); - } - - [Fact] - public void CreateStrategy_ShouldThrowArgumentException_WhenStrategyNameIsUnknown() - { - string strategyName = "UnknownStrategy"; - - ArgumentException exception = Assert.Throws(() => _factory.CreateStrategy(strategyName)); - } - - #endregion -} diff --git a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs index b8aa884..24cf397 100644 --- a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -2,7 +2,8 @@ using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services.DataStorage; using DataAggregator.Processor.Services.Prediction; -using DataAggregator.Processor.Services.PreProcessing; +using DataAggregator.Processor.Services.Processing.Abstraction; +using DataAggregator.Processor.Services.Processing.Factory; using DataAggregator.Processor.Services.Registration; using DataAggregator.Shared.Configuration.TimeSeries; using DataAggregator.Shared.Domain.DataType; @@ -11,205 +12,117 @@ namespace DataAggregator.Processor.Tests.Services.Prediction; -/// -/// Tests for the class. -/// public class MachinePredictionProcessorTests { private readonly Mock _mockInfluxRepository; private readonly Mock _mockRegistrationClient; - private readonly Mock _mockPredictionEngine; - private readonly Mock _mockStrategyFactory; - private readonly Mock _mockPreprocessingStrategy; + private readonly Mock _mockProcessorFactory; private readonly MachinePredictionProcessor _processor; + private readonly Mock _mockProcessor; - /// - /// Initializes a new instance of the class. - /// public MachinePredictionProcessorTests() { _mockInfluxRepository = new Mock(); _mockRegistrationClient = new Mock(); - _mockPredictionEngine = new Mock(); - _mockStrategyFactory = new Mock(); - _mockPreprocessingStrategy = new Mock(); + _mockProcessorFactory = new Mock(); + _mockProcessor = new Mock(); _processor = new MachinePredictionProcessor( _mockInfluxRepository.Object, _mockRegistrationClient.Object, - _mockPredictionEngine.Object, - _mockStrategyFactory.Object); + _mockProcessorFactory.Object); } - #region ProcessAsync tests - [Fact] public async Task ProcessAsync_ShouldReturnEarly_WhenCollectorInfoIsNull() { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); + var config = CreateValidMachineConfig(); _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync((CollectorInfoDto?)null); - // Act await _processor.ProcessAsync(config); - // Assert _mockInfluxRepository.Verify(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); } [Fact] public async Task ProcessAsync_ShouldReturnEarly_WhenNoValidSensorsFound() { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); - CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(new[] { "different_sensor" }); + var config = CreateValidMachineConfig(); + var collectorInfo = CreateCollectorInfoWithSensors(new[] { "different_sensor" }); _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo); - // Act await _processor.ProcessAsync(config); - // Assert _mockInfluxRepository.Verify(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); } [Fact] public async Task ProcessAsync_ShouldReturnEarly_WhenNoMeasurementsFound() { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); - CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + var config = CreateValidMachineConfig(); + var collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo); _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) .ReturnsAsync([]); - // Act - await _processor.ProcessAsync(config); - - // Assert - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); - _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny>()), Times.Never); - } - - [Fact] - public async Task ProcessAsync_ShouldReturnEarly_WhenPreprocessingFails() - { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); - CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); - List measurements = CreateTestMeasurements(); - - _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) - .ReturnsAsync(collectorInfo); - _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) - .ReturnsAsync(measurements); - _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) - .Returns(_mockPreprocessingStrategy.Object); - _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) - .Returns(new List()); - - // Act await _processor.ProcessAsync(config); - // Assert - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Never); _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(It.IsAny(), It.IsAny>()), Times.Never); } [Fact] public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMet() { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); - CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); - List measurements = CreateTestMeasurements(); - var preprocessedData = ProcessorTestHelper.GetValidTestData(); - - IEnumerable results = - new List - { - new MeasurementData(DateTime.Now, "Prediction", 0.85f) - }; - - float[] predictions = [0.85f]; + var config = CreateValidMachineConfig(); + var collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + var measurements = CreateTestMeasurements(); + var processedData = new List { new MeasurementData(DateTime.Now, "Prediction", 0.85f) }; _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo); _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) .ReturnsAsync(measurements); - _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) - .Returns(_mockPreprocessingStrategy.Object); - _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) - .Returns(preprocessedData); - _mockPredictionEngine.Setup(x => x.PredictAsync(It.IsAny(), It.IsAny>())) - .ReturnsAsync(results); - - // Act + _mockProcessor.Setup(x => x.ProcessAsync(It.IsAny>())) + .ReturnsAsync(processedData); + _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) + .Returns(new List { _mockProcessor.Object }); + await _processor.ProcessAsync(config); - // Assert - _mockInfluxRepository.Verify( - x => x.InitializeAsync( - collectorInfo.AssignedInfluxEndpoint.Endpoint, - collectorInfo.AssignedInfluxEndpoint.Token), - Times.Once); - _mockInfluxRepository.Verify( - x => x.QueryMeasurementsAsync( - config.MachineName, - It.IsAny(), - It.IsAny(), - It.IsAny>()), - Times.Once); - _mockPredictionEngine.Verify(x => x.PredictAsync(It.IsAny(), It.IsAny>()), Times.Once); - _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(config.MachineName, It.IsAny>()), Times.Once); + _mockInfluxRepository.Verify(x => x.WriteMeasurementAsync(config.MachineName, processedData), Times.Once); } [Fact] public async Task ProcessAsync_ShouldReinitializeInfluxConnection_WhenEndpointChanges() { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); - CollectorInfoDto collectorInfo1 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint1"); - CollectorInfoDto collectorInfo2 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint2"); - List measurements = CreateTestMeasurements(); - - var now = DateTime.UtcNow; - var preprocessedData = ProcessorTestHelper.GetValidTestData(); - - var predictions = new List - { - new MeasurementData(now, "Prediction", 0.85f) - }; + var config = CreateValidMachineConfig(); + var collectorInfo1 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint1"); + var collectorInfo2 = CreateCollectorInfoWithSensors(config.InputSensors, "endpoint2"); + var measurements = CreateTestMeasurements(); + var processedData = new List { new MeasurementData(DateTime.Now, "Prediction", 0.85f) }; _mockRegistrationClient.SetupSequence(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo1) .ReturnsAsync(collectorInfo2); _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) .ReturnsAsync(measurements); - _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) - .Returns(_mockPreprocessingStrategy.Object); - _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) - .Returns(preprocessedData); - _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) - .ReturnsAsync(predictions); - - // Act + _mockProcessor.Setup(x => x.ProcessAsync(It.IsAny>())) + .ReturnsAsync(processedData); + _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) + .Returns(new List { _mockProcessor.Object }); + await _processor.ProcessAsync(config); // First call with endpoint1 await _processor.ProcessAsync(config); // Second call with endpoint2 - // Assert - _mockInfluxRepository.Verify( - x => x.InitializeAsync( + _mockInfluxRepository.Verify(x => x.Initialize( collectorInfo1.AssignedInfluxEndpoint.Endpoint, collectorInfo1.AssignedInfluxEndpoint.Token), Times.Once); - _mockInfluxRepository.Verify( - x => x.InitializeAsync( + _mockInfluxRepository.Verify(x => x.Initialize( collectorInfo2.AssignedInfluxEndpoint.Endpoint, collectorInfo2.AssignedInfluxEndpoint.Token), Times.Once); } @@ -217,34 +130,24 @@ public async Task ProcessAsync_ShouldReinitializeInfluxConnection_WhenEndpointCh [Fact] public async Task ProcessAsync_ShouldNotReinitializeInfluxConnection_WhenEndpointIsSame() { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); - CollectorInfoDto collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); - List measurements = CreateTestMeasurements(); - - var now = DateTime.UtcNow; - var preprocessedData = ProcessorTestHelper.GetValidTestData(); - - var predictions = new List { new MeasurementData(now, "Prediction", 0.85f) }; + var config = CreateValidMachineConfig(); + var collectorInfo = CreateCollectorInfoWithSensors(config.InputSensors); + var measurements = CreateTestMeasurements(); + var processedData = new List { new MeasurementData(DateTime.Now, "Prediction", 0.85f) }; _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ReturnsAsync(collectorInfo); _mockInfluxRepository.Setup(x => x.QueryMeasurementsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) .ReturnsAsync(measurements); - _mockStrategyFactory.Setup(x => x.CreateStrategy(config.PreprocessingStrategy)) - .Returns(_mockPreprocessingStrategy.Object); - _mockPreprocessingStrategy.Setup(x => x.PreprocessAsync(It.IsAny>(), It.IsAny())) - .Returns(preprocessedData); - _mockPredictionEngine.Setup(x => x.PredictAsync(config.ModelPath, preprocessedData)) - .ReturnsAsync(predictions); - - // Act + _mockProcessor.Setup(x => x.ProcessAsync(It.IsAny>())) + .ReturnsAsync(processedData); + _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) + .Returns(new List { _mockProcessor.Object }); + await _processor.ProcessAsync(config); await _processor.ProcessAsync(config); - // Assert - _mockInfluxRepository.Verify( - x => x.InitializeAsync( + _mockInfluxRepository.Verify(x => x.Initialize( It.IsAny(), It.IsAny()), Times.Once); } @@ -252,28 +155,21 @@ public async Task ProcessAsync_ShouldNotReinitializeInfluxConnection_WhenEndpoin [Fact] public async Task ProcessAsync_ShouldThrowException_WhenRegistrationClientThrows() { - // Arrange - MachinePredictionConfig config = CreateValidMachineConfig(); + var config = CreateValidMachineConfig(); _mockRegistrationClient.Setup(x => x.GetCollectorInfoAsync(config.MachineName)) .ThrowsAsync(new InvalidOperationException("Registration service error")); - // Act & Assert await Assert.ThrowsAsync(() => _processor.ProcessAsync(config)); } - #endregion - - #region Helper methods - private static MachinePredictionConfig CreateValidMachineConfig() => new() { MachineName = "test_machine", - ModelPath = "test_model.onnx", InputSensors = ["sensor1", "sensor2"], - PreprocessingStrategy = "test_strategy", WindowSizeSeconds = 300, CycleIntervalSeconds = 60, Enabled = true, + ProcessingPipeline = new List { new { Strategy = "mock" } } }; private static CollectorInfoDto CreateCollectorInfoWithSensors(IEnumerable sensorNames, string endpoint = "http://localhost:8086") => new( @@ -290,6 +186,4 @@ private static List CreateTestMeasurements() new MeasurementData(DateTime.UtcNow, "sensor1", 10.5f), new MeasurementData(DateTime.UtcNow, "sensor2", 20.3f) ]; - - #endregion } diff --git a/tests/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs b/tests/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs index 5c1edd5..02733b4 100644 --- a/tests/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/Prediction/OnnxPredictionEngineTests.cs @@ -1,7 +1,10 @@ using System.Reflection.Metadata; using DataAggregator.Collector.Shared.Models; +using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services.Prediction; using Microsoft.ML.OnnxRuntime; +using System.Threading.Tasks; +using DataAggregator.Processor.Services.Processing.Onnx; namespace DataAggregator.Processor.Tests.Services.Prediction; @@ -10,145 +13,83 @@ namespace DataAggregator.Processor.Tests.Services.Prediction; /// public class OnnxPredictionEngineTests : IDisposable { - private readonly OnnxPredictionEngine _predictionEngine; + private OnnxPredictionEngine _predictionEngine; - /// - /// Initializes a new instance of the class. - /// public OnnxPredictionEngineTests() - => _predictionEngine = new OnnxPredictionEngine(); - - #region PredictAsync tests + { + // L'instance sera créée dans chaque test avec la bonne config + } [Fact] - public async Task PredictAsync_ShouldThrowFileNotFoundException_WhenModelPathDoesNotExist() + public async Task ProcessAsync_ShouldThrowFileNotFoundException_WhenModelPathDoesNotExist() { - // Arrange - string nonExistentModelPath = "non_existent_model.onnx"; - + var config = new OnnxPredictionConfig { ModelPath = "non_existent_model.onnx" }; + _predictionEngine = new OnnxPredictionEngine(config); var inputData = new List { new MeasurementData(DateTime.UtcNow, "GlobalActivityRatio", 1.0f), new MeasurementData(DateTime.UtcNow, "GlobalChangeDensity", 2.0f), new MeasurementData(DateTime.UtcNow, "InterAxisMeanCorrelation", 3.0f), }; - - // Act & Assert - FileNotFoundException exception = await Assert.ThrowsAsync( - () => _predictionEngine.PredictAsync(nonExistentModelPath, inputData)); + await Assert.ThrowsAsync(() => _predictionEngine.ProcessAsync(inputData)); } [Fact] - public async Task PredictAsync_ShouldCacheModel_WhenSameModelPathIsUsedMultipleTimes() + public async Task ProcessAsync_ShouldReturnCorrectOutputShape_WhenValidInputProvided() { - // Arrange - string modelPath = _testModelPath; - string copyPath = Path.Combine("resources", "opencn_model_copy.onnx"); - File.Copy(modelPath, copyPath, true); - + var config = new OnnxPredictionConfig { ModelPath = _testModelPath }; + _predictionEngine = new OnnxPredictionEngine(config); var inputData = ProcessorTestHelper.GetValidTestData(); - - // Act - IEnumerable result1 = await _predictionEngine.PredictAsync(copyPath, inputData); - File.Delete(copyPath); // Simulate model file deletion - IEnumerable result2 = await _predictionEngine.PredictAsync(copyPath, inputData); - - // Assert - Assert.NotNull(result1); - Assert.NotNull(result2); - Assert.Equal(result1.Count(), result2.Count()); - } - - [Fact] - public async Task PredictAsync_ShouldReturnCorrectOutputShape_WhenValidInputProvided() - { - // Arrange - string modelPath = _testModelPath; - var now = DateTime.UtcNow; - var inputData = ProcessorTestHelper.GetValidTestData(); - - // Act - IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); - - // Assert + var result = await _predictionEngine.ProcessAsync(inputData); Assert.NotNull(result); Assert.True(result.Count() > 0); } [Fact] - public async Task PredictAsync_ShouldReturnGoodResult_DifferentStateDataProvided() + public async Task ProcessAsync_ShouldReturnGoodResult_DifferentStateDataProvided() { - // Arrange - string modelPath = _testModelPath; + var config = new OnnxPredictionConfig { ModelPath = _testModelPath }; + _predictionEngine = new OnnxPredictionEngine(config); var inputDataShutdown = ProcessorTestHelper.GetValidShutdownStateData(); var inputDataProduction = ProcessorTestHelper.GetValidProductionStateData(); var inputDataIdle = ProcessorTestHelper.GetValidIdleStateData(); - - // Act - IEnumerable resultShutdown = await _predictionEngine.PredictAsync(modelPath, inputDataShutdown); - IEnumerable resultProduction = await _predictionEngine.PredictAsync(modelPath, inputDataProduction); - IEnumerable resultIdle = await _predictionEngine.PredictAsync(modelPath, inputDataIdle); - - // Assert + var resultShutdown = await _predictionEngine.ProcessAsync(inputDataShutdown); + var resultProduction = await _predictionEngine.ProcessAsync(inputDataProduction); + var resultIdle = await _predictionEngine.ProcessAsync(inputDataIdle); Assert.NotNull(resultShutdown); Assert.NotNull(resultProduction); Assert.NotNull(resultIdle); Assert.Equal("disable", resultShutdown.First(x => x.SensorName == "PredictedLabel.output_0").GetRawValue().ToString()); Assert.Equal("production", resultProduction.First(x => x.SensorName == "PredictedLabel.output_0").GetRawValue().ToString()); - Assert.Equal("enable", resultIdle.First(static x => x.SensorName == "PredictedLabel.output_0").GetRawValue().ToString()); - + Assert.Equal("enable", resultIdle.First(x => x.SensorName == "PredictedLabel.output_0").GetRawValue().ToString()); } [Fact] - public async Task PredictAsync_ShouldHandleEmptyInputArray_WhenProvided() + public async Task ProcessAsync_ShouldHandleEmptyInputArray_WhenProvided() { - // Arrange - string modelPath = _testModelPath; + var config = new OnnxPredictionConfig { ModelPath = _testModelPath }; + _predictionEngine = new OnnxPredictionEngine(config); var inputData = new List(); - - // Act - IEnumerable result = await _predictionEngine.PredictAsync(modelPath, inputData); - - // Assert + var result = await _predictionEngine.ProcessAsync(inputData); Assert.NotNull(result); } - #endregion - - #region Dispose tests - [Fact] public void Dispose_ShouldClearModelCache_WhenCalled() { - // Arrange - string modelPath = _testModelPath; + var config = new OnnxPredictionConfig { ModelPath = _testModelPath }; + _predictionEngine = new OnnxPredictionEngine(config); IEnumerable inputData = ProcessorTestHelper.GetValidTestData(); - try { - // Act - Load model into cache - _ = _predictionEngine.PredictAsync(modelPath, inputData).Result; - - // Act - Dispose + _ = _predictionEngine.ProcessAsync(inputData).Result; _predictionEngine.Dispose(); - - // Assert - Should be able to dispose without exception - Assert.True(true); // If we reach here, no exception was thrown - } - finally - { - // do nothing + Assert.True(true); } + finally { } } - #endregion - - #region Helper methods - private static string _testModelPath = Path.Combine("resources", "opencn_model.onnx"); - #endregion - - /// public void Dispose() => _predictionEngine?.Dispose(); } diff --git a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs index 01cc656..d274df0 100644 --- a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs @@ -56,33 +56,26 @@ public PredictionBackgroundServiceTests() [Fact] public async Task ExecuteAsync_ShouldStartSuccessfully_WhenValidConfigurationProvided() { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); + var config = CreateValidConfiguration(); _mockConfiguration.Setup(x => x.Value).Returns(config); - // Act Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start + await Task.Delay(100); await _backgroundService.StopAsync(_cancellationTokenSource.Token); - // Assert - Assert.True(true); // If we reach here, no exception was thrown + Assert.True(true); } [Fact] public async Task ExecuteAsync_ShouldScheduleEnabledMachines_WhenConfigurationContainsEnabledMachines() { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); + var config = CreateValidConfiguration(); _mockConfiguration.Setup(x => x.Value).Returns(config); - // Act Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start + await Task.Delay(100); await _backgroundService.StopAsync(_cancellationTokenSource.Token); - // Assert - // The service should have started without throwing exceptions _mockPredictionProcessor.Verify( x => x.ProcessAsync(It.IsAny()), Times.Exactly(config.Machines.Count(m => m.Enabled))); @@ -91,86 +84,50 @@ public async Task ExecuteAsync_ShouldScheduleEnabledMachines_WhenConfigurationCo [Fact] public async Task ExecuteAsync_ShouldHandleEmptyMachineList_WhenConfigurationContainsNoMachines() { - // Arrange var config = new PredictionServiceConfiguration { Machines = [], }; _mockConfiguration.Setup(x => x.Value).Returns(config); - // Act Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start + await Task.Delay(100); await _backgroundService.StopAsync(_cancellationTokenSource.Token); - // Assert - // The service should have started without throwing exceptions Assert.True(true); } - [Fact] - public async Task ExecuteAsync_ShouldThrowFileNotFoundException_WhenModelFileDoesNotExist() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - config.Machines[0].ModelPath = "non_existent_model.onnx"; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act & Assert - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - [Fact] public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenMachineNameIsEmpty() { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); + var config = CreateValidConfiguration(); config.Machines[0].MachineName = string.Empty; _mockConfiguration.Setup(x => x.Value).Returns(config); - // Act & Assert await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); } [Fact] public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenNoInputSensorsConfigured() { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); + var config = CreateValidConfiguration(); config.Machines[0].InputSensors.Clear(); _mockConfiguration.Setup(x => x.Value).Returns(config); - // Act & Assert - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - - [Fact] - public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenPreprocessingStrategyIsEmpty() - { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); - config.Machines[0].PreprocessingStrategy = string.Empty; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - // Act & Assert await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); } [Fact] public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() { - // Arrange - PredictionServiceConfiguration config = CreateValidConfiguration(); + var config = CreateValidConfiguration(); _mockConfiguration.Setup(x => x.Value).Returns(config); - // Act Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); - await Task.Delay(100); // Give it time to start + await Task.Delay(100); _cancellationTokenSource.Cancel(); await _backgroundService.StopAsync(_cancellationTokenSource.Token); - // Assert - // The service should have stopped without throwing exceptions Assert.True(true); } @@ -178,47 +135,36 @@ public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() #region Helper methods - private static PredictionServiceConfiguration CreateValidConfiguration() + private static PredictionServiceConfiguration CreateValidConfiguration() => new PredictionServiceConfiguration { - // Create a temporary model file for testing - string tempModelPath = Path.GetTempFileName() + ".onnx"; - File.WriteAllText(tempModelPath, "dummy model content"); - - return new PredictionServiceConfiguration - { - Machines = + Machines = [ new MachinePredictionConfig { MachineName = "test_machine_1", - ModelPath = tempModelPath, InputSensors = ["sensor1", "sensor2"], - PreprocessingStrategy = "ActuatorMergingCurrent", WindowSizeSeconds = 300, CycleIntervalSeconds = 60, Enabled = true, - Preprocessing = new PreprocessingConfig + ProcessingPipeline = new List { - EnableZScoreNormalization = true + new { Strategy = "actuatorcurrent", EnableZScoreNormalization = true, NormalizationParameters = new Dictionary() }, }, }, new MachinePredictionConfig { MachineName = "test_machine_2", - ModelPath = tempModelPath, InputSensors = ["sensor3", "sensor4"], - PreprocessingStrategy = "ActuatorMergingCurrent", WindowSizeSeconds = 600, CycleIntervalSeconds = 120, Enabled = true, - Preprocessing = new PreprocessingConfig + ProcessingPipeline = new List { - EnableZScoreNormalization = true + new { Strategy = "actuatorcurrent", EnableZScoreNormalization = true, NormalizationParameters = new Dictionary() }, }, } ], - }; - } + }; #endregion From b5f462a7a543d0407bf043343b70dfe7e5a0165c Mon Sep 17 00:00:00 2001 From: CoJaques Date: Mon, 28 Jul 2025 10:36:09 +0200 Subject: [PATCH 54/70] fix: Configuration management --- .../Configuration/MachinePredictionConfig.cs | 6 +- src/DataAggregator.Processor/Program.cs | 15 +++- .../Prediction/MachinePredictionProcessor.cs | 5 +- .../Services/PredictionBackgroundService.cs | 9 ++- .../Abstraction/IProcessorConfiguration.cs | 8 +++ .../Factory/DataProcessorFactory.cs | 40 +++++------ .../Factory/IDataProcessorFactory.cs | 5 +- .../Factory/ProcessorDescription.cs | 21 ++++++ .../ProcessorDescriptionJsonConverter.cs | 44 ++++++++++++ .../Processing/Onnx/OnnxPredictionConfig.cs | 4 +- .../Processing/Onnx/OnnxPredictionEngine.cs | 4 +- .../StateDeductionPostProcessorConfig.cs | 4 +- .../PreprocessingConfig.cs | 6 +- .../DataProcessorFactoryTests.cs | 68 ++++++++++++------- .../MachinePredictionProcessorTests.cs | 8 +-- .../PredictionBackgroundServiceTests.cs | 26 +++++-- 16 files changed, 198 insertions(+), 75 deletions(-) create mode 100644 src/DataAggregator.Processor/Services/Processing/Abstraction/IProcessorConfiguration.cs create mode 100644 src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescription.cs create mode 100644 src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescriptionJsonConverter.cs diff --git a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs index 1878a4a..d3ba85e 100644 --- a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs +++ b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs @@ -1,4 +1,6 @@ -namespace DataAggregator.Processor.Configuration; +using DataAggregator.Processor.Services.Processing.Factory; + +namespace DataAggregator.Processor.Configuration; /// /// Configuration for a specific machine prediction. @@ -33,5 +35,5 @@ public class MachinePredictionConfig /// /// Gets or sets the processing pipeline for this machine. /// - public List? ProcessingPipeline { get; set; } + public List ProcessingPipeline { get; set; } = new(); } diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index acf3413..6827537 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services; using DataAggregator.Processor.Services.DataStorage; @@ -25,8 +26,18 @@ // Register health checks builder.Services.AddHealthChecks(); -// Configure prediction service -builder.Services.Configure(builder.Configuration.GetSection("PredictionService")); +// Configuration management +string appSettingsJson = File.ReadAllText("appsettings.json"); +using var doc = JsonDocument.Parse(appSettingsJson); +JsonElement predictionServiceElement = doc.RootElement.GetProperty("PredictionService"); +string json = predictionServiceElement.GetRawText(); + +var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; +options.Converters.Add(new ProcessorDescriptionJsonConverter()); +PredictionServiceConfiguration? predictionConfig = JsonSerializer.Deserialize(json, options); +if (predictionConfig == null) + throw new Exception("Failed to deserialize PredictionServiceConfiguration"); +builder.Services.AddSingleton(predictionConfig); // Configure HTTP clients builder.Services.AddHttpClient("RegistrationClient", client => diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index c2f57b0..0aed8a9 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using DataAggregator.Collector.Shared.Models; using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services.DataStorage; @@ -130,9 +129,7 @@ private bool BuildPipelineIfNeeded(MachinePredictionConfig config) return false; } - string pipelineJson = JsonSerializer.Serialize(config.ProcessingPipeline); - var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); - _pipelineProcessors = processorFactory.CreateProcessors(pipelineElements); + _pipelineProcessors = processorFactory.CreateProcessors(config.ProcessingPipeline); } return true; diff --git a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs index 3e7c7c5..0406eb6 100644 --- a/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs +++ b/src/DataAggregator.Processor/Services/PredictionBackgroundService.cs @@ -1,6 +1,5 @@ using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services.Prediction; -using Microsoft.Extensions.Options; using Serilog; namespace DataAggregator.Processor.Services; @@ -14,7 +13,7 @@ namespace DataAggregator.Processor.Services; /// The prediction service configuration. /// The service provider. public class PredictionBackgroundService( - IOptions configuration, + PredictionServiceConfiguration configuration, IServiceProvider serviceProvider) : BackgroundService { #region Private fields @@ -37,7 +36,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) ValidateConfigurationAsync(); // Schedule machines - foreach (MachinePredictionConfig machineConfig in configuration.Value.Machines) + foreach (MachinePredictionConfig machineConfig in configuration.Machines) { if (machineConfig.Enabled) { @@ -47,7 +46,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) Log.Information( "Prediction background service started with {MachineCount} machines", - configuration.Value.Machines.Count(m => m.Enabled)); + configuration.Machines.Count(m => m.Enabled)); // Keep the service running while (!stoppingToken.IsCancellationRequested) @@ -84,7 +83,7 @@ public override async Task StopAsync(CancellationToken cancellationToken) private void ValidateConfigurationAsync() { - var enabledMachines = configuration.Value.Machines.Where(m => m.Enabled).ToList(); + var enabledMachines = configuration.Machines.Where(m => m.Enabled).ToList(); if (enabledMachines.Count == 0) { diff --git a/src/DataAggregator.Processor/Services/Processing/Abstraction/IProcessorConfiguration.cs b/src/DataAggregator.Processor/Services/Processing/Abstraction/IProcessorConfiguration.cs new file mode 100644 index 0000000..bc235a6 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/Abstraction/IProcessorConfiguration.cs @@ -0,0 +1,8 @@ +namespace DataAggregator.Processor.Services.Processing.Abstraction; + +/// +/// Flag interface for processor configuration. +/// +public interface IProcessorConfiguration +{ +} diff --git a/src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs b/src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs index 0e6420c..4e0a97a 100644 --- a/src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs +++ b/src/DataAggregator.Processor/Services/Processing/Factory/DataProcessorFactory.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using DataAggregator.Processor.Services.Prediction; using DataAggregator.Processor.Services.Processing.Abstraction; using DataAggregator.Processor.Services.Processing.Onnx; @@ -8,43 +7,38 @@ namespace DataAggregator.Processor.Services.Processing.Factory; /// -/// Factory implementation for creating processor strategies. +/// Defines a factory for creating data processors based on a pipeline description. /// public class DataProcessorFactory : IDataProcessorFactory { /// - public List CreateProcessors(IEnumerable pipelineConfig) + public List CreateProcessors(IEnumerable pipeline) { var processors = new List(); - foreach (JsonElement element in pipelineConfig) + foreach (ProcessorDescription desc in pipeline) { - if (!element.TryGetProperty("Strategy", out JsonElement strategyProp)) - throw new ArgumentException("Each processor config must have a 'Strategy' property."); - - string? strategy = strategyProp.GetString()?.ToLowerInvariant(); - - switch (strategy) + switch (desc.Name.ToLowerInvariant()) { case "actuatorcurrent": - PreprocessingConfig? preConfig = element.Deserialize(); - if (preConfig == null) - throw new ArgumentException("Preprocessing configuration is required for ActuatorCurrentFeatureExtractor."); - processors.Add(new ActuatorCurrentFeatureExtractor(preConfig)); + if (desc.Configuration is PreprocessingConfig preConfig) + processors.Add(new ActuatorCurrentFeatureExtractor(preConfig)); + else + throw new ArgumentException("Invalid config type for actuatorcurrent"); break; case "onnxprediction": - OnnxPredictionConfig? onnxConfig = element.Deserialize(); - if (onnxConfig == null) - throw new ArgumentException("ONNX prediction configuration is required."); - processors.Add(new OnnxPredictionEngine(onnxConfig)); + if (desc.Configuration is OnnxPredictionConfig onnxConfig) + processors.Add(new OnnxPredictionEngine(onnxConfig)); + else + throw new ArgumentException("Invalid config type for onnxprediction"); break; case "statedeductionpostprocessor": - StateDeductionPostProcessorConfig? postConfig = element.Deserialize(); - if (postConfig == null) - throw new ArgumentException("Post-processing configuration is required for MyCustomPostProcessor."); - processors.Add(new StateDeductionPostProcessor(postConfig)); + if (desc.Configuration is StateDeductionPostProcessorConfig stateConfig) + processors.Add(new StateDeductionPostProcessor(stateConfig)); + else + throw new ArgumentException("Invalid config type for statedeductionpostprocessor"); break; default: - throw new ArgumentException($"Unknown processor strategy: {strategy}"); + throw new ArgumentException($"Unknown processor name: {desc.Name}"); } } diff --git a/src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs b/src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs index 2aee1bb..2d2789d 100644 --- a/src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs +++ b/src/DataAggregator.Processor/Services/Processing/Factory/IDataProcessorFactory.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using DataAggregator.Processor.Services.Processing.Abstraction; namespace DataAggregator.Processor.Services.Processing.Factory; @@ -11,7 +10,7 @@ public interface IDataProcessorFactory /// /// Create multiple data processor which correspond to the pipeline configuration. /// - /// The pipeline configuration. + /// The pipeline configuration. /// Configured pipeline strategy. - public List CreateProcessors(IEnumerable pipelineConfig); + public List CreateProcessors(IEnumerable pipeline); } diff --git a/src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescription.cs b/src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescription.cs new file mode 100644 index 0000000..e61c337 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescription.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; +using DataAggregator.Processor.Services.Processing.Abstraction; + +namespace DataAggregator.Processor.Services.Processing.Factory; + +/// +/// Describes a processor with its name and configuration. +/// +[JsonConverter(typeof(ProcessorDescriptionJsonConverter))] +public class ProcessorDescription +{ + /// + /// Gets or sets the name of the processor. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets the configuration for the processor. + /// + public IProcessorConfiguration? Configuration { get; set; } +} diff --git a/src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescriptionJsonConverter.cs b/src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescriptionJsonConverter.cs new file mode 100644 index 0000000..a54c262 --- /dev/null +++ b/src/DataAggregator.Processor/Services/Processing/Factory/ProcessorDescriptionJsonConverter.cs @@ -0,0 +1,44 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using DataAggregator.Processor.Services.Processing.Abstraction; +using DataAggregator.Processor.Services.Processing.Onnx; +using DataAggregator.Processor.Services.Processing.PostProcessing.StateDeductionPostProcess; +using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; + +namespace DataAggregator.Processor.Services.Processing.Factory; + +/// +/// Coverts to and from JSON. +/// +public class ProcessorDescriptionJsonConverter : JsonConverter +{ + /// + public override ProcessorDescription Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var jsonDoc = JsonDocument.ParseValue(ref reader); + JsonElement root = jsonDoc.RootElement; + string name = root.GetProperty("Name").GetString() ?? string.Empty; + JsonElement configElement = root.GetProperty("Configuration"); + IProcessorConfiguration? config = name.ToLowerInvariant() switch + { + "actuatorcurrent" => configElement.Deserialize(options), + "onnxprediction" => configElement.Deserialize(options), + "statedeductionpostprocessor" => configElement.Deserialize(options), + _ => null, + }; + return new ProcessorDescription { Name = name, Configuration = config }; + } + + /// + public override void Write(Utf8JsonWriter writer, ProcessorDescription value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString("Name", value.Name); + writer.WritePropertyName("Configuration"); + if (value.Configuration != null) + JsonSerializer.Serialize(writer, value.Configuration, value.Configuration.GetType(), options); + else + writer.WriteNullValue(); + writer.WriteEndObject(); + } +} diff --git a/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs index d76903a..d8c1c09 100644 --- a/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs +++ b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionConfig.cs @@ -1,9 +1,11 @@ +using DataAggregator.Processor.Services.Processing.Abstraction; + namespace DataAggregator.Processor.Services.Processing.Onnx; /// /// Configuration for ONNX prediction. /// -public class OnnxPredictionConfig +public class OnnxPredictionConfig : IProcessorConfiguration { /// /// Gets or sets the path of the model. diff --git a/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionEngine.cs b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionEngine.cs index ef93280..ddb36b1 100644 --- a/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionEngine.cs +++ b/src/DataAggregator.Processor/Services/Processing/Onnx/OnnxPredictionEngine.cs @@ -27,7 +27,9 @@ public async Task> ProcessAsync( try { // Load the model or get the existing session - InferenceSession session = LoadOrGetModel(config.ModelPath); + string executablePath = AppContext.BaseDirectory; + string modelPath = Path.Combine(executablePath, config.ModelPath); + InferenceSession session = LoadOrGetModel(modelPath); if (!input.Any()) { diff --git a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs index efd57b6..3473550 100644 --- a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs +++ b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessorConfig.cs @@ -1,9 +1,11 @@ +using DataAggregator.Processor.Services.Processing.Abstraction; + namespace DataAggregator.Processor.Services.Processing.PostProcessing.StateDeductionPostProcess; /// /// Configuration for . /// -public class StateDeductionPostProcessorConfig +public class StateDeductionPostProcessorConfig : IProcessorConfiguration { /// /// Gets or sets the threshold for minimum number of consecutive cycles required to deduce a state change. diff --git a/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/PreprocessingConfig.cs b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/PreprocessingConfig.cs index 5825ccb..bbec98f 100644 --- a/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/PreprocessingConfig.cs +++ b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/PreprocessingConfig.cs @@ -1,9 +1,11 @@ -namespace DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; +using DataAggregator.Processor.Services.Processing.Abstraction; + +namespace DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; /// /// Configuration for preprocessing operations including Z-score normalization. /// -public class PreprocessingConfig +public class PreprocessingConfig : IProcessorConfiguration { /// /// Gets or sets a value indicating whether Z-score normalization is enabled. diff --git a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs index b84065b..e4cbe7b 100644 --- a/tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/PreProcessing/DataProcessorFactoryTests.cs @@ -3,7 +3,8 @@ using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; using DataAggregator.Processor.Services.Prediction; using DataAggregator.Processor.Services.Processing.PostProcessing.StateDeductionPostProcess; -using System.Text.Json; +using System.Collections.Generic; +using DataAggregator.Processor.Services.Processing.Onnx; namespace DataAggregator.Processor.Tests.Services.PreProcessing; @@ -17,15 +18,37 @@ public class DataProcessorFactoryTests public void CreateProcessors_ShouldReturnCorrectProcessors_ForValidPipeline() { // Arrange - string pipelineJson = @"[ - { ""Strategy"": ""actuatorcurrent"", ""EnableZScoreNormalization"": true, ""NormalizationParameters"": {} }, - { ""Strategy"": ""onnxprediction"", ""ModelPath"": ""model.onnx"" }, - { ""Strategy"": ""statedeductionpostprocessor"", ""Threshold"": 2 } - ]"; - var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + var pipeline = new List + { + new ProcessorDescription + { + Name = "actuatorcurrent", + Configuration = new PreprocessingConfig + { + EnableZScoreNormalization = true, + NormalizationParameters = new Dictionary() + } + }, + new ProcessorDescription + { + Name = "onnxprediction", + Configuration = new OnnxPredictionConfig + { + ModelPath = "model.onnx" + } + }, + new ProcessorDescription + { + Name = "statedeductionpostprocessor", + Configuration = new StateDeductionPostProcessorConfig + { + Threshold = 2 + } + } + }; // Act - var processors = _factory.CreateProcessors(pipelineElements); + var processors = _factory.CreateProcessors(pipeline); // Assert Assert.Equal(3, processors.Count); @@ -35,42 +58,41 @@ public void CreateProcessors_ShouldReturnCorrectProcessors_ForValidPipeline() } [Fact] - public void CreateProcessors_ShouldThrowArgumentException_ForUnknownStrategy() + public void CreateProcessors_ShouldThrowArgumentException_ForUnknownName() { // Arrange - string pipelineJson = @"[ - { ""Strategy"": ""unknownstrategy"" } - ]"; - var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + var pipeline = new List + { + new ProcessorDescription { Name = "unknownstrategy", Configuration = null } + }; // Act & Assert - Assert.Throws(() => _factory.CreateProcessors(pipelineElements)); + Assert.Throws(() => _factory.CreateProcessors(pipeline)); } [Fact] public void CreateProcessors_ShouldReturnEmptyList_ForEmptyPipeline() { // Arrange - string pipelineJson = "[]"; - var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + var pipeline = new List(); // Act - var processors = _factory.CreateProcessors(pipelineElements); + var processors = _factory.CreateProcessors(pipeline); // Assert Assert.Empty(processors); } [Fact] - public void CreateProcessors_ShouldThrowArgumentException_WhenStrategyMissing() + public void CreateProcessors_ShouldThrowArgumentException_WhenNameMissing() { // Arrange - string pipelineJson = @"[ - { ""EnableZScoreNormalization"": true } - ]"; - var pipelineElements = JsonDocument.Parse(pipelineJson).RootElement.EnumerateArray().ToList(); + var pipeline = new List + { + new ProcessorDescription { Name = "", Configuration = new PreprocessingConfig() } + }; // Act & Assert - Assert.Throws(() => _factory.CreateProcessors(pipelineElements)); + Assert.Throws(() => _factory.CreateProcessors(pipeline)); } } diff --git a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs index 24cf397..5118463 100644 --- a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -89,7 +89,7 @@ public async Task ProcessAsync_ShouldCompleteSuccessfully_WhenAllConditionsAreMe .ReturnsAsync(measurements); _mockProcessor.Setup(x => x.ProcessAsync(It.IsAny>())) .ReturnsAsync(processedData); - _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) + _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) .Returns(new List { _mockProcessor.Object }); await _processor.ProcessAsync(config); @@ -113,7 +113,7 @@ public async Task ProcessAsync_ShouldReinitializeInfluxConnection_WhenEndpointCh .ReturnsAsync(measurements); _mockProcessor.Setup(x => x.ProcessAsync(It.IsAny>())) .ReturnsAsync(processedData); - _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) + _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) .Returns(new List { _mockProcessor.Object }); await _processor.ProcessAsync(config); // First call with endpoint1 @@ -141,7 +141,7 @@ public async Task ProcessAsync_ShouldNotReinitializeInfluxConnection_WhenEndpoin .ReturnsAsync(measurements); _mockProcessor.Setup(x => x.ProcessAsync(It.IsAny>())) .ReturnsAsync(processedData); - _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) + _mockProcessorFactory.Setup(x => x.CreateProcessors(It.IsAny>())) .Returns(new List { _mockProcessor.Object }); await _processor.ProcessAsync(config); @@ -169,7 +169,7 @@ public async Task ProcessAsync_ShouldThrowException_WhenRegistrationClientThrows WindowSizeSeconds = 300, CycleIntervalSeconds = 60, Enabled = true, - ProcessingPipeline = new List { new { Strategy = "mock" } } + ProcessingPipeline = new List { new() } }; private static CollectorInfoDto CreateCollectorInfoWithSensors(IEnumerable sensorNames, string endpoint = "http://localhost:8086") => new( diff --git a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs index d274df0..c4a3c27 100644 --- a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs @@ -1,6 +1,8 @@ using DataAggregator.Processor.Configuration; using DataAggregator.Processor.Services; using DataAggregator.Processor.Services.Prediction; +using DataAggregator.Processor.Services.Processing.Factory; +using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; @@ -146,9 +148,17 @@ public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() WindowSizeSeconds = 300, CycleIntervalSeconds = 60, Enabled = true, - ProcessingPipeline = new List + ProcessingPipeline = new List { - new { Strategy = "actuatorcurrent", EnableZScoreNormalization = true, NormalizationParameters = new Dictionary() }, + new ProcessorDescription + { + Name = "actuatorcurrent", + Configuration = new PreprocessingConfig + { + EnableZScoreNormalization = true, + NormalizationParameters = new Dictionary() + } + } }, }, new MachinePredictionConfig @@ -158,9 +168,17 @@ public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() WindowSizeSeconds = 600, CycleIntervalSeconds = 120, Enabled = true, - ProcessingPipeline = new List + ProcessingPipeline = new List { - new { Strategy = "actuatorcurrent", EnableZScoreNormalization = true, NormalizationParameters = new Dictionary() }, + new ProcessorDescription + { + Name = "actuatorcurrent", + Configuration = new PreprocessingConfig + { + EnableZScoreNormalization = true, + NormalizationParameters = new Dictionary() + } + } }, } ], From beb1809e94e13439474e76e7d9f413322beceab9 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Wed, 30 Jul 2025 20:58:34 +0200 Subject: [PATCH 55/70] fix: projects folder --- DataAggregator.sln | 2 -- 1 file changed, 2 deletions(-) diff --git a/DataAggregator.sln b/DataAggregator.sln index cddc1c4..371bf54 100644 --- a/DataAggregator.sln +++ b/DataAggregator.sln @@ -38,8 +38,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Collector.Op EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Processor", "src\DataAggregator.Processor\DataAggregator.Processor.csproj", "{039EC00D-5EDF-4C48-B449-29CFB6750232}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{330C7B17-DB90-458C-B630-99F9C1B5EA45}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processor", "processor", "{EB9A5576-3E00-4007-8C72-2235E0A1546D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Processor.Tests", "tests\DataAggregator.Processor.Tests\DataAggregator.Processor.Tests.csproj", "{EFE47FE6-F41E-CDD6-0991-472080AD88B0}" From c9ecf5877c48b70a0f23715e58d27deaad1671a2 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Wed, 30 Jul 2025 21:00:38 +0200 Subject: [PATCH 56/70] feat: add flush interval to collector configuration --- .../Abstraction/CollectorService.cs | 2 +- .../Abstraction/Configuration/CollectorConfiguration.cs | 5 +++++ src/DataAggregator.Collector/appsettings.json | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/DataAggregator.Collector.Shared/Abstraction/CollectorService.cs b/src/DataAggregator.Collector.Shared/Abstraction/CollectorService.cs index a9b5c79..efb68fe 100644 --- a/src/DataAggregator.Collector.Shared/Abstraction/CollectorService.cs +++ b/src/DataAggregator.Collector.Shared/Abstraction/CollectorService.cs @@ -27,7 +27,7 @@ public class CollectorService( private readonly SemaphoreSlim _processingLock = new(1, 1); private readonly ConcurrentQueue _dataQueue = new(); - private readonly TimeSpan _flushInterval = TimeSpan.FromSeconds(1); // Flush every 1 second + private readonly TimeSpan _flushInterval = TimeSpan.FromMilliseconds(configuration.FlushIntervalMilliseconds); // Flush every 1 second private bool _isRunning; private CancellationTokenSource? _cancellationTokenSource; diff --git a/src/DataAggregator.Collector.Shared/Abstraction/Configuration/CollectorConfiguration.cs b/src/DataAggregator.Collector.Shared/Abstraction/Configuration/CollectorConfiguration.cs index ce549a6..ff15dbe 100644 --- a/src/DataAggregator.Collector.Shared/Abstraction/Configuration/CollectorConfiguration.cs +++ b/src/DataAggregator.Collector.Shared/Abstraction/Configuration/CollectorConfiguration.cs @@ -24,4 +24,9 @@ public class CollectorConfiguration /// Gets or sets the list of sensors configured for this collector. /// public List Sensors { get; set; } = []; + + /// + /// Gets or sets the interval in milliseconds for flushing data to the time series database. + /// + public int FlushIntervalMilliseconds { get; set; } = 1000; } diff --git a/src/DataAggregator.Collector/appsettings.json b/src/DataAggregator.Collector/appsettings.json index e7cb0b7..83198c7 100644 --- a/src/DataAggregator.Collector/appsettings.json +++ b/src/DataAggregator.Collector/appsettings.json @@ -20,6 +20,7 @@ "DeviceName": "Micro5", "Location": "Test", "HealthCheckEndpoint": "http://localhost:5000/health", + "FlushIntervalMilliseconds": 1000, "SamplingRate": 100, "CapnProto": { "ServerAddress": "192.168.53.15", From 62131cbdf6126e68fec9a7bfbac3ed01e3793e39 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Wed, 30 Jul 2025 21:01:30 +0200 Subject: [PATCH 57/70] feat: enhance post-processor --- .../Configuration/MachinePredictionConfig.cs | 11 +- .../Services/DataStorage/IDataRepository.cs | 9 + .../DataStorage/InfluxV3Repository.cs | 211 +++++++++++------- .../Prediction/MachinePredictionProcessor.cs | 24 +- .../StateDeductionPostProcessor.cs | 106 ++++++--- src/DataAggregator.Processor/appsettings.json | 53 +++-- .../MachinePredictionProcessorTests.cs | 2 +- .../PredictionBackgroundServiceTests.cs | 33 +-- 8 files changed, 275 insertions(+), 174 deletions(-) diff --git a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs index d3ba85e..f681ef4 100644 --- a/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs +++ b/src/DataAggregator.Processor/Configuration/MachinePredictionConfig.cs @@ -23,14 +23,19 @@ public class MachinePredictionConfig public List InputSensors { get; set; } = []; /// - /// Gets or sets the window size in seconds for data collection. + /// Gets or sets a value indicating whether Window size in seconds if true, otherwise in elements number. /// - public int WindowSizeSeconds { get; set; } = 60; + public bool WindowSizeInSeconds { get; set; } = true; + + /// + /// Gets or sets the window size unit depends on WindowSizeInSeconds property. + /// + public int WindowSize { get; set; } = 60; /// /// Gets or sets the cycle interval in seconds for this machine. /// - public int CycleIntervalSeconds { get; set; } = 1; + public double CycleIntervalSeconds { get; set; } = 1; /// /// Gets or sets the processing pipeline for this machine. diff --git a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs index d17cd27..833a89c 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/IDataRepository.cs @@ -25,6 +25,15 @@ public interface IDataRepository /// A list of measurement data. public Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors); + /// + /// Queries the last measurements from InfluxDB for a specific table and window size, filtering by sensors. + /// + /// The table name. + /// The size of the window. + /// The list of sensor information with type data. + /// A list of measurements data of windowSize size max. + public Task> QueryLastMeasurements(string table, int windowSize, List sensors); + /// /// Writes a single measurement to InfluxDB. /// diff --git a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs index 52a6281..7269174 100644 --- a/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs +++ b/src/DataAggregator.Processor/Services/DataStorage/InfluxV3Repository.cs @@ -43,97 +43,52 @@ public void Initialize(string endpoint, string token) } /// - public async Task> QueryMeasurementsAsync(string table, DateTime startTime, DateTime endTime, List sensors) + public async Task> QueryMeasurementsAsync( + string table, + DateTime startTime, + DateTime endTime, + List sensors) { if (_client == null) { throw new InvalidOperationException("InfluxDB client is not initialized."); } - try - { - string sensorColumns = string.Join(", ", sensors.Select(s => $"\"{s.SensorName}\"")); - - string query = $""" - SELECT time, {sensorColumns} - FROM "{table}" - WHERE time >= '{startTime:yyyy-MM-ddTHH:mm:ssZ}' - AND time < '{endTime:yyyy-MM-ddTHH:mm:ssZ}' - """; + string sensorColumns = string.Join(", ", sensors.Select(s => $"\"{s.SensorName}\"")); - var measurements = new List(); - var sensorDict = sensors.ToDictionary(s => s.SensorName, s => s); + string query = $""" + SELECT time, {sensorColumns} + FROM "{table}" + WHERE time >= '{startTime:yyyy-MM-ddTHH:mm:ssZ}' + AND time < '{endTime:yyyy-MM-ddTHH:mm:ssZ}' + """; - await foreach (PointDataValues point in _client.QueryPoints(query)) - { - // point contains the original structure with all fields at once - // This matches how we write data: one row per timestamp with multiple sensors - System.Numerics.BigInteger? timestampBigInt = point.GetTimestamp(); - if (timestampBigInt == null) - { - Log.Warning("Skipping point with null timestamp"); - continue; - } - - // Convert BigInteger timestamp to DateTime - DateTime timestamp = DateTimeOffset.FromUnixTimeMilliseconds((long)(timestampBigInt.Value / 1_000_000)).DateTime; + return await ExecuteMeasurementQuery(query, sensors, table, startTime, endTime); + } - string[] fieldsNames = point.GetFieldNames(); - foreach (string fieldName in fieldsNames) - { - string sensorName = fieldName; - object? value = point.GetField(fieldName); + /// + public async Task> QueryLastMeasurements( + string table, + int windowSize, + List sensors) + { + if (_client == null) + { + throw new InvalidOperationException("InfluxDB client is not initialized."); + } - // Only include sensors that were requested - if (sensorDict.TryGetValue(sensorName, out SensorInfoDto? sensorInfo) && value != null) - { - IMeasurementData? measurement = sensorInfo.DataType switch - { - SensorDataType.Boolean when bool.TryParse(value.ToString(), out bool boolValue) => - new MeasurementData(timestamp, sensorName, boolValue), - - SensorDataType.Integer when int.TryParse(value.ToString(), out int intValue) => - new MeasurementData(timestamp, sensorName, intValue), - - SensorDataType.Double or SensorDataType.Float when double.TryParse(value.ToString(), out double doubleValue) => - new MeasurementData(timestamp, sensorName, doubleValue), - - SensorDataType.String => - new MeasurementData(timestamp, sensorName, value.ToString() ?? string.Empty), - - _ => null, - }; - - if (measurement != null) - { - measurements.Add(measurement); - } - else - { - Log.Debug( - "Skipping value for sensor {Sensor} with type {DataType}: {Value}", - sensorName, - sensorInfo.DataType, - value); - } - } - } - } + string sensorColumns = string.Join(", ", sensors.Select(s => $"\"{s.SensorName}\"")); - Log.Debug( - "Queried {Count} measurements for table {Table} from {StartTime} to {EndTime}", - measurements.Count, - table, - startTime, - endTime); + string query = $""" + SELECT time, {sensorColumns} + FROM "{table}" + ORDER BY time DESC + LIMIT {windowSize} + """; - return measurements; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to query measurements from InfluxDB for table {Table}", table); - throw; - } + // Note: sorting back to ascending to preserve chronological order + List reversed = await ExecuteMeasurementQuery(query, sensors, table); + return reversed.OrderBy(m => m.TimeStamp).ToList(); } /// @@ -172,6 +127,104 @@ public async Task WriteMeasurementAsync(string tag, IEnumerable + /// Execute a generic InfluxDB query and convert results to a flat list of IMeasurementData. + /// Assumes the data is stored in a "wide" format (one row per timestamp, multiple sensors as columns). + /// + private async Task> ExecuteMeasurementQuery( + string query, + List sensors, + string table, + DateTime? start = null, + DateTime? end = null) + { + if (_client is null) + { + return []; + } + + var measurements = new List(); + var sensorDict = sensors.ToDictionary(s => s.SensorName, s => s); + + try + { + await foreach (PointDataValues point in _client.QueryPoints(query)) + { + // point contains the original structure with all fields at once + // This matches how we write data: one row per timestamp with multiple sensors + System.Numerics.BigInteger? timestampBigInt = point.GetTimestamp(); + if (timestampBigInt == null) + { + Log.Warning("Skipping point with null timestamp"); + continue; + } + + // Convert BigInteger timestamp to DateTime + DateTime timestamp = DateTimeOffset + .FromUnixTimeMilliseconds((long)(timestampBigInt.Value / 1_000_000)) + .DateTime; + + string[] fieldNames = point.GetFieldNames(); + foreach (string fieldName in fieldNames) + { + string sensorName = fieldName; + + // Only include sensors that were requested + if (!sensorDict.TryGetValue(sensorName, out SensorInfoDto? sensorInfo)) + continue; + + object? value = point.GetField(sensorName); + if (value == null) + continue; + + // Deserialize the value according to the expected data type + IMeasurementData? measurement = sensorInfo.DataType switch + { + SensorDataType.Boolean when bool.TryParse(value.ToString(), out bool b) => + new MeasurementData(timestamp, sensorName, b), + + SensorDataType.Integer when int.TryParse(value.ToString(), out int i) => + new MeasurementData(timestamp, sensorName, i), + + SensorDataType.Double or SensorDataType.Float when double.TryParse(value.ToString(), out double d) => + new MeasurementData(timestamp, sensorName, d), + + SensorDataType.String => + new MeasurementData(timestamp, sensorName, value.ToString() ?? string.Empty), + + _ => null, + }; + + if (measurement != null) + { + measurements.Add(measurement); + } + else + { + Log.Debug( + "Skipping value for sensor {Sensor} with type {DataType}: {Value}", + sensorName, + sensorInfo.DataType, + value); + } + } + } + + Log.Debug( + "Fetched {Count} measurements from {Table} {Range}", + measurements.Count, + table, + start != null && end != null ? $"from {start} to {end}" : "(last N)"); + + return measurements; + } + catch (Exception ex) + { + Log.Error(ex, "Query failed for table {Table}", table); + throw; + } + } + /// /// Disposes the InfluxDB client. /// diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index 0aed8a9..dfc6f60 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -137,13 +137,23 @@ private bool BuildPipelineIfNeeded(MachinePredictionConfig config) private async Task> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) { - DateTime endTime = DateTime.UtcNow; - DateTime startTime = endTime.AddSeconds(-config.WindowSizeSeconds); - return await influxRepository.QueryMeasurementsAsync( - config.MachineName, - startTime, - endTime, - sensors); + if (config.WindowSizeInSeconds) + { + DateTime endTime = DateTime.UtcNow; + DateTime startTime = endTime.AddSeconds(-config.WindowSize); + return await influxRepository.QueryMeasurementsAsync( + config.MachineName, + startTime, + endTime, + sensors); + } + else + { + return await influxRepository.QueryLastMeasurements( + config.MachineName, + config.WindowSize, + sensors); + } } #endregion diff --git a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs index cc0090c..0b2d757 100644 --- a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs +++ b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs @@ -9,9 +9,7 @@ namespace DataAggregator.Processor.Services.Processing.PostProcessing.StateDeduc /// public class StateDeductionPostProcessor(StateDeductionPostProcessorConfig config) : IDataProcessor { - private readonly string _resultOutputName = "PredictedLabel.output_0"; - private string? _lastState = null; - private int _stableCount = 0; + #region Public methods /// public Task> ProcessAsync(IEnumerable input) @@ -21,46 +19,82 @@ public Task> ProcessAsync(IEnumerable= config.Threshold) + return ConfirmPendingState(); + + return Task.FromResult(Enumerable.Empty()); + } + #endregion + + #region Private Methods + private Task> InitializeFirstState(List inputList, string state) + { + _lastState = state; + _pendingState = state; + return Task.FromResult>(inputList); + } + + private Task> HandleSameAsLastState(List inputList, string state) + { + if (_bufferedBatches.Count > 0) { - // first run, initialize the last state - _lastState = currentPredictedState; - _stableCount = 1; - return Task.FromResult>(inputList); + var corrected = _bufferedBatches + .SelectMany(batch => OverrideStateInBatch(batch, _lastState!)) + .ToList(); + + _bufferedBatches.Clear(); + _pendingState = state; + _stableCount = 0; + + return Task.FromResult>(corrected); } - if (currentPredictedState == _lastState) + _pendingState = state; + _stableCount = 0; + return Task.FromResult>(inputList); + } + + private void HandlePotentialStateChange(string currentState, List inputList) + { + if (currentState == _pendingState) { - // stable state, increment the stable count _stableCount++; - return Task.FromResult>(inputList); + _bufferedBatches.Add(inputList); } else { - // state change detected + _pendingState = currentState; _stableCount = 1; - - // Accept the state change only if the stable count reaches the threshold - if (_stableCount >= config.Threshold) - { - _lastState = currentPredictedState; - return Task.FromResult>(inputList); - } - else - { - if (prediction != null) - { - var forced = new MeasurementDataWrapper(prediction, _lastState!); - var output = inputList.Select(x => x.SensorName == _resultOutputName ? forced : x).ToList(); - return Task.FromResult>(output); - } - else - { - return Task.FromResult>(inputList); - } - } + _bufferedBatches.Clear(); + _bufferedBatches.Add(inputList); } } + private Task> ConfirmPendingState() + { + _lastState = _pendingState; + + var confirmed = _bufferedBatches + .SelectMany(batch => OverrideStateInBatch(batch, _lastState!)) + .ToList(); + + _bufferedBatches.Clear(); + return Task.FromResult>(confirmed); + } + + private IEnumerable OverrideStateInBatch(List batch, string forcedValue) + => batch.Select( + x => x.SensorName == _resultOutputName + ? new MeasurementDataWrapper(x, forcedValue) + : x); + private class MeasurementDataWrapper(IMeasurementData original, string forcedValue) : IMeasurementData { public DateTime TimeStamp => original.TimeStamp; @@ -71,4 +105,14 @@ private class MeasurementDataWrapper(IMeasurementData original, string forcedVal public object GetRawValue() => forcedValue; } + #endregion + + #region private fields + private readonly string _resultOutputName = "PredictedLabel.output_0"; + + private readonly List> _bufferedBatches = []; + private string? _lastState = null; + private string? _pendingState = null; + private int _stableCount = 0; + #endregion } diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index e5f7af0..19ac701 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -32,36 +32,43 @@ "current-amp-c", "current-amp-s" ], - "WindowSizeSeconds": 3, - "CycleIntervalSeconds": 1, + "WindowSize": 50, + "WindowSizeInSeconds": false, + "CycleIntervalSeconds": 0.5, "ProcessingPipeline": [ { - "Strategy": "actuatorcurrent", - "EnableZScoreNormalization": true, - "NormalizationParameters": { - "GlobalActivityRatio": [0.004157, 0.017644], - "GlobalChangeDensity": [0.432407, 0.143321], - "InterAxisMeanCorrelation": [-0.001363, 0.072809], - "InterAxisMaxCorrelation": [0.439072, 0.267887], - "InterAxisCorrelationVariance": [0.225831, 0.129697], - "AxisSynchronization": [-38.172882, 221.177505], - "AxisLoadBalance": [-0.045936, 0.327285], - "TemporalStability": [0.974642, 0.046761], - "GlobalSkewness": [-0.129752, 0.362594], - "GlobalKurtosis": [-0.570948, 0.396184], - "GlobalTrendSlope": [-0.000001, 0.000179], - "CoefficientOfVariation": [-17.267527, 234.962006], - "NormalizedIqrMedian": [0.633865, 107.640205], - "NormalizedIqrMean": [-14.154306, 267.521179] + "Name": "actuatorcurrent", + "Configuration": { + "EnableZScoreNormalization": true, + "NormalizationParameters": { + "GlobalActivityRatio": [ 0.004067, 0.018027 ], + "GlobalChangeDensity": [ 0.433931, 0.146315 ], + "InterAxisMeanCorrelation": [ -0.006368, 0.068250 ], + "InterAxisMaxCorrelation": [ 0.432673, 0.243142 ], + "InterAxisCorrelationVariance": [ 0.224938, 0.112596 ], + "AxisSynchronization": [ -60.031723, 818.528442 ], + "AxisLoadBalance": [ -0.044534, 0.330180 ], + "TemporalStability": [ 0.964727, 0.038193 ], + "GlobalSkewness": [ -0.119681, 0.371627 ], + "GlobalKurtosis": [ -0.568834, 0.398853 ], + "GlobalTrendSlope": [ 0.000011, 0.000304 ], + "CoefficientOfVariation": [ -11.505261, 823.236694 ], + "NormalizedIqrMedian": [ -22.622700, 1259.548584 ], + "NormalizedIqrMean": [ -6.469841, 807.403015 ] + } } }, { - "Strategy": "onnxprediction", - "ModelPath": "resources/opencn_model.onnx" + "Name": "onnxprediction", + "Configuration": { + "ModelPath": "resources/opencn_model.onnx" + } }, { - "Strategy": "myCustomPostProcess", - "Threshold": 0.5 + "Name": "statedeductionpostprocessor", + "Configuration": { + "Threshold": 2 + } } ] } diff --git a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs index 5118463..6721c30 100644 --- a/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/Prediction/MachinePredictionProcessorTests.cs @@ -166,7 +166,7 @@ public async Task ProcessAsync_ShouldThrowException_WhenRegistrationClientThrows { MachineName = "test_machine", InputSensors = ["sensor1", "sensor2"], - WindowSizeSeconds = 300, + WindowSize = 300, CycleIntervalSeconds = 60, Enabled = true, ProcessingPipeline = new List { new() } diff --git a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs index c4a3c27..01d35d7 100644 --- a/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs +++ b/tests/DataAggregator.Processor.Tests/Services/PredictionBackgroundServiceTests.cs @@ -4,7 +4,6 @@ using DataAggregator.Processor.Services.Processing.Factory; using DataAggregator.Processor.Services.Processing.PreProcessing.ActuatorMergingCurrentPreprocessing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; using Moq; namespace DataAggregator.Processor.Tests.Services; @@ -14,7 +13,6 @@ namespace DataAggregator.Processor.Tests.Services; /// public class PredictionBackgroundServiceTests : IDisposable { - private readonly Mock> _mockConfiguration; private readonly Mock _mockPredictionProcessor; private readonly Mock _serviceProvider; private readonly Mock _mockScope; @@ -24,7 +22,6 @@ public class PredictionBackgroundServiceTests : IDisposable public PredictionBackgroundServiceTests() { - _mockConfiguration = new Mock>(); _mockPredictionProcessor = new Mock(); _cancellationTokenSource = new CancellationTokenSource(); @@ -49,7 +46,7 @@ public PredictionBackgroundServiceTests() .Returns(_mockScope.Object); _backgroundService = new PredictionBackgroundService( - _mockConfiguration.Object, + CreateValidConfiguration(), _serviceProvider.Object); } @@ -59,7 +56,6 @@ public PredictionBackgroundServiceTests() public async Task ExecuteAsync_ShouldStartSuccessfully_WhenValidConfigurationProvided() { var config = CreateValidConfiguration(); - _mockConfiguration.Setup(x => x.Value).Returns(config); Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); await Task.Delay(100); @@ -72,7 +68,6 @@ public async Task ExecuteAsync_ShouldStartSuccessfully_WhenValidConfigurationPro public async Task ExecuteAsync_ShouldScheduleEnabledMachines_WhenConfigurationContainsEnabledMachines() { var config = CreateValidConfiguration(); - _mockConfiguration.Setup(x => x.Value).Returns(config); Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); await Task.Delay(100); @@ -90,7 +85,6 @@ public async Task ExecuteAsync_ShouldHandleEmptyMachineList_WhenConfigurationCon { Machines = [], }; - _mockConfiguration.Setup(x => x.Value).Returns(config); Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); await Task.Delay(100); @@ -99,31 +93,10 @@ public async Task ExecuteAsync_ShouldHandleEmptyMachineList_WhenConfigurationCon Assert.True(true); } - [Fact] - public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenMachineNameIsEmpty() - { - var config = CreateValidConfiguration(); - config.Machines[0].MachineName = string.Empty; - _mockConfiguration.Setup(x => x.Value).Returns(config); - - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - - [Fact] - public async Task ExecuteAsync_ShouldThrowInvalidOperationException_WhenNoInputSensorsConfigured() - { - var config = CreateValidConfiguration(); - config.Machines[0].InputSensors.Clear(); - _mockConfiguration.Setup(x => x.Value).Returns(config); - - await Assert.ThrowsAsync(() => _backgroundService.StartAsync(_cancellationTokenSource.Token)); - } - [Fact] public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() { var config = CreateValidConfiguration(); - _mockConfiguration.Setup(x => x.Value).Returns(config); Task task = _backgroundService.StartAsync(_cancellationTokenSource.Token); await Task.Delay(100); @@ -145,7 +118,7 @@ public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() { MachineName = "test_machine_1", InputSensors = ["sensor1", "sensor2"], - WindowSizeSeconds = 300, + WindowSize = 300, CycleIntervalSeconds = 60, Enabled = true, ProcessingPipeline = new List @@ -165,7 +138,7 @@ public async Task ExecuteAsync_ShouldStopGracefully_WhenCancellationRequested() { MachineName = "test_machine_2", InputSensors = ["sensor3", "sensor4"], - WindowSizeSeconds = 600, + WindowSize = 600, CycleIntervalSeconds = 120, Enabled = true, ProcessingPipeline = new List From f0373eefe211c49bc615bec8881baae97cfdb1a5 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Wed, 30 Jul 2025 21:52:20 +0200 Subject: [PATCH 58/70] feat: implement batch processing --- .../Prediction/MachinePredictionProcessor.cs | 101 +++++++++++++++--- src/DataAggregator.Processor/appsettings.json | 2 +- 2 files changed, 85 insertions(+), 18 deletions(-) diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index dfc6f60..c0d31ed 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -30,8 +30,8 @@ public async Task ProcessAsync(MachinePredictionConfig config) InitializeRepositoryIfNeeded(collectorInfo); - List measurements = await FetchDataWindowAsync(config, requestedSensors); - if (measurements.Count == 0) + IEnumerable> measurements = await FetchDataWindowAsync(config, requestedSensors); + if (!measurements.Any()) { Log.Warning("No measurements found for machine {MachineName} in the specified time window", config.MachineName); return; @@ -96,27 +96,49 @@ private void InitializeRepositoryIfNeeded(CollectorInfoDto collectorInfo) } } - private async Task?> RunPipelineAsync(IEnumerable data, string machineName) + private async Task> RunPipelineAsync( + IEnumerable> dataBlocks, + string machineName) { - foreach (IDataProcessor processor in _pipelineProcessors!) + if (_pipelineProcessors is null || _pipelineProcessors.Count == 0) { - try + Log.Error("No processing pipeline configured for machine {MachineName}", machineName); + return Array.Empty(); + } + + var results = new List(); + + foreach (IReadOnlyList block in dataBlocks) + { + IEnumerable? current = block; + + foreach (IDataProcessor processor in _pipelineProcessors) { - data = await processor.ProcessAsync(data); - if (data is null || !data.Any()) + try + { + current = await processor.ProcessAsync(current); + if (current is null || !current.Any()) + { + Log.Warning("Processor {Processor} returned no data for machine {MachineName} (block skipped)", processor.GetType().Name, machineName); + current = null; + break; + } + } + catch (Exception ex) { - Log.Warning("Processor {Processor} returned no data for machine {MachineName}", processor.GetType().Name, machineName); - return null; + Log.Error(ex, "Error in processor {Processor} for machine {MachineName} (block skipped)", processor.GetType().Name, machineName); + current = null; + break; } } - catch (Exception ex) + + if (current is not null) { - Log.Error(ex, "Error in processor {Processor} for machine {MachineName}", processor.GetType().Name, machineName); - return null; + results.AddRange(current); } } - return data; + return results; } private bool BuildPipelineIfNeeded(MachinePredictionConfig config) @@ -135,30 +157,75 @@ private bool BuildPipelineIfNeeded(MachinePredictionConfig config) return true; } - private async Task> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) + private async Task>> FetchDataWindowAsync(MachinePredictionConfig config, List sensors) { + List> measurements = []; + + // If we want to fetch the last N measurements, we must take inaccount the number of sensors + int windowSize = config.WindowSize * config.InputSensors.Count; + + // If the window size is in secondes, simply query the measurements for the last N seconds. if (config.WindowSizeInSeconds) { DateTime endTime = DateTime.UtcNow; DateTime startTime = endTime.AddSeconds(-config.WindowSize); - return await influxRepository.QueryMeasurementsAsync( + + List datas = await influxRepository.QueryMeasurementsAsync( config.MachineName, startTime, endTime, sensors); + + measurements.Add(datas); } else { - return await influxRepository.QueryLastMeasurements( + // Otherwise, query all the measurements since the last query, and slice it in windows size list + if (_lastQueryTime == DateTime.MinValue) + { + // For the first run, we assume we want all the element for the CycleIntervalSeconds period. + _lastQueryTime = DateTime.UtcNow.AddSeconds(-config.CycleIntervalSeconds); + } + + List datas = await influxRepository.QueryMeasurementsAsync( config.MachineName, - config.WindowSize, + _lastQueryTime, + DateTime.UtcNow, sensors); + + if (datas.Count < config.WindowSize) + return measurements; + + datas = [.. datas.OrderBy(d => d.TimeStamp)]; + + int fullBlockCount = datas.Count / config.WindowSize; + + // Slice the data into windows of size config.WindowSize + for (int i = 0; i < fullBlockCount; i++) + { + List block = datas.GetRange(i * config.WindowSize * config.InputSensors.Count, config.WindowSize); + measurements.Add(block); + } + + // Set the last query time to the maximum timestamp of the fetched data complete block, + // so that the next query will only fetch new data. + int lastProcessedIndex = (fullBlockCount * config.WindowSize) - 1; + _lastQueryTime = datas[lastProcessedIndex].TimeStamp; } + + Log.Debug("Fetched {Count} data blocks for machine {MachineName}", measurements.Count, config.MachineName); + + // TODO CJS : REMOVE + Log.Debug("First element timestamp: {Timestamp}", measurements.First().First().TimeStamp); + Log.Debug("Last element timestamp: {Timestamp}", measurements.Last().Last().TimeStamp); + + return measurements; } #endregion #region Private fields private List? _pipelineProcessors; private string? _lastEndpoint; + private DateTime _lastQueryTime = DateTime.MinValue; #endregion } diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index 19ac701..b418184 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -34,7 +34,7 @@ ], "WindowSize": 50, "WindowSizeInSeconds": false, - "CycleIntervalSeconds": 0.5, + "CycleIntervalSeconds": 1, "ProcessingPipeline": [ { "Name": "actuatorcurrent", From 11b9197098aab0a6f48b637054bd7ec29a850df5 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 31 Jul 2025 10:01:30 +0200 Subject: [PATCH 59/70] feat: set result timestamp to first sample timestamp --- .../ActuatorCurrentFeatureExtractor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs index 10ceb2b..25fcc24 100644 --- a/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs +++ b/src/DataAggregator.Processor/Services/Processing/PreProcessing/ActuatorMergingCurrentPreprocessing/ActuatorCurrentFeatureExtractor.cs @@ -31,8 +31,8 @@ public Task> ProcessAsync(IEnumerable x.TimeStamp); else meanTime = DateTime.UtcNow; From d8ae2f9250c73c470ae50fae927e78796410fe57 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 31 Jul 2025 10:04:31 +0200 Subject: [PATCH 60/70] clean: clean window slicing --- .../Prediction/MachinePredictionProcessor.cs | 16 ++++++++-------- .../StateDeductionPostProcessor.cs | 13 +------------ 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index c0d31ed..8a3981f 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -161,9 +161,6 @@ private async Task>> FetchDataWindow { List> measurements = []; - // If we want to fetch the last N measurements, we must take inaccount the number of sensors - int windowSize = config.WindowSize * config.InputSensors.Count; - // If the window size is in secondes, simply query the measurements for the last N seconds. if (config.WindowSizeInSeconds) { @@ -180,6 +177,9 @@ private async Task>> FetchDataWindow } else { + // If we want to fetch the last N measurements, we must take inaccount the number of sensors + int windowSize = config.WindowSize * config.InputSensors.Count; + // Otherwise, query all the measurements since the last query, and slice it in windows size list if (_lastQueryTime == DateTime.MinValue) { @@ -193,23 +193,23 @@ private async Task>> FetchDataWindow DateTime.UtcNow, sensors); - if (datas.Count < config.WindowSize) + if (datas.Count < windowSize) return measurements; datas = [.. datas.OrderBy(d => d.TimeStamp)]; - int fullBlockCount = datas.Count / config.WindowSize; + int fullBlockCount = datas.Count / windowSize; - // Slice the data into windows of size config.WindowSize + // Slice the data into windows of size windowSize. for (int i = 0; i < fullBlockCount; i++) { - List block = datas.GetRange(i * config.WindowSize * config.InputSensors.Count, config.WindowSize); + List block = datas.GetRange(i * windowSize, windowSize); measurements.Add(block); } // Set the last query time to the maximum timestamp of the fetched data complete block, // so that the next query will only fetch new data. - int lastProcessedIndex = (fullBlockCount * config.WindowSize) - 1; + int lastProcessedIndex = (fullBlockCount * windowSize) - 1; _lastQueryTime = datas[lastProcessedIndex].TimeStamp; } diff --git a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs index 0b2d757..2302c37 100644 --- a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs +++ b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs @@ -92,19 +92,8 @@ private Task> ConfirmPendingState() private IEnumerable OverrideStateInBatch(List batch, string forcedValue) => batch.Select( x => x.SensorName == _resultOutputName - ? new MeasurementDataWrapper(x, forcedValue) + ? new MeasurementData(x.TimeStamp, x.SensorName, forcedValue) : x); - - private class MeasurementDataWrapper(IMeasurementData original, string forcedValue) : IMeasurementData - { - public DateTime TimeStamp => original.TimeStamp; - - public string SensorName => original.SensorName; - - public Type ValueType => typeof(string); - - public object GetRawValue() => forcedValue; - } #endregion #region private fields From 4c24f5894d4af1ea1b9ea9a443c05634ac48e7b4 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 31 Jul 2025 10:04:56 +0200 Subject: [PATCH 61/70] feat: adapt settings for model using 30 sample window width --- src/DataAggregator.Processor/appsettings.json | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index b418184..f5429fc 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -32,7 +32,7 @@ "current-amp-c", "current-amp-s" ], - "WindowSize": 50, + "WindowSize": 30, "WindowSizeInSeconds": false, "CycleIntervalSeconds": 1, "ProcessingPipeline": [ @@ -41,20 +41,20 @@ "Configuration": { "EnableZScoreNormalization": true, "NormalizationParameters": { - "GlobalActivityRatio": [ 0.004067, 0.018027 ], - "GlobalChangeDensity": [ 0.433931, 0.146315 ], - "InterAxisMeanCorrelation": [ -0.006368, 0.068250 ], - "InterAxisMaxCorrelation": [ 0.432673, 0.243142 ], - "InterAxisCorrelationVariance": [ 0.224938, 0.112596 ], - "AxisSynchronization": [ -60.031723, 818.528442 ], - "AxisLoadBalance": [ -0.044534, 0.330180 ], - "TemporalStability": [ 0.964727, 0.038193 ], - "GlobalSkewness": [ -0.119681, 0.371627 ], - "GlobalKurtosis": [ -0.568834, 0.398853 ], - "GlobalTrendSlope": [ 0.000011, 0.000304 ], - "CoefficientOfVariation": [ -11.505261, 823.236694 ], - "NormalizedIqrMedian": [ -22.622700, 1259.548584 ], - "NormalizedIqrMean": [ -6.469841, 807.403015 ] + "GlobalActivityRatio": [ 0.003958, 0.018749 ], + "GlobalChangeDensity": [ 0.432119, 0.146720 ], + "InterAxisMeanCorrelation": [ -0.006328, 0.069353 ], + "InterAxisMaxCorrelation": [ 0.455234, 0.230869 ], + "InterAxisCorrelationVariance": [ 0.238157, 0.104391 ], + "AxisSynchronization": [ -38.056988, 293.112457 ], + "AxisLoadBalance": [ -0.045349, 0.330992 ], + "TemporalStability": [ 0.952670, 0.037949 ], + "GlobalSkewness": [ -0.122091, 0.380314 ], + "GlobalKurtosis": [ -0.562939, 0.403591 ], + "GlobalTrendSlope": [ 0.000049, 0.000477 ], + "CoefficientOfVariation": [ -14.905608, 295.991791 ], + "NormalizedIqrMedian": [ -26.388798, 1187.886230 ], + "NormalizedIqrMean": [ -10.343025, 301.199188 ] } } }, From 59a45573c5aa168a938057f3fbccbb03f5e92de1 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Thu, 31 Jul 2025 14:38:11 +0200 Subject: [PATCH 62/70] fix: state deduction --- .../StateDeductionPostProcessor.cs | 83 +++++++++---------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs index 2302c37..2a2d67e 100644 --- a/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs +++ b/src/DataAggregator.Processor/Services/Processing/PostProcessing/StateDeductionPostProcess/StateDeductionPostProcessor.cs @@ -22,61 +22,42 @@ public Task> ProcessAsync(IEnumerable>(inputList); - HandlePotentialStateChange(currentPredictedState, inputList); - - if (_stableCount >= config.Threshold) - return ConfirmPendingState(); - - return Task.FromResult(Enumerable.Empty()); - } - #endregion - - #region Private Methods - private Task> InitializeFirstState(List inputList, string state) - { - _lastState = state; - _pendingState = state; - return Task.FromResult>(inputList); - } - - private Task> HandleSameAsLastState(List inputList, string state) - { - if (_bufferedBatches.Count > 0) - { - var corrected = _bufferedBatches - .SelectMany(batch => OverrideStateInBatch(batch, _lastState!)) - .ToList(); - - _bufferedBatches.Clear(); - _pendingState = state; - _stableCount = 0; - - return Task.FromResult>(corrected); - } - - _pendingState = state; - _stableCount = 0; - return Task.FromResult>(inputList); - } - - private void HandlePotentialStateChange(string currentState, List inputList) - { - if (currentState == _pendingState) + if (currentPredictedState == _pendingState) { _stableCount++; _bufferedBatches.Add(inputList); + + if (_stableCount == config.Threshold) + return ConfirmPendingState(); + + return Task.FromResult(Enumerable.Empty()); } else { - _pendingState = currentState; + IEnumerable flushed = FlushInvalidTransitionAsLastState(); + + _pendingState = currentPredictedState; _stableCount = 1; _bufferedBatches.Clear(); _bufferedBatches.Add(inputList); + + return Task.FromResult(flushed); } } + #endregion + + #region Private methods + + private Task> InitializeFirstState(List inputList, string state) + { + _lastState = state; + _pendingState = state; + return Task.FromResult>(inputList); + } + private Task> ConfirmPendingState() { _lastState = _pendingState; @@ -89,19 +70,35 @@ private Task> ConfirmPendingState() return Task.FromResult>(confirmed); } + private IEnumerable FlushInvalidTransitionAsLastState() + { + if (_bufferedBatches.Count == 0 || _lastState == null) + return Enumerable.Empty(); + + var corrected = _bufferedBatches + .SelectMany(batch => OverrideStateInBatch(batch, _lastState)) + .ToList(); + + _bufferedBatches.Clear(); + return corrected; + } + private IEnumerable OverrideStateInBatch(List batch, string forcedValue) => batch.Select( x => x.SensorName == _resultOutputName ? new MeasurementData(x.TimeStamp, x.SensorName, forcedValue) : x); + #endregion - #region private fields + #region Private fields + private readonly string _resultOutputName = "PredictedLabel.output_0"; private readonly List> _bufferedBatches = []; private string? _lastState = null; private string? _pendingState = null; private int _stableCount = 0; + #endregion } From 60553cc812253b0bf245bb2b65f5113e73fd159b Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 5 Aug 2025 09:56:59 +0200 Subject: [PATCH 63/70] feat: remove unused logs and update configuration --- .../Services/Prediction/MachinePredictionProcessor.cs | 4 ---- src/DataAggregator.Processor/appsettings.json | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index 8a3981f..79e2bf4 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -215,10 +215,6 @@ private async Task>> FetchDataWindow Log.Debug("Fetched {Count} data blocks for machine {MachineName}", measurements.Count, config.MachineName); - // TODO CJS : REMOVE - Log.Debug("First element timestamp: {Timestamp}", measurements.First().First().TimeStamp); - Log.Debug("Last element timestamp: {Timestamp}", measurements.Last().Last().TimeStamp); - return measurements; } #endregion diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index f5429fc..d08c2b4 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -67,7 +67,7 @@ { "Name": "statedeductionpostprocessor", "Configuration": { - "Threshold": 2 + "Threshold": 3 } } ] From dc46cbfefeff1d02d79d9fd8c534a702844a0445 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 5 Aug 2025 10:00:47 +0200 Subject: [PATCH 64/70] feat: remove integration tests and update MachinePrediction behaviour when no results --- DataAggregator.sln | 15 ---------- .../Prediction/MachinePredictionProcessor.cs | 2 +- .../DataAggregator.Integration.Tests.csproj | 30 ------------------- .../UnitTest1.cs | 15 ---------- 4 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 tests/DataAggregator.Integration.Tests/DataAggregator.Integration.Tests.csproj delete mode 100644 tests/DataAggregator.Integration.Tests/UnitTest1.cs diff --git a/DataAggregator.sln b/DataAggregator.sln index 371bf54..7c3b036 100644 --- a/DataAggregator.sln +++ b/DataAggregator.sln @@ -17,8 +17,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Collector.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Registration.Tests", "tests\DataAggregator.Registration.Tests\DataAggregator.Registration.Tests.csproj", "{FB0B4F80-5801-407F-8425-54069C93DB60}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAggregator.Integration.Tests", "tests\DataAggregator.Integration.Tests\DataAggregator.Integration.Tests.csproj", "{12FEEB75-A03A-4389-980B-6B735C550CAC}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -112,18 +110,6 @@ Global {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|x64.Build.0 = Release|x64 {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|x86.ActiveCfg = Release|x86 {FB0B4F80-5801-407F-8425-54069C93DB60}.Release|x86.Build.0 = Release|x86 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x64.ActiveCfg = Debug|x64 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x64.Build.0 = Debug|x64 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x86.ActiveCfg = Debug|x86 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Debug|x86.Build.0 = Debug|x86 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|Any CPU.Build.0 = Release|Any CPU - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x64.ActiveCfg = Release|x64 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x64.Build.0 = Release|x64 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x86.ActiveCfg = Release|x86 - {12FEEB75-A03A-4389-980B-6B735C550CAC}.Release|x86.Build.0 = Release|x86 {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A5708E5-B5D1-4DA4-A2E4-323D3A21B182}.Debug|x64.ActiveCfg = Debug|x64 @@ -182,7 +168,6 @@ Global {E593EA7B-F713-4444-AE40-FDCEF462660A} = {AA521B32-6E75-46BD-ABDF-0450227A94C4} {63D70F2C-9C7D-4397-8150-37F343949530} = {247EF7A2-1DFD-4B51-AC7D-0FD13827CAC2} {FB0B4F80-5801-407F-8425-54069C93DB60} = {247EF7A2-1DFD-4B51-AC7D-0FD13827CAC2} - {12FEEB75-A03A-4389-980B-6B735C550CAC} = {247EF7A2-1DFD-4B51-AC7D-0FD13827CAC2} {95F8787D-6FE2-4173-8450-6762EA6FAE1F} = {E1AD9667-4C40-4CAF-8096-5FA749EBB2B1} {AA521B32-6E75-46BD-ABDF-0450227A94C4} = {E1AD9667-4C40-4CAF-8096-5FA749EBB2B1} {F5250AC4-9CCC-432B-8725-DAC6AE01CCF0} = {95F8787D-6FE2-4173-8450-6762EA6FAE1F} diff --git a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs index 79e2bf4..b8ff3d7 100644 --- a/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs +++ b/src/DataAggregator.Processor/Services/Prediction/MachinePredictionProcessor.cs @@ -44,7 +44,7 @@ public async Task ProcessAsync(MachinePredictionConfig config) } IEnumerable? processedData = await RunPipelineAsync(measurements, config.MachineName); - if (processedData == null) return; + if (processedData == null || !processedData.Any()) return; await influxRepository.WriteMeasurementAsync(config.MachineName, processedData); Log.Information("Prediction pipeline completed for machine {MachineName}", config.MachineName); diff --git a/tests/DataAggregator.Integration.Tests/DataAggregator.Integration.Tests.csproj b/tests/DataAggregator.Integration.Tests/DataAggregator.Integration.Tests.csproj deleted file mode 100644 index 1c5112b..0000000 --- a/tests/DataAggregator.Integration.Tests/DataAggregator.Integration.Tests.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net9.0 - enable - enable - false - true - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - diff --git a/tests/DataAggregator.Integration.Tests/UnitTest1.cs b/tests/DataAggregator.Integration.Tests/UnitTest1.cs deleted file mode 100644 index 562d33d..0000000 --- a/tests/DataAggregator.Integration.Tests/UnitTest1.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace DataAggregator.Integration.Tests; - -/// -/// -Tests for the DataAggregator.Integration project. -/// -public class UnitTest1 -{ - /// - /// Tests the integration functionality of the DataAggregator project. - /// - [Fact] - public void Test1() - { - } -} From 81a875b139a0b8118d442984d88c0b115e97b7a5 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 5 Aug 2025 13:59:55 +0200 Subject: [PATCH 65/70] feat: delete unused files and fix program.cs to use http in prod --- .../DataAggregator.Collector.http | 16 --------- src/DataAggregator.Collector/Program.cs | 33 +++++++++++-------- src/DataAggregator.Collector/appsettings.json | 3 +- src/DataAggregator.Processor/Program.cs | 18 ++++++++-- .../DataAggregator.Registration.http | 6 ---- src/DataAggregator.Registration/Program.cs | 20 ++++++++--- 6 files changed, 53 insertions(+), 43 deletions(-) delete mode 100644 src/DataAggregator.Collector/DataAggregator.Collector.http delete mode 100644 src/DataAggregator.Registration/DataAggregator.Registration.http diff --git a/src/DataAggregator.Collector/DataAggregator.Collector.http b/src/DataAggregator.Collector/DataAggregator.Collector.http deleted file mode 100644 index ea6bd0b..0000000 --- a/src/DataAggregator.Collector/DataAggregator.Collector.http +++ /dev/null @@ -1,16 +0,0 @@ -@DataAggregator.Collector_HostAddress = http://localhost:5091 - -GET {{DataAggregator.Collector_HostAddress}}/weatherforecast/ -Accept: application/json - -### - -@apiBaseUrl = http://localhost:5000 - -### Health Check -GET {{apiBaseUrl}}/api/healthcheck -Accept: application/json - -### Direct Health Endpoint -GET {{apiBaseUrl}}/health -Accept: application/json diff --git a/src/DataAggregator.Collector/Program.cs b/src/DataAggregator.Collector/Program.cs index b569324..ce647c5 100644 --- a/src/DataAggregator.Collector/Program.cs +++ b/src/DataAggregator.Collector/Program.cs @@ -5,6 +5,7 @@ using DataAggregator.Collector.Shared.DataStorage.Influx; using DataAggregator.Collector.Shared.LocalStorage; using DataAggregator.Collector.Shared.Registration; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Options; using Serilog; @@ -24,6 +25,16 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new() { Title = "DataAggregator Collector API", Version = "v1" })); +if (!builder.Environment.IsDevelopment()) +{ + builder.Services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); +} + // Get collector type from configuration string? collectorType = builder.Configuration["CollectorType"]; @@ -37,11 +48,12 @@ SetupConfiguration(builder); // Configure HTTP clients -builder.Services.AddHttpClient(); +string registrationBaseUrl = builder.Configuration["Collector:RegistrationService:BaseUrl"] ?? "http://localhost:5000"; +string registrationEndpoint = builder.Configuration["Collector:RegistrationService:Endpoint"] ?? "api/DeviceRegistration/register"; + builder.Services.AddHttpClient("RegistrationClient", client => { - string registrationEndpoint = builder.Configuration["RegistrationService:Endpoint"] ?? "http://localhost:5001"; - client.BaseAddress = new Uri(registrationEndpoint); + client.BaseAddress = new Uri(registrationBaseUrl); client.DefaultRequestHeaders.Add("Accept", "application/json"); }); @@ -60,14 +72,6 @@ IHttpClientFactory httpClientFactory = sp.GetRequiredService(); HttpClient httpClient = httpClientFactory.CreateClient("RegistrationClient"); - string? registrationEndpoint = builder.Configuration["Collector:RegistrationService:Endpoint"]; - - if (string.IsNullOrEmpty(registrationEndpoint)) - { - Log.Warning("Registration service endpoint not configured, using default: http://localhost:5001/api/DeviceRegistration/register"); - registrationEndpoint = "http://localhost:5001/api/DeviceRegistration/register"; - } - return new RegistrationService(httpClient, registrationEndpoint); }); @@ -117,8 +121,11 @@ }); } -app.UseHttpsRedirection(); -app.UseAuthorization(); +if (!app.Environment.IsDevelopment()) +{ + app.UseForwardedHeaders(); +} + app.MapControllers(); app.MapHealthChecks("/health"); diff --git a/src/DataAggregator.Collector/appsettings.json b/src/DataAggregator.Collector/appsettings.json index 83198c7..700cb6e 100644 --- a/src/DataAggregator.Collector/appsettings.json +++ b/src/DataAggregator.Collector/appsettings.json @@ -27,7 +27,8 @@ "Port": 7002 }, "RegistrationService": { - "Endpoint": "http://localhost:5137/api/DeviceRegistration/register" + "BaseUrl": "http://localhost:5137", + "Endpoint": "api/DeviceRegistration/register" }, "BufferSettings": { "MaxBufferSize": 10000 diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index 6827537..9ceee32 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -5,6 +5,7 @@ using DataAggregator.Processor.Services.Prediction; using DataAggregator.Processor.Services.Processing.Factory; using DataAggregator.Processor.Services.Registration; +using Microsoft.AspNetCore.HttpOverrides; using Serilog; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); @@ -23,6 +24,16 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new() { Title = "DataAggregator Processor API", Version = "v1" })); +if (!builder.Environment.IsDevelopment()) +{ + builder.Services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); +} + // Register health checks builder.Services.AddHealthChecks(); @@ -68,8 +79,11 @@ }); } -app.UseHttpsRedirection(); -app.UseAuthorization(); +if (!app.Environment.IsDevelopment()) +{ + app.UseForwardedHeaders(); +} + app.MapControllers(); app.MapHealthChecks("/health"); diff --git a/src/DataAggregator.Registration/DataAggregator.Registration.http b/src/DataAggregator.Registration/DataAggregator.Registration.http deleted file mode 100644 index 4e41139..0000000 --- a/src/DataAggregator.Registration/DataAggregator.Registration.http +++ /dev/null @@ -1,6 +0,0 @@ -@DataAggregator.Registration_HostAddress = http://localhost:5137 - -GET {{DataAggregator.Registration_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/src/DataAggregator.Registration/Program.cs b/src/DataAggregator.Registration/Program.cs index 28687d7..36efa74 100644 --- a/src/DataAggregator.Registration/Program.cs +++ b/src/DataAggregator.Registration/Program.cs @@ -4,6 +4,7 @@ using DataAggregator.Registration.DeviceManagement.Services; using DataAggregator.Registration.InfluxService.Configuration; using DataAggregator.Registration.InfluxService.Services; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.EntityFrameworkCore; using Serilog; @@ -23,6 +24,17 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new() { Title = "DataAggregator API", Version = "v1" })); +// Configure forwarded headers for reverse proxy scenarios +if (!builder.Environment.IsDevelopment()) +{ + builder.Services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); +} + // Configure Entity Framework Core with PostgreSQL string? pgHost = builder.Configuration["PGHOST"]; string? pgPort = builder.Configuration["PGPORT"]; @@ -86,14 +98,12 @@ c.RoutePrefix = string.Empty; }); } -else + +if (!app.Environment.IsDevelopment()) { - app.UseHsts(); + app.UseForwardedHeaders(); // Use forwarded headers in production } -app.UseHttpsRedirection(); -app.UseAuthorization(); - app.MapControllers(); app.MapHealthChecks("/health"); From 40e3066e70a50ed39d8ba481df5c9700b155cd22 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 5 Aug 2025 14:11:09 +0200 Subject: [PATCH 66/70] feat: update serilog management --- src/DataAggregator.Collector/Program.cs | 2 -- src/DataAggregator.Collector/appsettings.json | 24 ++++++++++++------- src/DataAggregator.Processor/Program.cs | 2 -- src/DataAggregator.Processor/appsettings.json | 23 +++++++++++------- src/DataAggregator.Registration/Program.cs | 4 +--- .../appsettings.json | 24 +++++++++++++++---- 6 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/DataAggregator.Collector/Program.cs b/src/DataAggregator.Collector/Program.cs index ce647c5..5a8aaa1 100644 --- a/src/DataAggregator.Collector/Program.cs +++ b/src/DataAggregator.Collector/Program.cs @@ -14,8 +14,6 @@ // Configure Serilog from appsettings.json Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) - .WriteTo.Console() - .Enrich.FromLogContext() .CreateLogger(); builder.Host.UseSerilog(); diff --git a/src/DataAggregator.Collector/appsettings.json b/src/DataAggregator.Collector/appsettings.json index 700cb6e..3dbac9b 100644 --- a/src/DataAggregator.Collector/appsettings.json +++ b/src/DataAggregator.Collector/appsettings.json @@ -1,20 +1,28 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, + "AllowedHosts": "*", + "Serilog": { "MinimumLevel": { "Default": "Information", "Override": { - "Microsoft": "Warning", + "Microsoft.AspNetCore": "Warning", "System": "Warning" } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {ServiceName}: {Message:lj}{NewLine}{Exception}" + } + } + ], + "Enrich": [ "FromLogContext" ], + "Properties": { + "ServiceName": "Collector" } }, - "AllowedHosts": "*", + "CollectorType": "OpenCN", "Collector": { "DeviceName": "Micro5", diff --git a/src/DataAggregator.Processor/Program.cs b/src/DataAggregator.Processor/Program.cs index 9ceee32..b8f3648 100644 --- a/src/DataAggregator.Processor/Program.cs +++ b/src/DataAggregator.Processor/Program.cs @@ -13,8 +13,6 @@ // Configure Serilog from appsettings.json Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) - .WriteTo.Console() - .Enrich.FromLogContext() .CreateLogger(); builder.Host.UseSerilog(); diff --git a/src/DataAggregator.Processor/appsettings.json b/src/DataAggregator.Processor/appsettings.json index d08c2b4..797729f 100644 --- a/src/DataAggregator.Processor/appsettings.json +++ b/src/DataAggregator.Processor/appsettings.json @@ -1,23 +1,28 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, + "AllowedHosts": "*", "Serilog": { "MinimumLevel": { "Default": "Information", "Override": { - "Microsoft": "Warning", + "Microsoft.AspNetCore": "Warning", "System": "Warning" } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {ServiceName}: {Message:lj}{NewLine}{Exception}" + } + } + ], + "Enrich": [ "FromLogContext" ], + "Properties": { + "ServiceName": "Processor" } }, - "AllowedHosts": "*", - "PredictionService": { "RegistrationServiceUrl": "http://localhost:5137", "Machines": [ diff --git a/src/DataAggregator.Registration/Program.cs b/src/DataAggregator.Registration/Program.cs index 36efa74..b614953 100644 --- a/src/DataAggregator.Registration/Program.cs +++ b/src/DataAggregator.Registration/Program.cs @@ -12,9 +12,7 @@ // Configure Serilog from appsettings.json Log.Logger = new LoggerConfiguration() - .ReadFrom.Configuration(builder.Configuration) // Read configuration from appsettings.json - .WriteTo.Console() // Write logs to the console - .Enrich.FromLogContext() // Add context to logs + .ReadFrom.Configuration(builder.Configuration) .CreateLogger(); builder.Host.UseSerilog(); // Use Serilog as the logging provider diff --git a/src/DataAggregator.Registration/appsettings.json b/src/DataAggregator.Registration/appsettings.json index fd0b67b..5be0699 100644 --- a/src/DataAggregator.Registration/appsettings.json +++ b/src/DataAggregator.Registration/appsettings.json @@ -1,11 +1,27 @@ { - "Logging": { - "LogLevel": { + "AllowedHosts": "*", + + "Serilog": { + "MinimumLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Override": { + "Microsoft.AspNetCore": "Warning", + "System": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console", + "Args": { + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {ServiceName}: {Message:lj}{NewLine}{Exception}" + } + } + ], + "Enrich": [ "FromLogContext" ], + "Properties": { + "ServiceName": "Registration" } }, - "AllowedHosts": "*", "Influx": { "Endpoints": [ From afd55fa4d361dbc5a386f29461a06b9dcef06c87 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 5 Aug 2025 14:15:25 +0200 Subject: [PATCH 67/70] feat: use connexion string from appsettings for registration --- src/DataAggregator.Registration/Program.cs | 15 ++------------- src/DataAggregator.Registration/appsettings.json | 4 ++++ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/DataAggregator.Registration/Program.cs b/src/DataAggregator.Registration/Program.cs index b614953..ea9f352 100644 --- a/src/DataAggregator.Registration/Program.cs +++ b/src/DataAggregator.Registration/Program.cs @@ -34,19 +34,8 @@ } // Configure Entity Framework Core with PostgreSQL -string? pgHost = builder.Configuration["PGHOST"]; -string? pgPort = builder.Configuration["PGPORT"]; -string? pgDb = builder.Configuration["PGDATABASE"]; -string? pgUser = builder.Configuration["PGUSER"]; -string? pgPassword = builder.Configuration["PGPASSWORD"]; - -if (string.IsNullOrEmpty(pgHost) || string.IsNullOrEmpty(pgDb) || string.IsNullOrEmpty(pgUser) || string.IsNullOrEmpty(pgPassword)) -{ - Log.Fatal("PostgreSQL environment variables are not properly configured."); - throw new InvalidOperationException("PostgreSQL environment variables are not properly configured."); -} - -string connectionString = $"Host={pgHost};Port={pgPort};Database={pgDb};Username={pgUser};Password={pgPassword}"; +string connectionString = builder.Configuration.GetConnectionString("DefaultConnection") + ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found in configuration."); builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); diff --git a/src/DataAggregator.Registration/appsettings.json b/src/DataAggregator.Registration/appsettings.json index 5be0699..e1d815c 100644 --- a/src/DataAggregator.Registration/appsettings.json +++ b/src/DataAggregator.Registration/appsettings.json @@ -23,6 +23,10 @@ } }, + "ConnectionStrings": { + "DefaultConnection": "Server=localhost;Port=5432;Database=dataaggregator;User Id=admin;Password=password123;" + }, + "Influx": { "Endpoints": [ { From 9944a1ef5de776f74ff97f012b6f3cf93efdf42d Mon Sep 17 00:00:00 2001 From: CoJaques Date: Tue, 5 Aug 2025 18:46:10 +0200 Subject: [PATCH 68/70] fix: Dockerfile --- docker/Dockerfile.Collector | 20 +++++++++++++++ docker/Dockerfile.Processor | 16 ++++++++++++ docker/Dockerfile.Registration | 16 ++++++++++++ src/DataAggregator.Collector/Dockerfile | 20 --------------- src/DataAggregator.Processor/Dockerfile | 30 ---------------------- src/DataAggregator.Registration/Dockerfile | 20 --------------- 6 files changed, 52 insertions(+), 70 deletions(-) create mode 100644 docker/Dockerfile.Collector create mode 100644 docker/Dockerfile.Processor create mode 100644 docker/Dockerfile.Registration delete mode 100644 src/DataAggregator.Collector/Dockerfile delete mode 100644 src/DataAggregator.Processor/Dockerfile delete mode 100644 src/DataAggregator.Registration/Dockerfile diff --git a/docker/Dockerfile.Collector b/docker/Dockerfile.Collector new file mode 100644 index 0000000..11b35c0 --- /dev/null +++ b/docker/Dockerfile.Collector @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +COPY NuGet.config /NuGet.config +COPY lib /lib + +COPY src/DataAggregator.Collector/*.csproj ./DataAggregator.Collector/ +COPY src/DataAggregator.Shared/*.csproj ./DataAggregator.Shared/ +COPY src/DataAggregator.Collector.OpenCNCapnProtoConnector/*.csproj ./DataAggregator.Collector.OpenCNCapnProtoConnector/ + +RUN dotnet restore ./DataAggregator.Collector/DataAggregator.Collector.App.csproj --configfile /NuGet.config + +COPY src/ ./ +WORKDIR /src/DataAggregator.Collector +RUN dotnet publish -c Release -o /app/publish --configfile /NuGet.config + +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +WORKDIR /app +COPY --from=build /app/publish . +ENTRYPOINT ["dotnet", "DataAggregator.Collector.App.dll"] diff --git a/docker/Dockerfile.Processor b/docker/Dockerfile.Processor new file mode 100644 index 0000000..ffbcf02 --- /dev/null +++ b/docker/Dockerfile.Processor @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +COPY src/DataAggregator.Processor/*.csproj ./DataAggregator.Processor/ +COPY src/DataAggregator.Shared/*.csproj ./DataAggregator.Shared/ + +RUN dotnet restore ./DataAggregator.Processor/DataAggregator.Processor.csproj + +COPY src/ ./ +WORKDIR /src/DataAggregator.Processor +RUN dotnet publish -c Release -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +WORKDIR /app +COPY --from=build /app/publish . +ENTRYPOINT ["dotnet", "DataAggregator.Processor.dll"] diff --git a/docker/Dockerfile.Registration b/docker/Dockerfile.Registration new file mode 100644 index 0000000..8201074 --- /dev/null +++ b/docker/Dockerfile.Registration @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +COPY src/DataAggregator.Registration/*.csproj ./DataAggregator.Registration/ +COPY src/DataAggregator.Shared/*.csproj ./DataAggregator.Shared/ + +RUN dotnet restore ./DataAggregator.Registration/DataAggregator.Registration.csproj + +COPY src/ ./ +WORKDIR /src/DataAggregator.Registration +RUN dotnet publish -c Release -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:9.0 +WORKDIR /app +COPY --from=build /app/publish . +ENTRYPOINT ["dotnet", "DataAggregator.Registration.dll"] diff --git a/src/DataAggregator.Collector/Dockerfile b/src/DataAggregator.Collector/Dockerfile deleted file mode 100644 index e748846..0000000 --- a/src/DataAggregator.Collector/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -WORKDIR /app -EXPOSE 80 - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -WORKDIR /src -COPY ["src/DataAggregator.Collector/DataAggregator.Collector.csproj", "src/DataAggregator.Collector/"] -COPY ["src/DataAggregator.Shared/DataAggregator.Shared.csproj", "src/DataAggregator.Shared/"] -RUN dotnet restore "src/DataAggregator.Collector/DataAggregator.Collector.csproj" -COPY . . -WORKDIR "/src/src/DataAggregator.Collector" -RUN dotnet build "DataAggregator.Collector.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "DataAggregator.Collector.csproj" -c Release -o /app/publish - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "DataAggregator.Collector.dll"] diff --git a/src/DataAggregator.Processor/Dockerfile b/src/DataAggregator.Processor/Dockerfile deleted file mode 100644 index 061cb5e..0000000 --- a/src/DataAggregator.Processor/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base -WORKDIR /app -EXPOSE 80 -EXPOSE 443 - -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build -WORKDIR /src -COPY ["src/DataAggregator.Processor/DataAggregator.Processor.csproj", "src/DataAggregator.Processor/"] -COPY ["src/DataAggregator.Shared/DataAggregator.Shared.csproj", "src/DataAggregator.Shared/"] -COPY ["src/DataAggregator.Collector.Shared/DataAggregator.Collector.Shared.csproj", "src/DataAggregator.Collector.Shared/"] -RUN dotnet restore "src/DataAggregator.Processor/DataAggregator.Processor.csproj" -COPY . . -WORKDIR "/src/src/DataAggregator.Processor" -RUN dotnet build "DataAggregator.Processor.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "DataAggregator.Processor.csproj" -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . - -# Create models directory for ONNX models -RUN mkdir -p /app/models - -# Set environment variables -ENV ASPNETCORE_URLS=http://+:80 -ENV ASPNETCORE_ENVIRONMENT=Production - -ENTRYPOINT ["dotnet", "DataAggregator.Processor.dll"] \ No newline at end of file diff --git a/src/DataAggregator.Registration/Dockerfile b/src/DataAggregator.Registration/Dockerfile deleted file mode 100644 index 383d17a..0000000 --- a/src/DataAggregator.Registration/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -WORKDIR /app -EXPOSE 80 - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -WORKDIR /src -COPY ["src/DataAggregator.Registration/DataAggregator.Registration.csproj", "src/DataAggregator.Registration/"] -COPY ["src/DataAggregator.Shared/DataAggregator.Shared.csproj", "src/DataAggregator.Shared/"] -RUN dotnet restore "src/DataAggregator.Registration/DataAggregator.Registration.csproj" -COPY . . -WORKDIR "/src/src/DataAggregator.Registration" -RUN dotnet build "DataAggregator.Registration.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "DataAggregator.Registration.csproj" -c Release -o /app/publish - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "DataAggregator.Registration.dll"] From 5dd9b292437d617af5bd2df901cb5d477fa65c8b Mon Sep 17 00:00:00 2001 From: CoJaques Date: Wed, 6 Aug 2025 11:25:22 +0200 Subject: [PATCH 69/70] feat: update docker compose and dockerfile --- docker-compose.yml | 113 ++++++++++++++------------------- docker/Dockerfile.Collector | 1 + docker/Dockerfile.Processor | 1 + docker/Dockerfile.Registration | 1 + 4 files changed, 51 insertions(+), 65 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4bd748c..bb3dede 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,71 +1,54 @@ -version: '3.8' services: - collector: - build: - context: . - dockerfile: src/DataAggregator.Collector/Dockerfile - ports: - - "5000:80" - environment: - - CollectorType=OpenCN - - ASPNETCORE_ENVIRONMENT=Development - depends_on: - - influxdb - - registration + postgres: + image: postgres:15 + environment: + POSTGRES_DB: dataaggregator + POSTGRES_USER: admin + POSTGRES_PASSWORD: password123 + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data - registration: - build: - context: . - dockerfile: src/DataAggregator.Registration/Dockerfile - ports: - - "5001:80" - environment: - - ASPNETCORE_ENVIRONMENT=Development - - PGHOST=postgres - - PGPORT=5432 - - PGDATABASE=dataaggregator - - PGUSER=admin - - PGPASSWORD=password123 - depends_on: - - postgres + influxdb: + image: influxdb:3-core + ports: + - "8181:8181" + environment: + INFLUXDB3_AUTH_TOKEN: YOUR-TOKEN + command: + - influxdb3 + - serve + - --node-id=node0 + - --object-store=file + - --data-dir=/var/lib/influxdb3 + volumes: + - influx_data:/var/lib/influxdb3 - postgres: - image: postgres:15 - environment: - POSTGRES_DB: dataaggregator - POSTGRES_USER: admin - POSTGRES_PASSWORD: password123 - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data + influxdb-ui: + image: influxdata/influxdb3-ui:1.0.0 + container_name: influxdb3-explorer + ports: + - "8888:80" + environment: + INFLUXDB_URL: http://influxdb:8181 + command: [ "--mode=admin" ] + depends_on: + - influxdb - influxdb: - image: influxdb:3-core - ports: - - "8181:8181" - environment: - INFLUXDB3_AUTH_TOKEN: your-token - command: - - influxdb3 - - serve - - --node-id=node0 - - --object-store=file - - --data-dir=/var/lib/influxdb3 - volumes: - - influx_data:/var/lib/influxdb3 + grafana: + image: grafana/grafana:latest + container_name: grafana + ports: + - "3001:3000" + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + volumes: + - grafana_data:/var/lib/grafana + - ./volumes/grafana/data:/data/ - influxdb-ui: - image: influxdata/influxdb3-ui:1.0.0 - container_name: influxdb3-explorer - ports: - - "8888:80" - environment: - INFLUXDB_URL: http://influxdb:8181 - command: ["--mode=admin"] - depends_on: - - influxdb - volumes: - postgres_data: - influx_data: + postgres_data: + influx_data: + grafana_data: diff --git a/docker/Dockerfile.Collector b/docker/Dockerfile.Collector index 11b35c0..3e8f84d 100644 --- a/docker/Dockerfile.Collector +++ b/docker/Dockerfile.Collector @@ -15,6 +15,7 @@ WORKDIR /src/DataAggregator.Collector RUN dotnet publish -c Release -o /app/publish --configfile /NuGet.config FROM mcr.microsoft.com/dotnet/aspnet:9.0 +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "DataAggregator.Collector.App.dll"] diff --git a/docker/Dockerfile.Processor b/docker/Dockerfile.Processor index ffbcf02..5bf8349 100644 --- a/docker/Dockerfile.Processor +++ b/docker/Dockerfile.Processor @@ -11,6 +11,7 @@ WORKDIR /src/DataAggregator.Processor RUN dotnet publish -c Release -o /app/publish FROM mcr.microsoft.com/dotnet/aspnet:9.0 +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "DataAggregator.Processor.dll"] diff --git a/docker/Dockerfile.Registration b/docker/Dockerfile.Registration index 8201074..5ae6a54 100644 --- a/docker/Dockerfile.Registration +++ b/docker/Dockerfile.Registration @@ -11,6 +11,7 @@ WORKDIR /src/DataAggregator.Registration RUN dotnet publish -c Release -o /app/publish FROM mcr.microsoft.com/dotnet/aspnet:9.0 +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "DataAggregator.Registration.dll"] From 6e8137a34e55b2285aab23a42f3c8839edcfaf88 Mon Sep 17 00:00:00 2001 From: CoJaques Date: Wed, 6 Aug 2025 11:38:28 +0200 Subject: [PATCH 70/70] feat: adapt docker compose --- docker-compose.yml => env/docker-compose.yml | 0 src/DataAggregator.Registration/appsettings.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename docker-compose.yml => env/docker-compose.yml (100%) diff --git a/docker-compose.yml b/env/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to env/docker-compose.yml diff --git a/src/DataAggregator.Registration/appsettings.json b/src/DataAggregator.Registration/appsettings.json index e1d815c..4ff9b31 100644 --- a/src/DataAggregator.Registration/appsettings.json +++ b/src/DataAggregator.Registration/appsettings.json @@ -31,7 +31,7 @@ "Endpoints": [ { "Name": "DefaultEndpoint", - "Token": "apiv3_ebQj3GBz6CuRk3Xo6XXXP2daOdE790MvqrIAYf4SJLC1Br46gxHq7vM5YZpouYr9-17bYQbDqGFus6MrIe3ClQ", + "Token": "YOUR_TOKEN", "Endpoint": "http://localhost:8181" } ]