From 14ce3cec4b388ae8b7ec5b28db57dbcbff06f147 Mon Sep 17 00:00:00 2001 From: April Novak Date: Wed, 27 Mar 2024 19:06:26 -0500 Subject: [PATCH] notebooks --- Pincell-solution.ipynb | 1885 ++++++++++++++++++++++++++++++++++++++++ Pincell.ipynb | 95 ++ 2 files changed, 1980 insertions(+) create mode 100644 Pincell-solution.ipynb create mode 100644 Pincell.ipynb diff --git a/Pincell-solution.ipynb b/Pincell-solution.ipynb new file mode 100644 index 000000000..57532dbb6 --- /dev/null +++ b/Pincell-solution.ipynb @@ -0,0 +1,1885 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Modeling a Pincell\n", + "In this module, we'll demonstrate the basic features of the Python API for constructing input files and running OpenMC. In it, we will show how to create a basic reflective pincell model that is equivalent to modeling an infinite array of fuel pins in a pressurized water reactor. We highly recommend having a copy of the [Python API reference documentation](https://docs.openmc.org/en/stable/pythonapi/index.html) open in another browser tab that you can refer to." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this example, we'll create a simple pincell that is composed of:\n", + "- UO2 with 3.5 weight% enriched in U-235 at 11 g/cm3\n", + "- zirconium clad at 6.5 g/cm3\n", + "- H2O moderator at 1.0 g/cm3\n", + "\n", + "The dimensions of our fuel pin will be as follows:\n", + "- Fuel outer radius = 0.46955 cm\n", + "- Clad inner radius = 0.47910 cm\n", + "- Clad outer radius = 0.54640 cm\n", + "- Fuel pin pitch = 1.44270 cm" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import openmc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basics of Jupyter Notebook\n", + "\n", + "You are working within a Jupyter notebook. Some common commands which will be useful:\n", + "- To execute a cell: `Shift+Enter`\n", + "- To insert a cell above or below: `Esc+a`, `Esc+b`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Naming Conventions\n", + "\n", + "Before we start working with OpenMC's Python API, it's helpful to understand the naming convention of objects so that you can conceptualize what is a function, what is a class, etc. OpenMC's Python interface follows the same naming convention that is adopted by many/most Python projects:\n", + "\n", + "- Module names are `lowercase`\n", + "- Classes are `CamelCase`\n", + "- Functions and class attributes/variables are `lowercase_with_underscores`\n", + "\n", + "To give a few specific examples:\n", + "\n", + "- `openmc.deplete` is the depletion _module_\n", + "- `openmc.run` is a _function_\n", + "- `openmc.Material` is a _class_\n", + "- `openmc.StatePoint` is a _class_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting Attributes\n", + "\n", + "When building OpenMC models, we will work with many different classes. Each class typically has _attributes_, a variable belonging to the class. When creating a class, you can often set those attributes directly when you instantiate the object, like this:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "my_cell = openmc.Cell(name=\"box\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or, you can assign values to attributes after you have already created the object. For example, the following is equivalent to the above:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "my_other_cell = openmc.Cell()\n", + "my_other_cell.name = \"box2\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can generally also mix-and-match, setting some attributes when you instantiate the object, and others at a later point." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "new_cell = openmc.Cell(name=\"box3\")\n", + "new_cell.temperature = 500" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## How to Get Help/Learn More\n", + "\n", + "When building OpenMC models, we recommend having a copy of the Python API documentation (linked earlier) open. You can also query information about classes and methods directly from Jupyter. For example, we can use `help` to get documentation on all of the valid attributes for functions and classes." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on class Cell in module openmc.cell:\n", + "\n", + "class Cell(openmc.mixin.IDManagerMixin)\n", + " | Cell(cell_id=None, name='', fill=None, region=None)\n", + " | \n", + " | A region of space defined as the intersection of half-space created by\n", + " | quadric surfaces.\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | cell_id : int, optional\n", + " | Unique identifier for the cell. If not specified, an identifier will\n", + " | automatically be assigned.\n", + " | name : str, optional\n", + " | Name of the cell. If not specified, the name is the empty string.\n", + " | fill : openmc.Material or openmc.UniverseBase or openmc.Lattice or None or iterable of openmc.Material, optional\n", + " | Indicates what the region of space is filled with\n", + " | region : openmc.Region, optional\n", + " | Region of space that is assigned to the cell.\n", + " | \n", + " | Attributes\n", + " | ----------\n", + " | id : int\n", + " | Unique identifier for the cell\n", + " | name : str\n", + " | Name of the cell\n", + " | fill : openmc.Material or openmc.UniverseBase or openmc.Lattice or None or iterable of openmc.Material\n", + " | Indicates what the region of space is filled with. If None, the cell is\n", + " | treated as a void. An iterable of materials is used to fill repeated\n", + " | instances of a cell with different materials.\n", + " | fill_type : {'material', 'universe', 'lattice', 'distribmat', 'void'}\n", + " | Indicates what the cell is filled with.\n", + " | region : openmc.Region or None\n", + " | Region of space that is assigned to the cell.\n", + " | rotation : Iterable of float\n", + " | If the cell is filled with a universe, this array specifies the angles\n", + " | in degrees about the x, y, and z axes that the filled universe should be\n", + " | rotated. The rotation applied is an intrinsic rotation with specified\n", + " | Tait-Bryan angles. That is to say, if the angles are :math:`(\\phi,\n", + " | \\theta, \\psi)`, then the rotation matrix applied is :math:`R_z(\\psi)\n", + " | R_y(\\theta) R_x(\\phi)` or\n", + " | \n", + " | .. math::\n", + " | \n", + " | \\left [ \\begin{array}{ccc} \\cos\\theta \\cos\\psi & -\\cos\\phi \\sin\\psi\n", + " | + \\sin\\phi \\sin\\theta \\cos\\psi & \\sin\\phi \\sin\\psi + \\cos\\phi\n", + " | \\sin\\theta \\cos\\psi \\\\ \\cos\\theta \\sin\\psi & \\cos\\phi \\cos\\psi +\n", + " | \\sin\\phi \\sin\\theta \\sin\\psi & -\\sin\\phi \\cos\\psi + \\cos\\phi\n", + " | \\sin\\theta \\sin\\psi \\\\ -\\sin\\theta & \\sin\\phi \\cos\\theta & \\cos\\phi\n", + " | \\cos\\theta \\end{array} \\right ]\n", + " | \n", + " | A rotation matrix can also be specified directly by setting this\n", + " | attribute to a nested list (or 2D numpy array) that specifies each\n", + " | element of the matrix.\n", + " | rotation_matrix : numpy.ndarray\n", + " | The rotation matrix defined by the angles specified in the\n", + " | :attr:`Cell.rotation` property.\n", + " | temperature : float or iterable of float\n", + " | Temperature of the cell in Kelvin. Multiple temperatures can be given\n", + " | to give each distributed cell instance a unique temperature.\n", + " | translation : Iterable of float\n", + " | If the cell is filled with a universe, this array specifies a vector\n", + " | that is used to translate (shift) the universe.\n", + " | paths : list of str\n", + " | The paths traversed through the CSG tree to reach each cell\n", + " | instance. This property is initialized by calling the\n", + " | :meth:`Geometry.determine_paths` method.\n", + " | num_instances : int\n", + " | The number of instances of this cell throughout the geometry.\n", + " | volume : float\n", + " | Volume of the cell in cm^3. This can either be set manually or\n", + " | calculated in a stochastic volume calculation and added via the\n", + " | :meth:`Cell.add_volume_information` method. For 'distribmat' cells\n", + " | it is the total volume of all instances.\n", + " | atoms : dict\n", + " | Mapping of nuclides to the total number of atoms for each nuclide\n", + " | present in the cell, or in all of its instances for a 'distribmat'\n", + " | fill. For example, {'U235': 1.0e22, 'U238': 5.0e22, ...}.\n", + " | \n", + " | .. versionadded:: 0.12\n", + " | bounding_box : openmc.BoundingBox\n", + " | Axis-aligned bounding box of the cell\n", + " | \n", + " | Method resolution order:\n", + " | Cell\n", + " | openmc.mixin.IDManagerMixin\n", + " | builtins.object\n", + " | \n", + " | Methods defined here:\n", + " | \n", + " | __contains__(self, point)\n", + " | \n", + " | __init__(self, cell_id=None, name='', fill=None, region=None)\n", + " | Initialize self. See help(type(self)) for accurate signature.\n", + " | \n", + " | __repr__(self)\n", + " | Return repr(self).\n", + " | \n", + " | add_volume_information(self, volume_calc)\n", + " | Add volume information to a cell.\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | volume_calc : openmc.VolumeCalculation\n", + " | Results from a stochastic volume calculation\n", + " | \n", + " | clone(self, clone_materials=True, clone_regions=True, memo=None)\n", + " | Create a copy of this cell with a new unique ID, and clones\n", + " | the cell's region and fill.\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | clone_materials : bool\n", + " | Whether to create separate copies of the materials filling cells\n", + " | contained in this cell, or the material filling this cell.\n", + " | clone_regions : bool\n", + " | Whether to create separate copies of the regions bounding cells\n", + " | contained in this cell, and the region bounding this cell.\n", + " | memo : dict or None\n", + " | A nested dictionary of previously cloned objects. This parameter\n", + " | is used internally and should not be specified by the user.\n", + " | \n", + " | Returns\n", + " | -------\n", + " | clone : openmc.Cell\n", + " | The clone of this cell\n", + " | \n", + " | create_xml_subelement(self, xml_element, memo=None)\n", + " | Add the cell's xml representation to an incoming xml element\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | xml_element : lxml.etree._Element\n", + " | XML element to be added to\n", + " | \n", + " | memo : set or None\n", + " | A set of object IDs representing geometry entities already\n", + " | written to ``xml_element``. This parameter is used internally\n", + " | and should not be specified by users.\n", + " | \n", + " | Returns\n", + " | -------\n", + " | None\n", + " | \n", + " | get_all_cells(self, memo=None)\n", + " | Return all cells that are contained within this one if it is filled with a\n", + " | universe or lattice\n", + " | \n", + " | Returns\n", + " | -------\n", + " | cells : dict\n", + " | Dictionary whose keys are cell IDs and values are :class:`Cell`\n", + " | instances\n", + " | \n", + " | get_all_materials(self, memo=None)\n", + " | Return all materials that are contained within the cell\n", + " | \n", + " | Returns\n", + " | -------\n", + " | materials : dict\n", + " | Dictionary whose keys are material IDs and values are\n", + " | :class:`Material` instances\n", + " | \n", + " | get_all_universes(self)\n", + " | Return all universes that are contained within this one if any of\n", + " | its cells are filled with a universe or lattice.\n", + " | \n", + " | Returns\n", + " | -------\n", + " | universes : dict\n", + " | Dictionary whose keys are universe IDs and values are\n", + " | :class:`Universe` instances\n", + " | \n", + " | get_nuclide_densities(self)\n", + " | Return all nuclides contained in the cell and their densities\n", + " | \n", + " | Returns\n", + " | -------\n", + " | nuclides : dict\n", + " | Dictionary whose keys are nuclide names and values are 2-tuples of\n", + " | (nuclide, density)\n", + " | \n", + " | get_nuclides(self)\n", + " | Returns all nuclides in the cell\n", + " | \n", + " | Returns\n", + " | -------\n", + " | nuclides : list of str\n", + " | List of nuclide names\n", + " | \n", + " | plot(self, *args, **kwargs)\n", + " | Display a slice plot of the cell.\n", + " | \n", + " | .. versionadded:: 0.14.0\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | origin : iterable of float\n", + " | Coordinates at the origin of the plot. If left as None then the\n", + " | bounding box center will be used to attempt to ascertain the origin.\n", + " | Defaults to (0, 0, 0) if the bounding box is not finite\n", + " | width : iterable of float\n", + " | Width of the plot in each basis direction. If left as none then the\n", + " | bounding box width will be used to attempt to ascertain the plot\n", + " | width. Defaults to (10, 10) if the bounding box is not finite\n", + " | pixels : Iterable of int or int\n", + " | If iterable of ints provided, then this directly sets the number of\n", + " | pixels to use in each basis direction. If int provided, then this\n", + " | sets the total number of pixels in the plot and the number of pixels\n", + " | in each basis direction is calculated from this total and the image\n", + " | aspect ratio.\n", + " | basis : {'xy', 'xz', 'yz'}\n", + " | The basis directions for the plot\n", + " | color_by : {'cell', 'material'}\n", + " | Indicate whether the plot should be colored by cell or by material\n", + " | colors : dict\n", + " | Assigns colors to specific materials or cells. Keys are instances of\n", + " | :class:`Cell` or :class:`Material` and values are RGB 3-tuples, RGBA\n", + " | 4-tuples, or strings indicating SVG color names. Red, green, blue,\n", + " | and alpha should all be floats in the range [0.0, 1.0], for example:\n", + " | \n", + " | .. code-block:: python\n", + " | \n", + " | # Make water blue\n", + " | water = openmc.Cell(fill=h2o)\n", + " | universe.plot(..., colors={water: (0., 0., 1.))\n", + " | seed : int\n", + " | Seed for the random number generator\n", + " | openmc_exec : str\n", + " | Path to OpenMC executable.\n", + " | axes : matplotlib.Axes\n", + " | Axes to draw to\n", + " | legend : bool\n", + " | Whether a legend showing material or cell names should be drawn\n", + " | legend_kwargs : dict\n", + " | Keyword arguments passed to :func:`matplotlib.pyplot.legend`.\n", + " | outline : bool\n", + " | Whether outlines between color boundaries should be drawn\n", + " | axis_units : {'km', 'm', 'cm', 'mm'}\n", + " | Units used on the plot axis\n", + " | **kwargs\n", + " | Keyword arguments passed to :func:`matplotlib.pyplot.imshow`\n", + " | \n", + " | Returns\n", + " | -------\n", + " | matplotlib.axes.Axes\n", + " | Axes containing resulting image\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Class methods defined here:\n", + " | \n", + " | from_xml_element(elem, surfaces, materials, get_universe) from builtins.type\n", + " | Generate cell from XML element\n", + " | \n", + " | Parameters\n", + " | ----------\n", + " | elem : lxml.etree._Element\n", + " | `` element\n", + " | surfaces : dict\n", + " | Dictionary mapping surface IDs to :class:`openmc.Surface` instances\n", + " | materials : dict\n", + " | Dictionary mapping material ID strings to :class:`openmc.Material`\n", + " | instances (defined in :math:`openmc.Geometry.from_xml`)\n", + " | get_universe : function\n", + " | Function returning universe (defined in\n", + " | :meth:`openmc.Geometry.from_xml`)\n", + " | \n", + " | Returns\n", + " | -------\n", + " | openmc.Cell\n", + " | Cell instance\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Readonly properties defined here:\n", + " | \n", + " | atoms\n", + " | \n", + " | bounding_box\n", + " | \n", + " | fill_type\n", + " | \n", + " | num_instances\n", + " | \n", + " | paths\n", + " | \n", + " | rotation_matrix\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Data descriptors defined here:\n", + " | \n", + " | fill\n", + " | \n", + " | name\n", + " | \n", + " | region\n", + " | \n", + " | rotation\n", + " | \n", + " | temperature\n", + " | \n", + " | translation\n", + " | \n", + " | volume\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Data and other attributes defined here:\n", + " | \n", + " | next_id = 3\n", + " | \n", + " | used_ids = {1, 2, 3}\n", + " | \n", + " | ----------------------------------------------------------------------\n", + " | Data descriptors inherited from openmc.mixin.IDManagerMixin:\n", + " | \n", + " | __dict__\n", + " | dictionary for instance variables (if defined)\n", + " | \n", + " | __weakref__\n", + " | list of weak references to the object (if defined)\n", + " | \n", + " | id\n", + "\n" + ] + } + ], + "source": [ + "help(openmc.Cell)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Attributes which have a default value will appear as `=` in the class/function definition. For example, in the above we see that the default name for a cell is the empty string, `''`.\n", + "\n", + "For classes, all of the member functions you can access will then be defined further down, with their associated documentation. To see documentation for just one specific function, you can use syntax like:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on method clone in module openmc.cell:\n", + "\n", + "clone(clone_materials=True, clone_regions=True, memo=None) method of openmc.cell.Cell instance\n", + " Create a copy of this cell with a new unique ID, and clones\n", + " the cell's region and fill.\n", + " \n", + " Parameters\n", + " ----------\n", + " clone_materials : bool\n", + " Whether to create separate copies of the materials filling cells\n", + " contained in this cell, or the material filling this cell.\n", + " clone_regions : bool\n", + " Whether to create separate copies of the regions bounding cells\n", + " contained in this cell, and the region bounding this cell.\n", + " memo : dict or None\n", + " A nested dictionary of previously cloned objects. This parameter\n", + " is used internally and should not be specified by the user.\n", + " \n", + " Returns\n", + " -------\n", + " clone : openmc.Cell\n", + " The clone of this cell\n", + "\n" + ] + } + ], + "source": [ + "my_cell = openmc.Cell()\n", + "help(my_cell.clone)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also query the type of a particular object using `type`. Note that we can also display the attributes for a particular object using `print`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "openmc.cell.Cell" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(my_cell)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cell\n", + "\tID =\t5\n", + "\tName =\t\n", + "\tFill =\tNone\n", + "\tRegion =\tNone\n", + "\tRotation =\tNone\n", + "\tTranslation =\tNone\n", + "\tVolume =\tNone\n", + "\n" + ] + } + ], + "source": [ + "print(my_cell)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to quickly see all of the member functions on a class, press `Tab` to perform tab-completion to view a drop-down list on all the options available to you. You can then do `Shift+Tab` to see the documentation for each function inline." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "#my_cell." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OpenMC Model\n", + "\n", + "The OpenMC `Model` class houses all of the pieces of a Monte Carlo simulation. We will assemble our pincell by progressively adding to a model, and then run that model." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "model = openmc.Model()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The `cross_sections.xml` file\n", + "\n", + "The `cross_sections.xml` tells OpenMC where it can find nuclide cross sections and $S(\\alpha,\\beta)$ tables. It serves the same purpose as MCNP's `xsdir` file and Serpent's `xsdata` file. As we mentioned, this can be set either by the `OPENMC_CROSS_SECTIONS` environment variable or the `Materials.cross_sections` attribute.\n", + "\n", + "Let's have a look at what's inside this file:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " ...\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ] + } + ], + "source": [ + "!cat $OPENMC_CROSS_SECTIONS | head -n 10\n", + "print(' ...')\n", + "!cat $OPENMC_CROSS_SECTIONS | tail -n 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining Materials\n", + "\n", + "Materials in OpenMC are defined as a set of nuclides with specified atom/weight fractions. To begin, we will create a material by making an instance of the `Material` class. In OpenMC, many objects, including materials, are identified by a \"unique ID\" (a positive integer). You can also give a material a `name` as well." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Material\n", + "\tID =\t1\n", + "\tName =\tzirconium\n", + "\tTemperature =\tNone\n", + "\tDensity =\tNone [sum]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\n" + ] + } + ], + "source": [ + "zirconium = openmc.Material(1, \"zirconium\")\n", + "print(zirconium)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One great feature in OpenMC's design is that the user interaction is designed to be as streamlined as possible. You don't *need* to set either an ID or a name (because often you can simply refer to an object with the actual object handle). If you were to create a material without any ID, OpenMC will assign a default for you." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Material\n", + "\tID =\t2\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\tNone [sum]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\n" + ] + } + ], + "source": [ + "mat = openmc.Material()\n", + "print(mat)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that an ID of 2 was automatically assigned. Let's now move on to adding nuclides to our material. The `Material` object has a method `add_element()` whose first argument is the name of the nuclide and second argument is the atom or weight fraction. We see that by default it assumes we want an atom fraction." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "#zirconium.add_element" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "zirconium.add_element('Zr', 1.0)\n", + "zirconium.set_density('g/cm3', 6.5)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Material\n", + "\tID =\t1\n", + "\tName =\tzirconium\n", + "\tTemperature =\tNone\n", + "\tDensity =\t6.5 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tZr90 =\t0.5145 [ao]\n", + "\tZr91 =\t0.1122 [ao]\n", + "\tZr92 =\t0.1715 [ao]\n", + "\tZr94 =\t0.1738 [ao]\n", + "\tZr96 =\t0.028 [ao]\n", + "\n" + ] + } + ], + "source": [ + "print(zirconium)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Element Expansion\n", + "We can see that OpenMC automatically inserted the natural abundances of the zirconium isotopes for us! How convenient! The way this feature works is as follows:\n", + "\n", + "- First, it checks whether `Materials.cross_sections` has been set, indicating the path to a `cross_sections.xml` file.\n", + "- If `Materials.cross_sections` isn't set, it looks for the `OPENMC_CROSS_SECTIONS` environment variable.\n", + "- If either of these are found, it scans the file to see what nuclides are actually available and will expand elements accordingly.\n", + "\n", + "Let's build our fuel material. For sake of illustration, let's suppose that we had O-16, but not natural oxygen in our fuel. Also note that OpenMC has a convenient feature to set the nuclide concentrations for weight percent enrichments in U-235." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# Add nuclides to uo2\n", + "uo2 = openmc.Material()\n", + "uo2.add_element('U', 1.0, enrichment=3.5)\n", + "uo2.add_nuclide('O16', 2.0)\n", + "uo2.set_density('g/cm3', 10.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With UO2 finished, let's now create materials for the coolant." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "water = openmc.Material()\n", + "water.add_element('H', 2.0)\n", + "water.add_nuclide('O16', 1.0)\n", + "water.set_density('g/cm3', 1.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An astute observer might now point out that this water material we just created will only use free-atom cross sections. We need to tell it to use an $S(\\alpha,\\beta)$ table so that the bound atom cross section is used at thermal energies. To do this, there's an `add_s_alpha_beta()` method." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "water.add_s_alpha_beta('c_H_in_H2O')" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/anovak/projects/cross_sections/endfb-vii.1-hdf5/cross_sections.xml\n", + "cross_sections.xml \u001b[1m\u001b[36mneutron\u001b[m\u001b[m \u001b[1m\u001b[36mphoton\u001b[m\u001b[m \u001b[1m\u001b[36mwmp\u001b[m\u001b[m\n", + "Ac225.h5 Ge76.h5 Sb121.h5\n", + "Ac226.h5 H1.h5 Sb123.h5\n", + "Ac227.h5 H2.h5 Sb124.h5\n", + "Ag107.h5 H3.h5 Sb125.h5\n", + "Ag109.h5 He3.h5 Sb126.h5\n", + "Ag110_m1.h5 He4.h5 Sc45.h5\n", + "Ag111.h5 Hf174.h5 Se74.h5\n", + "Al27.h5 Hf176.h5 Se76.h5\n", + "Am240.h5 Hf177.h5 Se77.h5\n", + "Am241.h5 Hf178.h5 Se78.h5\n", + "Am242.h5 Hf179.h5 Se79.h5\n", + "Am242_m1.h5 Hf180.h5 Se80.h5\n", + "Am243.h5 Hg196.h5 Se82.h5\n", + "Am244.h5 Hg198.h5 Si28.h5\n", + "Am244_m1.h5 Hg199.h5 Si29.h5\n", + "Ar36.h5 Hg200.h5 Si30.h5\n", + "Ar38.h5 Hg201.h5 Sm144.h5\n", + "Ar40.h5 Hg202.h5 Sm147.h5\n", + "As74.h5 Hg204.h5 Sm148.h5\n", + "As75.h5 Ho165.h5 Sm149.h5\n", + "Au197.h5 Ho166_m1.h5 Sm150.h5\n", + "B10.h5 I127.h5 Sm151.h5\n", + "B11.h5 I129.h5 Sm152.h5\n", + "Ba130.h5 I130.h5 Sm153.h5\n", + "Ba132.h5 I131.h5 Sm154.h5\n", + "Ba133.h5 I135.h5 Sn112.h5\n", + "Ba134.h5 In113.h5 Sn113.h5\n", + "Ba135.h5 In115.h5 Sn114.h5\n", + "Ba136.h5 Ir191.h5 Sn115.h5\n", + "Ba137.h5 Ir193.h5 Sn116.h5\n", + "Ba138.h5 K39.h5 Sn117.h5\n", + "Ba140.h5 K40.h5 Sn118.h5\n", + "Be7.h5 K41.h5 Sn119.h5\n", + "Be9.h5 Kr78.h5 Sn120.h5\n", + "Bi209.h5 Kr80.h5 Sn122.h5\n", + "Bk245.h5 Kr82.h5 Sn123.h5\n", + "Bk246.h5 Kr83.h5 Sn124.h5\n", + "Bk247.h5 Kr84.h5 Sn125.h5\n", + "Bk248.h5 Kr85.h5 Sn126.h5\n", + "Bk249.h5 Kr86.h5 Sr84.h5\n", + "Bk250.h5 La138.h5 Sr86.h5\n", + "Br79.h5 La139.h5 Sr87.h5\n", + "Br81.h5 La140.h5 Sr88.h5\n", + "C0.h5 Li6.h5 Sr89.h5\n", + "Ca40.h5 Li7.h5 Sr90.h5\n", + "Ca42.h5 Lu175.h5 Ta180.h5\n", + "Ca43.h5 Lu176.h5 Ta181.h5\n", + "Ca44.h5 Mg24.h5 Ta182.h5\n", + "Ca46.h5 Mg25.h5 Tb159.h5\n", + "Ca48.h5 Mg26.h5 Tb160.h5\n", + "Cd106.h5 Mn55.h5 Tc99.h5\n", + "Cd108.h5 Mo100.h5 Te120.h5\n", + "Cd110.h5 Mo92.h5 Te122.h5\n", + "Cd111.h5 Mo94.h5 Te123.h5\n", + "Cd112.h5 Mo95.h5 Te124.h5\n", + "Cd113.h5 Mo96.h5 Te125.h5\n", + "Cd114.h5 Mo97.h5 Te126.h5\n", + "Cd115_m1.h5 Mo98.h5 Te127_m1.h5\n", + "Cd116.h5 Mo99.h5 Te128.h5\n", + "Ce136.h5 N14.h5 Te129_m1.h5\n", + "Ce138.h5 N15.h5 Te130.h5\n", + "Ce139.h5 Na22.h5 Te132.h5\n", + "Ce140.h5 Na23.h5 Th227.h5\n", + "Ce141.h5 Nb93.h5 Th228.h5\n", + "Ce142.h5 Nb94.h5 Th229.h5\n", + "Ce143.h5 Nb95.h5 Th230.h5\n", + "Ce144.h5 Nd142.h5 Th231.h5\n", + "Cf246.h5 Nd143.h5 Th232.h5\n", + "Cf248.h5 Nd144.h5 Th233.h5\n", + "Cf249.h5 Nd145.h5 Th234.h5\n", + "Cf250.h5 Nd146.h5 Ti46.h5\n", + "Cf251.h5 Nd147.h5 Ti47.h5\n", + "Cf252.h5 Nd148.h5 Ti48.h5\n", + "Cf253.h5 Nd150.h5 Ti49.h5\n", + "Cf254.h5 Ni58.h5 Ti50.h5\n", + "Cl35.h5 Ni59.h5 Tl203.h5\n", + "Cl37.h5 Ni60.h5 Tl205.h5\n", + "Cm240.h5 Ni61.h5 Tm168.h5\n", + "Cm241.h5 Ni62.h5 Tm169.h5\n", + "Cm242.h5 Ni64.h5 Tm170.h5\n", + "Cm243.h5 Np234.h5 U230.h5\n", + "Cm244.h5 Np235.h5 U231.h5\n", + "Cm245.h5 Np236.h5 U232.h5\n", + "Cm246.h5 Np237.h5 U233.h5\n", + "Cm247.h5 Np238.h5 U234.h5\n", + "Cm248.h5 Np239.h5 U235.h5\n", + "Cm249.h5 O16.h5 U236.h5\n", + "Cm250.h5 O17.h5 U237.h5\n", + "Co58.h5 P31.h5 U238.h5\n", + "Co58_m1.h5 Pa229.h5 U239.h5\n", + "Co59.h5 Pa230.h5 U240.h5\n", + "Cr50.h5 Pa231.h5 U241.h5\n", + "Cr52.h5 Pa232.h5 V50.h5\n", + "Cr53.h5 Pa233.h5 V51.h5\n", + "Cr54.h5 Pb204.h5 W180.h5\n", + "Cs133.h5 Pb206.h5 W182.h5\n", + "Cs134.h5 Pb207.h5 W183.h5\n", + "Cs135.h5 Pb208.h5 W184.h5\n", + "Cs136.h5 Pd102.h5 W186.h5\n", + "Cs137.h5 Pd104.h5 Xe123.h5\n", + "Cu63.h5 Pd105.h5 Xe124.h5\n", + "Cu65.h5 Pd106.h5 Xe126.h5\n", + "Dy156.h5 Pd107.h5 Xe128.h5\n", + "Dy158.h5 Pd108.h5 Xe129.h5\n", + "Dy160.h5 Pd110.h5 Xe130.h5\n", + "Dy161.h5 Pm147.h5 Xe131.h5\n", + "Dy162.h5 Pm148.h5 Xe132.h5\n", + "Dy163.h5 Pm148_m1.h5 Xe133.h5\n", + "Dy164.h5 Pm149.h5 Xe134.h5\n", + "Er162.h5 Pm151.h5 Xe135.h5\n", + "Er164.h5 Pr141.h5 Xe136.h5\n", + "Er166.h5 Pr142.h5 Y89.h5\n", + "Er167.h5 Pr143.h5 Y90.h5\n", + "Er168.h5 Pu236.h5 Y91.h5\n", + "Er170.h5 Pu237.h5 Zn64.h5\n", + "Es251.h5 Pu238.h5 Zn65.h5\n", + "Es252.h5 Pu239.h5 Zn66.h5\n", + "Es253.h5 Pu240.h5 Zn67.h5\n", + "Es254.h5 Pu241.h5 Zn68.h5\n", + "Es254_m1.h5 Pu242.h5 Zn70.h5\n", + "Es255.h5 Pu243.h5 Zr90.h5\n", + "Eu151.h5 Pu244.h5 Zr91.h5\n", + "Eu152.h5 Pu246.h5 Zr92.h5\n", + "Eu153.h5 Ra223.h5 Zr93.h5\n", + "Eu154.h5 Ra224.h5 Zr94.h5\n", + "Eu155.h5 Ra225.h5 Zr95.h5\n", + "Eu156.h5 Ra226.h5 Zr96.h5\n", + "Eu157.h5 Rb85.h5 c_Al27.h5\n", + "F19.h5 Rb86.h5 c_Be.h5\n", + "Fe54.h5 Rb87.h5 c_Be_in_BeO.h5\n", + "Fe56.h5 Re185.h5 c_C6H6.h5\n", + "Fe57.h5 Re187.h5 c_D_in_D2O.h5\n", + "Fe58.h5 Rh103.h5 c_Fe56.h5\n", + "Fm255.h5 Rh105.h5 c_Graphite.h5\n", + "Ga69.h5 Ru100.h5 c_H_in_CH2.h5\n", + "Ga71.h5 Ru101.h5 c_H_in_CH4_liquid.h5\n", + "Gd152.h5 Ru102.h5 c_H_in_CH4_solid.h5\n", + "Gd153.h5 Ru103.h5 c_H_in_H2O.h5\n", + "Gd154.h5 Ru104.h5 c_H_in_ZrH.h5\n", + "Gd155.h5 Ru105.h5 c_O_in_BeO.h5\n", + "Gd156.h5 Ru106.h5 c_O_in_UO2.h5\n", + "Gd157.h5 Ru96.h5 c_SiO2_alpha.h5\n", + "Gd158.h5 Ru98.h5 c_U_in_UO2.h5\n", + "Gd160.h5 Ru99.h5 c_Zr_in_ZrH.h5\n", + "Ge70.h5 S32.h5 c_ortho_D.h5\n", + "Ge72.h5 S33.h5 c_ortho_H.h5\n", + "Ge73.h5 S34.h5 c_para_D.h5\n", + "Ge74.h5 S36.h5 c_para_H.h5\n" + ] + } + ], + "source": [ + "!ls $OPENMC_CROSS_SECTIONS\n", + "!ls /Users/anovak/projects/cross_sections/endfb-vii.1-hdf5/\n", + "!ls /Users/anovak/projects/cross_sections/endfb-vii.1-hdf5/neutron" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We are done with our materials -- now we just need to register them in our model." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Material\n", + "\tID =\t7\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t10.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tTrue\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tU234 =\t0.0003166930253944235 [ao]\n", + "\tU235 =\t0.03543164439454172 [ao]\n", + "\tU238 =\t0.964089368630351 [ao]\n", + "\tU236 =\t0.00016229394971280895 [ao]\n", + "\tO16 =\t2.0 [ao]\n", + ", Material\n", + "\tID =\t1\n", + "\tName =\tzirconium\n", + "\tTemperature =\tNone\n", + "\tDensity =\t6.5 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tZr90 =\t0.5145 [ao]\n", + "\tZr91 =\t0.1122 [ao]\n", + "\tZr92 =\t0.1715 [ao]\n", + "\tZr94 =\t0.1738 [ao]\n", + "\tZr96 =\t0.028 [ao]\n", + ", Material\n", + "\tID =\t8\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t1.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tH1 =\t1.99968852 [ao]\n", + "\tH2 =\t0.00031148 [ao]\n", + "\tO16 =\t0.999621 [ao]\n", + "\tO17 =\t0.000379 [ao]\n", + "]\n" + ] + } + ], + "source": [ + "model.materials = openmc.Materials([uo2, zirconium, water])\n", + "print(model.materials)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Material Modifications\n", + "\n", + "The power of OpenMC's Python API is the ability to easily modify and control your model." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Material\n", + "\tID =\t5\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t1.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tH1 =\t1.99968852 [ao]\n", + "\tH2 =\t0.00031148 [ao]\n", + "\tO16 =\t1.0 [ao]\n", + "\n", + "Material\n", + "\tID =\t5\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t1.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tH1 =\t1.99968852 [ao]\n", + "\tH2 =\t0.00031148 [ao]\n", + "\n", + "Material\n", + "\tID =\t5\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t1.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tH1 =\t1.99968852 [ao]\n", + "\tH2 =\t0.00031148 [ao]\n", + "\tO16 =\t0.999621 [ao]\n", + "\tO17 =\t0.000379 [ao]\n", + "\n" + ] + } + ], + "source": [ + "print(water)\n", + "\n", + "water.remove_nuclide('O16')\n", + "\n", + "print(water)\n", + "\n", + "water.add_element('O', 1.0)\n", + "\n", + "print(water)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that now O16 and O17 were automatically added. O18 is missing because our cross sections file (which is based on ENDF/B-VII.1) doesn't have O18." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Python Lists\n", + "\n", + "Many of OpenMC's classes are derived from Python's `list` type, and can therefore be appended, popped, etc." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(isinstance(model.materials, list))" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "new_fuel = openmc.Material()\n", + "new_fuel.add_nuclide('Pu239', 1.0)\n", + "new_fuel.add_nuclide('Si28', 2.0)\n", + "new_fuel.set_density('g/cm3', 9.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Material\n", + "\tID =\t8\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t9.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tSi28 =\t2.0 [ao]\n", + ", Material\n", + "\tID =\t8\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t9.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tFalse\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tSi28 =\t2.0 [ao]\n", + ", Material\n", + "\tID =\t9\n", + "\tName =\t\n", + "\tTemperature =\tNone\n", + "\tDensity =\t9.0 [g/cm3]\n", + "\tVolume =\tNone [cm^3]\n", + "\tDepletable =\tTrue\n", + "\tS(a,b) Tables \n", + "\tNuclides \n", + "\tPu239 =\t1.0 [ao]\n", + "\tSi28 =\t2.0 [ao]\n", + "]\n" + ] + } + ], + "source": [ + "model.materials.append(new_fuel)\n", + "print(model.materials)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining Geometry\n", + "\n", + "We now need to define the geometry. One way to do so is to use constructive solid geometry (CSG), also known as combinatorial geometry. The object that allows us to assign a material to a region of space is called a `Cell` (same concept in MCNP, for those familiar). There are four stages in building a cell:\n", + "\n", + "#### Surfaces\n", + "In order to define a region that we can assign to a cell, we must first define surfaces which bound the region. A *surface* is a locus of zeros of a function of Cartesian coordinates $x$, $y$, and $z$, e.g.\n", + "\n", + "- A plane perpendicular to the x axis: $x - x_0 = 0$\n", + "- A cylinder parallel to the z axis: $(x - x_0)^2 + (y - y_0)^2 - R^2 = 0$\n", + "- A sphere: $(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0$\n", + "\n", + "The full [list of available surfaces](https://docs.openmc.org/en/stable/pythonapi/base.html#building-geometry) is as follows.\n", + "\n", + "Planes:\n", + "\n", + "- `openmc.Plane` — An arbitrary plane of the form $Ax + By + Cz = D$\n", + "- `openmc.XPlane` — A plane perpendicular to the x axis of the form $x - x_0 = 0$\n", + "- `openmc.YPlane` — A plane perpendicular to the y axis of the form $y - y_0 = 0$\n", + "- `openmc.ZPlane` — A plane perpendicular to the z axis of the form $z - z_0 = 0$\n", + "\n", + "Quadrics:\n", + "\n", + "- `openmc.XCylinder` — An infinite cylinder whose length is parallel to the x-axis of the form $(y - y_0)^2 + (z - z_0)^2 = r^2$\n", + "- `openmc.YCylinder` — An infinite cylinder whose length is parallel to the x-axis of the form $(x - x_0)^2 + (z - z_0)^2 = r^2$\n", + "- `openmc.ZCylinder` — An infinite cylinder whose length is parallel to the x-axis of the form $(x - x_0)^2 + (y - y_0)^2 = r^2$\n", + "- `openmc.Sphere` — A sphere of the form $(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = r^2$\n", + "- `openmc.XCone` — A cone parallel to the x-axis of the form $(y - y_0)^2 + (z - z_0)^2 = r^2 (x - x_0)^2$\n", + "- `openmc.YCone` — A cone parallel to the y-axis of the form $(x - x_0)^2 + (z - z_0)^2 = r^2 (y - y_0)^2$\n", + "- `openmc.ZCone` — A cone parallel to the z-axis of the form $(x - x_0)^2 + (y - y_0)^2 = r^2 (z - z_0)^2$\n", + "- `openmc.Quadric` — A generic quadric surface\n", + "\n", + "Torii:\n", + "\n", + "- `openmc.XTorus` — A torus of the form $(x - x_0)^2/B^2 + (\\sqrt{(y - y_0)^2 + (z - z_0)^2} - A)^2/C^2 - 1 = 0$\n", + "- `openmc.YTorus` — A torus of the form $(y - y_0)^2/B^2 + (\\sqrt{(x - x_0)^2 + (z - z_0)^2} - A)^2/C^2 - 1 = 0$\n", + "- `openmc.ZTorus` — A torus of the form $(z - z_0)^2/B^2 + (\\sqrt{(x - x_0)^2 + (y - y_0)^2} - A)^2/C^2 - 1 = 0$\n", + "\n", + "#### Half-Spaces\n", + "A surface *half-space* is the region whose points satisfy a positive or negative inequality of the surface equation. For example, for a sphere of radius one centered at the origin, the surface equation is $f(x,y,z) = x^2 + y^2 + z^2 - 1 = 0$. Thus, we say that the negative half-space of the sphere is defined as the collection of points satisfying $f(x,y,z) < 0$, which one can reason is the inside of the sphere. Conversely, the positive half-space of the sphere would correspond to all points outside of the sphere.\n", + "\n", + "#### Regions\n", + "A region is then a combination of (or just one) half-spaces.\n", + "\n", + "#### Fills\n", + "Finally, a cell is complete once we have defined what is _filling_ the cell, which may be one of:\n", + "\n", + "- material\n", + "- nothing (`None`), or vacuum/void\n", + "- universe\n", + "- lattice" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "sphere = openmc.Sphere(r=1.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that by default the sphere is centered at the origin so we didn't have to supply `x0`, `y0`, or `z0` arguments. Strictly speaking, we could have omitted `r` as well since it defaults to one. To get the negative or positive half-space, we simply need to apply the `-` or `+` unary operators, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "openmc.surface.Halfspace" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "inside_sphere = -sphere\n", + "outside_sphere = +sphere\n", + "type(inside_sphere)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's see if `inside_sphere` actually contains points inside the sphere:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True False\n", + "False True\n" + ] + } + ], + "source": [ + "print((0,0,0) in inside_sphere, (0,0,2) in inside_sphere)\n", + "print((0,0,0) in outside_sphere, (0,0,2) in outside_sphere)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Everything works as expected! Now that we understand how to create half-spaces, we can create more complex volumes by combining half-spaces using Boolean operators: `&` (intersection), `|` (union), and `~` (complement):\n", + "\n", + "- `&`: logical AND\n", + "- `|`: logical OR\n", + "- `~`: logical NOT\n", + "\n", + "For example, let's say we want to define a region that is the top part of the sphere (all points inside the sphere that have $z > 0$." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "z_plane = openmc.ZPlane(0)\n", + "northern_hemisphere = -sphere & +z_plane" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "cell = openmc.Cell()\n", + "cell.region = northern_hemisphere\n", + "\n", + "# or...\n", + "cell = openmc.Cell(region=northern_hemisphere)\n", + "\n", + "cell.fill = water" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Universes and in-line plotting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A collection of cells is known as a universe and can be used as a repeatable unit when creating a model. Although we don't need it yet, the benefit of creating a universe is that we can visualize our geometry while we're creating it." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "universe = openmc.Universe()\n", + "universe.add_cell(cell)\n", + "\n", + "# this also works\n", + "universe = openmc.Universe(cells=[cell])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Universe` object has a `plot` method that will display our the universe as current constructed:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "dlsym(0x2041e1c40, openmc_sample_external_source): symbol not found", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[44], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43muniverse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot\u001b[49m\u001b[43m(\u001b[49m\u001b[43mwidth\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m2.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m2.0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/projects/openmc/openmc/universe.py:442\u001b[0m, in \u001b[0;36mUniverse.plot\u001b[0;34m(self, origin, width, pixels, basis, color_by, colors, seed, openmc_exec, axes, legend, axis_units, legend_kwargs, outline, **kwargs)\u001b[0m\n\u001b[1;32m 439\u001b[0m model\u001b[38;5;241m.\u001b[39mplots\u001b[38;5;241m.\u001b[39mappend(plot)\n\u001b[1;32m 441\u001b[0m \u001b[38;5;66;03m# Run OpenMC in geometry plotting mode\u001b[39;00m\n\u001b[0;32m--> 442\u001b[0m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mplot_geometry\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcwd\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtmpdir\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mopenmc_exec\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mopenmc_exec\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 444\u001b[0m \u001b[38;5;66;03m# Read image from file\u001b[39;00m\n\u001b[1;32m 445\u001b[0m img_path \u001b[38;5;241m=\u001b[39m Path(tmpdir) \u001b[38;5;241m/\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mplot_\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mplot\u001b[38;5;241m.\u001b[39mid\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.png\u001b[39m\u001b[38;5;124m'\u001b[39m\n", + "File \u001b[0;32m~/projects/openmc/openmc/model/model.py:828\u001b[0m, in \u001b[0;36mModel.plot_geometry\u001b[0;34m(self, output, cwd, openmc_exec)\u001b[0m\n\u001b[1;32m 824\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe Model.plots attribute must be specified \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 825\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mbefore executing this method!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 827\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m _change_directory(Path(cwd)):\n\u001b[0;32m--> 828\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mis_initialized\u001b[49m:\n\u001b[1;32m 829\u001b[0m \u001b[38;5;66;03m# Compute the volumes\u001b[39;00m\n\u001b[1;32m 830\u001b[0m openmc\u001b[38;5;241m.\u001b[39mlib\u001b[38;5;241m.\u001b[39mplot_geometry(output)\n\u001b[1;32m 831\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "File \u001b[0;32m~/projects/openmc/openmc/model/model.py:160\u001b[0m, in \u001b[0;36mModel.is_initialized\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 157\u001b[0m \u001b[38;5;129m@property\u001b[39m\n\u001b[1;32m 158\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mis_initialized\u001b[39m(\u001b[38;5;28mself\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mbool\u001b[39m:\n\u001b[1;32m 159\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 160\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mopenmc\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlib\u001b[39;00m\n\u001b[1;32m 161\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m openmc\u001b[38;5;241m.\u001b[39mlib\u001b[38;5;241m.\u001b[39mis_initialized\n\u001b[1;32m 162\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mImportError\u001b[39;00m:\n", + "File \u001b[0;32m~/projects/openmc/openmc/lib/__init__.py:59\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m c_bool\u001b[38;5;241m.\u001b[39min_dll(_dll, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mMCPL_ENABLED\u001b[39m\u001b[38;5;124m\"\u001b[39m)\u001b[38;5;241m.\u001b[39mvalue\n\u001b[1;32m 58\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01merror\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n\u001b[0;32m---> 59\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mcore\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n\u001b[1;32m 60\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnuclide\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n\u001b[1;32m 61\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mmaterial\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;241m*\u001b[39m\n", + "File \u001b[0;32m~/projects/openmc/openmc/lib/core.py:102\u001b[0m\n\u001b[1;32m 100\u001b[0m _dll\u001b[38;5;241m.\u001b[39mopenmc_global_bounding_box\u001b[38;5;241m.\u001b[39mrestype \u001b[38;5;241m=\u001b[39m c_int\n\u001b[1;32m 101\u001b[0m _dll\u001b[38;5;241m.\u001b[39mopenmc_global_bounding_box\u001b[38;5;241m.\u001b[39merrcheck \u001b[38;5;241m=\u001b[39m _error_handler\n\u001b[0;32m--> 102\u001b[0m \u001b[43m_dll\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopenmc_sample_external_source\u001b[49m\u001b[38;5;241m.\u001b[39margtypes \u001b[38;5;241m=\u001b[39m [c_size_t, POINTER(c_uint64), POINTER(_SourceSite)]\n\u001b[1;32m 103\u001b[0m _dll\u001b[38;5;241m.\u001b[39mopenmc_sample_external_source\u001b[38;5;241m.\u001b[39mrestype \u001b[38;5;241m=\u001b[39m c_int\n\u001b[1;32m 104\u001b[0m _dll\u001b[38;5;241m.\u001b[39mopenmc_sample_external_source\u001b[38;5;241m.\u001b[39merrcheck \u001b[38;5;241m=\u001b[39m _error_handler\n", + "File \u001b[0;32m/usr/local/Cellar/python@3.11/3.11.2_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/ctypes/__init__.py:389\u001b[0m, in \u001b[0;36mCDLL.__getattr__\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 387\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m name\u001b[38;5;241m.\u001b[39mstartswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__\u001b[39m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;129;01mand\u001b[39;00m name\u001b[38;5;241m.\u001b[39mendswith(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m__\u001b[39m\u001b[38;5;124m'\u001b[39m):\n\u001b[1;32m 388\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mAttributeError\u001b[39;00m(name)\n\u001b[0;32m--> 389\u001b[0m func \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[38;5;21;43m__getitem__\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 390\u001b[0m \u001b[38;5;28msetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m, name, func)\n\u001b[1;32m 391\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m func\n", + "File \u001b[0;32m/usr/local/Cellar/python@3.11/3.11.2_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/ctypes/__init__.py:394\u001b[0m, in \u001b[0;36mCDLL.__getitem__\u001b[0;34m(self, name_or_ordinal)\u001b[0m\n\u001b[1;32m 393\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__getitem__\u001b[39m(\u001b[38;5;28mself\u001b[39m, name_or_ordinal):\n\u001b[0;32m--> 394\u001b[0m func \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_FuncPtr\u001b[49m\u001b[43m(\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname_or_ordinal\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 395\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(name_or_ordinal, \u001b[38;5;28mint\u001b[39m):\n\u001b[1;32m 396\u001b[0m func\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m \u001b[38;5;241m=\u001b[39m name_or_ordinal\n", + "\u001b[0;31mAttributeError\u001b[0m: dlsym(0x2041e1c40, openmc_sample_external_source): symbol not found" + ] + } + ], + "source": [ + "universe.plot(width=(2.0, 2.0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, the plot will appear in the $x$-$y$ plane. We can change that with the `basis` argument." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "universe.plot(width=(2.0, 2.0), basis='xz')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we have particular fondness for, say, fuchsia, we can tell the `plot()` method to make our cell that color." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "universe.plot(width=(2.0, 2.0), basis='xz',\n", + " colors={cell: 'fuchsia'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Boundary Conditions\n", + "\n", + "When you create a surface, by default particles that pass through the surface will consider it to be transmissive, i.e. they pass through the surface freely. To specify boundary conditions, you simply need to set the `Surface.boundary_type` to one of:\n", + "\n", + "- `transmission` (default)\n", + "- `vacuum`\n", + "- `reflective`\n", + "- `periodic` (either rotational or translational)\n", + "- `white` (isotropic angular flux)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "northern_hemisphere.boundary_type = 'vacuum'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pin cell geometry\n", + "\n", + "We now have enough knowledge to create our pin-cell. We need three surfaces to define the fuel and clad:\n", + "\n", + "1. The outer surface of the fuel -- a cylinder parallel to the z axis\n", + "2. The inner surface of the clad -- same as above\n", + "3. The outer surface of the clad -- same as above\n", + "\n", + "These three surfaces will all be instances of `openmc.ZCylinder`, each with a different radius according to the specification." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fuel_outer_radius = openmc.ZCylinder(r=0.39)\n", + "clad_inner_radius = openmc.ZCylinder(r=0.40)\n", + "clad_outer_radius = openmc.ZCylinder(r=0.46)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the surfaces created, we can now take advantage of the built-in operators on surfaces to create regions for the fuel, the gap, and the clad:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fuel_region = -fuel_outer_radius\n", + "gap_region = +fuel_outer_radius & -clad_inner_radius\n", + "clad_region = +clad_inner_radius & -clad_outer_radius" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can create corresponding cells that assign materials to these regions. As with materials, cells have unique IDs that are assigned either manually or automatically. Note that the gap cell doesn't have any material assigned (it is void by default)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fuel = openmc.Cell(name='fuel')\n", + "fuel.fill = uo2\n", + "fuel.region = fuel_region\n", + "\n", + "gap = openmc.Cell(name='air gap')\n", + "gap.region = gap_region\n", + "\n", + "clad = openmc.Cell(name='clad')\n", + "clad.fill = zirconium\n", + "clad.region = clad_region" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we need to handle the coolant outside of our fuel pin. To do this, we create x- and y-planes that bound the geometry." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pitch = 1.26\n", + "left = openmc.XPlane(-pitch/2, boundary_type='reflective')\n", + "right = openmc.XPlane(pitch/2, boundary_type='reflective')\n", + "bottom = openmc.YPlane(-pitch/2, boundary_type='reflective')\n", + "top = openmc.YPlane(pitch/2, boundary_type='reflective')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The water region is going to be everything outside of the clad outer radius and within the box formed as the intersection of four half-spaces." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "water_region = +left & -right & +bottom & -top & +clad_outer_radius\n", + "\n", + "moderator = openmc.Cell(name='moderator')\n", + "moderator.fill = water\n", + "moderator.region = water_region" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The final step is to assign the cells we created to a universe and tell OpenMC that this universe is the \"root\" universe in our geometry. The `Geometry` is the final object that is actually exported to XML." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "root_universe = openmc.Universe(cells=(fuel, gap, clad, moderator))\n", + "\n", + "geometry = openmc.Geometry(root_universe)\n", + "geometry.export_to_xml()\n", + "!cat geometry.xml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Starting source and settings\n", + "\n", + "The Python API has a module `openmc.stats` with various univariate and multivariate probability distributions. We can use these distributions to create a starting source using the `openmc.Source` object. One can independently specify the spatial distribution (`space`), the angular distribution (`angle`), the energy distribution (`energy`), and the time distribution (`time`). For this example, we'll only specify the spatial distribution as a single point." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a point source\n", + "point = openmc.stats.Point((0, 0, 0))\n", + "source = openmc.Source(space=point)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's create a `Settings` object and give it the source we created along with specifying how many batches and particles we want to run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "settings = openmc.Settings()\n", + "settings.source = source\n", + "settings.batches = 100\n", + "settings.inactive = 10\n", + "settings.particles = 1000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "settings.export_to_xml()\n", + "!cat settings.xml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running OpenMC\n", + "\n", + "Running OpenMC from Python can be done using the `openmc.run()` function. This function allows you to set the number of MPI processes and OpenMP threads, if need be." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "openmc.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Great! OpenMC already told us our k-effective." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Geometry plotting\n", + "\n", + "We saw before that we could call the `Universe.plot()` method to show a universe while we were creating our geometry. There is also a built-in plotter in the codebase that is much faster than the Python plotter and has more options. The interface looks somewhat similar to the `Universe.plot()` method. Instead though, we create `Plot` instances, assign them to a `Plots` collection, export it to XML, and then run OpenMC in geometry plotting mode. As an example, let's specify that we want the plot to be colored by material (rather than by cell) and we assign yellow to fuel and blue to water." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot = openmc.Plot()\n", + "plot.filename = 'pinplot'\n", + "plot.width = (pitch, pitch)\n", + "plot.pixels = (200, 200)\n", + "plot.color_by = 'material'\n", + "plot.colors = {uo2: 'yellow', water: 'blue'}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With our plot created, we need to add it to a `Plots` collection which can be exported to XML." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plots = openmc.Plots([plot])\n", + "plots.export_to_xml()\n", + "!cat plots.xml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can run OpenMC in plotting mode by calling the `plot_geometry()` function. Under the hood this is calling `openmc --plot`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "openmc.plot_geometry()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can use functionality from IPython to display the `.png` image inline in our notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import Image\n", + "Image(\"pinplot.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OpenMC also provides us with a method on the `Plot` class that simplifies the workflow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot.to_ipython_image()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `Model` class\n", + "\n", + "So far, we've seen that to create and simulate a model, we had to create an instance of `Geometry`, `Materials`, and `Settings` and call the `export_to_xml` method on each of them. OpenMC also provides a `Model` class that aggregates these classes together an provides a single `export_to_xml` method that will export all files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Combine the three classes into a single model\n", + "model = openmc.Model()\n", + "model.geometry = geometry\n", + "model.materials = materials\n", + "model.settings = settings\n", + "\n", + "# Export all at once\n", + "model.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Model` class also has `run` method that will both export XML files *and* run a simulation, returning the name of the last statepoint file that was written:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we'll see later, the `Model` class has other useful features and is also needed for performing depletion/activation calculations." + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/Pincell.ipynb b/Pincell.ipynb new file mode 100644 index 000000000..f58f7e3b5 --- /dev/null +++ b/Pincell.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b38d9aa8-978c-412d-a828-3e3a134b6b51", + "metadata": { + "tags": [] + }, + "source": [ + "# Modeling a Pincell\n", + "In this module, we'll demonstrate the basic features of the Python API for constructing input files and running OpenMC. In it, we will show how to create a basic reflective pincell model that is equivalent to modeling an infinite array of fuel pins in a pressurized water reactor (PWR). We highly recommend having a copy of the [Python API reference documentation](https://docs.openmc.org/en/stable/pythonapi/index.html) open in another browser tab that you can refer to." + ] + }, + { + "cell_type": "markdown", + "id": "d25437f6-a52e-47bb-9ee2-81ee9d61892d", + "metadata": { + "tags": [] + }, + "source": [ + "For this example, we'll create a simple pin-cell that is composed of:\n", + "- UO2 at 10 g/cm3\n", + " - U235 at 0.02115 a/o\n", + " - U238 at 0.86032 a/o\n", + " - O16 at 0.11852 a/o\n", + "- Zirconium clad at 6.6 g/cm3\n", + " - Elemental Zr\n", + "- Water moderator at 1.0 g/cm3\n", + " - H1 at 2.0 a/o\n", + " - O16 at 1.0 a/o\n", + "\n", + "The dimensions of our fuel pin will be as follows:\n", + "- Fuel outer radius = 0.39 cm\n", + "- Clad inner radius = 0.40 cm\n", + "- Clad outer radius = 0.46 cm\n", + "- Fuel pin pitch = 1.26 cm" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6c1e32c0-ca56-4eb9-8763-b9f08695a268", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/usr/local/Cellar/jupyterlab/3.6.2/libexec/bin/python3.11\n" + ] + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "140b6b6a-b2ef-4968-bb9a-a22fdfbf5a71", + "metadata": {}, + "outputs": [], + "source": [ + "import openmc" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8c1399a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}