@@ -161,6 +161,50 @@ Returning a complete set if often not required, if you only want to have the fir
There are cases when you really need exactly one result. The :cpp:func:`cif::category::find1` can be used in that case, it will throw an exception if the query does not result in exactly one row.
NULL and ANY
------------
Sometimes items may be empty. The trouble is a bit that empty comes in two flavors: unknown and null. Null in *CIF* parlance means the item should not contain a value since it makes no sense in this case, the value stored in the file is a single dot character: ``'.'``. E.g. *atom_site* records may have a NULL value for label_seq_id for atoms that are part of a *non-polymer*.
The other empty value is indicated by a question mark character: ``'?'``. This means the value is simply unknown.
Both these are NULL in *libcifpp* conditions and can be searched for using :cpp:var:`cif::null`.
So you can search for:
.. code-block:: cpp
cif::condition c = "label_seq_id"_key == cif::null;
You might also want to look for a certain value and don't care in which item it is stored, in that case you can use :cpp:var:`cif::any`.
.. code-block:: cpp
cif::condition c = cif::any == "foo";
And in linked record you might have the items that have a value in both parent and child or both should be NULL. For that, you can request the value to return by find to be of type std::optional and then use that value to build the query. An example to explain this, let's find the location of the atom that is referenced as the first atom in a struct_conn record:
.. code-block:: cpp
// Take references to the two categories we need
auto struct_conn = db["struct_conn"];
auto atom_site = db["atom_site"];
// Loop over all rows in struct_conn taking only the values we need
// Note that the label_seq_id is returned as a std::optional<int>
// That means it may contain an integer or may be empty
for (const auto &[asym1, seqid1, authseqid1, atomid1] :
Although Quaternions are the preferred way of doing rotations, not every manipulation is a rotation and thus we need a matrix class as well. Matrices and their operations are encoded as matrix_expressions in *libcifpp* allowing the compiler to generate very fast code. See the :ref:`file_cif++_matrix.hpp` for what is on offer.
Crystals
--------
The *CIF* and *mmCIF* were initially developed to store crystallographic information on structures. Apart from coordinates and the chemical information the crystallographic information is important. This information can be split into two parts, a unit cell and a set of :ref:`symmetry-ops` making up a spacegroup. The spacegroup number and name are stored in the *symmetry* category. The corresponding symmetry operations can be obtained in *libcifpp* by using the :cpp:class:`cif::spacegroup`. The cell is stored in the category *cell* and likewise can be loaded using the :cpp:class:`cif::cell`. Together these two classes make up a crystal and so we have a :cpp:class:`cif::crystal` which contains both. You can easily create such a crystal object by passing the datablock containing the data to the constructor. As in:
.. code:: cpp
// Load the file
cif::file f("1cbs.cif.gz");
auto &db = f.front();
cif::crystal c(db);
.. _symmetry-ops:
Symmetry operations
-------------------
...
...
@@ -31,21 +45,28 @@ To give an idea how this works, here's a piece of code copied from one of the un
.. code:: cpp
using namespace cif::literals;
// Load the file
cif::file f(gTestDir / "2bi3.cif.gz");
// Simply assume we can use the first datablock
auto &db = f.front();
cif::mm::structure s(db);
// Load the crystal information
cif::crystal c(db);
// Take references to the two categories we need
auto struct_conn = db["struct_conn"];
auto atom_site = db["atom_site"];
// Loop over all rows in struct_conn taking only the values we need