Flip Matrix and Mass Addition translated to Python

I’m trying to move some very time consuming activities from grasshopper where they are single threaded out to an external Python with GHPythonRemote, but I’m not sure how to translate two components in Grasshopper… numpy.rot90() and numpy.add()…?

Any suggestion and help would be much appreciated

thanks
anders

@Anderssmith

Flip matrix is a transpose operation, so a value at row i and column j will be flipped across its diagonal to row j and column i:

FlipMatrix(a_{ij}) = a_{ji}

In numpy, this is typically done with the “T” attribute:

In [15]: a = np.arange(12).reshape(3, 4)

In [16]: a
Out[16]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [17]: a.T
Out[17]:
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

Mass addition is just the cumulative sum of all the elements in your array:

MassAddition(a) = \sum_{i=1}^{n} a_i

In numpy, this is the sum method (not the np.cumsum method!):

In [18]: a = np.arange(12)

In [19]: a
Out[19]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

In [20]: a.sum()
Out[20]: 66

When you do the GH to matrix/array translations, you may need to modify these operations slightly to account for multidimensional data, and ragged arrays.

You will have to deal with multidimensional data if the GH component “branches” the input data to maintain data that is nested multiple times. Numpy’s matrix operations works by default on 2D matrices, so if you have a set of GH branch each containing a list, or a list of lists, then you don’t have to worry about this. But if you nesting data beyond this, then you may have to specify along which axis the operation occurs. So, if you have a GH branch of 2D matrices, you have to specify if each matrix is transposed, or if whole thing is transposed along it’s 3D matrix diagonal. For most operations, you can do this by specifying the “axis” argument in numpy:

Ragged arrays refers to a nested data that don’t have a consistent size. In GH branches and plain python lists, you can nest (for example) a set of lists where each list has a different lenght. So for example, if you represent your geometry as a set of polygons, and the polygons don’t have a fixed number of vertices, you’ll run into this issue. Matrices don’t work with this kind of structure, so numpy will complain when you try to convert such lists into arrays. It’ll still turn it into an array, but will consider your values as np.objects, and not as numbers, and matrix operations won’t work. I think the easiest way to solve this is by padding your data with np.nan or zero values, or by flattening high dimensional data to (at minimum) 2D matrices.

@SaeranVasanthakumar
Thank you!

I can see how numpy.tranform() does the flipping and it works for me in the python terminal, but I don’t know how to get it to work in Grasshopper.

I have two inputs (energy received by a sensor and the area it represents) for very hour of the year (8760). So a list of lists.

I then multiply them to get the power produced at each sensor point for every hour of the year…this seems OK

When I now use Numpy.Tranform on the list of lists to get the data points from list of hourly energies over the year at each point, to list of point energies at each hour of the year. I’m doping something wrong here and it stops working in Grasshopper as I get some kind of object.

Adding up all the energies from the different points at each hour, is what I would then like to do, but I think the Numpy.som would just all everything into one final sum of all points at all hours…I would like to end with an aggregated 8760 list…like I do in the Grasshopper script. I think the solutions in to add axis=0…

…my problem seems to still be with numpy.transpose()

Any suggestions?
—BTW am I naive in thinking that this will speed the work up over doing it in Grasshopper?

@Anderssmith

—BTW am I naive in thinking that this will speed the work up over doing it in Grasshopper?

In this particular example I’m pretty sure the ghpythonremote method will be slower. The native GH components are probably comparable to an equivalent numpy operation (since both are using compiled libraries for the math), but don’t have to deal with the additional overhead of converting between the two languages, and calling an additional python interpreter that we have to do to work with ghpythonremote.

But you can get speedups if your numpy code replaces pure python code (especially replacing for loops with matrix operations), replace multiple GH components with fewer numpy operations, or best of all - getting out of Grasshopper. Personally, I’ve gotten orders of magnitude speedups by just saving Honeybee JSONs, or OSM files, at some partial state, and doing the rest of work in numpy, and just running the simulations through the command line. That way you can avoid the massive slowdown of large GH scripts.

Anyway, regarding your problem, your code look correct to me, but your inputs are not correct. It looks like you’re using implicit cycles in your GH component, meaning your code is executing for each list item, and not operating over the whole matrix at once. You can solve this by right-clicking the inputs and changing the option for how the input is structured. I also think your inputs will be GH trees, so you need to convert that to a python list of lists, which you can google how to do correctly. And once you’ve finished your numpy operations, you need to convert your numpy arrays back into python lists (or GH trees) to see the output correctly.

Check the dimension of your matrices with print(area.shape), once they are correct it should work. I wrote out the example below for what dimension everything should be (and also showing a quicker transformation using matrix multiplication).

