0
$\begingroup$

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]}].
$\endgroup$
  • $\begingroup$ 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};. $\endgroup$ – flinty Sep 29 '20 at 11:00
  • 1
    $\begingroup$ 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. $\endgroup$ – flinty Sep 29 '20 at 11:10
  • $\begingroup$ @flinty Thanks a lot, I have made the corrections. The code doesn't terminate yet $\endgroup$ – Natasha Sep 29 '20 at 11:20
  • $\begingroup$ @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 $\endgroup$ – Natasha Sep 29 '20 at 11:37
2
$\begingroup$

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.88157*10^-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.92651*10^-6, x[7] -> 0., y[7] -> 20.865,
    z[7] -> 11.5351, x[8] -> 11.3995, y[8] -> 54.3391, 
   z[8] -> 3.5958*10^-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)
$\endgroup$
  • $\begingroup$ 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. $\endgroup$ – Natasha Sep 29 '20 at 15:18
  • $\begingroup$ 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. $\endgroup$ – flinty Sep 29 '20 at 15:34
  • 1
    $\begingroup$ From Python? Use this plotly.com/python/v3/3d-network-graph or this 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. $\endgroup$ – flinty Sep 29 '20 at 15:44
  • 1
    $\begingroup$ 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. $\endgroup$ – flinty Oct 4 '20 at 14:15
  • 1
    $\begingroup$ 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"] $\endgroup$ – flinty Oct 4 '20 at 14:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.