r"""
.. This doc built from file 'with_doc_python_3dec.py' found in
\3dec\blockpython\test\examples\python_3dec
.. _usingpythonwith3dec:
Using Python with |3dec|
##############################
This section gives an example of using a Python program to interact
with |3dec|. In this example we create a simple |3dec| model and use
Python to apply boundary conditions and explore the model response.
First we import the |c| :ref:`itasca <pyapi_itasca>` module with the ``import``
statement. The ``itasca`` module defines the interaction between
Python and |3dec|. We import the ``itasca`` module as the shortened
name ``it``.
"""
import itasca as it
r"""
We are going to create some blocks to show features of the Python
scripting. The :py:func:`itasca.command` function is
used to issue a series of |3dec| commands.
These commands create a block then subdivide it into small blocks.
The triple quotes are used to define a multi-line string.
"""
it.command("""
model new
model large-strain off
block create brick 0 10
block densify segment 5
block cut joint-set o 5 5 5 dip 20 d-d 90
""")
r"""
Working with |3dec| block
^^^^^^^^^^^^^^^^^^^^^^^^^^^
We can get the number of blocks by using the
:py:func:`itasca.block.count` function. Typing the
following expression into the IPython terminal prints the number 160
to the screen.
"""
it.block.count()
r"""
The :py:func:`itasca <pyapi_itasca>` module defines functions and classes
for interacting with the |3dec| model. Some of these functions return
objects,
"""
b = it.block.find(1)
print(b)
r"""
Here :py:func:`itasca.block.find`\ (1) returns a Block
object for the block with id 1. This object is assigned to the Python
variable ``b``. Most |3dec| model items are objects (instances of a
class) like this. The Block object has many methods, for example
"""
b.pos()
r"""
The variable ``b`` is a Block object which represents a single |3dec|
block. ``pos`` (:py:func:`itasca.block.Block.pos`) is a method of this
object that returns the block centroid.
.. note::
The :py:func:`itasca.block.Block.pos` method returns a length three
vector represented as a ``vec3`` object. See |c| :ref:`vec <pyapi_vec>`
module for more information about vec3 objects and working
with vectors in Python.
"""
print("block", b.id(), "at", b.pos())
r"""
In Python the ``for`` statement is used to iterate over sequences of things,
"""
volume_sum = 0.0
for b in it.block.list():
volume_sum += b.vol()
r"""
In this example the ``for`` statement is used to loop over all the |3dec|
blocks. During each loop the variable ``b`` is a different ``Block`` object.
Finally, we check that the sum of the block volumes is what we expect.
"""
print(volume_sum)
assert abs(volume_sum - 1000.0)<1e-9
r"""
Let's find a block near the center of the model
"""
b = it.block.near((5,5,5))
r"""
and confirm its position with the ``pos`` method.
"""
b.pos()
r"""
Working with |3dec| Zone
^^^^^^^^^^^^^^^^^^^^^^^^^^
We generate zones inside blocks and subcontacts between deformable blocks.
Then we set constitutive model and some properties for zones and subcontacts.
Finally, insitu stresses are initialized in the model.
"""
it.command("""
block zone generate edgelength 1.0
block zone cmodel assign elastic
block zone property density 2950 young 12e9 poisson 0.25
""")
r"""
The ``props`` method (:py:func:`itasca.block.zone.Zone.props`) returns the zone
constitutive model properties as a Python dictionary object. The
dictionary keys are the property names and the values are the property
values.
"""
z = it.block.zone.find(1)
print(z)
z.props()
r"""
To access the individual parts of a dictionary the [ ] brackets are used with a string key,
"""
z.props()['bulk']
r"""
or the ``prop`` method (:py:func:`itasca.block.zone.Zone.prop`) can be used.
"""
z.prop('shear')
r"""
Zone properties can be set with the ``set_prop`` method (:py:func:`itasca.block.zone.Zone.set_prop`).
"""
z.set_prop('bulk', 8.5e9)
print(z.prop('bulk'))
r"""
There are two places where Python interacts with |3dec| zones:
(i) the functions that are part of the |c| :ref:`pyapi_itasca_block_zone`
Python module, and
(ii) the methods of an individual |c| :ref:`pyapi_itasca_block_zone_Zone` object.
In Python terminology, |c| :ref:`pyapi_itasca_block_zone` is a submodule of the
|c| :ref:`pyapi_itasca_block` module and |c| :ref:`pyapi_itasca_block_zone_Zone`
is a class defined in the |c| :ref:`pyapi_itasca_block_zone` module. For brevity
we often import the ``itasca`` module by the shortened name ``it``. Above,
:py:func:`itasca.block.zone.count`, :py:func:`itasca.block.zone.list`, and
:py:func:`itasca.block.zone.find` are functions defined in the |c| :ref:`pyapi_itasca_block_zone` module and
:py:func:`itasca.block.zone.Zone.pos_z` and :py:func:`itasca.block.zone.Zone.vol_z`
are |c| :ref:`pyapi_itasca_block_zone_Zone` object method calls.
- |c| :ref:`pyapi_itasca` gives a complete list of the ``itasca`` module functions and classes.
- |c| :ref:`pyapi_itasca_block_zone` gives a complete list of the ``itasca.block.zone`` module functions.
- |c| :ref:`pyapi_itasca_block_zone_Zone` gives a complete list of the ``Zone``
object methods.
Working with |3dec| vertex
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Similarly, Python can interact with |3dec| gridpoints via
- The |c| :ref:`pyapi_itasca_block_gridpoint` module functions and
- The |c| :ref:`pyapi_itasca_block_gridpoint_Gridpoint` object methods.
"""
gp = it.block.gridpoint.near((2,2,2))
print(gp.pos())
r"""
As with the blocks and zones we can iterate over all the |3dec| gridpoints with
a Python ``for`` statement.
"""
gp_mass = 0
for gp in it.block.gridpoint.list():
gp_mass += gp.mass()
z_mass = 0
for z in it.block.zone.list():
z_mass += z.mass()
print(gp_mass, z_mass)
r"""
Working with Contact and Subcontact
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"""
it.command("""
block contact gen-sub
block contact jmodel assign mohr
block contact property stiffness-normal 1e9 stiffness-shear 1e9 coh 1e20 fric 35
""")
"""
Similarly, the :py:func:`it.block.contact.find()
<pyapi_itasca_block_contact_find>` and :py:func:`it.block.contact.near()
<pyapi_itasca_block_contact_near>` functions can be used to get specific
contact.
"""
c1 = it.block.contact.find(1)
c2 = it.block.contact.near((0,2,2))
print(c1)
print(c2)
r"""
We get the list of all subcontacts associated with a contact
"""
for cx in c1.subcontacts():
print("subcontact ",cx.id(), "at", cx.pos())
r"""
Extra variables
^^^^^^^^^^^^^^^^^^^^
|3dec| Blocks, zones, gridpoints, and other Itasca model objects have extra
variables. This allows some data to be associated with the model
object. Python programs can read and write extra variables with the
methods ``extra`` and ``set_extra`` which are defined on all object
types.
"""
b.set_extra(1, 1.23)
b.set_extra(2, "a text string")
print(b.extra(1))
print(b.extra(2))
r"""
"""
z.set_extra(1, z.vol())
z.set_extra(2, z.model())
print(z.extra(1))
print(z.extra(2))
r"""
"""
gp.set_extra(1, gp.pos())
gp.set_extra(2, 86856527)
print(gp.extra(1))
print(gp.extra(2))
r"""
Typically in Python indices start at 0. The extra variables indices
are an exception to allow Python it work well with the Itasca software
GUI. Extra variables are stored in the model save file. This is a way
to associate Python data in the Itasca model state.
Using |3dec| Groups
^^^^^^^^^^^^^^^^^^^^
Groups are used to create a list of |3dec| model objects. Often this
is used to set a boundary condition or apply a material property to a
specific part of the model. Here we create a two groups of blocks
representing the upper and lower parts of our model using a |3dec|
command.
"""
it.command("""
block group "lower" range plane o 5 5 5 dip 20 d-d 90 above
block group "upper" range plane o 5 5 5 dip 20 d-d 90 below
block join range group "lower"
block join range group "upper"
""")
r"""
We can query if a block is part of a group using the ``Block.group`` method
(:py:func:`itasca.block.Block.group`).
"""
lower_blocks, upper_blocks = 0, 0
for b in it.block.list():
if b.group("default") == "lower":
lower_blocks += 1
elif b.group("default") == "upper":
upper_blocks += 1
print(lower_blocks, upper_blocks)
r"""
Next we cycle the model and inspect the model response to this loading.
"""
it.command("""
model gravity 0 0 -10
block face apply stress-zz -1e6 range position-x 10
block gridpoint apply velocity (0.,0.,0.) range position-z 0
model save "before_cycling"
model solve
""")
gp_top_middle = it.block.gridpoint.near((5,5,10))
gp_middle = it.block.gridpoint.near((5,5,5))
print(gp_top_middle.disp())
print(gp_middle.disp())
r"""
"""
central_zone = it.block.zone.near((5,5,5))
stress = central_zone.stress()
strain = central_zone.strain_inc()
print(stress)
print()
print(strain)
r"""
.. note::
The ``stress`` (:py:func:`itasca.block.zone.Zone.stress`) and ``strain``
(:py:func:`itasca.block.zone.Zone.strain_inc`) zone methods return symmetric
tensors as ``stens3`` objects. See the ``stens3`` type (:py:func:`vec.stens3`)
for more information about symmetric tensor objects and working with tensors
in Python.
Running parameter studies
^^^^^^^^^^^^^^^^^^^^^^^^^^^
In the following example the same model is run with 4 different values of
Young's modulus and the vertical displacement at the top center of the
model is printed.
"""
for modulus in [6e9, 8e9, 10e9, 12e9]:
it.command("""
model restore 'before_cycling'
block zone prop young {}
model solve
""".format(modulus))
vertical_disp = it.block.gridpoint.near((5,5,10)).disp_z()
result = "Modulus: {:.01e} Pa, vertical displacement: {:.03e}"
print(result.format(modulus, vertical_disp))
r"""
Handling |3dec| errors
^^^^^^^^^^^^^^^^^^^^^^^
With Python it is possible to catch and handle errors using the
built-in Python exception handling. In Python runtime errors are
called exceptions. Exceptions can be caught and handled with ``try``,
``except`` blocks.
"""
try:
it.command("""
block zone prop young 0
model solve
""")
except Exception as data:
print("an error occurred", data)
print("Python program execution continues...")
it.command("block zone prop young 8e9") # fix the problem
r"""
Python Type System
^^^^^^^^^^^^^^^^^^^^^
Occasionally, the type of a Python variable needs to be determined by a
Python program. The following examples demonstrate the Python type system.
"""
print(type(1.0) is int)
r"""
"""
print(type(1.0) is float)
r"""
"""
print(type("Itasca") is str)
r"""
"""
print(type(gp) is it.block.gridpoint.Gridpoint)
r"""
"""
print(type(gp) is it.block.zone.Zone)
r"""
"""
print(type(z) is it.block.gridpoint.Gridpoint)
r"""
"""
print(type(z) is it.block.zone.Zone)
r"""
Python Callback Functions
^^^^^^^^^^^^^^^^^^^^^^^^^^
Python functions can be run at specific points during the |3dec|
calculation sequence. The :py:func:`itasca.set_callback` function registers
Python functions to be called during cycling while the
:py:func:`itasca.remove_callback` removes these function callbacks.
"""
i=0
def my_callback(*args):
global i
i += 1
print("in Python callback function.")
it.set_callback("my_callback", -1)
it.command("cycle 5")
print("The Python callback function was called {} times".format(i))
it.remove_callback("my_callback",-1)
i=0
it.command("cycle 5")
print("The Python callback function was called {} times".format(i))
r"""
.. note::
Callback function registration is cleared when the :cmd:`model.new`
or :cmd:`model.restore` commands are given. Be careful not to
register the same Python function to be called more than once per
cycle. Use the :cmd:`fish.list.callbacks` command to see which
functions have already been registered.
There are three callback events which have special behavior in
Python. The "save", "restore" and "new" callback events are not
reset when the :cmd:`model.new` or :cmd:`model.restore` commands
are given. These callbacks allow Python programs to manually save
or read data associated with a model state. The function
:py:func:`itasca.state_callbacks` can be used to see which Python
functions are defined at these special callback events.
"""
pass