First steps in Python

To demonstrate the basic workflow in Python, we’ll cover how to:

  • create attribute and model managers;

  • create an attribute resource to hold simulation information;

  • populate the attribute resource with definitions for a particular simulation run;

  • generate an ensemble of input decks whose attributes use these definitions with different values as part of a sensitivity study;

  • load a geometric model that has side sets used to hold boundary conditions;

  • relate the side sets to boundary condition attributes in the ensemble; and

  • write an input deck for each entry in the ensemble.

Our running example will be a fluid mechanics problem where we run an ensemble of simulations to characterize the sensitivity of pump work to viscosity and inlet velocity at our expected operating conditions.

Setup

The first part of our script imports SMTK and creates managers:

1
2ares = smtk.attribute.Resource.create()
3mmgr = smtk.model.Resource.create()
4

This will almost always be the first thing your Python scripts do.

Problem Definition

Now that the environment is set up, we can define the attribute system that our simulation expects as its problem definition.

Everything in this entire section is usually replaced by creating an XML file describing the problem definition. However, the purpose of this tutorial is to demonstrate how to implement your simulation workflow in Python and there can be times when you wish to programmatically create a problem definition, or template file.

The first thing we do is create attribute Definitions for the basic types of simulation inputs we must provide:

1bcDef = ares.createDefinition('boundary condition')
2icDef = ares.createDefinition('initial condition')
3matDef = ares.createDefinition('material properties')
4
5# Our material is defined by a viscosity
6viscosity = smtk.attribute.DoubleItemDefinition.New('viscosity')
7matDef.addItemDefinition(viscosity)

Once we’ve created the material definition, we can add all the individual properties required to specify a material. More complicated definitions that allow materials of widely different types — each with its own unique parameters — is a topic for another day. Here, we just indicate that viscosity is the only parameter required to define our fluid, perhaps because the simulation assumes incompressibility.

The definitions for boundary and initial conditions are more complex because we might have different combinations of Dirichlet, Neumann, or Cauchy conditions for different variables over different portions of the domain. Rather than try to use a single Definition for all of these, we can use SMTK’s inheritance system to create derived Definitions:

 1# We have one initial condition for the interior of the domain:
 2fluidICDef = ares.createDefinition('fluid ic', icDef)
 3
 4# The specific types of BCDefs our example must define:
 5# At the outlet, pressure must be given.
 6# At the inlet, fluid velocity and temperature must be given.
 7# On the wall, heat flux must be specified.
 8outletBCDef = ares.createDefinition('outlet bc', bcDef)
 9inletBCDef = ares.createDefinition('inlet bc', bcDef)
10wallBCDef = ares.createDefinition('wall bc', bcDef)
11
12# Each ICDef/BCDef holds a different type of value:
13temperature = smtk.attribute.DoubleItemDefinition.New('temperature')
14pressure = smtk.attribute.DoubleItemDefinition.New('pressure')
15velocity = smtk.attribute.DoubleItemDefinition.New('velocity')
16heatflux = smtk.attribute.DoubleItemDefinition.New('heatflux')
17
18# Add the values to the appropriate BCDef:
19fluidICDef.addItemDefinition(temperature)
20fluidICDef.addItemDefinition(velocity)
21
22outletBCDef.addItemDefinition(pressure)
23
24inletBCDef.addItemDefinition(velocity)
25inletBCDef.addItemDefinition(temperature)
26
27wallBCDef.addItemDefinition(heatflux)

We could also have created a definition to hold global simulation parameters such as convergence criteria, halting criteria, and desired accuracy; but for simplicity, we will assume the simulation package has reasonable defaults.

