# Constructing an `Interchange`

An [`Interchange`] is most commonly constructed via the
[`Interchange.from_smirnoff()`] class method. `from_smirnoff()` takes an OpenFF
Toolkit [`ForceField`] and applies it to a molecular topology to construct an
`Interchange`. `ForceField` implements SMIRNOFF, the Open Force Field
Initiative's [next-generation force field format]. It uses SMIRKS codes instead
of atom types to describe the chemistry of each parameter, which both reduces
the number of parameters needed to accurately describe biomolecular systems and
makes augmenting and extending the force field much simpler.

The molecular topology is simply a description of the system that should be
parameterized. It is usually given as a list of OpenFF Toolkit
[`Molecule`] objects, though specialist users can pass in a [`Topology`]
instead. A `Molecule` describes a single molecule, including its atoms, formal
charges, and full connectivity information. `Molecule` objects can be
constructed in a [variety of ways] from most popular molecular data formats.
A `Molecule` describes chemistry directly, rather than in terms of a
molecular mechanics model; the `Interchange` and `ForceField` are responsible
for applying simulation parameters.

Positions and velocities can be applied to an `Interchange` directly via the
`.positions` and `.velocities` attributes. Likewise, box vectors can be applied
to the `.box` attribute. Positions and velocities for a system of *n* atoms are
taken as *n*×3 Numpy arrays, and boxes are described as 3×3 arrays of
box vectors. As with all numerical inputs to OpenFF software, OpenFF recommends
assigning units; see [](interchange_units).

:::{mermaid}

---

alt: "Flowchart describing the construction and use of an Interchange (See textual description below)"
align: center

---
flowchart LR
    OFFXML
    SMILES/SDF/PDB
    subgraph np [numpy]
        BoxVecs[Box vectors]
        Positions
        Velocities
    end
    subgraph tk [openff.toolkit]
        Molecule([Molecule])
        ForceField([ForceField])
    end
    subgraph int [openff.interchange]
        Interchange[(Interchange)]
        FromSmirnoff[["from_smirnoff()"]]
    end

    style tk fill:#2f9ed2,color:#fff,stroke:#555;
    style int fill:#ee4266,color:#fff,stroke:#555;
    style np fill:#eee,color:#000,stroke:#555;

    classDef default stroke:#555;

    classDef code font-family:cousine,font-size:10pt,font-weight:bold;
    class FromSmirnoff,Molecule,ForceField,Interchange,tk,int,np code

    OFFXML --> ForceField --> FromSmirnoff
    SMILES/SDF/PDB --> Molecule --> FromSmirnoff
    FromSmirnoff --> Interchange
    Positions -.-> Molecule
    Positions -.-> Interchange
    BoxVecs -.-> FromSmirnoff
    BoxVecs -.-> Interchange
    Velocities -...-> Interchange
:::

`from_smirnoff()` also includes some convenience tools for assigning box vectors
and positions. If every `Molecule` passed to `from_smirnoff()` has at least one
conformer defined, atom positions for the new `Interchange` are taken from the
zeroth conformer of each `Molecule`. This allows conformers generated by the
OpenFF Toolkit or taken from a structure file to be used with Interchange. Box
vectors can also be supplied to the `from_smirnoff()` method via the `box`
argument, or taken from the `Topology`. Thus, constructing a system consisting
of a single ethane molecule in a 10×10×10 Angstrom box with the Sage force field
can be done very simply:

```python
from openff.interchange import Interchange
from openff.toolkit import Molecule, ForceField
from openff.units import unit
import numpy as np

mol = Molecule.from_smiles("CC")
mol.generate_conformers()
sage = ForceField("openff-2.0.0.offxml")
cubic_box = unit.Quantity(30 * np.eye(3), unit.angstrom)

interchange = Interchange.from_smirnoff(topology=[mol], force_field=sage, box=cubic_box)
```

## Content of an Interchange object

An [`Interchange`](openff.interchange.components.interchange.Interchange) object
stores all the information known about a system; this includes its chemistry,
how that chemistry is represented by a force field, and how the system is
organized in 3D space. An `Interchange` object has four components:

1. **Topology**: Stores chemical information, such as connectivity and formal charges, independently of force field
2. **Collections**: Maps the chemical information to force field parameters. The Force Field itself is not stored in the `Interchange`
3. **Positions**: Cartesian co-ordinates of atoms
4. **Box vectors**: Periodicity information
5. **Velocities**: Cartesian velocities of atoms

None are strictly required; an `Interchange` object can be constructed containing none of the above components:

```python
from openff.interchange import Interchange

empty_interchange = Interchange()
assert empty_interchange.topology is None
assert empty_interchange.collections is {}
assert empty_interchange.positions is None
assert empty_interchange.box is None
assert empty_interchange.velocities is None
```

An empty `Interchange` is not useful in itself, but a meaningful object can be
constructed programmatically by building and assigning each component
piecemeal. `Interchange` provides a broad API for automated construction which
makes construction easier and less error prone in common cases.

### Topology

The molecular topology of an Interchange is stored as an OpenFF Toolkit
[`Topology`] object in the [`Interchange.topology`] property. This `Topology`
model is able to store rich chemical information (atoms connected by bonds with
fractional bond orders, bond and atom stereochemistry, aromaticity, etc.) or
minimal, molecular mechanics-focused representations (particles with masses
that are connected to each other) as needed.

A topology is technically optional, and any conversions requiring topological
information will fail if any required data is missing. However, most conversions
do require some information from the topology.

### Collections

Collections are classes that store force field information in a format that associates assigned
parameters with their source and allows parameters to be inspected and even
modified with the full power of SMIRKS-based direct chemical perception. They
are discussed further in [](collections.md)

