Repository: Materials-Consortia/optimade-python-tools
Pull request #241 (2020-03-28 17:31:49)

Add adapters - Base design + 'structures' (+ 'references'... sort of)
The goal of the adapters is to contain a conversion or adaptation ability in a single Python class.

The class will be a proxy for `optimade.models.EntryResource`, where the specific entries will take the place, e.g., `StructureResource`.

In this way, one can instatiate the adapter class with a JSON resource object matching it, and get all the properties as attributes. E.g., one could get the lattice vectors of a structure then by calling
`structures_adapter_class_object.attributes.lattice_vectors`. The reason to not have the adapter class be a direct link or subclass of the model class, is to not inherit all that comes with it, i.e., do not inherit `pyndantic`'s `BaseModel`, etc. While it can trip most IDEs, it is a more elegant solution that opens the doors to other possibilities, in my opinion.

Besides being a handy wrapper for an entity resource object for a client, it also serves as a converter/adapter to other known and widely used libraries for the specific entity type. For a structures resource object, this may be libraries such as pymatgen, ASE, AiiDA, etc...
It may also be nice to be able to convert the structures resource object to a Crystallographic Information File (CIF) file or other. These adapter/conversion functions may be added in a separate file and then referenced in the adapter class.

Thus, the adapter class stays small at first glance, but spreads its tentacles wide throughout both the current package, but also a multitude of other libraries, packages, and data formats.

In general, whenever the adapter class tries to get an attribute, it goes through the following steps:

1. If the attribute starts with `get_`, try to look up if this is a known conversion, then perform the conversion and return it as a string or specific package/library Python class.
2. Try to fetch the OPTIMADE model property.
3. Raise AttributeError if the attribute is still unknown, i.e., the previous steps failed and didn't raise already.

---

Missing parts of this PR:

- Add tests:
  - [X] General tests for the adapter class.
  - Tests for each converter function:
    - [x] AiiDA
    - [x] ASE
    - [x] CIF
    - [x] PDB
    - [x] PDBx/mmCIF
    - [x] pymatgen
- [x] Generalize the adapter main class and have `Structure` inherit from it.
- [x] Add `Reference`.
  (Currently added as an empty inheritence, meaning there are currently no conversions available for `Reference`).

Helpful additions/corrections to this PR:

- Optimize the various conversion functions (especially `get_cif` and `get_pbdx_mmcif`).
- Add more well-known or much used data format converters and packages/libraries.
  (Keeping in mind that some of these libraries have a large quantity of converters already, e.g., ASE already converts to PDB and CIF formats, and so therefore these should only be included if going through, e.g., ASE, may end in a loss of data - I may not have been entirely strict enough with this myself already 😅).

codecov[bot]: # [Codecov](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241?src=pr&el=h1) Report
> Merging [#241](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241?src=pr&el=desc) into [master](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/commit/7768f62f2530c8310ae77fe92556a91f2dfe91af&el=desc) will **decrease** coverage by `12.70%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/graphs/tree.svg?width=650&height=150&src=pr&token=UJAtmqkZZO)](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           master     #241       +/-   ##
===========================================
- Coverage   87.11%   74.40%   -12.71%     
===========================================
  Files          43       51        +8     
  Lines        1862     2180      +318     
===========================================
  Hits         1622     1622               
- Misses        240      558      +318     
```

| Flag | Coverage Δ | |
|---|---|---|
| #unittests | `74.40% <0.00%> (-12.71%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [optimade/adapters/\_\_init\_\_.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvX19pbml0X18ucHk=) | `0.00% <0.00%> (ø)` | |
| [optimade/adapters/structures/\_\_init\_\_.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvc3RydWN0dXJlcy9fX2luaXRfXy5weQ==) | `0.00% <0.00%> (ø)` | |
| [optimade/adapters/structures/aiida.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvc3RydWN0dXJlcy9haWlkYS5weQ==) | `0.00% <0.00%> (ø)` | |
| [optimade/adapters/structures/ase.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvc3RydWN0dXJlcy9hc2UucHk=) | `0.00% <0.00%> (ø)` | |
| [optimade/adapters/structures/cif.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvc3RydWN0dXJlcy9jaWYucHk=) | `0.00% <0.00%> (ø)` | |
| [optimade/adapters/structures/proteindatabank.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvc3RydWN0dXJlcy9wcm90ZWluZGF0YWJhbmsucHk=) | `0.00% <0.00%> (ø)` | |
| [optimade/adapters/structures/pymatgen.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvc3RydWN0dXJlcy9weW1hdGdlbi5weQ==) | `0.00% <0.00%> (ø)` | |
| [optimade/adapters/structures/utils.py](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree#diff-b3B0aW1hZGUvYWRhcHRlcnMvc3RydWN0dXJlcy91dGlscy5weQ==) | `0.00% <0.00%> (ø)` | |
| ... and [6 more](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241?src=pr&el=footer). Last update [7768f62...e99a8b9](https://codecov.io/gh/Materials-Consortia/optimade-python-tools/pull/241?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

shyamd: I'm not fully seeing what this does. How does this plug into the server for instance?
CasperWA: > I'm not fully seeing what this does. How does this plug into the server for instance?

It's not for server use, more for client use. The main goal is to interpret OPTIMADE structures and retrieve their information/convert them to client-usable data formats and constructs.
CasperWA: > thanks @CasperWA , I think having these converters in the optimade python tools is very useful.

Thanks :) Me too, hence the "fold back" from the client I'm working on.

> May I suggest to add both individual extras for the corresponding dependencies + a `converters` extra that contains them all?

I thought about this, got tired at the idea, and set it aside for a time when I have not more exciting ideas for how the adapter should work. I.e., I will definitely add this to the `setup.py` 👍 
ml-evs: Not going to review this until everyone else has finished hacking at it (ping me @CasperWA), but think this looks very useful and is definitely in the spirit of "optimade-python-tools". 

Once we get this in, I think we should focus for a while on writing docs that showcase how this repo can be used by implementers of servers and clients. Would we even want to provide a simple Python/CLI client implementation in this repo, given that we have most of the tools already? (I guess this would depend on how much this treads on the toes of your fancier client @CasperWA)
CasperWA: @ml-evs @ltalirz I consider this ready for review now.
I have added a lot of tests, and tried to come up with edge-cases, and what to do for various optional properties. This has hopefully resulted in more robust conversion functions.

Some of the conversion function tests are the same from test file to test file. This may be made more elegant, but in order to properly separate out the test functions if one should not have the specific dependency installed, this is the design I chose.

Note also, the kind of ugly addition to our CI workflows, due to AiiDA.
In order to create a `StructureData` AiiDA Node, one _must_ have a functioning AiiDA profile loaded, hence all the extra stuff.
I have opened an issue aiidateam/aiida-core#3898 to try and make this more gentle on the lines of code in our workflows. Since this design idea from the side of AiiDA is so deeply integrated into its code base, it is difficult to see how we can get to a state, where it may not be needed to set up an AiiDA profile, just for testing this.

Finally, if you have any optimizations for the various converters, now is the time! :) If you want to add a bunch of new ones (especially to the `references` adapter), perhaps consider either doing it in another PR or _quickly_ add it to this PR :)
I know the PDBx/mmCIF is a bit of a mess, but I don't have the time now to research a whole new and to me unknown format to get it properly up to speed - sorry. I feel this is better than nothing for now?
CasperWA: > Not going to review this until everyone else has finished hacking at it (ping me @CasperWA), but think this looks very useful and is definitely in the spirit of "optimade-python-tools".

Consider yourself "pinged" @ml-evs :) (But not until Tuesday the 14th and onwards).

> Once we get this in, I think we should focus for a while on writing docs that showcase how this repo can be used by implementers of servers and clients. (...)

I think that would definitely be the next big "project" for the repo. Also, we should make sure to update to v1.0.0 ASAP (both the package version, but I was thinking mainly of the OPTIMADE API version). Maybe v1 of the package should indeed wait until we have docs in place.

> (...) Would we even want to provide a simple Python/CLI client implementation in this repo, given that we have most of the tools already? (I guess this would depend on how much this treads on the toes of your fancier client @CasperWA)

So as soon as this PR goes through, we could (in theory) adopt my client. That way I can update the current version with _this_ adapter. And I don't know how fancy it is... But you can now [check it out on binder](https://mybinder.org/v2/gh/aiidalab/aiidalab-optimade/pure_jupyter_version?urlpath=%2Fapps%2FOPTIMADE_general.ipynb) to judge for yourself 😅 
I could even move the repository [aiidalab/aiidalab-optimade](https://github.com/aiidalab/aiidalab-optimade) over to Materials-Consortia? I have discussed this loosely with @giovannipizzi already.
I would still want to create an AiiDA lab-specific edition of the client, but the most recent version, which is about to be merged into the `develop` branch, is completely AiiDA- and AiiDA lab-agnostic - only depending on the Jupyter environment, really.

But I don't know if we want an OPTIMADE client in Jupyter under the Materials-Consortia umbrella?

In any case! We should probably create an issue for this discussion, or rather pick it up at a consortium meeting.
shyamd: I would say we want to make a stark distinction between a client and a jupyter-widget. The client should be agnostic of use, so generally just a python class that can be instantiated with an OPTIMADE endpoint URL that allows traversal and retrieval. I would argue this `adapters` PR is really a part of the client and not core. We might think about breaking this into 3 namespace-packages. One for core models and definitions. One for the server. One for the client. 
ml-evs: > Consider yourself "pinged" @ml-evs :) (But not until Tuesday the 14th and onwards).

:+1:

> I think that would definitely be the next big "project" for the repo. Also, we should make sure to update to v1.0.0 ASAP (both the package version, but I was thinking mainly of the OPTIMADE API version). Maybe v1 of the package should indeed wait until we have docs in place.

:+1:

> So as soon as this PR goes through, we could (in theory) adopt my client. That way I can update the current version with _this_ adapter. And I don't know how fancy it is... But you can now [check it out on binder](https://mybinder.org/v2/gh/aiidalab/aiidalab-optimade/pure_jupyter_version?urlpath=%2Fapps%2FOPTIMADE_general.ipynb) to judge for yourself sweat_smile

This is very impressive, I love it! Definitely going to have a look through the code, I didn't realise ipywidgets could be so powerful. 

This goes far above and beyond what I had in mind for this repository, but perhaps there is potential for some overlap? I was imagining some simple client classes that allow you to get the pydantic models from a server in pure Python (trivial given that we do this already) and perhaps a small CLI on top of that. This could obviously be achieved with `wget`... perhaps we could think of a simple OPTIMADE use case that could be achieved more easily with this approach, e.g. use the CLI to find all structures of a given formula across all of the providers and stick them in a json file, with an easy way to read it back in with the pydantic models.

> In any case! We should probably create an issue for this discussion, or rather pick it up at a consortium meeting.

:+1: 
ml-evs: > I would say we want to make a stark distinction between a client and a jupyter-widget. The client should be agnostic of use, so generally just a python class that can be instantiated with an OPTIMADE endpoint URL that allows traversal and retrieval. I would argue this `adapters` PR is really a part of the client and not core. We might think about breaking this into 3 namespace-packages. One for core models and definitions. One for the server. One for the client.

I think we're in agreement, though I'm not against bundling the models/definitions with our example server and a lightweight example client in one package (for now, at least) but we should discuss this at the next meeting.

We currently have some entangled layers to unpick, like the server being required to generate an openapi spec (not sure we can get around this), and I don't think our grammar/filtering is mature enough yet to be separated from the example server either, at least not before OPTIMADE 1.0.
shyamd: Actually that's exactly what namespace packages are for. We can still develop these packages under the same repo. As a result, build-time cross dependencies are not an issue. Since all the packages will share one version, we can keep them all consistent as well.

https://packaging.python.org/guides/packaging-namespace-packages/

For an example on how this works, you might look at our MPContribs repo:
https://github.com/materialsproject/MPContribs
ml-evs: > Actually that's exactly what namespace packages are for. We can still develop these packages under the same repo. As a result, build-time cross dependencies are not an issue. Since all the packages will share one version, we can keep them all consistent as well.

Ah, gotcha, this looks like a sensible way to go then. 
CasperWA: @ml-evs I haven't addressed all your comments yet, and I don't know how much more I will be able to do today.
However, it is in a much better state now, I think. The tests have been modularized and a clear path to doing some centralized testing of all conversion functions has opened up, but not yet followed.
Even more, is that it seems most of the modules _can_ indeed handle unknown values, as long as it's explicitly converted to `float("nan")`.
CasperWA: All right @ml-evs ! I have finished addressing your review comments, it should be ready for re-review.

Patch:
diff --git a/.github/aiida/profile.yaml b/.github/aiida/profile.yaml
new file mode 100644
index 00000000..582d2859
--- /dev/null
+++ b/.github/aiida/profile.yaml
@@ -0,0 +1,14 @@
+---
+profile: PLACEHOLDER_PROFILE
+email: aiida@localhost
+first_name: AiiDA
+last_name: OPTIMADE
+institution: Materials-Consortia
+db_backend: PLACEHOLDER_BACKEND
+db_engine: postgresql_psycopg2
+db_host: localhost
+db_port: 5432
+db_name: PLACEHOLDER_DATABASE_NAME
+db_username: postgres
+db_password: test
+repository: PLACEHOLDER_REPOSITORY
\ No newline at end of file
diff --git a/.github/aiida/setup_aiida.sh b/.github/aiida/setup_aiida.sh
new file mode 100755
index 00000000..2c481e3a
--- /dev/null
+++ b/.github/aiida/setup_aiida.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -ev
+
+# Replace the placeholders in configuration files with actual values
+CONFIG="${GITHUB_WORKSPACE}/.github/aiida"
+sed -i "s|PLACEHOLDER_BACKEND|${AIIDA_TEST_BACKEND}|" "${CONFIG}/profile.yaml"
+sed -i "s|PLACEHOLDER_PROFILE|test_${AIIDA_TEST_BACKEND}|" "${CONFIG}/profile.yaml"
+sed -i "s|PLACEHOLDER_DATABASE_NAME|test_${AIIDA_TEST_BACKEND}|" "${CONFIG}/profile.yaml"
+sed -i "s|PLACEHOLDER_REPOSITORY|/tmp/test_repository_test_${AIIDA_TEST_BACKEND}/|" "${CONFIG}/profile.yaml"
+
+verdi setup --config "${CONFIG}/profile.yaml"
+
+verdi profile setdefault test_${AIIDA_TEST_BACKEND}
diff --git a/.github/workflows/deps_eager.yml b/.github/workflows/deps_eager.yml
index 9a4487ec..8390ba83 100644
--- a/.github/workflows/deps_eager.yml
+++ b/.github/workflows/deps_eager.yml
@@ -18,13 +18,25 @@ jobs:
         python-version: [3.6, 3.7, 3.8]
 
     services:
-        mongo:
-            image: mongo:4.2
-            ports:
-            - 27017:27017
+      mongo:
+        image: mongo:4.2
+        ports:
+        - 27017:27017
+      postgres:
+        image: postgres:10
+        env:
+          POSTGRES_DB: test_django
+          POSTGRES_PASSWORD: test
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports:
+        - 5432:5432
 
     steps:
-    - uses: actions/checkout@master
+    - uses: actions/checkout@v2
 
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v1
@@ -35,8 +47,8 @@ jobs:
       run: |
         python -m pip install --upgrade pip
         pip install -U setuptools
-        # Install package clean with all dependencies followed by eager update install
-        pip install -e .[testing,django,elastic]
+        # Install package clean with test dependencies followed by eager update install
+        pip install -e .[testing]
         pip install -U --upgrade-strategy eager -r .github/workflows/requirements_eager.txt
 
     - name: Run tests on updated packages
