The Workspace and Projects#

Caution

You probably don’t need to worry about these features until your simulations get large and complicated.

The Workspace is equivalent to a web browser window, while a Project is like tabs inside the browser. Each Project is an isolated OpenPNM simulation with a single Network and all associated objects. All Projects are stored in the same Workspace. There can be only 1 Workspace open at a given time, so all new projects are registered in the same Workspace. Projects and Workspaces can be saved and loaded.

import openpnm as op
op.visualization.set_mpl_style()

Usage of Projects and Workspace#

Initialize the Workspace and save in a variable called ws, and print it to verify that it is currently empty:

ws = op.Workspace()
print(ws)
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

You don’t need to create a project, as they get automatically created each time a new network is initialized:

pn = op.network.Cubic(shape=[4, 4, 4])

This created a new project and added the network to it automatically:

print(pn.project)
══════════════════════════════════════════════════════════════════════════════
Object Name : Object Class and ID
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
net : <openpnm.network.Cubic at 0x7f3dbc3362f0>
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

It is possible to create an empty project and tell the network initialization to use it, but this is not usually necessary:

proj = ws.new_project()
pn2 = op.network.Demo(project=proj)

You can view all active projects by printing the workspace via:

print(ws)
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
proj_01
══════════════════════════════════════════════════════════════════════════════
Object Name : Object Class and ID
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
net : <openpnm.network.Cubic at 0x7f3dbc3362f0>
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
proj_02
══════════════════════════════════════════════════════════════════════════════
Object Name : Object Class and ID
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
net : <openpnm.network.Demo at 0x7f3dbc356ed0>
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

A project can be purged from the workspace via ws.close_project(proj). Let’s print workspace again,:

ws.close_project(proj)
print(ws)
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
proj_01
══════════════════════════════════════════════════════════════════════════════
Object Name : Object Class and ID
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
net : <openpnm.network.Cubic at 0x7f3dbc3362f0>
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

Workspace is a Singleton#

The Workspace object is a singleton, which is “design pattern” where only ONE instance of an object can be created in any given session. The reason OpenPNM uses this pattern is to enable the lookup of object relationships, such as which project a network belongs to or what network is associated with a phase. This is done as follows. Let’s first create a new network:

pn = op.network.Demo(shape=[3, 3, 3])
print(pn.name)
net

Now let’s scan through each project in the workspace until we find the one that contains pn.

ws = op.Workspace()
for project in ws.values():
    for item in project:
        if item is pn:
            # If this were a function, here would be "return project"
            break
print(project)
══════════════════════════════════════════════════════════════════════════════
Object Name : Object Class and ID
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
net : <openpnm.network.Demo at 0x7f3dbbb09440>
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

We refer to this as a “bottom-up” approach since the object is basically looking for itself. A “top-down” approach would be the case where relationships are hard-coded such that phase.project actually contains a handle to the project (i.e. phase.project = proj). In the “bottom-up” approach, accessing phase.project actually triggers a function that does the above search.

Similarly we can find the network associated with a given phase (or algorithm) as follows:

ws = op.Workspace()
air = op.phase.Air(network=pn)
for project in ws.values():
    for item in project:
        if item is pn:
            # If this were a function, here would be "return item"
            break
pn
net : <openpnm.network.Demo at 0x7f3dbbb09440>

The above two lookups may seem convoluted, but they have the benefit of not requiring that pn have an attribute containing handles to all associated objects. It was found in V1 that this lead to memory leaks since all objects contained references to other objects, and it was very tricky to actually delete something. The bottom-up search avoids this, and with the help of Python’s “syntactic sugar” we can make it all occur behind the scenes and uphold the appearance that you’re accessing the object directly.

One Network per Project#

Only