Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add draft for ff-output #442

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

add draft for ff-output #442

wants to merge 6 commits into from

Conversation

fgrunewald
Copy link
Member

@fgrunewald fgrunewald commented Jul 6, 2022

An initial draft for a ff-file writer. @pckroon some initial feedback would be appreciated. Once the general layout is good, I write tests.

@fgrunewald fgrunewald requested a review from pckroon July 6, 2022 09:28
Copy link
Member

@pckroon pckroon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments and design remarks. I don't think sphinx will like your docstrings at the moment, but we can take a look at that later.

What I'm mainly missing is the why. Why do we want/need a FF writer?
As for the functionality of the code, I guess it looks ok, but the proof will be in the pudding. Ideally you'd make some tests where hypothesis creates a ForceField, you write it, you parse it, and you confirm they're the same. Undoubtedly there are corner cases missing here, such as [variables], and all the thinks links can do...

Comment on lines 42 to 43
self.normal_order_block_atoms = ["atype", "resid", "resname",
"atomname", "charge_group", "charge", "mass"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe these should be class attributes/variables rather than instance attributes/variables.

Comment on lines +49 to +54
for name, block in self.forcefield.blocks.items():
self.stream.write("[ moleculetype ]\n")
excl = str(block.nrexcl)
self.stream.write(f"{name} {excl}\n")
self.write_atoms_block(block.nodes(data=True))
self.write_interaction_dict(block.interactions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stupid suggestion: how do you feel about giving blocks a str/write/repr method instead?
Also for links/modifications/...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move this one further up the chain, give it to ForceField. Note that I'm not convinced it's a good idea though, just an idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, group all these into a write_block function

Comment on lines 57 to 60
self.write_link_header(link)
self.write_atoms_link(link.nodes(data=True))
self.write_interaction_dict(link.interactions)
self.write_edges(link.edges)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group these to write_link

inter_dict: `class:dict[list[vermouth.molecule.Interaction]]`
the interaction dict to write
"""
for inter_type in inter_dict:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See also Molecule.sort_interactions

Comment on lines +82 to +93
def write_edges(self, edges):
"""
Writes edges to `self.stream` into the edges directive.

Parameters
----------
edges: abc.iteratable
pair-wise iteratable edge list
"""
self.stream.write("[ edges ]\n")
for idx, jdx in edges:
self.stream.write(f"{idx} {jdx}\n")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only write edges that are not already in interactions?

Parameters
----------
edges: abc.iteratable
pair-wise iteratable edge list
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pairwise edges?

self.stream.write("[ atoms ]\n")
for idx, (node, attrs) in enumerate(nodes):
idx += 1
attr_line = " ".join([str(attrs[attr]) for attr in self.normal_order_block_atoms ])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about any other attributes?

"""
self.stream.write("[ atoms ]\n")
for node_key, attributes in nodes:
attr_line = " {" + " ,".join([f"\"{key}\": \"{value}\"" for key, value in attributes.items()]) + "}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attr line is defined as json, so abuse that with json.dumps

for interaction in inter_dict[inter_type]:
atom_string = " ".join(interaction.atoms)
param_string = " ".join(interaction.parameters)
meta_string = "{" + " ,".join([f"\"{key}\": \"{value}\"" for key, value in interaction.meta.items()]) + "}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json.dumps

@fgrunewald
Copy link
Member Author

@pckroon the why is easy: Because we already use that in 2 projects relating to automatic link generation from template itp-files. Also it is useful in the API as you can read in ff-files manipulate them not manually and then write the links again e.g. look for all links that do this and change attribute there.

About the corner cases, they are not so bad. The input files are more condensed because of some hacks and tricks but once they are at link level everything gets resolved. The writer will not write the same force-field by line-comparison but a more verbose but also more robust version. For examples all variables should be resolved in the link, they just get written directly to the links.

@pckroon
Copy link
Member

pckroon commented Jul 6, 2022

@pckroon the why is easy: Because we already use that in 2 projects relating to automatic link generation from template itp-files. Also it is useful in the API as you can read in ff-files manipulate them not manually and then write the links again e.g. look for all links that do this and change attribute there.

Ok, that sounds very reasonable. I personally also like being to both read and write things at the same time.

About the corner cases, they are not so bad. The input files are more condensed because of some hacks and tricks but once they are at link level everything gets resolved. The writer will not write the same force-field by line-comparison but a more verbose but also more robust version. For examples all variables should be resolved in the link, they just get written directly to the links.

This I understand and agree with. But parsing the manual/condensed and the automatic/explicit FF must result in the same FF object. So comparison should be done on parsed FF level indeed.
I think you're confusing macros and variables here ;) Macros will be resolved upon parsing and are invisible to the FF object. Variables are not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants