docs: def n_join(self, other, psi=-40.76, omega=-178.25, phi=-65.07,
                 o_c_n_angle=None, c_n_ca_angle=None, c_n_length=None, relabel=True):
          """Joins other to self at the N-terminus via a peptide bond.
  
          Notes
          -----
          This function directly modifies self. It does not return a new object.
  
          Parameters
          ----------
          other: Residue or Polypeptide
          psi: float
              Psi torsion angle (degrees) between final `Residue` of other
              and first `Residue` of self.
          omega: float
              Omega torsion angle (degrees) between final `Residue` of
              other and first `Residue` of self.
          phi: float
              Phi torsion angle (degrees) between final `Residue` of other
              and first `Residue` of self.
          o_c_n_angle: float or None
              Desired angle between O, C (final `Residue` of other) and N
              (first `Residue` of self) atoms. If `None`, default value is
              taken from `ideal_backbone_bond_angles`.
          c_n_ca_angle: float or None
              Desired angle between C (final `Residue` of other) and N, CA
              (first `Residue` of self) atoms. If `None`, default value is taken
              from `ideal_backbone_bond_angles`.
          c_n_length: float or None
              Desired peptide bond length between final `Residue` of other
              and first `Residue` of self. If None, default value is taken
              from ideal_backbone_bond_lengths.
          relabel: bool
              If True, relabel_all is run on self before returning.
  
          Raises
          ------
          TypeError:
              If other is not a `Residue` or a `Polypeptide`
          """
          if isinstance(other, Residue):
              other = Polypeptide([other])
          if not isinstance(other, Polypeptide):
              raise TypeError(
                  'Only Polypeptide or Residue objects can be joined to a Polypeptide')
          if abs(omega) >= 90:
              peptide_conformation = 'trans'
          else:
              peptide_conformation = 'cis'
          if o_c_n_angle is None:
              o_c_n_angle = ideal_backbone_bond_angles[peptide_conformation]['o_c_n']
          if c_n_ca_angle is None:
              c_n_ca_angle = ideal_backbone_bond_angles[peptide_conformation]['c_n_ca']
          if c_n_length is None:
              c_n_length = ideal_backbone_bond_lengths['c_n']
          r1 = self[0]
          r1_n = r1['N']._vector
          r1_ca = r1['CA']._vector
          r1_c = r1['C']._vector
          # p1 is point that will be used to position the C atom of r2.
          p1 = r1_ca[:]
          # rotate p1 by c_n_ca_angle, about axis perpendicular to the
          # r1_n, r1_ca, r1_c plane, passing through r1_ca.
          axis = numpy.cross((r1_ca - r1_n), (r1_c - r1_n))
          q = Quaternion.angle_and_axis(angle=c_n_ca_angle, axis=axis)
          p1 = q.rotate_vector(v=p1, point=r1_n)
          # Ensure p1 is separated from r1_n by the correct distance.
          p1 = r1_n + (c_n_length * unit_vector(p1 - r1_n))
          # translate other so that its final C atom is at p1
          other.translate(vector=(p1 - other[-1]['C']._vector))
          # Force CA-C=O-N to be in a plane, and fix O=C-N angle accordingly
          measured_dihedral = dihedral(
              other[-1]['CA'], other[-1]['C'], other[-1]['O'], r1['N'])
          desired_dihedral = 180.0
          axis = other[-1]['O'] - other[-1]['C']
          other.rotate(angle=(measured_dihedral - desired_dihedral),
                       axis=axis, point=other[-1]['C']._vector)
          axis = (numpy.cross(other[-1]['O'] - other[-1]
                              ['C'], r1['N'] - other[-1]['C']))
          measured_o_c_n = angle_between_vectors(
              other[-1]['O'] - other[-1]['C'], r1['N'] - other[-1]['C'])
          other.rotate(angle=(measured_o_c_n - o_c_n_angle),
                       axis=axis, point=other[-1]['C']._vector)
          # rotate other to obtain desired phi, omega, psi values at the join.
          measured_phi = dihedral(other[-1]['C'], r1['N'], r1['CA'], r1['C'])
          other.rotate(angle=(phi - measured_phi),
                       axis=(r1_n - r1_ca), point=r1_ca)
          measured_omega = dihedral(
              other[-1]['CA'], other[-1]['C'], r1['N'], r1['CA'])
          other.rotate(angle=(measured_omega - omega),
                       axis=(r1['N'] - other[-1]['C']), point=r1_n)
          measured_psi = dihedral(
              other[-1]['N'], other[-1]['CA'], other[-1]['C'], r1['N'])
          other.rotate(angle=-(measured_psi - psi), axis=(other[-1]['CA'] - other[-1]['C']),
                       point=other[-1]['CA']._vector)
          self._monomers = other._monomers + self._monomers
          if relabel:
              self.relabel_all()
          self.tags['assigned_ff'] = False
          return