Different behavior in `fill_box` and `fill_region`. `fill_region` errors when using a list of Compounds
chrisjonesBSU opened this issue · 5 comments
Bug summary
the fill_region
function in packing.py
isn't working as expected, specifically when using a list of compounds and a list of n_compounds
Code to reproduce the behavior
import mbuild as mb
methane = mb.load("C", smiles=True)
ethane = mb.load("CC", smiles=True)
# This works with `fill_box`
box_compound = mb.fill_box(
compound=[methane, ethane], n_compounds=[10, 10], box=[2,2,2]
)
# This fails with `fill_region`
box = mb.box.Box(lengths=[2, 2, 2], angles=[90, 90, 90])
region_compound = mb.fill_region(
compound=[methane, ethane],
n_compounds=[10, 10],
region=box,
bounds = [[0, 0, 1, 2, 2, 2]]
)
I get the following error when running the mb.fill_region
portion:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[3], line 5
2 ethane = mb.load("CC", smiles=True)
4 box = mb.box.Box(lengths=[2, 2, 2], angles=[90, 90, 90])
----> 5 region_compound = mb.fill_region(
6 compound=[methane, ethane],
7 n_compounds=[10, 10],
8 region=box,
9 bounds = [[0, 0, 1, 2, 2, 2]]
10 )
File ~/miniconda3/envs/test-mbuild/lib/python3.11/site-packages/mbuild/packing.py:431, in fill_region(compound, n_compounds, region, overlap, bounds, seed, sidemax, edge, fix_orientation, temp_file, update_port_locations)
429 filled = Compound()
430 filled = _create_topology(filled, compound, n_compounds)
--> 431 filled.update_coordinates(
432 filled_xyz.name, update_port_locations=update_port_locations
433 )
434 finally:
435 for file_handle in compound_xyz_list:
File ~/miniconda3/envs/test-mbuild/lib/python3.11/site-packages/mbuild/compound.py:1818, in Compound.update_coordinates(self, filename, update_port_locations)
1816 self._update_port_locations(xyz_init)
1817 else:
-> 1818 self = conversion.load(filename, compound=self, coords_only=True)
File ~/miniconda3/envs/test-mbuild/lib/python3.11/site-packages/mbuild/conversion.py:127, in load(filename_or_object, relative_to_module, compound, coords_only, rigid, smiles, infer_hierarchy, backend, ignore_box_warn, **kwargs)
117 return load_smiles(
118 smiles_or_filename=filename_or_object,
119 compound=compound,
(...)
123 **kwargs,
124 )
125 # Last, if none of the above, load from file
126 else:
--> 127 return load_file(
128 filename=filename_or_object,
129 relative_to_module=relative_to_module,
130 compound=compound,
131 coords_only=coords_only,
132 rigid=rigid,
133 backend=backend,
134 infer_hierarchy=infer_hierarchy,
135 **kwargs,
136 )
File ~/miniconda3/envs/test-mbuild/lib/python3.11/site-packages/mbuild/conversion.py:412, in load_file(filename, relative_to_module, compound, coords_only, rigid, backend, infer_hierarchy, **kwargs)
410 tmp = read_xyz(filename)
411 if tmp.n_particles != compound.n_particles:
--> 412 raise ValueError(
413 "Number of atoms in {filename}"
414 "does not match {compound}".format(**locals())
415 )
416 ref_and_compound = zip(
417 tmp._particles(include_ports=False),
418 compound.particles(include_ports=False),
419 )
420 for ref_particle, particle in ref_and_compound:
ValueError: Number of atoms in /tmp/tmpn75wd68o.xyzdoes not match <Compound 130 particles, 110 bonds, non-periodic, id: 140559818198608>
fill_region
does work when compound
and n_compounds
aren't passed in as lists larger than length 1:
methane = mb.load("C", smiles=True)
box = mb.box.Box(lengths=[2, 2, 2], angles=[90, 90, 90])
region_compound = mb.fill_region(
compound=[methane],
n_compounds=[20],
region=box,
bounds = [[0, 0, 1, 2, 2, 2]]
)
Software versions
- Which version of mBuild are you using? 0.15.1
- Which version of Python (
python --version
)? 3.11 - Which operating system? Linux
Following!
Ok, turns out this wasn't really a bug in the functionality, but poor documentation and a missing check/validation step.
397 for comp, m_compounds, rotate, items_n in zip(
398 compound, n_compounds, fix_orientation, container
399 ):
In this for loop/zip, if container
is only of length 1 while compound
and n_compounds
are of length loner than 1, it only goes through this for loop once (or as long as the smallest length item in zip
)
I think we'll have to handle bounds
in the same way we do fix_orientation
343 if not isinstance(fix_orientation, (list, set)):
344 fix_orientation = [fix_orientation] * len(compound)
In the example of my original comment, I only have 1 boundary condition, that I want to fill with both methane and ethane (i.e. fill the region above a surface), so I would have to do something like:
import mbuild as mb
methane = mb.load("C", smiles=True)
ethane = mb.load("CC", smiles=True)
pack_box = mb.box.Box(lengths=[2, 2, 2], angles=[90, 90, 90])
fill_bounds = [0, 0, 1, 2, 2, 2]
region_compound = mb.fill_region(
compound=[methane, ethane],
n_compounds=[10, 10],
region=pack_box,
bounds=[[fill_bounds, fill_bounds]]
)
I think it would make sense to be able to use a single boundary condition that can be used with any length of compound
and n_compounds
, while still having the functionality to define boundaries on a molecule-by-molecule basis, which is basically how fix_orientation
works.
I'll work on a PR.
Although, we could keep it as is, and require that the user is explicit in defining packing boundaries (like my example above), then we just need a check to make sure bounds
is the same length as compound
and n_compounds
. It is convenient to only have to define the boundary condition once if you want to pack it with a mixture, but also, requiring the user to be more explicit/thoughtful in what parameters they are passing in helps avoid confusion and assumptions that cause issues later.
Good find! Yeah, I agree a smitch more documentation + better validation (so more meaningful error) would be great!
Closed by #1088