diff --git a/modules/mol/alg/pymod/CMakeLists.txt b/modules/mol/alg/pymod/CMakeLists.txt
index 979503197c3532b301a1e3abf1b8c3f5a93113ff..146774722d330305601088fd7fa18735b821c001 100644
--- a/modules/mol/alg/pymod/CMakeLists.txt
+++ b/modules/mol/alg/pymod/CMakeLists.txt
@@ -11,6 +11,7 @@ set(OST_MOL_ALG_PYMOD_SOURCES
   export_molck.cc
   export_membrane.cc
   export_entity_to_density.cc
+  export_biounit.cc
 )
 
 set(OST_MOL_ALG_PYMOD_MODULES
diff --git a/modules/mol/alg/pymod/export_biounit.cc b/modules/mol/alg/pymod/export_biounit.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1b1e6b354b66d8f0b3e0673086bb1d94fe8a3f52
--- /dev/null
+++ b/modules/mol/alg/pymod/export_biounit.cc
@@ -0,0 +1,86 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2023 by the OpenStructure authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+
+#include <boost/python.hpp>
+using namespace boost::python;
+
+#include <ost/mol/alg/biounit.hh>
+
+namespace{
+
+  PyObject* wrap_to_bytes(ost::mol::alg::BUInfo& bu) {
+    String str = bu.ToString();
+    return PyBytes_FromStringAndSize(str.c_str(), str.size());
+  }
+  
+  ost::mol::alg::BUInfo wrap_from_bytes(boost::python::object obj) {
+    String str(PyBytes_AsString(obj.ptr()), PyBytes_Size(obj.ptr()));
+    return ost::mol::alg::BUInfo::FromString(str);
+  }
+
+  list wrap_get_au_chains(const ost::mol::alg::BUInfo& info) {
+    list return_list;
+    const std::vector<std::vector<String> >& au_chains = info.GetAUChains();
+    for(uint i = 0; i < au_chains.size(); ++i) {
+      list tmp;
+      for(uint j = 0; j < au_chains[i].size(); ++j) {
+        tmp.append(au_chains[i][j]);
+      }
+      return_list.append(tmp);
+    }
+    return return_list;
+  }
+
+  list wrap_get_transformations(const ost::mol::alg::BUInfo& info) {
+    list return_list;
+    const std::vector<std::vector<geom::Mat4> >& tfs = info.GetTransformations();
+    for(uint i = 0; i < tfs.size(); ++i) {
+      list tmp;
+      for(uint j = 0; j < tfs[i].size(); ++j) {
+        tmp.append(tfs[i][j]);
+      }
+      return_list.append(tmp);
+    }
+    return return_list;
+  }
+
+  ost::mol::EntityHandle wrap_CreateBU_one(const ost::mol::EntityHandle& asu,
+                                           const ost::io::MMCifInfoBioUnit& bu) {
+    return ost::mol::alg::CreateBU(asu, bu);
+  }
+
+  ost::mol::EntityHandle wrap_CreateBU_two(const ost::mol::EntityHandle& asu,
+                                           const ost::mol::alg::BUInfo& bu) {
+    return ost::mol::alg::CreateBU(asu, bu);
+  }
+
+} // anon ns
+
+void export_biounit() {
+
+  class_<ost::mol::alg::BUInfo>("BUInfo", init<const ost::io::MMCifInfoBioUnit&>())
+    .def("FromBytes", &wrap_from_bytes).staticmethod("FromBytes")
+    .def("ToBytes", &wrap_to_bytes)
+    .def("GetAUChains", &wrap_get_au_chains)
+    .def("GetTransformations", &wrap_get_transformations)
+  ;
+
+  def("CreateBU", &wrap_CreateBU_one);
+  def("CreateBU", &wrap_CreateBU_two);
+}
diff --git a/modules/mol/alg/pymod/wrap_mol_alg.cc b/modules/mol/alg/pymod/wrap_mol_alg.cc
index e1b80398615764e98b67b912ae0e75758b24c136..295bea2bb84a0a3beb7784e26a9ae4efd81a9089 100644
--- a/modules/mol/alg/pymod/wrap_mol_alg.cc
+++ b/modules/mol/alg/pymod/wrap_mol_alg.cc
@@ -50,6 +50,7 @@ void export_sec_struct();
 void export_sec_struct_segments();
 void export_find_membrane();
 void export_entity_to_density();
+void export_biounit();
 
 namespace {
   
@@ -321,6 +322,7 @@ BOOST_PYTHON_MODULE(_ost_mol_alg)
   export_sec_struct_segments();
   export_find_membrane();
   export_entity_to_density();
+  export_biounit();
   
   def("LocalDistDiffTest", lddt_a, (arg("sequence_separation")=0,arg("local_lddt_property_string")=""));
   def("LocalDistDiffTest", lddt_c, (arg("local_lddt_property_string")=""));
diff --git a/modules/mol/alg/src/CMakeLists.txt b/modules/mol/alg/src/CMakeLists.txt
index c90616ba298908dcc12d798291ba0a1770306328..49f58f50e27c0f7eeb29feafac4d4523b5cfb753 100644
--- a/modules/mol/alg/src/CMakeLists.txt
+++ b/modules/mol/alg/src/CMakeLists.txt
@@ -24,6 +24,7 @@ set(OST_MOL_ALG_HEADERS
   molck.hh
   find_membrane.hh
   entity_to_density.hh
+  biounit.hh
 )
 
 set(OST_MOL_ALG_SOURCES
@@ -51,6 +52,7 @@ set(OST_MOL_ALG_SOURCES
   molck.cc
   find_membrane.cc
   entity_to_density.cc
+  biounit.cc
 )
 
 set(MOL_ALG_DEPS ost_mol ost_seq ost_img ost_img_alg ost_seq_alg ost_conop)
diff --git a/modules/mol/alg/src/biounit.cc b/modules/mol/alg/src/biounit.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2ff12d7dd7950e63452ab8599290b338902e2b87
--- /dev/null
+++ b/modules/mol/alg/src/biounit.cc
@@ -0,0 +1,321 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2023 by the OpenStructure authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+
+#include <ost/mol/alg/biounit.hh>
+#include <ost/mol/entity_view.hh>
+
+namespace{
+
+// dump and load vectors with various types of integers 
+template<typename T>
+void LoadIntVec(std::istream& stream, std::vector<T>& vec) {
+  uint32_t size;
+  stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  vec.resize(size);
+  stream.read(reinterpret_cast<char*>(&vec[0]), size*sizeof(T));
+}
+
+template<typename T>
+void DumpIntVec(std::ostream& stream, const std::vector<T>& vec) {
+  uint32_t size = vec.size();
+  stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  stream.write(reinterpret_cast<const char*>(&vec[0]), size*sizeof(T));
+}
+
+// dump and load strings
+void Load(std::istream& stream, String& str) {
+  uint32_t size;
+  stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  str.resize(size);
+  stream.read(&str[0], size);
+}
+
+void Dump(std::ostream& stream, const String& str) {
+  uint32_t size = str.size();
+  stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  stream.write(&str[0], size);
+}
+
+// dump and load vectors with strings
+void Load(std::istream& stream, std::vector<String>& vec) {
+  std::vector<uint8_t> string_sizes;
+  String str;
+  LoadIntVec(stream, string_sizes);
+  Load(stream, str);
+  vec.resize(string_sizes.size());
+  int idx = 0;
+  for(uint i = 0; i < string_sizes.size(); ++i) {
+    vec[i] = str.substr(idx, string_sizes[i]);
+    idx += string_sizes[i];
+  }
+}
+
+void Dump(std::ostream& stream, const std::vector<String>& vec) {
+  String total_string;
+  std::vector<uint8_t> string_sizes;
+  for(auto it = vec.begin(); it != vec.end(); ++it) {
+    if(it->size() > std::numeric_limits<uint8_t>::max()) {
+      std::stringstream ss;
+      ss << "Max string size that can be encoded is "; 
+      ss << std::numeric_limits<uint8_t>::max() << " cannot encode "<< *it; 
+    }
+    string_sizes.push_back(it->size());
+    total_string += *it;
+  }
+  DumpIntVec(stream, string_sizes);
+  Dump(stream, total_string);
+}
+
+// dump and load vectors with Mat4
+void Load(std::istream& stream, std::vector<geom::Mat4>& vec) {
+  uint32_t size;
+  stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  vec.resize(size);
+  for(uint i = 0; i < size; ++i) {
+    stream.read(reinterpret_cast<char*>(vec[i].Data()),16*sizeof(Real));
+  }
+}
+
+void Dump(std::ostream& stream, const std::vector<geom::Mat4>& vec) {
+  uint32_t size = vec.size();
+  stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  for(uint i = 0; i < size; ++i) {
+    stream.write(reinterpret_cast<const char*>(vec[i].Data()), 16*sizeof(Real));
+  }
+}
+
+} // anon ns
+
+
+namespace ost{ namespace mol{ namespace alg{
+
+BUInfo::BUInfo(const ost::io::MMCifInfoBioUnit& bu) {
+
+  // translate MMCifInfoBioUnit objects into simpler data structures
+  au_chains = bu.GetChainList();
+
+  const std::vector<std::pair<int, int> >& bu_ch_intvl = bu.GetChainIntervalList();
+  for(auto it = bu_ch_intvl.begin(); it != bu_ch_intvl.end(); ++it) {
+    chain_intvl.push_back(it->first);      
+    chain_intvl.push_back(it->second);      
+  }
+
+  const std::vector<std::vector<ost::io::MMCifInfoTransOpPtr> >& bu_op_list = bu.GetOperations();
+  for(auto i = bu_op_list.begin(); i != bu_op_list.end(); ++i) {
+    std::vector<geom::Mat4> mat_list;
+    for(auto j = i->begin(); j != i->end(); ++j) {
+      geom::Mat4 m;
+      m.PasteRotation((*j)->GetMatrix());
+      m.PasteTranslation((*j)->GetVector());
+      mat_list.push_back(m);
+    }
+    operations.push_back(mat_list);
+  }
+
+  const std::vector<std::pair<int, int> >& bu_op_intvl = bu.GetOperationsIntervalList();
+  for(auto it = bu_op_intvl.begin(); it != bu_op_intvl.end(); ++it) {
+    op_intvl.push_back(it->first);      
+    op_intvl.push_back(it->second);      
+  }
+}
+
+void BUInfo::ToStream(std::ostream& stream) const {
+  Dump(stream, au_chains);
+  DumpIntVec(stream, chain_intvl);
+  uint32_t size = operations.size();
+  stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  for(auto it = operations.begin(); it != operations.end(); ++it) {
+    Dump(stream, *it);
+  }
+  DumpIntVec(stream, op_intvl);
+}
+
+BUInfo BUInfo::FromStream(std::istream& stream) {
+  BUInfo info;
+  Load(stream, info.au_chains);
+  LoadIntVec(stream, info.chain_intvl);
+  uint32_t size = 0;
+  stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
+  info.operations.resize(size);
+  for(uint i = 0; i < size; ++i) {
+    Load(stream, info.operations[i]);
+  }
+  LoadIntVec(stream, info.op_intvl);
+  return info;
+}
+
+BUInfo BUInfo::FromString(const String& s) {
+  std::istringstream in_stream(s);
+  BUInfo info = BUInfo::FromStream(in_stream);
+  return info;
+}
+
+String BUInfo::ToString() const {
+  std::ostringstream out_stream;
+  this->ToStream(out_stream);
+  return out_stream.str();
+}
+
+const std::vector<std::vector<String> >& BUInfo::GetAUChains() const {
+  if(au_chains_.empty()) {
+    this->_InitTransforms();
+  }
+  return au_chains_;
+}
+
+const std::vector<std::vector<geom::Mat4> >& BUInfo::GetTransformations() const {
+  if(transforms_.empty()) {
+    this->_InitTransforms();
+  }
+  return transforms_;
+}
+
+void BUInfo::_InitTransforms() const {
+  int n_intervals = chain_intvl.size() / 2;
+  for(int intvl_idx = 0; intvl_idx < n_intervals; ++intvl_idx) {
+    // extract relevant chain names from asu
+    ////////////////////////////////////////
+    std::vector<String> chain_names;
+    int chain_start = chain_intvl[2*intvl_idx];
+    int chain_end = chain_intvl[2*intvl_idx+1];
+    for(int ch_idx = chain_start; ch_idx < chain_end; ++ch_idx) {
+      chain_names.push_back(au_chains[ch_idx]);
+    }
+    au_chains_.push_back(chain_names);
+    // extract operations that will be applied to those chains
+    //////////////////////////////////////////////////////////
+    std::vector<geom::Mat4> transforms;
+    int op_start = op_intvl[2*intvl_idx];
+    int op_end = op_intvl[2*intvl_idx+1];
+    if(op_end > op_start) {
+      for(auto it = operations[op_start].begin();
+          it != operations[op_start].end(); ++it) {
+        transforms.push_back(*it);
+      }
+      ++op_start;
+      while(op_start < op_end) {
+        std::vector<geom::Mat4> tmp_transforms;
+        for(auto i = operations[op_start].begin(); 
+            i != operations[op_start].end(); ++i) {
+          for(auto j = transforms.begin(); j != transforms.end(); ++j) {
+            tmp_transforms.push_back((*j)*(*i));
+          }
+        }
+        transforms = tmp_transforms;
+        ++op_start;
+      }
+    }
+    transforms_.push_back(transforms);
+  }
+}
+
+ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu,
+                                const ost::io::MMCifInfoBioUnit& bu) {
+  BUInfo bu_info(bu);
+  return CreateBU(asu, bu_info);
+}
+
+ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu,
+                                const BUInfo& bu_info) {
+  ost::mol::EntityHandle ent = ost::mol::CreateEntity();
+  ent.SetName(asu.GetName());
+  ost::mol::XCSEditor ed = ent.EditXCS(mol::BUFFERED_EDIT);
+
+  // For chain naming. First occurence: 1.<au_cname>, Second: 2.<au_cname> etc.
+  std::map<String, int> chain_counter;
+
+  const std::vector<std::vector<String> >& au_chains = bu_info.GetAUChains();
+  const std::vector<std::vector<geom::Mat4> >& transforms =
+  bu_info.GetTransformations();
+
+  for(uint chain_intvl = 0; chain_intvl < au_chains.size(); ++chain_intvl) {
+    if(au_chains[chain_intvl].empty()) continue;
+    // derive all bonds related to that chain_intvl
+    // potentially also interchain bonds
+    std::stringstream query_ss;
+    query_ss << "cname=" << au_chains[chain_intvl][0];
+    for(uint i = 1; i < au_chains[chain_intvl].size(); ++i) {
+      query_ss << ',' << au_chains[chain_intvl][i];
+    }
+    ost::mol::EntityView asu_view = asu.Select(query_ss.str());
+    const ost::mol::BondHandleList& bond_list = asu_view.GetBondList();
+
+    // process all transformations
+    for(uint t_idx = 0; t_idx < transforms[chain_intvl].size(); ++t_idx) {
+      const geom::Mat4& m = transforms[chain_intvl][t_idx];
+      // key: au_at.GetHashCode, value: bu_at
+      // required for bond buildup in the end
+      std::map<long, AtomHandle> atom_mapper;
+      for(uint c_idx = 0; c_idx < au_chains[chain_intvl].size(); ++c_idx) {
+        String au_cname = au_chains[chain_intvl][c_idx];
+        if(chain_counter.find(au_cname) == chain_counter.end()) {
+          chain_counter[au_cname] = 1;
+        }
+        std::stringstream bu_cname_ss;
+        bu_cname_ss << chain_counter[au_cname] << '.' << au_cname;
+        chain_counter[au_cname] += 1;
+        ost::mol::ChainHandle asu_ch = asu.FindChain(au_cname);
+        if(!asu_ch.IsValid()) {
+          std::stringstream ss;
+          ss << "Cannot construct biounit with asu chain "<<au_cname;
+          ss << ". Specified interval only has: " <<au_chains[chain_intvl][0];
+          for(uint i = 1; i < au_chains[chain_intvl].size(); ++i) {
+            ss << ',' << au_chains[chain_intvl][i];
+          }
+          throw ost::Error(ss.str());
+        }
+        ost::mol::ChainHandle bu_ch = ed.InsertChain(bu_cname_ss.str());
+        ed.SetChainType(bu_ch, asu_ch.GetType());
+        ost::mol::ResidueHandleList au_res_list = asu_ch.GetResidueList();
+        for(auto res_it = au_res_list.begin();
+            res_it != au_res_list.end(); ++res_it) {
+          ost::mol::ResidueHandle bu_res = ed.AppendResidue(bu_ch,
+            res_it->GetName(), res_it->GetNumber());
+          bu_res.SetOneLetterCode(res_it->GetOneLetterCode());
+          bu_res.SetSecStructure(res_it->GetSecStructure());
+          bu_res.SetChemClass(res_it->GetChemClass());
+          bu_res.SetChemType(res_it->GetChemType());
+          bu_res.SetIsProtein(res_it->IsProtein());
+          bu_res.SetIsLigand(res_it->IsLigand());
+          ost::mol::AtomHandleList au_at_list = res_it->GetAtomList();
+          for(auto at_it = au_at_list.begin(); at_it != au_at_list.end(); ++at_it) {
+            geom::Vec3 bu_at_pos = geom::Vec3(m*geom::Vec4(at_it->GetPos()));
+            ost::mol::AtomHandle bu_at = ed.InsertAtom(bu_res, at_it->GetName(),
+                                                       bu_at_pos,
+                                                       at_it->GetElement(),
+                                                       at_it->GetOccupancy(),
+                                                       at_it->GetBFactor(),
+                                                       at_it->IsHetAtom());
+            atom_mapper[at_it->GetHashCode()] = bu_at;
+          }
+        }
+      }
+
+      // connect
+      for(auto it = bond_list.begin(); it != bond_list.end(); ++it) {
+        ed.Connect(atom_mapper[it->GetFirst().GetHashCode()],
+                   atom_mapper[it->GetSecond().GetHashCode()]);
+      }
+
+    }
+  }
+  return ent;
+}
+
+}}} // ns
diff --git a/modules/mol/alg/src/biounit.hh b/modules/mol/alg/src/biounit.hh
new file mode 100644
index 0000000000000000000000000000000000000000..3af0483e19bb5fa5afe816d93ebdd8a919613861
--- /dev/null
+++ b/modules/mol/alg/src/biounit.hh
@@ -0,0 +1,66 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2023 by the OpenStructure authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+#ifndef OST_MOL_ALG_BIOUNIT_HH
+#define OST_MOL_ALG_BIOUNIT_HH
+
+#include <ost/mol/entity_handle.hh>
+#include <ost/io/mmcif_info.hh>
+
+namespace ost { namespace mol { namespace alg {
+
+struct BUInfo {
+
+  BUInfo() { };
+
+  BUInfo(const ost::io::MMCifInfoBioUnit& bu);
+
+  void ToStream(std::ostream& stream) const;
+
+  static BUInfo FromStream(std::istream& stream);
+
+  String ToString() const;
+
+  static BUInfo FromString(const String& s);
+
+  const std::vector<std::vector<String> >& GetAUChains() const;
+
+  const std::vector<std::vector<geom::Mat4> >& GetTransformations() const;
+
+  void _InitTransforms() const;
+
+  std::vector<String> au_chains;
+  std::vector<int> chain_intvl;
+  std::vector<std::vector<geom::Mat4> > operations;
+  std::vector<int> op_intvl;
+
+private:
+  mutable std::vector<std::vector<String> > au_chains_;
+  mutable std::vector<std::vector<geom::Mat4> > transforms_;
+};
+
+ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu,
+                                const ost::io::MMCifInfoBioUnit& bu);
+
+
+ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu,
+                                const BUInfo& bu_info);
+
+}}} // ns
+
+#endif // OST_MOL_ALG_BIOUNIT_HH
diff --git a/modules/mol/alg/tests/CMakeLists.txt b/modules/mol/alg/tests/CMakeLists.txt
index 4e3126dd88d49cecef7235c368ee1835d34b5e9c..268d09c8153ad1b2d5fe756981416e5373e4b0dd 100644
--- a/modules/mol/alg/tests/CMakeLists.txt
+++ b/modules/mol/alg/tests/CMakeLists.txt
@@ -12,6 +12,7 @@ set(OST_MOL_ALG_UNIT_TESTS
   test_qsscore.py
   test_stereochemistry.py
   test_contact_score.py
+  test_biounit.py
 )
 
 if (COMPOUND_LIB)
