// Section 7 : statique comparative et MLE avec le moteur MathNet.Numerics.
#r "nuget: MathNet.Numerics, 5.0.0"
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.Optimization;
const double DeltaSc = 0.6;
const double EpsEngagementSc = 0.05;
const int RoundsSc = 400;
double[] rAutreGridSc = { 0.0, 1.0, 2.0, 3.0, 4.0, 6.0 };
int[] seedsSc = { 0, 1, 7, 42 };
static double SigmoidSc(double value) => 1.0 / (1.0 + Math.Exp(-Math.Clamp(value, -30.0, 30.0)));
static double PCoopSympathieSc(double rAutre, double alpha, double beta,
double rPropre, double tPropre, double delta)
{
double avantageCoop = (rPropre + alpha * rAutre - tPropre) / (1.0 - delta);
return SigmoidSc(beta * avantageCoop);
}
static int[] SimulerComptesSc(double[] grid, int[] seeds, int rounds,
Func<double, double> probability)
{
var counts = new int[grid.Length];
foreach (int seed in seeds)
{
var rng = new Random(seed);
for (int i = 0; i < grid.Length; i++)
for (int round = 0; round < rounds; round++)
if (rng.NextDouble() < probability(grid[i])) counts[i]++;
}
return counts;
}
static double BinomialNllSc(double[] grid, int[] counts, int n, Func<double, double> probability)
{
double nll = 0.0;
for (int i = 0; i < grid.Length; i++)
{
double p = Math.Clamp(probability(grid[i]), 1e-9, 1.0 - 1e-9);
nll -= counts[i] * Math.Log(p) + (n - counts[i]) * Math.Log(1.0 - p);
}
return nll;
}
static (double B0, double B1, double SeB1) AjusterLogistiqueSc(
double[] grid, int[] counts, int n)
{
var objective = ObjectiveFunction.Value(x =>
{
if (Math.Abs(x[0]) > 30.0 || Math.Abs(x[1]) > 30.0) return 1e12;
return BinomialNllSc(grid, counts, n, r => SigmoidSc(x[0] + x[1] * r));
});
var result = NelderMeadSimplex.Minimum(
objective,
Vector<double>.Build.DenseOfArray(new[] { 0.0, 0.0 }),
Vector<double>.Build.DenseOfArray(new[] { 1.0, 0.25 }));
double b0 = result.MinimizingPoint[0], b1 = result.MinimizingPoint[1];
double h00 = 0.0, h01 = 0.0, h11 = 0.0;
foreach (double r in grid)
{
double p = SigmoidSc(b0 + b1 * r);
double w = n * p * (1.0 - p);
h00 += w;
h01 += w * r;
h11 += w * r * r;
}
double det = h00 * h11 - h01 * h01;
double seB1 = Math.Sqrt(h00 / det);
return (b0, b1, seB1);
}
static (double Q, double Alpha, double Beta) AjusterMelangeSc(
double[] grid, int[] counts, int n, double eps,
double rPropre, double tPropre, double delta)
{
(double Value, Vector<double> Point)? best = null;
foreach (double q0 in new[] { 0.2, 0.5, 0.8 })
foreach (double a0 in new[] { 0.3, 0.8, 1.5 })
{
var objective = ObjectiveFunction.Value(x =>
{
double q = x[0], alpha = x[1], beta = x[2];
if (q < 0.0 || q > 1.0 || alpha < 0.0 || alpha > 4.0 || beta < 0.05 || beta > 10.0)
return 1e12;
return BinomialNllSc(grid, counts, n, r =>
q * (1.0 - eps) + (1.0 - q) * PCoopSympathieSc(r, alpha, beta, rPropre, tPropre, delta));
});
var result = NelderMeadSimplex.Minimum(
objective,
Vector<double>.Build.DenseOfArray(new[] { q0, a0, 1.0 }),
Vector<double>.Build.DenseOfArray(new[] { 0.08, 0.15, 0.2 }));
if (best is null || result.FunctionInfoAtMinimum.Value < best.Value.Value)
best = (result.FunctionInfoAtMinimum.Value, result.MinimizingPoint);
}
return (best!.Value.Point[0], best.Value.Point[1], best.Value.Point[2]);
}
int observationsParCelluleSc = seedsSc.Length * RoundsSc;
bool gainsPropresConstantsSc = rAutreGridSc.All(_ => R == 3.0 && T == 5.0 && P == 1.0 && S == 0.0);
bool gainsAutruiVarientSc = rAutreGridSc.Distinct().Count() == rAutreGridSc.Length;
Console.WriteLine($"Condition d'interpretabilite : gains propres constants = {gainsPropresConstantsSc} ; " +
$"R_autre prend {rAutreGridSc.Length} valeurs distinctes = {gainsAutruiVarientSc}");
Console.WriteLine($"Grille R_autre : [{string.Join(", ", rAutreGridSc.Select(x => x.ToString("F1")))}]");
Console.WriteLine();
// Contrôle 1 : sympathie pure, alpha connu.
int[] comptesSympathieSc = SimulerComptesSc(
rAutreGridSc, seedsSc, RoundsSc,
r => PCoopSympathieSc(r, 0.8, 1.0, R, T, DeltaSc));
var fitSympathieSc = AjusterLogistiqueSc(rAutreGridSc, comptesSympathieSc, observationsParCelluleSc);
double alphaHatSympathieSc = fitSympathieSc.B1 * (R - T) / fitSympathieSc.B0;
Console.WriteLine("--- CONTROLE 1 : sympathie pure (alpha vrai = 0.8) ---");
Console.WriteLine($" taux : [{string.Join(", ", comptesSympathieSc.Select(k => ((double)k / observationsParCelluleSc).ToString("F3")))}]");
Console.WriteLine($" MathNet MLE : alpha_hat = {alphaHatSympathieSc:F3}, pente b1 = {fitSympathieSc.B1:+0.000;-0.000}");
Console.WriteLine($" ecart absolu a la valeur vraie = {Math.Abs(alphaHatSympathieSc - 0.8):F3}");
Console.WriteLine();
// Contrôle 2 : engagement pur, règle plate avec bruit indépendant de R_autre.
int[] comptesEngagementSc = SimulerComptesSc(
rAutreGridSc, seedsSc, RoundsSc, _ => 1.0 - EpsEngagementSc);
var fitEngagementSc = AjusterLogistiqueSc(rAutreGridSc, comptesEngagementSc, observationsParCelluleSc);
double ciBasPenteSc = fitEngagementSc.B1 - 1.96 * fitEngagementSc.SeB1;
double ciHautPenteSc = fitEngagementSc.B1 + 1.96 * fitEngagementSc.SeB1;
Console.WriteLine("--- CONTROLE 2 : engagement pur (regle fixe, insensible) ---");
Console.WriteLine($" taux : [{string.Join(", ", comptesEngagementSc.Select(k => ((double)k / observationsParCelluleSc).ToString("F3")))}]");
Console.WriteLine($" pente b1 = {fitEngagementSc.B1:+0.0000;-0.0000}, CI95 Wald " +
$"[{ciBasPenteSc:+0.0000;-0.0000}, {ciHautPenteSc:+0.0000;-0.0000}] " +
$"-> couvre 0 : {ciBasPenteSc <= 0.0 && ciHautPenteSc >= 0.0}");
Console.WriteLine();
// Agent sous test : mélange 50 % engagement, 50 % sympathie alpha=0.6.
int[] comptesMelangeSc = SimulerComptesSc(
rAutreGridSc, seedsSc, RoundsSc,
r => 0.5 * (1.0 - EpsEngagementSc) + 0.5 * PCoopSympathieSc(r, 0.6, 1.0, R, T, DeltaSc));
var fitMelangeSc = AjusterMelangeSc(
rAutreGridSc, comptesMelangeSc, observationsParCelluleSc,
EpsEngagementSc, R, T, DeltaSc);
Console.WriteLine("--- AGENT SOUS TEST : melange (q vrai = 0.5, alpha vrai = 0.6) ---");
Console.WriteLine($" taux : [{string.Join(", ", comptesMelangeSc.Select(k => ((double)k / observationsParCelluleSc).ToString("F3")))}]");
Console.WriteLine($" MathNet NelderMead : q_hat = {fitMelangeSc.Q:F3}, alpha_hat = {fitMelangeSc.Alpha:F3}, beta_hat = {fitMelangeSc.Beta:F3}");
Console.WriteLine($" ecarts absolus : |q_hat - 0.5| = {Math.Abs(fitMelangeSc.Q - 0.5):F3}, " +
$"|alpha_hat - 0.6| = {Math.Abs(fitMelangeSc.Alpha - 0.6):F3}");
Console.WriteLine(" verdict : masse plate et composante sensible separees par le moteur .NET.");