@@ -49,6 +61,20 @@ jobs:
       env:
         OPTIMADE_CI_FORCE_MONGO: 0
 
+    - name: Install adapter conversion dependencies (eagerly)
+      run: |
+        pip install -U --upgrade-strategy eager -e .[all]
+        # AiiDA-specific
+        reentry scan
+
+    - name: Setup up environment for AiiDA
+      env:
+        AIIDA_TEST_BACKEND: django
+      run: .github/aiida/setup_aiida.sh
+
+    - name: Run previously skipped tests for adapter conversion
+      run: pytest -rs --cov=./optimade/ --cov-report=xml --cov-append tests/adapters/
+
   # deps_clean-install:
   #   runs-on: ubuntu-latest
   #   strategy:
@@ -58,7 +84,7 @@ jobs:
   #       python-version: [3.6, 3.7, 3.8]
 
   #   steps:
-  #   - uses: actions/checkout@master
+  #   - uses: actions/checkout@v2
 
   #   - name: Set up Python ${{ matrix.python-version }}
   #     uses: actions/setup-python@v1
diff --git a/.github/workflows/deps_lint.yml b/.github/workflows/deps_lint.yml
index f4710728..c6feb66b 100644
--- a/.github/workflows/deps_lint.yml
+++ b/.github/workflows/deps_lint.yml
@@ -12,7 +12,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@master
+    - uses: actions/checkout@v2
 
     - name: Set up Python 3.7
       uses: actions/setup-python@v1
@@ -37,7 +37,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@master
+    - uses: actions/checkout@v2
 
     - name: Set up Python 3.7
       uses: actions/setup-python@v1
@@ -59,7 +59,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@master
+    - uses: actions/checkout@v2
 
     - name: Set up Python 3.7
       uses: actions/setup-python@v1
@@ -94,7 +94,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@master
+    - uses: actions/checkout@v2
 
     - name: Build the Docker images
       run: docker-compose build
@@ -138,13 +138,25 @@ jobs:
         python-version: [3.6, 3.7, 3.8]
 
     services:
-        mongo:
-            image: mongo:4.2
-            ports:
-            - 27017:27017
+      mongo:
+        image: mongo:4.2
+        ports:
+        - 27017:27017
+      postgres:
+        image: postgres:10
+        env:
+          POSTGRES_DB: test_django
+          POSTGRES_PASSWORD: test
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports:
+        - 5432:5432
 
     steps:
-    - uses: actions/checkout@master
+    - uses: actions/checkout@v2
 
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v1
@@ -157,7 +169,7 @@ jobs:
         pip install -U setuptools
         # Install static dependencies followed by "normal" install
         python -m pip install -r .github/workflows/requirements.txt
-        pip install -e .[all]
+        pip install -e .[testing]
 
     - name: Run all tests (using a real MongoDB)
       run: pytest -rs --cov=./optimade/ --cov-report=xml
@@ -169,6 +181,20 @@ jobs:
       env:
         OPTIMADE_CI_FORCE_MONGO: 0
 
+    - name: Install adapter conversion dependencies
+      run: |
+        pip install -e .[all]
+        # AiiDA-specific
+        reentry scan
+
+    - name: Setup up environment for AiiDA
+      env:
+        AIIDA_TEST_BACKEND: django
+      run: .github/aiida/setup_aiida.sh
+
+    - name: Run previously skipped tests for adapter conversion
+      run: pytest -rs --cov=./optimade/ --cov-report=xml --cov-append tests/adapters/
+
     - name: Upload coverage to Codecov
       if: matrix.python-version == 3.7 && github.repository == 'Materials-Consortia/optimade-python-tools'
       uses: codecov/codecov-action@v1
diff --git a/.github/workflows/publish-on-pypi.yml b/.github/workflows/publish-on-pypi.yml
index a699dfd1..03655a10 100644
--- a/.github/workflows/publish-on-pypi.yml
+++ b/.github/workflows/publish-on-pypi.yml
@@ -12,7 +12,7 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@master
+      uses: actions/checkout@v2
 
     - name: Set up Python 3.7
       uses: actions/setup-python@v1
diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt
index 7254f41d..18660b2c 100644
--- a/.github/workflows/requirements.txt
+++ b/.github/workflows/requirements.txt
@@ -1,5 +1,5 @@
-lark-parser==0.8.1
-fastapi==0.52.0
+lark-parser==0.8.5
+fastapi==0.53.1
 pydantic==1.4
 email_validator==1.0.5
 requests==2.23.0
