diff --git a/actions/ost-compare-structures b/actions/ost-compare-structures
index a16c76f06ce60137bff4fc2f9ac9de443e672585..8364be451edcec48e10512166b97d8e03374e502 100644
--- a/actions/ost-compare-structures
+++ b/actions/ost-compare-structures
@@ -743,12 +743,11 @@ def _Main():
                 ost.LogInfo(str(lddt_settings).rstrip())
                 ost.LogInfo("===")
                 oligo_lddt_scorer = qs_scorer.GetOligoLDDTScorer(lddt_settings)
-                for scorer_index, lddt_scorer in enumerate(
-                        oligo_lddt_scorer.sc_lddt_scorers):
-                    # Get chains and renumber according to alignment (for lDDT)
-                    model_chain = lddt_scorer.model.chains[0].GetName()
-                    reference_chain = \
-                        lddt_scorer.references[0].chains[0].GetName()
+                for mapped_lddt_scorer in oligo_lddt_scorer.mapped_lddt_scorers:
+                    # Get data
+                    lddt_scorer = mapped_lddt_scorer.lddt_scorer
+                    model_chain = mapped_lddt_scorer.model_chain_name
+                    reference_chain = mapped_lddt_scorer.reference_chain_name
                     if skip_score:
                         ost.LogInfo(
                             " --> Skipping single chain lDDT because "
@@ -785,8 +784,7 @@ def _Main():
                                 "total_contacts": lddt_scorer.total_contacts}
                             if opts.save_per_residue_scores:
                                 per_residue_sc = \
