0

This is a follow up to my previous post here

I am trying to run a function from Python using wolframclient. The input arguments are passed from Python as Python datatypes.

The following is saved in test.m file (for Mathematica script please check)

solutioncheck[edges_, vd_, vl_, ew_] := (
edges = UndirectedEdge @@@ edges; vcoords = List @@ vd;
ew = Normal @ KeyMap[UndirectedEdge @@ # &,ew];

g3d = Graph3D[vl, edges, VertexCoordinates -> vcoords, EdgeWeight->ew, VertexLabels->Placed["Name",Center],
EdgeLabels->{e_:>Placed["EdgeWeight",Center]}, VertexSize->.5, BaseStyle->16];


vars3d = Array[Through[{x, y, z}@#] &, Length@vd];
\[Lambda] = 1/100.; lbnd = 0; ubnd = 500;

obj3d = Total[(Norm[vars3d[[First@#]] - vars3d[[Last@#]]] - # /. (Rule @@@ ew))^2 & /@ EdgeList[g3d]] + \[Lambda] * Total[Norm /@ (vars3d - Values@vd)];
solution3d = Last @ Minimize[{obj3d, And @@ Thread[lbnd <= Join @@ vars3d <= ubnd]}, Join @@ vars3d];

edgeLengths3d = # -> Norm[vars3d[[First@#]] - vars3d[[Last@#]]] /.solution3d & /@ EdgeList[g3d];
ResourceFunction["PrettyGrid"][{#, # /. ew, # /. edgeLengths3d} & /@EdgeList[g3d],  "ColumnHeadings" -> {"edge", "EdgeWeight", "Edge Length"}];

z1 = Values[solution3d] // Partition[#, 3] &;

theFile = File["result.txt"];

Export[theFile, z1, "Table"];

)

Using wolframclient library in Python, I tried

    from wolframclient.evaluation import WolframLanguageSession
    from wolframclient.language import wl, wlexpr, Global
session = WolframLanguageSession()

edges = [(1, 2), (1, 3), (1, 4), (2, 5), (2, 6), (5, 6), (3, 4), (3, 7), (6, 7), (7, 8), (2, 9)]
vl = [1, 2, 3, 4, 5, 6, 7, 8, 9]
ew = {(1, 2): 49.6, (1, 3): 74.4, (1, 4): 49.6, (2, 5): 37.2, (2, 6): 74.4, (5, 6): 49.6, (3, 4): 37.2, (3, 7): 24.8, (6, 7): 62, (7, 8): 37.2, (2, 9): 24.8}
vd = {1: [75., 25., 0], 2: [115., 45., 0], 3: [10., 5., 0], 4: [45., 0, 0], 5: [90., 60., 0], 6: [45., 55., 0], 7: [0, 25., 0], 8: [10., 50., 0], 9: [115., 25., 0]}
with WolframLanguageSession() as s:
    s.evaluate(wl.Get('test.m'))
    s.evaluate(Global.solutioncheck(edges, vd, vl, ew))

I am not sure what's wrong in the above, the run doesn't terminate.

Could someone please look into this?

EDIT: For some reason, I think the graph object isn't created

I get the following message when I Print(ew)

A graph object is expected at position 1 in EdgeList[Power[Plus[Times[-1, g3d], Norm[Plus[Part[{{x[1], y[1], z[1]}, {x[2], y[2], z[2]}, {x[3], y[3], z[3]}, {x[4], y[4], z[4]}, {x[5], y[5], z[5]}, {x[6], y[6], z[6]}, {x[7], y[7], z[7]}, {x[8], y[8], z[8]}, {x[9], y[9], z[9]}}, First[g3d]], Times[-1, Part[{{x[1], y[1], z[1]}, {x[2], y[2], z[2]}, {x[3], y[3], z[3]}, {x[4], y[4], z[4]}, {x[5], y[5], z[5]}, {x[6], y[6], z[6]}, {x[7], y[7], z[7]}, {x[8], y[8], z[8]}, {x[9], y[9], z[9]}}, Last[g3d]]]]]], 2]].
Tag Times in Times[500, obj3d, Print] is Protected.
Nonatomic expression expected at position 1 in First[g3d].
The expression First[g3d] cannot be used as a part specification.
Further output of MessageName[Part, pkspec1] will be suppressed during this calculation.
Nonatomic expression expected at position 1 in Last[g3d].
The expression Last[g3d] cannot be used as a part specification.
The expression First[g3d] cannot be used as a part specification.
The expression Last[g3d] cannot be used as a part specification.
A graph object is expected at position 1 in EdgeList[g3d -> Norm[Plus[Part[{{250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}}, First[g3d]], Times[-1, Part[{{250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}, {250, 250, 250}}, Last[g3d]]]]]].
Tag Times in Times[edgeLengths3d, Print, {x[1] -> 250, y[1] -> 250, z[1] -> 250, x[2] -> 250, y[2] -> 250, z[2] -> 250, x[3] -> 250, y[3] -> 250, z[3] -> 250, x[4] -> 250, y[4] -> 250, z[4] -> 250, x[5] -> 250, y[5] -> 250, z[5] -> 250, x[6] -> 250, y[6] -> 250, z[6] -> 250, x[7] -> 250, y[7] -> 250, z[7] -> 250, x[8] -> 250, y[8] -> 250, z[8] -> 250, x[9] -> 250, y[9] -> 250, z[9] -> 250}] is Protected.
{edgeLengths3d} is neither a list of replacement rules nor a valid dispatch table, and so cannot be used for replacing.
A graph object is expected at position 1 in EdgeList[{g3d, g3d, ReplaceAll[g3d, edgeLengths3d]}].
Further output of MessageName[EdgeList, graph] will be suppressed during this calculation.
A graph object is expected at position 1 in EdgeList[{Item[edge, Alignment -> {Center, Baseline}, BaseStyle -> Bold, ItemSize -> {Automatic, 1.5}], Item[EdgeWeight, Alignment -> {Center, Baseline}, BaseStyle -> Bold, ItemSize -> {Automatic, 1.5}], Item[Edge Length, Alignment -> {Center, Baseline}, BaseStyle -> Bold, ItemSize -> {Automatic, 1.5}]}, {g3d, g3d, ReplaceAll[g3d, edgeLengths3d]}].
Natasha
  • 359
  • 1
  • 10
  • Add some Prints to your .m file. It then produces some output. First argument ... is not a string. Your edges, vd ,vl, ew are already in list/association forms at the point of calling the function so you don't need ImportString and you can remove the entire line {edges,vl,ew,vd}=ImportString[#,"PythonExpression"]&/@{edges,vl,ew,vd};. – flinty Sep 29 '20 at 11:00
  • 1
    Your Mathematica code doesn't really make sense. You have solution3d appearing on a line starting with edgeLengths3d = ... as a replacement rule. Yet you haven't even defined solution3d yet, which appears later on the line starting solution3d = Last @ Minimize[ .... Also it has the same name as the function it is in, so you should change that. Also edgeLengths3d gets defined twice. – flinty Sep 29 '20 at 11:10
  • @flinty Thanks a lot, I have made the corrections. The code doesn't terminate yet – Natasha Sep 29 '20 at 11:20
  • @flinty Thanks a lot, I have made the corrections. An output could be generated but there are issues. The output is not right. Please check my edit – Natasha Sep 29 '20 at 11:37

1 Answers1

2

I have rewritten your code somewhat. It now completes in python and it works in Mathematica. Please tell me if the outputs are reasonable.

solution3d[edges_, vd_, vl_, ew_] := Module[{
   uedges = UndirectedEdge @@@ edges,
   vcoords = List @@ vd,
   ew2 = KeyMap[UndirectedEdge @@ # &, ew],
   vars3d, \[Lambda], lbnd, ubnd, obj3d, err, sol, edgeLengths3d},

[Lambda] = 1/100.; lbnd = 0; ubnd = 500; vars3d = Array[Through[{x, y, z}@#] &, Length@vd];

obj3d = Total[ (EuclideanDistance[vars3d[[First[#]]], vars3d[[Last[#]]]] - ew2[#])^2 & /@ Keys[ew2] ] + [Lambda]*Total[ MapThread[EuclideanDistance[#1, #2] &, {vars3d, Values[vd]}] ];

{err, sol} = Quiet[ NMinimize[{obj3d, And @@ Thread[lbnd <= Join @@ vars3d <= ubnd]}, Flatten[vars3d]] ];

edgeLengths3d = # -> EuclideanDistance[ vars3d[[First@#]], vars3d[[Last@#]] ] /. sol & /@ Keys[ew2];

Export["result.txt", Partition[Values[sol], 3], "Table"]; Return[<|"Error" -> err, "Solution" -> sol, "EdgeLengths" -> edgeLengths3d|>]; ]

(* test it out *) edges = {{1, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}, {5, 6}, {3, 4}, {3, 7}, {6, 7}, {7, 8}, {2, 9}}; vl = {1, 2, 3, 4, 5, 6, 7, 8, 9}; ew = <|{1, 2} -> 49.6, {1, 3} -> 74.4, {1, 4} -> 49.6, {2, 5} -> 37.2, {2, 6} -> 74.4, {5, 6} -> 49.6, {3, 4} -> 37.2, {3, 7} -> 24.8, {6, 7} -> 62, {7, 8} -> 37.2, {2, 9} -> 24.8|>; vd = <|1 -> {75., 25., 0}, 2 -> {115., 45., 0}, 3 -> {10., 5., 0}, 4 -> {45., 0, 0}, 5 -> {90., 60., 0}, 6 -> {45., 55., 0}, 7 -> {0, 25., 0}, 8 -> {10., 50., 0}, 9 -> {115., 25., 0}|>;

solution3d[edges, vd, vl, ew]

(* result: ) <|"Error" -> 0.880709, "Solution" -> {x[1] -> 77.6922, y[1] -> 22.5731, z[1] -> 0.00182091, x[2] -> 121.007, y[2] -> 46.7324, z[2] -> 0.000518892, x[3] -> 6.80262, y[3] -> 0., z[3] -> 7.8815710^-7, x[4] -> 28.67, y[4] -> 30.0881, z[4] -> 0.0677469, x[5] -> 95.0311, y[5] -> 73.3522, z[5] -> 0.166862, x[6] -> 47.5991, y[6] -> 58.8693, z[6] -> 2.9265110^-6, x[7] -> 0., y[7] -> 20.865, z[7] -> 11.5351, x[8] -> 11.3995, y[8] -> 54.3391, z[8] -> 3.595810^-6, x[9] -> 114.484, y[9] -> 22.8077, z[9] -> 0.029674}, "EdgeLengths" -> {1 [UndirectedEdge] 2 -> 49.5968, 1 [UndirectedEdge] 3 -> 74.3967, 1 [UndirectedEdge] 4 -> 49.595, 2 [UndirectedEdge] 5 -> 37.194, 2 [UndirectedEdge] 6 -> 74.4045, 5 [UndirectedEdge] 6 -> 49.5941, 3 [UndirectedEdge] 4 -> 37.1952, 3 [UndirectedEdge] 7 -> 24.7928, 6 [UndirectedEdge] 7 -> 61.9923, 7 [UndirectedEdge] 8 -> 37.1957, 2 [UndirectedEdge] 9 -> 24.798}|>

As before, solution3d exports its results but also returns an Association which you can access from python as a dictionary:

from wolframclient.evaluation import WolframLanguageSession
from wolframclient.language import wl, wlexpr, Global

session = WolframLanguageSession()

edges = [(1, 2), (1, 3), (1, 4), (2, 5), (2, 6), (5, 6), (3, 4), (3, 7), (6, 7), (7, 8), (2, 9)] vl = [1, 2, 3, 4, 5, 6, 7, 8, 9] ew = {(1, 2): 49.6, (1, 3): 74.4, (1, 4): 49.6, (2, 5): 37.2, (2, 6): 74.4, (5, 6): 49.6, (3, 4): 37.2, (3, 7): 24.8, (6, 7): 62, (7, 8): 37.2, (2, 9): 24.8} vd = {1: [75., 25., 0], 2: [115., 45., 0], 3: [10., 5., 0], 4: [45., 0, 0], 5: [90., 60., 0], 6: [45., 55., 0], 7: [0, 25., 0], 8: [10., 50., 0], 9: [115., 25., 0]} with WolframLanguageSession() as s: s.evaluate(wl.Get('test.m')) result = s.evaluate(Global.solution3d(edges, vd, vl, ew)) ## This should be a dictionary ## of the form {'Error': err, 'Solution':(...), 'EdgeLengths':(...)}

print(result)

flinty
  • 25,147
  • 2
  • 20
  • 86
  • Hi @flinty Thank you so much Yes, the results are reasonable. Could you please explain why the line g3d = Graph3D[vl, edges, VertexCoordinates -> vcoords, EdgeWeight->ew, VertexLabels->Placed["Name",Center], EdgeLabels->{e_:>Placed["EdgeWeight",Center]}, VertexSize->.5, BaseStyle->16]; has been removed. In Mathematica this was useful to visualize the graph network. But I am not sure why the graph object isn't working while running from Python. I would like to know if there is a way to create the graph object and visualize the network while running via wolframclient. – Natasha Sep 29 '20 at 15:18
  • Because it's unnecessary inside a function unless you are returning it, which you are not. You were also suppressing the output with a semicolon so it wouldn't show up either. Also you're not going to get any visual outputs calling it from Python anyway because you can't spawn a front-end, just a headless kernel. If you want visual outputs while driving Mathematica from python, you should rasterize and export them as images. – flinty Sep 29 '20 at 15:34
  • Thank you very much for the clarification. If you want visual outputs while driving Mathematica from python, you should rasterize and export them as images. I guess this will help me in viewing the 3D graph as a 2D image. May I know if there is a way to view the 3D image? Will I be able to export as a html file and view the 3D? If it is possible, could you please provide references to some examples that show how to do this? – Natasha Sep 29 '20 at 15:42
  • 1
    From Python? Use this https://plotly.com/python/v3/3d-network-graph or this https://www.idtools.com.au/3d-network-graphs-python-mplot3d-toolkit . It's a lot of work. You'd probably be better off ditching python and just doing it all from within Mathematica, but you may have other reasons for using python. – flinty Sep 29 '20 at 15:44
  • But when I pass edges = [(1, 2), (1, 3), (1, 4), (2, 5), (2, 6), (5, 6), (3, 4), (3, 7), (6, 7), (7, 8), (2, 9)] vl = [1, 2, 3, 4, 5, 6, 7, 8, 9] ew = {(1, 2): 49.6, (1, 3): 74.4, (1, 4): 49.6, (2, 5): 37.2, (2, 6): 74.4, (5, 6): 49.6, (3, 4): 37.2, (3, 7): 24.8, (6, 7): 62, (7, 8): 37.2, (2, 9): 24.8} vd = {1: [75., 25., 0], 2: [115., 45., 0], 3: [10., 5., 0], 4: [45., 0, 0], 5: [90., 60., 0], 6: [45., 55., 0], 7: [0, 25., 0], 8: [10., 50., 0], 9: [115., 25., 0]} from Python the code doesn't run successfully. Could you please suggest how the input arguments have to be passed from Python? – Natasha Oct 04 '20 at 04:54
  • @Natasha But it does work with that input for me from python. You should copy just the function solution3d into test.m and remove everything else, not the rest. You have to wait ~20 seconds or so for your test.py to finish executing. What problem are you experiencing? I am using Python 3.8.3 and Mathematica 12.1.1.0 – flinty Oct 04 '20 at 13:05
  • Messages displayed in command line here, call here and function here Could you please have a look? – Natasha Oct 04 '20 at 14:08
  • You have Print in there but that first Tag Times... error means you've not ended each Print with a semicolon. Are you sure you've actually replaced your test.m file with my code above, or have you modified it? – flinty Oct 04 '20 at 14:13
  • 1
    What is this syntax? Print(uedges) and Print(ew2). That's not correct Mathematica syntax. You should write Print[uedges]; and Print[ew2]; with brackets and semicolons, not parentheses. – flinty Oct 04 '20 at 14:15
  • Ahh, that was my mistake. I failed to notice the square brackets in MMA syntax. The output is written successfully now. But from what I see using Print[] doesn't display the output in python . I'm not sure if there is a way to print these outputs in Python terminal. Also, could you please explain why solution3d = Last @ Minimize[{obj3d, And @@ Thread[lbnd <= Join @@ vars3d <= ubnd]}, Join @@ vars3d]; was changed to NMinimize ? Could you please suggest how to return err while using solution3d = Last @ Minimize[{obj3d, And @@ Thread[lbnd <= Join @@ vars3d <= ubnd]}, Join @@ vars3d]; ? – Natasha Oct 04 '20 at 14:28
  • 1
    You should be using NMinimize because it's a numerical problem. The error is already returned - note that I return an association: Return[<|"Error" -> err, "Solution" -> sol, "EdgeLengths" -> edgeLengths3d|>];. From Python please try this: result = s.evaluate(Global.solution3d(edges, vd, vl, ew)) print(result) and you will see that result contains a dictionary where you can get the err like this: result["Error"] – flinty Oct 04 '20 at 14:35