using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using UAnalytics.Interop;
namespace UAnalytics;
public sealed class AnalyticsClient : IDisposable
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
private bool _disposed;
public string GetVersion()
{
var ptr = NativeInterop.uanalytics_version();
var version = Marshal.PtrToStringUTF8(ptr) ?? "unknown";
NativeInterop.uanalytics_free_string(ptr);
return version;
}
public JsonElement XbarRChart(double[][] subgroups, string[]? rules = null)
=> CallNative(NativeInterop.uanalytics_xbar_r_chart,
rules is null ? new { subgroups } : (object)new { subgroups, rules });
public JsonElement PChart(ulong[][] samples, double? pBar = null)
=> CallNative(NativeInterop.uanalytics_p_chart, new { samples, pBar });
public JsonElement LaneyPChart(ulong[][] samples, double? pBar = null, double? phi = null)
=> CallNative(NativeInterop.uanalytics_laney_p_chart, new { samples, pBar, phi });
public JsonElement NpChart(ulong[] defectives, ulong sampleSize)
=> CallNative(NativeInterop.uanalytics_np_chart, new { defectives, sampleSize });
public JsonElement CChart(ulong[] defects)
=> CallNative(NativeInterop.uanalytics_c_chart, new { defects });
public JsonElement UChart(double[][] samples, double? uBar = null)
=> CallNative(NativeInterop.uanalytics_u_chart, new { samples, uBar });
public JsonElement LaneyUChart(double[][] samples, double? uBar = null, double? phi = null)
=> CallNative(NativeInterop.uanalytics_laney_u_chart, new { samples, uBar, phi });
public JsonElement GChart(double[] gaps)
=> CallNative(NativeInterop.uanalytics_g_chart, new { gaps });
public JsonElement TChart(double[] times)
=> CallNative(NativeInterop.uanalytics_t_chart, new { times });
public JsonElement XbarSChart(double[][] subgroups, string[]? rules = null)
=> CallNative(NativeInterop.uanalytics_xbar_s_chart,
rules is null ? new { subgroups } : (object)new { subgroups, rules });
public JsonElement ImrChart(double[] values, string[]? rules = null)
=> CallNative(NativeInterop.uanalytics_imr_chart,
rules is null ? new { values } : (object)new { values, rules });
public JsonElement RunRules(double[] values, double ucl, double cl, double lcl,
string[]? rules = null)
=> CallNative(NativeInterop.uanalytics_run_rules,
rules is null
? new { values, limits = new { ucl, cl, lcl } }
: (object)new { values, limits = new { ucl, cl, lcl }, rules });
public JsonElement ProcessCapability(double[] data, double? usl, double? lsl, double? target = null,
double? sigmaWithin = null)
=> CallNative(NativeInterop.uanalytics_process_capability,
new { data, usl, lsl, target, sigma_within = sigmaWithin });
public JsonElement PercentileCapability(double[] data, double? usl, double? lsl)
=> CallNative(NativeInterop.uanalytics_percentile_capability,
new { data, usl, lsl });
public JsonElement GageRRXbarR(double[][][] measurements, double? tolerance = null)
=> CallNative(NativeInterop.uanalytics_gage_rr_xbar_r,
new { measurements, tolerance });
public JsonElement GageRRAnova(double[][][] measurements, double? tolerance = null)
=> CallNative(NativeInterop.uanalytics_gage_rr_anova,
new { measurements, tolerance });
public JsonElement WeibullMle(double[] failureTimes)
=> CallNative(NativeInterop.uanalytics_weibull_mle,
new { failure_times = failureTimes });
public JsonElement WeibullMrr(double[] failureTimes)
=> CallNative(NativeInterop.uanalytics_weibull_mrr,
new { failure_times = failureTimes });
public JsonElement WeibullReliability(double shape, double scale, double[]? times = null,
double[]? fractionsFailed = null)
=> CallNative(NativeInterop.uanalytics_weibull_reliability,
new { shape, scale, times = times ?? [], fractions_failed = fractionsFailed ?? [] });
public JsonElement BoxCoxCapability(double[] data, double? usl = null, double? lsl = null,
(double Min, double Max)? lambdaRange = null)
=> CallNative(NativeInterop.uanalytics_boxcox_capability,
new
{
data,
usl,
lsl,
lambda_range = lambdaRange is { } r ? new[] { r.Min, r.Max } : null,
});
public double SigmaToPpm(double sigma)
=> CallNative(NativeInterop.uanalytics_sigma_to_ppm, new { sigma }).GetProperty("value").GetDouble();
public double PpmToSigma(double ppm)
=> CallNative(NativeInterop.uanalytics_ppm_to_sigma, new { ppm }).GetProperty("value").GetDouble();
public JsonElement OneSampleTTest(double[] data, double mu0)
=> CallNative(NativeInterop.uanalytics_one_sample_t_test, new { data, mu0 });
public JsonElement TwoSampleTTest(double[] a, double[] b)
=> CallNative(NativeInterop.uanalytics_two_sample_t_test, new { a, b });
public JsonElement PairedTTest(double[] x, double[] y)
=> CallNative(NativeInterop.uanalytics_paired_t_test, new { x, y });
public JsonElement MannWhitneyUTest(double[] a, double[] b)
=> CallNative(NativeInterop.uanalytics_mann_whitney_u_test, new { a, b });
public JsonElement WilcoxonSignedRankTest(double[] x, double[] y)
=> CallNative(NativeInterop.uanalytics_wilcoxon_signed_rank_test, new { x, y });
public JsonElement JarqueBeraTest(double[] data)
=> CallNative(NativeInterop.uanalytics_jarque_bera_test, new { data });
public JsonElement ShapiroWilkTest(double[] data)
=> CallNative(NativeInterop.uanalytics_shapiro_wilk_test, new { data });
public JsonElement AndersonDarlingTest(double[] data)
=> CallNative(NativeInterop.uanalytics_anderson_darling_test, new { data });
public JsonElement AdfTest(double[] data, string model = "constant", int? maxLags = null)
=> CallNative(NativeInterop.uanalytics_adf_test, new { data, model, max_lags = maxLags });
public JsonElement MannKendallTest(double[] data)
=> CallNative(NativeInterop.uanalytics_mann_kendall_test, new { data });
public JsonElement OneWayAnova(double[][] groups)
=> CallNative(NativeInterop.uanalytics_one_way_anova, new { groups });
public JsonElement KruskalWallisTest(double[][] groups)
=> CallNative(NativeInterop.uanalytics_kruskal_wallis_test, new { groups });
public JsonElement LeveneTest(double[][] groups)
=> CallNative(NativeInterop.uanalytics_levene_test, new { groups });
public JsonElement BartlettTest(double[][] groups)
=> CallNative(NativeInterop.uanalytics_bartlett_test, new { groups });
public JsonElement ChiSquaredGoodnessOfFit(double[] observed, double[] expected)
=> CallNative(NativeInterop.uanalytics_chi_squared_goodness_of_fit, new { observed, expected });
public JsonElement ChiSquaredIndependence(double[][] table)
=> CallNative(NativeInterop.uanalytics_chi_squared_independence, new { table });
public JsonElement FisherExactTest(long[][] table)
=> CallNative(NativeInterop.uanalytics_fisher_exact_test, new { table });
public double[] BonferroniCorrection(double[] pValues)
=> Values(CallNative(NativeInterop.uanalytics_bonferroni_correction, new { p_values = pValues }));
public double[] BenjaminiHochberg(double[] pValues)
=> Values(CallNative(NativeInterop.uanalytics_benjamini_hochberg, new { p_values = pValues }));
private static double[] Values(JsonElement body)
=> body.GetProperty("values").EnumerateArray().Select(v => v.GetDouble()).ToArray();
public JsonElement LaplaceTrendTest(double[] times, double? end)
=> CallNative(NativeInterop.uanalytics_laplace_trend_test, PointProcessRequest(times, end));
public JsonElement MilHdbk189Test(double[] times, double? end)
=> CallNative(NativeInterop.uanalytics_mil_hdbk_189_test, PointProcessRequest(times, end));
public JsonElement PowerLawProcessFit(double[] times, double? end)
=> CallNative(NativeInterop.uanalytics_power_law_process_fit, PointProcessRequest(times, end));
private static object PointProcessRequest(double[] times, double? end)
=> end is { } t
? new { times, observation = (object)new { truncation = "time", end = t } }
: new { times, observation = (object)new { truncation = "failure" } };
public JsonElement DetectChangepoints(double[] data, double? penalty = null, int? minSegmentLen = null,
string cost = "l2")
=> CallNative(NativeInterop.uanalytics_detect_changepoints,
penalty is null
? new { data, cost, min_segment_len = minSegmentLen }
: (object)new { data, cost, penalty, min_segment_len = minSegmentLen });
public JsonElement DetectChangepointsMulti(double[][] signals, double? penalty = null,
int? minSegmentLen = null, string cost = "l2")
=> CallNative(NativeInterop.uanalytics_detect_changepoints_multi,
penalty is null
? new { signals, cost, min_segment_len = minSegmentLen }
: (object)new { signals, cost, penalty, min_segment_len = minSegmentLen });
public JsonElement Cusum(double[] data, double target, double sigma, double? k = null, double? h = null)
=> CallNative(NativeInterop.uanalytics_cusum, new { data, target, sigma, k, h });
public JsonElement Ewma(double[] data, double target, double sigma, double? lambda = null,
double? lFactor = null)
=> CallNative(NativeInterop.uanalytics_ewma, new { data, target, sigma, lambda, l_factor = lFactor });
public JsonElement EstimatePeriod(double[] data)
=> CallNative(NativeInterop.uanalytics_estimate_period, new { data });
public JsonElement SpectralResidual(double[] data, int? averagingWindow = null, int? judgementWindow = null,
double? threshold = null, double? minZscore = null, double? sensitivity = null, int? batchSize = null)
=> CallNative(NativeInterop.uanalytics_spectral_residual,
new
{
data,
averaging_window = averagingWindow,
judgement_window = judgementWindow,
threshold,
min_zscore = minZscore,
sensitivity,
batch_size = batchSize
});
public JsonElement CorrelationMatrix(double[][] variables, string method = "pearson")
=> CallNative(NativeInterop.uanalytics_correlation_matrix,
new { variables, method });
public JsonElement SimpleRegression(double[] x, double[] y)
=> CallNative(NativeInterop.uanalytics_simple_regression,
new { x, y });
public JsonElement FitBest(double[] data)
=> CallNative(NativeInterop.uanalytics_fit_best,
new { data });
private delegate int NativeFunc(string json, out IntPtr result);
private JsonElement CallNative(NativeFunc func, object request)
{
NonFinite.Check(request, JsonOptions.PropertyNamingPolicy!);
var requestJson = JsonSerializer.Serialize(request, JsonOptions);
var code = func(requestJson, out var resultPtr);
try
{
if (resultPtr == IntPtr.Zero)
throw new AnalyticsException(code, "Null result from engine");
var resultJson = Marshal.PtrToStringUTF8(resultPtr);
if (string.IsNullOrEmpty(resultJson))
throw new AnalyticsException(code, "Empty result from engine");
if (code != 0)
throw AnalyticsException.FromErrorBody(code, resultJson);
return JsonDocument.Parse(resultJson).RootElement.Clone();
}
finally
{
if (resultPtr != IntPtr.Zero)
NativeInterop.uanalytics_free_string(resultPtr);
}
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
GC.SuppressFinalize(this);
}
}
}
public class AnalyticsException : Exception
{
public int Code { get; }
public string? Reason { get; }
public int? Index { get; }
public string? Parameter { get; }
public JsonElement? Details { get; }
public AnalyticsException(int code, string message) : base(message)
{
Code = code;
}
public AnalyticsException(int code, string message, string? reason, int? index) : base(message)
{
Code = code;
Reason = reason;
Index = index;
}
public AnalyticsException(int code, string message, string? reason, int? index, string? parameter,
JsonElement? details) : this(code, message, reason, index)
{
Parameter = parameter;
Details = details;
}
internal static AnalyticsException FromErrorBody(int code, string body)
{
try
{
using var doc = JsonDocument.Parse(body);
var root = doc.RootElement;
var message = root.TryGetProperty("error", out var e) && e.ValueKind == JsonValueKind.String
? e.GetString()!
: body;
string? reason = root.TryGetProperty("code", out var c) && c.ValueKind == JsonValueKind.String
? c.GetString()
: null;
int? index = root.TryGetProperty("index", out var i) && i.ValueKind == JsonValueKind.Number
? i.GetInt32()
: null;
string? parameter = root.TryGetProperty("parameter", out var p) && p.ValueKind == JsonValueKind.String
? p.GetString()
: null;
return new AnalyticsException(code, message, reason, index, parameter, root.Clone());
}
catch (JsonException)
{
return new AnalyticsException(code, body);
}
}
}