diff --git a/optimade/adapters/__init__.py b/optimade/adapters/__init__.py
new file mode 100644
index 00000000..5429d7b2
--- /dev/null
+++ b/optimade/adapters/__init__.py
@@ -0,0 +1,7 @@
+# pylint: disable=undefined-variable
+from .exceptions import *
+from .references import *
+from .structures import *
+
+
+__all__ = exceptions.__all__ + references.__all__ + structures.__all__
diff --git a/optimade/adapters/base.py b/optimade/adapters/base.py
new file mode 100644
index 00000000..f1b8b36d
--- /dev/null
+++ b/optimade/adapters/base.py
@@ -0,0 +1,120 @@
+import re
+from typing import Union, Dict, Callable, Any, Tuple, List
+
+from pydantic import BaseModel  # pylint: disable=no-name-in-module
+
+from optimade.models import EntryResource
+
+
+class EntryAdapter:
+    """Base class for lazy resource entry adapters
+    :param entry: JSON OPTIMADE single resource entry.
+    """
+
+    ENTRY_RESOURCE: EntryResource = EntryResource
+    _type_converters: Dict[str, Callable] = {}
+
+    def __init__(self, entry: dict):
+        self._entry = None
+        self._converted = {}
+
+        self.entry = entry
+
+        # Note that these return also the default values for otherwise non-provided properties.
+        self._common_converters = {
+            "json": self.entry.json,  # Return JSON serialized string, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeljson
+            "dict": self.entry.dict,  # Return Python dict, see https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
+        }
+
+    @property
+    def entry(self):
+        """Get OPTIMADE entry"""
+        return self._entry
+
+    @entry.setter
+    def entry(self, value: dict):
+        """Set OPTIMADE entry
+        If already set, print that this can _only_ be set once.
+        """
+        if self._entry is None:
+            self._entry = self.ENTRY_RESOURCE(**value)
+        else:
+            print("entry can only be set once and is already set.")
+
+    def convert(self, format: str) -> Any:
+        """Convert OPTIMADE entry to desired format"""
+        if (
+            format not in self._type_converters
+            and format not in self._common_converters
+        ):
+            raise AttributeError(
+                f"Non-valid entry type to convert to: {format}\n"
+                f"Valid entry types: {tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}"
+            )
+
+        if self._converted.get(format, None) is None:
+            if format in self._type_converters:
+                self._converted[format] = self._type_converters[format](self.entry)
+            else:
+                self._converted[format] = self._common_converters[format]()
+
+        return self._converted[format]
+
+    @staticmethod
+    def _get_model_attributes(
+        starting_instances: Union[Tuple[BaseModel], List[BaseModel]], name: str
+    ) -> Any:
+        """Helper method for retrieving the OPTIMADE model's attribute, supporting "."-nested attributes"""
+        for res in starting_instances:
+            nested_attributes = name.split(".")
+            for nested_attribute in nested_attributes:
+                if nested_attribute in getattr(res, "__fields__", {}):
+                    res = getattr(res, nested_attribute)
+                else:
+                    res = None
+                    break
+            if res is not None:
+                return res
+        raise AttributeError
+
+    def __getattr__(self, name: str) -> Any:
+        """Get converted entry or attribute from OPTIMADE entry
+        Support any level of "."-nested OPTIMADE ENTRY_RESOURCE attributes, e.g., `attributes.species` for StuctureResource.
+        NOTE: All nested attributes must individually be subclasses of `pydantic.BaseModel`,
+        i.e., one can not access nested attributes in lists by passing a "."-nested `name` to this method,
+        e.g., `attributes.species.name` or `attributes.species[0].name` will not work for variable `name`.
+
+        Order:
+        - Try to return converted entry if using `as_<_type_converters key>`.
+        - Try to return OPTIMADE ENTRY_RESOURCE (nested) attribute.
+        - Try to return OPTIMADE ENTRY_RESOURCE.attributes (nested) attribute.
+        - Raise AttributeError
+        """
+        # as_<entry_type>
+        if name.startswith("as_"):
+            entry_type = "_".join(name.split("_")[1:])
+            return self.convert(entry_type)
+
+        # Try returning ENTRY_RESOURCE attribute
+        try:
+            res = self._get_model_attributes((self.entry, self.entry.attributes), name)
+        except AttributeError:
+            pass
+        else:
+            return res
+
+        # Non-valid attribute
+        entry_resource_name = re.match(
+            r"(<class ')([a-zA-Z_]+\.)*([a-zA-Z_]+)('>)", str(self.ENTRY_RESOURCE)
+        )
+        entry_resource_name = (
+            entry_resource_name.group(3)
+            if entry_resource_name is not None
+            else "UNKNOWN RESOURCE"
+        )
+        raise AttributeError(
+            f"Unknown attribute: {name}\n"
+            "If you want to get a converted entry as <entry_type> use `as_<entry_type>`, "
+            f"where `<entry_type>` is one of {tuple(self._type_converters.keys()) + tuple(self._common_converters.keys())}\n"
+            f"Otherwise, you can try to retrieve an OPTIMADE {entry_resource_name} attribute or property."
+        )
diff --git a/optimade/adapters/exceptions.py b/optimade/adapters/exceptions.py
new file mode 100644
index 00000000..c58a6072
--- /dev/null
+++ b/optimade/adapters/exceptions.py
@@ -0,0 +1,5 @@
+__all__ = ("ConversionError",)
+
+
+class ConversionError(Exception):
+    """Could not convert entry to format"""
diff --git a/optimade/adapters/references/__init__.py b/optimade/adapters/references/__init__.py
new file mode 100644
index 00000000..6a661e1c
--- /dev/null
+++ b/optimade/adapters/references/__init__.py
@@ -0,0 +1,16 @@
+from typing import Union
+
+from optimade.models import ReferenceResource
+
+from optimade.adapters.base import EntryAdapter
+
+
+__all__ = ("Reference",)
+
+
+class Reference(EntryAdapter):
+    """Lazy reference resource converter
+    :param reference: a single JSON OPTIMADE reference resource entry.
+    """
+
+    ENTRY_RESOURCE = ReferenceResource
diff --git a/optimade/adapters/structures/__init__.py b/optimade/adapters/structures/__init__.py
new file mode 100644
index 00000000..0ad2172a
--- /dev/null
+++ b/optimade/adapters/structures/__init__.py
@@ -0,0 +1,30 @@
+from typing import Union
+
+from optimade.models import StructureResource
+
+from optimade.adapters.base import EntryAdapter
+
+from .aiida import get_aiida_structure_data
+from .ase import get_ase_atoms
+from .cif import get_cif
+from .proteindatabank import get_pdb, get_pdbx_mmcif
+from .pymatgen import get_pymatgen
+
+
+__all__ = ("Structure",)
+
+
+class Structure(EntryAdapter):
+    """Lazy structure resource converter
+    :param structure: a single JSON OPTIMADE structure resource entry.
+    """
+
+    ENTRY_RESOURCE = StructureResource
+    _type_converters = {
+        "aiida_structuredata": get_aiida_structure_data,
+        "ase": get_ase_atoms,
+        "cif": get_cif,
+        "pdb": get_pdb,
+        "pdbx_mmcif": get_pdbx_mmcif,
+        "pymatgen": get_pymatgen,
+    }
diff --git a/optimade/adapters/structures/aiida.py b/optimade/adapters/structures/aiida.py
new file mode 100644
index 00000000..755c7739
--- /dev/null
+++ b/optimade/adapters/structures/aiida.py
@@ -0,0 +1,75 @@
+from optimade.models import StructureResource as OptimadeStructure
+
+from optimade.adapters.structures.utils import pad_cell, pad_positions
+
+try:
+    from aiida.orm.nodes.data.structure import StructureData, Kind, Site
+except (ImportError, ModuleNotFoundError):
+    from warnings import warn
+
+    StructureData = None
+    AIIDA_NOT_FOUND = (
+        "AiiDA not found, cannot convert structure to an AiiDA StructureData"
+    )
+
+
+__all__ = ("get_aiida_structure_data",)
+
+
+def get_aiida_structure_data(optimade_structure: OptimadeStructure) -> StructureData:
+    """ Get AiiDA StructureData from OPTIMADE structure
+    :param optimade_structure: OPTIMADE structure
+    :return: StructureData
+    """
+    if globals().get("StructureData", None) is None:
+        warn(AIIDA_NOT_FOUND)
+        return None
+
+    attributes = optimade_structure.attributes
+
+    # Convert null/None values to float("nan")
+    lattice_vectors, adjust_cell = pad_cell(attributes.lattice_vectors)
+    structure = StructureData(cell=lattice_vectors)
+
+    # Add Kinds
+    for kind in attributes.species:
+        symbols = []
+        concentration = []
+        for index, chemical_symbol in enumerate(kind.chemical_symbols):
+            # NOTE: The non-chemical element identifier "X" is identical to how AiiDA handles this,
+            # so it will be treated the same as any other true chemical identifier.
+            if chemical_symbol == "vacancy":
+                # Skip. This is how AiiDA handles vacancies;
+                # to not include them, while keeping the concentration in a site less than 1.
+                continue
+            else:
+                symbols.append(chemical_symbol)
+                concentration.append(kind.concentration[index])
+
+        # AiiDA needs a definition for the mass, and for it to be > 0
+        # mass is OPTIONAL for OPTIMADE structures
+        mass = kind.mass if kind.mass else 1
+
+        structure.append_kind(
+            Kind(symbols=symbols, weights=concentration, mass=mass, name=kind.name)
+        )
+
+    # Convert null/None values to float("nan")
+    cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)
+
+    # Add Sites
+    for index in range(attributes.nsites):
+        # range() to ensure 1-to-1 between kind and site
+        structure.append_site(
+            Site(
+                kind_name=attributes.species_at_sites[index],
+                position=cartesian_site_positions[index],
+            )
+        )
+
+    if adjust_cell:
+        structure._adjust_default_cell(
+            pbc=[bool(dim.value) for dim in attributes.dimension_types]
+        )
+
+    return structure
diff --git a/optimade/adapters/structures/ase.py b/optimade/adapters/structures/ase.py
new file mode 100644
index 00000000..6bc05b20
--- /dev/null
+++ b/optimade/adapters/structures/ase.py
@@ -0,0 +1,56 @@
+from typing import Dict
+from warnings import warn
+
+from optimade.models import Species as OptimadeStructureSpecies
+from optimade.models import StructureResource as OptimadeStructure
+
+from optimade.adapters.exceptions import ConversionError
+
+try:
+    from ase import Atoms, Atom
+except (ImportError, ModuleNotFoundError):
+    Atoms = None
+    ASE_NOT_FOUND = "ASE not found, cannot convert structure to an ASE Atoms"
+
+
+__all__ = ("get_ase_atoms",)
+
+
+def get_ase_atoms(optimade_structure: OptimadeStructure) -> Atoms:
+    """ Get ASE Atoms from OPTIMADE structure
+
+    NOTE: Cannot handle partial occupancies (this includes vacancies)
+
+    :param optimade_structure: OPTIMADE structure
+    :return: ASE.Atoms
+    """
+    if globals().get("Atoms", None) is None:
+        warn(ASE_NOT_FOUND)
+        return None
+
+    attributes = optimade_structure.attributes
+
+    # Cannot handle partial occupancies
+    if "disorder" in attributes.structure_features:
+        raise ConversionError(
+            "ASE cannot handle structures with partial occupancies, sorry."
+        )
+
+    species: Dict[str, OptimadeStructureSpecies] = {
+        species.name: species for species in attributes.species
+    }
+
+    atoms = []
+    for site_number in range(attributes.nsites):
+        species_name = attributes.species_at_sites[site_number]
+        site = attributes.cartesian_site_positions[site_number]
+
+        current_species = species[species_name]
+
+        atoms.append(
+            Atom(symbol=species_name, position=site, mass=current_species.mass)
+        )
+
+    return Atoms(
+        symbols=atoms, cell=attributes.lattice_vectors, pbc=attributes.dimension_types
+    )
diff --git a/optimade/adapters/structures/cif.py b/optimade/adapters/structures/cif.py
new file mode 100644
index 00000000..c12b3557
--- /dev/null
+++ b/optimade/adapters/structures/cif.py
@@ -0,0 +1,139 @@
+import re
+from typing import Dict
+
+from optimade.models import Species as OptimadeStructureSpecies
+from optimade.models import StructureResource as OptimadeStructure
+
+from optimade.adapters.structures.utils import (
+    cell_to_cellpar,
+    pad_positions,
+    fractional_coordinates,
+)
+
+try:
+    import numpy as np
+except ImportError:
+    from warnings import warn
+
+    np = None
+    NUMPY_NOT_FOUND = "NumPy not found, cannot convert structure to CIF"
+
+
+__all__ = ("get_cif",)
+
+
+def get_cif(  # pylint: disable=too-many-locals,too-many-branches
+    optimade_structure: OptimadeStructure,
+) -> str:
+    """ Get CIF file as string from OPTIMADE structure
+
+    Based on `ase.io.cif:write_cif()`.
+
+    :param optimade_structure: OPTIMADE structure
+    :param formatting: What formatting to use for the CIF file data keys.
+        Can be either "mp" or "default".
+    :param encoding: Encoding used for the string. CIF files use "latin-1" as standard.
+        If encoding is "str", a Python str object will be returned.
+    :return: str
+    """
+    # NumPy is needed for calculations
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    cif = """#
+# Created from an OPTIMADE structure.
+#
+# See https://www.optimade.org and/or
+# https://github.com/Materials-Consortia/OPTIMADE for more information.
+#
+"""
+
+    cif += f"data_{optimade_structure.id}\n\n"
+
+    attributes = optimade_structure.attributes
+
+    # Do this only if there's three non-zero lattice vectors
+    # NOTE: This also negates handling of lattice_vectors with null/None values
+    if all(attributes.dimension_types):
+        a_vector, b_vector, c_vector, alpha, beta, gamma = cell_to_cellpar(
+            attributes.lattice_vectors
+        )
+
+        cif += (
+            f"_cell_length_a                    {a_vector:g}\n"
+            f"_cell_length_b                    {b_vector:g}\n"
+            f"_cell_length_c                    {c_vector:g}\n"
+            f"_cell_angle_alpha                 {alpha:g}\n"
+            f"_cell_angle_beta                  {beta:g}\n"
+            f"_cell_angle_gamma                 {gamma:g}\n\n"
+        )
+        cif += (
+            "_symmetry_space_group_name_H-M    'P 1'\n"
+            "_symmetry_int_tables_number       1\n\n"
+            "loop_\n"
+            "  _symmetry_equiv_pos_as_xyz\n"
+            "  'x, y, z'\n\n"
+        )
+
+        # Since some structure viewers are having issues with cartesian coordinates,
+        # we calculate the fractional coordinates if this is a 3D structure and we have all the necessary information.
+        if not hasattr(attributes, "fractional_site_positions"):
+            sites, _ = pad_positions(attributes.cartesian_site_positions)
+            attributes.fractional_site_positions = fractional_coordinates(
+                cell=attributes.lattice_vectors, cartesian_positions=sites
+            )
+
+    # NOTE: This is otherwise a bit ahead of its time, since this OPTIMADE property is part of an open PR.
+    # See https://github.com/Materials-Consortia/OPTIMADE/pull/206
+    coord_type = (
+        "fract" if hasattr(attributes, "fractional_site_positions") else "Cartn"
+    )
+
+    cif += (
+        "loop_\n"
+        "  _atom_site_label\n"  # species.name
+        "  _atom_site_occupancy\n"  # species.concentration
+        f"  _atom_site_{coord_type}_x\n"  # cartesian_site_positions
+        f"  _atom_site_{coord_type}_y\n"  # cartesian_site_positions
+        f"  _atom_site_{coord_type}_z\n"  # cartesian_site_positions
+        "  _atom_site_thermal_displace_type\n"  # Set to 'Biso'
+        "  _atom_site_B_iso_or_equiv\n"  # Set to 1.0:f
+        "  _atom_site_type_symbol\n"  # species.chemical_symbols
+    )
+
+    if coord_type == "fract":
+        sites, _ = pad_positions(attributes.fractional_site_positions)
+    else:
+        sites, _ = pad_positions(attributes.cartesian_site_positions)
+
+    species: Dict[str, OptimadeStructureSpecies] = {
+        species.name: species for species in attributes.species
+    }
+
+    for site_number in range(attributes.nsites):
+        species_name = attributes.species_at_sites[site_number]
+        site = sites[site_number]
+
+        current_species = species[species_name]
+
+        for index, symbol in enumerate(current_species.chemical_symbols):
+            if symbol == "vacancy":
+                continue
+
+            label = species_name
+            if len(current_species.chemical_symbols) > 1:
+                if (
+                    "vacancy" in current_species.chemical_symbols
+                    and len(current_species.chemical_symbols) == 2
+                ):
+                    pass
+                else:
+                    label = f"{symbol}{index + 1}"
+
+            cif += (
+                f"  {label:8} {current_species.concentration[index]:6.4f} {site[0]:8.5f}  "
+                f"{site[1]:8.5f}  {site[2]:8.5f}  {'Biso':4}  {'1.000':6}  {symbol}\n"
+            )
+
+    return cif
diff --git a/optimade/adapters/structures/proteindatabank.py b/optimade/adapters/structures/proteindatabank.py
new file mode 100644
index 00000000..0c02dca1
--- /dev/null
+++ b/optimade/adapters/structures/proteindatabank.py
@@ -0,0 +1,252 @@
+# pylint: disable=unreachable
+from typing import Dict
+
+try:
+    import numpy as np
+except ImportError:
+    from warnings import warn
+
+    np = None
+    NUMPY_NOT_FOUND = "NumPy not found, cannot convert structure to your desired format"
+
+from optimade.models import Species as OptimadeStructureSpecies
+from optimade.models import StructureResource as OptimadeStructure
+
+from optimade.adapters.structures.utils import (
+    cell_to_cellpar,
+    cellpar_to_cell,
+    fractional_coordinates,
+    pad_positions,
+    scaled_cell,
+)
+
+
+__all__ = ("get_pdb", "get_pdbx_mmcif")
+
+
+def get_pdbx_mmcif(  # pylint: disable=too-many-locals
+    optimade_structure: OptimadeStructure,
+) -> str:  # pragma: no cover
+    """ Write Protein Data Bank (PDB) structure in the PDBx/mmCIF format from OPTIMADE structure
+
+    Inspired by `ase.io.proteindatabank:write_proteindatabank()` in the ASE package,
+    as well as `ase.io.cif:write_cif()`.
+
+    :param optimade_structure: OPTIMADE structure
+    :return: str
+    """
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    cif = """#
+# Created from an OPTIMADE structure.
+#
+# See https://www.optimade.org and/or
+# https://github.com/Materials-Consortia/OPTIMADE for more information.
+#
+# CIF 2.0 format, specifically mmCIF (PDBx).
+# See http://mmcif.wwpdb.org for more information.
+#
+"""
+
+    entry_id = f"{optimade_structure.type}{optimade_structure.id}"
+    cif += f"data_{entry_id}\n_entry.id                         {entry_id}\n#\n"
+
+    attributes = optimade_structure.attributes
+
+    # Do this only if there's three non-zero lattice vectors
+    if all(attributes.dimension_types):
+        a_vector, b_vector, c_vector, alpha, beta, gamma = cell_to_cellpar(
+            attributes.lattice_vectors
+        )
+
+        cif += (
+            f"_cell.entry_id                    {entry_id}\n"
+            f"_cell.length_a                    {a_vector:g}\n"
+            f"_cell.length_b                    {b_vector:g}\n"
+            f"_cell.length_c                    {c_vector:g}\n"
+            f"_cell.angle_alpha                 {alpha:g}\n"
+            f"_cell.angle_beta                  {beta:g}\n"
+            f"_cell.angle_gamma                 {gamma:g}\n"
+            "_cell.Z_PDB                       1\n#\n"
+        )
+        cif += (
+            f"_symmetry.entry_id                {entry_id}\n"
+            "_symmetry.space_group_name_H-M    'P 1'\n"
+            "_symmetry.Int_Tables_number       1\n#\n"
+        )
+
+        # Since some structure viewers are having issues with cartesian coordinates,
+        # we calculate the fractional coordinates if this is a 3D structure and we have all the necessary information.
+        if not hasattr(attributes, "fractional_site_positions"):
+            sites, _ = pad_positions(attributes.cartesian_site_positions)
+            attributes.fractional_site_positions = fractional_coordinates(
+                cell=attributes.lattice_vectors, cartesian_positions=sites
+            )
+
+    # TODO: The following lines are perhaps needed to create a "valid" PDBx/mmCIF file.
+    # However, at the same time, the information here is "default" and will for all structures "at this moment in time"
+    # be the same. I.e., no information is gained by adding this now.
+    # If it is found that they indeed are needed to create a "valid" PDBx/mmCIF file, they should be included in the output.
+    # cif += (
+    #     "loop_\n"
+    #     "_struct_asym.id\n"
+    #     "_struct_asym.entity_id\n"
+    #     "A  1\n#\n"  # At this point, not using this feature.
+    # )
+
+    # cif += (
+    #     "loop_\n"
+    #     "_chem_comp.id\n"
+    #     "X\n#\n"  # At this point, not using this feature.
+    # )
+
+    # cif += (
+    #     "loop_\n"
+    #     "_entity.id\n"
+    #     "1\n#\n"  # At this point, not using this feature.
+    # )
+
+    # NOTE: This is otherwise a bit ahead of its time, since this OPTIMADE property is part of an open PR.
+    # See https://github.com/Materials-Consortia/OPTIMADE/pull/206
+    coord_type = (
+        "fract" if hasattr(attributes, "fractional_site_positions") else "Cartn"
+    )
+
+    cif += (
+        "loop_\n"
+        "_atom_site.group_PDB\n"  # Always "ATOM"
+        "_atom_site.id\n"  # number (1-counting)
+        "_atom_site.type_symbol\n"  # species.chemical_symbols
+        "_atom_site.label_atom_id\n"  # species.checmical_symbols symbol + number
+        # For these next keys, see the comment above.
+        # "_atom_site.label_asym_id\n"  # Will be set to "A" _struct_asym.id above
+        # "_atom_site.label_comp_id\n"  # Will be set to "X" _chem_comp.id above
+        # "_atom_site.label_entity_id\n"  # Will be set to "1" _entity.id above
+        # "_atom_site.label_seq_id\n"
+        "_atom_site.occupancy\n"  # species.concentration
+        f"_atom_site.{coord_type}_x\n"  # cartesian_site_positions
+        f"_atom_site.{coord_type}_y\n"  # cartesian_site_positions
+        f"_atom_site.{coord_type}_z\n"  # cartesian_site_positions
+        "_atom_site.thermal_displace_type\n"  # Set to 'Biso'
+        "_atom_site.B_iso_or_equiv\n"  # Set to 1.0:f
+    )
+
+    if coord_type == "fract":
+        sites, _ = pad_positions(attributes.fractional_site_positions)
+    else:
+        sites, _ = pad_positions(attributes.cartesian_site_positions)
+
+    species: Dict[str, OptimadeStructureSpecies] = {
+        species.name: species for species in attributes.species
+    }
+
+    for site_number in range(attributes.nsites):
+        species_name = attributes.species_at_sites[site_number]
+        site = sites[site_number]
+
+        current_species = species[species_name]
+
+        for index, symbol in enumerate(current_species.chemical_symbols):
+            if symbol == "vacancy":
+                continue
+
+            label = f"{species_name.upper()}{site_number + 1}"
+            if len(current_species.chemical_symbols) > 1:
+                if (
+                    "vacancy" in current_species.chemical_symbols
+                    and len(current_species.chemical_symbols) == 2
+                ):
+                    pass
+                else:
+                    label = f"{symbol.upper()}{index + 1}"
+
+            cif += (
+                f"ATOM  {site_number + 1:5d}  {symbol}  {label:8}  "
+                f"{current_species.concentration[index]:6.4f}  {site[0]:8.5f}  "
+                f"{site[1]:8.5f}  {site[2]:8.5f}  {'Biso':4}  {'1.000':6}\n"
+            )
+
+    return cif
+
+
+def get_pdb(  # pylint: disable=too-many-locals
+    optimade_structure: OptimadeStructure,
+) -> str:
+    """ Write Protein Data Bank (PDB) structure in the old PDB format from OPTIMADE structure
+
+    Inspired by `ase.io.proteindatabank.write_proteindatabank()` in the ASE package.
+
+    :param optimade_structure: OPTIMADE structure
+    :return: str
+    """
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    pdb = ""
+
+    attributes = optimade_structure.attributes
+
+    rotation = None
+    if all(attributes.dimension_types):
+        currentcell = np.asarray(attributes.lattice_vectors)
+        cellpar = cell_to_cellpar(currentcell)
+        exportedcell = cellpar_to_cell(cellpar)
+        rotation = np.linalg.solve(currentcell, exportedcell)
+        # Setting Z-value = 1 and using P1 since we have all atoms defined explicitly
+        Z = 1
+        spacegroup = "P 1"
+        pdb += (
+            f"CRYST1{cellpar[0]:9.3f}{cellpar[1]:9.3f}{cellpar[2]:8.3f}"
+            f"{cellpar[3]:7.2f}{cellpar[4]:7.2f}{cellpar[5]:7.2f} {spacegroup:11s}{Z:4d}\n"
+        )
+
+        for i, vector in enumerate(scaled_cell(currentcell)):
+            pdb += f"SCALE{i + 1}    {vector[0]:10.6f}{vector[1]:10.6f}{vector[2]:10.6f}     {0:10.5f}\n"
+
+    # There is a limit of 5 digit numbers in this field.
+    pdb_maxnum = 100000
+    bfactor = 1.0
+
+    pdb += "MODEL     1\n"
+
+    species: Dict[str, OptimadeStructureSpecies] = {
+        species.name: species for species in attributes.species
+    }
+
+    cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)
+    sites = np.asarray(cartesian_site_positions)
+    if rotation is not None:
+        sites = sites.dot(rotation)
+
+    for site_number in range(attributes.nsites):
+        species_name = attributes.species_at_sites[site_number]
+        site = sites[site_number]
+
+        current_species = species[species_name]
+
+        for index, symbol in enumerate(current_species.chemical_symbols):
+            if symbol == "vacancy":
+                continue
+
+            label = species_name
+            if len(current_species.chemical_symbols) > 1:
+                if (
+                    "vacancy" in current_species.chemical_symbols
+                    and len(current_species.chemical_symbols) == 2
+                ):
+                    pass
+                else:
+                    label = f"{symbol}{index + 1}"
+
+            pdb += (
+                f"ATOM  {site_number % pdb_maxnum:5d} {label:4} MOL     1    "
+                f"{site[0]:8.3f}{site[1]:8.3f}{site[2]:8.3f}"
+                f"{current_species.concentration[index]:6.2f}"
+                f"{bfactor:6.2f}          {symbol.upper():2}  \n"
+            )
+    pdb += "ENDMDL\n"
+
+    return pdb
diff --git a/optimade/adapters/structures/pymatgen.py b/optimade/adapters/structures/pymatgen.py
new file mode 100644
index 00000000..0b23c161
--- /dev/null
+++ b/optimade/adapters/structures/pymatgen.py
@@ -0,0 +1,103 @@
+from typing import Union, Dict, List
+
+from optimade.models import Species as OptimadeStructureSpecies
+from optimade.models import StructureResource as OptimadeStructure
+from optimade.models.structures import Periodicity
+
+from optimade.adapters.structures.utils import pad_positions
+
+try:
+    from pymatgen import Structure, Molecule
+
+except (ImportError, ModuleNotFoundError):
+    from warnings import warn
+
+    Structure = None
+    Molecule = None
+    PYMATGEN_NOT_FOUND = "Pymatgen not found, cannot convert structure to a pymatgen Structure or Molecule"
+
+
+__all__ = ("get_pymatgen",)
+
+
+def get_pymatgen(optimade_structure: OptimadeStructure) -> Union[Structure, Molecule]:
+    """ Get pymatgen Structure or Molecule from OPTIMADE structure
+
+    :param optimade_structure: OPTIMADE structure
+    :return: pymatgen.Structure , pymatgen.Molecule
+    """
+    if globals().get("Structure", None) is None:
+        warn(PYMATGEN_NOT_FOUND)
+        return None
+
+    if all(optimade_structure.attributes.dimension_types):
+        return _get_structure(optimade_structure)
+
+    return _get_molecule(optimade_structure)
+
+
+def _get_structure(optimade_structure: OptimadeStructure) -> Structure:
+    """Create pymatgen Structure from OPTIMADE structure"""
+
+    attributes = optimade_structure.attributes
+
+    cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)
+
+    return Structure(
+        lattice=attributes.lattice_vectors,
+        species=_pymatgen_species(
+            nsites=attributes.nsites,
+            species=attributes.species,
+            species_at_sites=attributes.species_at_sites,
+        ),
+        coords=cartesian_site_positions,
+        coords_are_cartesian=True,
+    )
+
+
+def _get_molecule(optimade_structure: OptimadeStructure) -> Molecule:
+    """Create pymatgen Molecule from OPTIMADE structure"""
+
+    attributes = optimade_structure.attributes
+
+    cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)
+
+    return Molecule(
+        species=_pymatgen_species(
+            nsites=attributes.nsites,
+            species=attributes.species,
+            species_at_sites=attributes.species_at_sites,
+        ),
+        coords=cartesian_site_positions,
+    )
+
+
+def _pymatgen_species(
+    nsites: int, species: List[OptimadeStructureSpecies], species_at_sites: List[str]
+) -> List[Dict[str, float]]:
+    """
+    Create list of {"symbol": "concentration"} per site for values to pymatgen species parameters.
+    Remove vacancies, if they are present.
+    """
+
+    optimade_species = {_.name: _ for _ in species}
+
+    pymatgen_species = []
+    for site_number in range(nsites):
+        species_name = species_at_sites[site_number]
+        current_species = optimade_species[species_name]
+
+        chemical_symbols = []
+        concentration = []
+        for index, symbol in enumerate(current_species.chemical_symbols):
+            if symbol == "vacancy":
+                # Skip. This is how pymatgen handles vacancies;
+                # to not include them, while keeping the concentration in a site less than 1.
+                continue
+            else:
+                chemical_symbols.append(symbol)
+                concentration.append(current_species.concentration[index])
+
+        pymatgen_species.append(dict(zip(chemical_symbols, concentration)))
+
+    return pymatgen_species
diff --git a/optimade/adapters/structures/utils.py b/optimade/adapters/structures/utils.py
new file mode 100644
index 00000000..32fe3c36
--- /dev/null
+++ b/optimade/adapters/structures/utils.py
@@ -0,0 +1,249 @@
+# pylint: disable=invalid-name
+from typing import List, Tuple, Iterable
+
+from optimade.models import StructureResourceAttributes
+from optimade.models.structures import Vector3D
+
+from optimade.adapters.exceptions import ConversionError
+
+try:
+    import numpy as np
+except ImportError:
+    from warnings import warn
+
+    np = None
+    NUMPY_NOT_FOUND = "NumPy not found, cannot convert structure to your desired format"
+
+
+def scaled_cell(
+    cell: Tuple[Vector3D, Vector3D, Vector3D]
+) -> Tuple[Vector3D, Vector3D, Vector3D]:
+    """Return a scaled 3x3 cell from cartesian 3x3 cell (`lattice_vectors`)
+
+    This is based on PDB's method of calculating SCALE from CRYST data.
+    For more info, see https://www.wwpdb.org/documentation/file-format-content/format33/sect8.html#SCALEn
+    """
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    cell = np.asarray(cell)
+
+    volume = np.dot(cell[0], np.cross(cell[1], cell[2]))
+    scale = []
+    for i in range(3):
+        vector = np.cross(cell[(i + 1) % 3], cell[(i + 2) % 3]) / volume
+        scale.append(tuple(vector))
+    return tuple(scale)
+
+
+def fractional_coordinates(
+    cell: Tuple[Vector3D, Vector3D, Vector3D], cartesian_positions: List[Vector3D]
+) -> List[Vector3D]:
+    """Returns fractional coordinates and wraps coordinates to be [0;1[
+
+    NOTE: Based on `ase.atoms:Atoms.get_scaled_positions()`.
+    """
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    cell = np.asarray(cell)
+    cartesian_positions = np.asarray(cartesian_positions)
+
+    fractional = np.linalg.solve(cell.T, cartesian_positions.T).T
+
+    # Expecting a bulk 3D structure here, note, this may change in the future.
+    # See `ase.atoms:Atoms.get_scaled_positions()` for ideas on how to handle lower dimensional structures.
+    # Furthermore, according to ASE we need to modulo 1.0 twice.
+    # This seems to be due to small floats % 1.0 becomes 1.0, hence twice makes it 0.0.
+    for i in range(3):
+        fractional[:, i] %= 1.0
+        fractional[:, i] %= 1.0
+
+    return [tuple(position) for position in fractional]
+
+
+def cell_to_cellpar(cell, radians=False):
+    """Returns the cell parameters [a, b, c, alpha, beta, gamma].
+
+    Angles are in degrees unless radian=True is used.
+
+    NOTE: Based on `ase.geometry.cell:cell_to_cellpar()`.
+    """
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    cell = np.asarray(cell)
+
+    lengths = [np.linalg.norm(vector) for vector in cell]
+    angles = []
+    for i in range(3):
+        j = i - 1
+        k = i - 2
+        outer_product = lengths[j] * lengths[k]
+        if outer_product > 1e-16:
+            x_vector = np.dot(cell[j], cell[k]) / outer_product
+            angle = 180.0 / np.pi * np.arccos(x_vector)
+        else:
+            angle = 90.0
+        angles.append(angle)
+    if radians:
+        angles = [angle * np.pi / 180 for angle in angles]
+    return np.array(lengths + angles)
+
+
+def unit_vector(x):
+    """Return a unit vector in the same direction as x."""
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    y = np.array(x, dtype="float")
+    return y / np.linalg.norm(y)
+
+
+def cellpar_to_cell(cellpar, ab_normal=(0, 0, 1), a_direction=None):
+    """Return a 3x3 cell matrix from cellpar=[a,b,c,alpha,beta,gamma].
+
+    Angles must be in degrees.
+
+    The returned cell is orientated such that a and b
+    are normal to `ab_normal` and a is parallel to the projection of
+    `a_direction` in the a-b plane.
+
+    Default `a_direction` is (1,0,0), unless this is parallel to
+    `ab_normal`, in which case default `a_direction` is (0,0,1).
+
+    The returned cell has the vectors va, vb and vc along the rows. The
+    cell will be oriented such that va and vb are normal to `ab_normal`
+    and va will be along the projection of `a_direction` onto the a-b
+    plane.
+
+    Example:
+
+    >>> cell = cellpar_to_cell([1, 2, 4, 10, 20, 30], (0, 1, 1), (1, 2, 3))
+    >>> np.round(cell, 3)
+    array([[ 0.816, -0.408,  0.408],
+           [ 1.992, -0.13 ,  0.13 ],
+           [ 3.859, -0.745,  0.745]])
+
+    NOTE: Direct copy of `ase.geometry.cell:cellpar_to_cell()`.
+    """
+    if globals().get("np", None) is None:
+        warn(NUMPY_NOT_FOUND)
+        return None
+
+    if a_direction is None:
+        if np.linalg.norm(np.cross(ab_normal, (1, 0, 0))) < 1e-5:
+            a_direction = (0, 0, 1)
+        else:
+            a_direction = (1, 0, 0)
+
+    # Define rotated X,Y,Z-system, with Z along ab_normal and X along
+    # the projection of a_direction onto the normal plane of Z.
+    a_direction_array = np.array(a_direction)
+    Z = unit_vector(ab_normal)
+    X = unit_vector(a_direction_array - np.dot(a_direction_array, Z) * Z)
+    Y = np.cross(Z, X)
+
+    # Express va, vb and vc in the X,Y,Z-system
+    alpha, beta, gamma = 90.0, 90.0, 90.0
+    if isinstance(cellpar, (int, float)):
+        a = b = c = cellpar
+    elif len(cellpar) == 1:
+        a = b = c = cellpar[0]
+    elif len(cellpar) == 3:
+        a, b, c = cellpar
+    else:
+        a, b, c, alpha, beta, gamma = cellpar
+
+    # Handle orthorhombic cells separately to avoid rounding errors
+    eps = 2 * np.spacing(90.0, dtype=np.float64)  # around 1.4e-14
+    # alpha
+    if abs(abs(alpha) - 90) < eps:
+        cos_alpha = 0.0
+    else:
+        cos_alpha = np.cos(alpha * np.pi / 180.0)
+    # beta
+    if abs(abs(beta) - 90) < eps:
+        cos_beta = 0.0
+    else:
+        cos_beta = np.cos(beta * np.pi / 180.0)
+    # gamma
+    if abs(gamma - 90) < eps:
+        cos_gamma = 0.0
+        sin_gamma = 1.0
+    elif abs(gamma + 90) < eps:
+        cos_gamma = 0.0
+        sin_gamma = -1.0
+    else:
+        cos_gamma = np.cos(gamma * np.pi / 180.0)
+        sin_gamma = np.sin(gamma * np.pi / 180.0)
+
+    # Build the cell vectors
+    va = a * np.array([1, 0, 0])
+    vb = b * np.array([cos_gamma, sin_gamma, 0])
+    cx = cos_beta
+    cy = (cos_alpha - cos_beta * cos_gamma) / sin_gamma
+    cz_sqr = 1.0 - cx * cx - cy * cy
+    assert cz_sqr >= 0
+    cz = np.sqrt(cz_sqr)
+    vc = c * np.array([cx, cy, cz])
+
+    # Convert to the Cartesian x,y,z-system
+    abc = np.vstack((va, vb, vc))
+    T = np.vstack((X, Y, Z))
+    cell = np.dot(abc, T)
+
+    return cell
+
+
+def _pad_iter_of_iters(
+    iterable: Iterable[Iterable],
+    padding: float = None,
+    outer: Iterable = None,
+    inner: Iterable = None,
+) -> Tuple[Iterable[Iterable], bool]:
+    """Turn any null/None values into a float in given iterable of iterables"""
+    try:
+        padding = float(padding)
+    except (TypeError, ValueError):
+        padding = float("nan")
+
+    outer = outer if outer is not None else list
+    inner = inner if outer is not None else tuple
+
+    padded_iterable = any(
+        value is None for inner_iterable in iterable for value in inner_iterable
+    )
+
+    if padded_iterable:
+        padded_iterable_of_iterables = []
+        for inner_iterable in iterable:
+            new_inner_iterable = inner(
+                value if value is not None else padding for value in inner_iterable
+            )
+            padded_iterable_of_iterables.append(new_inner_iterable)
+        iterable = outer(padded_iterable_of_iterables)
+
+    return iterable, padded_iterable
+
+
+def pad_positions(
+    positions: List[Vector3D], padding: float = None
+) -> Tuple[List[Vector3D], bool]:
+    """Turn any null/None values into a float in given list of positions"""
+    return _pad_iter_of_iters(
+        iterable=positions, padding=padding, outer=list, inner=tuple,
+    )
+
+
+def pad_cell(
+    lattice_vectors: Tuple[Vector3D, Vector3D, Vector3D], padding: float = None
+) -> Tuple[Tuple[Vector3D, Vector3D, Vector3D], bool]:
+    """Turn any null/None values into a float in given tuple of lattice_vectors"""
+    return _pad_iter_of_iters(
+        iterable=lattice_vectors, padding=padding, outer=tuple, inner=tuple,
+    )
diff --git a/optimade/server/config.py b/optimade/server/config.py
index 30db5dfb..078d9cbb 100644
--- a/optimade/server/config.py
+++ b/optimade/server/config.py
@@ -1,5 +1,6 @@
 # pylint: disable=no-self-argument
 import json
