#r "nuget: pythonnet,3.1.0"
using Python.Runtime;
using System.IO;
// === Tranche 3 (#10382) : pont PythonNet -> automata-lib 9.2.0, LE MÊME MOTEUR que le jumeau Python ===
// Pattern de Sudoku-5 / GT-13 : CPython hébergé dans le kernel .NET Interactive, bloc GIL unique,
// tout le calcul côté Python, les résultats reviennent par Get<string>.
string[] dllCandidates =
{
Environment.GetEnvironmentVariable("PYTHONNET_PYDLL"),
@"C:\ProgramData\miniconda3\python313.dll",
};
// Repli portable (Linux, macOS, ou Windows sans le CPython ci-dessus) : le premier
// interpreteur (python3, puis python) qui importe le module fournit sa bibliotheque
// partagee, son prefixe et son sys.path, environnement virtuel compris.
static void InitPythonFromInterpreter(string module)
{
const string probe =
"import importlib, json, os, sys, sysconfig\n" +
"importlib.import_module(sys.argv[1])\n" +
"v = sysconfig.get_config_var; lib = v('LIBDIR') or ''; mm = sys.version_info[:2]\n" +
"c = [os.path.join(lib, n) for n in (v('INSTSONAME'), v('LDLIBRARY')) if n]\n" +
"c += [os.path.join(d, 'libpython%d.%d.dylib' % mm) for d in (lib, os.path.join(sys.base_prefix, 'lib'))]\n" +
"c.append(os.path.join(sys.base_prefix, 'python%d%d.dll' % mm))\n" +
"dll = next((p for p in c if os.path.isfile(p)), '')\n" +
"print(json.dumps({'dll': dll, 'home': sys.base_prefix, 'path': [p for p in sys.path if p]}))\n";
foreach (var exe in new[] { "python3", "python" })
{
try
{
var psi = new System.Diagnostics.ProcessStartInfo(exe)
{ RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false };
foreach (var arg in new[] { "-c", probe, module }) psi.ArgumentList.Add(arg);
using var p = System.Diagnostics.Process.Start(psi);
var stderr = p.StandardError.ReadToEndAsync();
string json = p.StandardOutput.ReadToEnd();
p.WaitForExit();
if (p.ExitCode != 0) continue;
var info = System.Text.Json.JsonDocument.Parse(json).RootElement;
string dll = info.GetProperty("dll").GetString();
if (string.IsNullOrEmpty(dll)) continue;
Runtime.PythonDLL = dll;
PythonEngine.PythonHome = info.GetProperty("home").GetString();
PythonEngine.Initialize();
using (Py.GIL())
{
var path = info.GetProperty("path").EnumerateArray()
.Select(e => (PyObject)new PyString(e.GetString())).ToArray();
Py.Import("sys").SetAttr("path", new PyList(path));
}
return;
}
catch (System.ComponentModel.Win32Exception) { } // interpreteur absent du PATH
}
throw new System.IO.FileNotFoundException(
$"Aucun CPython n'importe {module} : l'installer (pip install), ou definir PYTHONNET_PYDLL.");
}
string pyDll = dllCandidates.FirstOrDefault(File.Exists);
if (pyDll != null)
{
string pyDir = Path.GetDirectoryName(pyDll);
Environment.SetEnvironmentVariable(
"PATH", pyDir + Path.PathSeparator + Environment.GetEnvironmentVariable("PATH"));
Runtime.PythonDLL = pyDll;
PythonEngine.Initialize();
}
else InitPythonFromInterpreter("automata");
Console.WriteLine($"Pont PythonNet : CPython {PythonEngine.Version} -> automata-lib (même moteur que le jumeau Python)");
Console.WriteLine();
using (Py.GIL())
{
dynamic scope = Py.CreateScope();
scope.Exec(@"
from automata.fa.nfa import NFA
from automata.fa.dfa import DFA
# ---------- Mouvement 1 : l'expérience NFA ""ab"" du jumeau Python, rejouée sur le même moteur ----------
nfa_ab = NFA(
states={'q0', 'q1', 'q2'}, input_symbols={'a', 'b'},
transitions={'q0': {'a': {'q0', 'q1'}}, 'q1': {'b': {'q2'}}, 'q2': {'a': {'q2'}, 'b': {'q2'}}},
initial_state='q0', final_states={'q2'})
words1 = ['ab', 'aab', 'abab', 'a', 'b', 'ba', 'aa']
lines1 = ['Mouvement 1 : NFA de ""ab"" (jumeau Python) rejoué via le moteur']
lines1.append('Vecteur de tests (identique au jumeau) :')
for w in words1:
lines1.append(' %-6s -> %s' % (repr(w), 'Accepté' if nfa_ab.accepts_input(w) else 'Rejeté'))
import importlib.metadata as md
lines1.append('moteur : automata-lib %s sous CPython' % md.version('automata-lib'))
summary1 = chr(10).join(lines1)
# ---------- Mouvement 2 : DFA nombre pair de a + complément PAR LE MOTEUR ----------
dfa_even_a = DFA(
states={'q_even', 'q_odd'}, input_symbols={'a', 'b'},
transitions={'q_even': {'a': 'q_odd', 'b': 'q_even'}, 'q_odd': {'a': 'q_even', 'b': 'q_odd'}},
initial_state='q_even', final_states={'q_even'})
words2 = ['', 'a', 'aa', 'aaa', 'b', 'ab', 'aba', 'bab']
verdicts = {w: dfa_even_a.accepts_input(w) for w in words2}
comp = dfa_even_a.complement()
inverses = {w: comp.accepts_input(w) for w in words2}
ok = all(verdicts[w] != inverses[w] for w in words2)
lines2 = ['Mouvement 2 : DFA nombre pair de a (jumeau Python) + complement() du moteur']
lines2.append('Vecteur de tests (identique au jumeau) :')
for w in words2:
lines2.append(' %-6s -> %s' % (repr(w), 'Pair' if verdicts[w] else 'Impair'))
lines2.append('complement() : inversion vérifiée sur les %d mots -> %s' % (len(words2), ok))
summary2 = chr(10).join(lines2)
# ---------- Mouvement 3 : les opérations que la Tranche 1 from-scratch n'implémente pas ----------
det = DFA.from_nfa(nfa_ab)
mini = det.minify()
sd = mini.symmetric_difference(det)
preservation = sd.isempty()
u = dfa_even_a.union(mini)
i_ = dfa_even_a.intersection(mini)
temoins = [('ab', 'pair? non | dans le langage? oui'), ('aa', 'pair? oui | dans le langage? non'), ('ba', 'pair? non | dans le langage? non'), ('a', 'pair? non | dans le langage? non')]
sub = DFA.from_substring(input_symbols={'a','b'}, substring='ab')
sd2 = mini.symmetric_difference(sub)
equiv_contient_ab = sd2.isempty()
lines3 = ['Mouvement 3 : opérations natives du moteur (Prong B)']
lines3.append('from_nfa : NFA %d états -> DFA %d états ; minify -> %d états' % (len(nfa_ab.states), len(det.states), len(mini.states)))
lines3.append('minify préserve le langage (symmetric_difference vide) : %s' % preservation)
lines3.append('')
lines3.append('union / intersection natives (témoins) :')
for w, attendu in temoins:
lines3.append(' %-4s union=%-5s intersection=%-5s (%s)' % (repr(w), u.accepts_input(w), i_.accepts_input(w), attendu))
lines3.append('')
lines3.append('contraste mesuré : from_substring(ab) vs le NFA -- langages équivalents = %s' % equiv_contient_ab)
lines3.append(' témoin bab : from_substring accepte=%s, NFA accepte=%s' % (sub.accepts_input('bab'), nfa_ab.accepts_input('bab')))
lines3.append(' -> le NFA exige au moins un a avant le premier b : son langage est a+ . b . (a|b)*, pas contient-ab')
summary3 = chr(10).join(lines3)
summary4 = 'Objets vivants dans le scope moteur : nfa_ab.accepts_input(aab) = %s, accepts_input(bab) = %s' % (nfa_ab.accepts_input('aab'), nfa_ab.accepts_input('bab'))");
Console.WriteLine(scope.Get<string>("summary1"));
Console.WriteLine();
Console.WriteLine(scope.Get<string>("summary2"));
Console.WriteLine();
Console.WriteLine(scope.Get<string>("summary3"));
Console.WriteLine();
Console.WriteLine(scope.Get<string>("summary4"));
Console.WriteLine("--> le C# pilote le moteur par l'API du scope (Exec + Get<string>) : le pont est une API, pas un eval aveugle.");
}
PythonEngine.Shutdown();