##
Symmetry restraints

You can restrain two groups of atoms to be the same during optimization of the objective function. This is achieved by adding the sum of squares of the differences between the equivalent distances (similar to distance RMS deviation) to the objective function being optimized. See Equation A.99.

After creating a `symmetry` object, you can call its `append` function to add additional pairs of groups. This allows some equivalent atoms to be weighted more strongly than others. Finally, add the `symmetry` object to the Restraints.symmetry list.

`symmetry(set1, set2, weight)`
Creates a new symmetry restraint which will constrain the interatomic distances in `set1` to be the same as in `set2`. (The `append` function takes the same parameters.) Both sets are just lists of atoms or objects which contain atoms, such as `Residue` or `selection` objects. Note that each set must contain the same number of atoms. Note also that the order is important. (If using `selection` objects, the atoms are always sorted in the same order as seen in the PDB file.)

See Section 2.2.13 for an example of using symmetry restraints with the `automodel` class.

Example: examples/commands/define_symmetry.py

      # Example for: model.symmetry.define()

      # This will force two copies of 1fas to have similar mainchain
      # conformation.

      from modeller import *
      from modeller.scripts import complete_pdb
      from modeller.optimizers import conjugate_gradients, molecular_dynamics

      log.level(1, 1, 1, 1, 0)
      env = environ()
      env.io.atom_files_directory = ['../atom_files']
      env.libs.topology.read(file='$(LIB)/top_heav.lib')
      env.libs.parameters.read(file='$(LIB)/par.lib')

      def defsym(mdl, seg1, seg2):
          sel1 = selection(mdl.residue_range(*seg1)).only_mainchain()
          sel2 = selection(mdl.residue_range(*seg2)).only_mainchain()
          mdl.restraints.symmetry.append(symmetry(sel1, sel2, 1.0))

      # Generate two copies of a segment:
      mdl = complete_pdb(env, '2abx', model_segment=('1:A', '74:B'))
      mdl.rename_segments(segment_ids=('A', 'B'), renumber_residues=(1, 1))

      myedat = energy_data(dynamic_sphere = False)
      atmsel = selection(mdl)
      atmsel.energy(edat=myedat)
      atmsel.randomize_xyz(deviation=6.0)
      # Define the two segments (chains in this case) to be identical:
      defsym(mdl, seg1=('1:A', '74:A'), seg2=('1:B', '74:B'))

      # Create optimizer objects
      cg = conjugate_gradients()
      md = molecular_dynamics(md_return='FINAL')

      # Make them identical by optimizing the initial randomized structure
      # without any other restraints:
      atmsel.energy(edat=myedat)
      mdl.write(file='define_symmetry-1.atm')
      cg.optimize(atmsel, max_iterations=300, edat=myedat)
      mdl.write(file='define_symmetry-2.atm')
      atmsel.energy(edat=myedat)

      # Now optimize with stereochemical restraints so that the
      # result is not so distorted a structure (still distorted
      # because optimization is not thorough):
      myedat.dynamic_sphere = True
      mdl.restraints.make(atmsel, restraint_type='stereo', spline_on_site=False,
                          edat=myedat)
      atmsel.randomize_xyz(deviation=3.0)
      for method in (cg, md, cg):
          method.optimize(atmsel, max_iterations=300, edat=myedat, output='REPORT')
      mdl.write(file='define_symmetry-3.atm')
      atmsel.energy(edat=myedat)

      # Report on symmetry violations
      mdl.restraints.symmetry.report(0.3)

      # Create a blank alignment so that superpose uses its 1:1 default
      aln = alignment(env)

      mdl = model(env, file='define_symmetry-3.atm', model_segment=('1:A', '74:A'))
      mdl2 = model(env, file='define_symmetry-3.atm', model_segment=('1:B', '74:B'))
      atmsel = selection(mdl).only_mainchain()
      atmsel.superpose(mdl2, aln)