using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;
using UAnalytics.Interop;
namespace UAnalytics;
public sealed class AnalyticsClient : IDisposable
{
private bool _disposed;
internal Action<string>? ResponseObserver { get; set; }
public string GetVersion()
{
var ptr = NativeInterop.uanalytics_version();
var version = Marshal.PtrToStringUTF8(ptr) ?? "unknown";
NativeInterop.uanalytics_free_string(ptr);
return version;
}
public XbarRChartResult XbarRChart(double[][] subgroups, IReadOnlyList<RunRule>? rules = null)
=> Call(NativeInterop.uanalytics_xbar_r_chart, AnalyticsJson.Default.XbarRChartResult,
("subgroups", Request.Rows("subgroups", subgroups)), ("rules", Request.Rules(rules)));
public PChartResult PChart(ulong[][] samples, double? pBar = null)
=> Call(NativeInterop.uanalytics_p_chart, AnalyticsJson.Default.PChartResult,
("samples", Request.Counts(samples)), ("p_bar", Request.Num("p_bar", pBar)));
public LaneyPChartResult LaneyPChart(ulong[][] samples, double? pBar = null, double? phi = null)
=> Call(NativeInterop.uanalytics_laney_p_chart, AnalyticsJson.Default.LaneyPChartResult,
("samples", Request.Counts(samples)), ("p_bar", Request.Num("p_bar", pBar)),
("phi", Request.Num("phi", phi)));
public FixedLimitChartResult NpChart(ulong[] defectives, ulong sampleSize)
=> Call(NativeInterop.uanalytics_np_chart, AnalyticsJson.Default.FixedLimitChartResult,
("defectives", Request.Counts(defectives)), ("sample_size", JsonValue.Create(sampleSize)));
public FixedLimitChartResult CChart(ulong[] defects)
=> Call(NativeInterop.uanalytics_c_chart, AnalyticsJson.Default.FixedLimitChartResult,
("defects", Request.Counts(defects)));
public UChartResult UChart(double[][] samples, double? uBar = null)
=> Call(NativeInterop.uanalytics_u_chart, AnalyticsJson.Default.UChartResult,
("samples", Request.Rows("samples", samples)), ("u_bar", Request.Num("u_bar", uBar)));
public LaneyUChartResult LaneyUChart(double[][] samples, double? uBar = null, double? phi = null)
=> Call(NativeInterop.uanalytics_laney_u_chart, AnalyticsJson.Default.LaneyUChartResult,
("samples", Request.Rows("samples", samples)), ("u_bar", Request.Num("u_bar", uBar)),
("phi", Request.Num("phi", phi)));
public GChartResult GChart(double[] gaps)
=> Call(NativeInterop.uanalytics_g_chart, AnalyticsJson.Default.GChartResult,
("gaps", Request.Nums("gaps", gaps)));
public TChartResult TChart(double[] times)
=> Call(NativeInterop.uanalytics_t_chart, AnalyticsJson.Default.TChartResult,
("times", Request.Nums("times", times)));
public XbarSChartResult XbarSChart(double[][] subgroups, IReadOnlyList<RunRule>? rules = null)
=> Call(NativeInterop.uanalytics_xbar_s_chart, AnalyticsJson.Default.XbarSChartResult,
("subgroups", Request.Rows("subgroups", subgroups)), ("rules", Request.Rules(rules)));
public ImrChartResult ImrChart(double[] values, IReadOnlyList<RunRule>? rules = null)
=> Call(NativeInterop.uanalytics_imr_chart, AnalyticsJson.Default.ImrChartResult,
("values", Request.Nums("values", values)), ("rules", Request.Rules(rules)));
public IReadOnlyList<ChartPoint> RunRules(double[] values, double ucl, double cl, double lcl,
IReadOnlyList<RunRule>? rules = null)
=> Call(NativeInterop.uanalytics_run_rules, AnalyticsJson.Default.IReadOnlyListChartPoint,
("values", Request.Nums("values", values)),
("limits", Request.Body(
("ucl", Request.Num("limits.ucl", ucl)),
("cl", Request.Num("limits.cl", cl)),
("lcl", Request.Num("limits.lcl", lcl)))),
("rules", Request.Rules(rules)));
public CapabilityResult ProcessCapability(double[] data, double? usl, double? lsl, double? target = null,
double? sigmaWithin = null)
=> Call(NativeInterop.uanalytics_process_capability, AnalyticsJson.Default.CapabilityResult,
("data", Request.Nums("data", data)), ("usl", Request.Num("usl", usl)),
("lsl", Request.Num("lsl", lsl)), ("target", Request.Num("target", target)),
("sigma_within", Request.Num("sigma_within", sigmaWithin)));
public PercentileCapabilityResult PercentileCapability(double[] data, double? usl, double? lsl)
=> Call(NativeInterop.uanalytics_percentile_capability, AnalyticsJson.Default.PercentileCapabilityResult,
("data", Request.Nums("data", data)), ("usl", Request.Num("usl", usl)),
("lsl", Request.Num("lsl", lsl)));
public GageRRResult GageRRXbarR(double[][][] measurements, double? tolerance = null)
=> Call(NativeInterop.uanalytics_gage_rr_xbar_r, AnalyticsJson.Default.GageRRResult,
("measurements", Request.Rows("measurements", measurements)),
("tolerance", Request.Num("tolerance", tolerance)));
public GageRRAnovaResult GageRRAnova(double[][][] measurements, double? tolerance = null)
=> Call(NativeInterop.uanalytics_gage_rr_anova, AnalyticsJson.Default.GageRRAnovaResult,
("measurements", Request.Rows("measurements", measurements)),
("tolerance", Request.Num("tolerance", tolerance)));
public WeibullMleResult WeibullMle(double[] failureTimes)
=> Call(NativeInterop.uanalytics_weibull_mle, AnalyticsJson.Default.WeibullMleResult,
("failure_times", Request.Nums("failure_times", failureTimes)));
public WeibullMrrResult WeibullMrr(double[] failureTimes)
=> Call(NativeInterop.uanalytics_weibull_mrr, AnalyticsJson.Default.WeibullMrrResult,
("failure_times", Request.Nums("failure_times", failureTimes)));
public WeibullReliabilityResult WeibullReliability(double shape, double scale, double[]? times = null,
double[]? fractionsFailed = null)
=> Call(NativeInterop.uanalytics_weibull_reliability, AnalyticsJson.Default.WeibullReliabilityResult,
("shape", Request.Num("shape", shape)), ("scale", Request.Num("scale", scale)),
("times", Request.Nums("times", times ?? [])),
("fractions_failed", Request.Nums("fractions_failed", fractionsFailed ?? [])));
public BoxCoxCapabilityResult BoxCoxCapability(double[] data, double? usl = null, double? lsl = null,
(double Min, double Max)? lambdaRange = null)
=> Call(NativeInterop.uanalytics_boxcox_capability, AnalyticsJson.Default.BoxCoxCapabilityResult,
("data", Request.Nums("data", data)), ("usl", Request.Num("usl", usl)),
("lsl", Request.Num("lsl", lsl)),
("lambda_range", lambdaRange is { } r ? Request.Nums("lambda_range", [r.Min, r.Max]) : null));
public double SigmaToPpm(double sigma)
=> Call(NativeInterop.uanalytics_sigma_to_ppm, AnalyticsJson.Default.ValueBody,
("sigma", Request.Num("sigma", sigma))).Value;
public double PpmToSigma(double ppm)
=> Call(NativeInterop.uanalytics_ppm_to_sigma, AnalyticsJson.Default.ValueBody,
("ppm", Request.Num("ppm", ppm))).Value;
public TestResult OneSampleTTest(double[] data, double mu0)
=> Call(NativeInterop.uanalytics_one_sample_t_test, AnalyticsJson.Default.TestResult,
("data", Request.Nums("data", data)), ("mu0", Request.Num("mu0", mu0)));
public TestResult TwoSampleTTest(double[] a, double[] b)
=> Call(NativeInterop.uanalytics_two_sample_t_test, AnalyticsJson.Default.TestResult,
("a", Request.Nums("a", a)), ("b", Request.Nums("b", b)));
public TestResult PairedTTest(double[] x, double[] y)
=> Call(NativeInterop.uanalytics_paired_t_test, AnalyticsJson.Default.TestResult,
("x", Request.Nums("x", x)), ("y", Request.Nums("y", y)));
public TestResult MannWhitneyUTest(double[] a, double[] b)
=> Call(NativeInterop.uanalytics_mann_whitney_u_test, AnalyticsJson.Default.TestResult,
("a", Request.Nums("a", a)), ("b", Request.Nums("b", b)));
public TestResult WilcoxonSignedRankTest(double[] x, double[] y)
=> Call(NativeInterop.uanalytics_wilcoxon_signed_rank_test, AnalyticsJson.Default.TestResult,
("x", Request.Nums("x", x)), ("y", Request.Nums("y", y)));
public TestResult JarqueBeraTest(double[] data)
=> Call(NativeInterop.uanalytics_jarque_bera_test, AnalyticsJson.Default.TestResult,
("data", Request.Nums("data", data)));
public ShapiroWilkResult ShapiroWilkTest(double[] data)
=> Call(NativeInterop.uanalytics_shapiro_wilk_test, AnalyticsJson.Default.ShapiroWilkResult,
("data", Request.Nums("data", data)));
public AndersonDarlingResult AndersonDarlingTest(double[] data)
=> Call(NativeInterop.uanalytics_anderson_darling_test, AnalyticsJson.Default.AndersonDarlingResult,
("data", Request.Nums("data", data)));
public AdfResult AdfTest(double[] data, AdfModel model = AdfModel.Constant, int? maxLags = null)
=> Call(NativeInterop.uanalytics_adf_test, AnalyticsJson.Default.AdfResult,
("data", Request.Nums("data", data)),
("model", Request.Option(model, AnalyticsJson.Default.AdfModel)),
("max_lags", maxLags is { } l ? JsonValue.Create(l) : null));
public MannKendallResult MannKendallTest(double[] data)
=> Call(NativeInterop.uanalytics_mann_kendall_test, AnalyticsJson.Default.MannKendallResult,
("data", Request.Nums("data", data)));
public AnovaResult OneWayAnova(double[][] groups)
=> Call(NativeInterop.uanalytics_one_way_anova, AnalyticsJson.Default.AnovaResult,
("groups", Request.Rows("groups", groups)));
public TestResult KruskalWallisTest(double[][] groups)
=> Call(NativeInterop.uanalytics_kruskal_wallis_test, AnalyticsJson.Default.TestResult,
("groups", Request.Rows("groups", groups)));
public TestResult LeveneTest(double[][] groups)
=> Call(NativeInterop.uanalytics_levene_test, AnalyticsJson.Default.TestResult,
("groups", Request.Rows("groups", groups)));
public TestResult BartlettTest(double[][] groups)
=> Call(NativeInterop.uanalytics_bartlett_test, AnalyticsJson.Default.TestResult,
("groups", Request.Rows("groups", groups)));
public TestResult ChiSquaredGoodnessOfFit(double[] observed, double[] expected)
=> Call(NativeInterop.uanalytics_chi_squared_goodness_of_fit, AnalyticsJson.Default.TestResult,
("observed", Request.Nums("observed", observed)), ("expected", Request.Nums("expected", expected)));
public TestResult ChiSquaredIndependence(double[][] table)
=> Call(NativeInterop.uanalytics_chi_squared_independence, AnalyticsJson.Default.TestResult,
("table", Request.Rows("table", table)));
public TestResult FisherExactTest(long[][] table)
=> Call(NativeInterop.uanalytics_fisher_exact_test, AnalyticsJson.Default.TestResult,
("table", Request.Counts(table)));
public IReadOnlyList<double> BonferroniCorrection(double[] pValues)
=> Call(NativeInterop.uanalytics_bonferroni_correction, AnalyticsJson.Default.ValuesBody,
("p_values", Request.Nums("p_values", pValues))).Values;
public IReadOnlyList<double> BenjaminiHochberg(double[] pValues)
=> Call(NativeInterop.uanalytics_benjamini_hochberg, AnalyticsJson.Default.ValuesBody,
("p_values", Request.Nums("p_values", pValues))).Values;
public TrendTestResult LaplaceTrendTest(double[] times, double? end)
=> Call(NativeInterop.uanalytics_laplace_trend_test, AnalyticsJson.Default.TrendTestResult,
PointProcessRequest(times, end));
public TrendTestResult MilHdbk189Test(double[] times, double? end)
=> Call(NativeInterop.uanalytics_mil_hdbk_189_test, AnalyticsJson.Default.TrendTestResult,
PointProcessRequest(times, end));
public PowerLawFitResult PowerLawProcessFit(double[] times, double? end)
=> Call(NativeInterop.uanalytics_power_law_process_fit, AnalyticsJson.Default.PowerLawFitResult,
PointProcessRequest(times, end));
private static (string, JsonNode?)[] PointProcessRequest(double[] times, double? end)
=>
[
("times", Request.Nums("times", times)),
("observation", end is { } t
? Request.Body(("truncation", JsonValue.Create("time")), ("end", Request.Num("observation.end", t)))
: Request.Body(("truncation", JsonValue.Create("failure")))),
];
public ChangepointResult DetectChangepoints(double[] data, double? penalty = null, int? minSegmentLen = null,
PeltCost cost = PeltCost.L2)
=> Call(NativeInterop.uanalytics_detect_changepoints, AnalyticsJson.Default.ChangepointResult,
("data", Request.Nums("data", data)),
("cost", Request.Option(cost, AnalyticsJson.Default.PeltCost)),
("penalty", Request.Num("penalty", penalty)),
("min_segment_len", minSegmentLen is { } m ? JsonValue.Create(m) : null));
public ChangepointResult DetectChangepointsMulti(double[][] signals, double? penalty = null,
int? minSegmentLen = null, PeltCost cost = PeltCost.L2)
=> Call(NativeInterop.uanalytics_detect_changepoints_multi, AnalyticsJson.Default.ChangepointResult,
("signals", Request.Rows("signals", signals)),
("cost", Request.Option(cost, AnalyticsJson.Default.PeltCost)),
("penalty", Request.Num("penalty", penalty)),
("min_segment_len", minSegmentLen is { } m ? JsonValue.Create(m) : null));
public CusumResult Cusum(double[] data, double target, double sigma, double? k = null, double? h = null)
=> Call(NativeInterop.uanalytics_cusum, AnalyticsJson.Default.CusumResult,
("data", Request.Nums("data", data)), ("target", Request.Num("target", target)),
("sigma", Request.Num("sigma", sigma)), ("k", Request.Num("k", k)), ("h", Request.Num("h", h)));
public EwmaResult Ewma(double[] data, double target, double sigma, double? lambda = null,
double? lFactor = null)
=> Call(NativeInterop.uanalytics_ewma, AnalyticsJson.Default.EwmaResult,
("data", Request.Nums("data", data)), ("target", Request.Num("target", target)),
("sigma", Request.Num("sigma", sigma)), ("lambda", Request.Num("lambda", lambda)),
("l_factor", Request.Num("l_factor", lFactor)));
public PeriodResult EstimatePeriod(double[] data)
=> Call(NativeInterop.uanalytics_estimate_period, AnalyticsJson.Default.PeriodResult,
("data", Request.Nums("data", data)));
public SpectralResidualResult SpectralResidual(double[] data, int? averagingWindow = null, int? judgementWindow = null,
double? threshold = null, double? minZscore = null, double? sensitivity = null, int? batchSize = null)
=> Call(NativeInterop.uanalytics_spectral_residual, AnalyticsJson.Default.SpectralResidualResult,
("data", Request.Nums("data", data)),
("averaging_window", averagingWindow is { } a ? JsonValue.Create(a) : null),
("judgement_window", judgementWindow is { } j ? JsonValue.Create(j) : null),
("threshold", Request.Num("threshold", threshold)),
("min_zscore", Request.Num("min_zscore", minZscore)),
("sensitivity", Request.Num("sensitivity", sensitivity)),
("batch_size", batchSize is { } b ? JsonValue.Create(b) : null));
public CorrelationMatrixResult CorrelationMatrix(double[][] variables, CorrelationMethod method = CorrelationMethod.Pearson)
=> Call(NativeInterop.uanalytics_correlation_matrix, AnalyticsJson.Default.CorrelationMatrixResult,
("variables", Request.Rows("variables", variables)),
("method", Request.Option(method, AnalyticsJson.Default.CorrelationMethod)));
public RegressionResult SimpleRegression(double[] x, double[] y)
=> Call(NativeInterop.uanalytics_simple_regression, AnalyticsJson.Default.RegressionResult,
("x", Request.Nums("x", x)), ("y", Request.Nums("y", y)));
public IReadOnlyList<DistributionFit> FitBest(double[] data)
=> Call(NativeInterop.uanalytics_fit_best, AnalyticsJson.Default.IReadOnlyListDistributionFit,
("data", Request.Nums("data", data)));
private delegate int NativeFunc(string json, out IntPtr result);
private T Call<T>(NativeFunc func, JsonTypeInfo<T> result, params (string Key, JsonNode? Value)[] members)
{
var body = Invoke(func, Request.Body(members).ToJsonString());
ResponseObserver?.Invoke(body);
return JsonSerializer.Deserialize(body, result)
?? throw new AnalyticsException(-4, "The engine returned null.");
}
private static string Invoke(NativeFunc func, string requestJson)
{
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 resultJson;
}
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);
}
}
}