-                                    oligo_lddt_scorer.GetPerResidueScores(
-                                        scorer_index)
+                                    mapped_lddt_scorer.GetPerResidueScores()
                                 ost.LogInfo("Per residue local lDDT (reference):")
                                 ost.LogInfo("Chain\tResidue Number\tResidue Name"
                                             "\tlDDT\tConserved Contacts\tTotal "
diff --git a/modules/doc/actions.rst b/modules/doc/actions.rst
index 0ef131cf61d3aca7469c06ab7d5bf670f1c14bde..c8502eff4a82e27b1702f57604b364a555b4d66c 100644
--- a/modules/doc/actions.rst
+++ b/modules/doc/actions.rst
@@ -37,7 +37,7 @@ future. Optionally, the local scores for lDDT can also be dumped to the output
 file. Additionally, cleaned up structures can be saved to the disk.
 The output file has following format:
 
-.. code-block:: python
+.. code-block:: none
 
   {
       "result": {
diff --git a/modules/io/doc/io.rst b/modules/io/doc/io.rst
index 0f5c04dd4663505c7fb7d21b79d7487b16d1846e..580c6650a8ead19afc4d372bc65b106fdb7027f2 100644
--- a/modules/io/doc/io.rst
+++ b/modules/io/doc/io.rst
@@ -351,7 +351,7 @@ before computing the lDDT scores it is required to pass parameter file based on
 Engh and Huber parameters, and on the atomic radii as defined in the Cambridge
 Structural Database. OpenStructure ships with default file called
 `stereo_chemical_props.txt` located in `$OST_ROOT/share/openstructure`
-directory. A function :function:`~ost.io.ReadStereoChemicalPropsFile` is used to
+directory. A function :func:`~ost.io.ReadStereoChemicalPropsFile` is used to
 read this file.
 
 
diff --git a/modules/mol/alg/pymod/qsscoring.py b/modules/mol/alg/pymod/qsscoring.py
index 8686ff05c3f4b2d7cc11949f401db30226c5b495..aa55203a930c5ee20372df794a38a2bfc2a9af55 100644
--- a/modules/mol/alg/pymod/qsscoring.py
+++ b/modules/mol/alg/pymod/qsscoring.py
@@ -355,8 +355,9 @@ class QSscorer:
     There will be one alignment for each mapped chain and they are ordered by
     their chain names in :attr:`qs_ent_1`.
 
-    The sequences of the alignments have views attached into
-    :attr:`QSscoreEntity.ent` of :attr:`qs_ent_1` and :attr:`qs_ent_2`.
+    The sequences of the alignments are named according to the chain name and
+    have views attached into :attr:`QSscoreEntity.ent` of :attr:`qs_ent_1` and
+    :attr:`qs_ent_2`.
 
     :getter: Computed on first use (cached)
     :type: :class:`list` of :class:`~ost.seq.AlignmentHandle`
@@ -832,6 +833,9 @@ def GetContacts(entity, calpha_only, dist_thr=12.0):
   # DONE
   return contacts
 
+###############################################################################
+# Oligo-lDDT scores
+###############################################################################
 
 class OligoLDDTScorer(object):
   """A simple class to calculate oligomeric lDDT score."""
@@ -855,7 +859,7 @@ class OligoLDDTScorer(object):
     self._lddt_ref = None
     self._lddt_mdl = None
     self._oligo_lddt_scorer = None
-    self._sc_lddt_scorers = None
+    self._mapped_lddt_scorers = None
 
   @property
   def lddt_ref(self):
@@ -890,48 +894,136 @@ class OligoLDDTScorer(object):
     return self._oligo_lddt_scorer
 
   @property
-  def sc_lddt_scorers(self):
-    if self._sc_lddt_scorers is None:
-      self._sc_lddt_scorers = list()
+  def mapped_lddt_scorers(self):
+    if self._mapped_lddt_scorers is None:
+      self._mapped_lddt_scorers = list()
       for aln in self.alignments:
-        # Get chains and renumber according to alignment (for lDDT)
-        reference = Renumber(
-          aln.GetSequence(0),
-          old_number_label=self._old_number_label).CreateFullView()
-        refseq = seq.CreateSequence(
-          "reference_renumbered",
-          aln.GetSequence(0).GetString())
-        refseq.AttachView(reference)
-        aln.AddSequence(refseq)
-        model = Renumber(
-          aln.GetSequence(1),
-          old_number_label=self._old_number_label).CreateFullView()
-        modelseq = seq.CreateSequence(
-          "model_renumbered",
-          aln.GetSequence(1).GetString())
-        modelseq.AttachView(model)
-        aln.AddSequence(modelseq)
-        # Filter to CA-only if desired (done after AttachView to not mess it up)
-        if self.calpha_only:
-          lddt_scorer = lDDTScorer(
-            references=[reference.Select('aname=CA')],
-            model=model.Select('aname=CA'),
-            settings=self.settings)
-        else:
-          lddt_scorer = lDDTScorer(
-            references=[reference],
-            model=model,
-            settings=self.settings)
-        lddt_scorer.alignment = aln  # a bit of a hack
-        self._sc_lddt_scorers.append(lddt_scorer)
-    return self._sc_lddt_scorers
-
-  def GetPerResidueScores(self, scorer_index):
+        mapped_lddt_scorer = MappedLDDTScorer(aln, self.calpha_only,
+                                              self.settings)
+        self._mapped_lddt_scorers.append(mapped_lddt_scorer)
+    return self._mapped_lddt_scorers
+
+  @property
+  def sc_lddt_scorers(self):
+    return [mls.lddt_scorer for mls in self.mapped_lddt_scorers]
+
+  @property
+  def sc_lddt(self):
+    if self._sc_lddt is None:
+      self._sc_lddt = list()
+      for lddt_scorer in self.sc_lddt_scorers:
+        try:
+          self._sc_lddt.append(lddt_scorer.global_score)
+        except Exception as ex:
+          LogError('Single chain lDDT failed:', str(ex))
+          self._sc_lddt.append(0.0)
+    return self._sc_lddt
+
+  @property
+  def weighted_lddt(self):
+    if self._weighted_lddt is None:
+      scores = [s.global_score for s in self.sc_lddt_scorers]
+      weights = [s.total_contacts for s in self.sc_lddt_scorers]
+      nominator = sum([s * w for s, w in zip(scores, weights)])
+      denominator = sum(weights)
+      if denominator > 0:
+        self._weighted_lddt = nominator / float(denominator)
+      else:
+        self._weighted_lddt = 0.0
+    return self._weighted_lddt
+
+  ##############################################################################
+  # Class internal helpers (anything that doesnt easily work without this class)
+  ##############################################################################
+
+  def _PrepareOligoEntities(self):
+    # simple wrapper to avoid code duplication
+    self._lddt_ref, self._lddt_mdl = _MergeAlignedChains(self.alignments,
+                                                         self.ref,
+                                                         self.mdl,
+                                                         self.calpha_only)
+
+
+class MappedLDDTScorer(object):
+  """A simple class to calculate a single-chain lDDT score on a given chain to
+  chain mapping as extracted from :class:`OligoLDDTScorer`.
+
+  :param alignment: Sets :attr:`alignment`
+  :param calpha_only: Sets :attr:`calpha_only`
+  :param settings: Sets :attr:`settings`
+  
+  .. attribute:: alignment
+
+    Alignment with two sequences named according to the mapped chains and with
+    views attached to both sequences (e.g. one of the items of
+    :attr:`QSScorer.alignments`).
+
+    The first sequence is assumed to be the reference and the second one the
+    model. Since the lDDT score is not symmetric (extra residues in model are
+    ignored), the order is important.
+
+    :type: :class:`~ost.seq.AlignmentHandle`
+  
+  .. attribute:: calpha_only
+
+    If true, restricts lDDT score to CA only.
+
+    :type: :class:`bool`
+
+  .. attribute:: settings
+
+    Settings to use for lDDT scoring.
+
+    :type: :class:`~mol.alg.lDDTSettings`
+
+  .. attribute:: lddt_scorer
+
+    lDDT Scorer object for the given chains.
+
+    :type: :class:`~mol.alg.lDDTScorer`
+
+  .. attribute:: reference_chain_name
+
+    Chain name of the reference.
+
+    :type: :class:`str`
+
+  .. attribute:: model_chain_name
+
+    Chain name of the model.
+
+    :type: :class:`str`
+  """
+  def __init__(self, alignment, calpha_only, settings):
+    # prepare fields
+    self.alignment = alignment
+    self.calpha_only = calpha_only
+    self.settings = settings
+    self.lddt_scorer = None # set in _InitScorer
+    self.reference_chain_name = alignment.sequences[0].name
+    self.model_chain_name = alignment.sequences[1].name
+    self._old_number_label = "old_num"
+    self._extended_alignment = None  # set in _InitScorer
+    # initialize lDDT scorer
+    self._InitScorer()
+
+  def GetPerResidueScores(self):
+    """
+    :return: Scores for each residue
+    :rtype:  :class:`list` of :class:`dict` with one item for each residue
+             existing in model and reference:
+
+             - "residue_number": Residue number in reference chain
+             - "residue_name": Residue number in reference chain
+             - "lddt": local lDDT
+             - "conserved_contacts": number of conserved contacts
+             - "total_contacts": total number of contacts
+    """
     scores = list()
     assigned_residues = list()
     # Make sure the score is calculated
-    self.sc_lddt_scorers[scorer_index].global_score
-    for col in self.sc_lddt_scorers[scorer_index].alignment:
+    self.lddt_scorer.global_score
+    for col in self._extended_alignment:
       if col[0] != "-" and col.GetResidue(3).IsValid():
         ref_res = col.GetResidue(0)
         mdl_res = col.GetResidue(1)
@@ -968,42 +1060,43 @@ class OligoLDDTScorer(object):
             "total_contacts": mdl_res_renum.GetFloatProp(self.settings.label + "_total")})
     return scores
 
-  @property
-  def sc_lddt(self):
-    if self._sc_lddt is None:
-      self._sc_lddt = list()
-      for lddt_scorer in self.sc_lddt_scorers:
-        try:
-          self._sc_lddt.append(lddt_scorer.global_score)
-        except Exception as ex:
-          LogError('Single chain lDDT failed:', str(ex))
-          self._sc_lddt.append(0.0)
-    return self._sc_lddt
-
-  @property
-  def weighted_lddt(self):
-    if self._weighted_lddt is None:
-      scores = [s.global_score for s in self.sc_lddt_scorers]
-      weights = [s.total_contacts for s in self.sc_lddt_scorers]
-      nominator = sum([s * w for s, w in zip(scores, weights)])
-      denominator = sum(weights)
-      if denominator > 0:
-        self._weighted_lddt = nominator / float(denominator)
-      else:
-        self._weighted_lddt = 0.0
-    return self._weighted_lddt
-
   ##############################################################################
   # Class internal helpers (anything that doesnt easily work without this class)
   ##############################################################################
 
-  def _PrepareOligoEntities(self):
-    # simple wrapper to avoid code duplication
-    self._lddt_ref, self._lddt_mdl = _MergeAlignedChains(self.alignments,
-                                                         self.ref,
-                                                         self.mdl,
-                                                         self.calpha_only)
-
+  def _InitScorer(self):
+    # Use copy of alignment (extended by 2 extra sequences for renumbering)
+    aln = self.alignment.Copy()
+    # Get chains and renumber according to alignment (for lDDT)
+    reference = Renumber(
+      aln.GetSequence(0),
+      old_number_label=self._old_number_label).CreateFullView()
+    refseq = seq.CreateSequence(
+      "reference_renumbered",
+      aln.GetSequence(0).GetString())
+    refseq.AttachView(reference)
+    aln.AddSequence(refseq)
+    model = Renumber(
+      aln.GetSequence(1),
+      old_number_label=self._old_number_label).CreateFullView()
+    modelseq = seq.CreateSequence(
+      "model_renumbered",
+      aln.GetSequence(1).GetString())
+    modelseq.AttachView(model)
+    aln.AddSequence(modelseq)
+    # Filter to CA-only if desired (done after AttachView to not mess it up)
+    if self.calpha_only:
+      self.lddt_scorer = lDDTScorer(
+        references=[reference.Select('aname=CA')],
+        model=model.Select('aname=CA'),
+        settings=self.settings)
+    else:
+      self.lddt_scorer = lDDTScorer(
+        references=[reference],
+        model=model,
+        settings=self.settings)
+    # Store alignment for later
+    self._extended_alignment = aln
 
 ###############################################################################
 # HELPERS
@@ -2528,4 +2621,4 @@ def _ComputeLDDTScore(ref, mdl):
 
 # specify public interface
 __all__ = ('QSscoreError', 'QSscorer', 'QSscoreEntity', 'FilterContacts',
-           'GetContacts', 'OligoLDDTScorer')
+           'GetContacts', 'OligoLDDTScorer', 'MappedLDDTScorer')