Collections are optional, though any conversions requiring force field parameters
will fail if the necessary data are missing.

### Positions and velocities

Particle positions and velocities are each stored as a unit-tagged $N×3$ matrix,
where $N$ is the number of particles in the topology. For systems with no
virtual sites, $N$ is the number of atoms; for systems with virtual sites, $N$
is the number of atoms plus the number of virtual sites.

Positions and velocities are represented in nanometers and nanometers per
femtosecond respectively both internally and when reported. However, they can
be set with positions of other units, and can also be converted as desired.
Internally, Interchange uses `openff-units` to store units, but units from the
`openmm.units` or `unyt` packages are also supported. See [](interchange_units) for more details.

Interchange assumes positions and velocities without units are expressed in the
default units above. The setter will immediately tag the unitless vectors with
units; the internal state always has units explicitly associated with the
values.

Positions and velocities are optional, though any conversions requiring them
will fail if they are missing.

```pycon
>>> from openff.interchange import Interchange
>>> from openff.units import unit
>>> from openff.toolkit.topology import Molecule
>>> molecule = Molecule.from_smiles("CCO")
>>> molecule.generate_conformers(n_conformers=1)
>>> molecule.conformers[0]
Quantity(value=array([[ 0.88165321, -0.04478118, -0.01474324],
       [-0.58171004, -0.37572459,  0.05098497],
       [-1.35004062,  0.75806983,  0.17615782],
       [ 1.26504668,  0.17421359,  1.01224746],
       [ 1.01649295,  0.87054063, -0.60898906],
       [ 1.47635802, -0.89454965, -0.39185017],
       [-0.78535559, -0.99682774,  0.96832828],
       [-0.83550563, -1.00354494, -0.81588946],
       [-1.08693898,  1.51260405, -0.3762466 ]]), unit=angstrom)
>>> model = Interchange()
>>> model.positions is None
True
>>> model.positions = molecule.conformers[0]
>>> model.positions
<Quantity([[ 0.08816532 -0.00447812 -0.00147432]
 [-0.058171   -0.03757246  0.0050985 ]
 [-0.13500406  0.07580698  0.01761578]
 [ 0.12650467  0.01742136  0.10122475]
 [ 0.10164929  0.08705406 -0.06089891]
 [ 0.1476358  -0.08945496 -0.03918502]
 [-0.07853556 -0.09968277  0.09683283]
 [-0.08355056 -0.10035449 -0.08158895]
 [-0.1086939   0.1512604  -0.03762466]], 'nanometer')>
>>> model.positions.m_as(unit.angstrom)
array([[ 0.88165321, -0.04478118, -0.01474324],
       [-0.58171004, -0.37572459,  0.05098497],
       [-1.35004062,  0.75806983,  0.17615782],
       [ 1.26504668,  0.17421359,  1.01224746],
       [ 1.01649295,  0.87054063, -0.60898906],
       [ 1.47635802, -0.89454965, -0.39185017],
       [-0.78535559, -0.99682774,  0.96832828],
       [-0.83550563, -1.00354494, -0.81588946],
       [-1.08693898,  1.51260405, -0.3762466 ]])
```

### Box vectors

Information about the periodic box of a system is stored as a unit-tagged $3×3$
matrix, following conventional periodic box vectors and the implementation in
[`OpenMM`](http://docs.openmm.org/latest/userguide/theory/05_other_features.html#periodic-boundary-conditions).

Box vectors are represented with nanometers internally and when reported.
However, they can be set with box vectors of other units, and can also be
converted as desired.

If box vectors are passed to the setter without tagged units, nanometers will be
assumed. The setter will immediately tag the unitless vectors with units; the
internal state always has units explicitly associated with with the values.

If a $1×3$ matrix (array) is passed to the setter, it is assumed that these
values correspond to the lengths of a rectangular unit cell ($a_x$, $b_y$,
$c_z$).

Box vectors are optional; if it is `None` it is implied that the `Interchange`
object represents a non-periodic system. However, note that passing `None` to
the `box` argument of `Interchange.from_smirnoff()` may produce a periodic
`Interchange` if box vectors are present in the `Topology`.

```pycon

>>> from openff.interchange import Interchange
>>> from openff.units import unit
>>> import numpy as np
>>> model = Interchange()
>>> model.box is None
True
>>> model.box = np.eye(3) * 4 * unit.nanometer
>>> model.box
<Quantity([[4. 0. 0.]
 [0. 4. 0.]
 [0. 0. 4.]], 'nanometer')>
>>> model.box = [3, 4, 5]
<Quantity([[3. 0. 0.]
 [0. 4. 0.]
 [0. 0. 5.]], 'nanometer')>
>>> model.box = [28, 28, 28] * unit.angstrom
<Quantity([[2.8 0. 0.]
 [0. 2.8 0.]
 [0. 0. 2.8]], 'nanometer')>
>>> model.box.m_as(unit.angstrom)
array([[28.,  0.,  0.],
       [ 0., 28.,  0.],
       [ 0.,  0., 28.]])
```

[`Interchange`]: openff.interchange.Interchange
[`Interchange.from_smirnoff()`]: openff.interchange.Interchange.from_smirnoff
[`Interchange.topology`]: openff.interchange.Interchange.topology
[`ForceField`]: openff.toolkit.typing.engines.smirnoff.forcefield.ForceField
[`Topology`]: openff.toolkit.topology.Topology
[`Molecule`]: openff.toolkit.topology.Molecule
[next-generation force field format]: https://open-forcefield-toolkit.readthedocs.io/en/stable/users/smirnoff.html
[variety of ways]: https://open-forcefield-toolkit.readthedocs.io/en/latest/users/molecule_cookbook.html