+import logging
 from typing import Optional, Dict, List
 
 try:
@@ -15,6 +16,7 @@ from optimade.models import Implementation, Provider
 
 
 DEFAULT_CONFIG_FILE_PATH = str(Path.home().joinpath(".optimade.json"))
+logger = logging.getLogger("optimade")
 
 
 class NoFallback(Exception):
@@ -109,7 +111,7 @@ class ServerConfig(BaseSettings):
     )
 
     @root_validator(pre=True)
-    def load_default_settings(cls, values):
+    def load_default_settings(cls, values):  # pylint: disable=no-self-argument
         """
         Loads settings from a root file if available and uses that as defaults in
         place of built in defaults
@@ -119,8 +121,13 @@ class ServerConfig(BaseSettings):
         new_values = {}
 
         if config_file_path.exists() and config_file_path.is_file():
+            logger.debug("Found config file at: %s", config_file_path)
             with open(config_file_path) as f:
                 new_values = json.load(f)
+        else:
+            logger.debug(  # pragma: no cover
+                "Did not find config file at: %s", config_file_path
+            )
 
         new_values.update(values)
 
diff --git a/setup.py b/setup.py
index 0a59ced8..1ec8576b 100644
--- a/setup.py
+++ b/setup.py
@@ -4,19 +4,30 @@ from setuptools import setup, find_packages
 module_dir = Path(__file__).resolve().parent
 
 # Dependencies
-mongo_deps = ["pymongo~=3.10", "mongomock~=3.19"]
-server_deps = ["uvicorn", "Jinja2~=2.11"] + mongo_deps
+# Server minded
 django_deps = ["django>=2.2.9,<4.0"]
 elastic_deps = ["elasticsearch-dsl>=6.4,<8.0"]
+mongo_deps = ["pymongo~=3.10", "mongomock~=3.19"]
+server_deps = ["uvicorn", "Jinja2~=2.11"] + mongo_deps
+
+# Client minded
+aiida_deps = ["aiida-core~=1.1"]
+ase_deps = ["ase~=3.19"]
+cif_deps = ["numpy~=1.18"]
+pdb_deps = cif_deps
+pymatgen_deps = ["pymatgen~=2020.3"]
+client_deps = cif_deps
+
+# General
 testing_deps = [
-    "pytest~=5.3",
+    "pytest~=5.4",
     "pytest-cov",
     "codecov",
     "openapi-spec-validator",
     "jsondiff",
 ] + server_deps
-dev_deps = ["pylint", "black", "pre-commit", "invoke"] + testing_deps
-all_deps = dev_deps + django_deps + elastic_deps
+dev_deps = ["pylint", "black", "pre-commit", "invoke"] + testing_deps + client_deps
+all_deps = dev_deps + django_deps + elastic_deps + aiida_deps + ase_deps + pymatgen_deps
 
 setup(
     name="optimade",
@@ -44,8 +55,8 @@ setup(
     ],
     python_requires=">=3.6",
     install_requires=[
-        "lark-parser~=0.8.1",
-        "fastapi~=0.52",
+        "lark-parser~=0.8.5",
+        "fastapi~=0.53",
         "pydantic~=1.4",
         "email_validator",
         "requests~=2.23",
@@ -54,11 +65,17 @@ setup(
     extras_require={
         "all": all_deps,
         "dev": dev_deps,
-        "server": server_deps,
         "testing": testing_deps,
+        "server": server_deps,
+        "client": client_deps,
         "django": django_deps,
         "elastic": elastic_deps,
         "mongo": mongo_deps,
+        "aiida": aiida_deps,
+        "ase": ase_deps,
+        "cif": cif_deps,
+        "pdb": pdb_deps,
+        "pymatgen": pymatgen_deps,
     },
     entry_points={
         "console_scripts": ["optimade_validator=optimade.validator:validate"]


Test patch:
diff --git a/pytest.ini b/pytest.ini
index 9628d74e..a04215eb 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -4,3 +4,4 @@ filterwarnings =
     ignore:.*"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead.*:DeprecationWarning
     ignore:.*Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated.*:DeprecationWarning
     ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning
+    ignore:.*not found, cannot convert structure.*:UserWarning
diff --git a/tests/adapters/references/conftest.py b/tests/adapters/references/conftest.py
new file mode 100644
index 00000000..52fbe3f3
--- /dev/null
+++ b/tests/adapters/references/conftest.py
@@ -0,0 +1,28 @@
+import json
+from pathlib import Path
+from random import choice
+
+import pytest
+
+from optimade.adapters.references import Reference
+
+
+@pytest.fixture
+def RAW_REFERENCES():
+    """Read and return raw_references.json"""
+    with open(
+        Path(__file__).parent.joinpath("raw_test_references.json"), "r"
+    ) as raw_data:
+        return json.load(raw_data)
+
+
+@pytest.fixture
+def raw_reference(RAW_REFERENCES):
+    """Return random raw reference from raw_references.json"""
+    return choice(RAW_REFERENCES)
+
+
+@pytest.fixture
+def reference(raw_reference):
+    """Create and return adapters.Reference"""
+    return Reference(raw_reference)
diff --git a/tests/adapters/references/raw_test_references.json b/tests/adapters/references/raw_test_references.json
new file mode 100644
index 00000000..a3f46d81
--- /dev/null
+++ b/tests/adapters/references/raw_test_references.json
@@ -0,0 +1,56 @@
+[
+  {
+    "id": "dijkstra1968",
+    "type": "references",
+    "attributes": {
+      "last_modified": "2019-11-12T14:24:37.331000",
+      "authors": [
+        {
+          "name": "Edsger W. Dijkstra",
+          "firstname": "Edsger",
+          "lastname": "Dijkstra"
+        }
+      ],
+      "doi": "10.1145/362929.362947",
+      "journal": "Communications of the ACM",
+      "title": "Go To Statement Considered Harmful",
+      "year": "1968"
+    }
+  },
+  {
+    "id": "maddox1988",
+    "type": "references",
+    "attributes": {
+      "last_modified": "2019-11-27T14:24:37.331000",
+      "authors": [
+        {
+          "name": "John Maddox",
+          "firstname": "John",
+          "lastname": "Maddox"
+        }
+      ],
+      "doi": "10.1038/335201a0",
+      "journal": "Nature",
+      "title": "Crystals From First Principles",
+      "year": "1988"
+    }
+  },
+  {
+    "id": "dummy/2019",
+    "type": "references",
+    "attributes": {
+      "last_modified": "2019-11-23T14:24:37.332000",
+      "authors": [
+        {
+          "name": "A Nother",
+          "firstname": "A",
+          "lastname": "Nother"
+        }
+      ],
+      "doi": "10.1038/00000",
+      "journal": "JACS",
+      "title": "Dummy reference that should remain orphaned from all structures for testing purposes",
+      "year": "2019"
+    }
+  }
+]
\ No newline at end of file
diff --git a/tests/adapters/references/test_references.py b/tests/adapters/references/test_references.py
new file mode 100644
index 00000000..aa18871d
--- /dev/null
+++ b/tests/adapters/references/test_references.py
@@ -0,0 +1,86 @@
+import pytest
+
+from optimade.adapters import Reference
+from optimade.models import ReferenceResource
+
+
+class TestReference:
+    """Test Reference adapter"""
+
+    def test_instantiate(self, RAW_REFERENCES):
+        """Try instantiating Reference for all raw test references"""
+        for reference in RAW_REFERENCES:
+            new_Reference = Reference(reference)
+            assert isinstance(new_Reference.entry, ReferenceResource)
+
+    def test_setting_entry(self, capfd, RAW_REFERENCES):
+        """Make sure entry can only be set once"""
+        reference = Reference(RAW_REFERENCES[0])
+        reference.entry = RAW_REFERENCES[1]
+        captured = capfd.readouterr()
+        assert "entry can only be set once and is already set." in captured.out
+
+    @pytest.mark.skip(
+        "Currently, there are no conversion types available for references"
+    )
+    def test_convert(self, reference):
+        """Test convert() works
+        Choose currently known entry type - must be updated if no longer available.
+        """
+        if not reference._type_converters:
+            pytest.fail("_type_converters is seemingly empty. This should not be.")
+
+        chosen_type = "SOME_VALID_TYPE"
+        if chosen_type not in reference._type_converters:
+            pytest.fail(
+                f"{chosen_type} not found in _type_converters: {reference._type_converters} - "
+                "please update test tests/adapters/references/test_references.py:TestReference."
+                "test_convert()"
+            )
+
+        converted_reference = reference.convert(chosen_type)
+        assert isinstance(converted_reference, (str, None.__class__))
+        assert converted_reference == reference._converted[chosen_type]
+
+    def test_convert_wrong_format(self, reference):
+        """Test AttributeError is raised if format does not exist"""
+        nonexistant_format = 0
+        right_wrong_format_found = False
+        while not right_wrong_format_found:
+            if str(nonexistant_format) not in reference._type_converters:
+                nonexistant_format = str(nonexistant_format)
+                right_wrong_format_found = True
+            else:
+                nonexistant_format += 1
+
+        with pytest.raises(
+            AttributeError,
+            match=f"Non-valid entry type to convert to: {nonexistant_format}",
+        ):
+            reference.convert(nonexistant_format)
+
+    def test_getattr_order(self, reference):
+        """The order of getting an attribute should be:
+        1. `as_<entry type format>`
+        2. `<entry type attribute>`
+        3. `<entry type attributes attributes>`
+        3. `raise AttributeError with custom message`
+        """
+        # If passing attribute starting with `as_`, it should call `self.convert()`
+        with pytest.raises(
+            AttributeError, match=f"Non-valid entry type to convert to: "
+        ):
+            reference.as_
+
+        # If passing valid ReferenceResource attribute, it should return said attribute
+        for attribute, attribute_type in (
+            ("id", str),
+            ("authors", list),
+            ("attributes.authors", list),
+        ):
+            assert isinstance(getattr(reference, attribute), attribute_type)
+
+        # Otherwise, it should raise AttributeError
+        for attribute in ("nonexistant_attribute", "attributes.nonexistant_attribute"):
+            with pytest.raises(AttributeError, match=f"Unknown attribute: {attribute}"):
+                getattr(reference, attribute)
diff --git a/tests/adapters/structures/__init__.py b/tests/adapters/structures/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/adapters/structures/conftest.py b/tests/adapters/structures/conftest.py
new file mode 100644
index 00000000..72b51434
--- /dev/null
+++ b/tests/adapters/structures/conftest.py
@@ -0,0 +1,64 @@
+import json
+from pathlib import Path
+from random import choice
+from typing import List
+
+import pytest
+
+from optimade.adapters.structures import Structure
+
+
+@pytest.fixture
+def RAW_STRUCTURES() -> List[dict]:
+    """Read and return raw_structures.json"""
+    with open(
+        Path(__file__).parent.joinpath("raw_test_structures.json"), "r"
+    ) as raw_data:
+        return json.load(raw_data)
+
+
+@pytest.fixture
+def SPECIAL_SPECIES_STRUCTURES() -> List[dict]:
+    """Read and return special_species.json"""
+    with open(Path(__file__).parent.joinpath("special_species.json"), "r") as raw_data:
+        return json.load(raw_data)
+
+
+@pytest.fixture
+def raw_structure(RAW_STRUCTURES) -> dict:
+    """Return random raw structure from raw_structures.json"""
+    return choice(RAW_STRUCTURES)
+
+
+@pytest.fixture
+def structure(raw_structure) -> Structure:
+    """Create and return adapters.Structure"""
+    return Structure(raw_structure)
+
+
+@pytest.fixture
+def structures(RAW_STRUCTURES) -> List[Structure]:
+    """Create and return list of adapters.Structure"""
+    return [Structure(_) for _ in RAW_STRUCTURES]
+
+
+@pytest.fixture
+def null_position_structure(raw_structure) -> Structure:
+    """Create and return adapters.Structure with sites that have None values"""
+    raw_structure["attributes"]["cartesian_site_positions"][0] = [None] * 3
+    if "structure_features" in raw_structure["attributes"]:
+        if "unknown_positions" not in raw_structure["attributes"]["structure_features"]:
+            raw_structure["attributes"]["structure_features"].append(
+                "unknown_positions"
+            )
+    else:
+        raw_structure["attributes"]["structure_feature"] = ["unknown_positions"]
+    return Structure(raw_structure)
+
+
+@pytest.fixture
+def null_lattice_vector_structure(raw_structure) -> Structure:
+    """Create and return adapters.Structure with lattice_vectors that have None values"""
+    raw_structure["attributes"]["lattice_vectors"][0] = [None] * 3
+    raw_structure["attributes"]["dimension_types"][0] = 0
+    return Structure(raw_structure)
diff --git a/tests/adapters/structures/raw_test_structures.json b/tests/adapters/structures/raw_test_structures.json
new file mode 100644
index 00000000..00e0a391
--- /dev/null
+++ b/tests/adapters/structures/raw_test_structures.json
@@ -0,0 +1,3695 @@
+[
+  {
+    "id": "mpf_1",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.331000",
+      "elements": [
+        "Ac"
+      ],
+      "nelements": 1,
+      "elements_ratios": [
+        1
+      ],
+      "chemical_formula_descriptive": "Ac",
+      "chemical_formula_reduced": "Ac",
+      "chemical_formula_anonymous": "A",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          1.2503264826932692,
+          0,
+          0
+        ],
+        [
+          0,
+          9.888509716321765,
+          0
+        ],
+        [
+          0,
+          0,
+          0.2972637673241818
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.17570227444196573,
+          0.17570227444196573,
+          0.17570227444196573
+        ]
+      ],
+      "nsites": 1,
+      "species_at_sites": [
+        "Ac"
+      ],
+      "species": [
+        {
+          "name": "Ac",
+          "chemical_symbols": [
+            "Ac"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ac"
+    },
+    "relationships": {
+      "references": {
+        "data": [
+          {
+            "id": "dijkstra1968",
+            "type": "references"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id": "mpf_2",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.331000",
+      "elements": [
+        "Ac",
+        "Ag",
+        "Ir"
+      ],
+      "nelements": 3,
+      "elements_ratios": [
+        0.5,
+        0.25,
+        0.25
+      ],
+      "chemical_formula_descriptive": "Ac2AgIr",
+      "chemical_formula_reduced": "Ac2AgIr",
+      "chemical_formula_anonymous": "A2BC",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          8.79234151692028,
+          0,
+          0
+        ],
+        [
+          0,
+          2.7335844428185343,
+          0
+        ],
+        [
+          0,
+          0,
+          6.350176421077278
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.2096302780439051,
+          0.2096302780439051,
+          0.2096302780439051
+        ],
+        [
+          0.2096302780439051,
+          0.2096302780439051,
+          0.2096302780439051
+        ],
+        [
+          0.2096302780439051,
+          0.2096302780439051,
+          0.2096302780439051
+        ],
+        [
+          0.2096302780439051,
+          0.2096302780439051,
+          0.2096302780439051
+        ]
+      ],
+      "nsites": 4,
+      "species_at_sites": [
+        "Ac",
+        "Ac",
+        "Ag",
+        "Ir"
+      ],
+      "species": [
+        {
+          "name": "Ac",
+          "chemical_symbols": [
+            "Ac"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Ir",
+          "chemical_symbols": [
+            "Ir"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ac-Ag-Ir"
+    },
+    "relationships": {
+      "references": {
+        "data": [
+          {
+            "id": "dijkstra1968",
+            "type": "references"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id": "mpf_3",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.331000",
+      "elements": [
+        "Ac",
+        "Ag",
+        "Pb"
+      ],
+      "nelements": 3,
+      "elements_ratios": [
+        0.5,
+        0.25,
+        0.25
+      ],
+      "chemical_formula_descriptive": "Ac2AgPb",
+      "chemical_formula_reduced": "Ac2AgPb",
+      "chemical_formula_anonymous": "A2BC",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          7.698325441636717,
+          0,
+          0
+        ],
+        [
+          0,
+          3.9419030762941096,
+          0
+        ],
+        [
+          0,
+          0,
+          0.5746646140691603
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.6522459815095935,
+          0.6522459815095935,
+          0.6522459815095935
+        ],
+        [
+          0.6522459815095935,
+          0.6522459815095935,
+          0.6522459815095935
+        ],
+        [
+          0.6522459815095935,
+          0.6522459815095935,
+          0.6522459815095935
+        ],
+        [
+          0.6522459815095935,
+          0.6522459815095935,
+          0.6522459815095935
+        ]
+      ],
+      "nsites": 4,
+      "species_at_sites": [
+        "Ac",
+        "Ac",
+        "Ag",
+        "Pb"
+      ],
+      "species": [
+        {
+          "name": "Ac",
+          "chemical_symbols": [
+            "Ac"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Pb",
+          "chemical_symbols": [
+            "Pb"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ac-Ag-Pb"
+    },
+    "relationships": {
+      "references": {
+        "data": [
+          {
+            "id": "maddox1988",
+            "type": "references"
+          }
+        ]
+      }
+    }
+  },
+  {
+    "id": "mpf_23",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.335000",
+      "elements": [
+        "Ac",
+        "Mg"
+      ],
+      "nelements": 2,
+      "elements_ratios": [
+        0.6666666666666666,
+        0.3333333333333333
+      ],
+      "chemical_formula_descriptive": "Ac2Mg",
+      "chemical_formula_reduced": "Ac2Mg",
+      "chemical_formula_anonymous": "A2B",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          1.2755343366952576,
+          0,
+          0
+        ],
+        [
+          0,
+          5.82414982265072,
+          0
+        ],
+        [
+          0,
+          0,
+          0.8392960659156112
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.5399745988507391,
+          0.5399745988507391,
+          0.5399745988507391
+        ],
+        [
+          0.5399745988507391,
+          0.5399745988507391,
+          0.5399745988507391
+        ],
+        [
+          0.5399745988507391,
+          0.5399745988507391,
+          0.5399745988507391
+        ]
+      ],
+      "nsites": 3,
+      "species_at_sites": [
+        "Ac",
+        "Ac",
+        "Mg"
+      ],
+      "species": [
+        {
+          "name": "Ac",
+          "chemical_symbols": [
+            "Ac"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Mg",
+          "chemical_symbols": [
+            "Mg"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ac-Mg"
+    }
+  },
+  {
+    "id": "mpf_30",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.337000",
+      "elements": [
+        "Ac",
+        "O"
+      ],
+      "nelements": 2,
+      "elements_ratios": [
+        0.4,
+        0.6
+      ],
+      "chemical_formula_descriptive": "Ac2O3",
+      "chemical_formula_reduced": "Ac2O3",
+      "chemical_formula_anonymous": "A3B2",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          5.270508031864836,
+          0,
+          0
+        ],
+        [
+          0,
+          7.390955992107494,
+          0
+        ],
+        [
+          0,
+          0,
+          3.0995955580253574
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.15817967032742697,
+          0.15817967032742697,
+          0.15817967032742697
+        ],
+        [
+          0.15817967032742697,
+          0.15817967032742697,
+          0.15817967032742697
+        ],
+        [
+          0.15817967032742697,
+          0.15817967032742697,
+          0.15817967032742697
+        ],
+        [
+          0.15817967032742697,
+          0.15817967032742697,
+          0.15817967032742697
+        ],
+        [
+          0.15817967032742697,
+          0.15817967032742697,
+          0.15817967032742697
+        ]
+      ],
+      "nsites": 5,
+      "species_at_sites": [
+        "Ac",
+        "Ac",
+        "O",
+        "O",
+        "O"
+      ],
+      "species": [
+        {
+          "name": "Ac",
+          "chemical_symbols": [
+            "Ac"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ac-O"
+    }
+  },
+  {
+    "id": "mpf_110",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.351000",
+      "elements": [
+        "Ac",
+        "Cu",
+        "F",
+        "O"
+      ],
+      "nelements": 4,
+      "elements_ratios": [
+        0.2,
+        0.2,
+        0.2,
+        0.4
+      ],
+      "chemical_formula_descriptive": "AcCuO2F",
+      "chemical_formula_reduced": "AcCuO2F",
+      "chemical_formula_anonymous": "A2BCD",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          8.877504144188517,
+          0,
+          0
+        ],
+        [
+          0,
+          6.281098675533934,
+          0
+        ],
+        [
+          0,
+          0,
+          0.9199940132786888
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.27842674271448375,
+          0.27842674271448375,
+          0.27842674271448375
+        ],
+        [
+          0.27842674271448375,
+          0.27842674271448375,
+          0.27842674271448375
+        ],
+        [
+          0.27842674271448375,
+          0.27842674271448375,
+          0.27842674271448375
+        ],
+        [
+          0.27842674271448375,
+          0.27842674271448375,
+          0.27842674271448375
+        ],
+        [
+          0.27842674271448375,
+          0.27842674271448375,
+          0.27842674271448375
+        ]
+      ],
+      "nsites": 5,
+      "species_at_sites": [
+        "Ac",
+        "Cu",
+        "F",
+        "O",
+        "O"
+      ],
+      "species": [
+        {
+          "name": "Ac",
+          "chemical_symbols": [
+            "Ac"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cu",
+          "chemical_symbols": [
+            "Cu"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "F",
+          "chemical_symbols": [
+            "F"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ac-Cu-F-O"
+    }
+  },
+  {
+    "id": "mpf_200",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.365000",
+      "elements": [
+        "Ag"
+      ],
+      "nelements": 1,
+      "elements_ratios": [
+        1
+      ],
+      "chemical_formula_descriptive": "Ag",
+      "chemical_formula_reduced": "Ag",
+      "chemical_formula_anonymous": "A",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          9.637799319633432,
+          0,
+          0
+        ],
+        [
+          0,
+          6.587212108623569,
+          0
+        ],
+        [
+          0,
+          0,
+          9.773065370403186
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.06033221863150484,
+          0.06033221863150484,
+          0.06033221863150484
+        ]
+      ],
+      "nsites": 1,
+      "species_at_sites": [
+        "Ag"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag"
+    }
+  },
+  {
+    "id": "mpf_220",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.369000",
+      "elements": [
+        "Ag",
+        "Br",
+        "Cl",
+        "Te"
+      ],
+      "nelements": 4,
+      "elements_ratios": [
+        0.625,
+        0.0625,
+        0.0625,
+        0.25
+      ],
+      "chemical_formula_descriptive": "Ag10Te4BrCl",
+      "chemical_formula_reduced": "Ag10Te4BrCl",
+      "chemical_formula_anonymous": "A10B4CD",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          5.327614134703666,
+          0,
+          0
+        ],
+        [
+          0,
+          1.8722403722833025,
+          0
+        ],
+        [
+          0,
+          0,
+          4.493462985508826
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ],
+        [
+          0.9164521459850508,
+          0.9164521459850508,
+          0.9164521459850508
+        ]
+      ],
+      "nsites": 16,
+      "species_at_sites": [
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Br",
+        "Cl",
+        "Te",
+        "Te",
+        "Te",
+        "Te"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Br",
+          "chemical_symbols": [
+            "Br"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cl",
+          "chemical_symbols": [
+            "Cl"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Te",
+          "chemical_symbols": [
+            "Te"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-Br-Cl-Te"
+    }
+  },
+  {
+    "id": "mpf_259",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.377000",
+      "elements": [
+        "Ag",
+        "C",
+        "Cl",
+        "N",
+        "O",
+        "S"
+      ],
+      "nelements": 6,
+      "elements_ratios": [
+        0.2,
+        0.1,
+        0.1,
+        0.1,
+        0.4,
+        0.1
+      ],
+      "chemical_formula_descriptive": "Ag2CSNClO4",
+      "chemical_formula_reduced": "Ag2CSNClO4",
+      "chemical_formula_anonymous": "A4B2CDEF",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          7.7673999365129625,
+          0,
+          0
+        ],
+        [
+          0,
+          9.869311574261594,
+          0
+        ],
+        [
+          0,
+          0,
+          4.715180447893034
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ],
+        [
+          0.48493191553525494,
+          0.48493191553525494,
+          0.48493191553525494
+        ]
+      ],
+      "nsites": 10,
+      "species_at_sites": [
+        "Ag",
+        "Ag",
+        "C",
+        "Cl",
+        "N",
+        "O",
+        "O",
+        "O",
+        "O",
+        "S"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "C",
+          "chemical_symbols": [
+            "C"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cl",
+          "chemical_symbols": [
+            "Cl"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "N",
+          "chemical_symbols": [
+            "N"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "S",
+          "chemical_symbols": [
+            "S"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-C-Cl-N-O-S"
+    }
+  },
+  {
+    "id": "mpf_272",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.380000",
+      "elements": [
+        "Ag",
+        "C",
+        "Cl",
+        "H",
+        "N"
+      ],
+      "nelements": 5,
+      "elements_ratios": [
+        0.08333333333333333,
+        0.25,
+        0.041666666666666664,
+        0.5,
+        0.125
+      ],
+      "chemical_formula_descriptive": "Ag2H12C6N3Cl",
+      "chemical_formula_reduced": "Ag2H12C6N3Cl",
+      "chemical_formula_anonymous": "A12B6C3D2E",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          6.436155587878937,
+          0,
+          0
+        ],
+        [
+          0,
+          4.664748073818609,
+          0
+        ],
+        [
+          0,
+          0,
+          0.554511276982016
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ],
+        [
+          0.2111398706842884,
+          0.2111398706842884,
+          0.2111398706842884
+        ]
+      ],
+      "nsites": 24,
+      "species_at_sites": [
+        "Ag",
+        "Ag",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "Cl",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "N",
+        "N",
+        "N"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "C",
+          "chemical_symbols": [
+            "C"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cl",
+          "chemical_symbols": [
+            "Cl"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "H",
+          "chemical_symbols": [
+            "H"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "N",
+          "chemical_symbols": [
+            "N"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-C-Cl-H-N"
+    }
+  },
+  {
+    "id": "mpf_276",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.381000",
+      "elements": [
+        "Ag",
+        "C",
+        "H",
+        "N",
+        "O"
+      ],
+      "nelements": 5,
+      "elements_ratios": [
+        0.08,
+        0.08,
+        0.08,
+        0.24,
+        0.52
+      ],
+      "chemical_formula_descriptive": "Ag2H2C2N6O13",
+      "chemical_formula_reduced": "Ag2H2C2N6O13",
+      "chemical_formula_anonymous": "A13B6C2D2E2",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          2.9527224047457192,
+          0,
+          0
+        ],
+        [
+          0,
+          9.517463347989763,
+          0
+        ],
+        [
+          0,
+          0,
+          7.304866428016529
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ],
+        [
+          0.5949974616251396,
+          0.5949974616251396,
+          0.5949974616251396
+        ]
+      ],
+      "nsites": 25,
+      "species_at_sites": [
+        "Ag",
+        "Ag",
+        "C",
+        "C",
+        "H",
+        "H",
+        "N",
+        "N",
+        "N",
+        "N",
+        "N",
+        "N",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "C",
+          "chemical_symbols": [
+            "C"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "H",
+          "chemical_symbols": [
+            "H"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "N",
+          "chemical_symbols": [
+            "N"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-C-H-N-O"
+    }
+  },
+  {
+    "id": "mpf_281",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.382000",
+      "elements": [
+        "Ag",
+        "C",
+        "Cl",
+        "H",
+        "N",
+        "O",
+        "S"
+      ],
+      "nelements": 7,
+      "elements_ratios": [
+        0.08695652173913043,
+        0.08695652173913043,
+        0.043478260869565216,
+        0.34782608695652173,
+        0.21739130434782608,
+        0.13043478260869565,
+        0.08695652173913043
+      ],
+      "chemical_formula_descriptive": "Ag2H8C2S2N5ClO3",
+      "chemical_formula_reduced": "Ag2H8C2S2N5ClO3",
+      "chemical_formula_anonymous": "A8B5C3D2E2F2G",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          1.2419933831646812,
+          0,
+          0
+        ],
+        [
+          0,
+          9.07403560922435,
+          0
+        ],
+        [
+          0,
+          0,
+          2.5607011301206617
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ],
+        [
+          0.5043032722543139,
+          0.5043032722543139,
+          0.5043032722543139
+        ]
+      ],
+      "nsites": 23,
+      "species_at_sites": [
+        "Ag",
+        "Ag",
+        "C",
+        "C",
+        "Cl",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "N",
+        "N",
+        "N",
+        "N",
+        "N",
+        "O",
+        "O",
+        "O",
+        "S",
+        "S"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "C",
+          "chemical_symbols": [
+            "C"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cl",
+          "chemical_symbols": [
+            "Cl"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "H",
+          "chemical_symbols": [
+            "H"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "N",
+          "chemical_symbols": [
+            "N"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "S",
+          "chemical_symbols": [
+            "S"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-C-Cl-H-N-O-S"
+    }
+  },
+  {
+    "id": "mpf_446",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.412000",
+      "elements": [
+        "Ag",
+        "Br",
+        "Cl",
+        "Hg",
+        "I",
+        "S"
+      ],
+      "nelements": 6,
+      "elements_ratios": [
+        0.2222222222222222,
+        0.05555555555555555,
+        0.1111111111111111,
+        0.2777777777777778,
+        0.05555555555555555,
+        0.2777777777777778
+      ],
+      "chemical_formula_descriptive": "Ag4Hg5S5IBrCl2",
+      "chemical_formula_reduced": "Ag4Hg5S5IBrCl2",
+      "chemical_formula_anonymous": "A5B5C4D2EF",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          5.132691156529571,
+          0,
+          0
+        ],
+        [
+          0,
+          1.8270015294181907,
+          0
+        ],
+        [
+          0,
+          0,
+          4.5549149749018225
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ],
+        [
+          0.7184493830657678,
+          0.7184493830657678,
+          0.7184493830657678
+        ]
+      ],
+      "nsites": 18,
+      "species_at_sites": [
+        "Ag",
+        "Ag",
+        "Ag",
+        "Ag",
+        "Br",
+        "Cl",
+        "Cl",
+        "Hg",
+        "Hg",
+        "Hg",
+        "Hg",
+        "Hg",
+        "I",
+        "S",
+        "S",
+        "S",
+        "S",
+        "S"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Br",
+          "chemical_symbols": [
+            "Br"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cl",
+          "chemical_symbols": [
+            "Cl"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Hg",
+          "chemical_symbols": [
+            "Hg"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "I",
+          "chemical_symbols": [
+            "I"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "S",
+          "chemical_symbols": [
+            "S"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-Br-Cl-Hg-I-S"
+    }
+  },
+  {
+    "id": "mpf_551",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.434000",
+      "elements": [
+        "Ag",
+        "B",
+        "C",
+        "Cl",
+        "H",
+        "N",
+        "O",
+        "P"
+      ],
+      "nelements": 8,
+      "elements_ratios": [
+        0.013513513513513514,
+        0.13513513513513514,
+        0.20270270270270271,
+        0.02702702702702703,
+        0.5405405405405406,
+        0.013513513513513514,
+        0.04054054054054054,
+        0.02702702702702703
+      ],
+      "chemical_formula_descriptive": "AgB10P2H40C15NCl2O3",
+      "chemical_formula_reduced": "AgB10P2H40C15NCl2O3",
+      "chemical_formula_anonymous": "A40B15C10D3E2F2GH",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          5.055370731514176,
+          0,
+          0
+        ],
+        [
+          0,
+          4.77221883540092,
+          0
+        ],
+        [
+          0,
+          0,
+          5.704704278000719
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ],
+        [
+          0.449480176317956,
+          0.449480176317956,
+          0.449480176317956
+        ]
+      ],
+      "nsites": 74,
+      "species_at_sites": [
+        "Ag",
+        "B",
+        "B",
+        "B",
+        "B",
+        "B",
+        "B",
+        "B",
+        "B",
+        "B",
+        "B",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "C",
+        "Cl",
+        "Cl",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "N",
+        "O",
+        "O",
+        "O",
+        "P",
+        "P"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "B",
+          "chemical_symbols": [
+            "B"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "C",
+          "chemical_symbols": [
+            "C"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cl",
+          "chemical_symbols": [
+            "Cl"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "H",
+          "chemical_symbols": [
+            "H"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "N",
+          "chemical_symbols": [
+            "N"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "P",
+          "chemical_symbols": [
+            "P"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-B-C-Cl-H-N-O-P"
+    }
+  },
+  {
+    "id": "mpf_632",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.450000",
+      "elements": [
+        "Ag",
+        "C",
+        "Cl",
+        "H",
+        "N",
+        "O",
+        "S"
+      ],
+      "nelements": 7,
+      "elements_ratios": [
+        0.034482758620689655,
+        0.10344827586206896,
+        0.034482758620689655,
+        0.4827586206896552,
+        0.20689655172413793,
+        0.034482758620689655,
+        0.10344827586206896
+      ],
+      "chemical_formula_descriptive": "AgH14C3S3N6ClO",
+      "chemical_formula_reduced": "AgH14C3S3N6ClO",
+      "chemical_formula_anonymous": "A14B6C3D3EFG",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          4.438269887249414,
+          0,
+          0
+        ],
+        [
+          0,
+          0.0037263506973483906,
+          0
+        ],
+        [
+          0,
+          0,
+          2.879535498740032
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ],
+        [
+          0.9116303665281374,
+          0.9116303665281374,
+          0.9116303665281374
+        ]
+      ],
+      "nsites": 29,
+      "species_at_sites": [
+        "Ag",
+        "C",
+        "C",
+        "C",
+        "Cl",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "H",
+        "N",
+        "N",
+        "N",
+        "N",
+        "N",
+        "N",
+        "O",
+        "S",
+        "S",
+        "S"
+      ],
+      "species": [
+        {
+          "name": "Ag",
+          "chemical_symbols": [
+            "Ag"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "C",
+          "chemical_symbols": [
+            "C"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Cl",
+          "chemical_symbols": [
+            "Cl"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "H",
+          "chemical_symbols": [
+            "H"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "N",
+          "chemical_symbols": [
+            "N"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "S",
+          "chemical_symbols": [
+            "S"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ag-C-Cl-H-N-O-S"
+    }
+  },
+  {
+    "id": "mpf_3803",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.943000",
+      "elements": [
+        "Ba",
+        "Ce",
+        "Fe",
+        "H",
+        "Na",
+        "O",
+        "Si",
+        "Ti"
+      ],
+      "nelements": 8,
+      "elements_ratios": [
+        0.045454545454545456,
+        0.045454545454545456,
+        0.022727272727272728,
+        0.022727272727272728,
+        0.022727272727272728,
+        0.6136363636363636,
+        0.18181818181818182,
+        0.045454545454545456
+      ],
+      "chemical_formula_descriptive": "Ba2NaCe2Ti2FeSi8HO27",
+      "chemical_formula_reduced": "Ba2NaCe2Ti2FeSi8HO27",
+      "chemical_formula_anonymous": "A27B8C2D2E2FGH",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          3.6001175409341037,
+          0,
+          0
+        ],
+        [
+          0,
+          4.438478811675378,
+          0
+        ],
+        [
+          0,
+          0,
+          7.718814146224643
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ],
+        [
+          0.8417172367979051,
+          0.8417172367979051,
+          0.8417172367979051
+        ]
+      ],
+      "nsites": 44,
+      "species_at_sites": [
+        "Ba",
+        "Ba",
+        "Ce",
+        "Ce",
+        "Fe",
+        "H",
+        "Na",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Ti",
+        "Ti"
+      ],
+      "species": [
+        {
+          "name": "Ba",
+          "chemical_symbols": [
+            "Ba"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Ce",
+          "chemical_symbols": [
+            "Ce"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Fe",
+          "chemical_symbols": [
+            "Fe"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "H",
+          "chemical_symbols": [
+            "H"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Na",
+          "chemical_symbols": [
+            "Na"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Si",
+          "chemical_symbols": [
+            "Si"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Ti",
+          "chemical_symbols": [
+            "Ti"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ba-Ce-Fe-H-Na-O-Si-Ti"
+    }
+  },
+  {
+    "id": "mpf_3819",
+    "type": "structures",
+    "attributes": {
+      "last_modified": "2019-06-08T05:13:37.945000",
+      "elements": [
+        "Ba",
+        "F",
+        "H",
+        "Mn",
+        "Na",
+        "O",
+        "Re",
+        "Si",
+        "Ti"
+      ],
+      "nelements": 9,
+      "elements_ratios": [
+        0.045454545454545456,
+        0.022727272727272728,
+        0.022727272727272728,
+        0.022727272727272728,
+        0.022727272727272728,
+        0.5909090909090909,
+        0.045454545454545456,
+        0.18181818181818182,
+        0.045454545454545456
+      ],
+      "chemical_formula_descriptive": "Ba2NaTi2MnRe2Si8HO26F",
+      "chemical_formula_reduced": "Ba2NaTi2MnRe2Si8HO26F",
+      "chemical_formula_anonymous": "A26B8C2D2E2FGHI",
+      "dimension_types": [
+        1,
+        1,
+        1
+      ],
+      "lattice_vectors": [
+        [
+          0.541264110585089,
+          0,
+          0
+        ],
+        [
+          0,
+          0.5211563701526833,
+          0
+        ],
+        [
+          0,
+          0,
+          4.063577553377723
+        ]
+      ],
+      "cartesian_site_positions": [
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ],
+        [
+          0.24056412649964642,
+          0.24056412649964642,
+          0.24056412649964642
+        ]
+      ],
+      "nsites": 44,
+      "species_at_sites": [
+        "Ba",
+        "Ba",
+        "F",
+        "H",
+        "Mn",
+        "Na",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "O",
+        "Re",
+        "Re",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Si",
+        "Ti",
+        "Ti"
+      ],
+      "species": [
+        {
+          "name": "Ba",
+          "chemical_symbols": [
+            "Ba"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "F",
+          "chemical_symbols": [
+            "F"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "H",
+          "chemical_symbols": [
+            "H"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Mn",
+          "chemical_symbols": [
+            "Mn"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Na",
+          "chemical_symbols": [
+            "Na"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "O",
+          "chemical_symbols": [
+            "O"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Re",
+          "chemical_symbols": [
+            "Re"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Si",
+          "chemical_symbols": [
+            "Si"
+          ],
+          "concentration": [
+            1
+          ]
+        },
+        {
+          "name": "Ti",
+          "chemical_symbols": [
+            "Ti"
+          ],
+          "concentration": [
+            1
+          ]
+        }
+      ],
+      "structure_features": [],
+      "_exmpl_chemsys": "Ba-F-H-Mn-Na-O-Re-Si-Ti"
+    },
+    "relationships": {
+      "references": {
+        "data": [
+          {
+            "id": "dummy/2019",
+            "type": "references"
+          }
+        ]
+      }
+    }
+  }
+]
\ No newline at end of file
diff --git a/tests/adapters/structures/special_species.json b/tests/adapters/structures/special_species.json
new file mode 100644
index 00000000..530aee1e
--- /dev/null
+++ b/tests/adapters/structures/special_species.json
@@ -0,0 +1,140 @@
+[
+  {
+    "id": "mpf_1",
+    "type": "structures",
+    "attributes": {
+        "last_modified": "2019-06-08T05:13:37.331000",
+        "elements": [
+        "Ac"
+        ],
+        "nelements": 1,
+        "elements_ratios": [
+        1
+        ],
+        "chemical_formula_descriptive": "Ac",
+        "chemical_formula_reduced": "Ac",
+        "chemical_formula_anonymous": "A",
+        "dimension_types": [
+        1,
+        1,
+        1
+        ],
+        "lattice_vectors": [
+        [
+            1.2503264826932692,
+            0,
+            0
+        ],
+        [
+            0,
+            9.888509716321765,
+            0
+        ],
+        [
+            0,
+            0,
+            0.2972637673241818
+        ]
+        ],
+        "cartesian_site_positions": [
+        [
+            0.17570227444196573,
+            0.17570227444196573,
+            0.17570227444196573
+        ]
+        ],
+        "nsites": 1,
+        "species_at_sites": [
+        "Ac"
+        ],
+        "species": [
+        {
+            "name": "Ac",
+            "chemical_symbols": ["Ac", "vacancy"],
+            "concentration": [0.9, 0.1]
+        }
+        ],
+        "structure_features": ["disorder"],
+        "_exmpl_chemsys": "Ac"
+    },
+    "relationships": {
+        "references": {
+        "data": [
+            {
+            "id": "dijkstra1968",
+            "type": "references"
+            }
+        ]
+        }
+    }
+  },
+  {
+    "id": "mpf_1",
+    "type": "structures",
+    "attributes": {
+        "last_modified": "2019-06-08T05:13:37.331000",
+        "elements": [
+        "Ac"
+        ],
+        "nelements": 1,
+        "elements_ratios": [
+        1
+        ],
+        "chemical_formula_descriptive": "Ac",
+        "chemical_formula_reduced": "Ac",
+        "chemical_formula_anonymous": "A",
+        "dimension_types": [
+        1,
+        1,
+        1
+        ],
+        "lattice_vectors": [
+        [
+            1.2503264826932692,
+            0,
+            0
+        ],
+        [
+            0,
+            9.888509716321765,
+            0
+        ],
+        [
+            0,
+            0,
+            0.2972637673241818
+        ]
+        ],
+        "cartesian_site_positions": [
+        [
+            0.17570227444196573,
+            0.17570227444196573,
+            0.17570227444196573
+        ]
+        ],
+        "nsites": 1,
+        "species_at_sites": [
+        "AcX"
+        ],
+        "species": [
+        {
+            "name": "AcX",
+            "chemical_symbols": ["Ac", "X"],
+            "concentration": [0.9, 0.1]
+        }
+        ],
+        "structure_features": ["disorder"],
+        "_exmpl_chemsys": "AcX"
+    },
+    "relationships": {
+        "references": {
+        "data": [
+            {
+            "id": "dijkstra1968",
+            "type": "references"
+            }
+        ]
+        }
+    }
+  }
+]
\ No newline at end of file
diff --git a/tests/adapters/structures/test_aiida.py b/tests/adapters/structures/test_aiida.py
new file mode 100644
index 00000000..0f5ba9db
--- /dev/null
+++ b/tests/adapters/structures/test_aiida.py
@@ -0,0 +1,62 @@
+# pylint: disable=import-error
+import json
+from pathlib import Path
+import re
+from typing import List
+
+import pytest
+
+from .utils import get_min_ver
+
+min_ver = get_min_ver("aiida-core")
+aiida = pytest.importorskip(
+    "aiida",
+    minversion=min_ver,
+    reason=f"aiida-core must be installed with minimum version {min_ver} for these tests to"
+    " be able to run",
+)
+
+from aiida import load_profile
+
+load_profile()
+
+from aiida.orm import StructureData
+
+from optimade.models.structures import Periodicity
+
+from optimade.adapters import Structure
+from optimade.adapters.exceptions import ConversionError
+from optimade.adapters.structures.aiida import get_aiida_structure_data
+
+
+def test_successful_conversion(RAW_STRUCTURES):
+    """Make sure its possible to convert"""
+    for structure in RAW_STRUCTURES:
+        assert isinstance(get_aiida_structure_data(Structure(structure)), StructureData)
+
+
+def test_null_positions(null_position_structure):
+    """Make sure null positions are handled"""
+    assert isinstance(get_aiida_structure_data(null_position_structure), StructureData)
+
+
+def test_null_lattice_vectors(null_lattice_vector_structure):
+    """Make sure null lattice vectors are handled"""
+    assert isinstance(
+        get_aiida_structure_data(null_lattice_vector_structure), StructureData
+    )
+
+
+def test_special_species(SPECIAL_SPECIES_STRUCTURES):
+    """Make sure vacancies and non-chemical symbols ("X") are handled"""
+    for special_structure in SPECIAL_SPECIES_STRUCTURES:
+        structure = Structure(special_structure)
+
+        assert isinstance(get_aiida_structure_data(structure), StructureData)
+
+        if "vacancy" in structure.attributes.species[0].chemical_symbols:
+            assert get_aiida_structure_data(structure).has_vacancies
+            assert not get_aiida_structure_data(structure).is_alloy
+        else:
+            assert not get_aiida_structure_data(structure).has_vacancies
+            assert get_aiida_structure_data(structure).is_alloy
diff --git a/tests/adapters/structures/test_ase.py b/tests/adapters/structures/test_ase.py
new file mode 100644
index 00000000..f55edfcc
--- /dev/null
+++ b/tests/adapters/structures/test_ase.py
@@ -0,0 +1,53 @@
+# pylint: disable=import-error
+import json
+from pathlib import Path
+import re
+from typing import List
+
+import pytest
+
+from .utils import get_min_ver
+
+min_ver = get_min_ver("ase")
+ase = pytest.importorskip(
+    "ase",
+    minversion=min_ver,
+    reason=f"ase must be installed with minimum version {min_ver} for these tests to"
+    " be able to run",
+)
+
+from ase import Atoms
+
+from optimade.models.structures import Periodicity
+
+from optimade.adapters import Structure
+from optimade.adapters.exceptions import ConversionError
+from optimade.adapters.structures.ase import get_ase_atoms
+
+
+def test_successful_conversion(RAW_STRUCTURES):
+    """Make sure its possible to convert"""
+    for structure in RAW_STRUCTURES:
+        assert isinstance(get_ase_atoms(Structure(structure)), Atoms)
+
+
+def test_null_positions(null_position_structure):
+    """Make sure null positions are handled"""
+    assert isinstance(get_ase_atoms(null_position_structure), Atoms)
+
+
+def test_null_lattice_vectors(null_lattice_vector_structure):
+    """Make sure null lattice vectors are handled"""
+    assert isinstance(get_ase_atoms(null_lattice_vector_structure), Atoms)
+
+
+def test_special_species(SPECIAL_SPECIES_STRUCTURES):
+    """Make sure vacancies and non-chemical symbols ("X") are handled"""
+    for special_structure in SPECIAL_SPECIES_STRUCTURES:
+        structure = Structure(special_structure)
+
+        with pytest.raises(
+            ConversionError,
+            match="ASE cannot handle structures with partial occupancies",
+        ):
+            get_ase_atoms(structure)
diff --git a/tests/adapters/structures/test_cif.py b/tests/adapters/structures/test_cif.py
new file mode 100644
index 00000000..12061a29
--- /dev/null
+++ b/tests/adapters/structures/test_cif.py
@@ -0,0 +1,44 @@
+# pylint: disable=import-error
+import json
+from pathlib import Path
+import re
+from typing import List
+
+import pytest
+
+from .utils import get_min_ver
+
+min_ver = get_min_ver("numpy")
+numpy = pytest.importorskip(
+    "numpy",
+    minversion=min_ver,
+    reason=f"numpy must be installed with minimum version {min_ver} for these tests to"
+    " be able to run",
+)
+
+from optimade.adapters import Structure
+from optimade.adapters.structures.cif import get_cif
+
+
+def test_successful_conversion(RAW_STRUCTURES):
+    """Make sure its possible to convert"""
+    for structure in RAW_STRUCTURES:
+        assert isinstance(get_cif(Structure(structure)), str)
+
+
+def test_null_positions(null_position_structure):
+    """Make sure null positions are handled"""
+    assert isinstance(get_cif(null_position_structure), str)
+
+
+def test_null_lattice_vectors(null_lattice_vector_structure):
+    """Make sure null lattice vectors are handled"""
+    assert isinstance(get_cif(null_lattice_vector_structure), str)
+
+
+def test_special_species(SPECIAL_SPECIES_STRUCTURES):
+    """Make sure vacancies and non-chemical symbols ("X") are handled"""
+    for special_structure in SPECIAL_SPECIES_STRUCTURES:
+        structure = Structure(special_structure)
+
+        assert isinstance(get_cif(structure), str)
diff --git a/tests/adapters/structures/test_pdb.py b/tests/adapters/structures/test_pdb.py
new file mode 100644
index 00000000..e4aed1f4
--- /dev/null
+++ b/tests/adapters/structures/test_pdb.py
@@ -0,0 +1,44 @@
+# pylint: disable=import-error
+import json
+from pathlib import Path
+import re
+from typing import List
+
+import pytest
+
+from .utils import get_min_ver
+
+min_ver = get_min_ver("numpy")
+numpy = pytest.importorskip(
+    "numpy",
+    minversion=min_ver,
+    reason=f"numpy must be installed with minimum version {min_ver} for these tests to"
+    " be able to run",
+)
+
+from optimade.adapters import Structure
+from optimade.adapters.structures.proteindatabank import get_pdb
+
+
+def test_successful_conversion(RAW_STRUCTURES):
+    """Make sure its possible to convert"""
+    for structure in RAW_STRUCTURES:
+        assert isinstance(get_pdb(Structure(structure)), str)
+
+
+def test_null_positions(null_position_structure):
+    """Make sure null positions are handled"""
+    assert isinstance(get_pdb(null_position_structure), str)
+
+
+def test_null_lattice_vectors(null_lattice_vector_structure):
+    """Make sure null lattice vectors are handled"""
+    assert isinstance(get_pdb(null_lattice_vector_structure), str)
+
+
+def test_special_species(SPECIAL_SPECIES_STRUCTURES):
+    """Make sure vacancies and non-chemical symbols ("X") are handled"""
+    for special_structure in SPECIAL_SPECIES_STRUCTURES:
+        structure = Structure(special_structure)
+
+        assert isinstance(get_pdb(structure), str)
diff --git a/tests/adapters/structures/test_pdbx_mmcif.py b/tests/adapters/structures/test_pdbx_mmcif.py
new file mode 100644
index 00000000..6231baf6
--- /dev/null
+++ b/tests/adapters/structures/test_pdbx_mmcif.py
@@ -0,0 +1,44 @@
+# pylint: disable=import-error
+import json
+from pathlib import Path
+import re
+from typing import List
+
+import pytest
+
+from .utils import get_min_ver
+
+min_ver = get_min_ver("numpy")
+numpy = pytest.importorskip(
+    "numpy",
+    minversion=min_ver,
+    reason=f"numpy must be installed with minimum version {min_ver} for these tests to"
+    " be able to run",
+)
+
+from optimade.adapters import Structure
+from optimade.adapters.structures.proteindatabank import get_pdbx_mmcif
+
+
+def test_successful_conversion(RAW_STRUCTURES):
+    """Make sure its possible to convert"""
+    for structure in RAW_STRUCTURES:
+        assert isinstance(get_pdbx_mmcif(Structure(structure)), str)
+
+
+def test_null_positions(null_position_structure):
+    """Make sure null positions are handled"""
+    assert isinstance(get_pdbx_mmcif(null_position_structure), str)
+
+
+def test_null_lattice_vectors(null_lattice_vector_structure):
+    """Make sure null lattice vectors are handled"""
+    assert isinstance(get_pdbx_mmcif(null_lattice_vector_structure), str)
+
+
+def test_special_species(SPECIAL_SPECIES_STRUCTURES):
+    """Make sure vacancies and non-chemical symbols ("X") are handled"""
+    for special_structure in SPECIAL_SPECIES_STRUCTURES:
+        structure = Structure(special_structure)
+
+        assert isinstance(get_pdbx_mmcif(structure), str)
diff --git a/tests/adapters/structures/test_pymatgen.py b/tests/adapters/structures/test_pymatgen.py
new file mode 100644
index 00000000..ee22823c
--- /dev/null
+++ b/tests/adapters/structures/test_pymatgen.py
@@ -0,0 +1,63 @@
+# pylint: disable=import-error
+import re
+from typing import List
+
+import pytest
+
+from .utils import get_min_ver
+
+min_ver = get_min_ver("pymatgen")
+pymatgen = pytest.importorskip(
+    "pymatgen",
+    minversion=min_ver,
+    reason=f"pymatgen must be installed with minimum version {min_ver} for these tests to"
+    " be able to run",
+)
+
+from pymatgen import Molecule, Structure as PymatgenStructure
+
+from optimade.models.structures import Periodicity
+
+from optimade.adapters import Structure
+from optimade.adapters.structures.pymatgen import (
+    get_pymatgen,
+    _get_structure,
+    _get_molecule,
+)
+
+
+def test_successful_conversion(RAW_STRUCTURES):
+    """Make sure its possible to convert"""
+    for structure in RAW_STRUCTURES:
+        assert isinstance(
+            get_pymatgen(Structure(structure)), (PymatgenStructure, Molecule)
+        )
+
+
+def test_successful_conversion_structure(structure):
+    """Make sure its possible to convert to pymatgen Structure"""
+    assert isinstance(_get_structure(structure), PymatgenStructure)
+    assert isinstance(get_pymatgen(structure), PymatgenStructure)
+
+
+def test_null_lattice_vectors(null_lattice_vector_structure):
+    """Make sure null lattice vectors are handled
+
+    This also respresents a test for successful conversion to pymatgen Molecule
+    """
+    assert isinstance(_get_molecule(null_lattice_vector_structure), Molecule)
+    assert isinstance(get_pymatgen(null_lattice_vector_structure), Molecule)
+
+
+def test_null_positions(null_position_structure):
+    """Make sure null positions are handled"""
+    assert isinstance(get_pymatgen(null_position_structure), PymatgenStructure)
+    assert isinstance(_get_structure(null_position_structure), PymatgenStructure)
+    assert isinstance(_get_molecule(null_position_structure), Molecule)
+
+
+def test_special_species(SPECIAL_SPECIES_STRUCTURES):
+    """Make sure vacancies and non-chemical symbols ("X") are handled"""
+    for special_structure in SPECIAL_SPECIES_STRUCTURES:
+        structure = Structure(special_structure)
+        assert isinstance(get_pymatgen(structure), PymatgenStructure)
diff --git a/tests/adapters/structures/test_structures.py b/tests/adapters/structures/test_structures.py
new file mode 100644
index 00000000..d058d093
--- /dev/null
+++ b/tests/adapters/structures/test_structures.py
@@ -0,0 +1,144 @@
+import pytest
+
+from optimade.adapters import Structure
+from optimade.models import StructureResource
+
+try:
+    import aiida
+    import ase
+    import numpy
+    import pymatgen
+except ImportError:
+    all_modules_found = False
+else:
+    all_modules_found = True
+
+
+class TestStructure:
+    """Test Structure adapter"""
+
+    def test_instantiate(self, RAW_STRUCTURES):
+        """Try instantiating Structure for all raw test structures"""
+        for structure in RAW_STRUCTURES:
+            new_Structure = Structure(structure)
+            assert isinstance(new_Structure.entry, StructureResource)
+
+    def test_setting_entry(self, capfd, RAW_STRUCTURES):
+        """Make sure entry can only be set once"""
+        structure = Structure(RAW_STRUCTURES[0])
+        structure.entry = RAW_STRUCTURES[1]
+        captured = capfd.readouterr()
+        assert "entry can only be set once and is already set." in captured.out
+
+    def test_convert(self, structure):
+        """Test convert() works
+        Choose currently known entry type - must be updated if no longer available.
+        """
+        if not structure._type_converters:
+            pytest.fail("_type_converters is seemingly empty. This should not be.")
+
+        chosen_type = "cif"
+        if chosen_type not in structure._type_converters:
+            pytest.fail(
+                f"{chosen_type} not found in _type_converters: {structure._type_converters} - "
+                "please update test tests/adapters/structures/test_structures.py:TestStructure."
+                "test_convert()"
+            )
+
+        converted_structure = structure.convert(chosen_type)
+        assert isinstance(converted_structure, (str, None.__class__))
+        assert converted_structure == structure._converted[chosen_type]
+
+    def test_convert_wrong_format(self, structure):
+        """Test AttributeError is raised if format does not exist"""
+        nonexistant_format = 0
+        right_wrong_format_found = False
+        while not right_wrong_format_found:
+            if str(nonexistant_format) not in structure._type_converters:
+                nonexistant_format = str(nonexistant_format)
+                right_wrong_format_found = True
+            else:
+                nonexistant_format += 1
+
+        with pytest.raises(
+            AttributeError,
+            match=f"Non-valid entry type to convert to: {nonexistant_format}",
+        ):
+            structure.convert(nonexistant_format)
+
+    def test_getattr_order(self, structure):
+        """The order of getting an attribute should be:
+        1. `as_<entry type format>`
+        2. `<entry type attribute>`
+        3. `<entry type attributes attributes>`
+        4. `raise AttributeError` with custom message
+        """
+        # If passing attribute starting with `as_`, it should call `self.convert()`
+        with pytest.raises(
+            AttributeError, match=f"Non-valid entry type to convert to: "
+        ):
+            structure.as_
+
+        # If passing valid StructureResource attribute, it should return said attribute
+        # Test also nested attributes with `getattr()`.
+        for attribute, attribute_type in (
+            ("id", str),
+            ("species", list),
+            ("attributes.species", list),
+        ):
+            assert isinstance(getattr(structure, attribute), attribute_type)
+
+        # Otherwise, it should raise AttributeError
+        for attribute in ("nonexistant_attribute", "attributes.nonexistant_attribute"):
+            with pytest.raises(AttributeError, match=f"Unknown attribute: {attribute}"):
+                getattr(structure, attribute)
+
+    @pytest.mark.skipif(
+        all_modules_found,
+        reason="This test checks what happens if a conversion-dependent module cannot be found. "
+        "All could be found, i.e., it has no meaning.",
+    )
+    def test_no_module_conversion(self, structure):
+        """Make sure a warnings is raised and None is returned for conversions with non-existing modules"""
+        import importlib
+
+        CONVERSION_MAPPING = {
+            "aiida": ["aiida_structuredata"],
+            "ase": ["ase"],
+            "numpy": ["cif", "pdb", "pdbx_mmcif"],
+            "pymatgen": ["pymatgen"],
+        }
+
+        modules_to_test = []
+        for module in ("aiida", "ase", "numpy", "pymatgen"):
+            try:
+                importlib.import_module(module)
+            except (ImportError, ModuleNotFoundError):
+                modules_to_test.append(module)
+
+        if not modules_to_test:
+            pytest.fail(
+                "No modules found to test - it seems all modules are installed."
+            )
+
+        for module in modules_to_test:
+            for conversion_function in CONVERSION_MAPPING[module]:
+                with pytest.warns(
+                    UserWarning, match="not found, cannot convert structure to"
+                ):
+                    converted_structure = structure.convert(conversion_function)
+                assert converted_structure is None
+
+    def test_common_converters(self, raw_structure, RAW_STRUCTURES):
+        """Test common converters"""
+        structure = Structure(raw_structure)
+
+        assert structure.as_json == StructureResource(**raw_structure).json()
+        assert structure.as_dict == StructureResource(**raw_structure).dict()
+
+        # Since calling .dict() and .json() will return also all default-valued properties,
+        # the raw structure should at least be a sub-set of the resource's full list of properties.
+        for raw_structure in RAW_STRUCTURES:
+            raw_structure_property_set = set(raw_structure.keys())
+            resource_property_set = set(Structure(raw_structure).as_dict.keys())
+            assert raw_structure_property_set.issubset(resource_property_set)
diff --git a/tests/adapters/structures/test_utils.py b/tests/adapters/structures/test_utils.py
new file mode 100644
index 00000000..e4a7eb17
--- /dev/null
+++ b/tests/adapters/structures/test_utils.py
@@ -0,0 +1,138 @@
+import math
+
+import pytest
+
+from .utils import get_min_ver
+
+min_ver = get_min_ver("numpy")
+numpy = pytest.importorskip(
+    "numpy",
+    minversion=min_ver,
+    reason=f"numpy must be installed with minimum version {min_ver} for these tests to"
+    " be able to run",
+)
+
+from optimade.models import StructureResource
+
+from optimade.adapters.exceptions import ConversionError
+from optimade.adapters.structures.utils import (
+    _pad_iter_of_iters,
+    cell_to_cellpar,
+    fractional_coordinates,
+    pad_cell,
+    pad_positions,
+    scaled_cell,
+)
+
+
+# TODO: Add tests for cell_to_cellpar, unit_vector, cellpar_to_cell
+
+
+def test_pad_positions(null_position_structure):
+    """Make sure None values in cartesian_site_positions are converted to padding float value"""
+    positions, padded_position = pad_positions(
+        null_position_structure.attributes.cartesian_site_positions
+    )
+
+    assert not any(value is None for vector in positions for value in vector)
+    assert padded_position
+
+    positions, padded_position = pad_positions(positions)
+
+    assert not any(value is None for vector in positions for value in vector)
+    assert not padded_position
+
+
+def test_pad_cell(null_lattice_vector_structure):
+    """Make sure None values in lattice_vectors are converted to padding float value"""
+    lattice_vectors, padded_cell = pad_cell(
+        null_lattice_vector_structure.attributes.lattice_vectors
+    )
+
+    assert not any(value is None for vector in lattice_vectors for value in vector)
+    assert padded_cell
+
+    lattice_vectors, padded_cell = pad_cell(lattice_vectors)
+
+    assert not any(value is None for vector in lattice_vectors for value in vector)
+    assert not padded_cell
+
+
+def test__pad_iter_of_iters():
+    """Test _pad_iter_of_iters"""
+    iterable = [(0.0,) * 3, (0.0,) * 3, (None,) * 3]
+
+    padded_iterable, padded_iterable_bool = pad_cell(iterable)
+
+    assert padded_iterable_bool
+    assert all(math.isnan(_) for _ in padded_iterable[-1])
+    for i in range(2):
+        assert padded_iterable[i] == (0.0,) * 3
+
+    for valid_padding_value in (3.0, 3, "3", "3.0"):
+        padded_iterable, padded_iterable_bool = pad_cell(iterable, valid_padding_value)
+
+        assert padded_iterable_bool
+        assert padded_iterable[-1] == (float(valid_padding_value),) * 3
+        for i in range(2):
+            assert padded_iterable[i] == (0.0,) * 3
+
+    # Since nan != nan, the above for-loop cannot be used for nan
+    valid_padding_value = "nan"
+    padded_iterable, padded_iterable_bool = pad_cell(iterable, valid_padding_value)
+
+    assert padded_iterable_bool
+    assert all(math.isnan(_) for _ in padded_iterable[-1])
+    assert all(math.isnan(_) for _ in (float(valid_padding_value),) * 3)
+    for i in range(2):
+        assert padded_iterable[i] == (0.0,) * 3
+
+    invalid_padding_value = "x"
+    padded_iterable, padded_iterable_bool = pad_cell(iterable, invalid_padding_value)
+
+    assert padded_iterable_bool
+    assert all(math.isnan(_) for _ in padded_iterable[-1])
+    for i in range(2):
+        assert padded_iterable[i] == (0.0,) * 3
+
+
+def test_scaled_cell_and_fractional_coordinates(structures):
+    """Make sure these two different calculations arrive at the same result"""
+    for structure in structures:
+        scale = scaled_cell(structure.lattice_vectors)
+        scale = numpy.asarray(scale)
+        cartesian_positions = numpy.asarray(structure.cartesian_site_positions)
+        scaled_fractional_positions = (scale.T @ cartesian_positions.T).T
+        for i in range(3):
+            scaled_fractional_positions[:, i] %= 1.0
+            scaled_fractional_positions[:, i] %= 1.0
+        scaled_fractional_positions = [
+            tuple(position) for position in scaled_fractional_positions
+        ]
+
+        calculated_fractional_positions = fractional_coordinates(
+            cell=structure.lattice_vectors,
+            cartesian_positions=structure.cartesian_site_positions,
+        )
+
+        for scaled_position, calculated_position in zip(
+            scaled_fractional_positions, calculated_fractional_positions
+        ):
+            assert scaled_position == pytest.approx(calculated_position)
+
+
+def test_scaled_cell_consistency(structure):
+    """Test scaled_cell's PDB-designated validation: inverse of det(SCALE) = Volume of cell"""
+    # Manual calculation of volume = |a_1 . (a_2 x a_3)|
+    a_1 = structure.lattice_vectors[0]
+    a_2 = structure.lattice_vectors[1]
+    a_3 = structure.lattice_vectors[2]
+    a_mid_0 = a_2[1] * a_3[2] - a_2[2] * a_3[1]
+    a_mid_1 = a_2[2] * a_3[0] - a_2[0] * a_3[2]
+    a_mid_2 = a_2[0] * a_3[1] - a_2[1] * a_3[0]
+    volume_from_cellpar = abs(a_1[0] * a_mid_0 + a_1[1] * a_mid_1 + a_1[2] * a_mid_2)
+
+    scale = scaled_cell(structure.lattice_vectors)
+    volume_from_scale = 1 / numpy.linalg.det(scale)
+
+    assert volume_from_scale == pytest.approx(volume_from_cellpar)
diff --git a/tests/adapters/structures/utils.py b/tests/adapters/structures/utils.py
new file mode 100644
index 00000000..fb889a6d
--- /dev/null
+++ b/tests/adapters/structures/utils.py
@@ -0,0 +1,14 @@
+from pathlib import Path
+import re
+
+
+def get_min_ver(dependency: str) -> str:
+    """Retrieve version of `dependency` from setup.py, raise if not found."""
+    setup_py = Path(__file__).parent.joinpath("../../../setup.py")
+    with open(setup_py, "r") as setup_file:
+        for line in setup_file.readlines():
+            min_ver = re.findall(fr'"{dependency}~=([0-9]+(\.[0-9]+){{,2}})"', line)
+            if min_ver:
+                return min_ver[0][0]
+        else:
+            raise RuntimeError(f"Cannot find {dependency} dependency in setup.py")