Executing code in v.5.2 kernel from within v.7.01 session through MathLink

浪子不回头ぞ 提交于 2019-11-27 14:30:46

Here is an implementation based on Simon's code. It still requires improvement. The one unclear thing to me is how to handle Messages generated in the slave (v.5.2) kernel.

Here is my code:

Clear[linkEvaluate]
SetAttributes[linkEvaluate, HoldRest]
linkEvaluate[link_LinkObject, expr_] := Catch[
   Module[{out = {}, postScript = {}, packet, outputs = {}},
    While[LinkReadyQ[link], 
     Print["From the buffer:\t", LinkRead[link]]];
    LinkWrite[link, Unevaluated[EnterExpressionPacket[expr]]];
    While[Not@MatchQ[packet = LinkRead[link], InputNamePacket[_]],
     Switch[packet,
      DisplayPacket[_], AppendTo[postScript, First@packet],
      DisplayEndPacket[_], AppendTo[postScript, First@packet]; 
      CellPrint@
         Cell[GraphicsData["PostScript", #], "Output", 
          CellLabel -> "Kernel 5.2 PostScript ="] &@
       StringJoin[postScript]; postScript = {},
      TextPacket[_], 
      If[StringMatchQ[First@packet, 
        WordCharacter .. ~~ "::" ~~ WordCharacter .. ~~ ": " ~~ __], 
       CellPrint@
        Cell[BoxData@
          RowBox[{StyleBox["Kernel 5.2 Message = ", 
             FontColor -> Blue], First@packet}], "Message"], 
       CellPrint@
        Cell[First@packet, "Output", CellLabel -> "Kernel 5.2 Print"]],
      OutputNamePacket[_], AppendTo[outputs, First@packet];,
      ReturnExpressionPacket[_], AppendTo[outputs, First@packet];,
      _, AppendTo[out, packet]
      ]
     ];
    If[Length[out] > 0, Print[out]];
    Which[
     (l = Length[outputs]) == 0, Null,
     l == 2, Last@outputs,
     True, multipleOutput[outputs]
     ]
    ]];
Clear[kernel5Evaluate]
SetAttributes[kernel5Evaluate, HoldAll]
kernel5Evaluate[expr_] := 
 If[TrueQ[MemberQ[Links[], $kern5]], linkEvaluate[$kern5, expr], 
  Clear[$kern5]; $kern5 = LinkLaunch[
    "C:\\Program Files\\Wolfram Research\\Mathematica\\5.2\\MathKernel.exe -mathlink"]; 
  LinkRead[$kern5]; 
  LinkWrite[$kern5, 
   Unevaluated[EnterExpressionPacket[$MessagePrePrint = InputForm;]]];
   LinkRead[$kern5]; kernel5Evaluate[expr]]

Here are test expressions:

plot = kernel5Evaluate[Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi}]]
plot = kernel5Evaluate[Plot[Sin[x], {x, 0, Pi}]; Plot[Sin[x], {x, -Pi, Pi}]] // 
  DeleteCases[#, HoldPattern[DefaultFont :> $DefaultFont], Infinity] &
s = kernel5Evaluate[
  NDSolve[{y'[x] == y[x] Cos[x + y[x]], y[0] == 1}, y, {x, 0, 30}]]
s // InputForm // Short
kernel5Evaluate[1/0; Print["s"];]

It seems to work as expected. However it could be better...

Here's my attempt at what you want,

First I define linkEvaluate that takes an active Link and passes it an expression. If there's things for LinkRead still to read, then it reads them until there are no more. Then it writes the expression and waits for the results to come back. Then it reads the output until there's nothing left to read. Normally, it then returns the first ReturnExpressionPacket unless you have set the final, optional argument, all, to True - in which case it returns everything it read.

Clear[linkEvaluate]
SetAttributes[linkEvaluate, HoldRest]
linkEvaluate[link_LinkObject, expr_, all : (True | False) : False] := 
  Catch[Module[{out = {}},
    While[LinkReadyQ[link], PrintTemporary[LinkRead[link]]];
    If[LinkReadyQ[link], Throw["huh"]];
    LinkWrite[link, Unevaluated[EnterExpressionPacket[expr]]];
    While[! LinkReadyQ[link], Pause[.1]];
    While[LinkReadyQ[link], AppendTo[out, LinkRead[link]]];
    If[all, out, Cases[out, _ReturnExpressionPacket][[1, 1]]]
    ]];

Then kernel5Evaluate first checks if the global $kern5 is defined as a LinkObject, if not then it defines it. It then simply passes the work over to linkEvaluate. You will have to replace "math5" with the filename and path of your Mma 5.2 kernel.

Clear[kernel5Evaluate]
SetAttributes[kernel5Evaluate, HoldAll]
kernel5Evaluate[expr_, all:(True|False):False] := If[TrueQ[MemberQ[Links[], $kern5]], 
  linkEvaluate[$kern5, expr, all], 
  Clear[$kern5]; $kern5 = LinkLaunch["math5 -mathlink"]; kernel5Evaluate[expr,all]
  ]

Here is working implementation of what I wanted. I have added checking for a dead MathLink connection as suggested by Todd Gayley here. Now kernel5Evaluate works reliable even if the slave kernel was terminated in unusual way. I also have much improved parsing of Messages and added some diagnostic messages for kernel5Evaluate. Here is the code:

$kern5Path = "C:\\Program Files\\Wolfram Research\\Mathematica\\5.2\\MathKernel.exe";

Clear[printMessage, printPrint, printPostScript]
printMessage[str_String] := 
  CellPrint@
   Cell[BoxData[
     RowBox[StringSplit[str, 
        x : ("MyDelimeterStart" | "MyDelimeterEnd") :> x] //. {x___, 
         "MyDelimeterStart", y_, "MyDelimeterEnd", z___} :> {x, 
         ToExpression[y], z}]], "Message", 
    CellLabel -> "(Kernel 5.2)", ShowCellLabel -> True];
printPostScript = 
  CellPrint@
    Cell[GraphicsData["PostScript", #], "Graphics", 
     CellLabel -> "(Kernel 5.2 PostScript)", ShowCellLabel -> True] &;
printPrint[str_String] := 
  CellPrint@
   Cell[If[StringTake[str, -1] === "\n", StringDrop[str, -1], str], 
    "Print", CellLabel -> "(Kernel 5.2 print, text mode)", 
    ShowCellLabel -> True];

Clear[linkEvaluate]
SetAttributes[linkEvaluate, HoldAllComplete]
linkEvaluate[link_LinkObject, expr_] := Catch[
   Module[{out = {}, postScript = {}, packet, result = Null},
    If[LinkReadyQ[link], 
     While[LinkReadyQ[link], 
      Print["Rest of the buffer:\t", 
       packet = LinkRead[link, Hold]]];
     If[Not@MatchQ[packet, Hold[InputNamePacket[_]]], 
      Message[kernel5Evaluate::linkIsBusy]; Throw[$Failed]]];
    LinkWrite[link, Unevaluated[EnterExpressionPacket[expr]]];
    While[
     Check[Not@
       MatchQ[packet = LinkRead[link, Hold], 
        Hold[InputNamePacket[_]]], 
      Message[kernel5Evaluate::linkIsClosed]; Throw[$Failed]],
     Switch[packet,
      Hold@DisplayPacket[_String], 
      AppendTo[postScript, First@First@packet],
      Hold@DisplayEndPacket[_String], 
      AppendTo[postScript, First@First@packet]; 
      printPostScript@StringJoin[postScript]; postScript = {},
      Hold@MessagePacket[__], ,
      Hold@TextPacket[_String], 
      If[StringMatchQ[First@First@packet, 
        WordCharacter .. ~~ "::" ~~ WordCharacter .. ~~ ": " ~~ __], 
       printMessage[First@First@packet], 
       printPrint[First@First@packet]],
      Hold@OutputNamePacket[_], ,
      Hold@ReturnExpressionPacket[_], result = First[First[packet]],
      _, AppendTo[out, packet]
      ]
     ];
    If[Length[out] > 0, Print["Unparsed packets: ", out]];
    result
    ]];
Clear[kernel5Evaluate]
SetAttributes[kernel5Evaluate, HoldAllComplete]
kernel5Evaluate::usage = "kernel5Evaluate[\!\(\*
StyleBox[\"expr\",\nFontFamily->\"Times New Roman\",\n\
FontSlant->\"Italic\"]\)] writes \!\(\*
StyleBox[\"expr\",\nFontFamily->\"Times New Roman\",\n\
FontSlant->\"Italic\"]\) to MathKernel 5.2, returns output and prints \
messages generated during computation.";
kernel5Evaluate::linkIsBusy = 
  "Kernel 5.2 is still running previous calculation.";
kernel5Evaluate::linkIsClosed = "Connection to Kernel 5.2 is lost.";
kernel5Evaluate::kernel5NotFound = 
  "Path `1` not found. Please set variable $kern5Path to correct path \
to MathKernel 5.2.";
kernel5Evaluate[expr_] :=
 If[TrueQ[MemberQ[Links[], $kern5]],
  If[LinkReadyQ[$kern5]; First[LinkError[$kern5]] == 0, 
   With[{$kern5 = $kern5}, linkEvaluate[$kern5, expr]], 
   LinkClose[$kern5]; kernel5Evaluate[expr]],
  Clear[$kern5];
  If[FileExistsQ[$kern5Path],
   $kern5 = LinkLaunch[$kern5Path <> " -mathlink -noinit"]; 
   LinkRead[$kern5]; LinkWrite[$kern5,
    Unevaluated[
     EnterExpressionPacket[$MessagePrePrint = ("MyDelimeterStart" <> 
          ToString[ToBoxes[#]] <> "MyDelimeterEnd") &; 
      SetOptions[$Output, {PageWidth -> Infinity}];]]]; 
   LinkRead[$kern5]; kernel5Evaluate[expr], 
   Message[kernel5Evaluate::kernel5NotFound, $kern5Path]; $Failed]
  ]

And here are some test expressions:

kernel5Evaluate[Unevaluated[2 + 2]]
kernel5Evaluate[$Version]
kernel5Evaluate[Quit[]]
kernel5Evaluate[Print["some string"];]
kernel5Evaluate[Sin[1,]]
kernel5Evaluate[1/0]

kernel5Evaluate[{Plot[Sin[x], {x, 0, Pi}], 
   Plot[Sin[x], {x, -Pi, Pi}]}] // 
 DeleteCases[#, HoldPattern[DefaultFont :> $DefaultFont], Infinity] &

kernel5Evaluate[Plot3D[Sin[x y], {x, 0, Pi}, {y, 0, Pi}]];
ListPlot3D[First@%, Mesh -> Full, DataRange -> MeshRange /. Last[%]]

s = kernel5Evaluate[
  NDSolve[{y'[x] == y[x] Cos[x + y[x]], y[0] == 1}, y, {x, 0, 30}]]
% // InputForm // Short

kernel5Evaluate[ContourPlot[Sin[x y], {x, -5, 5}, {y, -5, 5}]];
ListContourPlot[First@%, DataRange -> MeshRange /. Last[%], 
 Contours -> 10, 
 Method -> {"Refinement" -> {"CellDecomposition" -> "Quad"}}]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!