# TOP/ITP and FNC in RMC_POT This page describes the functionality implemented by the following two classes. - `RmcPotDatTop`: Creates and edits GROMACS-style `*.top` and `*.itp` files for RMC_POT. - `RmcPotDatFnc`: Creates and edits Fixed Neighbour Constraint (`*.fnc`) files for RMC_POT. TOP and FNC are not the same thing. - TOP/ITP describes molecular topology, bonds, angles, dihedrals, 1–4 pairs, explicit exclusions, virtual sites, and related information. - FNC specifies fixed-neighbour constraints that keep distances between final RMC atom indices within prescribed ranges. In RMC_POT, ordinary FNC (`fnc=1/2/3`) and flexible molecules using TOP (`fnc=4`) are distinct features. Ordinary FNC and flexible molecules are not used simultaneously. --- # 1. `RmcPotDatTop` ## 1.1 Supported TOP sections `RmcPotDatTop` handles the following sections recognized by RMC_POT. ```text [ moleculetype ] [ atoms ] [ pairs ] [ bonds ] [ angles ] [ dihedrals ] [ exclusions ] [ virtual_sites2 ] [ virtual_sites3 ] [ virtual_sites4 ] [ system ] [ molecules ] ``` It also handles preprocessor directives recognized by RMC_POT. ```text #include #ifdef #ifndef #else #endif ``` ## 1.2 Reading and writing files ```python from rmc_pot_dat_top import RmcPotDatTop top = RmcPotDatTop("sample", auto_write=False) ``` If the extension is omitted, the name becomes `sample.top`. An existing file is loaded during initialization. The main file operations are as follows. | Method | Function | |---|---| | `path` | Returns the current TOP/ITP path. | | `load()` | Loads an existing TOP/ITP file. | | `write()` | Validates and writes the current contents. | | `write(out_name)` | Writes to a different filename. | | `clear()` | Clears all stored contents. | `write()` calls `validate()` before saving. ## 1.3 Automatic saving With `auto_write=True`, changes are written after each setter or addition method. ```python top = RmcPotDatTop("sample", auto_write=True) top.add_comment("comment") ``` When creating multiple entries together, use `auto_write=False` to avoid saving intermediate states. ```python top = RmcPotDatTop("sample", auto_write=False) top.add_comment("comment", auto_write=False) top.set_system("sample system", auto_write=False) top.write() ``` ## 1.4 Low-level line and section operations Low-level methods are available for assembling TOP files directly. | Method | Function | |---|---| | `add_raw_line(line)` | Adds an arbitrary line outside sections. | | `add_comment(comment)` | Adds a comment line beginning with `;`. | | `add_section(name)` | Adds a new section. | | `add_section_line(section_name, line)` | Adds an arbitrary line to a specified section. | | `get_sections()` | Returns all sections. | | `get_sections(name)` | Returns sections with the specified name. | | `get_last_section(name)` | Returns the last section with the specified name. | | `remove_sections(name)` | Removes all sections with the specified name. | Use `add_section_line()` to preserve TOP lines that cannot be expressed using the dedicated methods. ## 1.5 Includes and conditional directives ```python top.add_include("molecule.itp", auto_write=False) top.add_ifdef("_DEUTERIUM", auto_write=False) top.add_raw_line("; deuterium definition", auto_write=False) top.add_else(auto_write=False) top.add_raw_line("; hydrogen definition", auto_write=False) top.add_endif(auto_write=False) ``` The corresponding methods are as follows. | Method | Output | |---|---| | `add_include(filename)` | `#include "filename"` | | `add_ifdef(symbol)` | `#ifdef symbol` | | `add_ifndef(symbol)` | `#ifndef symbol` | | `add_else()` | `#else` | | `add_endif()` | `#endif` | `validate()` checks matching `#ifdef`/`#ifndef` and `#endif` directives, as well as the position of `#else`. --- # 2. Molecular types and atoms in TOP ## 2.1 `[ moleculetype ]` ```python top.begin_molecule_type( name="SnI4", exclusions=3, auto_write=False, ) ``` `exclusions` corresponds to GROMACS `nrexcl`. An RMC_POT-specific `index_offset` may also be specified when necessary. ```python top.begin_molecule_type( name="MOL2", exclusions=3, index_offset=5000, auto_write=False, ) ``` ## 2.2 `[ atoms ]` ```python top.add_atom( serial=1, atom_type="Sn", residue_number=1, residue_name="SnI4", atom_name="Sn", charge_group=1, charge=0.0, mass=None, first_rmc_index=1, second_rmc_index=2, auto_write=False, ) ``` The line generated by this method has the following format. ```text serial type resnr residue atom cgnr charge [mass] ; first_rmc_index second_rmc_index ``` The two values after the semicolon are specific to RMC_POT. - `first_rmc_index`: RMC index of this local atom in the first molecule of the same molecular type. - `second_rmc_index`: RMC index of this local atom in the second molecule of the same molecular type. These are not molecule numbers. For each local atom, they record the RMC atom indices corresponding to the first and second molecular instances. ### Example index mapping for SnI4 In the first SnI4 representation in the RMC_POT manual, the mapping is as follows. | Local atom | First molecule | Second molecule | |---|---:|---:| | Sn | 1 | 2 | | I1 | 1001 | 1005 | | I2 | 1002 | 1006 | | I3 | 1003 | 1007 | | I4 | 1004 | 1008 | The corresponding `[ atoms ]` entries are written as follows. ```text 1 Sn 1 SnI4 Sn 1 0 ; 1 2 2 I 1 SnI4 I1 1 0 ; 1001 1005 3 I 1 SnI4 I2 1 0 ; 1002 1006 4 I 1 SnI4 I3 1 0 ; 1003 1007 5 I 1 SnI4 I4 1 0 ; 1004 1008 ``` ## 2.3 Virtual atoms ```python top.add_virtual_atom( serial=4, atom_type="VS", residue_number=1, residue_name="MOL", atom_name="VS", charge_group=1, charge=-1.0, mass=0.0, auto_write=False, ) ``` `add_virtual_atom()` adds an entry to `[ atoms ]` with RMC indices `0 0`. This indicates a virtual site rather than a real atom to RMC_POT. --- # 3. Interactions in TOP The atom numbers below are GROMACS local atom numbers within one molecule, not RMC atom indices. ## 3.1 `[ pairs ]` ```python top.add_pair( atom_i=1, atom_j=4, function_type=1, auto_write=False, ) ``` V/W parameters can also be specified explicitly when required by the force field. ```python top.add_pair( atom_i=1, atom_j=4, function_type=1, v_parameter=V_VALUE, w_parameter=W_VALUE, auto_write=False, ) ``` `v_parameter` and `w_parameter` must either both be specified or both be omitted. ## 3.2 `[ bonds ]` ```python top.add_bond( atom_i=1, atom_j=2, equilibrium_nm=B0_NM, force_constant=KB, sigma=SIGMA_BOND, auto_write=False, ) ``` - `equilibrium_nm`: Equilibrium bond distance in nm. - `force_constant`: Force constant of the harmonic bond potential. - `sigma`: RMC_POT weight, specified at the first occurrence of the required bond type. Replace `B0_NM`, `KB`, and `SIGMA_BOND` with values from the chosen force field or independently determined RMC_POT parameters. This class does not estimate these values. To place RMC-specific parameters after the semicolon, specify the following. ```python rmc_parameters_after_semicolon=True ``` ## 3.3 `[ angles ]` ```python top.add_angle( atom_i=1, atom_j=2, atom_k=3, equilibrium_degree=THETA0_DEG, force_constant=KA, sigma=SIGMA_ANGLE, auto_write=False, ) ``` The central atom is `atom_j`. ## 3.4 Periodic dihedrals ```python top.add_periodic_dihedral( atom_i=1, atom_j=2, atom_k=3, atom_l=4, equilibrium_degree=PHI0_DEG, force_constant=KPHI, multiplicity=N, sigma=SIGMA_DIHEDRAL, auto_write=False, ) ``` ## 3.5 Harmonic dihedrals ```python top.add_harmonic_dihedral( atom_i=1, atom_j=2, atom_k=3, atom_l=4, equilibrium_degree=XI0_DEG, force_constant=KXI, sigma=SIGMA_DIHEDRAL, auto_write=False, ) ``` ## 3.6 Ryckaert–Bellemans dihedral ```python top.add_rb_dihedral( atom_i=1, atom_j=2, atom_k=3, atom_l=4, coefficients=[C0, C1, C2, C3, C4, C5], sigma=SIGMA_DIHEDRAL, auto_write=False, ) ``` `coefficients` must contain exactly six values. ## 3.7 Explicit exclusions ```python top.add_exclusion( centre_atom=1, excluded_atoms=[2, 3, 4], auto_write=False, ) ``` Adds a central atom and its excluded partners to `[ exclusions ]`. --- # 4. Virtual sites ## 4.1 Virtual sites defined by two atoms ```python top.add_virtual_site2( site=4, atom_i=1, atom_j=2, a=A, host_atom=1, auto_write=False, ) ``` ## 4.2 Virtual sites defined by three atoms ```python top.add_virtual_site3( site=5, atom_i=1, atom_j=2, atom_k=3, function_type=1, parameters=[A, B], host_atom=1, auto_write=False, ) ``` The required number of `parameters` depends on `function_type`. | Function type | RMC_POT category | Parameters | |---:|---|---| | 1 | 3 | `a, b` | | 2 | 3fd | `a, d_nm` | | 3 | 3fad | `theta_degree, d_nm` | | 4 | 3out | `a, b, c_nm_inverse` | ## 4.3 Virtual sites defined by four atoms ```python top.add_virtual_site4( site=6, atom_i=1, atom_j=2, atom_k=3, atom_l=4, a=A, b=B, c_nm=C_NM, host_atom=1, auto_write=False, ) ``` --- # 5. `[ system ]` and `[ molecules ]` ```python top.set_system("SnI4 index mapping example", auto_write=False) top.set_molecules([ ("SnI4", 2), ], auto_write=False) ``` `set_system()` and `set_molecules()` replace existing sections with the same names. Molecule names in `[ molecules ]` must already be defined in `[ moleculetype ]`. --- # 6. High-level TOP generation methods ## 6.1 `add_molecule_template()` Use this when multiple molecules of the same type exist and their RMC atom indices follow a regular pattern. ```python atoms = [ { "serial": 1, "atom_type": "Sn", "residue_name": "SnI4", "atom_name": "Sn", "charge": 0.0, "mass": None, }, { "serial": 2, "atom_type": "I", "residue_name": "SnI4", "atom_name": "I1", "charge": 0.0, "mass": None, }, { "serial": 3, "atom_type": "I", "residue_name": "SnI4", "atom_name": "I2", "charge": 0.0, "mass": None, }, { "serial": 4, "atom_type": "I", "residue_name": "SnI4", "atom_name": "I3", "charge": 0.0, "mass": None, }, { "serial": 5, "atom_type": "I", "residue_name": "SnI4", "atom_name": "I4", "charge": 0.0, "mass": None, }, ] first_instance_indices = { 1: 1, 2: 1001, 3: 1002, 4: 1003, 5: 1004, } second_instance_indices = { 1: 2, 2: 1005, 3: 1006, 4: 1007, 5: 1008, } top.add_molecule_template( molecule_name="SnI4", atoms=atoms, first_instance_indices=first_instance_indices, second_instance_indices=second_instance_indices, exclusions=3, auto_write=False, ) ``` This example only demonstrates how to specify index mappings; it does not establish the validity of bond, charge, or nonbonded parameters. ### `first_instance_indices` and `second_instance_indices` Each dictionary maps a local atom number within a molecule to its RMC atom index. ```text local serial -> RMC atom index ``` Each dictionary contains the complete mapping for all local atoms in the first or second molecular instance, respectively. ### Entries that can be added together `add_molecule_template()` accepts the following arguments. - `atoms` - `pairs` - `bonds` - `angles` - `periodic_dihedrals` - `harmonic_dihedrals` - `rb_dihedrals` - `explicit_exclusions` Each interaction entry is a keyword-argument dictionary passed to the corresponding low-level method. ```python bonds = [ { "atom_i": 1, "atom_j": 2, "equilibrium_nm": B0_NM, "force_constant": KB, "sigma": SIGMA_BOND, }, ] ``` ## 6.2 `validate_rmc_index_progression()` Checks whether the index difference between the first and second instances remains constant for the third and subsequent instances. ```python RmcPotDatTop.validate_rmc_index_progression( first_instance=first_instance_indices, second_instance=second_instance_indices, all_instances=[ first_instance_indices, second_instance_indices, third_instance_indices, ], ) ``` The following relation is checked for each local atom. ```text index in instance n = first index + (n - 1) * (second index - first index) ``` If the mapping does not satisfy this condition, `add_molecule_template()` cannot enable RMC_POT to reconstruct the indices of subsequent molecules unambiguously. ## 6.3 `add_explicit_network_molecule()` This method enumerates an irregular continuous network as a single topology unit covering the entire cell. ```python top.add_explicit_network_molecule( molecule_name="NETWORK", atoms=atoms, exclusions=2, bonds=bonds, angles=angles, auto_write=False, ) ``` Each `atoms` entry may specify its final RMC index using `rmc_index`. ```python { "serial": 1, "rmc_index": 1, "atom_type": "Si", "residue_name": "NETWORK", "atom_name": "Si1", "charge": 0.0, "mass": None, } ``` If `rmc_index` is omitted, `serial` is used. This method automatically sets the following values. ```text first_rmc_index = rmc_index second_rmc_index = rmc_index [ system ] = molecule_name [ molecules ] = molecule_name 1 ``` This approach explicitly specifies all atoms in the cell and all required bonds, angles, and dihedrals. It does not arbitrarily split a continuous network, such as SiO2 glass, into independent small molecules. --- # 7. TOP validation `validate()` primarily checks the following. - Proper matching of conditional preprocessor directives. - Number and format of lines in `[ moleculetype ]`. - That `[ atoms ]` follows `[ moleculetype ]`. - Presence of first and second RMC indices in `[ atoms ]`. - Duplicate local serial numbers within a molecule. - That bonds, angles, dihedrals, etc. refer to existing local serial numbers. - Absence of duplicate `[ system ]` sections. - That molecule names in `[ molecules ]` have been defined. - That molecule counts are at least one. Validation checks TOP syntax and internal references, but not the physical validity of force-field parameters. --- # 8. `RmcPotDatFnc` ## 8.1 Information stored in FNC FNC stores two kinds of information. 1. `rmin`–`rmax` for each constraint type. 2. For each RMC atom index, the partner atom indices and their constraint types. Atom numbers in FNC are not local molecular numbers. They are 1-based final RMC atom indices corresponding to the final `*.cfg` file. ## 8.2 Initialization ```python from rmc_pot_dat_fnc import RmcPotDatFnc fnc = RmcPotDatFnc( "sample", atom_count=TOTAL_ATOM_COUNT, auto_write=False, ) ``` `atom_count` is the total number of atoms in the RMC structure. Initialization creates an empty neighbour list for every atom from 1 through `atom_count`. To load an existing FNC file, specify only its filename. ```python fnc = RmcPotDatFnc("existing.fnc", auto_write=False) ``` ## 8.3 File operations | Method | Function | |---|---| | `path` | Returns the current FNC path. | | `initialize(atom_count)` | Resets all internal state. | | `load()` | Loads an existing FNC file. | | `write()` | Validates and writes the FNC file. | | `write(out_name)` | Writes to a different filename. | | `set_title(title)` | Sets the header title. | | `get_title()` | Returns the title. | | `get_atom_count()` | Returns the total number of atoms. | --- # 9. FNC distance-constraint types ## 9.1 Setting one type at a time ```python fnc.set_distance_range( constraint_type=1, rmin=RMIN_1, rmax=RMAX_1, auto_write=False, ) ``` `constraint_type` values are added consecutively, starting at 1. ## 9.2 Setting multiple types at once Dictionary form: ```python fnc.set_distance_ranges( { 1: (RMIN_1, RMAX_1), 2: (RMIN_2, RMAX_2), }, auto_write=False, ) ``` List form: ```python fnc.set_distance_ranges( [ (RMIN_1, RMAX_1), (RMIN_2, RMAX_2), ], auto_write=False, ) ``` The first list element corresponds to constraint type 1, and the second to type 2. Retrieval: ```python ranges = fnc.get_distance_ranges() ``` This class does not infer distance ranges from structures or element names. Specify `rmin` and `rmax` explicitly based on the system being studied. --- # 10. Operations on FNC atom pairs ## 10.1 Adding one reciprocal pair ```python fnc.add_pair( atom_index1=ATOM_I, atom_index2=ATOM_J, constraint_type=1, auto_write=False, ) ``` `add_pair()` registers both directions simultaneously. ```text ATOM_I -> ATOM_J ATOM_J -> ATOM_I ``` ## 10.2 Adding multiple pairs at once ```python fnc.add_pairs( [ (ATOM_I, ATOM_J, 1), (ATOM_K, ATOM_L, 2), ], auto_write=False, ) ``` Each tuple has the following order. ```text (atom_index1, atom_index2, constraint_type) ``` ## 10.3 Adding a one-way neighbour entry ```python fnc.add_neighbour( atom_index=ATOM_I, neighbour_index=ATOM_J, constraint_type=1, reciprocal=False, auto_write=False, ) ``` Use `add_pair()` for ordinary reciprocal FNC pairs. `add_neighbour()` is a low-level operation. Setting `reciprocal=True` registers both directions. ## 10.4 Replacing the neighbour list of one atom ```python fnc.set_atom_neighbours( atom_index=ATOM_I, neighbours=[ (ATOM_J, 1), (ATOM_K, 2), ], auto_write=False, ) ``` Each entry has the following format. ```text (neighbour_atom_index, constraint_type) ``` This method replaces only the list belonging to the specified atom. It does not automatically update the partner atoms. Prefer `add_pair()` or `add_pairs()` when reciprocal registration is required. Retrieval: ```python neighbours = fnc.get_atom_neighbours(ATOM_I) ``` The return value is a list of `FncNeighbour` objects. ## 10.5 Removing a pair ```python fnc.remove_pair( ATOM_I, ATOM_J, reciprocal=True, auto_write=False, ) ``` With `reciprocal=True`, the pair is removed in both directions. ## 10.6 Removing all pairs for one atom ```python fnc.clear_atom_neighbours( ATOM_I, reciprocal=True, auto_write=False, ) ``` With `reciprocal=True`, reverse references stored by partner atoms are also removed. --- # 11. Expanding FNC templates ## 11.1 Expanding one instance Expands local molecular pairs into actual RMC atom indices. ```python local_pairs = [ (1, 2, 1), (1, 3, 1), ] local_to_rmc = { 1: RMC_INDEX_1, 2: RMC_INDEX_2, 3: RMC_INDEX_3, } fnc.add_template_instance( local_pairs=local_pairs, local_to_rmc=local_to_rmc, auto_write=False, ) ``` `local_pairs` has the following format. ```text (local_atom_1, local_atom_2, constraint_type) ``` `local_to_rmc` defines the following mapping. ```text local atom serial -> final RMC atom index ``` This method does not generate pairs from element names, coordinates, or distances. It only maps the supplied local pairs to the supplied RMC indices. ## 11.2 Expanding multiple instances ```python fnc.add_template_instances( local_pairs=local_pairs, instances=[ { 1: RMC_INDEX_A1, 2: RMC_INDEX_A2, 3: RMC_INDEX_A3, }, { 1: RMC_INDEX_B1, 2: RMC_INDEX_B2, 3: RMC_INDEX_B3, }, ], auto_write=False, ) ``` Unlike TOP's `first_instance_indices`/`second_instance_indices`, FNC's `add_template_instances()` requires explicit mappings for every instance. This is because the FNC file directly stores final RMC indices for all atoms. --- # 12. Complete-graph FNC constraints ```python fnc.add_group_complete_graph( atom_indices=[ATOM_1, ATOM_2, ATOM_3, ATOM_4], constraint_type=1, auto_write=False, ) ``` Registers every possible pair within the specified group as an FNC pair. For four atoms, the following six pairs are added. ```text 1-2 1-3 1-4 2-3 2-4 3-4 ``` This does not simply connect a central atom to surrounding atoms. It constrains every atom pair within the group, so use it only where appropriate. --- # 13. FNC validation and retrieval ## 13.1 `validate()` ```python fnc.validate() ``` Reciprocal registration is also checked by default. The main checks are as follows. - `atom_count` is at least 1. - Constraint types are consecutive, starting at 1. - Atom entries exist for all indices from 1 through `atom_count`. - Partner atom indices are within range. - Constraint types are defined. - No duplicate neighbour entries exist for the same atom. - Each FNC pair is registered in the reverse direction with the same type. Use the following only when validating special one-way data. ```python fnc.validate(require_reciprocal=False) ``` ## 13.2 Retrieving the pair list ```python pairs = fnc.as_pairs(unique=True) ``` With `unique=True`, each reciprocally registered pair is returned only once. ```text (i, j, constraint_type) ``` To retrieve both directions: ```python pairs = fnc.as_pairs(unique=False) ``` --- # 14. Basic procedure for creating an FNC file Replace the example distance ranges and atom indices with values appropriate to the target structure. ```python from rmc_pot_dat_fnc import RmcPotDatFnc fnc = RmcPotDatFnc( "sample", atom_count=TOTAL_ATOM_COUNT, auto_write=False, ) fnc.set_title("Fixed neighbours constraints (.FNC) file", auto_write=False) fnc.set_distance_ranges( [ (RMIN_1, RMAX_1), (RMIN_2, RMAX_2), ], auto_write=False, ) fnc.add_pairs( [ (ATOM_I, ATOM_J, 1), (ATOM_K, ATOM_L, 2), ], auto_write=False, ) fnc.validate() fnc.write() ``` --- # 15. Choosing between TOP and FNC | Purpose | Use | |---|---| | Ordinary FNC to keep atom-pair distances within specified ranges | `RmcPotDatFnc` | | Generate `*.fnc` for `fnc=1/2/3` | `RmcPotDatFnc` | | Bond, angle, and dihedral potentials for flexible molecules | `RmcPotDatTop` | | Specify molecular topology with `fnc=4` | `RmcPotDatTop` | | GROMACS-style topology for nonbonded potentials | `RmcPotDatTop` | | 1–4 pairs, exclusions, and virtual sites | `RmcPotDatTop` | | Handle multiple identical molecules with regular index arrangements | `add_molecule_template()` | | Enumerate an irregular network covering the entire cell as one unit | `add_explicit_network_molecule()` | | Expand the same local FNC pairs to multiple instances | `add_template_instances()` | It is important not to confuse ordinary FNC with flexible molecules defined through TOP. --- # 16. Implementation notes ## TOP - Atom numbers in `add_bond()` and similar methods are local serial numbers within a molecule. - Only the first and second indices at the end of `[ atoms ]` are RMC atom indices. - Bond lengths, force constants, charges, and sigma values are not determined automatically. - `validate()` checks syntax and referential consistency, not the physical validity of parameters. - Use `add_molecule_template()` only when RMC index arrangements follow a regular pattern. - Do not assume nonexistent small-molecule templates for irregular networks. ## FNC - All atom numbers are final RMC atom indices. - `set_atom_neighbours()` does not automatically create reverse references. - Use `add_pair()`/`add_pairs()` for ordinary reciprocal pairs. - `add_group_complete_graph()` constrains every atom pair within the group. - Distance ranges and pairs are not inferred automatically. - Validation, including reciprocal-registration checks, is performed before `write()`.