pymcnp 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
demo/main.py ADDED
@@ -0,0 +1,24 @@
1
+ import sys
2
+
3
+ import docopt
4
+
5
+
6
+ DEMO_DOCOPT = """
7
+ Usage:
8
+ demo <inp>
9
+ """
10
+
11
+
12
+ def main():
13
+ args = docopt.docopt(DEMO_DOCOPT)
14
+
15
+ try:
16
+ open(args['<inp>'])
17
+ print(f"[DEMO] Args: {' '.join(sys.argv)}")
18
+ except FileNotFoundError:
19
+ print('[DEMO] Input file not found.')
20
+ exit(1)
21
+
22
+ file = open('.' + ''.join(args['<inp>'].split('.')[:-1]) + '.outp', 'x')
23
+ file.write('MCNP Output! :)')
24
+ file.close()
@@ -0,0 +1,56 @@
1
+ """
2
+ GUI program to look at geometry
3
+ """
4
+
5
+ import matplotlib.pyplot as plt
6
+ from mcnptools import build_geometry as bg
7
+
8
+ plt.rc('font', size=14)
9
+ plt.style.use('seaborn-v0_8-darkgrid')
10
+ fig = plt.figure(constrained_layout=True, figsize=(16, 8))
11
+ gs = fig.add_gridspec(1, 2, width_ratios=[0.7, 0.3], height_ratios=[1])
12
+ ax_3d = fig.add_subplot(gs[0, 0], projection='3d')
13
+ ax_menu = fig.add_subplot(gs[0, 1])
14
+ ax_menu.set_xticks([])
15
+ ax_menu.set_yticks([])
16
+
17
+ # MCNP file
18
+ file_path = 'test_output_files/geom_gui.i'
19
+
20
+ soil = bg.Geometry(
21
+ color='blue',
22
+ name='Soil',
23
+ h_menu=0,
24
+ fig=fig,
25
+ ax_3d=ax_3d,
26
+ ax_menu=ax_menu,
27
+ soil=True,
28
+ )
29
+ soil.make_object(x0=0, y0=0, z0=-200, height=150, radius=200, axis='z')
30
+
31
+ png = bg.Geometry(
32
+ color='green', name='PNG (TR1)', h_menu=0.89, fig=fig, ax_3d=ax_3d, ax_menu=ax_menu
33
+ )
34
+ png.make_object(x0=0, y0=-30, z0=0, height=30, radius=10, axis='y')
35
+
36
+ rover = bg.Geometry(
37
+ color='black',
38
+ name='Rover (TR2)',
39
+ h_menu=0.65,
40
+ fig=fig,
41
+ ax_3d=ax_3d,
42
+ ax_menu=ax_menu,
43
+ )
44
+ rover.make_object(x0=30, y0=-50, z0=0, height=100, radius=12, axis='y')
45
+
46
+ detector = bg.Geometry(
47
+ color='red',
48
+ name='Detector (TR3)',
49
+ h_menu=0.42,
50
+ fig=fig,
51
+ ax_3d=ax_3d,
52
+ ax_menu=ax_menu,
53
+ )
54
+ detector.make_object(x0=60, y0=0, z0=0, height=-10, radius=5, axis='z')
55
+
56
+ gui = bg.Transform(file_path, [soil, png, rover, detector])
mcnptools/mcnprun.py ADDED
@@ -0,0 +1,187 @@
1
+ """
2
+ Run MCNP through Python using the package subprocess.
3
+ Code modifed from https://gist.github.com/abnowack
4
+ """
5
+
6
+ import numpy as np
7
+ import subprocess
8
+ import shutil
9
+ import os
10
+ from pathlib import Path
11
+
12
+
13
+ def set_mcnp_environment(DATAPATH, ISCDATA, MCNPPATH, force=False):
14
+ if force or 'DATAPATH' not in os.environ:
15
+ os.environ['DATAPATH'] = DATAPATH
16
+
17
+ if force or 'ISCDATA' not in os.environ:
18
+ os.environ['ISCDATA'] = ISCDATA
19
+
20
+ if force or 'MCNPPATH' not in os.environ:
21
+ os.environ['MCNPPATH'] = MCNPPATH
22
+ os.environ['PATH'] += os.pathsep + str(os.environ['MCNPPATH'])
23
+
24
+
25
+ def create_directory(directory):
26
+ """
27
+ If directory already exists, append 1 to the name and create a new one.
28
+ If directory does not exist, create it.
29
+
30
+ Parameters:
31
+ directory (Path object): Directory path to check/create.
32
+ """
33
+ if directory.exists():
34
+ print(f"Directory '{directory}' already exists.")
35
+ new_name = directory.name + f'-{np.random.randint(0, 999)}'
36
+ new_dir = directory.parent / new_name
37
+ new_dir.mkdir()
38
+ print(f"Directory '{new_dir}' created successfully.")
39
+ else:
40
+ new_dir = directory
41
+ new_dir.mkdir()
42
+ print(f"Directory '{new_dir}' created successfully.")
43
+ return new_dir
44
+
45
+
46
+ def clean_directory(clean_dir):
47
+ shutil.rmtree(clean_dir)
48
+
49
+
50
+ DATAPATH = r'C:\MCNP62\MCNP_DATA'
51
+ ISCDATA = r'C:\MCNP62\MCNP_CODE\MCNP620\Utilities\ISC\data'
52
+ MCNPPATH = r'C:\MCNP62\MCNP_CODE\bin'
53
+
54
+ set_mcnp_environment(DATAPATH, ISCDATA, MCNPPATH)
55
+
56
+ input_file = 'C:/Users/mayll/Documents/MCNP-tools/examples/Data/proton-dist.i'
57
+ # test_dir = "C:/Users/mayll/Documents/MCNP-tools/test-delete"
58
+
59
+ # args = f"i={input_file} o=testout.o"
60
+
61
+ # run = subprocess.run("mcnp6.exe " + args, shell=True, cwd=test_dir, capture_output=True)
62
+ # print(run)
63
+
64
+
65
+ def run_mcnp(input_file, out_dir=None, out_name=None, cores=1, clean=False):
66
+ """
67
+ Python interface for running MCNP
68
+
69
+ Parameters
70
+ ----------
71
+ input_file : string
72
+ Input file full path.
73
+ out_dir : string, optional.
74
+ Directory where output files will be saved. The default is None
75
+ (will be saved to the same directory as the input file)
76
+ out_name : string, optional.
77
+ Output file name. The default is None
78
+ (will be saved with the same name as the input file)
79
+ cores : int, optional
80
+ Number of threads used (not available for all particle types). The default is 1.
81
+ clean : bool, optional
82
+ Delete secondary output files. The default is False.
83
+
84
+ Returns
85
+ -------
86
+ Dictionary with information about the run.
87
+ """
88
+ input_file = Path(input_file)
89
+ if out_dir is None:
90
+ out_dir = input_file.parent / 'test-folder'
91
+ new_out_dir = create_directory(out_dir)
92
+ else:
93
+ new_out_dir = Path(out_dir)
94
+ if out_name is None:
95
+ new_name = input_file.name
96
+ else:
97
+ new_name = out_name
98
+ if cores <= 2:
99
+ args = f'i={input_file} o={new_name}.o'
100
+ else:
101
+ args = f'i={input_file} o={new_name}.o tasks {cores}'
102
+
103
+ args = f'i={input_file} o={new_name}.o'
104
+ run = subprocess.run('mcnp6.exe ' + args, shell=True, cwd=new_out_dir, capture_output=True)
105
+ return run
106
+
107
+
108
+ out_dir = 'C:/Users/mayll/Documents/MCNP-tools/examples/Data/test-folder'
109
+ run = run_mcnp(input_file=input_file)
110
+
111
+ # @contextmanager
112
+ # def run_mcnp(card, extra_args=None, params=None, cores=1, clean=True):
113
+ # """ Python interface for running MCNP programs.
114
+ # Parameters
115
+ # ----------
116
+ # card : str
117
+ # MCNP input card, can contain variable expressions using str.format(params)
118
+ # extra_args: dict
119
+ # Dictionary of additional arguments for MCNP, eg `mcnp $KEY=$VALUE`
120
+ # params : dict, optional
121
+ # Values to apply on MCNP input card
122
+ # cores : int
123
+ # Number of CPU cores for MCNP to run on
124
+ # clean : bool
125
+ # Clean the temporary output directory
126
+ # Returns
127
+ # -------
128
+ # run : subprocess.CompletedProcess
129
+ # Contains successful run information
130
+ # output_dir : file directory
131
+ # Temporary output directory containing MCNP results
132
+ # """
133
+ # if params:
134
+ # card = card.format(**params)
135
+
136
+ # args = []
137
+ # if extra_args:
138
+ # for (arg, val) in extra_args:
139
+ # args.append('{}={}'.format(arg, val))
140
+
141
+ # args.append('tasks {}'.format(cores))
142
+
143
+ # # create temp folder
144
+ # output_dir = tempfile.mkdtemp()
145
+ # with open(output_dir + '\\input.i', 'w') as f:
146
+ # f.write(card)
147
+
148
+ # args.append('i=input.i')
149
+
150
+ # # mcnp doesn't use errorcodes or stderr, so Popen doesnt add features
151
+ # # stick with simpler check_output and parse stdout
152
+ # run = subprocess.run(['mcnp6.exe'] + args, shell=True, cwd=output_dir, capture_output=True)
153
+
154
+ # # check for errors in stdout
155
+ # stdout_str = str(run.stdout)
156
+ # if 'bad trouble' in stdout_str or 'fatal error' in stdout_str:
157
+ # print('Error in MCNP outout')
158
+ # print(run.args)
159
+ # print(run.stdout)
160
+ # status = False
161
+ # else:
162
+ # status = True
163
+
164
+ # try:
165
+ # yield (run, output_dir)
166
+ # finally:
167
+ # if clean:
168
+ # print(output_dir)
169
+ # clean_directory(output_dir)
170
+
171
+ # if __name__ == '__main__':
172
+ # DATAPATH=r'C:\MCNP62\MCNP_DATA'
173
+ # ISCDATA=r'C:\MCNP62\MCNP_CODE\MCNP620\Utilities\ISC\data'
174
+ # MCNPPATH=r'C:\MCNP62\MCNP_CODE\bin'
175
+
176
+ # set_mcnp_environment(DATAPATH, ISCDATA, MCNPPATH)
177
+
178
+
179
+ # input_card = \
180
+ # r"""PUT YOUR MCNP INPUT CARD HERE
181
+ # IT CAN HAVE {} for formatting the card with values filled in by params arg in run_mcnp
182
+ # """
183
+
184
+ # # use run_mcnp in the following way as a context manager
185
+ # with run_mcnp(input_card, cores=4, clean=False) as (run, out_dir):
186
+ # print(run.stdout)
187
+ # print(out_dir)
pymcnp/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ from . import cli
2
+ from .files import inp
3
+ from .files import outp
4
+ from .files import ptrac
5
+ from .files import utils
6
+ from .files.inp import Inp
7
+ from .files.outp import ReadOutput
8
+ from .files.ptrac import Ptrac
9
+
10
+ from .version import __version__
11
+
12
+ __all__ = [
13
+ "cli",
14
+ "inp",
15
+ "outp",
16
+ "ptrac",
17
+ "utils",
18
+ "Inp",
19
+ "ReadOutput",
20
+ "Ptrac",
21
+ "__version__",
22
+ ]
pymcnp/cli/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .ls import Ls
2
+ from .run import Run
3
+
4
+ __all__ = [
5
+ 'Ls',
6
+ 'Run',
7
+ ]
pymcnp/cli/_io.py ADDED
@@ -0,0 +1,41 @@
1
+ """
2
+ '_utils'
3
+ """
4
+
5
+ import datetime
6
+
7
+
8
+ ERROR_INSUFFICENT_ARGS = '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m Insufficent Arguments.'
9
+ ERROR_EXCESSIVE_ARGS = '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m Excesive Arguments.'
10
+ ERROR_UNRECOGNIZED_ARGS = '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m Unrecognized Arguments.'
11
+ ERROR_UNRECOGNIZED_OPTION = '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m Unrecognized Option.'
12
+ ERROR_ALIAS_NOT_FOUND = '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m INP Alias Not Found.'
13
+ ERROR_FILE_NOT_FOUND = '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m File Not Found.'
14
+
15
+
16
+ def ERROR_INP_VALUE(msg: str) -> str:
17
+ return '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m INP Syntax Error: ' + msg
18
+
19
+
20
+ def ERROR_INP_SYNTAX(msg: str) -> str:
21
+ return '\x1b[1mpymcnp: \x1b[31mERROR:\x1b[0m INP Value Error: ' + msg
22
+
23
+
24
+ INFO_RUNNING_CQEDITOR = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Launching CQ-Editor.'
25
+ INFO_RUNNING_MCNP = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Launching MCNP.'
26
+ INFO_BUILD_INP = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Built INP.'
27
+ INFO_BUILD_CELL = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Built Cell.'
28
+ INFO_BUILD_SURFACE = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Built Surface.'
29
+ INFO_BUILD_DATUM = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Built Datum.'
30
+ INFO_BUILD_TITLE = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Built Title.'
31
+ INFO_BUILD_MESSAGE = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Built Message.'
32
+ INFO_BUILD_OTHER = '\x1b[1mpymcnp: \x1b[31m\x1b[34mINFO:\x1b[0m Built Other.'
33
+
34
+
35
+ def get_timestamp() -> str:
36
+ return datetime.datetime.today().strftime('%Y-%m-%d--%H-%M-%S')
37
+
38
+
39
+ def error(msg):
40
+ print(msg)
41
+ exit(1)
pymcnp/cli/_state.py ADDED
@@ -0,0 +1,131 @@
1
+ import inspect
2
+ from pathlib import Path
3
+ import sys
4
+ from typing import Callable
5
+
6
+
7
+ class FileTable:
8
+ """Store information in a file, so that it can be used across multiple processes.
9
+
10
+ We use this mechanism to store information that is needed when, for example,
11
+ running MCNP simulations in parallel.
12
+ """
13
+
14
+ def __init__(self):
15
+ self.table = {}
16
+ self.path = Path.cwd() / '.pymcnp' / '_files.py'
17
+
18
+ self.path.parent.mkdir(exist_ok=True, parents=True)
19
+
20
+ if not self.path.is_file():
21
+ self.path.write_text('table = {}')
22
+
23
+ def append(self, alias, path):
24
+ self._sync_up()
25
+
26
+ if alias in self._table:
27
+ raise ValueError
28
+
29
+ self._table[alias] = path
30
+ self._sync_down()
31
+
32
+ def remove(self, alias):
33
+ self._sync_up()
34
+
35
+ if alias not in self._table:
36
+ raise ValueError
37
+
38
+ path = self._table.pop(alias)
39
+ self._sync_down()
40
+
41
+ return path
42
+
43
+ def access(self, alias):
44
+ self._sync_up()
45
+
46
+ if alias not in self._table:
47
+ raise ValueError
48
+
49
+ return self._table[alias]
50
+
51
+ def _sync_up(self):
52
+ """Read information from `_files.py`."""
53
+
54
+ path = sys.path
55
+ sys.path.append(str(self.path.parent.absolute()))
56
+ import _files
57
+
58
+ sys.path = path
59
+
60
+ self._table = _files.table
61
+
62
+ return self
63
+
64
+ def _sync_down(self):
65
+ """Write information to `_files.py`."""
66
+
67
+ self.path.write_text(f'table = {self._table.__repr__()}')
68
+
69
+ def __iter__(self):
70
+ self = self._sync_up()
71
+ return iter(self._table.items())
72
+
73
+
74
+ class RunConfig:
75
+ """Store pre and posthooks as well as the run command for parallel runs."""
76
+
77
+ def __init__(self):
78
+ self.path = Path.cwd() / '.pymcnp' / '_run.py'
79
+ self.command = 'mcnp'
80
+
81
+ self.path.parent.mkdir(exist_ok=True, parents=True)
82
+
83
+ if not self.path.is_file():
84
+ self._sync_down()
85
+
86
+ def prehook(self):
87
+ pass
88
+
89
+ def posthook(self):
90
+ pass
91
+
92
+ def set_command(self, command: str):
93
+ self.command = command
94
+ self._sync_down()
95
+
96
+ def set_prehook(self, prehook: Callable):
97
+ self.prehook = prehook
98
+ self._sync_down()
99
+
100
+ def set_posthook(self, posthook: Callable):
101
+ self.posthook = posthook
102
+ self._sync_down()
103
+
104
+ def _sync_up(self):
105
+ """Read command, prehook, and posthook from `_run.py`."""
106
+
107
+ path = sys.path
108
+ sys.path.append(str(self.path.parent.absolute()))
109
+ import _run
110
+
111
+ sys.path = path
112
+
113
+ self.command = _run.command
114
+ self.prehook = _run.prehook
115
+ self.posthook = _run.posthook
116
+
117
+ return self
118
+
119
+ def _sync_down(self):
120
+ """Write command, prehook, and posthook to `_run.py`."""
121
+
122
+ prehook = 'def prehook():\n' + ''.join(inspect.getsourcelines(self.prehook)[0][1:])
123
+ posthook = 'def posthook():\n' + ''.join(inspect.getsourcelines(self.posthook)[0][1:])
124
+
125
+ self.path.write_text(
126
+ f"command = '{self.command}'\n" + '\n' + prehook + '\n' + posthook + '\n'
127
+ )
128
+
129
+
130
+ table = FileTable()._sync_up()
131
+ run = RunConfig()._sync_up()
pymcnp/cli/file.py ADDED
@@ -0,0 +1,49 @@
1
+ """
2
+ ``file`` provides utilities for aliasing PyMCNP file objects.
3
+
4
+ ``file`` contains the procedures for executing the ``pymcnp file`` command.
5
+ This module underpins the CLI by simplifying commands.
6
+ """
7
+
8
+ import sys
9
+
10
+ import docopt
11
+
12
+ from . import _state
13
+
14
+
15
+ PYMCNP_INP_DOC = """
16
+ Usage:
17
+ pymcnp file <alias> ( --read=<file> | --delete )
18
+
19
+ Options:
20
+ -r --read=<file> Read INP file and create alias.
21
+ -d --delete Delete alias.
22
+ """
23
+
24
+
25
+ def main(argv: list[str] = sys.argv[1:]) -> None:
26
+ """
27
+ ``main`` executes the ``pymcnp file`` command.
28
+
29
+ ``main`` processes the given command line arguments, and creates or deletes
30
+ PyMCNP aliased INP objects.
31
+
32
+ Parameters:
33
+ argv: Tokenized list of CLI arguments.
34
+ """
35
+
36
+ args = docopt.docopt(PYMCNP_INP_DOC, argv=argv)
37
+
38
+ if args['--read']:
39
+ # Adding alias to save.
40
+ try:
41
+ _state.table.append(args['<alias>'], args['--read'])
42
+ except ValueError:
43
+ print('ALREADY!')
44
+ elif args['--delete']:
45
+ # Removing alias from save.
46
+ try:
47
+ _state.table.remove(args['<alias>'])
48
+ except ValueError:
49
+ print('NOT THERE!')
pymcnp/cli/ls.py ADDED
@@ -0,0 +1,161 @@
1
+ """
2
+ ``ls`` provides utilities for listing MCNP information.
3
+
4
+ ``ls`` contains the procedures for executing the ``pymcnp ls`` command and
5
+ utilities for listing MCNP information.
6
+ """
7
+
8
+ import sys
9
+ from typing import Final
10
+
11
+ import docopt
12
+
13
+ from ..files import inp
14
+ from . import _state
15
+
16
+
17
+ class Ls:
18
+ """
19
+ ``Ls``
20
+ """
21
+
22
+ def __init__(self, inpt: inp.Inp):
23
+ """
24
+ ``__init__`` initializes ``Ls``.
25
+ """
26
+
27
+ if inpt is None:
28
+ raise ValueError
29
+
30
+ self.inpt: Final[inp.Inp] = inpt
31
+
32
+ def list_cells(self) -> str:
33
+ """
34
+ ``list_cells`` tabulates INP object cell card information.
35
+
36
+ Returns:
37
+ String of INP object cell information.
38
+ """
39
+
40
+ out = (
41
+ '\x1b[4m{:^12.12}\x1b[24m ' * 3 + '\x1b[4m{:^25.25}\x1b[24m \x1b[4m{:^25.25}\x1b[24m\n'
42
+ )
43
+ out = out.format('NUMBER', 'MATERIAL', 'DENSITY', 'GEOMETRY', 'OPTIONS')
44
+
45
+ for cell in self.inpt.cells._cards.values():
46
+ out += (
47
+ f"{cell.number.to_mcnp():<12} "
48
+ f"{cell.material.to_mcnp():<12} "
49
+ f"{cell.density.to_mcnp() if cell.density.to_mcnp() else '':<12} "
50
+ f"{cell.geometry.to_mcnp():<25} {'':<25}\n"
51
+ )
52
+
53
+ return out
54
+
55
+ def list_surfaces(self) -> str:
56
+ """
57
+ ``list_surfaces`` tabulates INP object surface card information.
58
+
59
+ Returns:
60
+ String of INP object surface information.
61
+ """
62
+
63
+ out = '\x1b[4m{:^12.12}\x1b[24m \x1b[4m{:^12.12}\x1b[24m \x1b[4m{:^12.12}\x1b[24m \x1b[4m{:^51.51}\x1b[24m\n'
64
+ out = out.format('NUMBER', 'MNEMONIC', 'TRANSFORM', 'PARAMETERS')
65
+
66
+ for surface in self.inpt.surfaces._cards.values():
67
+ out += (
68
+ f"{surface.number.to_mcnp():<12} "
69
+ f"{surface.mnemonic.to_mcnp():<12} "
70
+ f"{surface.transfrom.to_mcnp() if surface.transform else '':<12} "
71
+ f"{'':<51}\n"
72
+ )
73
+
74
+ return out
75
+
76
+ def list_data(self) -> str:
77
+ """
78
+ ``list_data`` tabulates INP object data card information.
79
+
80
+ Returns:
81
+ String of INP object data information.
82
+ """
83
+
84
+ out = '\x1b[4m{:^12.12}\x1b[24m \x1b[4m{:^12.12}\x1b[24m \x1b[4m{:^12.12}\x1b[24m \x1b[4m{:^51.51}\x1b[24m\n'
85
+ out = out.format('NUMBER', 'SUFFIX', 'DESIGNATOR', 'PARAMETERS')
86
+
87
+ for datum in self.inpt.data._cards.values():
88
+ out += (
89
+ f"{datum.mnemonic:<12.12} "
90
+ f"{datum.suffix.to_mcnp() if hasattr(datum, 'suffix') else '':<12} "
91
+ f"{datum.designator.to_mcnp() if hasattr(datum, 'designator') else '':<12} "
92
+ f"{'':<51.51}\n"
93
+ )
94
+
95
+ return out
96
+
97
+ @staticmethod
98
+ def list_inpts(inpts: tuple[str, inp.Inp]) -> str:
99
+ """
100
+ ``list_inpts`` tabulates INP objects.
101
+
102
+ Returns:
103
+ String of INP object inpt information.
104
+ """
105
+
106
+ out = '\x1b[4m{:^25.25}\x1b[24m \x1b[4m{:^51.51}\x1b[24m \x1b[4m{:^51.51}\x1b[24m\n'.format(
107
+ 'NAME', 'TITLE', 'OTHER'
108
+ )
109
+ for alias, inpt in list(inpts):
110
+ out += f'{alias:<25.25} {inpt.title:<51.51} {repr(inpt.other):<51.51}\n'
111
+
112
+ return out
113
+
114
+
115
+ PYMCNP_LS_DOC = """
116
+ Usage:
117
+ pymcnp ls [(<alias> (--cells|--surfaces|--data)...)]
118
+
119
+ Options:
120
+ -f --file List from INP file.
121
+ -c --cells List aliased INP cell cards.
122
+ -s --surfaces List aliased INP surface cards.
123
+ -d --data List aliased INP data cards.
124
+ """
125
+
126
+
127
+ def main(argv: list[str] = sys.argv[1:]) -> None:
128
+ """
129
+ ``main`` executes the ``pymcnp ls`` command.
130
+
131
+ ``main`` processes the given command line arguments, and it lists, prints,
132
+ and tabulates PyMCNP object contents.
133
+
134
+ Parameters:
135
+ argv: Tokenized list of CLI arguments.
136
+ """
137
+
138
+ args = docopt.docopt(PYMCNP_LS_DOC, argv=argv)
139
+
140
+ if args['<alias>']:
141
+ # Listing aliased PyMCNP object content.
142
+
143
+ try:
144
+ filename = _state.table.access(args['<alias>'])
145
+ except ValueError:
146
+ print('NOPE!')
147
+ exit(1)
148
+
149
+ ls = Ls(inp.Inp.from_mcnp_file(filename))
150
+
151
+ if args['--cells']:
152
+ print(ls.list_cells())
153
+ if args['--surfaces']:
154
+ print(ls.list_surfaces())
155
+ if args['--data']:
156
+ print(ls.list_data())
157
+ else:
158
+ # Listing all aliased PyMCNP objects.
159
+ print(
160
+ Ls.list_inpts([(alias, inp.Inp.from_mcnp_file(path)) for alias, path in _state.table])
161
+ )