Now that we have quantities of interest specified for each attribute definition, we can provide hints to make data entry less error-prone. For instance:

  • SMTK accepts units for items that store continuous or discrete numeric values. These are presented to the user during entry to avoid confusion.

  • Viscosity, temperature, and pressure have absolute minimum values. Items that store quantitative values accept minimum and maximum values; a boolean argument passed with the minimum or maximum indicates whether the limit value is attainable.

  • Items store a single value in their given primitive type by default. But arrays of values may be accepted and these arrays may be fixed in size (as our 2-dimensional velocity requires exactly 2 components) or extensible between a minimum and maximum array length.

  • It is also possible to mark an Item so that its entries should come from an enumerated set of acceptable values, but we do not illustrate that here.

 1viscosity.setMinRange(0, True)
 2viscosity.setUnits('Pa * s')  # or Poise [P]
 3
 4temperature.setMinRange(-273.15, True)
 5temperature.setUnits('deg C')
 6
 7pressure.setMinRange(0, True)
 8pressure.setUnits('Pa')
 9
10heatflux.setUnits('W / m^2 / K')
11
12velocity.setNumberOfRequiredValues(2)
13velocity.setUnits('m / s')
14velocity.setDefaultValue(0.)

Now that our item definitions are constrained, we can create attributes from the definitions.

Simulation Preparation

The previous steps prepared a template that we can now use to create input decks for multiple simulations. The first step in creating input decks is to instantiate attributes based on the definitions above:

1fluidIC = ares.createAttribute('fluidIC', fluidICDef)
2wallBC = ares.createAttribute('wallBC', wallBCDef)
3inletBC = ares.createAttribute('inletBC', inletBCDef)
4outletBC = ares.createAttribute('outletBC', outletBCDef)
5matProp = ares.createAttribute('fluid', matDef)

When you ask the Resource to create an Attribute, it provides an Item to match every ItemDefinition in the attribute’s underlying Definition. These items are initialized to their default values (when a default has been provided) and keep track of whether they have been changed or not. We will change the values of these attributes soon, but first let’s consider their relationship to the model geometry.

In the simplest workflow, you will already have a geometric model of the simulation domain(s) and the domain boundaries with the relevant groups of vertices, edges, faces, and/or volumes named for use in applying boundary and initial conditions. Here, we read in an SMTK model session assuming that these groups already exist with names that we know:

 1# Read in an SMTK-native B-Rep model:
 2# TODO: Replace with resource.readModel()
 3jsonFile = open(modelFileName, 'r')
 4json = jsonFile.read()
 5smtk.model.SessionIOJSON.loadModelRecords(json, mmgr)
 6
 7# Now find groups corresponding to IC/BCs:
 8models = mmgr.findEntitiesByProperty('name', 'Test Model')
 9model = smtk.model.Model(models[0])
10groups = model.groups()
11if groups and len(groups):
12    wallGroup = next((g for g in groups if g.name() == 'wall'))
13    inletGroup = next((g for g in groups if g.name() == 'inlet'))
14    outletGroup = next((g for g in groups if g.name() == 'outlet'))
15    fluidGroup = next((g for g in groups if g.name() == 'fluid'))
16
17    fluidIC.associateEntity(fluidGroup)
18    outletBC.associateEntity(outletGroup)
19    inletBC.associateEntity(inletGroup)
20    wallBC.associateEntity(wallGroup)

Now we can loop over the parameters we wish to study and create an ensemble for sensitivity analysis.

 1# FIXME: Actually put this inside a loop that exports input decks.
 2matProp.findDouble('viscosity').setValue(1.002e-3)  # [Pa * s]
 3
 4fluidIC.findDouble('temperature').setValue(25)  # [C]
 5fluidIC.findDouble('velocity').setValue(0, 0.)  # [m / s]
 6fluidIC.findDouble('velocity').setValue(1, 0.)  # [m / s]
 7
 8outletBC.findDouble('pressure').setValue(101300)  # [Pa]
 9
10inletBC.findDouble('velocity').setValue(0, 0.25)  # [m / s]
11inletBC.findDouble('velocity').setValue(1, 0.00)  # [m / s]
12inletBC.findDouble('temperature').setValue(50)  # [C]
13
14wallBC.findDouble('heatflux').setValue(1.0)  # [W / m^2 / K]