From fc40bffee9b337cd54bb0747d0a6abe23411a6a7 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Tue, 22 Oct 2024 17:29:54 -0500 Subject: [PATCH 01/28] Enabled sphinx doctest. --- doc/source/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index b6d52d1b..9530fda1 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -36,6 +36,7 @@ "sphinx.ext.napoleon", "sphinx.ext.intersphinx", "sphinx.ext.extlinks", + "sphinx.ext.doctest", "sphinx_sitemap", "sphinx_favicon", ] From 6b6c85fffefe599aa951dfb7e360204166218127 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Tue, 22 Oct 2024 17:37:15 -0500 Subject: [PATCH 02/28] Added dependencies to run demos and test them. --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index baab33d5..a1a85bea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,8 @@ develop = [ "montepy[doc]", "montepy[format]", ] +demos = ["jupyter"] +demo-test = ["papermill"] [project.urls] From 709378ef9fb3f5d7cb9ce2c02530542f0cc0387b Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Tue, 22 Oct 2024 17:37:37 -0500 Subject: [PATCH 03/28] Made a workflow to test all documentation and demos. --- .github/workflows/main.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 630f96d9..7e35b503 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -95,7 +95,7 @@ jobs: github-token: ${{ secrets.github_token }} - doc-test: + doc-build: runs-on: ubuntu-latest steps: @@ -124,6 +124,28 @@ jobs: make linkcheck - name: test sitemap run: python .github/scripts/check_sitemap.py + + doc-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + - name: set up python 3.12 + uses: actions/setup-python@v5 + with: + python-version: 3.12 + - run: pip install . montepy[doc,build,demo-test] + - run: python -m doctest README.md + name: Test readme code + - run: | + cd doc + make doctest + name: Test all example code in documentation. + - run: for file in demo/*.ipynb; do papermill $file demo/foo.ipynb; done + name: Test all demo notebooks format-test: runs-on: ubuntu-latest From f06775a0941149b5da89b84a8e27ef29e49f89ac Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Tue, 22 Oct 2024 17:39:43 -0500 Subject: [PATCH 04/28] Update main.yml Fixed typo. --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e35b503..726d4e76 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -141,8 +141,8 @@ jobs: - run: python -m doctest README.md name: Test readme code - run: | - cd doc - make doctest + cd doc + make doctest name: Test all example code in documentation. - run: for file in demo/*.ipynb; do papermill $file demo/foo.ipynb; done name: Test all demo notebooks From 8210673f6b9ec52237d534c3fd8985d29bcf87fb Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Wed, 23 Oct 2024 09:12:53 -0500 Subject: [PATCH 05/28] Fixed simple bugs in documentation demos. --- doc/source/developing.rst | 12 ++-- doc/source/starting.rst | 129 +++++++++++++++++++++----------------- doc/source/tests | 1 + doc/source/tricks.rst | 4 +- doc/source/utilities.rst | 3 +- 5 files changed, 84 insertions(+), 65 deletions(-) create mode 120000 doc/source/tests diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 05f0e59b..4468ce42 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -426,7 +426,6 @@ For example the ``Surface`` number setter looks like: assert number > 0 if self._problem: self._problem.surfaces.check_number(number) - self._mutated = True self._surface_number = number @@ -524,7 +523,6 @@ For example the ``Surface`` number setter looks like:: assert number > 0 if self._problem: self._problem.surfaces.check_number(number) - self._mutated = True self._surface_number = number Data Cards that Modify Cells :class:`~montepy.data_inputs.cell_modifier.CellModifierInput` @@ -727,14 +725,14 @@ For a ``Surface`` it is owned by the ``Surfaces`` collection owned by the ``MCNP A cell then borrows this object by referencing it in its own ``Surfaces`` collections. For example: +>>> import montepy >>> # owns ->>> x = Cell() ->>> hex(id(x)) -'0x7f4c6c89dc30' +>>> x = montepy.Cell() +>>> id = hex(id(x)) >>> # borrows >>> new_list = [x] ->>> hex(id(new_list[0])) -'0x7f4c6c89dc30' +>>> id == hex(id(new_list[0])) +True The general principle is that only one-directional pointers should be used, and bidirectional pointers should never be used. diff --git a/doc/source/starting.rst b/doc/source/starting.rst index f6f362d9..05693fed 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -1,6 +1,9 @@ Getting Started with MontePy ============================ +.. testsetup:: * + import montepy + MontePy is a python API for reading, editing, and writing MCNP input files. It does not run MCNP nor does it parse MCNP output files. The library provides a semantic interface for working with input files, or our preferred terminology: problems. @@ -74,9 +77,9 @@ MontePy offers the :func:`montepy.read_input` (actually :func:`~montepy.input_pa It will read the specified MCNP input file, and return an MontePy :class:`~montepy.mcnp_problem.MCNP_Problem` object. >>> import montepy ->>> problem = montepy.read_input("foo.imcnp") +>>> problem = montepy.read_input("tests/inputs/test.imcnp") >>> len(problem.cells) -4 +5 Writing a File -------------- @@ -101,8 +104,11 @@ This can be changed by ``overwrite=True``. The method :func:`~montepy.mcnp_problem.MCNP_Problem.write_problem` also accepts an open file handle, stream, or other object with a ``write()`` method. ->>> with open("/path/to/file", "w") as fh: +>>> with open("foo_bar.imcnp", "w") as fh: ... problem.write_problem(fh) +>>> new_problem = montepy.read_input("foo_bar.imcnp") +>>> len(new_problem.cells) +5 If no changes are made to the problem in MontePy, the entire file should just be parroted out as it was in the original file @@ -129,8 +135,9 @@ For example say we have this simple MCNP input file (saved as foo.imcnp) :: TR1 0 0 1.0 TR2 0 0 1.00001 -We can then open this file in MontePy, and then modify it slightly, and save it again:: +We can then open this file in MontePy, and then modify it slightly, and save it again: +.. doctest:: import montepy problem = montepy.read_input("foo.imcnp") problem.cells[1].number = 5 @@ -220,6 +227,7 @@ by its number. So say you want to access cell 2 from a problem it is accessible quickly by: +>>> prob = montepy.read_input("tests/inputs/test.imcnp") >>> prob.cells[2] CELL: 2 MATERIAL: 2, ['iron'] @@ -244,27 +252,13 @@ The ``NumberedObjectCollection`` has various mechanisms internally to avoid numb (two objects having the same number). >>> import montepy ->>> prob = montepy.read_input("foo.i") +>>> prob = montepy.read_input("tests/inputs/test.imcnp") >>> cell = montepy.Cell() >>> cell.number = 2 -prob.cells.append(cell) ---------------------------------------------------------------------------- -NumberConflictError Traceback (most recent call last) - in -----> 1 prob.cells.append(cell) -~/dev/montepy/doc/montepy/numbered_object_collection.py in append(self, obj) - 130 assert isinstance(obj, self._obj_class) - 131 if obj.number in self.numbers: ---> 132 raise NumberConflictError( - 133 ( - 134 "There was a numbering conflict when attempting to add " -NumberConflictError: There was a numbering conflict when attempting to add CELL: 2 -None - to . Conflict was with CELL: 2 -None -SURFACE: 4, CZ -SURFACE: 5, PZ -SURFACE: 6, PZ +>>> prob.cells.append(cell) +Traceback (most recent call last): + ... +montepy.errors.NumberConflictError: Number 2 is already in use for the collection: by CELL: 2, mat: 2, DENS: 8.0 atom/b-cm There are a number of tools to avoid this though: @@ -298,10 +292,14 @@ You can iterate over a generator, as well as check if an item is in the generato First it is iterable: +>>> problem = montepy.read_input("tests/inputs/test.imcnp") >>> for number in problem.cells.numbers: ->>> print(number) +... print(number) 1 2 +3 +99 +5 You can also check if a number is in use: @@ -315,12 +313,15 @@ by making "stale" information. This can be done by making a copy of it with ``list()``. >>> for num in problem.cells.numbers: ->>> print(num) +... print(num) 1 2 +3 +99 +5 >>> numbers = list(problem.cells.numbers) >>> numbers -[1,2] +[1, 2, 3, 99, 5] >>> problem.cells[1].number = 1000 >>> 1000 in problem.cells.numbers True @@ -372,11 +373,11 @@ So there is a convenient way to update a surface, but how do you easily get the For instance what if you want to shift a cell up in Z by 10 cm? It would be horrible to have to get each surface by their number, and hoping you don't change the numbers along the way. -One way you might think of is: oh let's just filter the surfaces by their type?:: +One way you might think of is: oh let's just filter the surfaces by their type?: - for surface in cell.surfaces: - if surface.surface_type == montepy.surfaces.surface_type.SurfaceType.PZ: - surface.location += 10 +>>> for surface in cell.surfaces: +... if surface.surface_type == montepy.surfaces.surface_type.SurfaceType.PZ: +... surface.location += 10 Wow that's rather verbose. This was the only way to do this with the API for awhile. @@ -387,8 +388,8 @@ These are very easy to find: they are just the lower case version of the MCNP surface mnemonic. This previous code is much simpler now:: - for surface in cell.surfaces.pz: - surface.location += 10 +>>> for surface in cell.surfaces.pz: +... surface.location += 10 Cells ----- @@ -403,18 +404,26 @@ For example: ``cell.importance.neutron`` or ``cell.importance.photon``. For a complete list see :class:`~montepy.particle.Particle`. You can also quickly get the information by passing an instance of :class:`~montepy.particle.Particle` as a key to importance. -For example: :: +For example: - for particle in problem.mode: - print(cell.importance[particle]) - print(cell.importance[montepy.Particle.NEUTRON]) +>>> for particle in sorted(problem.mode): +... print(particle, cell.importance[particle]) +neutron 0.0 +photon 0.0 +>>> print(cell.importance[montepy.Particle.NEUTRON]) +0.0 There's also a lot of convenient ways to do bulk modifications. There is the :func:`~montepy.data_inputs.importance.Importance.all` property that lets you set the importance for all particles in the problem at once. -For example: :: +For example: - problem.set_mode("n p") - cell.importance.all = 2.0 +>>> problem.set_mode("n p e") +>>> cell.importance.all = 2.0 +>>> for particle in sorted(problem.mode): +... print(particle, cell.importance[particle]) +electron 2.0 +neutron 2.0 +photon 2.0 This will set the importances for the neutron and photon. @@ -453,13 +462,19 @@ If the cell density is set to a mass density ``cell.atom_density`` will return ` Setting the value for one of these densities will change the density mode. MontePy does not convert mass density to atom density and vice versa. +>>> problem = montepy.read_input("tests/inputs/test.imcnp") +>>> cell = problem.cells[3] >>> cell.mass_density -9.8 +1.0 >>> cell.atom_density -None +Traceback (most recent call last): + ... +AttributeError: Cell 3 is in mass density.. Did you mean: 'mass_density'? >>> cell.atom_density = 0.5 >>> cell.mass_density -None +Traceback (most recent call last): + ... +AttributeError: Cell 3 is in atom density.. Did you mean: 'atom_density'? Geometry ^^^^^^^^ @@ -496,16 +511,19 @@ This is done very simply and pythonic. For a :class:`~montepy.surfaces.surface.Surface` you just need to mark the surface as positive (``+``) or negative (``-``) (using the unary operators). This actually creates a new object so don't worry about modifying the surface. +>>> bottom_plane = montepy.surfaces.surface.Surface() +>>> top_plane = montepy.surfaces.surface.Surface() >>> type(+bottom_plane) -montepy.surfaces.half_space.UnitHalfSpace + >>> type(-bottom_plane) -montepy.surfaces.half_space.UnitHalfSpace + For cells the plus/minus operator doesn't make sense. Instead you use the binary not operator (``~``). +>>> capsule_cell = montepy.Cell() >>> type(~capsule_cell) -montepy.surfaces.half_space.HalfSpace + Combining Half-Spaces @@ -581,14 +599,15 @@ The new cell will attempt to use ``starting_number`` as its number. If this number is taken ``step`` will be added to it until an available number is found. For example: ->>> base_cell = montepy.Cell() ->>> base_cell.number = 1 +>>> base_cell = problem.cells[1] +>>> base_cell.number +1 >>> # clone with an available number >>> new_cell = base_cell.clone(starting_number=1000) >>> new_cell.number 1000 >>> # force a number collision ->>> new_cell = base_cell.clone(starting_number= 1, step =5) +>>> new_cell = base_cell.clone(starting_number=1, step=5) >>> new_cell.number 6 @@ -606,8 +625,8 @@ For example, if you have a problem read in already: >>> new_cell.material is cell.material True >>> new_cell = cell.clone(clone_material=True) ->>> new_cell.material.number -2 +>>> new_cell.material.number # materials 2,3 are taken. +4 >>> new_cell.material is cell.material False @@ -633,16 +652,14 @@ If a cell is not assigned to any universe it will be assigned to Universe 0, *no To change what cells are in a universe you can set this at the cell level. This is done to prevent a cell from being assigned to multiple universes -.. code-block:: python - - universe = problem.universes[350] - for cell in problem.cells[1:5]: - cell.universe = universe +>>> universe = problem.universes[350] +>>> for cell in problem.cells[1:5]: +... cell.universe = universe We can confirm this worked with the generator ``universe.cells``: >>> [cell.number for cell in universe.cells] -[1, 2, 3, 4, 5] +[1, 2, 3, 5, 4] Claiming Cells ^^^^^^^^^^^^^^ diff --git a/doc/source/tests b/doc/source/tests new file mode 120000 index 00000000..eddac70b --- /dev/null +++ b/doc/source/tests @@ -0,0 +1 @@ +../../tests/ \ No newline at end of file diff --git a/doc/source/tricks.rst b/doc/source/tricks.rst index 6905cc84..b95fc0cc 100644 --- a/doc/source/tricks.rst +++ b/doc/source/tricks.rst @@ -59,7 +59,9 @@ For some problems you can quickly move a whole problem axially (in z) with a sim .. note:: Make sure you loop over all the surfaces in the problem, and not over all the cells. For instance: - + + >>> import montepy + >>> problem = montepy.MCNP_Problem("foo.imcnp") >>> for cell in problem.cells: >>> for surface in cell.surfaces: >>> surface.location += 10 diff --git a/doc/source/utilities.rst b/doc/source/utilities.rst index 4d42e3ad..fbf84df6 100644 --- a/doc/source/utilities.rst +++ b/doc/source/utilities.rst @@ -93,7 +93,8 @@ Dealing with Encoding Issues You are likely here because you got an error message something like this: ->>> montepy.read_input("example.imcnp") +>>> import montepy # doctest: +SKIP +>>> montepy.read_input("example.imcnp") # doctest: +SKIP UnicodeDecodeError Traceback (most recent call last) UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 1132: ordinal not in range(128) From 136ebcfa79e6f3573bc953edbdf76fac14e43c47 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Wed, 23 Oct 2024 09:13:39 -0500 Subject: [PATCH 06/28] Removed extra blank lines from cell repr. --- montepy/cell.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/montepy/cell.py b/montepy/cell.py index e5901b01..f142b4ff 100644 --- a/montepy/cell.py +++ b/montepy/cell.py @@ -637,9 +637,8 @@ def __repr__(self): ret += "atom/b-cm\n" else: ret += "g/cc\n" - for surface in self._surfaces: - ret += str(surface) + "\n" - ret += "\n" + surf_strs = [str(s) for s in self.surfaces] + ret += "\n".join(surf_strs) return ret def __lt__(self, other): From 8f66c0b9bf2ce366a35b6b626efd04cd7ffbefa6 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Wed, 23 Oct 2024 09:20:21 -0500 Subject: [PATCH 07/28] Limit tests to scope to avoid docs recursion. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a1a85bea..d282a0ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,3 +107,4 @@ minversion = "6.0" junit_logging = "all" junit_family="xunit2" filterwarnings="error" +testpaths = ["tests"] From 7cd2849ac34a25d6193f05f23532873457678f18 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Wed, 23 Oct 2024 09:20:36 -0500 Subject: [PATCH 08/28] Removed excess blank line from str test. --- tests/test_cell_problem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cell_problem.py b/tests/test_cell_problem.py index baad1418..8dd1ed8c 100644 --- a/tests/test_cell_problem.py +++ b/tests/test_cell_problem.py @@ -102,7 +102,7 @@ def test_cell_str(self): cell = Cell(card) self.assertEqual(str(cell), "CELL: 1, mat: 0, DENS: 0.5 atom/b-cm") self.assertEqual( - repr(cell), "CELL: 1 \nVoid material \ndensity: 0.5 atom/b-cm\n\n" + repr(cell), "CELL: 1 \nVoid material \ndensity: 0.5 atom/b-cm\n" ) def test_cell_paremeters_no_eq(self): From 9178980a744fc3871a4849680425a40bea61f8d3 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 07:49:23 -0500 Subject: [PATCH 09/28] Switched to using phmutest for README. --- .github/workflows/main.yml | 2 +- pyproject.toml | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index afed3c39..38f67c77 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -140,7 +140,7 @@ jobs: with: python-version: 3.12 - run: pip install . montepy[doc,build,demo-test] - - run: python -m doctest README.md + - run: python -m phmutest README.md --replmode --log name: Test readme code - run: | cd doc diff --git a/pyproject.toml b/pyproject.toml index d282a0ac..649ccf25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ test = [ "hypothesis", "pytest-profiling", "montepy[build]", + "phmutest", ] # This is needed for a sphinx bug. See #414. doc = [ @@ -62,9 +63,7 @@ build = [ "setuptools-scm>=8", ] develop = [ - "montepy[test]", - "montepy[doc]", - "montepy[format]", + "montepy[test,doc,format]", ] demos = ["jupyter"] demo-test = ["papermill"] From 4769e43d6c33c0be2f28829b9879cdcd3a5aa7aa Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 07:53:39 -0500 Subject: [PATCH 10/28] Removed deprecated material demo for now. --- demo/Pin_cell.ipynb | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/demo/Pin_cell.ipynb b/demo/Pin_cell.ipynb index 3c5aa11d..114a29ca 100644 --- a/demo/Pin_cell.ipynb +++ b/demo/Pin_cell.ipynb @@ -49,8 +49,7 @@ "There's some issues\n", "==================\n", "\n", - "* Density was defined wrong\n", - "* Wrong cross section data were used." + "* Density was defined wrong" ] }, { @@ -76,31 +75,6 @@ " print(cell)" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "6b2ba1cc-65be-43ad-8999-5bc3f5b04968", - "metadata": {}, - "outputs": [], - "source": [ - "material = problem.materials[1]\n", - "for material in problem.materials:\n", - " for componenet in material.material_components:\n", - " componenet.library = \"00c\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f1c794a6-3926-4bbb-b82d-a2dab8d8f741", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "problem.materials" - ] - }, { "cell_type": "markdown", "id": "54111f69-8bcc-4c6b-9c34-81e4d615240e", From 8d4943d82fec2642ccf6d101f4ed580f1e5e284c Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 07:54:21 -0500 Subject: [PATCH 11/28] Fixed filenot found error. --- demo/Pin_cell.ipynb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/demo/Pin_cell.ipynb b/demo/Pin_cell.ipynb index 114a29ca..0434e374 100644 --- a/demo/Pin_cell.ipynb +++ b/demo/Pin_cell.ipynb @@ -8,6 +8,7 @@ "outputs": [], "source": [ "import montepy\n", + "import os\n", "montepy.__version__" ] }, @@ -94,6 +95,9 @@ "metadata": {}, "outputs": [], "source": [ + "#make folder\n", + "os.mkdir(\"parametric\")\n", + "\n", "fuel_wall = problem.surfaces[1]\n", "gap_wall = problem.surfaces[2]\n", "clad_wall = problem.surfaces[3]\n", @@ -112,6 +116,14 @@ " plane.location = (pitch / 2) * (-1 if plane.location < 0 else 1)\n", " problem.write_to_file(f\"parametric/pin_cell_{pin_radius}r_{pitch}p.imcnp\")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f75012f-d2bc-40c6-9167-08205c4a2395", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -130,7 +142,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.12.3" } }, "nbformat": 4, From 7c5c9721e555bb614cc2eb82b01a2b96dc6cf01b Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 07:56:02 -0500 Subject: [PATCH 12/28] Installed correct dependencies. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 38f67c77..cb425ae9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -139,7 +139,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: 3.12 - - run: pip install . montepy[doc,build,demo-test] + - run: pip install . montepy[doc,build,demo-test,test] - run: python -m phmutest README.md --replmode --log name: Test readme code - run: | From ce185046049558999f6ae41fc83893ecc461c29b Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 09:26:51 -0500 Subject: [PATCH 13/28] Added code copy button. --- doc/source/conf.py | 1 + pyproject.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 9530fda1..72211d20 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -39,6 +39,7 @@ "sphinx.ext.doctest", "sphinx_sitemap", "sphinx_favicon", + "sphinx_copybutton" ] # Add any paths that contain templates here, relative to this directory. diff --git a/pyproject.toml b/pyproject.toml index 649ccf25..26a63934 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ doc = [ "pydata_sphinx_theme", "sphinx-sitemap", "sphinx-favicon", + "sphinx-copybutton" ] format = ["black>=23.3.0"] build = [ From 158f3829c5385592472c0c94262518305bf15991 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 10:55:26 -0500 Subject: [PATCH 14/28] Add demo file to docs --- doc/foo.imcnp | 1 + doc/source/conf.py | 2 +- doc/source/foo.imcnp | 14 ++++++++++++++ doc/tests | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) create mode 120000 doc/foo.imcnp create mode 100644 doc/source/foo.imcnp create mode 120000 doc/tests diff --git a/doc/foo.imcnp b/doc/foo.imcnp new file mode 120000 index 00000000..08f9a66a --- /dev/null +++ b/doc/foo.imcnp @@ -0,0 +1 @@ +source/foo.imcnp \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py index 72211d20..4adf71f5 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -54,7 +54,7 @@ html_baseurl = "https://www.montepy.org/" sitemap_url_scheme = "{link}" -html_extra_path = ["robots.txt"] +html_extra_path = ["robots.txt", "foo.imcnp"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. diff --git a/doc/source/foo.imcnp b/doc/source/foo.imcnp new file mode 100644 index 00000000..050b96a9 --- /dev/null +++ b/doc/source/foo.imcnp @@ -0,0 +1,14 @@ +Example Problem +1 0 -1 2 -3 +2 0 -4 5 -6 + +1 CZ 0.5 +2 PZ 0 +3 PZ 1.5 +4 CZ 0.500001 +5 PZ 1.5001 +6 PZ 2.0 + +kcode 1.0 100 25 100 +TR1 0 0 1.0 +TR2 0 0 1.00001 diff --git a/doc/tests b/doc/tests new file mode 120000 index 00000000..90ec7c43 --- /dev/null +++ b/doc/tests @@ -0,0 +1 @@ +../tests/ \ No newline at end of file From 33291a59dd0f2490c38621a20c8f8ad5b618886d Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 10:56:18 -0500 Subject: [PATCH 15/28] Add more surface tests to test. --- tests/inputs/test.imcnp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/inputs/test.imcnp b/tests/inputs/test.imcnp index dcfe0a9b..074438b0 100644 --- a/tests/inputs/test.imcnp +++ b/tests/inputs/test.imcnp @@ -10,7 +10,7 @@ c -1000 $ dollar comment imp:n,p=1 U=350 trcl=5 2 2 8 - -1005 + -1005 -1015 1020 -1025 imp:n=1 imp:p=0.5 3 3 -1 @@ -28,6 +28,9 @@ C surfaces 1000 SO 1 1005 RCC 0 1.5 -0.5 0 0 1 0.25 1010 SO 3 +1015 CZ 5.0 +1020 PZ 10 +1025 PZ 15 C data C materials From 043c0bbd80d88b0cd422b97feaf222ea65ec376c Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 10:57:40 -0500 Subject: [PATCH 16/28] Made most example code testable and robust. --- doc/source/developing.rst | 53 ++++--- doc/source/starting.rst | 312 +++++++++++++++++++++++--------------- doc/source/tricks.rst | 51 +++++-- 3 files changed, 266 insertions(+), 150 deletions(-) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 4468ce42..1af832ad 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -6,10 +6,20 @@ The syntax layers handle the boring syntax things: like multi-line cards, and co The semantic layer takes this information and makes sense of it, like what the material number in a cell card is. .. note:: + Punchcards are dead. For this reason MontePy refrains from using antiquated terminology like "cards" and "decks". Instead MontePy refers to "inputs", and "files" or "problems". +.. note:: + Demo code is based on `tests/inputs/test.imcnp`. + You can load this with: + + .. testcode:: + + import montepy + problem = montepy.read_input("tests/inputs/test.imcnp") + Contributing ------------ @@ -97,7 +107,7 @@ Version information is stored in git tags, and retrieved using `setuptools scm `_. The version tag shall match the regular expression: -``v\d.\d+.\d+``. +``v\d\.\d+\.\d+``. These tags will be applied by a maintainer during the release process, and cannot be applied by normal users. @@ -309,6 +319,7 @@ and :func:`~montepy.input_parser.syntax_node.ValueNode.is_negatable_identifier` This will make it so that ``value`` always returns a positive value, and so :func:`~montepy.input_parser.syntax_node.ValueNode.is_negative` returns a boolean value. .. note:: + Setting :func:`~montepy.input_parser.syntax_node.ValueNode.is_negatable_identifier` to ``True`` will convert the ValueNode to an integer ValueNode (via :func:`~montepy.input_parser.syntax_node.ValueNode._convert_to_int`). @@ -336,14 +347,20 @@ This should include most if not all internal state information. See this example for :class:`~montepy.cell.Cell` ->>> str(cell) -CELL: 2, mat: 2, DENS: 8.0 g/cm3 ->>> repr(cell) -CELL: 2 -MATERIAL: 2, ['iron'] -density: 8.0 atom/b-cm -SURFACE: 1005, RCC - +.. doctest:: + :skipif: True # skip because multi-line doc tests are kaputt + + >>> cell = problem.cells[2] + >>> print(str(cell)) + CELL: 2, mat: 2, DENS: 8.0 atom/b-cm + >>> print(repr(cell)) + CELL: 2 + MATERIAL: 2, ['iron'] + density: 8.0 atom/b-cm + SURFACE: 1005, RCC + SURFACE: 1015, CZ + SURFACE: 1020, PZ + SURFACE: 1025, PZ Writing to File (Format for MCNP Input) """"""""""""""""""""""""""""""""""""""" @@ -725,14 +742,16 @@ For a ``Surface`` it is owned by the ``Surfaces`` collection owned by the ``MCNP A cell then borrows this object by referencing it in its own ``Surfaces`` collections. For example: ->>> import montepy ->>> # owns ->>> x = montepy.Cell() ->>> id = hex(id(x)) ->>> # borrows ->>> new_list = [x] ->>> id == hex(id(new_list[0])) -True +.. doctest:: + + >>> import montepy + >>> # owns + >>> x = montepy.Cell() + >>> old_id = hex(id(x)) + >>> # borrows + >>> new_list = [x] + >>> old_id == hex(id(new_list[0])) + True The general principle is that only one-directional pointers should be used, and bidirectional pointers should never be used. diff --git a/doc/source/starting.rst b/doc/source/starting.rst index 05693fed..643a6ba9 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -2,6 +2,7 @@ Getting Started with MontePy ============================ .. testsetup:: * + import montepy MontePy is a python API for reading, editing, and writing MCNP input files. @@ -118,7 +119,7 @@ MontePy will do its best to guess the formatting of the original value and to re However, this may not always be possible, especially if more digits are needed to keep information (e.g., ``10`` versus ``1000``). In this case MontePy will warn you that value will take up more space which may break your pretty formatting. -For example say we have this simple MCNP input file (saved as foo.imcnp) :: +For example say we have this simple MCNP input file (saved as :download:`foo.imcnp`) :: Example Problem 1 0 -1 2 -3 @@ -138,6 +139,7 @@ For example say we have this simple MCNP input file (saved as foo.imcnp) :: We can then open this file in MontePy, and then modify it slightly, and save it again: .. doctest:: + import montepy problem = montepy.read_input("foo.imcnp") problem.cells[1].number = 5 @@ -210,6 +212,7 @@ Technically these are :class:`~montepy.numbered_object_collection.NumberedObject but it looks like a ``dict``, walks like a ``dict``, and quacks like ``dict``, so most users can just treat it like that. .. note:: + Though these collections are based on a dict, they don't behave exactly like a dict. For a dict the iteration (e.g., ``for key in dict:``) iterates over the keys. Also when you check if an item is in a dict (e.g., ``if key in dict:``) it checks if the item is a key. @@ -227,12 +230,19 @@ by its number. So say you want to access cell 2 from a problem it is accessible quickly by: ->>> prob = montepy.read_input("tests/inputs/test.imcnp") ->>> prob.cells[2] -CELL: 2 -MATERIAL: 2, ['iron'] -density: 8.0 atom/b-cm -SURFACE: 1005, RCC + +.. doctest:: + :skipif: True # skip because multi-line doc tests are kaputt + + >>> prob = montepy.read_input("tests/inputs/test.imcnp") + >>> prob.cells[2] + CELL: 2 + MATERIAL: 2, ['iron'] + density: 8.0 atom/b-cm + SURFACE: 1005, RCC + SURFACE: 1015, CZ + SURFACE: 1020, PZ + SURFACE: 1025, PZ Collections are Iterable @@ -240,10 +250,12 @@ Collections are Iterable Collections are also iterable, meaning you can iterate through it quickly and easily. For instance say you want to increase all cell numbers by 1,000. -This can be done quickly with a for loop:: - - for cell in problem.cells: - cell.number += 1000 +This can be done quickly with a for loop: + +.. testcode:: + + for cell in problem.cells: + cell.number += 1000 Number Collisions (should) be Impossible ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -251,14 +263,19 @@ Number Collisions (should) be Impossible The ``NumberedObjectCollection`` has various mechanisms internally to avoid number collisions (two objects having the same number). ->>> import montepy ->>> prob = montepy.read_input("tests/inputs/test.imcnp") ->>> cell = montepy.Cell() ->>> cell.number = 2 ->>> prob.cells.append(cell) -Traceback (most recent call last): - ... -montepy.errors.NumberConflictError: Number 2 is already in use for the collection: by CELL: 2, mat: 2, DENS: 8.0 atom/b-cm +.. testcode:: + + import montepy + prob = montepy.read_input("tests/inputs/test.imcnp") + cell = montepy.Cell() + cell.number = 2 + prob.cells.append(cell) + +.. testoutput:: + + Traceback (most recent call last): + ... + montepy.errors.NumberConflictError: Number 2 is already in use for the collection: by CELL: 2, mat: 2, DENS: 8.0 atom/b-cm There are a number of tools to avoid this though: @@ -292,14 +309,19 @@ You can iterate over a generator, as well as check if an item is in the generato First it is iterable: ->>> problem = montepy.read_input("tests/inputs/test.imcnp") ->>> for number in problem.cells.numbers: -... print(number) -1 -2 -3 -99 -5 +.. testcode:: + + problem = montepy.read_input("tests/inputs/test.imcnp") + for number in problem.cells.numbers: + print(number) + +.. testoutput:: + + 1 + 2 + 3 + 99 + 5 You can also check if a number is in use: @@ -375,9 +397,11 @@ It would be horrible to have to get each surface by their number, and hoping you One way you might think of is: oh let's just filter the surfaces by their type?: ->>> for surface in cell.surfaces: -... if surface.surface_type == montepy.surfaces.surface_type.SurfaceType.PZ: -... surface.location += 10 +.. testcode:: + + for surface in cell.surfaces: + if surface.surface_type == montepy.surfaces.surface_type.SurfaceType.PZ: + surface.location += 10 Wow that's rather verbose. This was the only way to do this with the API for awhile. @@ -386,10 +410,12 @@ But MontePy 0.0.5 fixed this with: you guessed it: generators. The :class:`~montepy.surface_collection.Surfaces` collection has a generator for every type of surface in MCNP. These are very easy to find: they are just the lower case version of the MCNP surface mnemonic. -This previous code is much simpler now:: +This previous code is much simpler now: ->>> for surface in cell.surfaces.pz: -... surface.location += 10 +.. testcode:: + + for surface in cell.surfaces.pz: + surface.location += 10 Cells ----- @@ -405,25 +431,29 @@ For a complete list see :class:`~montepy.particle.Particle`. You can also quickly get the information by passing an instance of :class:`~montepy.particle.Particle` as a key to importance. For example: - ->>> for particle in sorted(problem.mode): -... print(particle, cell.importance[particle]) -neutron 0.0 -photon 0.0 ->>> print(cell.importance[montepy.Particle.NEUTRON]) -0.0 + +.. doctest:: + + >>> for particle in sorted(problem.mode): + ... print(particle, cell.importance[particle]) + neutron 0.0 + photon 0.0 + >>> print(cell.importance[montepy.Particle.NEUTRON]) + 0.0 There's also a lot of convenient ways to do bulk modifications. There is the :func:`~montepy.data_inputs.importance.Importance.all` property that lets you set the importance for all particles in the problem at once. For example: ->>> problem.set_mode("n p e") ->>> cell.importance.all = 2.0 ->>> for particle in sorted(problem.mode): -... print(particle, cell.importance[particle]) -electron 2.0 -neutron 2.0 -photon 2.0 +.. doctest:: + + >>> problem.set_mode("n p e") + >>> cell.importance.all = 2.0 + >>> for particle in sorted(problem.mode): + ... print(particle, cell.importance[particle]) + electron 2.0 + neutron 2.0 + photon 2.0 This will set the importances for the neutron and photon. @@ -462,19 +492,21 @@ If the cell density is set to a mass density ``cell.atom_density`` will return ` Setting the value for one of these densities will change the density mode. MontePy does not convert mass density to atom density and vice versa. ->>> problem = montepy.read_input("tests/inputs/test.imcnp") ->>> cell = problem.cells[3] ->>> cell.mass_density -1.0 ->>> cell.atom_density -Traceback (most recent call last): - ... -AttributeError: Cell 3 is in mass density.. Did you mean: 'mass_density'? ->>> cell.atom_density = 0.5 ->>> cell.mass_density -Traceback (most recent call last): - ... -AttributeError: Cell 3 is in atom density.. Did you mean: 'atom_density'? +.. doctest:: + + >>> problem = montepy.read_input("tests/inputs/test.imcnp") + >>> cell = problem.cells[3] + >>> cell.mass_density + 1.0 + >>> cell.atom_density + Traceback (most recent call last): + ... + AttributeError: Cell 3 is in mass density.. Did you mean: 'mass_density'? + >>> cell.atom_density = 0.5 + >>> cell.mass_density + Traceback (most recent call last): + ... + AttributeError: Cell 3 is in atom density.. Did you mean: 'atom_density'? Geometry ^^^^^^^^ @@ -511,19 +543,23 @@ This is done very simply and pythonic. For a :class:`~montepy.surfaces.surface.Surface` you just need to mark the surface as positive (``+``) or negative (``-``) (using the unary operators). This actually creates a new object so don't worry about modifying the surface. ->>> bottom_plane = montepy.surfaces.surface.Surface() ->>> top_plane = montepy.surfaces.surface.Surface() ->>> type(+bottom_plane) - ->>> type(-bottom_plane) - +.. doctest:: + + >>> bottom_plane = montepy.surfaces.surface.Surface() + >>> top_plane = montepy.surfaces.surface.Surface() + >>> type(+bottom_plane) + + >>> type(-bottom_plane) + For cells the plus/minus operator doesn't make sense. Instead you use the binary not operator (``~``). ->>> capsule_cell = montepy.Cell() ->>> type(~capsule_cell) - +.. doctest:: + + >>> capsule_cell = montepy.Cell() + >>> type(~capsule_cell) + Combining Half-Spaces @@ -537,25 +573,40 @@ As with OpenMC, the set logic operations have been mapped to python's bit logic * ``~``, the not operator, represents a set complement. .. note:: + When you combine two half-spaces with a logical operator you create a new half-space. In this case the concept of a side becomes much more about "in" and "out". .. note:: + Half-spaces need not be contiguous. Order of precedence and grouping is automatically handled by python so you can easily write complicated geometry in one-line. -.. code-block:: python - +.. testcode:: + + # build blank surfaces + bottom_plane = montepy.surfaces.axis_plane.AxisPlane() + top_plane = montepy.surfaces.axis_plane.AxisPlane() + fuel_cylinder = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + clad_cylinder = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + clad_od = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + other_fuel = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + bottom_plane.number = 1 + top_plane.number = 2 + fuel_cylinder.number = 3 + clad_cylinder.number = 4 + clad_od.number = 5 + #make weird truncated fuel sample slug_half_space = +bottom_plane & -top_plane & -fuel_cylinder gas_gap = ~slug_half_space & +bottom_plane & -top_plane & -clad_cylinder cladding = ~gas_gap & ~slug_half_space & +bottom_plane & -top_plane & -clad_od - # make weird multi-part cell - slugs = (+bottom_plane & -top_plane & -fuel_cylinder) | (+bottom_plane & -top_plane & -other_fuel) + slugs = (+bottom_plane & -top_plane & -fuel_cylinder) | (+bottom_plane & -top_plane & -other_fuel) .. note:: + MontePy does not check if the geometry definition is "rational". It doesn't check for being finite, existant (having any volumen at all), or being infinite. Nor does it check for overlapping geometry. @@ -564,15 +615,22 @@ Setting and Modifying Geometry """""""""""""""""""""""""""""" The half-space defining a cell's geometry is stored in ``cell.geometry`` (:func:`~montepy.cell.Cell.geometry`). -This property can be rather simply set.:: +This property can be rather simply set. + +.. testcode:: + fuel_cell = montepy.Cell() fuel_cell.geometry = +bottom_plane & - top_plane & - fuel_cylinder -This will completely redefine the cell's geometry. You can also modify the geometry with augmented assign operators, ``&=``, and ``|=``.:: +This will completely redefine the cell's geometry. You can also modify the geometry with augmented assign operators, ``&=``, and ``|=``. - fuel_cell.geometry |= other_fuel_region +.. testcode:: + + other_fuel_region = -montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + fuel_cell.geometry |= other_fuel_region .. warning:: + Be careful when using ``&=`` and ``|=`` with complex geometries as the order of operations may not be what you expected. You can check the geometry logic by printing it. MontePy will show you its internal (`binary tree `_) representation of the logic. @@ -599,17 +657,19 @@ The new cell will attempt to use ``starting_number`` as its number. If this number is taken ``step`` will be added to it until an available number is found. For example: ->>> base_cell = problem.cells[1] ->>> base_cell.number -1 ->>> # clone with an available number ->>> new_cell = base_cell.clone(starting_number=1000) ->>> new_cell.number -1000 ->>> # force a number collision ->>> new_cell = base_cell.clone(starting_number=1, step=5) ->>> new_cell.number -6 +.. doctest:: + + >>> base_cell = problem.cells[1] + >>> base_cell.number + 1 + >>> # clone with an available number + >>> new_cell = base_cell.clone(starting_number=1000) + >>> new_cell.number + 1000 + >>> # force a number collision + >>> new_cell = base_cell.clone(starting_number=1, step=5) + >>> new_cell.number + 6 Cells can also clone their material, and their dividers. By default this is not done, and only a new ``HalfSpace`` instance is created that points to the same objects. @@ -617,30 +677,34 @@ This is done so that the geometry definitions of the two cells can be edited wit For a lot of problems this is preferred in order to avoid creating geometry gaps due to not using the same surfaces in geometry definitions. For example, if you have a problem read in already: ->>> cell = problem.cells[1] ->>> cell.material.number -1 ->>> new_cell = cell.clone() ->>> #the material didn't change ->>> new_cell.material is cell.material -True ->>> new_cell = cell.clone(clone_material=True) ->>> new_cell.material.number # materials 2,3 are taken. -4 ->>> new_cell.material is cell.material -False +.. doctest:: + + >>> cell = problem.cells[1] + >>> cell.material.number + 1 + >>> new_cell = cell.clone() + >>> #the material didn't change + >>> new_cell.material is cell.material + True + >>> new_cell = cell.clone(clone_material=True) + >>> new_cell.material.number # materials 2,3 are taken. + 4 + >>> new_cell.material is cell.material + False When children objects (:class:`~montepy.data_inputs.material.Material`, :class:`~montepy.surfaces.surface.Surface`, and :class:`~montepy.cell.Cell`) are cloned the numbering behavior is defined by the problem's instance's instance of the respective collection (e.g., :class:`~montepy.materials.Materials`) by the properties: :func:`~montepy.numbered_object_collection.NumberedObjectCollection.starting_number` and :func:`~montepy.numbered_object_collection.NumberedObjectCollection.step`. For example: ->>> problem.materials.starting_number = 100 ->>> problem.cells[1].material.number -1 ->>> new_cell = problem.cells[1].clone(clone_material=True) ->>> new_cell.material.number -100 +.. doctest:: + + >>> problem.materials.starting_number = 100 + >>> problem.cells[1].material.number + 1 + >>> new_cell = problem.cells[1].clone(clone_material=True) + >>> new_cell.material.number + 100 Universes --------- @@ -652,14 +716,18 @@ If a cell is not assigned to any universe it will be assigned to Universe 0, *no To change what cells are in a universe you can set this at the cell level. This is done to prevent a cell from being assigned to multiple universes ->>> universe = problem.universes[350] ->>> for cell in problem.cells[1:5]: -... cell.universe = universe +.. testcode:: + + universe = problem.universes[350] + for cell in problem.cells[1:5]: + cell.universe = universe We can confirm this worked with the generator ``universe.cells``: ->>> [cell.number for cell in universe.cells] -[1, 2, 3, 5, 4] +.. doctest:: + + >>> [cell.number for cell in universe.cells] + [1, 2, 3, 5, 4] Claiming Cells ^^^^^^^^^^^^^^ @@ -670,7 +738,7 @@ For all cells passed (either as a single ``Cell``, a ``list`` of cells, or a ``C will be removed from their current universe, and moved to this universe. This simplifies the above code to just being: -.. code-block:: python +.. testcode:: universe = problem.universes[350] universe.claim(problem.cells[1:5]) @@ -682,7 +750,7 @@ Creating a new universe is very straight forward. You just need to initialize it with a new number, and then add it to the problem: -.. code-block:: python +.. testcode:: universe = montepy.Universe(333) problem.universes.append(universe) @@ -690,9 +758,11 @@ and then add it to the problem: Now you can add cells to this universe as you normally would. .. note:: + A universe with no cells assigned will not be written out to the MCNP input file, and will "dissapear". .. note:: + Universe number collisions are not checked for when a universe is created, but only when it is added to the problem. Make sure to plan accordingly, and consider using :func:`~montepy.numbered_object_collection.NumberedObjectCollection.request_number`. @@ -707,16 +777,20 @@ Filling is handled by the :class:`~montepy.data_cards.fill.Fill` object in ``cel To fill a cell with a specific universe you can just run: -.. code-block:: python +.. testcode:: - cell.fill.universe = universe + cell = problem.cells[2] + cell.fill.universe = universe This will then fill the cell with a single universe with no transform. You can also easy apply a transform to the filling universe with: -.. code-block:: python +.. testcode:: - cell.fill.tranform = transform + transform = montepy.data_inputs.transform.Transform() + transform.number = 5 + transform.displacement_vector = [1, 2, 0] + cell.fill.tranform = transform .. note:: @@ -752,7 +826,7 @@ If there are many errors not all may be found at once due to how errors are hand This is done by executing it with the ``-c`` flag, and specifying a file, or files to check. You can also use linux globs:: - python -m montepy -c inputs/*.imcnp + python -m montepy -c tests/inputs/*.imcnp MontePy will then show which file it is reading, and show a warning for every potential error with the input file it has found. @@ -764,13 +838,13 @@ If you want to try to troubleshoot errors in python you can do this with the fol 1. Setup a new Problem object: - .. code-block:: python + .. testcode:: problem = montepy.MCNP_Problem("foo.imcnp") 1. Next load the input file with the ``check_input`` set to ``True``. - .. code-block:: python + .. testcode:: problem.parse_input(True) diff --git a/doc/source/tricks.rst b/doc/source/tricks.rst index b95fc0cc..f7404d44 100644 --- a/doc/source/tricks.rst +++ b/doc/source/tricks.rst @@ -3,6 +3,17 @@ Tips and Tricks Here's a random collection of some tips and tricks that should make your life easier. +.. note:: + + All examples come from one of the MontePy test inputs. + You can access it by: + + .. testcode:: + + import montepy + problem = montepy.read_input("tests/inputs/test.imcnp") + surfaces = problem.surfaces + .. contents:: Table of Contents :depth: 3 @@ -22,24 +33,30 @@ Here are some examples: Getting Highest PZ plane ~~~~~~~~~~~~~~~~~~~~~~~~ ->>> max(surfaces.pz, key = lambda x: x.location) -Surface: 1 PZ +.. doctest:: + + >>> max(surfaces.pz, key = lambda x: x.location) + SURFACE: 1025, PZ, periodic surface: None, transform: None, constants: [15.0] Getting the Lowest PZ plane ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similarly you can get the lowest surface with the min function: ->>> min(surfaces.pz, key = lambda x: x.location) -Surface: 5 PZ +.. doctest:: + + >>> min(surfaces.pz, key = lambda x: x.location) + SURFACE: 1020, PZ, periodic surface: None, transform: None, constants: [10.0] Getting the Largest CZ Cylinder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to before you can use this method to find cylinders by their radius: ->>> max(surfaces.cz, key = lambda x: x.radius) -surface: 10 CZ +.. doctest:: + + >>> max(surfaces.cz, key = lambda x: x.radius) + SURFACE: 1015, CZ, periodic surface: None, transform: None, constants: [5.0] Translating Cells ----------------- @@ -49,21 +66,27 @@ Translating experiment axially For some problems you can quickly move a whole problem axially (in z) with a simple loop like: ->>> for surface in problem.surfaces.pz: ->>> surface.location += 10 +.. testcode:: + + for surface in problem.surfaces.pz: + surface.location += 10 .. note:: + This only works for problems that are infinite in Z, with "cutting" PZ planes, such as a fuel rod. This is because only the PZ surfaces are moving. If you had a sphere in the problem for instance, this would break the problem geometry then as the sphere would not move as well. .. note:: + Make sure you loop over all the surfaces in the problem, and not over all the cells. For instance: - - >>> import montepy - >>> problem = montepy.MCNP_Problem("foo.imcnp") - >>> for cell in problem.cells: - >>> for surface in cell.surfaces: - >>> surface.location += 10 + + .. testcode:: + + import montepy + problem = montepy.MCNP_Problem("foo.imcnp") + for cell in problem.cells: + for surface in cell.surfaces: + surface.location += 10 will cause geometry errors. Surfaces are commonly used by multiple cells so each surface may be translated repeatedly. From 6e2a63af1c98e96d6c2f11b9d078877dbf4aadcc Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 10:59:41 -0500 Subject: [PATCH 17/28] Fixed a few stragglers in doc test. --- doc/source/starting.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/starting.rst b/doc/source/starting.rst index 643a6ba9..9b6b35c3 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -446,6 +446,7 @@ There is the :func:`~montepy.data_inputs.importance.Importance.all` property tha For example: .. doctest:: + :skipif: True >>> problem.set_mode("n p e") >>> cell.importance.all = 2.0 @@ -787,9 +788,10 @@ You can also easy apply a transform to the filling universe with: .. testcode:: + import numpy as np transform = montepy.data_inputs.transform.Transform() transform.number = 5 - transform.displacement_vector = [1, 2, 0] + transform.displacement_vector = np.array([1, 2, 0]) cell.fill.tranform = transform .. note:: From d3ada2a69cdc3e430f60f00e56d477574282e6cd Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Thu, 31 Oct 2024 11:12:56 -0500 Subject: [PATCH 18/28] Updated tests with more surfaces in test.imcnp --- tests/test_integration.py | 14 +++++++++++--- tests/test_numbered_collection.py | 6 +++--- tests/test_syntax_parsing.py | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 8a4b1b4e..c569c31c 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -44,7 +44,7 @@ def data_universe_problem(): def test_original_input(simple_problem): - cell_order = [Message, Title] + [Input] * 26 + cell_order = [Message, Title] + [Input] * 29 for i, input_ob in enumerate(simple_problem.original_inputs): assert isinstance(input_ob, cell_order[i]) @@ -89,7 +89,7 @@ def test_material_parsing(simple_problem): def test_surface_parsing(simple_problem): - surf_numbers = [1000, 1005, 1010] + surf_numbers = [1000, 1005, 1010, 1015, 1020, 1025] for i, surf in enumerate(simple_problem.surfaces): assert surf.number == surf_numbers[i] @@ -130,10 +130,18 @@ def test_cells_parsing_linking(simple_problem): mats = simple_problem.materials mat_answer = [mats[1], mats[2], mats[3], None, None] surfs = simple_problem.surfaces - surf_answer = [{surfs[1000]}, {surfs[1005]}, set(surfs), {surfs[1010]}, set()] + surf_answer = [ + {surfs[1000]}, + {surfs[1005], *surfs[1015:1026]}, + set(surfs[1000:1011]), + {surfs[1010]}, + set(), + ] cells = simple_problem.cells complements = [set()] * 4 + [{cells[99]}] for i, cell in enumerate(simple_problem.cells): + print(cell) + print(surf_answer[i]) assert cell.number == cell_numbers[i] assert cell.material == mat_answer[i] surfaces = set(cell.surfaces) diff --git a/tests/test_numbered_collection.py b/tests/test_numbered_collection.py index 0f82aff4..bd6842ed 100644 --- a/tests/test_numbered_collection.py +++ b/tests/test_numbered_collection.py @@ -20,7 +20,7 @@ def test_bad_init(self): def test_numbers(self): cell_numbers = [1, 2, 3, 99, 5] - surf_numbers = [1000, 1005, 1010] + surf_numbers = [1000, 1005, 1010, 1015, 1020, 1025] mat_numbers = [1, 2, 3] problem = self.simple_problem self.assertEqual(list(problem.cells.numbers), cell_numbers) @@ -225,9 +225,9 @@ def test_slice(self): test_numbers = [c.number for c in self.simple_problem.cells[5::-1]] self.assertEqual([5, 3, 2, 1], test_numbers) test_numbers = [s.number for s in self.simple_problem.surfaces[1000::10]] - self.assertEqual([1000, 1010], test_numbers) + self.assertEqual([1000, 1010, 1020], test_numbers) test_numbers = [s.number for s in self.simple_problem.surfaces[:]] - self.assertEqual([1000, 1005, 1010], test_numbers) + self.assertEqual([1000, 1005, 1010, 1015, 1020, 1025], test_numbers) test_numbers = [m.number for m in self.simple_problem.materials[:2]] self.assertEqual([1, 2], test_numbers) test_numbers = [m.number for m in self.simple_problem.materials[::2]] diff --git a/tests/test_syntax_parsing.py b/tests/test_syntax_parsing.py index 4ad93926..1534d588 100644 --- a/tests/test_syntax_parsing.py +++ b/tests/test_syntax_parsing.py @@ -1347,7 +1347,7 @@ def testReadInput(self): ) mcnp_in = montepy.input_parser.mcnp_input input_order = [mcnp_in.Message, mcnp_in.Title] - input_order += [mcnp_in.Input] * 26 + input_order += [mcnp_in.Input] * 29 for i, input in enumerate(generator): print(input.input_lines) print(input_order[i]) From 7b208b775885e7064f244041e67bf864130d7730 Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Thu, 31 Oct 2024 11:58:22 -0500 Subject: [PATCH 19/28] made demo test depend on demo. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 26a63934..0ad39002 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ develop = [ "montepy[test,doc,format]", ] demos = ["jupyter"] -demo-test = ["papermill"] +demo-test = ["montepy[demos]", "papermill"] [project.urls] From bb7b82f6123d441cc6e96f62b1d73a7d769ff810 Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Thu, 31 Oct 2024 12:00:36 -0500 Subject: [PATCH 20/28] Changed path for demo tests. --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cb425ae9..09b6fbb0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -146,7 +146,9 @@ jobs: cd doc make doctest name: Test all example code in documentation. - - run: for file in demo/*.ipynb; do papermill $file demo/foo.ipynb; done + - run: | + cd demo + for file in *.ipynb; do papermill $file foo.ipynb; done name: Test all demo notebooks format-test: From 7b2d95c59a42459e13824a2aea7fae43520d45d2 Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Thu, 31 Oct 2024 13:47:29 -0500 Subject: [PATCH 21/28] Update main.yml Fix typos --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 09b6fbb0..5c7f7ee9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -147,8 +147,8 @@ jobs: make doctest name: Test all example code in documentation. - run: | - cd demo - for file in *.ipynb; do papermill $file foo.ipynb; done + cd demo + for file in *.ipynb; do papermill $file foo.ipynb; done name: Test all demo notebooks format-test: From f59b5924160efe5d65442bfd5395002db2c8e5e5 Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Sat, 2 Nov 2024 15:57:55 -0500 Subject: [PATCH 22/28] Started writing migration plan. --- doc/source/migrations/migrate0_1.rst | 26 ++++++++++++++++++-------- montepy/particle.py | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/doc/source/migrations/migrate0_1.rst b/doc/source/migrations/migrate0_1.rst index 945fd2d6..1a26314d 100644 --- a/doc/source/migrations/migrate0_1.rst +++ b/doc/source/migrations/migrate0_1.rst @@ -44,11 +44,21 @@ and will be removed in MontePy 1.0.0. isotopic, isomeric, or atomic data. -New Interface -------------- -Currently the replacement interface has not been fully designed yet. -If you have input you can `join the discussion `_. -There will also be some alpha-testing announced in that discussion. - -Once MontePy 1.0.0 is released this will be updated with information about the new interface, -and how to migrate to it. +New Interface & Migration +------------------------- + +.. note:: + + This design is not finalized and is subject to change. + This is the currently planned design for ``1.0.0a1``. + If you have input you can `join the discussion `_. + This is alos where alpha-testing will be announced. + +``material_components`` +^^^^^^^^^^^^^^^^^^^^^^^ + +Material composition data has moved from ``Material.material_components`` to the ``Material`` itself. +``Material`` is now a list-like iterable. +It is a list of tuples which are ``(nuclide, fraction)`` pairs. + +.. testcode:: diff --git a/montepy/particle.py b/montepy/particle.py index 21359266..ab2a8a61 100644 --- a/montepy/particle.py +++ b/montepy/particle.py @@ -3,7 +3,7 @@ @unique -class Particle(Enum): +class Particle(str, Enum): """ Supported MCNP supported particles. From 669a107912cf0473961c0987bb56b94ae3bdbc8c Mon Sep 17 00:00:00 2001 From: Micah Gale Date: Sat, 2 Nov 2024 16:20:59 -0500 Subject: [PATCH 23/28] Added high level summary to migration. --- doc/source/migrations/migrate0_1.rst | 52 ++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/doc/source/migrations/migrate0_1.rst b/doc/source/migrations/migrate0_1.rst index 1a26314d..21c8088b 100644 --- a/doc/source/migrations/migrate0_1.rst +++ b/doc/source/migrations/migrate0_1.rst @@ -54,11 +54,59 @@ New Interface & Migration If you have input you can `join the discussion `_. This is alos where alpha-testing will be announced. -``material_components`` -^^^^^^^^^^^^^^^^^^^^^^^ +``material_components`` removal +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Material composition data has moved from ``Material.material_components`` to the ``Material`` itself. ``Material`` is now a list-like iterable. It is a list of tuples which are ``(nuclide, fraction)`` pairs. .. testcode:: + :skipif: True # avoid running on < 1.0.0 + + >>> import montepy + >>> problem = montepy.read_input("tests/inputs/test.imcnp") + >>> mat = problem.materials[1] + >>> mat[0] + (Nuclide('92235.80c'), 5) + >>> mat[1] + (Nuclide('92238.80c'), 95) + +Searching Components +^^^^^^^^^^^^^^^^^^^^ + +Finding a specific ``Nuclide`` in a ``Material`` is now much easier. +First there will be a ``Material.find`` method that takes either a ``Nuclide`` string, +or various over search criteria (e.g., ``element``), +and creates a generator of all matching component tuples. + +If you want to check if a ``Material`` contains a specific ``Nuclide`` +you can simply test ``nuclide in material``. +The ``Material.contains`` function will provide more options, +such as setting a minimum threshold, and testing for multiple nuclides at once. + +Adding Nuclides +^^^^^^^^^^^^^^^ +Adding a new nuclide will be easiest with the ``add_nuclide`` function. + +Editing Nuclide Compositon +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Editing a material composition will be very similar to editing a ``list``. +Existing components can be set to a nuclide component nuclide. +Also existing components can be deleted with ``del``. + + +``Isotope`` Deprecation and Removal +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The decision was made to remove the name ``Isotope``. +This is because not all material components are an isotope, +they may be an isomer, or event an element. +Rather the MCNP generalized terminology of ``Nuclide`` was adopted. +The idea of a specific nuclide, e.g., ``H-1`` was separated from an +MCNP material component e.g., ``1001.80c``. +The actual ``Nuclide`` information was moved to a new class: ``Nucleus``, +that is immutable. +The ``Nuclide`` wraps this and adds a ``Library`` object to specify the nuclear data that is used. +It makes sense to be able to change a library. +It does not make sense to change the intrinsic properties of a nuclide (i.e., ``Z``, ``A``, etc.). From f14ab30922767652e32937f6d4b96d3582b8337f Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Mon, 4 Nov 2024 07:26:06 -0600 Subject: [PATCH 24/28] Undid fancy enum work. --- montepy/particle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/montepy/particle.py b/montepy/particle.py index ab2a8a61..21359266 100644 --- a/montepy/particle.py +++ b/montepy/particle.py @@ -3,7 +3,7 @@ @unique -class Particle(str, Enum): +class Particle(Enum): """ Supported MCNP supported particles. From a2e9ea8327a2ca9981a59c7fb576c55ce3e64704 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Tue, 12 Nov 2024 13:29:51 -0600 Subject: [PATCH 25/28] Added feedback from @tjlaboss. --- doc/source/starting.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/starting.rst b/doc/source/starting.rst index 9b6b35c3..62f96736 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -5,9 +5,9 @@ Getting Started with MontePy import montepy -MontePy is a python API for reading, editing, and writing MCNP input files. -It does not run MCNP nor does it parse MCNP output files. -The library provides a semantic interface for working with input files, or our preferred terminology: problems. +MontePy is a Python API for reading, editing, and writing MCNP input files. +The library provides a semantic interface for working with input files ("MCNP problems"). +It does not run MCNP, nor does it parse MCNP output files. It understands that the second entry on a cell card is the material number, and will link the cell with its material object. From d52b2a56073dfa36c22cda695cf76e69ab7b83d1 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Tue, 12 Nov 2024 13:29:51 -0600 Subject: [PATCH 26/28] Added feedback from @tjlaboss. --- doc/source/starting.rst | 4 ++-- montepy/cell.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/source/starting.rst b/doc/source/starting.rst index 62f96736..490bec9e 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -582,7 +582,7 @@ As with OpenMC, the set logic operations have been mapped to python's bit logic Half-spaces need not be contiguous. -Order of precedence and grouping is automatically handled by python so you can easily write complicated geometry in one-line. +Order of precedence and grouping is automatically handled by Python so you can easily write complicated geometry in one-line. .. testcode:: @@ -609,7 +609,7 @@ Order of precedence and grouping is automatically handled by python so you can e .. note:: MontePy does not check if the geometry definition is "rational". - It doesn't check for being finite, existant (having any volumen at all), or being infinite. + It doesn't check for being finite, existant (having any volume at all), or being infinite. Nor does it check for overlapping geometry. Setting and Modifying Geometry diff --git a/montepy/cell.py b/montepy/cell.py index f142b4ff..5b07d7d0 100644 --- a/montepy/cell.py +++ b/montepy/cell.py @@ -637,8 +637,7 @@ def __repr__(self): ret += "atom/b-cm\n" else: ret += "g/cc\n" - surf_strs = [str(s) for s in self.surfaces] - ret += "\n".join(surf_strs) + ret += "\n".join([str(s) for s in self.surfaces]) return ret def __lt__(self, other): From 088863f1ae6fd11a4988eb9e25df69d88f506abe Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Tue, 12 Nov 2024 13:36:28 -0600 Subject: [PATCH 27/28] Fixed typo. --- doc/source/migrations/migrate0_1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/migrations/migrate0_1.rst b/doc/source/migrations/migrate0_1.rst index 21c8088b..81d4be5d 100644 --- a/doc/source/migrations/migrate0_1.rst +++ b/doc/source/migrations/migrate0_1.rst @@ -52,7 +52,7 @@ New Interface & Migration This design is not finalized and is subject to change. This is the currently planned design for ``1.0.0a1``. If you have input you can `join the discussion `_. - This is alos where alpha-testing will be announced. + This is also where alpha-testing will be announced. ``material_components`` removal ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 27d72bebaa744a87063dc40e92825fe045753ab6 Mon Sep 17 00:00:00 2001 From: "Micah D. Gale" Date: Tue, 12 Nov 2024 14:42:29 -0600 Subject: [PATCH 28/28] Added coefficients to surfaces to make it sensical. --- doc/source/starting.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/starting.rst b/doc/source/starting.rst index 490bec9e..ec413b6b 100644 --- a/doc/source/starting.rst +++ b/doc/source/starting.rst @@ -588,11 +588,17 @@ Order of precedence and grouping is automatically handled by Python so you can e # build blank surfaces bottom_plane = montepy.surfaces.axis_plane.AxisPlane() + bottom_plane.location = 0.0 top_plane = montepy.surfaces.axis_plane.AxisPlane() + top_plane.location = 10.0 fuel_cylinder = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + fuel_cylinder.radius = 1.26 / 2 clad_cylinder = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + clad_cylinder.radius = (1.26 / 2) + 1e-3 # fuel, gap, cladding clad_od = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + clad_od.radius = clad_cylinder.radius + 0.1 # add thickness other_fuel = montepy.surfaces.cylinder_on_axis.CylinderOnAxis() + other_fuel.radius = 3.0 bottom_plane.number = 1 top_plane.number = 2 fuel_cylinder.number = 3