# Define inputs and dimensions.
# Renaming 'energy' to flux (energy per unit area) for clarity.
flux = np.arange(8760 * 4).reshape(8760,4)  #  flux.shape is (8760 x 4), units are kWh/m2
area = np.arange(4).reshape(4, 1)  #  area.shape is (4 x 1), units are m2

# Option 1: Use matrix multiplication
energy1 = flux @ area  #  (8760 x 1)  # energy1.shape is (8760 x 1), units are kWh  
energy1 = energy1.reshape(8760)  # energy1.shape is (8760), units are kWh

# Option 2: Using transpose, then sum
energy2 = np.sum(flux.T * area, axis=0)  # energy2.shape is (8760) ,  units are kWh

# Confirm both results are equal
assert np.array_equal(energy1, energy2)  # Result is True

# Convert numpy array to list
energy_gh = energy1.tolist()  # energy_gh is a list of length 8760

Thus is extremely helpful, so thank you for helpig a python noob
I get an error when I want to multiply flux and area to get energy1 in your option 1 solution…

energy1 = flux * area

ValueError Traceback (most recent call last)
/var/folders/qw/g1kz9tvs3fx_fzmmvyp0y56m0000gn/T/ipykernel_69089/1603009329.py in
----> 1 energy1 = flux * area

ValueError: operands could not be broadcast together with shapes (8760,4) (4,1)

If I reshape area to (1,4) then it will multiply, but I can’t get it to energy1.reshape(8760) to work in the following step.

energy1 = energy1.reshape(8760)

ValueError Traceback (most recent call last)
/var/folders/qw/g1kz9tvs3fx_fzmmvyp0y56m0000gn/T/ipykernel_69089/1659059532.py in
----> 1 energy1 = energy1.reshape(8760)

ValueError: cannot reshape array of size 35040 into shape (8760,)

…any thoughts or suggestions?

thanks
anders

Looks like you made a typo when you copied my example, this line should be:
energy1 = flux @ area

The “@” symbol is used in numpy for matrix multiplication, whereas the “*” is used for element-wise multiplication.

Aaah, maybe I get it now, this is all a learning curve to me…So thank you for baring with me.

I first tried to use @, but got an error and tried * instead and this messed things up. I now know that @ for multiplying matrixes only became available in 3.5 and I use python 2.7 as this i required by GHPythonRemote …

I’ve now read up on it this morning and should have used np.matmul()…it works in python 2.7 in a terminal, but when I plug it into Grasshopper I get an error ‘input operand does not have enough dimensions’!

The ‘flux’ matrix input has 8760 values for each sensor point, while the ‘area’ matrix only has one per sensor point…I’ve tried with newaxis, but it doesn’t work so I’m struggling to find a way to make them compatible

Thanks for your help!

What’s the shape of “flux” and “area”? You can print(flux.shape); print(area.shape) to figure this out.

The shape just has to be consistent with the mathematical operation you’re doing[1]. Let’s say you have 4 area points. For matrix multiplication, your taking the dot product of the right-side column, and the left-side row, so your shapes have to have matching dimensions along those axis, i.e. np.matmul((8760, 4), (4, 1)). For element-wise multiplication, your multiplying rows to rows, so (4,8760) * (4,1) or equivalently (8760,4).T * (4,1) will work.

It sounds like your area may have a shape of (4,), in which case np.newaxis should have worked to reshape it into a (4,1) matrix. But you need to check the actual output to confirm this, reshaping numpy axis isn’t very intuitive, and its easy to add the new axis in the wrong dimension. I would suggest using area.reshape(-1, 1) to add the extra dimension, the “-1” acts as a placeholder, and numpy will replace it with whatever dimension is neccessary to get the column dimension to equal 1. It should be a lot easier then using np.newaxis.

[1] ETA: Most of the time. In certain cases, numpy uses its “broadcasting” to infer certain operations even when the dimension doesn’t really work out mathematically. This is usually the case with element-wise operations.

I’m sorry for my limited python/matrix/GH knowledge…I think my problem might be that I’m not getting the two GH trees/lists correctly into python. When I run print np.shape(area) i get an empty set of brackets and when I try to reshape I get a runtime error that DataTree[Object] has no attribute ‘reshape’

I’ve set both inputs to access trees with type hint ‘Float’…what am I missing?

I’m sorry for my limited python/matrix/GH knowledge…

Don’t worry, we all have to start somewhere :slight_smile: … I’ve used GH for years, and still don’t really understand GH datatrees.

The good news is I think we’ve found the root of your problem - your inputs need to be correctly converted from GH data into python lists. In my first response, I mentioned that you will need to convert the GH datatrees into a nested python lists, but I’m now thinking an easier solution here is to just flatten your inputs into lists, and then reshape the flat structure back into its 2D structure with numpy. That would skip the slow, complicated step of recursively converting the GH datatree into a numpy-friendly lists.

Anyway, I’ll see if I can get a working example of how to set this up in GH, later today.