Repository: Kohulan/Smiles-TO-iUpac-Translator (language: python)

Issue:
Token-level confidence
I have added the option to get token-level confidence values based on the output of the final layer of the Transformer decoder. I don't have access to the checkpoints I cannot test it right now. 

__
Sorry for the failing test, I they will only pass once the new model is on Zenodo and the link is updated.


Discussion / hints:

Fix patch:
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..758cd86
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,24 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the OS, Python version and other tools you might need
+build:
+  os: ubuntu-22.04
+  tools:
+    python: "3.11"
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+  configuration: docs/conf.py
+
+# Optionally build your docs in additional formats such as PDF
+formats:
+  - epub
+
+# Optionally set the version of Python and requirements required to build your docs
+python:
+  install:
+    - requirements: docs/requirements.txt
diff --git a/README.md b/README.md
index 24924e4..6601903 100644
--- a/README.md
+++ b/README.md
@@ -1,84 +1,151 @@
-[![License](https://img.shields.io/badge/License-MIT%202.0-blue.svg)](https://opensource.org/licenses/MIt)
-[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/Kohulan/Smiles-TO-iUpac-Translator/graphs/commit-activity)
-![Workflow](https://github.com/Kohulan/Smiles-TO-iUpac-Translator/actions/workflows/Check_errors.yml/badge.svg)
-[![GitHub issues](https://img.shields.io/github/issues/Kohulan/Smiles-TO-iUpac-Translator.svg)](https://GitHub.com/Kohulan/Smiles-TO-iUpac-Translator/issues/)
-[![GitHub contributors](https://img.shields.io/github/contributors/Kohulan/Smiles-TO-iUpac-Translator.svg)](https://GitHub.com/Kohulan/Smiles-TO-iUpac-Translator/graphs/contributors/)
-[![tensorflow](https://img.shields.io/badge/TensorFlow-2.10.1-FF6F00.svg?style=flat&logo=tensorflow)](https://www.tensorflow.org)
-[![GitHub release](https://img.shields.io/github/release/Kohulan/Smiles-TO-iUpac-Translator.svg)](https://GitHub.com/Kohulan/Smiles-TO-iUpac-Translator/releases/)
-[![PyPI version fury.io](https://badge.fury.io/py/STOUT-pypi.svg)](https://pypi.org/project/STOUT-pypi/)
-![versions](https://img.shields.io/pypi/pyversions/STOUT-pypi.svg)
-
-![GitHub Logo](https://github.com/Kohulan/Smiles-TO-iUpac-Translator/blob/stout-1/important_assets/STOUT.png?raw=true)
-
-# STOUT V2.0 - Smiles TO iUpac Translator Version 2.0
-This repository contains STOUT-V2, SMILES to IUPAC name translator using transformers. STOUT-V2 can translate SMILES to IUPAC names and IUPAC names back to a valid SMILES string. STOUT-V1 is already published and for more details check [here](https://github.com/Kohulan/Smiles-TO-iUpac-Translator)
-
-#### OS-Support: Linux, MACOS and Windows (On Windows you can run STOUT inside the Ubuntu shell). But It is highly recommended to use a Linux system.
-
-# Usage
-
-### We suggest to use STOUT inside a Conda environment, which makes the dependencies to install easily.
-- Conda can be downloaded as part of the [Anaconda](https://www.anaconda.com/) or the [Miniconda](https://conda.io/en/latest/miniconda.html) plattforms (Python 3.7). We recommend to install miniconda3. Using Linux you can get it with:
-```shell
-$ wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
-$ bash Miniconda3-latest-Linux-x86_64.sh
-```
-## How to install STOUT
+<h1 align="center">
+  <br>
+  <a href="https://github.com/Kohulan/Smiles-TO-iUpac-Translator"><img src="https://github.com/Kohulan/Smiles-TO-iUpac-Translator/blob/development/docs/_static/STOUT.png?raw=true" alt="STOUT Logo" width="400"></a>
+  <br>
+V2.0
+  <br>
+</h1>
 
-## Install directly using PyPi
-```shell
-pip install STOUT-pypi
-```
+<h4 align="center">Smiles TO iUpac Translator: Advanced Chemical Nomenclature Translation</h4>
+
+<p align="center">
+  <a href="https://opensource.org/licenses/MIt">
+    <img src="https://img.shields.io/badge/License-MIT%202.0-blue.svg" alt="License">
+  </a>
+  <a href="https://github.com/Kohulan/Smiles-TO-iUpac-Translator/graphs/commit-activity">
+    <img src="https://img.shields.io/badge/Maintained%3F-yes-blue.svg" alt="Maintenance">
+  </a>
+  <a href="https://github.com/Kohulan/Smiles-TO-iUpac-Translator/actions/workflows/Check_errors.yml">
+    <img src="https://github.com/Kohulan/Smiles-TO-iUpac-Translator/actions/workflows/Check_errors.yml/badge.svg" alt="Workflow">
+  </a>
+  <br>
+  <a href="https://GitHub.com/Kohulan/Smiles-TO-iUpac-Translator/issues/">
+    <img src="https://img.shields.io/github/issues/Kohulan/Smiles-TO-iUpac-Translator.svg" alt="GitHub issues">
+  </a>
+  <a href="https://GitHub.com/Kohulan/Smiles-TO-iUpac-Translator/graphs/contributors/">
+    <img src="https://img.shields.io/github/contributors/Kohulan/Smiles-TO-iUpac-Translator.svg" alt="GitHub contributors">
+  </a>
+  <a href="https://www.tensorflow.org">
+    <img src="https://img.shields.io/badge/TensorFlow-2.15.0-FF6F00.svg?style=flat&logo=tensorflow" alt="tensorflow">
+  </a>
+  <br>
+  <a href="https://GitHub.com/Kohulan/Smiles-TO-iUpac-Translator/releases/">
+    <img src="https://img.shields.io/github/release/Kohulan/Smiles-TO-iUpac-Translator.svg" alt="GitHub release">
+  </a>
+  <a href="https://pypi.org/project/STOUT-pypi/">
+    <img src="https://badge.fury.io/py/STOUT-pypi.svg" alt="PyPI version">
+  </a>
+  <a href="https://pypi.org/project/STOUT-pypi/">
+    <img src="https://img.shields.io/pypi/pyversions/STOUT-pypi.svg" alt="Python versions">
+  </a>
+  <a href="https://doi.org/10.5281/zenodo.13318286">
+    <img src="https://zenodo.org/badge/DOI/10.5281/zenodo.13318286.svg" alt="DOI">
+  </a>
+</p>
+
+<p align="center">
+  <a href="#key-features">Key Features</a> •
+  <a href="#installation">Installation</a> •
+  <a href="#how-to-use">How To Use</a> •
+  <a href="#acknowledgements">Acknowledgements</a> •
+  <a href="#citation">Citation</a>
+</p>
+
+<p align="center">
+  <img src="https://github.com/Kohulan/Smiles-TO-iUpac-Translator/raw/master/docs/_static/STOUT_demo.gif" alt="STOUT Demo">
+</p>
+
+## Key Features
+
+<ul>
+  <li>🧪 Translate SMILES to IUPAC names</li>
+  <li>🔬 Convert IUPAC names back to valid SMILES strings</li>
+  <li>🤖 Powered by advanced transformer models</li>
+  <li>💻 Cross-platform support (Linux, macOS, Windows via Ubuntu shell)</li>
+  <li>🚀 High-performance chemical nomenclature translation</li>
+</ul>
+
+## Installation
 
-## In a conda environment
-```shell
-conda create --name STOUT python=3.8 
+<p>Choose your preferred installation method:</p>
+
+<details>
+<summary><b>📦 PyPI Installation</b></summary>
+<pre><code>pip install STOUT-pypi</code></pre>
+</details>
+
+<details>
+<summary><b>🐍 Conda Environment Setup</b></summary>
+<pre><code>conda create --name STOUT python=3.10 
 conda activate STOUT
-conda install -c decimer stout-pypi
-```
+conda install -c decimer stout-pypi</code></pre>
+</details>
 
-## Straight from the repository
-```shell
-pip install git+https://github.com/Kohulan/Smiles-TO-iUpac-Translator.git
-```
+<details>
+<summary><b>📥 Direct Repository Installation</b></summary>
+<pre><code>pip install git+https://github.com/Kohulan/Smiles-TO-iUpac-Translator.git</code></pre>
+</details>
 
-## Simple usage
-```python3
+## How To Use
 
+```python
 from STOUT import translate_forward, translate_reverse
 
 # SMILES to IUPAC name translation
-
 SMILES = "CN1C=NC2=C1C(=O)N(C(=O)N2C)C"
 IUPAC_name = translate_forward(SMILES)
-print("IUPAC name of "+SMILES+" is: "+IUPAC_name)
+print(f"🧪 IUPAC name of {SMILES} is: {IUPAC_name}")
 
 # IUPAC name to SMILES translation
-
 IUPAC_name = "1,3,7-trimethylpurine-2,6-dione"
 SMILES = translate_reverse(IUPAC_name)
-print("SMILES of "+IUPAC_name+" is: "+SMILES)
-
+print(f"🔬 SMILES of {IUPAC_name} is: {SMILES}")
 ```
 
-#### Happy Brewing... 🍺
+## Acknowledgements
 
-## How to cite us?
+<p align="center">
+  Research supported with Cloud TPUs from Google's TPU Research Cloud (TRC)
+  <br><br>
+  <img src="https://user-images.githubusercontent.com/30716951/220350828-913e6645-6a0a-403c-bcb8-160d061d4606.png" width="200">
+</p>
 
-Rajan, K., Zielesny, A. & Steinbeck, C. STOUT: SMILES to IUPAC names using neural machine translation. J Cheminform 13, 34 (2021). https://doi.org/10.1186/s13321-021-00512-4
+<h2 align="center">Part of the DECIMER Project</h2>
+
+<p align="center">
+  <a href="https://decimer.ai">
+    <img src="https://github.com/Kohulan/DECIMER-Image-to-SMILES/raw/master/assets/DECIMER.gif" alt="DECIMER Logo" width="400">
+  </a>
+</p>
 
-## Acknowledgement
-- Research supported with Cloud TPUs from Google's TPU Research Cloud (TRC) 
+<h2 align="center">About Us</h2>
 
 <p align="center">
-  <img src="https://user-images.githubusercontent.com/30716951/220350828-913e6645-6a0a-403c-bcb8-160d061d4606.png" width="500" class="center">
+  <a href="https://cheminf.uni-jena.de">
+    <img src="https://github.com/Kohulan/DECIMER-Image-to-SMILES/blob/master/assets/CheminfGit.png?raw=true" alt="Cheminformatics and Computational Metabolomics Group" width="300">
+  </a>
 </p>
 
-# STOUT-V2 is part of DECIMER project
-[![GitHub Logo](https://github.com/Kohulan/DECIMER-Image-to-SMILES/raw/master/assets/DECIMER.gif)](https://decimer.ai)
+## Citation
 
-# More about Us
+<blockquote>
+Rajan, K., Zielesny, A. & Steinbeck, C. STOUT: SMILES to IUPAC names using neural machine translation. J Cheminform 13, 34 (2021). https://doi.org/10.1186/s13321-021-00512-4
+</blockquote>
+
+## Model Card
+
+<blockquote>
+Rajan, K., Steinbeck, C., & Zielesny, A. (2024). STOUT V2 - Model library. Zenodo. https://doi.org/10.5281/zenodo.13318286
+</blockquote>
 
-[![GitHub Logo](https://github.com/Kohulan/DECIMER-Image-to-SMILES/blob/master/assets/CheminfGit.png?raw=true)](https://cheminf.uni-jena.de)
+<h2 align="center">Repository Analytics</h2>
 
-![Alt](https://repobeats.axiom.co/api/embed/c66cc0ff5bc3ae91ccc8a3f7ed20eb05c735d753.svg "Repobeats analytics image")
+<p align="center">
+  <img src="https://repobeats.axiom.co/api/embed/c66cc0ff5bc3ae91ccc8a3f7ed20eb05c735d753.svg" alt="Repobeats analytics image">
+</p>
+
+<hr>
+
+<p align="center">
+  Made with ❤️ by the <a href="https://cheminf.uni-jena.de">Steinbeck Group</a> 
+</p>
diff --git a/STOUT/__init__.py b/STOUT/__init__.py
index e53f612..d6cf829 100644
--- a/STOUT/__init__.py
+++ b/STOUT/__init__.py
@@ -26,7 +26,7 @@
 
 """
 
-__version__ = "2.0.6"
+__version__ = "2.0.7"
 
 __all__ = [
     "STOUT",
diff --git a/STOUT/predictor_demo.py b/STOUT/predictor_demo.py
index 01218d3..67b6d02 100644
--- a/STOUT/predictor_demo.py
+++ b/STOUT/predictor_demo.py
@@ -4,7 +4,7 @@
 # STOUT - IUPAC name to SMILES example
 
 file_iupac = open(
-    "IUPAC_names_test.txt", "r"
+    "test_data/IUPAC_names_test.txt", "r"
 )  # file is available in the Github repository
 file_out = open("SMILES_predictions", "w")
 
@@ -17,7 +17,9 @@
 file_out.close()
 
 # STOUT - SMILES to IUPAC names example
-file_smiles = open("SMILES_test.txt", "r")  # file is available in the Github repository
+file_smiles = open(
+    "test_data/SMILES_test.txt", "r"
+)  # file is available in the Github repository
 file_out = open("IUPAC_predictions", "w")
 
 for i, line in enumerate(file_smiles):
diff --git a/STOUT/repack/helper.py b/STOUT/repack/helper.py
index 25e07b5..4d0c03e 100644
--- a/STOUT/repack/helper.py
+++ b/STOUT/repack/helper.py
@@ -1,101 +1,107 @@
-import os
 import tensorflow as tf
-import tensorflow.keras as keras
 import re
-import unicodedata
 import numpy as np
 import pystow
+from typing import List, Tuple, Dict
 import zipfile
-from jpype import startJVM, getDefaultJVMPath
-from jpype import JClass, JVMNotFoundException, isJVMStarted
-
-# Start JVM to use CDK in python
-try:
-    jvmPath = getDefaultJVMPath()
-except JVMNotFoundException:
-    print(
-        "If you see this message, for some reason JPype",
-        "cannot find jvm.dll.",
-        "This indicates that the environment varibale JAVA_HOME",
-        "is not set properly.",
-        "You can set it or set it manually in the code",
-    )
-    jvmPath = "Define/path/or/set/JAVA_HOME/variable/properly"
-if not isJVMStarted():
-    cdk_path = "https://github.com/cdk/cdk/releases/download/cdk-2.8/cdk-2.8.jar"
-    jar_path = str(pystow.join("STOUT-V2")) + "/cdk-2.8.jar"
-
-    if not os.path.exists(jar_path):
-        jar_path = pystow.ensure("STOUT-V2", url=cdk_path)
+from rdkit import Chem
 
-    startJVM(jvmPath, "-ea", "-Djava.class.path=" + str(jar_path))
 
-cdk_base = "org.openscience.cdk"
+def get_canonical(smiles: str) -> str:
+    """
+    Generate canonical SMILES representation from a given SMILES string.
 
-# Converts the unicode file to ascii
-def unicode_to_ascii(s: str) -> str:
-    """Converts a unicode string to an ASCII string
+    This function takes a SMILES string as input and generates its canonical representation using the RDKit library.
+    If the input SMILES string is valid, it returns the canonical SMILES representation; otherwise, it returns
+    "Invalid SMILES string".
 
     Args:
-        s (str): Takes a string in unicode format.
+        smiles (str): The input SMILES string.
 
     Returns:
-        str: returns a ASCII formatted string.
+        str: The canonical SMILES representation of the input molecule.
+
+    Note:
+        - The canonical SMILES representation is generated using RDKit's MolToSmiles function with kekuleSmiles
+          set to True and isomericSmiles set to True.
+        - If the input SMILES string is invalid (cannot be converted to a molecule), the function returns
+          None.
     """
+    mol = Chem.MolFromSmiles(smiles, sanitize=False)
+    if mol:
+        canonicalSMILES = Chem.MolToSmiles(mol, kekuleSmiles=True, isomericSmiles=True)
+        return canonicalSMILES
+    else:
+        return None
 
-    return "".join(
-        c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn"
-    )
 
+def preprocess_input(input_string: str) -> str:
+    """
+    Preprocess input string for a natural language processing task.
 
-def preprocess_sentence(w: str) -> str:
-    """Takes in a sentence, removes white spaces and generates a clean sentence. At the begining of the sentesnce a <start> token will be added
-    and at the end an <end> token will be added and the modified sentence will be returned.
+    This function takes an input string and adds special start and end tokens ("<start>" and "<end>") to it to
+    indicate the beginning and end of the input sequence for a natural language processing task.
 
     Args:
-        w (str): input sentence to be modified.
+        input_string (str): The input string to be preprocessed.
 
     Returns:
-        str: modified sentence with start and end tokens.
+        str: The preprocessed input string with start and end tokens added.
     """
-    w = unicode_to_ascii(w.strip())
+    input_string = "<start> " + input_string + " <end>"
+    return input_string
 
-    # creating a space between a word and the punctuation following it
-    # eg: "he is a boy." => "he is a boy ."
-    # Reference:- https://stackoverflow.com/questions/3645931/python-padding-punctuation-with-white-spaces-keeping-punctuation
-    w = re.sub(r"([?.!,¿])", r" \1 ", w)
-    w = re.sub(r'[" "]+', " ", w)
 
-    # replacing everything with space except (a-z, A-Z, ".", "?", "!", ",")
-    # w = re.sub(r"[^a-zA-Z?.!,¿]+", " ", w)
+def split_smiles(SMILES: str) -> str:
+    """
+    Splits a SMILES string into individual characters separated by spaces.
 
-    w = w.strip()
+    Args:
+        SMILES (str): The input SMILES string to be split.
 
-    # adding a start and an end token to the sentence
-    # so that the model know when to start and stop predicting.
-    w = "<start> " + w + " <end>"
-    return w
+    Returns:
+        str: The tokenized SMILES string where each character is separated by a space.
 
+    Example:
+        >>> split_smiles('CCO')
+        'C C O'
+    """
+    SMILES = SMILES.replace("\\/", "/")
+    canonical_SMILES = get_canonical(SMILES)
+    if canonical_SMILES:
+        splitted_list = list(canonical_SMILES)
+        tokenized_SMILES = re.sub(
+            r"\s+(?=[a-z])", "", " ".join(map(str, splitted_list))
+        )
+        return tokenized_SMILES
 
-def get_smiles_cdk(smiles: str) -> str:
-    """This function takes the user input SMILES and Canonicalize it
-       using the CDK Canonicalisation algorthim.
+
+def split_iupac(IUPACName: str) -> str:
+    """
+    Splits an IUPAC name into individual characters separated by spaces,
+    replacing spaces within the IUPAC name with a special character (§).
 
     Args:
-        smiles (string): SMILES string given by the user.
+        IUPACName (str): The input IUPAC name to be split.
 
     Returns:
-        smiles (string): Canonicalised and kekulized SMILES using the CDK.
+        str: The tokenized IUPAC name where each character is separated by a space and
+             spaces within the name are replaced with '§'.
+
+    Raises:
+        ValueError: If the IUPAC name cannot be processed.
 
+    Example:
+        >>> split_iupac('1,3,7-trimethylpurine-2,6-dione')
+        '1 , 3 , 7 - t r i m e t h y l p u r i n e - 2 , 6 - d i o n e'
     """
-    cdk_base = "org.openscience.cdk"
-    SCOB = JClass(cdk_base + ".DefaultChemObjectBuilder")
-    SmiFlavour = JClass(cdk_base + ".smiles.SmiFlavor")
-    SmilesGenerator = JClass(cdk_base + ".smiles.SmilesGenerator")(SmiFlavour.Absolute)
-    SmilesParser = JClass(cdk_base + ".smiles.SmilesParser")(SCOB.getInstance())
-    molecule = SmilesParser.parseSmiles(smiles)
-    CanSMILES = SmilesGenerator.create(molecule)
-    return CanSMILES
+    try:
+        splitted_list = list(IUPACName.replace(" ", "§"))
+        tokenized_IUPACname = " ".join(map(str, splitted_list))
+        return tokenized_IUPACname
+    except Exception as e:
+        print(e)
+        print(IUPACName)
 
 
 def tokenize_input(input_SMILES: str, inp_lang, inp_max_length: int) -> np.array:
@@ -111,9 +117,9 @@ def tokenize_input(input_SMILES: str, inp_lang, inp_max_length: int) -> np.array
         tokenized_input (np.array): The SMILES get split into meaningful chunks
         and gets converted into meaningful tokens. The tokens are arrays.
     """
-    sentence = preprocess_sentence(input_SMILES)
+    sentence = preprocess_input(input_SMILES)
     inputs = [inp_lang.word_index[i] for i in sentence.split(" ")]
-    tokenized_input = keras.preprocessing.sequence.pad_sequences(
+    tokenized_input = tf.keras.preprocessing.sequence.pad_sequences(
         [inputs], maxlen=inp_max_length, padding="post"
     )
 
@@ -121,35 +127,87 @@ def tokenize_input(input_SMILES: str, inp_lang, inp_max_length: int) -> np.array
 
 
 def detokenize_output(predicted_array: np.array, targ_lang) -> str:
-    """This function takes a predited input array and returns
-       a IUPAC name by detokenizing the input.
+    """
+    Detokenize the predicted output array.
+
+    This function takes a predicted output array, typically generated during natural language processing tasks,
+    and detokenizes it using the target language index to word mapping. It removes special tokens like "<start>"
+    and "<end>", and replaces any custom tokens such as "§" with spaces.
 
     Args:
-        predicted_array (np.array): The predicted_array is returned by the model.
-        targ_lang: keras_preprocessing.text.Tokenizer object with target language.
+        predicted_array (np.array): The predicted output array, usually containing token indices.
+        targ_lang: The target language mapping from index to word.
 
     Returns:
-        prediction (str): The predicted array gets detokenized by the tokenizer,
-        The unnessary spaces, start and the end tokens will bve removed and
-        a proper IUPAC name is returned in a string format.
+        str: The detokenized prediction as a single string.
+
+    Note:
+        - The function iterates through the predicted array and maps each token index to its corresponding word
+          using the target language mapping.
+        - Special tokens "<start>" and "<end>" are removed from the detokenized output.
+        - Custom tokens, such as "§", are replaced with spaces.
     """
     outputs = [targ_lang.index_word[i] for i in predicted_array[0].numpy()]
     prediction = (
-        " ".join([str(elem) for elem in outputs])
-        .replace("<start> ", "")
-        .replace(" <end>", "")
-        .replace(" ", "")
+        "".join([str(elem) for elem in outputs])
+        .replace("§", " ")
+        .replace("<start>", "")
+        .replace("<end>", "")
     )
-
     return prediction
 
 
-def create_look_ahead_mask(size):
+def detokenize_output_add_confidence(
+    predicted_array: tf.Tensor,
+    confidence_array: tf.Tensor,
+    targ_lang: Dict,
+) -> List[Tuple[str, float]]:
+    """
+    This function takes the predicted array of tokens as well as the confidence values
+    returned by the Transformer Decoder and returns a list of tuples
+    that contain each token and the confidence value.
+
+    Args:
+        predicted_array (tf.Tensor): Transformer Decoder output array (predicted tokens)
+
+    Returns:
+        str: SMILES string
+    """
+    prediction_with_confidence = [
+        (
+            targ_lang.index_word[predicted_array[0].numpy()[i]],
+            confidence_array[i].numpy(),
+        )
+        for i in range(len(confidence_array))
+    ]
+    prediction_with_confidence_ = prediction_with_confidence[1:-1]
+    return prediction_with_confidence_
+
+
+def create_look_ahead_mask(size: int) -> tf.Tensor:
+    """
+    Creates a look-ahead mask for masking future tokens in a sequence.
+
+    Args:
+        size (int): The size of the mask (sequence length).
+
+    Returns:
+        tf.Tensor: A look-ahead mask tensor of shape (size, size).
+    """
     mask = 1 - tf.linalg.band_part(tf.ones((size, size)), -1, 0)
     return mask  # (seq_len, seq_len)
 
 
-def create_padding_mask(seq):
+def create_padding_mask(seq: tf.Tensor) -> tf.Tensor:
+    """
+    Creates a padding mask for a given sequence.
+
+    Args:
+        seq (tf.Tensor): The input sequence tensor.
+
+    Returns:
+        tf.Tensor: A padding mask tensor of shape (batch_size, 1, 1, seq_len).
+    """
     seq = tf.cast(tf.math.equal(seq, 0), tf.float32)
 
     # add extra dimensions to add the padding
@@ -157,7 +215,17 @@ def create_padding_mask(seq):
     return seq[:, tf.newaxis, tf.newaxis, :]  # (batch_size, 1, 1, seq_len)
 
 
-def create_masks(inp, tar):
+def create_masks(inp: tf.Tensor, tar: tf.Tensor) -> tuple:
+    """
+    Creates the necessary masks for the Transformer model.
+
+    Args:
+        inp (tf.Tensor): The input tensor for the encoder.
+        tar (tf.Tensor): The target tensor for the decoder.
+
+    Returns:
+        tuple: A tuple containing the encoder padding mask, combined mask, and decoder padding mask.
+    """
     # Encoder padding mask
     enc_padding_mask = create_padding_mask(inp)
 
diff --git a/STOUT/repack/repack_model.py b/STOUT/repack/repack_model.py
index cee2519..6d45f47 100644
--- a/STOUT/repack/repack_model.py
+++ b/STOUT/repack/repack_model.py
@@ -6,9 +6,12 @@
 
 import tensorflow as tf
 import os
+import sys
 import pickle
-import transformer_model_4_repack as nmt_model_transformer
 import helper
+import transformer_model_4_repack as nmt_model_transformer
+
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
 print(tf.__version__)
 
@@ -18,6 +21,7 @@
 print(tf.__version__)
 
 # Always select a GPU if available
+
 os.environ["CUDA_VISIBLE_DEVICES"] = "0"
 
 # Scale memory growth as needed
@@ -115,6 +119,7 @@ def __call__(self, sentence: tf.Tensor[tf.int32]) -> tf.Tensor[tf.int64]:
 
         output_array = tf.TensorArray(dtype=tf.int64, size=0, dynamic_size=True)
         output_array = output_array.write(0, start)
+        confidence_array = tf.TensorArray(dtype=tf.float32, size=0, dynamic_size=True)
 
         for t in tf.range(targ_max_length):
             output = tf.transpose(output_array.stack())
@@ -138,13 +143,17 @@ def __call__(self, sentence: tf.Tensor[tf.int32]) -> tf.Tensor[tf.int64]:
 
             predicted_id = tf.argmax(predictions, axis=-1)
 
+            confidence = predictions[0, 0, int(predicted_id[0, 0])]
+            output_array = output_array.write(t + 1, predicted_id[0])
+            confidence_array = confidence_array.write(t + 1, confidence)
+
             output_array = output_array.write(t + 1, predicted_id[0])
 
             if predicted_id == end:
                 break
         output = tf.transpose(output_array.stack())
 
-        return output, sentence
+        return output, confidence_array.stack()
 
 
 # Create an instance of this Translator class
@@ -177,7 +186,7 @@ def __call__(self, sentence: tf.Tensor[tf.int32]) -> tf.Tensor[tf.int64]:
             tf.Tensor[tf.int64]: predicted output as an array.
         """
 
-        (result, tokens) = self.translator(sentence)
+        result = self.translator(sentence)
 
         return result
 
diff --git a/STOUT/repack/transformer_model_4_repack.py b/STOUT/repack/transformer_model_4_repack.py
index 84ea3e6..e4ab821 100644
--- a/STOUT/repack/transformer_model_4_repack.py
+++ b/STOUT/repack/transformer_model_4_repack.py
@@ -1,13 +1,35 @@
 import tensorflow as tf
 import numpy as np
+from typing import Tuple, Optional
 
 
-def get_angles(pos, i, d_model):
+def get_angles(pos: int, i: int, d_model: int) -> np.ndarray:
+    """
+    Computes the angles for the positional encoding.
+
+    Args:
+        pos (int): The position index.
+        i (int): The dimension index.
+        d_model (int): The model dimensionality.
+
+    Returns:
+        np.ndarray: The computed angles for the given position and dimension.
+    """
     angle_rates = 1 / np.power(10000, (2 * (i // 2)) / np.float32(d_model))
     return pos * angle_rates
 
 
-def positional_encoding(position, d_model):
+def positional_encoding(position: int, d_model: int) -> tf.Tensor:
+    """
+    Generates the positional encoding for a given position and model dimensionality.
+
+    Args:
+        position (int): The maximum position index.
+        d_model (int): The dimensionality of the model.
+
+    Returns:
+        tf.Tensor: A tensor representing the positional encoding.
+    """
     angle_rads = get_angles(
         np.arange(position)[:, np.newaxis], np.arange(d_model)[np.newaxis, :], d_model
     )
@@ -23,24 +45,26 @@ def positional_encoding(position, d_model):
     return tf.cast(pos_encoding, dtype=tf.float32)
 
 
-def scaled_dot_product_attention(q, k, v, mask):
-    """Calculate the attention weights.
+def scaled_dot_product_attention(
+    q: tf.Tensor, k: tf.Tensor, v: tf.Tensor, mask: Optional[tf.Tensor] = None
+) -> Tuple[tf.Tensor, tf.Tensor]:
+    """
+    Calculate the attention weights.
     q, k, v must have matching leading dimensions.
     k, v must have matching penultimate dimension, i.e.: seq_len_k = seq_len_v.
-    The mask has different shapes depending on its type(padding or look ahead)
+    The mask has different shapes depending on its type (padding or look ahead)
     but it must be broadcastable for addition.
 
     Args:
-      q: query shape == (..., seq_len_q, depth)
-      k: key shape == (..., seq_len_k, depth)
-      v: value shape == (..., seq_len_v, depth_v)
-      mask: Float tensor with shape broadcastable
-            to (..., seq_len_q, seq_len_k). Defaults to None.
+        q: query shape == (..., seq_len_q, depth)
+        k: key shape == (..., seq_len_k, depth)
+        v: value shape == (..., seq_len_v, depth_v)
+        mask: Float tensor with shape broadcastable
+              to (..., seq_len_q, seq_len_k). Defaults to None.
 
     Returns:
-      output, attention_weights
+        Tuple[tf.Tensor, tf.Tensor]: output, attention_weights
     """
-
     matmul_qk = tf.matmul(q, k, transpose_b=True)  # (..., seq_len_q, seq_len_k)
 
     # scale matmul_qk
@@ -138,7 +162,6 @@ def __init__(self, d_model, num_heads, dff, rate=0.1):
         self.dropout2 = tf.keras.layers.Dropout(rate)
 
     def call(self, x, training, mask):
-
         attn_output, _ = self.mha(x, x, x, mask)  # (batch_size, input_seq_len, d_model)
         attn_output = self.dropout1(attn_output, training=training)
         out1 = self.layernorm1(x + attn_output)  # (batch_size, input_seq_len, d_model)
@@ -219,7 +242,6 @@ def __init__(
         self.dropout = tf.keras.layers.Dropout(rate)
 
     def call(self, x, training, mask):
-
         seq_len = tf.shape(x)[1]
 
         # adding embedding and position encoding.
@@ -260,7 +282,6 @@ def __init__(
         self.dropout = tf.keras.layers.Dropout(rate)
 
     def call(self, x, enc_output, training, look_ahead_mask, padding_mask):
-
         seq_len = tf.shape(x)[1]
         attention_weights = {}
 
@@ -305,10 +326,11 @@ def __init__(
             num_layers, d_model, num_heads, dff, target_vocab_size, pe_target, rate
         )
 
-        self.final_layer = tf.keras.layers.Dense(target_vocab_size)
+        self.final_layer = tf.keras.layers.Dense(
+            target_vocab_size, activation="softmax"
+        )
 
     def call(self, inputs, training):
-
         inp, tar, enc_padding_mask, look_ahead_mask, dec_padding_mask = inputs
 
         enc_output = self.tokenizer(
diff --git a/STOUT/stout.py b/STOUT/stout.py
index 2744198..2f8e12f 100644
--- a/STOUT/stout.py
+++ b/STOUT/stout.py
@@ -4,7 +4,6 @@
 import os
 import pickle
 import pystow
-import re
 import logging
 from .repack import helper
 
@@ -14,9 +13,6 @@
 # Silence tensorflow errors. optional not recommened if your model is not working properly.
 os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
 
-# Print tensorflow version
-# print("Tensorflow version: " + tf.__version__)
-
 # Always select a GPU if available
 os.environ["CUDA_VISIBLE_DEVICES"] = "0"
 
@@ -29,9 +25,9 @@
 default_path = pystow.join("STOUT-V2", "models")
 
 # model download location
-model_url = "https://storage.googleapis.com/decimer_weights/models.zip"
+model_url = "https://zenodo.org/records/12542360/files/models.zip?download=1"
 model_path = str(default_path) + "/translator_forward/"
-
+print(model_path)
 # download models to a default location
 if not os.path.exists(model_path):
     helper.download_trained_weights(model_url, default_path)
@@ -44,18 +40,23 @@
 reloaded_reverse = tf.saved_model.load(default_path.as_posix() + "/translator_reverse")
 
 
-def translate_forward(smiles: str) -> str:
-    """Takes user input splits them into words and generates tokens.
-    Tokens are then passed to the model and the model predicted tokens are retrieved.
-    The predicted tokens gets detokenized and the final result is returned in a string format.
+def load_forward_translation_utils() -> tuple:
+    """Loads essential utilities for forward translation, including input and
+    target tokenizers and the maximum input length.
 
-    Args:
-        smiles (str): user input SMILES in string format.
+    This function loads pre-trained tokenizers for input and target languages from pickle files and sets the maximum length
+    for input sequences. The pickle files are assumed to be located in the 'assets' directory relative to the default path.
 
     Returns:
-        result (str): The predicted IUPAC names in string format.
+        tuple: A tuple containing:
+            - inp_lang (Tokenizer): The input language tokenizer.
+            - targ_lang (Tokenizer): The target language tokenizer.
+            - inp_max_length (int): The maximum length for input sequences.
+
+    Raises:
+        FileNotFoundError: If the tokenizer pickle files are not found in the specified directory.
+        pickle.UnpicklingError: If there is an error while unpickling the tokenizer files.
     """
-
     # Load important pickle files which consists the tokenizers and the maxlength setting
     inp_lang = pickle.load(
         open(default_path.as_posix() + "/assets/tokenizer_input.pkl", "rb")
@@ -63,54 +64,109 @@ def translate_forward(smiles: str) -> str:
     targ_lang = pickle.load(
         open(default_path.as_posix() + "/assets/tokenizer_target.pkl", "rb")
     )
-    inp_max_length = pickle.load(
-        open(default_path.as_posix() + "/assets/max_length_inp.pkl", "rb")
-    )
-    if len(smiles) == 0:
-        return ""
-    smiles = smiles.replace("\\/", "/")
-    smiles_canon = helper.get_smiles_cdk(smiles)
-    if smiles_canon:
-        splitted_list = list(smiles_canon)
-        tokenized_SMILES = re.sub(
-            r"\s+(?=[a-z])", "", " ".join(map(str, splitted_list))
-        )
-        decoded = helper.tokenize_input(tokenized_SMILES, inp_lang, inp_max_length)
-        result_predited = reloaded_forward(decoded)
-        result = helper.detokenize_output(result_predited, targ_lang)
-        return result
-    else:
-        return "Could not generate IUPAC name for SMILES provided."
+    inp_max_length = 602
+    return inp_lang, targ_lang, inp_max_length
 
 
-def translate_reverse(iupacname: str) -> str:
-    """Takes user input splits them into words and generates tokens.
-    Tokens are then passed to the model and the model predicted tokens are retrieved.
-    The predicted tokens gets detokenized and the final result is returned in a string format.
+def load_reverse_translation_utils() -> tuple:
+    """Loads necessary utilities for reverse translation from pickle files.
 
-    Args:
-        iupacname (str): user input IUPAC names in string format.
+    This function loads the input and target tokenizers as well as the
+    maximum length setting for input sequences. The tokenizers are loaded
+    from pickle files located in the 'assets' directory under the default path.
 
     Returns:
-        result (str): The predicted SMILES in string format.
+        tuple: A tuple containing the following elements:
+            - inp_lang (object): The tokenizer for the input language.
+            - targ_lang (object): The tokenizer for the target language.
+            - inp_max_length (int): The maximum length of input sequences.
+
+    Raises:
+        FileNotFoundError: If any of the pickle files cannot be found.
+        pickle.UnpicklingError: If there is an error unpickling the files.
     """
-
-    # Load important pickle files which consists the tokenizers and the maxlength setting
     targ_lang = pickle.load(
         open(default_path.as_posix() + "/assets/tokenizer_input.pkl", "rb")
     )
     inp_lang = pickle.load(
         open(default_path.as_posix() + "/assets/tokenizer_target.pkl", "rb")
     )
-    inp_max_length = pickle.load(
-        open(default_path.as_posix() + "/assets/max_length_targ.pkl", "rb")
-    )
+    inp_max_length = 602
+    return inp_lang, targ_lang, inp_max_length
+
 
-    splitted_list = list(iupacname)
-    tokenized_IUPACname = " ".join(map(str, splitted_list))
-    decoded = helper.tokenize_input(tokenized_IUPACname, inp_lang, inp_max_length)
+def translate_forward(smiles: str, add_confidence: bool = False) -> str:
+    """
+    Takes user input, splits them into words, and generates tokens. Tokens are
+    then passed to the model and the model-predicted tokens are retrieved. The
+    predicted tokens get detokenized and the final result is returned in a
+    string format. If add_confidence is true, a list of tuples is returned,
+    where the first element is the token and the second element is the
+    confidence value.
 
-    result_predited = reloaded_reverse(decoded)
-    result = helper.detokenize_output(result_predited, targ_lang)
+    Args:
+        smiles (str): User input SMILES in string format.
+        add_confidence (bool): If True, the confidence values of the predicted tokens
+        are returned as well.
 
+    Returns:
+        result (str): The predicted IUPAC names in string format.
+        result (List[tuples]): Tokens and confidence values
+    """
+    # TODO: loading this for every call is inefficient
+    # --> move to init of a translator class
+    inp_lang, targ_lang, inp_max_length = load_forward_translation_utils()
+
+    if len(smiles) == 0:
+        return "Check SMILES string"
+    else:
+        canonical_smiles = helper.split_smiles(smiles)
+        if canonical_smiles:
+            decoded = helper.tokenize_input(canonical_smiles, inp_lang, inp_max_length)
+            result_predited, confidence_array = reloaded_forward(decoded)
+            if add_confidence:
+                result = helper.detokenize_output_add_confidence(
+                    result_predited, confidence_array, targ_lang
+                )
+            else:
+                result = helper.detokenize_output(result_predited, targ_lang)
+            return result
+        else:
+            return "Could not generate IUPAC name for SMILES provided."
+
+
+def translate_reverse(iupacname: str, add_confidence: bool = False) -> str:
+    """Takes user input splits them into words and generates tokens. Tokens are
+    then passed to the model and the model predicted tokens are retrieved. The
+    predicted tokens get detokenized and the final result is returned in a
+    string format. If add_confidence is true, a list of tuples is returned,
+    where the first element is the token and the second element is the
+    confidence value.
+
+    Args:
+        iupacname (str): user input IUPAC names in string format.
+        add_confidence (bool): If True, the confidence values of the predicted tokens
+        are returned as well.
+
+    Returns:
+        result (str): The predicted SMILES in string format.
+        OR
+        result(List[tuples]) Tokens, confidence values
+    """
+    # TODO: loading this for every call is inefficient
+    # --> move to init of a translator class
+    inp_lang, targ_lang, inp_max_length = load_reverse_translation_utils()
+    if len(iupacname) == 0:
+        return "Check IUPAC name string"
+    else:
+        splitted_name = helper.split_iupac(iupacname)
+
+    decoded = helper.tokenize_input(splitted_name, inp_lang, inp_max_length)
+    result_predited, confidence_array = reloaded_reverse(decoded)
+    if add_confidence:
+        result = helper.detokenize_output_add_confidence(
+            result_predited, confidence_array, targ_lang
+        )
+    else:
+        result = helper.detokenize_output(result_predited, targ_lang)
     return result
diff --git a/STOUT/trainer/STOUT_V2_Trainer.py b/STOUT/trainer/STOUT_V2_Trainer.py
index b7cc09a..931e449 100644
--- a/STOUT/trainer/STOUT_V2_Trainer.py
+++ b/STOUT/trainer/STOUT_V2_Trainer.py
@@ -75,7 +75,6 @@ def read_tfrecord(example):
 
 
 def get_training_dataset(batch_size=BATCH_SIZE, buffered_size=BUFFER_SIZE):
-
     options = tf.data.Options()
     filenames = sorted(
         tf.io.gfile.glob(
@@ -98,7 +97,6 @@ def get_training_dataset(batch_size=BATCH_SIZE, buffered_size=BUFFER_SIZE):
 
 
 def get_validation_dataset(batch_size=BATCH_SIZE, buffered_size=BUFFER_SIZE):
-
     options = tf.data.Options()
     filenames = sorted(
         tf.io.gfile.glob(
@@ -361,7 +359,6 @@ def step_fn(inputs):
         validation_batch += 1
 
         if validation_batch == validation_steps:
-
             print(
                 "Validation_Loss {:.4f} Accuracy {:.4f}".format(
                     validation_loss.result(), validation_accuracy.result()
diff --git a/STOUT/trainer/nmt_model_transformer.py b/STOUT/trainer/nmt_model_transformer.py
index 57e7dc7..d02cf6c 100644
--- a/STOUT/trainer/nmt_model_transformer.py
+++ b/STOUT/trainer/nmt_model_transformer.py
@@ -138,7 +138,6 @@ def __init__(self, d_model, num_heads, dff, rate=0.1):
         self.dropout2 = tf.keras.layers.Dropout(rate)
 
     def call(self, x, training, mask):
-
         attn_output, _ = self.mha(x, x, x, mask)  # (batch_size, input_seq_len, d_model)
         attn_output = self.dropout1(attn_output, training=training)
         out1 = self.layernorm1(x + attn_output)  # (batch_size, input_seq_len, d_model)
@@ -219,7 +218,6 @@ def __init__(
         self.dropout = tf.keras.layers.Dropout(rate)
 
     def call(self, x, training, mask):
-
         seq_len = tf.shape(x)[1]
 
         # adding embedding and position encoding.
@@ -260,7 +258,6 @@ def __init__(
         self.dropout = tf.keras.layers.Dropout(rate)
 
     def call(self, x, enc_output, training, look_ahead_mask, padding_mask):
-
         seq_len = tf.shape(x)[1]
         attention_weights = {}
 
@@ -310,7 +307,6 @@ def __init__(
     def call(
         self, inp, tar, training, enc_padding_mask, look_ahead_mask, dec_padding_mask
     ):
-
         enc_output = self.tokenizer(
             inp, training, enc_padding_mask
         )  # (batch_size, inp_seq_len, d_model)
diff --git a/Untitled.ipynb b/Untitled.ipynb
new file mode 100644
index 0000000..c7bc551
--- /dev/null
+++ b/Untitled.ipynb
@@ -0,0 +1,101 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "cef08029",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "2024-03-14 13:35:29.500202: I tensorflow/core/util/port.cc:113] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n",
+      "2024-03-14 13:35:29.537715: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
+      "2024-03-14 13:35:29.537739: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
+      "2024-03-14 13:35:29.538676: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n",
+      "2024-03-14 13:35:29.544841: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n",
+      "To enable the following instructions: AVX2 AVX512F AVX512_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n",
+      "2024-03-14 13:35:30.202257: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Yo\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "2024-03-14 13:35:33.404520: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1929] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 30948 MB memory:  -> device: 0, name: Tesla V100-PCIE-32GB, pci bus id: 0000:40:00.0, compute capability: 7.0\n"
+     ]
+    }
+   ],
+   "source": [
+    "from STOUT import translate_forward, translate_reverse"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "e7ae0589",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "2024-03-14 13:36:02.299442: I external/local_tsl/tsl/platform/default/subprocess.cc:304] Start cannot spawn child process: No such file or directory\n"
+     ]
+    },
+    {
+     "ename": "ValueError",
+     "evalue": "not enough values to unpack (expected 2, got 1)",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
+      "\u001b[0;32m/tmp/ipykernel_213288/598744495.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtranslate_forward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"CN1C=NC2=C1C(=O)N(C(=O)N2C)C\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+      "\u001b[0;32m/mnt/raid_drive/STOUTV2/ZINC/STOUT_trained_model_V4/Smiles-TO-iUpac-Translator/STOUT/stout.py\u001b[0m in \u001b[0;36mtranslate_forward\u001b[0;34m(smiles, add_confidence)\u001b[0m\n\u001b[1;32m    104\u001b[0m         )\n\u001b[1;32m    105\u001b[0m         \u001b[0mdecoded\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhelper\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtokenize_input\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtokenized_SMILES\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minp_lang\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minp_max_length\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 106\u001b[0;31m         \u001b[0mresult_predited\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mconfidence_array\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mreloaded_forward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdecoded\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    107\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0madd_confidence\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    108\u001b[0m             result = helper.detokenize_output_add_confidence(\n",
+      "\u001b[0;31mValueError\u001b[0m: not enough values to unpack (expected 2, got 1)"
+     ]
+    }
+   ],
+   "source": [
+    "translate_forward(\"CN1C=NC2=C1C(=O)N(C(=O)N2C)C\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "faf63ca4",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "DECIMER_V2",
+   "language": "python",
+   "name": "myenv"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d4bb2cb
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/STOUT.rst b/docs/STOUT.rst
new file mode 100644
index 0000000..43b9d0b
--- /dev/null
+++ b/docs/STOUT.rst
@@ -0,0 +1,29 @@
+STOUT package
+=============
+
+Submodules
+----------
+
+STOUT.stout module
+------------------
+
+.. automodule:: STOUT.stout
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+STOUT.repack.helper module
+--------------------------
+
+.. automodule:: STOUT.repack.helper 
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: STOUT
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/_static/STOUT.png b/docs/_static/STOUT.png
new file mode 100644
index 0000000..a6d7e15
Binary files /dev/null and b/docs/_static/STOUT.png differ
diff --git a/docs/_static/STOUT.svg b/docs/_static/STOUT.svg
new file mode 100644
index 0000000..9dc7c31
--- /dev/null
+++ b/docs/_static/STOUT.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="_4c46ae78-3341-4eb6-857f-8cc785f3b307_Image" data-name="_4c46ae78-3341-4eb6-857f-8cc785f3b307 Image" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 171.75 366">
+  <image id="_4c46ae78-3341-4eb6-857f-8cc785f3b307_Image-2" data-name="_4c46ae78-3341-4eb6-857f-8cc785f3b307 Image" width="229" height="488" transform="scale(.75)" xlink:href="data:image/png;base64,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"/>
+</svg>
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..c518efc
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,88 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath(".."))
+import STOUT
+from datetime import datetime
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = "STOUT - Smiles-TO-iUpac-Translator"
+version = STOUT.__version__
+current_year = datetime.today().year
+copyright = "2021-{}, Kohulan Rajan at the Friedrich Schiller University Jena".format(
+    current_year
+)
+author = "Kohulan Rajan"
+rst_prolog = """
+.. |current_year| replace:: {}
+""".format(
+    current_year
+)
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+    "sphinx.ext.autodoc",
+    "sphinx_autodoc_typehints",
+    "sphinx.ext.autosummary",
+    "nbsphinx",
+    "sphinx.ext.mathjax",
+    "sphinx.ext.githubpages",
+    "sphinx.ext.viewcode",
+]
+
+templates_path = ["_templates"]
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+source_suffix = ".rst"
+
+# The master toctree document.
+master_doc = "index"
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = "sphinx"
+
+# Decides the language used for syntax highlighting of code blocks.
+highlight_language = "python3"
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = "furo"
+
+html_theme_options = {
+    "light_css_variables": {
+        "color-brand-primary": "#E37B74",
+        "color-brand-content": "#E37B74",
+        "color-code-background": "#F8F8F8",
+        "color-code-border": "#E37B74",
+        "color-admonition-background": "#FEECEC",
+        "color-link": "#E37B74",
+        "color-pre-background": "#F8F8F8",
+        "color-pre-border": "#E37B74",
+    },
+    "dark_css_variables": {
+        "color-brand-primary": "#E37B74",
+        "color-brand-content": "#E37B74",
+        "color-code-background": "#222222",
+        "color-code-border": "#E37B74",
+        "color-admonition-background": "#331E1C",
+        "color-link": "#E37B74",
+        "color-pre-background": "#222222",
+        "color-pre-border": "#E37B74",
+    },
+    "sidebar_hide_name": True,
+    "navigation_with_keys": True,
+    "top_of_page_button": "edit",
+}
+
+html_static_path = ["_static"]
+html_favicon = "_static/STOUT.svg"
+html_logo = "_static/STOUT.png"
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..a2fb906
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,38 @@
+.. STOUT - Smiles-TO-iUpac-Translator documentation master file, created by
+   sphinx-quickstart on Tue Jul  2 12:39:46 2024.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to STOUT - Smiles-TO-iUpac-Translator's documentation!
+==============================================================
+
+.. image:: https://github.com/Kohulan/Smiles-TO-iUpac-Translator/blob/development/docs/_static/STOUT.png?raw=true
+  :width: 500
+  :align: center
+
+STOUT V2, a transformer-based model that accurately translates SMILES strings into IUPAC names. Trained on a dataset of nearly 1 billion molecules, STOUT V2 achieves high accuracy on test and benchmark datasets. a web application is accessible at stout.decimer.ai.
+
+For comments, bug reports or feature ideas, please use github issues
+or send an email to kohulan.rajan@uni-jena.de
+
+Installation
+============
+
+Install STOUT in the command line using pip:
+
+.. code-block::
+
+   pip install STOUT-pypi
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   modules
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..32bb245
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/modules.rst b/docs/modules.rst
new file mode 100644
index 0000000..2f777cb
--- /dev/null
+++ b/docs/modules.rst
@@ -0,0 +1,7 @@
+STOUT
+=====
+
+.. toctree::
+   :maxdepth: 4
+
+   STOUT
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..a3a8a21
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,10 @@
+sphinx_autodoc_typehints
+tensorflow==2.15.0
+pystow
+unicodedata2
+jpype1
+rdkit
+furo
+nbsphinx
+sphinx_material
+sphinx_rtd_theme
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index da9973b..fd6fadf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
-tensorflow-gpu==2.12.0
+tensorflow==2.15.0
 pystow
 unicodedata2
 jpype1
+rdkit
diff --git a/setup.py b/setup.py
index fda350e..eb9bb80 100644
--- a/setup.py
+++ b/setup.py
@@ -3,17 +3,19 @@
 import setuptools
 import platform
 
-if (platform.processor() == 'arm' or platform.processor() == 'i386') and platform.system() == 'Darwin':
-    tensorflow_os = "tensorflow-macos==2.10.0"
+if (
+    platform.processor() == "arm" or platform.processor() == "i386"
+) and platform.system() == "Darwin":
+    tensorflow_os = "tensorflow-macos==2.15.0"
 else:
-    tensorflow_os = "tensorflow==2.10.1"
-    
+    tensorflow_os = "tensorflow==2.15.0"
+
 with open("README.md", "r") as fh:
     long_description = fh.read()
 
 setuptools.setup(
     name="STOUT-pypi",
-    version="2.0.6",
+    version="2.0.7",
     author="Kohulan Rajan",
     author_email="kohulan.rajan@uni-jena.de",
     maintainer="Kohulan Rajan",
@@ -29,6 +31,7 @@
         "pystow",
         "unicodedata2",
         "jpype1",
+        "rdkit",
     ],
     package_data={"STOUT": ["repack/*.*", "trainer/*.*"]},
     classifiers=[


Test patch:
diff --git a/STOUT/IUPAC_names_test.txt b/STOUT/test_data/IUPAC_names_test.txt
similarity index 100%
rename from STOUT/IUPAC_names_test.txt
rename to STOUT/test_data/IUPAC_names_test.txt
diff --git a/STOUT/SMILES_test.txt b/STOUT/test_data/SMILES_test.txt
similarity index 100%
rename from STOUT/SMILES_test.txt
rename to STOUT/test_data/SMILES_test.txt
diff --git a/Tests/test_functions.py b/tests/test_functions.py
similarity index 97%
rename from Tests/test_functions.py
rename to tests/test_functions.py
index 9edc5f3..a304b0f 100644
--- a/Tests/test_functions.py
+++ b/tests/test_functions.py
@@ -1,4 +1,3 @@
-import pytest
 from STOUT import translate_forward, translate_reverse