How to use igraph python's metamagic class? - python

The python interface of igraph has a class called metamagic, serving the purpose to collect graphical parameters for plotting. I am writing a module using igraph, and I almost started to write my own wrapper functions for this purpose, when I've found metamagic in the documentation. But after searching and trying, it's still not clear how to use these classes. If I define an AttributeCollectorBase class for edges, like this:
class VisEdge(igraph.drawing.metamagic.AttributeCollectorBase):
width = 0.002
color = "#CCCCCC44"
Then, is there an easy way to pass all these parameters to the igraph.plot() function? Or I can only do one by one, like this: plot(graph,edge_color=VisEdge(graph.es).color)?
And what if I would like to use not constant parameters, but calculate by a custom function? For example, vertex_size proportional to degree. The func parameter of the AttributeSpecification class supposed to do this, isn't it? But I haven't seen any example how to use it. If I define an AttributeSpecification instance, like this:
ds = igraph.drawing.metamagic.AttributeSpecification(name="vertex_size",alt_name="size",default=2,func='degree')
After how to pass it to an AtributeCollector, and finally to plot()?

(To put things in context: I am the author of the Python interface of igraph).
I'm not sure whether the metamagic package is the right tool for you. The only purpose of the AttributeCollectorBase class is to allow the vertex and edge drawers in igraph (see the igraph.drawing.vertex and igraph.drawing.edge packages) to define what vertex and edge attributes they are able to treat as visual properties in a nice and concise manner (without me having to type too much). So, for instance, if you take a look at the DefaultVertexDrawer class in igraph.drawing.vertex, you can see that I construct a VisualVertexBuilder class by deriving it from AttributeCollectorBase as follows:
class VisualVertexBuilder(AttributeCollectorBase):
"""Collects some visual properties of a vertex for drawing"""
_kwds_prefix = "vertex_"
color = ("red", self.palette.get)
frame_color = ("black", self.palette.get)
frame_width = 1.0
...
Later on, when the DefaultVertexDrawer is being used in DefaultGraphDrawer, I simply construct a VisualVertexBuilder as follows:
vertex_builder = vertex_drawer.VisualVertexBuilder(graph.vs, kwds)
where graph.vs is the vertex sequence of the graph (so the vertex builder can get access to the vertex attributes) and kwds is the set of keyword arguments passed to plot(). The vertex_builder variable then allows me to retrieve the calculated, effective visual properties of vertex i by writing something like vertex_builder[i].color; here, it is the responsibility of the VisualVertexBuilder to determine the effective color by looking at the vertex and checking its color attribute as well as looking at the keyword arguments and checking whether it contains vertex_color.
The bottom line is that the AttributeCollectorBase class is likely to be useful to you only if you are implementing a custom graph, vertex or edge drawer and you want to specify which vertex attributes you wish to treat as visual properties. If you only want to plot a graph and derive the visual properties of that particular graph from some other data, then AttributeCollectorBase is of no use to you. For instance, if you want the size of the vertex be proportional to the degree, the preferred way to do it is either this:
sizes = rescale(graph.degree(), out_range=(0, 10))
plot(graph, vertex_size=sizes)
or this:
graph.vs["size"] = rescale(graph.degree(), out_range=(0, 10))
plot(g)
If you have many visual properties, the best way is probably to collect them into a dictionary first and then pass that dictionary to plot(); e.g.:
visual_props = dict(
vertex_size = rescale(graph.degree(), out_range=(0, 10)),
edge_width = rescale(graph.es["weight"], out_range=(0, 5), scale=log10)
)
plot(g, **visual_props)
Take a look at the documentation of the rescale function for more details. If you want to map some vertex property into the color of the vertex, you can still use rescale to map the property into the range 0-255, then round them to the nearest integer and use a palette when plotting:
palette = palettes["red-yellow-green"]
colors = [round(x) for x in rescale(g.degree(), out_range=(0, len(palette)-1))]
plot(g, vertex_color=colors, palette=palette)

Related

Python: igraph - set vertexseq none value to a specific one

