Handling ModelCIF (modelarchive.modelcif)
The modelcif module assists the ModelArchive team to prepare deposition data before storing in the database. For the most part this involves converting files from PDB legacy format into ModelCIF, as well as refining submitted mmCIF/ ModelCIF files.
Functionality can be broadly divided into accessing and editing ModelCIF files.
One note on performance: by the nature of the task addressed here, code clarity is preferred over raw efficiency. Scripts that translate user data into ModelCIF for a deposition, are run once and offline, so it does not matter if execution takes one minute or five. In general, preparing data usually takes far longer than running the code itself.
Keep this in mind when implementing ModelCIF support in your own tool, you may prefer to draw inspiration from this module rather than use it directly… or use the python-modelcif package straight away.
Accessing ModelCIF (modelarchive.modelcif.access)
Functionality to access data in a ModelCIF file.
- class modelarchive.modelcif.access.MABlock(model_data)[source]
Bases:
objectWrapper around
gemmi.cif.Blockfor mmCIF/ ModelCIF structure files.Reads a single mmCIF block from a file or string and exposes the full
gemmi.cif.Blockinterface via attribute delegation, extended by convenience methods for common ModelCIF operations.- source
Path to the mmCIF input file,
Noneifmodel_dataprovides CIF data as string.- Type:
str | None
- doc
The parsed CIF document.
- Type:
- block
The sole block of the CIF document.
- Type:
- __getattr__(name)[source]
Delegate attribute lookup to the wrapped
gemmi.cif.Block.Called only when normal attribute resolution has already failed (default Python behaviour), so
self.blockitself is always found through the standard mechanism. Any attribute present onblockis transparently forwarded; anything else raisesAttributeErroras usual.- Parameters:
name (str) – Name of the attribute to look up.
- Returns:
The attribute value from
block.- Return type:
- Raises:
AttributeError – If
nameis not found onblockeither.
- __iter__()[source]
Iterate over the wrapped
gemmi.cif.Block.Delegates directly to
gemmi.cif.Block.__iter__(), yielding whatever the underlying block exposes during iteration (typically its items).- Returns:
An iterator over the block’s contents.
- Return type:
iterator
- add_category(category, after=None, **kwargs)[source]
Add a new single-row category to the block.
If the category already exists it is overwritten. The new category can be positioned immediately after an existing one by passing its name as
after.- Parameters:
category (str) – mmCIF category name to create.
after (str | None) – Name of an existing category after which the new one should be placed. If
None, the category is appended to the end.**kwargs – Item names mapped to lists of values (one value per row — pass single-element lists for a one-row category).
- Returns:
None
- add_to_category(category, match=None, silent=False, **kwargs)[source]
Update item values in an existing mmCIF category.
Locates a row in
categoryand overwrites the values of the items named bykwargs. Whenmatchis given, the row is identified by a key–value pair; otherwise the category must contain exactly one row.Existing non-placeholder values (i.e. not
'.'or'?') are reported to stderr unlesssilentisTrue.- Parameters:
category (str) – mmCIF category name, e.g.
'_entry'.match (tuple[str, str] | None) – A
(item_name, value)pair used to identify the target row. IfNone, the category must have exactly one row.silent (bool) – Suppress replacement messages. Defaults to
False.**kwargs – Item names mapped to their new values.
- Returns:
None
- Raises:
RuntimeError – If
categoryis absent or empty (viafind_strict()), or if no row matches thematchcriterion.
- find_strict(name, columns)[source]
Return a table from the block, raising if it is absent or empty.
- Parameters:
- Returns:
The requested table.
- Return type:
gemmi.cif.Table
- Raises:
RuntimeError – If the table is not found or contains no rows.
- get_category(category)[source]
Return a category as a dict, compatible with
add_category().Wraps
gemmi.cif.Block.get_mmcif_category(). Returns an empty dict when the category is not present in the block. The returned dict can be modified and passed directly toadd_category()to round-trip a category:entity_dict = block.get_category("entity") # ... modify entity_dict ... block.add_category("entity", **entity_dict)
- get_sequence(entity)[source]
Return the one-letter sequence for a polymer entity.
Reads residue entries from
_entity_poly_seqand converts each three-letter code to a one-letter code viagemmi.find_tabulated_residue(). Reading is done without caching, so calling this method repeatedly for many entities is inefficient.- Parameters:
entity (str) – Numeric entity ID as a string.
- Returns:
One-letter amino-acid sequence for the entity.
- Return type:
- Raises:
RuntimeError – If
_entity_poly_seqis absent or empty.RuntimeError – If residue numbering in
_entity_poly_seqis not strictly sequential.
- property polymer_targets
Subset of
targetswhose_entity.typeis'polymer'.Lazily populated on first access by filtering
targets.
- property targets
Mapping of target entities keyed by their entity ID.
Lazily populated on first access from the
_ma_target_entityand_entitycategories. Each value is a dict with the keys_ma_target_entity.entity_id,_ma_target_entity.sequenceand_ma_target_entity.type.- Returns:
Target entity information keyed by entity ID string.
- Return type:
- Raises:
RuntimeError – If an entity ID appears more than once in
_ma_target_entity.
- write_file(filename, compress=False, style=Style.Simple)[source]
Write the CIF document to disk, with optional gzip compression.
If
compressisTrue, or iffilenamealready ends with'.gz', the output is written as a gzip-compressed file. The'.gz'suffix is appended automatically whencompressisTruebut the suffix is missing.
- modelarchive.modelcif.access.get_table(block, category, items=None)[source]
Get a
gemmi.cif.Tablefrom agemmi.cif.Blockfor a category.It is much more convenient to work with
gemmi.cif.Tableobjects instead of Gemmi’s loops and pairs directly. Imagine a ModelCIF file in which a certain category is represented as loop, while another ModelCIF file stores the same category as list of pairs. Both representations may be valid ModelCIF files and would require two separate handlers implemented for essentially the same data.By using
gemmi.cif.Tableas a wrapper, loops and pairs can be treated uniformly, allowing you to handle both cases through a single code base.Gemmi provides two functions to retrieve tables,
find_mmcif_category()andfind(). One of them just needs a category name and the other requires a category name and a list of columns to fetch. So, different behaviour again and… lets just accept:get_table()hides these details away and happily returns a table, whether you provide a list of items or not. If a list of items is given, the resulting table will contain only those columns. Plus, in case the category can’t be found in block, an empty list is returned, which feels more pythonic than getting an empty table back of length 0. Retrieving an empty list also makes looping over a table easier.Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import access >>> # get sample CIF data >>> cif_data = '''data_test ... _ma_qa_metric.id 1 ... _ma_qa_metric.description test_score ... loop_ ... _ma_qa_metric_local.ordinal_id ... _ma_qa_metric_local.metric_value ... _ma_qa_metric_local.metric_id ... 1 1.0 1 ... 2 1.5 1 ... ''' >>> block = cif.read_string(cif_data).sole_block() >>> table = access.get_table(block, "_ma_qa_metric") >>> len(table) 1 >>> table[-1]["description"] 'test_score' >>> table = access.get_table( ... block, ... "_ma_qa_metric_local", ... items=["metric_id", "metric_value"], ... ) >>> # table should have 2 columns and 2 rows >>> table <gemmi.cif.Table 2 x 2> >>> # columns are sorted as requested, not as stored >>> table.tags[0] '_ma_qa_metric_local.metric_id' >>> table.tags[1] '_ma_qa_metric_local.metric_value'
- Parameters:
block (
gemmi.cif.Block) – CIF data block holding the categories of the CIF document.category (str) – Category to fetch from
block, single category only, no Joins. Gemmi requires category names to end with., so this function adds it if missing.items (list[str]) – List of items to fetch as columns. Order of columns (items) follows the provided list. If
None, the whole category with all its items as columns will be fetched. In case ofNone, items are fetched in the same order as they are found in the CIF document.
- Returns:
The requested table if category can be found, otherwise empty list.
- Return type:
Editing ModelCIF (modelarchive.modelcif.edit)
Functionality to extend and modify ModelCIF files.
- exception modelarchive.modelcif.edit.MoveIdxToFarError(category, idx)[source]
Bases:
RuntimeErrorException if repositioning exceeds the size of document-category-list.
Primarily used by
move_category(), on the attempt to move a category to a position that does not exist within the correspondinggemmi.cif.Block. For example, if thegemmi.cif.Blockobject contains 10 categories, trying to move a category to position 15 will fail and should raise this exception.
- exception modelarchive.modelcif.edit.NotFoundCategoryError(category=None, msg=None)[source]
Bases:
NotFoundErrorException if a category can not be found.
This exception should be raised when a function expects a specific category to exist in the corresponding
gemmi.cif.Block, but the category cannot be retrieved.
- exception modelarchive.modelcif.edit.NotFoundError(subject, value, msg)[source]
Bases:
RuntimeErrorGeneral exception for ‘things’ that can not be found.
If
msgis omitted, generates a message “<SUBJECT> ‘<VALUE>’ does not exist”. Ifvalueis a list with more than one element, the message will be written in plural mode. Ifsubjectis a list or tuple, a second element will be used as plural of the subject.This exception should not be raised directly, it exists to define other “NotFound” exceptions inheriting from it.
- Parameters:
subject (str|list|tuple) – The ‘thing’ that can not be found, used in the generated message. If
list:ortuple, a second element is used as plural.value (str|list) – The name of what can not be found, used in the generated message. Provied a list of values to get a message fitting plural.
msg (str) – Optional alternative error message.
- exception modelarchive.modelcif.edit.NotFoundItemError(item=None, msg=None)[source]
Bases:
NotFoundErrorException if an item can not be found.
This exception should be raised when a function expects a specific item to exist in the corresponding CIF category, but the item cannot be retrieved.
- modelarchive.modelcif.edit.add_category(block, category, item_data, index=None, mod_cat_itms=None, raw=False)[source]
Introduce a new category to a
gemmi.cif.Blockand populate it.Add
categorytoblockusing data fromitem_data.item_datais a dictionary with the CIF item names as keys and values as values to the items. On single values, named-pairs will be created, on lists with more than one value, a loop will be created.indexcan be used to place the category at a certain position. Use an integer for a specific place in the category list or a string of form[after|before]:<CATEGORY>for relative positioning.Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import edit >>> # start with an empty CIF document >>> cif_data = '''data_test ... ''' >>> block = cif.read_string(cif_data).sole_block() >>> # lets add entities >>> _ = edit.add_category( ... block, ... "_entity", ... { ... "id": [1, 2, 3], ... "type": ["polymer", "non-polymer", "water"], ... }, ... ) >>> print(block.as_string()) data_test loop_ _entity.id _entity.type 1 polymer 2 non-polymer 3 water >>> # lets add an "_entry" ID before the entities >>> _ = edit.add_category( ... block, "_entry", {"id": "1FOO"}, index="before:_entity" ... ) >>> print(block.as_string()) data_test _entry.id 1FOO loop_ _entity.id _entity.type 1 polymer 2 non-polymer 3 water
- Parameters:
block (
gemmi.cif.Block) – CIF data block holding the categories of the CIF document.category (str) – Name of the new category to be created.
item_data (dict[str, list[Any]|Any]) – Attributes and values to be added to the new category. Dictionary with item names as keys. Values are either a list of values or a single value. If a single value is provided (or a list containing only one element), a named key-value pair is created instead of a loop.
index (int|str) – Placement of the new category within
block. This can be an integer for exact positioning, or a string of form[after|before]:<CATEGORY>for relative positioning. In relative positioning,<CATEGORY>specifies the name of the category before or after whichcatwill be placed.mod_cat_itms (dict[str, set[str]] | None) – A record of what has been modified. Dictionary of category assigned a set of items changed. Items which already have the value of the update, are not recorded. This is meant for the revision history, most likely you can ignore it.
raw (bool, optional) – If True, do not force quoting strings containing whitespace.
- Returns:
A record of what has been modified. To be used with a revision history, most likely you can ignore it.
- Return type:
- Raises:
MoveIdxToFarError – If the target position is outside
block. For example, ifblockcontains 10 categories, trying to create a category at position 15 will raise this error.
- modelarchive.modelcif.edit.add_column(block, category, item, callback, pos=-1, raw=False)[source]
Extend a category with a new item and populate it using a callback.
Thinking of ModelCIF categories as tables, this function adds a new column (item) to a table that already exists in
block. Acallbackfunction, to be provided, is executed with each row to compute the value for the new column. This avoids having a static list to fetch the values from.make_res_per_chain_counter()is an example of a stateful implementation of a working callback.The callback has to be of form
function(row)and return the value to be set for theitemin the givenrow.Examples
>>> # Add "ndb_seq_num" to "_pdbx_nonpoly_scheme" including values >>> # Reminder: "ndb_seq_num" -> column, "_pdbx_nonpoly_scheme" -> table >>> from gemmi import cif >>> from modelarchive.modelcif import edit >>> cif_data = '''data_test ... loop_ ... _pdbx_nonpoly_scheme.asym_id ... _pdbx_nonpoly_scheme.entity_id ... _pdbx_nonpoly_scheme.mon_id ... _pdbx_nonpoly_scheme.pdb_seq_num ... C 1 ATP 1 ... D 2 HEM 1 ... E 3 HOH 1 ... E 3 HOH 2 ... ''' >>> block = cif.read_string(cif_data).sole_block() >>> edit.add_column( ... block, ... "_pdbx_nonpoly_scheme", ... "ndb_seq_num", ... edit.make_res_per_chain_counter("asym_id"), ... pos=-1, ... ) >>> print(block.as_string()) data_test loop_ _pdbx_nonpoly_scheme.asym_id _pdbx_nonpoly_scheme.entity_id _pdbx_nonpoly_scheme.mon_id _pdbx_nonpoly_scheme.pdb_seq_num _pdbx_nonpoly_scheme.ndb_seq_num C 1 ATP 1 1 D 2 HEM 1 1 E 3 HOH 1 1 E 3 HOH 2 2 >>> # "ndb_seq_num" was appended as last column according to pos=-1
- Parameters:
block (
gemmi.cif.Block) – block holding the categories of the CIF document.category (str) – The CIF category (table) to add the item to.
item (str) – The item (column) to be added.
callback (Callable[[
gemmi.cif.Table.Row], int]) – Function to be executed to compute values for each row of the new column.pos (int) – Position to insert the column at. Default is at the end (-1). Inserting at the beginning requires
pos=1.raw (bool) – Force to not quote strings containing white-spaces.
- Returns:
None
- Raises:
NotFoundCategoryError – If
categorycan not be found inblock.
- modelarchive.modelcif.edit.add_rows(block, category, row_dict, ordinal_item='ordinal', mod_cat_itms=None, raw=False)[source]
Add rows to a
categoryinblockusing an item-dictionary.Thinking of ModelCIF categories as tables, this function adds new rows (items) to a table (
category) inblock. Ifcategorydoes not yet exist, it will be created. If multiple rows are provided, the newcategorywill be created as loop, pairs otherwise. When adding row(s) to an existing pairs-category, the function will convert thecategoryinto a loop.Input data is provided via
row_dict. It must be adictoflist(for a single row, values may be single elements instead of lists). Item names are used as keys inrow_dict. Missing items that exist incategorywill be added as.in new rows. The order of items inrow_dictcan be arbitrary; this function will align them with the existing order incategory.ordinal_itemdescribes a unique numerical ID for each row. If provided, the function will automatically increment it for new rows. In ModelCIF, this column is often calledordinalthough some categories use different names.Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import edit >>> # start with an empty CIF document >>> cif_data = '''data_test ... ''' >>> block = cif.read_string(cif_data).sole_block() >>> # Lets add an entity to create a category in block. ordinal_item >>> # is set to None on purpose to show how it works later. >>> _ = edit.add_rows( ... block, ... "_entity", ... {"id": 1, "details": "Protein", "type": "polymer"}, ... ordinal_item=None, ... ) >>> # see how the _entity category is created as couple of pairs >>> print(block.as_string()) data_test _entity.id 1 _entity.details Protein _entity.type polymer >>> # Add a second row (pairs will turn into a loop). This time, include >>> # ordinal_item to let the function take care of incrementing IDs. >>> _ = edit.add_rows( ... block, ... "_entity", ... {"details": ["H2O"], "type": ["water"]}, ... ordinal_item="id", ... ) >>> # Now _entity is a loop and _entity.id was incremented automatically >>> print(block.as_string()) data_test loop_ _entity.id _entity.details _entity.type 1 Protein polymer 2 H2O water >>> # As a last example, add multiple new rows at once but skip the >>> # 'details' column. >>> _ = edit.add_rows( ... block, ... "_entity", ... {"type": ["polymer", "polymer"]}, ... ordinal_item="id", ... ) >>> # Now there are two more polymer entities in the loop but since >>> # the 'details' information was missing, the function added '.' in >>> # those fields. >>> print(block.as_string()) data_test loop_ _entity.id _entity.details _entity.type 1 Protein polymer 2 H2O water 3 . polymer 4 . polymer
- Parameters:
block (
gemmi.cif.Block) – CIF data block holding the categories of the CIF document.category (str) – Name of the category to which row(s) will be added.
row_dict (dict[str, list | Any]) – Row data to be added to
category. Keys are item names of the category. Values must be lists when adding multiple rows. For a single row, values may be provided as scalars instead of lists. If an item is missing fromrow_dictbut exists in the category, ‘.’ will be assigned for that item in the new row(s).ordinal_item (str | None) – If the category includes an ordinal (in database terms a primary key), this identifies the item name of it. If
ordinal_itemis provided, the latest ordinal will be read from the category and automatically incremented for new rows. UseNonein case the category does not have an ordinal or if the ordinal should be set explicitly. The ordinal does not need to be included inrow_dict.mod_cat_itms (dict[str, set[str]] | None) – A record of what has been modified. Dictionary of category assigned a set of items changed. Items which already have the value of the update, are not recorded. This is meant for the revision history, most likely you can ignore it.
raw (bool, optional) – If True, do not force quoting strings containing whitespace.
- Returns:
A record of what has been modified. To be used with a revision history, most likely you can ignore it.
- Return type:
- Raises:
ValueError – In case item lists in
row_dictare not of equal length.
- modelarchive.modelcif.edit.make_copy_value_in_row(src_item)[source]
Returns a callback that returns a value from the same row.
Supposed to be used in functions that require a callback, e.g.
add_column().Meant to copy values over from the same row. That is handy in case a missing column needs to be populated with values, e.g. if author defined values are missing but required, copy over the “label” fields.
Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import edit >>> # Add _atom_site.auth_comp_id from _atom_site.label_comp_id >>> # Note: category _atom_site is heavily cropped in thsi example to >>> # keep it concise >>> CIF_DATA = '''data_test ... # ... loop_ ... _atom_site.group_PDB ... _atom_site.type_symbol ... _atom_site.label_atom_id ... _atom_site.label_comp_id ... _atom_site.label_asym_id ... _atom_site.auth_seq_id ... ATOM C CA MET A 1 ... ATOM C CA ALA A 2 ... ATOM C CA THR A 3 ... ATOM C CA ALA A 4 ... ATOM C CA ALA A 5 ... ATOM C CA TYR A 6 ... ''' >>> block = cif.read_string(CIF_DATA).sole_block() >>> edit.add_column( ... block, ... "_atom_site", ... "auth_comp_id", ... edit.make_copy_value_in_row("label_comp_id"), ... ) >>> print(block.as_string()) data_test loop_ _atom_site.group_PDB _atom_site.type_symbol _atom_site.label_atom_id _atom_site.label_comp_id _atom_site.label_asym_id _atom_site.auth_seq_id _atom_site.auth_comp_id ATOM C CA MET A 1 MET ATOM C CA ALA A 2 ALA ATOM C CA THR A 3 THR ATOM C CA ALA A 4 ALA ATOM C CA ALA A 5 ALA ATOM C CA TYR A 6 TYR
- Parameters:
src_item (str) – the column name to copy from (source).
- Returns:
Callback function usable as
callbackinadd_column().- Return type:
Callable[[
gemmi.cif.Table.Row], str]
Note
This function may be outsourced to a supporting module, if
editgets to- big.
- modelarchive.modelcif.edit.make_res_per_chain_counter(asym_id_item)[source]
Returns a stateful callback function counting residues per chain.
make_res_per_chain_counter()returns a function that can be used ascallbackinadd_column().The returned callback assigns consecutive residue numbers within each chain of a table, starting at 1. When the chain identifier changes between two rows while iterating over the table, the counter is reset to 1.
Examples
>>> # Add item "ndb_seq_num" to category "_pdbx_nonpoly_scheme" >>> # Reminder: "ndb_seq_num" -> column, "_pdbx_nonpoly_scheme" -> table >>> from gemmi import cif >>> from modelarchive.modelcif import edit >>> cif_data = '''data_test ... loop_ ... _pdbx_nonpoly_scheme.asym_id ... _pdbx_nonpoly_scheme.auth_seq_num ... _pdbx_nonpoly_scheme.entity_id ... _pdbx_nonpoly_scheme.mon_id ... _pdbx_nonpoly_scheme.pdb_seq_num ... C 1 3 ATP 1 ... D 1 4 HEM 1 ... E 1 5 HOH 1 ... E 2 5 HOH 2 ... ''' >>> block = cif.read_string(cif_data).sole_block() >>> # Using make_res_per_chain_counter() in add_column() will add a >>> # column to the loop_ and populate it with values: >>> edit.add_column( ... block, ... "_pdbx_nonpoly_scheme", ... "ndb_seq_num", ... edit.make_res_per_chain_counter("asym_id"), # CALLBACK ... pos=5, ... ) >>> print(block.as_string()) data_test loop_ _pdbx_nonpoly_scheme.asym_id _pdbx_nonpoly_scheme.auth_seq_num _pdbx_nonpoly_scheme.entity_id _pdbx_nonpoly_scheme.mon_id _pdbx_nonpoly_scheme.ndb_seq_num _pdbx_nonpoly_scheme.pdb_seq_num C 1 3 ATP 1 1 D 1 4 HEM 1 1 E 1 5 HOH 1 1 E 2 5 HOH 2 2 >>> # "ndb_seq_num" is inserted as fifth column. The ATP in chain C >>> # ("asym_id") gets "ndb_seq_num" 1 and the HEM in chain D also gets >>> # "ndb_seq_num" 1. But the HOH, both live in chain E together, get >>> # "ndb_seq_num" 1 and 2. So for each chain, counting starts at 1 >>> # and per compound in a chain, the counter is increased by 1.
- Parameters:
asym_id_item (str) – Item name hosting the chain name.
- Returns:
Callback function usable as
callbackinadd_column().- Return type:
Callable[[
gemmi.cif.Table.Row], int]
Note
This function may be outsourced to a supporting module, if
editgets too big.
- modelarchive.modelcif.edit.move_category(block, cat, idx)[source]
Move a category to a new position in a
gemmi.cif.Block.By design, ModelCIF files are not intended to be read or edited manually. Instead, dedicated applications should handle the format, providing functionality to view and modify the data. However, at ModelArchive we occasionally need to open ModelCIF files in an editor to inspect specific details. In such cases, it is helpful to have related categories grouped together, reducing the need to jump back and forth between different categories. This asks for a function to reposition categories within a ModelCIF file.
move_category()takes categorycatand moves it to positionidxin the CIF blockblock. The parameteridxis somewhat special: it can be just an integer index, specifying the exact position to movecatto. That comes in handy placing categories at the beginning (idx=0) or at the end (idx=-1) ofblock. However, specifying an absolute index is often less useful in practice, as categories are typically organised relative to related categories. For this purpose,idxprovides a special syntax:[after|before]:<CATEGORY>. For example, if you want to put category_ma_qa_metricin front of category_ma_qa_metric_local, you can useidx="before:_ma_qa_metric_local"forcat=_ma_qa_metric…Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import edit >>> # get sample CIF data >>> cif_data = '''data_test ... _ma_qa_metric.id 1 ... _ma_qa_metric.description test_score ... loop_ ... _ma_qa_metric_local.ordinal_id ... _ma_qa_metric_local.metric_value ... _ma_qa_metric_local.metric_id ... 1 1.0 1 ... 2 1.5 1 ... ''' >>> block = cif.read_string(cif_data).sole_block() >>> # move _ma_qa_metric_local to BEFORE _ma_qa_metric >>> edit.move_category( ... block, ... "_ma_qa_metric_local", ... "before:_ma_qa_metric", ... ) >>> print(block.as_string()) data_test loop_ _ma_qa_metric_local.ordinal_id _ma_qa_metric_local.metric_value _ma_qa_metric_local.metric_id 1 1.0 1 2 1.5 1 _ma_qa_metric.id 1 _ma_qa_metric.description test_score >>> # move _ma_qa_metric to the front >>> edit.move_category(block, "_ma_qa_metric", 0) >>> print(block.as_string()) data_test _ma_qa_metric.id 1 _ma_qa_metric.description test_score loop_ _ma_qa_metric_local.ordinal_id _ma_qa_metric_local.metric_value _ma_qa_metric_local.metric_id 1 1.0 1 2 1.5 1
- Parameters:
block (
gemmi.cif.Block) – CIF block to operate on.cat (str) – Name of the CIF category to be moved.
idx (int|str) – Position to move
catto. This can be an integer for exact positioning, or a string of form[after|before]:<CATEGORY>for relative positioning. In relative positioning,<CATEGORY>specifies the name of the category before or after whichcatwill be placed. If<CATEGORY>can not be found,catwill not be relocated.
- Returns:
None
- Raises:
NotFoundCategoryError – If
catcan not be found inblock.MoveIdxToFarError – If the target position is outside
block. For example, ifblockcontains 10 categories, trying to move a category to position 15 will raise this error.
- modelarchive.modelcif.edit.sort(table_or_block, item, category=None, key=None)[source]
Sort a
gemmi.cif.Tableorgemmi.cif.Blockin-place by the given item.This may be useful after editing a table, to sort it by a selected column (e.g. the ordinal). Numerical values are sorted numerically, all others lexicographically.
keycan take a function to extract a comparison key from each row. This is helpful for cases like_citation.id, where special values (e.g.id=primary) might need to be placed first.Works on an already loaded
gemmi.cif.Table, or on agemmi.cif.Block(requirescategory) to sort many categories one after another in less code.Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import access, edit >>> # start with an empty CIF document >>> CIF_DATA = '''data_test ... loop_ ... _citation.id ... _citation.journal_full ... _citation.title ... _citation.year ... _citation.journal_volume ... 3 "The Lord of the Rings" "Return of the King" 1955 3 ... 1 "The Lord of the Rings" "The Fellowship of the Ring" 1954 2 ... 2 "The Lord of the Rings" "The Two Towers" 1954 1 ... primary . "The Hobbit or There and Back Again" 1937 . ... ''' >>> block = cif.read_string(CIF_DATA).sole_block() >>> table = access.get_table(block, "_citation") >>> # first sort without a key function >>> edit.sort(table, "id") >>> # This sorts the LOTR books properly, but the 'primary' book is at >>> # the bottom >>> print(block.as_string()) data_test loop_ _citation.id _citation.journal_full _citation.title _citation.year _citation.journal_volume 1 "The Lord of the Rings" "The Fellowship of the Ring" 1954 2 2 "The Lord of the Rings" "The Two Towers" 1954 1 3 "The Lord of the Rings" "Return of the King" 1955 3 primary . "The Hobbit or There and Back Again" 1937 . >>> # sort again (this time by block), with a lambda that puts >>> # 'primary' first >>> edit.sort( ... block, ... "id", ... category="_citation", ... key=lambda row: ( ... (0, "") if row["id"] == "primary" else (1, row["id"]) ... ), ... ) >>> print(block.as_string()) data_test loop_ _citation.id _citation.journal_full _citation.title _citation.year _citation.journal_volume primary . "The Hobbit or There and Back Again" 1937 . 1 "The Lord of the Rings" "The Fellowship of the Ring" 1954 2 2 "The Lord of the Rings" "The Two Towers" 1954 1 3 "The Lord of the Rings" "Return of the King" 1955 3
- Parameters:
table_or_block (
gemmi.cif.Table|gemmi.cif.Block) – Object to be sorted. Ongemmi.cif.Block, the corresponding table will be loaded usingcategory.item (str) – Name of the column (item) in the table to sort by.
category (str, optional) – Name of the category when sorting a
gemmi.cif.Block.key (callable, optional) – Function taking a row and returning a sortable value. Defaults to lexicographic
row[item]with a fix for numerical sorting.
- Returns:
None
- Raises:
ValueError – If
table_or_blockis agemmi.cif.Blockobject but nocategorywas provided.
Fixing AlphaFold 2 ModelCIF files (modelarchive.modelcif.fix_af2)
ModelCIF files generated by AlphaFold 2 deviate from the official ModelCIF definition dictionary in specific cases. Here are functions to fix this.
- class modelarchive.modelcif.fix_af2.GlobalConfRankMultimer(value)[source]
Bases:
Global,NormalizedScoreDefault ranking score used by AlphaFold-Multimer
- name = 'ranking-confidence (ipTM*0.8+pTM*0.2)'
- software = None
- class modelarchive.modelcif.fix_af2.GlobalIpTM(value)[source]
-
Predicted protein-protein interface score based on TM-score in [0,1]
- name = 'ipTM'
- software = None
- class modelarchive.modelcif.fix_af2.GlobalPLDDT(value)[source]
-
Predicted accuracy according to the CA-only lDDT in [0,100]
- name = 'pLDDT'
- software = None
- class modelarchive.modelcif.fix_af2.GlobalPTM(value)[source]
-
Predicted accuracy according to the TM-score score in [0,1]
- name = 'pTM'
- software = None
- class modelarchive.modelcif.fix_af2.LocalPLDDT(residue, value)[source]
-
Predicted accuracy according to the CA-only lDDT in [0,100]
- name = 'pLDDT'
- software = None
- class modelarchive.modelcif.fix_af2.LocalPairwisePAE(residue1, residue2, value)[source]
Bases:
LocalPairwise,PAEPredicted aligned error (in Angstroms)
- name = 'PAE'
- software = None
- modelarchive.modelcif.fix_af2.assemble_modelcif_software(soft_dict, params_dict)[source]
Create a
modelcif.SoftwareWithParametersinstance from dictionaries.- Parameters:
soft_dict (dict) – Software metadata as returned by functions such as
get_colabfold_software(). Must contain the keysname,classification,description,location,type,version, andcitation.params_dict (dict) – Software parameters, where each key is passed as the parameter name and each value as the parameter value to
modelcif.SoftwareParameter.
- Returns:
A ModelCIF software object with associated parameters.
- Return type:
- modelarchive.modelcif.fix_af2.get_af2_config(af_version, af_params=None, custom_ranking=None, up_version=None, up_rel_date=None, pdb_rel_date=None)[source]
Get configuration data for an AlphaFold 2 modelling run.
Derives modelling settings from the provided AlphaFold 2 version and parameters, builds a human-readable description of the run, and returns a configuration dictionary for use by downstream functions.
- Parameters:
af_version (str) – AlphaFold 2 version string (e.g.
"2.3.2").af_params (dict, optional) – Non-default AlphaFold 2 parameters. Recognised keys include
model_preset,db_preset,num_multimer_predictions_per_model,models_to_relax,run_relax,max_template_date, andnum_ensemble. Defaults to an empty dict if not provided.custom_ranking (str, optional) – Custom model ranking expression. If not provided, defaults to
"pLDDT"for monomer runs and"ipTM*0.8+pTM*0.2"for multimer runs.up_version (str, optional) – UniProt release in
"YYYY_MM"format current at the time of AF2 installation. (see https://www.uniprot.org/release-notes)up_rel_date (datetime.date, optional) – Release date corresponding to
up_version.pdb_rel_date (datetime.date, optional) – PDB release date current at the time of AF2 installation. Relevant for multimer runs using templates.
- Returns:
Configuration data for downstream functions. Keys:
af_params(dict): Parameters as passed (or empty dict).af_version(str): AlphaFold 2 version string as passed.description(str): Human-readable run description.use_templates(bool): Whether templates were used.use_small_bfd(bool): Whether the reduced BFD database setting was used.use_multimer(bool): Whether multimer mode was used.up_version(str or None): UniProt release as passed.up_rel_date(datetime.date or None): UniProt release date as passed.pdb_rel_date(datetime.date or None): PDB release date as passed.seq_dbs(list[modelcif.ReferenceDatabase]): Sequence DB objects.
- Return type:
- modelarchive.modelcif.fix_af2.get_af2_sequence_dbs(config_data)[source]
Get AF2 sequence databases and store them in
config_data.Builds a list of
modelcif.ReferenceDatabaseobjects based on the AlphaFold 2 configuration and writes it toconfig_data["seq_dbs"]. The selection depends on the database preset, AF2 version, and whether multimer mode or templates are used.- Parameters:
config_data (dict) –
AF2 configuration data, as returned by
get_af2_config(). Relevant keys:af_version(str): AlphaFold 2 version string; determines which MGnify and UniRef variants are added.use_small_bfd(bool): IfTrue, uses Reduced BFD instead of full BFD.use_multimer(bool): IfTrue, adds TrEMBL, Swiss-Prot, and PDB seqres databases.use_templates(bool): IfTrue, adds a PDB sequence database (PDB seqres for multimer, PDB70 for monomer).up_version(str or None): UniProt release version, passed to theversionattribute of UniRef90, TrEMBL, and Swiss-Prot database objects.up_rel_date(datetime.date or None): UniProt release date, passed to therelease_dateattribute of UniRef90, TrEMBL, and Swiss-Prot database objects.pdb_rel_date(datetime.date or None): PDB release date, passed to therelease_dateattribute of the PDB seqres database object.
- Returns:
Results are written to
config_data["seq_dbs"]as a list ofmodelcif.ReferenceDatabaseobjects.- Return type:
None
- modelarchive.modelcif.fix_af2.get_af2_software(version=None, is_multimer=False)[source]
Get AlphaFold 2 as a
dictfor creating a software object.- Parameters:
- Returns:
A dictionary with software metadata suitable for creating a ModelCIF software object. The
nameandcitationentries differ depending onis_multimer.- Return type:
- modelarchive.modelcif.fix_af2.get_cf_config(cf_config, ur30_db_version=None, tpl_db=None, tpl_db_version=None)[source]
Process a ColabFold configuration into a standardised data dictionary.
- Parameters:
cf_config (dict) – Raw ColabFold configuration data, typically read from a ColabFold configuration file. Must contain the keys
version,msa_mode,model_type,num_recycles,use_templates, andrank_by. Optional keys includecommit,pair_mode,recycle_early_stop_tolerance,stop_at_score,num_seeds,num_models,use_amber, andnum_relax.ur30_db_version (str, optional) – Version of the UniRef30 database used. Should only be
Noneif the database was not used.tpl_db (str, optional) – Template database used. Accepted values are
"PDB70","PDB100", orNoneif no template database was used.tpl_db_version (str, optional) – Version of the template database used. Should only be
Noneif the database was not used.
- Returns:
A dictionary with processed ColabFold configuration data for further use in model preparation.
- Return type:
- Raises:
ValueError – If
msa_modeis not one of the known values.ValueError – If
model_typeis not one of the known values.ValueError – If
rank_byis not one of the known values.
- modelarchive.modelcif.fix_af2.get_cf_db_versions(dt, num_days_unk=1)[source]
Get ColabFold database versions for a given date.
Returns the UniRef30, template database name, and template database version used by the ColabFold MSA server on a given date. Based on https://github.com/sokrypton/ColabFold/wiki/MSA-Server-Database-History.
- Parameters:
dt (datetime.date) – Date for which to look up the database versions.
num_days_unk (int) – Number of days around a database switch date within which the result is considered unknown. Defaults to 1.
- Returns:
- A 3-tuple of
(ur30_db_version, tpl_db, tpl_db_version), each a
str. Values are set to"UNK"ifdtfalls withinnum_days_unkdays of a switch date, if the template database version is unknown, or if no matching date range is found.
- A 3-tuple of
- Return type:
- modelarchive.modelcif.fix_af2.get_cf_sequence_dbs(config_data)[source]
Get ColabFold sequence databases and store them in
config_data.Looks up a hardcoded list of known ColabFold sequence databases and populates
config_data["seq_dbs"]withmodelcif.ReferenceDatabaseinstances corresponding to the databases requested viaconfig_data["seq_db_keys"]. If a template database is specified viaconfig_data["tpl_db"], it is appended as well. UniRef database entries require a version string inconfig_data["ur30_db_version"]; template database entries require a version string inconfig_data["tpl_db_version"].- Parameters:
config_data (dict) – Configuration data dictionary. Relevant keys:
seq_db_keys(list ofstr) — sequence database identifiers to look up;ur30_db_version(strorNone) — version string required when"UniRef"is inseq_db_keys;tpl_db(strorNone) — optional template database identifier;tpl_db_version(strorNone) — version string required whentpl_dbis set. On return,seq_dbsis added as a list ofmodelcif.ReferenceDatabaseinstances.- Returns:
None
- Raises:
ValueError – If
"UniRef"is inseq_db_keysbutur30_db_versionisNone.ValueError – If
tpl_dbis set buttpl_db_versionisNone.ValueError – If a resolved database key is not found in the hardcoded set of known ColabFold databases.
- modelarchive.modelcif.fix_af2.get_cf_sw_plus_params(config_data, use_localcolabfold=False)[source]
Create a list of software and parameters for a ColabFold protocol step.
- Parameters:
config_data (dict) – ColabFold configuration data as returned by
get_cf_config().use_localcolabfold (bool) – If
True, prepend LocalColabFold to the list of software entries.
- Returns:
A list of
(software, parameters)tuples suitable for use in a protocol.- Return type:
- modelarchive.modelcif.fix_af2.get_colabfold_software(version=None)[source]
Get ColabFold as a
dictfor creating a software object.
- modelarchive.modelcif.fix_af2.get_galaxy_software(version)[source]
Get Galaxy as a software dictionary for a ModelCIF file.
Builds a dictionary suitable for creating a
modelcif.Softwareobject, with citation and download URL derived from the provided version string.
- modelarchive.modelcif.fix_af2.get_localcolabfold_software(version=None)[source]
Get LocalColabFold as a
dictfor creating a software object.
- modelarchive.modelcif.fix_af2.get_mmseqs2_software(version=None)[source]
Get MMseqs2 as a
dictfor creating a software object.
- modelarchive.modelcif.fix_af2.get_sequence(chn, use_auth=False)[source]
Get the sequence of an OpenStructure chain, inserting
'-'for gaps.- Parameters:
chn (ost.mol.ChainHandle or ost.mol.ChainView) –
OST chain to extract the sequence from. Any object providing the following interface can be used as a drop-in replacement for the OST chain object:
chn.residues: sequence of residue objects, each providingchn.residues[i].number.num(int): internal residue numberchn.residues[i].one_letter_code(str): single-letter codechn.residues[i].GetStringProp("pdb_auth_resnum")(str): author residue number as an integer string, only required ifuse_auth=True
use_auth (bool) – If
True, use PDB author residue numbers instead of internal residue numbers.
- Returns:
One-letter code sequence with
'-'characters inserted for gaps.- Return type:
- modelarchive.modelcif.fix_af2.store_as_modelcif(mdl_data, out_dir, mdl_fle_stem, compress)[source]
Assemble model data into a ModelCIF file and write it to disk.
Creates a
modelcif.Systemfrom the provided data, attaches entities, models, QA scores, associated files, and a modelling protocol, then writes the result as a ModelCIF file. Optionally compresses the output and packages associated files into a ZIP archive.- Parameters:
mdl_data (dict) –
Dictionary with model data. Expected keys:
title(str): Title of the modelling system.mdl_id(str): Model identifier; converted to upper case.model_details(str): Free-text description of the model.audit_authors(list[str]): Author names for the audit record.ranked_mdls(list[dict]): Per-model atom data.target_entities(list[dict]): Target sequence data used to build asymmetric units and entities.config_data(dict): Configuration data; must contain the keyseq_dbswith reference database entries for the protocol.protocol(dict): Modelling protocol description passed to_get_modelcif_protocol().acc_files(dict, optional): Mapping of labels to associated file descriptors, each containingdetails,destination_file_name,source_file_path,file_format, andfile_content.af2_protocol_name(str, optional): If present, used to assign software metadata to AlphaFold 2 QA metric classes.
out_dir (
str|Path) – Directory to write the output file(s) to.mdl_fle_stem (str) – Base name for the output file, without extension.
compress (bool) – If
True, the mmCIF file is gzip-compressed after writing.
- Returns:
File name of the written mmCIF file, relative to
out_dir. Ends with.cifor.cif.gzdepending oncompress.- Return type:
Fixing AlphaFold 3 ModelCIF files (modelarchive.modelcif.fix_af3)
ModelCIF files generated by AlphaFold 3 deviate from the official ModelCIF definition dictionary in specific cases. In particular, for homomeric assemblies, each molecular entity copy is written as a separate entity in the CIF document, instead of defining a single entity referenced multiple times. This module provides functionality to correct the deviations.
For ModelCIF conversions done for ModelArchive, we first fix entity desctiptions
and then call the following functions in order:
fix_modelcif_issues(),
fix_citation(),
fix_software_location(),
fix_model_name(),
fix_protocol(),
add_per_residue_plddt(), and
add_data_from_json_files() (if AF3 JSON files available and with
pairwise_in_zip=True and use_local_pairwise_if_possible=True).
- exception modelarchive.modelcif.fix_af3.NotIdentifiedContextRecordError(category, item=None, context=None)[source]
Bases:
NotIdentifiedRecordErrorException if a record for a specific context can not be identified.
- exception modelarchive.modelcif.fix_af3.NotIdentifiedDuplicatedRecordError(category, record_id)[source]
Bases:
NotIdentifiedRecordErrorException if a duplicated record is found in a table.
- exception modelarchive.modelcif.fix_af3.NotIdentifiedRecordError(msg)[source]
Bases:
RuntimeErrorGeneral exception for records that can not be identified in a table.
This exception should not be raised directly, it exists to define other “NotIdentified” exceptions inheriting from it.
- Parameters:
msg (str) – Exception message.
- exception modelarchive.modelcif.fix_af3.NotIdentifiedSingleRecordError(category, item=None, value=None)[source]
Bases:
NotIdentifiedRecordErrorException if a specific record can not be identified in a table.
- modelarchive.modelcif.fix_af3.add_data_from_json_files(block, input_path, full_qe_path, summary_qe_path, out_zip_path, pairwise_in_zip=True, use_local_pairwise_if_possible=False)[source]
Add QA metrics and metadata from AF3 JSON files to a ModelCIF block.
Reads the AF3 input JSON, full confidence JSON, and summary confidence JSON to populate QA metric categories in
block. Packs the input JSON and, optionally, pairwise QA scores into a ZIP archive atout_zip_path. Updates_ma_qa_metric,_ma_qa_metric_global,_ma_qa_metric_feature,_ma_qa_metric_local_pairwise,_ma_qa_metric_feature_pairwise,_ma_entry_associated_files,_ma_associated_archive_file_details,_audit_conform|, and_ma_software_parameter(the latter only if model seeds or recycle counts are present in the input JSON).The function derives feature lists from
_atom_site. Per-residue pairwise scores are written to_ma_qa_metric_local_pairwisewhenuse_local_pairwise_if_possibleisTrueand all tokens are polymer residues (noHETATM); in all other cases_ma_qa_metric_feature_pairwiseis used.- Parameters:
block (
gemmi.cif.Block) – mmCIF block to be updated in place. Must already contain_atom_site,_entity,_ma_qa_metric,_ma_qa_metric_global,_ma_software_group, and_entrycategories.input_path (Path | str) – Path to the AF3 input JSON file. Server output:
<JOBNAME>_job_request.json.full_qe_path (Path | str) – Path to the JSON file containing per-atom and per-token confidence arrays (pLDDT, PAE, contact probabilities). Server output:
<JOBNAME>_full_data_<N>.json; code output:<JOBNAME>_confidences.json.summary_qe_path (Path | str) – Path to the JSON file containing summary confidence values (pTM, ipTM, ranking score, etc.). Server output:
<JOBNAME>_summary_confidences_<N>.json; code output:<JOBNAME>_summary_confidences.json.out_zip_path (Path | str) – Path for the output ZIP archive. The archive always contains
input.json(a copy ofinput_path). Ifpairwise_in_zipisTrue, apairwise_qa.ciffile with the pairwise QA metrics is also included. The value of_ma_entry_associated_files.file_urlis set to the bare filename (i.e. without any directory component), so the main CIF file and the ZIP must reside in the same directory.pairwise_in_zip (bool) – If
True, pairwise QA scores are written to a separatepairwise_qa.ciffile and packaged inside the ZIP archive rather than embedded directly inblock. Defaults toTrue.use_local_pairwise_if_possible (bool) – If
Trueand every token in the structure is a polymer residue (noHETATMrecords),_ma_qa_metric_local_pairwiseis used for pairwise token scores instead of_ma_qa_metric_feature_pairwise. Defaults toFalse.
- Returns:
None
- Raises:
RuntimeError – If
full_qe_pathorsummary_qe_pathcontain score keys that are not listed inknown_scores.
- modelarchive.modelcif.fix_af3.add_json_files_in_archive_file(block, input_path, full_qe_path, summary_qe_path, out_zip_path)[source]
Package AF3 JSON files as accompanying data without processing them.
Writes ModelCIF categories
_ma_entry_associated_filesand_ma_associated_archive_file_detailstoblockand packages the three AF3 JSON files into a ZIP archive atout_zip_path. Alternative toadd_data_from_json_files()with the same..._pathparameters; use this function when the JSON files should be stored as-is rather than parsed for QA metrics.Warning
Existing
_ma_entry_associated_filesand_ma_associated_archive_file_detailscategories inblockare overwritten without checking their prior contents.- Parameters:
block (
gemmi.cif.Block) – mmCIF block to be updated in place. Must already contain an_entrycategory with exactly one row.input_path (Path | str) – Path to the AF3 input JSON file. Server output:
<JOBNAME>_job_request.json.full_qe_path (Path | str) – Path to the JSON file containing per-atom and per-token confidence arrays (pLDDT, PAE, contact probabilities). Server output:
<JOBNAME>_full_data_<N>.json; code output:<JOBNAME>_confidences.json.summary_qe_path (Path | str) – Path to the JSON file containing summary confidence values (pTM, ipTM, ranking score, etc.). Server output:
<JOBNAME>_summary_confidences_<N>.json; code output:<JOBNAME>_summary_confidences.json.out_zip_path (Path | str) – Path for the output ZIP archive. The archive contains
input.json,summary_confidences.json, andconfidences.json. The value of_ma_entry_associated_files.file_urlis set to the bare filename (i.e. without any directory component), so the main CIF file and the ZIP must reside in the same directory.
- Returns:
None
- modelarchive.modelcif.fix_af3.add_per_residue_plddt(block)[source]
Add average per-residue pLDDT scores to an AF3 ModelCIF file.
Adds
_ma_qa_metric_localdata derived from B-factor values in_atom_site. The per-residue pLDDT is computed as the mean over all atoms of a residue. Non-polymer residues (missing value in_atom_site.label_seq_id) are excluded.If
_ma_qa_metric_localis already present in the block, the function exits early with a warning. If no local pLDDT entry exists in_ma_qa_metric, it will be added; if more than one local pLDDT entry is found, an exception is raised as this is most likely an error in the ModelCIF file.This fix targets AF3 files predating version 3.0.1, which lack
_ma_qa_metric_local.Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import access, fix_af3 >>> # Please note: the example CIF document for this case has the >>> # _atom_site category reduce to the bare minimum to make the >>> # mechanics of add_per_residue_plddt() work, to keep the example >>> # shorter. >>> CIF_DATA = '''data_test ... # ... loop_ ... _ma_software_group.group_id ... _ma_software_group.ordinal_id ... _ma_software_group.software_id ... 1 1 1 ... # ... loop_ ... _software.classification ... _software.date ... _software.description ... _software.name ... _software.pdbx_ordinal ... _software.type ... _software.version ... other ? "Structure prediction" AlphaFold 1 package AlphaFold-beta ... # ... loop_ ... _atom_site.group_PDB ... _atom_site.label_comp_id ... _atom_site.label_asym_id ... _atom_site.label_seq_id ... _atom_site.B_iso_or_equiv ... _atom_site.pdbx_PDB_model_num ... ATOM MET A 1 35.00 1 ... ATOM ALA A 2 50.30 1 ... ATOM THR A 3 65.75 1 ... ''' >>> block = cif.read_string(CIF_DATA).sole_block() >>> fix_af3.add_per_residue_plddt(block) >>> # After execution, the CIF document has categories _ma_qa_metric >>> # and _ma_qa_metric_local added >>> # There should be only 1 record in _ma_qa_metric >>> qa_dict = block.get_mmcif_category("_ma_qa_metric.") >>> print(qa_dict) {'id': ['1'], 'mode': ['local'], 'name': ['pLDDT'], 'software_group_id': ['1'], 'type': ['pLDDT']} >>> # There should be 3 records of local scores >>> table = access.get_table(block, "_ma_qa_metric_local.") >>> print("# chain res seqID pLDDT") # chain res seqID pLDDT >>> for r in table: ... print( ... f"{r['ordinal_id']} {r['label_asym_id']} " ... + f"{r['label_comp_id']} {r['label_seq_id']} " ... + f"{r['metric_value']}" ... ) 1 A MET 1 35.0 2 A ALA 2 50.3 3 A THR 3 65.75
- Parameters:
block (
gemmi.cif.Block) – CIF block to operate on.- Returns:
None
- Raises:
RuntimeError – If
_ma_qa_metriccontains more than one local pLDDT entry.
- modelarchive.modelcif.fix_af3.fix_citation(block)[source]
Normalise the AlphaFold 3 citation in a ModelCIF
block.Ensures that the AlphaFold 3 publication (PMID 38718835) is not marked as the “primary” citation, assigns a numeric citation ID instead. Fixes an incomplete AlphaFold 3 citation. Replaces the author list with the full curated list of names and updates its citation ID. Reorders citations so that the primary entry appears first and links the citation to the corresponding software record.
This adjustment is not required for valid ModelCIF files, but follows ModelArchive conventions where the primary citation must refer to the deposited model rather than the software used to generate it.
Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import access, fix_af3 >>> # start with an empty CIF document >>> CIF_DATA = '''data_test ... _citation.id primary ... _citation.country UK ... _citation.journal_full Nature ... _citation.journal_id_ASTM NATUAS ... _citation.journal_id_CSD 0006 ... _citation.journal_id_ISSN 0028-0836 ... _citation.journal_volume 630 ... _citation.page_first 493 ... _citation.page_last 500 ... _citation.pdbx_database_id_DOI 10.1038/s41586-024-07487-w ... _citation.pdbx_database_id_PubMed 38718835 ... _citation.title 'Accurate structure prediction of biomolecular ...' ... _citation.year 2024 ... # ... loop_ ... _citation_author.citation_id ... _citation_author.name ... _citation_author.ordinal ... primary "Google DeepMind AlphaFold Team" 1 ... primary "Isomorphic Labs Team" 2 ... # ... loop_ ... _software.classification ... _software.date ... _software.description ... _software.name ... _software.pdbx_ordinal ... _software.type ... _software.version ... other ? "Structure prediction" AlphaFold 1 package AlphaFold-beta ... ''' >>> block = cif.read_string(CIF_DATA).sole_block() >>> fix_af3.fix_citation(block) >>> # The usual block.as_string() output would be too much for a >>> # docstring, just check some important values. >>> table = access.get_table(block, "_citation") >>> assert table[0]["id"] == "1" >>> table = access.get_table(block, "_citation_author") >>> assert table[0]["name"] != "Google DeepMind AlphaFold Team" >>> table = access.get_table(block, "_software") >>> assert table[0]["citation_id"] == "1"
- Parameters:
block (
gemmi.cif.Block) – CIF block to operate on.- Returns:
None
- Raises:
edit.NotFoundCategoryError – If
_softwarecategory can not be found.NotIdentifiedSingleRecordError – If required item is missing from
_citationcategory. If item values are not as expected for_citationcategory.NotIdentifiedDuplicatedRecordError – If multiple entries for AlphaFold are found in
_softwarecategory. In that case, the “right” record can not be identified.
- modelarchive.modelcif.fix_af3.fix_model_name(block, mdl_rank)[source]
Normalise
_ma_model_list.model_namefor given rank.AlphaFold 3 sets
_ma_model_list.model_nameto “Top ranked model” for all models, regardless of their rank. This function rewrites the value such that onlymdl_rank == 1is labelled “Top ranked model”. All other ranks are renamed to “#<mdl_rank> ranked model”.Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import fix_af3 >>> # get sample CIF data >>> cif_data = '''data_test ... _ma_model_list.data_id 1 ... _ma_model_list.model_name "Top ranked model" ... _ma_model_list.model_type "Ab initio model" ... _ma_model_list.ordinal_id 1 ... ''' >>> block = cif.read_string(cif_data).sole_block() >>> fix_af3.fix_model_name(block, 2) >>> print(block.as_string()) data_test _ma_model_list.data_id 1 _ma_model_list.model_name "#2 ranked model" _ma_model_list.model_type "Ab initio model" _ma_model_list.ordinal_id 1 >>> fix_af3.fix_model_name(block, 1) >>> print(block.as_string()) data_test _ma_model_list.data_id 1 _ma_model_list.model_name "Top ranked model" _ma_model_list.model_type "Ab initio model" _ma_model_list.ordinal_id 1
- Parameters:
block (
gemmi.cif.Block) – CIF block to operate on.mdl_rank (int) – Rank of the AlphaFold 3 model. If
mdl_rank == 1, the name is set to “Top ranked model”.
- Returns:
None
- Raises:
RuntimeError – If the
_ma_model_listcategory contains more than one row.edit.NotFoundCategoryError – no software entry found for AF3.
edit.NotFoundItemError – If
_ma_model_list.model_namecan not be found inblock.
- modelarchive.modelcif.fix_af3.fix_modelcif_issues(block, compdict_cache='.compdict_cache')[source]
Fix multiple small issues in AF3 ModelCIF files.
Things corrected:
_atom_site.auth_comp_idgets added if not present_pdbx_poly_seq_scheme.pdb_mon_idgets added if not present_pdbx_branch_scheme.pdb_mon_idgets added if not present_pdbx_entity_branch_listgets added when_pdbx_branch_schemeexists_pdbx_nonpoly_scheme.ndb_seq_numgets added if not presentsingle sugars mistakenly marked as ‘branched’ entity will be relabelled to ‘non-polymer’ in the
_entitycategoryduplicated molecular entities are reduced to a single molecular entity (AF3 adds a molecular entity per copy of a molecule)
atom names are changed to comply with IUPAC
ligand naming scheme
LIG_<CHARACTER>is replaced with proper molecule names (if possible, via RCSB)if
_ma_data_ref_dbset, replace wrong “id” item with “data_id”, remove duplicate entries and set necessary data in_ma_data
- Parameters:
block (
gemmi.cif.Block) – CIF block to operate on.compdict_cache (str | Path) – Path to the cache file for RCSB API calls for chemical compounds. Defaults to
.compdict_cache.
- Returns:
None
- Raises:
NotIdentifiedSingleRecordError – If a record to be deleted cannot be found.
RuntimeError – If entities to be merged have differing data, if an empty entity is found, or if a SMILES string cannot be identified.
RuntimeError – If entities to be merged have differing data, if an empty entity is found, if no
_entity_polyrecord is found for an entity ID, if polymer types of duplicated entities mismatch, or if a SMILES string cannot be identified via the RCSB API.
- modelarchive.modelcif.fix_af3.fix_protocol(block)[source]
Fix the MA protocol to a single well-formed step.
Rewrites
_ma_data,_ma_data_group, and_ma_protocol_stepfrom scratch based on the existing_ma_target_entity,_ma_model_listand_ma_software_groupcategories.Warning
Existing
_ma_data,_ma_data_group, and_ma_protocol_stepcategories inblockare overwritten without checking their prior contents.It is important that
fix_modelcif_issues()andfix_model_name()are called before this since this function uses existing data from_ma_model_listand_ma_data_ref_dbset there.
Data layout after the call:
_ma_data:One record per target entity (content_type “target”) followed by one record per model (content_type “model coordinates”). If
_ma_data_ref_dbis available, records for them are added as well. Names for the data items are taken from_entity.pdbx_description,_ma_model_list.model_nameand_ma_data_ref_db.name, respectively. IDs are assigned sequentially starting at 1._ma_data_group:Group 1 - all target data IDs (input side).
Group 2 - all model data IDs (output side).
_ma_protocol_step:A single step referencing the AF3 software group, group 1 as input, and group 2 as output.
Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import access, fix_af3 >>> # start with an empty CIF document >>> CIF_DATA = '''data_test ... # ... loop_ ... _entity.id ... _entity.pdbx_description ... _entity.type ... 1 "bestest polymer in universe" polymer ... 2 "second best polythingi in universe" polymer ... # ... loop_ ... _ma_target_entity.data_id ... _ma_target_entity.entity_id ... _ma_target_entity.origin ... 1 1 . ... 1 2 . ... # ... _ma_model_list.data_id 1 ... _ma_model_list.model_group_id 1 ... _ma_model_list.model_group_name "AlphaFold-beta-20231127 (...)" ... _ma_model_list.model_id 1 ... _ma_model_list.model_name "Top ranked model" ... _ma_model_list.model_type "Ab initio model" ... _ma_model_list.ordinal_id 1 ... # ... loop_ ... _ma_software_group.group_id ... _ma_software_group.ordinal_id ... _ma_software_group.software_id ... 1 1 1 ... # ... loop_ ... _software.classification ... _software.date ... _software.description ... _software.name ... _software.pdbx_ordinal ... _software.type ... _software.version ... other ? "Structure prediction" AlphaFold 1 package AlphaFold-beta ... ''' >>> block = cif.read_string(CIF_DATA).sole_block() >>> fix_af3.fix_protocol(block) >>> access.get_table(block, "_entity").erase() >>> access.get_table(block, "_ma_data").erase() >>> access.get_table(block, "_ma_data_group").erase() >>> access.get_table(block, "_ma_model_list").erase() >>> access.get_table(block, "_ma_software_group").erase() >>> access.get_table(block, "_ma_target_entity").erase() >>> access.get_table(block, "_software").erase() >>> print(block.as_string()) data_test loop_ _ma_protocol_step.ordinal_id _ma_protocol_step.protocol_id _ma_protocol_step.step_id _ma_protocol_step.method_type _ma_protocol_step.details _ma_protocol_step.software_group_id _ma_protocol_step.input_data_group_id _ma_protocol_step.output_data_group_id 1 1 1 modeling 'Model generated with AlphaFold 3.' 1 1 2
- Parameters:
block (
gemmi.cif.Block) – CIF block to operate on.- Returns:
None
- Raises:
edit.NotFoundCategoryError – If any required source category is absent:
_entity,_ma_target_entity,_ma_model_list, or_ma_software_group.edit.NotFoundItemError – If
_ma_target_entity.data_id,_ma_model_list.data_idor_ma_model_list.model_nameare missing.NotIdentifiedDuplicatedRecordError – If multiple
_ma_software_grouprecords exist and the AF3 entry cannot be unambiguously identified in_software.NotIdentifiedContextRecordError – If multiple
_ma_software_grouprecords exist but no AF3 entry can be found in_softwareat all.
- modelarchive.modelcif.fix_af3.fix_software_location(block)[source]
Ensures the AlphaFold 3
_softwareentry has a correct location URL.Determines whether the ModelCIF
blockoriginates from the AlphaFold 3 server or a local installation and sets the corresponding URL in_software.location. If the column does not yet exist it is created; otherwise only the row for AlphaFold 3 is updated.Examples
>>> from gemmi import cif >>> from modelarchive.modelcif import access, fix_af3 >>> # start with an empty CIF document >>> CIF_DATA = '''data_test ... _pdbx_data_usage.details "... alphafoldserver.com/output-terms." ... _pdbx_data_usage.id 1 ... _pdbx_data_usage.type license ... _pdbx_data_usage.url ? ... # ... loop_ ... _software.classification ... _software.date ... _software.description ... _software.name ... _software.pdbx_ordinal ... _software.type ... _software.version ... other ? "Structure prediction" AlphaFold 1 package AlphaFold-beta ... ''' >>> block = cif.read_string(CIF_DATA).sole_block() >>> fix_af3.fix_software_location(block) >>> # Just check that _software.location exists and has the right value >>> table = access.get_table(block, "_software") >>> assert "_software.location" in table.tags >>> assert table[0]["location"] == "https://alphafoldserver.com/" >>> # Change block to look like ModelCIF file from local installation >>> table = access.get_table(block, "_pdbx_data_usage") >>> table[0]["details"] = "...github.com/google-deepmind/alphafold3..." >>> fix_af3.fix_software_location(block) >>> # Check _software.location to point to GitHub, now >>> table = access.get_table(block, "_software") >>> assert table[0]["location"] == "https://github.com/google-deepmind/alphafold3"
- Parameters:
block (
gemmi.cif.Block) – CIF block to operate on.- Returns:
None
- Raises:
NotIdentifiedContextRecordError – If no AlphaFold 3 entry is found in the
_softwaretable.NotIdentifiedContextRecordError – If the origin of the AlphaFold 3 license could not be identified in the
_pdbx_data_usagetable.NotIdentifiedDuplicatedRecordError – If multiple entries for AlphaFold 3 are found in the
_softwaretable.
ModelCif as dict (modelarchive.modelcif.ma_dict)
Functionality for ModelCIF data/ categories when represented as
dict.
- modelarchive.modelcif.ma_dict.add_row_to_category_dict(cat_dict, row_dict, ordinal_item=None, null_value=False)[source]
Add a row to a category dict or create it if empty.
Appends a new row to
cat_dict, which holds mmCIF category data as a column-oriented dict of lists, as obtained fromgemmi.cif.Block.find_mmcif_category(raw=False)or an empty dict. Ifcat_dictis empty, it is populated fromrow_dictdirectly.- Parameters:
cat_dict (dict[str]) – Column-oriented category dict to update. Each key maps to a list of values, one entry per row.
row_dict (dict[str]) – Dict with keys and single values representing the new row. Keys not present in
cat_dictare added as new columns, back-filled withnull_value. Keys only incat_dictare filled withnull_value.ordinal_item (
strorNone) – Key whose value is auto-incremented on each call. Set to"1"whencat_dictis empty. If set but not present in a non-emptycat_dict, it is silently ignored. Defaults toNone.null_value – Value used to fill missing keys.
Falsefor inapplicable ('.'),Nonefor unknown ('?'). Defaults toFalse.
- Returns:
- The new ordinal value as a string, or
Noneifordinal_itemis not set.
- Return type:
strorNone- Raises:
ValueError – If column lengths in
cat_dictare inconsistent after appending.