Cheers, there are no explicit relationships in the IFC that I can see that link the window to the space.
One possibility of how Solibri is deriving this, is looking at the ObjectPlacement of the window (i.e. its origin point) and the ObjectPlacement of each space, and measuring the closest space. This obviously has room for error, but indeed I get the same result as Solibri if I derive it this way. If you want to, you can find the matrix of the ObjectPlacement of any element (wall, window, etc) using ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
. The advantage of this method is that it is the cheapest.
Another potentially more accurate way is to use the actual geometry of the window. Given that windows do not intersect with the space, thinking out loud perhaps you could find the normal and centroid of every face in an IfcSpace, and then a normal and centroid of the largest face for each window. Then, by comparing normals and centroids, you can map windows to spaces. Hopefully, this is not too expensive, as windows are typically mapped so you calculate once, then many windows should share the same mapped instance of the geometry. Happy to demonstrate how to do this if you want to go down this route.
I should note that the file does have space boundaries, which is what you really want - proper explicit stuff for energy analysis, not guesswork and deriving. However, it only has space boundaries linking the space to the slab, not the space to the window.