# Path: guacamol/utils/chemistry.py
# def smiles_to_rdkit_mol(smiles: str) -> Optional[Chem.Mol]:
#     """
#     Converts a SMILES string to a RDKit molecule.
# 
#     Args:
#         smiles: SMILES string of the molecule
# 
#     Returns:
#         RDKit Mol, None if the SMILES string is invalid
#     """
#     mol = Chem.MolFromSmiles(smiles)
# 
#     #  Sanitization check (detects invalid valence)
#     if mol is not None:
#         try:
#             Chem.SanitizeMol(mol)
#         except ValueError:
#             return None
# 
#     return mol
# 
# Path: guacamol/score_modifier.py
# class ScoreModifier:
#     """
#     Interface for score modifiers.
#     """
# 
#     @abstractmethod
#     def __call__(self, x):
#         """
#         Apply the modifier on x.
# 
#         Args:
#             x: float or np.array to modify
# 
#         Returns:
#             float or np.array (depending on the type of x) after application of the distance function.
#         """
# 
# class LinearModifier(ScoreModifier):
#     """
#     Score modifier that multiplies the score by a scalar (default: 1, i.e. do nothing).
#     """
# 
#     def __init__(self, slope=1.0):
#         self.slope = slope
# 
#     def __call__(self, x):
#         return self.slope * x
# 
# Path: guacamol/utils/math.py
# def geometric_mean(values: List[float]) -> float:
#     """
#     Computes the geometric mean of a list of values.
#     """
#     a = np.array(values)
#     return a.prod() ** (1.0 / len(a))


from abc import abstractmethod
from typing import List, Optional
from rdkit import Chem
from guacamol.utils.chemistry import smiles_to_rdkit_mol
from guacamol.score_modifier import ScoreModifier, LinearModifier
from guacamol.utils.math import geometric_mean
import logging
import numpy as np


logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())


class InvalidMolecule(Exception):
    pass


class ScoringFunction:
    """
    Base class for an objective function.

    In general, do not inherit directly from this class. Prefer `MoleculewiseScoringFunction` or `BatchScoringFunction`.
    """

    def __init__(self, score_modifier: ScoreModifier = None) -> None:
        """
        Args:
            score_modifier: Modifier to apply to the score. If None, will be LinearModifier()
        """
        self.score_modifier = score_modifier
        self.corrupt_score = -1.0

    @property
    def score_modifier(self):
        return self._score_modifier

    @score_modifier.setter

# Path: guacamol/utils/chemistry.py
# def smiles_to_rdkit_mol(smiles: str) -> Optional[Chem.Mol]:
#     """
#     Converts a SMILES string to a RDKit molecule.
# 
#     Args:
#         smiles: SMILES string of the molecule
# 
#     Returns:
#         RDKit Mol, None if the SMILES string is invalid
#     """
#     mol = Chem.MolFromSmiles(smiles)
# 
#     #  Sanitization check (detects invalid valence)
#     if mol is not None:
#         try:
#             Chem.SanitizeMol(mol)
#         except ValueError:
#             return None
# 
#     return mol
# 
# Path: guacamol/score_modifier.py
# class ScoreModifier:
#     """
#     Interface for score modifiers.
#     """
# 
#     @abstractmethod
#     def __call__(self, x):
#         """
#         Apply the modifier on x.
# 
#         Args:
#             x: float or np.array to modify
# 
#         Returns:
#             float or np.array (depending on the type of x) after application of the distance function.
#         """
# 
# class LinearModifier(ScoreModifier):
#     """
#     Score modifier that multiplies the score by a scalar (default: 1, i.e. do nothing).
#     """
# 
#     def __init__(self, slope=1.0):
#         self.slope = slope
# 
#     def __call__(self, x):
#         return self.slope * x
# 
# Path: guacamol/utils/math.py
# def geometric_mean(values: List[float]) -> float:
#     """
#     Computes the geometric mean of a list of values.
#     """
#     a = np.array(values)
#     return a.prod() ** (1.0 / len(a))


# Path: guacamol/scoring_function.py
from abc import abstractmethod
from typing import List, Optional
from rdkit import Chem
from guacamol.utils.chemistry import smiles_to_rdkit_mol
from guacamol.score_modifier import ScoreModifier, LinearModifier
from guacamol.utils.math import geometric_mean
import logging
import numpy as np



logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())


class InvalidMolecule(Exception):
    pass


class ScoringFunction:
    """
    Base class for an objective function.

    In general, do not inherit directly from this class. Prefer `MoleculewiseScoringFunction` or `BatchScoringFunction`.
    """

    def __init__(self, score_modifier: ScoreModifier = None) -> None:
        """
        Args:
            score_modifier: Modifier to apply to the score. If None, will be LinearModifier()
        """
        self.score_modifier = score_modifier
        self.corrupt_score = -1.0

    @property
    def score_modifier(self):
        return self._score_modifier

    @score_modifier.setter

    def score_modifier(self, modifier: Optional[ScoreModifier]):