I want to learn more about igraph libary. I want to make a simple graph with two nodes labeled at different time. In igraph tutorial I have found its possible to label the list at all after I generated the graph. It works with code:
from igraph import *
p = Graph()
p.add_vertices(1)
p.vs["label"] = ["test", "test1"]
layout = p.layout("kk")
plot(p, layout=layout)
but what, if I want to label the graph step by step. That means I first want add some vertices then label it. Then perform some calculation add some other vertices and label that other on after I added it without loosing the first ones. I just tried it with following code, but for some reason it does not work as I want. How can I solve it?
from igraph import *
p = Graph()
p.add_vertices(1)
p.vs["label"] = ["test"]
p.add_vertices(1)
p.vs["label"][1] = "test1"
layout = p.layout("kk")
plot(p, layout=layout)
g.vs["label"][1] = "test1" essentially boils down to this:
labels = g.vs["label"]
labels[1] = "test1"
Now, g.vs["label"] extracts the label attribute of all the vertices, constructs a Python list, fills the attributes into the list and then returns the list to you. If you run type(g.vs["label"]), you can see that labels is an ordinary Python list. Since it is not a view into the internal data structure, any modifications that you make to the list will not be reflected in the graph itself.
On the other hand, g.vs[1]["label"] = "test1" will do something like this:
vertex = g.vs[1]
vertex["label"] = "test1"
and this will work because g.vs[1] is a Vertex object (you can check it with type(g.vs[1]), and using the Vertex object as a dictionary will update the underlying attributes.
Footnote: in theory, g.vs["label"] could return some kind of a proxy object that represents the "label" attribute of each vertex and that writes any changes back to the underlying graph. However, it has not been implemented this way in earlier versions of the igraph Python interface and we cannot change it now without potentially breaking code of people who rely on the fact that g.vs["label"] returns a copy.

Extend/wrap an object ad-hoc with more functionality

The following question adresses a problem I often encounter. Basically, there are solutions like the adaptor pattern, but I find it a bit unsatisfying.
Suppose I have a class Polygon which implements an - uhm - polygon with quite some functionality. Many of those Polygon live in my program, some as lonely variables, some in collection structures.
Now, there's a function that needs an argument type that is basically a Polygon, but with some additional features. Let's say, a Polygon who can return some metrics: his volume, center of gravity, and angular mass. Plus, the function also needs the methods of the original Polygon.
A first idea is:
class Polygon:
# defines my polygon
class PolygonWithMetrics(Polygon):
# - extends polygon with the metrics
# - takes Polygon as argument upon construction
# - would need to delegate many functions to Polygon
def functionUsingPolygonWithMetrics(p):
# use functions of Polygon and PolygonWithMetrics
# driving code:
p = Polygon(some args here)
... more code ...
p_with_metrics = PolygonWithMetrics(p) # Bummer - problem here...
functionUsingPolygonWithMetrics(p_with_metrics)
The problem: It would require to delegate many many functions from PolygonWithMetrics into the original Polygon.
A second idea is:
class Polygon:
# defines my polygon
class PolygonMetrics:
# takes a polygon and provides metrics methods on it
def functionUsingPolygonWithMetrics(p):
# use functions of Polygon and PolygonMetrics
# driving code:
p = Polygon(some args here)
... more code ...
p_with_metrics = PolygonMetrics(p)
functionUsingPolygonWithMetrics(p, p_with_metrics) # Bummer - problem here...
This idea takes the original Polygon as an argument, plus a second object that provides the metrics functions. The problem is that I would need to change the signature of functionUsingPolygonWithMetrics.
What I would really need is an idea how to extend an existing object ad-hoc with some more functionality, without the problems given in idea 1 and 2.
I could imagine an idea roughly like this, where the job is mostly done by PolygonWithMetrics:
class Polygon:
# defines my polygon
class PolygonWithMetrics(maybe inherits something):
# - takes a Polygon and provides metrics methods on it
# - upon construction, it will take a polygon
# - will expose the full functionality of Polygon automatically
def functionUsingPolygonWithMetrics(p):
# use functions of Polygon and PolygonWithMetrics
# driving code:
p = Polygon(some args here)
... more code ...
p_with_metrics = PolygonWithMetrics(p)
functionUsingPolygonWithMetrics(p)
Three questions arise:
Does this pattern have sort of a name?
Is it a good idea, or should I resort to some more adviced techniques?
How to do it in Python?

Getting (t, c, k) values from OpenCascade surfaces

I've created a library for creating and using b-spline surfaces in Python, utilizing parallel scipy.interpolate.RectBivariateSpline() instances to hold the knot vectors, (X, Y, Z) control point mesh, and degrees in u and v (the (t, c, k) tuple against which surface evaluation is performed). I also wrote a STEP parser to read surface data exported from CAD packages; I take the (t, c, k) values from the b_spline_surface_with_knots entities in the file and stuff them into my own objects. The surface library works pretty well for me, but the STEP parser is a pain and fails in various ways almost every time I use it. So I've tried using a 'real' STEP parser, like this:
from OCC.STEPControl import STEPControl_Reader
from OCC.IFSelect import IFSelect_RetDone, IFSelect_ItemsByEntity
step_reader = STEPControl_Reader()
status = step_reader.ReadFile('c:/LPT/nomdata/lpt3.stp')
if status == IFSelect_RetDone: # check status
failsonly = False
step_reader.PrintCheckLoad(failsonly, IFSelect_ItemsByEntity)
step_reader.PrintCheckTransfer(failsonly, IFSelect_ItemsByEntity)
ok = step_reader.TransferRoot(1)
_nbs = step_reader.NbShapes()
aResShape = step_reader.Shape(1)
else:
print("Error: can't read file.")
sys.exit(0)
Now I have this aResShape object, but no amount of poking and prodding it in IPython (nor googling) reveals how to get at the (t, c, k) values that define the surface.
Can someone please point me to the method that will reveal these values? Or is there possibly another Python-based STEP parser that's a little less opaque?
The question is a bit old, but just in case anybody else hits here with a similar problem...
The result of step_reader.Shape() is a TopoDS_Shape, which is a topological entity which can be divided into the following component topologies:
Vertex – a zero-dimensional shape corresponding to a point in geometry;
Edge – a shape corresponding to a curve, and bound by a vertex at each extremity;
Wire – a sequence of edges connected by their vertices;
Face – part of a plane (in 2D geometry) or a surface (in 3D geometry) bounded by a closed wire;
Shell – a collection of faces connected by some edges of their wire boundaries;
Solid – a part of 3D space bound by a shell;
Compound solid – a collection of solids.
Tipically, you'd query it with the method TopoDS_Shape::ShapeType() in order know what is that shape (vertex? edge?, ...).
If the model is formed by a single b-spline surface, the shape then should be a TopoDS_Face, that you can get by calling:
face = aResShape.Face();
Once you have the TopoDS_Face at hand, you can get the underlying geometry (Geom_Surface) like this:
surface = BRepAdaptor_Surface(face).Surface().BSpline();
Now that you have had access to the underlying geometry, you can call this object's methods and they will provide you with the information you need.
They are documented here:
https://www.opencascade.com/doc/occt-7.1.0/refman/html/class_geom___b_spline_surface.html
OpenCASCADE documentation may seem confusing, but I think you might be interested on this topic:
https://www.opencascade.com/doc/occt-7.0.0/overview/html/occt_user_guides__modeling_data.html#occt_modat_3
Hope it helps.

OOP Programming style decision

I just have a quick question about an OOP programming I've been having difficulty deciding. The premise is that I'm making a set of very simple geometric classes such as vertex and angle and vector objects, but one of the classes, the line class to be specific, is a little different. It's basically just a collection of methods that I use one time only, I never actually save a line object for later use or recollection of data anywhere else in the program. An example usage to demonstrate my point would be this:
class Line:
def __init__(self, vertex1, vertex2):
self.start = vertex1
self.end = vertex2
def to_the_left(self, vertex):
"""Check to see if a vertex is to the left of the line segment."""
#code stuff
data = Line(Vertex(0, 0), Vertex(10, 0)).to_the_left(Vertex(5, 5))
I only ever instantiate Line(Vertex(0, 0), Vertex(10, 0)) once to retrieve the data. So I was thinking that I might as well just have a bunch of functions available instead of packing it all into a class, but then I was skeptical about doing that since there are a ton of methods that would have to be converted to functions.
Another thing I was thinking of doing was to make a Line class and then convert all it's methods into normal functions like so:
#continuing from the code above
def to_the_left(line_start, line_end, vertex):
return Line(line_start, line_end).to_the_left(vertex)
data = to_the_left(Vertex(0, 0), Vertex(10, 0), Vertex(5, 5))
Which method do you think I should use?
I would opt for using an object as you might need to do multiple operations on Line.
For example you might compute the length, if it's to the left, and some other operation. You might need to pass the Line around who knows.
One thing you might want to consider is instead of using Line and Vertex, use Vector which acts as both. If your vertex has x,y you can make a Vector that has x,y,w.
In this scheme w=1 for vertices and w=0 for Lines - it would simplify a lot of code.
Look up Homogenous coordinates to learn more

What is the difference between object and node in Maya (scripting)?

This question may sound silly I know, but I would like to know exactly what is the difference and in which way the hierarchy works in their case.
If I create for instance a polyCylinder and bind it to a variable
exVar = cmds.polyCylinder(name='cylinder_01')
And now I print exVar, I get a list with two Unicode string items: one for the name of the object and another one for the name of the node.
[u'cylinder_01', u'polyCylinder1']
If I go to the Outliner I just can see cylinder_01, I cannot see the polyCylinder1 item.
What do they mean? Is there any way to visualize them in the Outliner or the Hypergraph?
Thanks in advance.
cylinder_01 is the transform, which handles translation, rotation, scale, etc.
polyCylinder1 is the shape, which holds the vertices, polygons, shader connections, etc.
The shape is parented to the transform. You can see it in the Outliner if you select Display > Shapes

Categories