diff --git a/modules/mol/alg/tests/test_biounit.py b/modules/mol/alg/tests/test_biounit.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3dc77ae61df64dfc7f4d27ff8c96310c9bfd91c
--- /dev/null
+++ b/modules/mol/alg/tests/test_biounit.py
@@ -0,0 +1,104 @@
+import unittest, os, sys
+import ost
+from ost import io, geom, mol
+from ost.mol.alg import BUInfo
+
+class TestBioUnit(unittest.TestCase):
+
+  def test_bu(self):
+    ent, seqres, info = io.LoadMMCIF("testfiles/1out.cif.gz", 
+                                     seqres=True,
+                                     info=True)
+
+    # Create BUInfo from MMCifInfoBioUnit
+    biounits = info.GetBioUnits()
+    self.assertEqual(len(biounits), 1)
+    bu_info = BUInfo(biounits[0])
+
+    # directly use the dump and load mechanism
+    # IF ANY UNIT TEST FAILS, DISABLE THE FOLLOWING TWO LINES
+    # TO EXCLUDE THE POSSIBILITY OF ISSUES IN BUINFO IO FUNCTIONALITY
+    bytes_str = bu_info.ToBytes()
+    bu_info = BUInfo.FromBytes(bytes_str)
+
+    # check whether properties in BUInfo object are correctly set
+    asu_chains = bu_info.GetAUChains()
+    self.assertEqual(asu_chains, [["A", "B", "C", "D", "E", "F"]])
+    transformations = bu_info.GetTransformations()
+    self.assertEqual(len(transformations), 1)
+    self.assertEqual(len(transformations[0]), 2)
+    self.assertEqual(transformations[0][0], geom.Mat4()) # identity
+
+    # reconstruct biounit
+    bu = mol.alg.CreateBU(ent, bu_info)
+    self.assertEqual([ch.GetName() for ch in bu.chains],
+                     ["1.A", "1.B", "1.C", "1.D", "1.E", "1.F",
+                      "2.A", "2.B", "2.C", "2.D", "2.E", "2.F"])
+
+    # extract copies of original assymetric units from biounit
+    for ch in bu.chains:
+      ch.SetIntProp("asu_copy_idx", int(ch.GetName().split('.')[0]))
+    asu_copy_one = bu.Select("gcasu_copy_idx=1")
+    asu_copy_two = bu.Select("gcasu_copy_idx=2")
+
+    # compare bonds - well, this tests whether bonds have been correctly
+    # transferred. BUT: we can be pretty sure that all atoms/residues/chains
+    # have been transferred too when explicitely checking the bonds.
+    asu_bonds = ent.GetBondList()
+    asu_bonds_desc = list()
+    for b in asu_bonds:
+      at_one = b.GetFirst()
+      at_one_cname = at_one.GetChain().GetName()
+      at_one_rname = at_one.GetResidue().GetName()
+      at_one_rnum = at_one.GetResidue().GetNumber().GetNum()
+      at_one_name = at_one.GetName()
+      at_one_desc = f"{at_one_cname}.{at_one_rname}.{at_one_rnum}.{at_one_name}"
+      at_two = b.GetSecond()
+      at_two_cname = at_two.GetChain().GetName()
+      at_two_rname = at_two.GetResidue().GetName()
+      at_two_rnum = at_two.GetResidue().GetNumber().GetNum()
+      at_two_name = at_two.GetName()
+      at_two_desc = f"{at_two_cname}.{at_two_rname}.{at_two_rnum}.{at_two_name}"
+      asu_bonds_desc.append((at_one_desc, at_two_desc))
+
+    asu_copy_one_bonds = asu_copy_one.GetBondList()
+    asu_copy_one_bonds_desc = list()
+    for b in asu_copy_one_bonds:
+      at_one = b.GetFirst()
+      at_one_cname = at_one.GetChain().GetName().split('.')[1]
+      at_one_rname = at_one.GetResidue().GetName()
+      at_one_rnum = at_one.GetResidue().GetNumber().GetNum()
+      at_one_name = at_one.GetName()
+      at_one_desc = f"{at_one_cname}.{at_one_rname}.{at_one_rnum}.{at_one_name}"
+      at_two = b.GetSecond()
+      at_two_cname = at_two.GetChain().GetName().split('.')[1]
+      at_two_rname = at_two.GetResidue().GetName()
+      at_two_rnum = at_two.GetResidue().GetNumber().GetNum()
+      at_two_name = at_two.GetName()
+      at_two_desc = f"{at_two_cname}.{at_two_rname}.{at_two_rnum}.{at_two_name}"
+      asu_copy_one_bonds_desc.append((at_one_desc, at_two_desc))
+    
+    asu_copy_two_bonds = asu_copy_two.GetBondList()
+    asu_copy_two_bonds_desc = list()
+    for b in asu_copy_two_bonds:
+      at_one = b.GetFirst()
+      at_one_cname = at_one.GetChain().GetName().split('.')[1]
+      at_one_rname = at_one.GetResidue().GetName()
+      at_one_rnum = at_one.GetResidue().GetNumber().GetNum()
+      at_one_name = at_one.GetName()
+      at_one_desc = f"{at_one_cname}.{at_one_rname}.{at_one_rnum}.{at_one_name}"
+      at_two = b.GetSecond()
+      at_two_cname = at_two.GetChain().GetName().split('.')[1]
+      at_two_rname = at_two.GetResidue().GetName()
+      at_two_rnum = at_two.GetResidue().GetNumber().GetNum()
+      at_two_name = at_two.GetName()
+      at_two_desc = f"{at_two_cname}.{at_two_rname}.{at_two_rnum}.{at_two_name}"
+      asu_copy_two_bonds_desc.append((at_one_desc, at_two_desc))
+
+    self.assertEqual(asu_bonds_desc, asu_copy_one_bonds_desc)
+    self.assertEqual(asu_bonds_desc, asu_copy_two_bonds_desc)
+
+
+if __name__ == "__main__":
+  from ost import testutils
+  testutils.RunTests()
diff --git a/modules/mol/alg/tests/testfiles/1out.cif.gz b/modules/mol/alg/tests/testfiles/1out.cif.gz
new file mode 100644
index 0000000000000000000000000000000000000000..753337af9ab9dc0eca11a4391f1e9e92f5148332
Binary files /dev/null and b/modules/mol/alg/tests/testfiles/1out.cif.gz differ