<commit_before># Copyright 2015, <NAME> <<EMAIL>>
#
# This file is part of Azrael (https://github.com/olitheolix/azrael)
#
# Azrael is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Azrael 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Azrael. If not, see <http://www.gnu.org/licenses/>.
"""
Igor is a stateless class to manage rigid body constraints.
"""
import logging
import azrael.config as config
import azrael.database as database

from IPython import embed as ipshell
from azrael.types import RetVal, ConstraintMeta, ConstraintP2P
from azrael.types import Constraint6DofSpring2

# List of known constraints and the associated named tuple.
_Known_Constraints = {
    'P2P': ConstraintP2P,
    '6DOFSPRING2': Constraint6DofSpring2,
}


class Igor:
    """
    """
    def __init__(self):
        # Create a Class-specific logger.
        name = '.'.join([__name__, self.__class__.__name__])
        self.logit = logging.getLogger(name)

        # Create the database handle and local constraint cache.
        self.db = database.dbHandles['Constraints']
        self._cache = {}

    def reset(self):
        """
        Flush the constraint database.

        :return: success
        """
        self.db.drop()
        self._cache = {}
        return RetVal(True, None, None)

    def updateLocalCache(self):
        """
        Download *all* constraints from the database into the local cache.

        :return: Number of unique constraints in local cache after operation.
        """
        # Create a Mongo cursor that retrieves all constraints (minus the _id
        # field).
        prj = {'_id': False}
        cursor = self.db.find({}, prj)

        # Flush the cache.
        self._cache = {}

        # Convenience.
        cache = self._cache
        CM = ConstraintMeta

        # Build an iterator that returns the constraints from the data base and
        # wraps them into ConstraintMeta tuples.
        constraints = (CM(**_) for _ in cursor)

        # Iterate over all constraints and substitute the 'condata' attribute
        # with the correct named tuple for the respective constraint. This will
        # build a dictionary where the keys are ConstraintMeta tuple with a
        # value of *None* for the data field, and the value is the same
        # `ConstraintMeta` data but with a valid 'condata' attribute.
        for con in constraints:
            try:
                NT = _Known_Constraints[con.contype.upper()]
                con = con._replace(condata=NT(**con.condata))
            except KeyError:
                # Skip over unknown constraints.
                msg = 'Ignoring unknown constraint {}'.format(con.contype)
                self.logit.info(msg)
                continue

            # Replace the 'condata' field in the constraint. This will become
            # the key for the self._cache dictionary.
            key = con._replace(condata=None)
            cache[key] = con
            del con, key

        # Return the number of valid constraints now in the cache.
        return RetVal(True, None, len(self._cache))

    def addConstraints(self, constraints: (tuple, list)):
        """
        Add all ``constraints`` to the database.

        All entries in ``constraints`` must be ``ConstraintMeta`` instances,
        and their `data` attribute must be a valid ``Consraint***`` instance.

        This method will skip over all constraints with an invalid/unknown
        type.

        It will return the number of constraints

        fixme: add sanity checks.

        :param list constraints: a list of ``ConstraintMeta`` instances.
        :return: number of newly added constraints.
        """
        queries = []
        for con in constraints:
            # Skip all constraints with an unknown type.
            if con.contype.upper() not in _Known_Constraints:
                continue

            # Convenience.
            rb_a, rb_b = con.rb_a, con.rb_b

            # The first body must not be None.
            if rb_a is None or rb_b is None:
                continue

            # Sort the body IDs. This will simplify the logic to fetch- and
            # process constraints.
            rb_a, rb_b = sorted((rb_a, rb_b))
            con = con._replace(rb_a=rb_a, rb_b=rb_b)

            # Convert content of the 'condata' field into a dictionary to store
            # it in MongoDB without loosing the attribute names.
            con = con._replace(condata=con.condata._asdict())

            # Insert the constraints into MongoDB. The constraint query must
            # match both objects IDs, the type, and the constraint ID.
            tmp = {'rb_a': rb_a, 'rb_b': rb_b,
                   'contype': con.contype, 'aid': con.aid}
            queries.append((tmp, con._asdict()))

        # Return immediately if the list of constraints to add is empty.
        if len(queries) == 0:
            return RetVal(True, None, 0)

        # Compile the bulk query and execute it.
        bulk = self.db.initialize_unordered_bulk_op()
        for q, c in queries:
            bulk.find(q).upsert().update({'$setOnInsert': c})
        ret = bulk.execute()

        # Return the number of newly created constraints.
        return RetVal(True, None, ret['nUpserted'])

    def getConstraints(self, bodyIDs: (set, tuple, list)):
        """
        Return all constraints that involve any of the bodies in ``bodyIDs``.

        ..note:: this method only consults the local cache. Depending on your
                 circumstances you may want to call ``updateLocalCache``
                 first.

        :param list[int] bodyIDs: list of body IDs
        :return: list of ``ConstraintMeta`` instances.
        :rtype: tuple
        """
        # Sanity check: remove all non-integers and turn the list into a set
        # for fast look ups.
        bodyIDs = set([_ for _ in bodyIDs if isinstance(_, int)])

        # Iterate over all constraints and pick the ones that contain at least
        # one of the bodies specified in `bodyIDs`.
        out = []
        for tmp in self._cache:
            if not (tmp.rb_a in bodyIDs or tmp.rb_b in bodyIDs):
                continue
            out.append(self._cache[tmp])
        return RetVal(True, None, tuple(out))

    def getAllConstraints(self):
        """
        Return all constraints in the local cache.

        ..note:: this method only consults the local cache. Depending on your
                 circumstances you may want to to call ``updateLocalCache``
                 first.

        :return: list of all ``ConstraintMeta`` instances in local cache.
        :rtype: tuple
        """
        return RetVal(True, None, tuple(self._cache.values()))<commit_msg>TLC in 'Igor'.<commit_after># Copyright 2015, <NAME> <<EMAIL>>
#
# This file is part of Azrael (https://github.com/olitheolix/azrael)
#
# Azrael is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Azrael 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Azrael. If not, see <http://www.gnu.org/licenses/>.
"""
Igor is a stateless class to manage rigid body constraints.
"""
import logging
import azrael.config as config
import azrael.database as database

from IPython import embed as ipshell
from azrael.types import RetVal, ConstraintMeta, ConstraintP2P
from azrael.types import Constraint6DofSpring2

# List of known constraints and the associated named tuple.
_Known_Constraints = {
    'P2P': ConstraintP2P,
    '6DOFSPRING2': Constraint6DofSpring2,
}


class Igor:
    """
    """
    def __init__(self):
        # Create a Class-specific logger.
        name = '.'.join([__name__, self.__class__.__name__])
        self.logit = logging.getLogger(name)

        # Create the database handle and local constraint cache.
        self.db = database.dbHandles['Constraints']
        self._cache = {}

    def reset(self):
        """
        Flush the constraint database.

        :return: success
        """
        self.db.drop()
        self._cache = {}
        return RetVal(True, None, None)

    def updateLocalCache(self):
        """
        Download *all* constraints from the database into the local cache.

        :return: Number of unique constraints in local cache after operation.
        """
        # Create a Mongo cursor that retrieves all constraints (minus the _id
        # field).
        prj = {'_id': False}
        cursor = self.db.find({}, prj)

        # Flush the cache.
        self._cache = {}

        # Convenience.
        cache = self._cache
        CM = ConstraintMeta

        # Build an iterator that returns the constraints from the data base and
        # wraps them into ConstraintMeta tuples.
        constraints = (CM(**_) for _ in cursor)

        # Iterate over all constraints and substitute the 'condata' attribute
        # with the correct named tuple for the respective constraint. This will
        # build a dictionary where the keys are ConstraintMeta tuple with a
        # value of *None* for the data field, and the value is the same
        # `ConstraintMeta` data but with a valid 'condata' attribute.
        for con in constraints:
            try:
                NT = _Known_Constraints[con.contype.upper()]
                con = con._replace(condata=NT(**con.condata))
            except KeyError:
                # Skip over unknown constraints.
                msg = 'Ignoring unknown constraint {}'.format(con.contype)
                self.logit.info(msg)
                continue

            # Replace the 'condata' field in the constraint. This will become
            # the key for the self._cache dictionary.
            key = con._replace(condata=None)
            cache[key] = con
            del con, key

        # Return the number of valid constraints now in the cache.
        return RetVal(True, None, len(self._cache))

    def addConstraints(self, constraints: (tuple, list)):
        """
        Add all ``constraints`` to the database.

        All entries in ``constraints`` must be ``ConstraintMeta`` instances,
        and their `data` attribute must be a valid ``Consraint***`` instance.

        This method will skip over all constraints with an invalid/unknown
        type.

        It will return the number of constraints

        fixme: add sanity checks.

        :param list constraints: a list of ``ConstraintMeta`` instances.
        :return: number of newly added constraints.
        """
        queries = []
        for con in constraints:
            # Skip all constraints with an unknown type.
            if con.contype.upper() not in _Known_Constraints:
                continue

            # Convenience.
            rb_a, rb_b = con.rb_a, con.rb_b

            # The first body must not be None.
            if rb_a is None or rb_b is None:
                continue

            # Sort the body IDs. This will simplify the logic to fetch- and
            # process constraints.
            rb_a, rb_b = sorted((rb_a, rb_b))
            con = con._replace(rb_a=rb_a, rb_b=rb_b)

            # Convert content of the 'condata' field into a dictionary to store
            # it in MongoDB without loosing the attribute names.
            con = con._replace(condata=con.condata._asdict())

            # Insert the constraints into MongoDB. The constraint query must
            # match both objects IDs, the type, and the constraint ID.
            tmp = {'rb_a': rb_a, 'rb_b': rb_b,
                   'contype': con.contype, 'aid': con.aid}
            queries.append((tmp, con._asdict()))

        # Return immediately if the list of constraints to add is empty.
        if len(queries) == 0:
            return RetVal(True, None, 0)

        # Compile the bulk query and execute it.
        bulk = self.db.initialize_unordered_bulk_op()
        for q, c in queries:
            bulk.find(q).upsert().update({'$setOnInsert': c})
        ret = bulk.execute()

        # Return the number of newly created constraints.
        return RetVal(True, None, ret['nUpserted'])

    def getConstraints(self, bodyIDs: (set, tuple, list)):
        """
        Return all constraints that involve any of the bodies in ``bodyIDs``.

        ..note:: this method only consults the local cache. Depending on your
                 circumstances you may want to call ``updateLocalCache``
                 first.

        :param list[int] bodyIDs: list of body IDs
        :return: list of ``ConstraintMeta`` instances.
        :rtype: tuple
        """
        # Reduce bodyIDs to set of all integer valued IDs for fast look ups.
        bodyIDs = {_ for _ in bodyIDs if isinstance(_, int)}

        # Iterate over all constraints and pick the ones that contain at least
        # one of the bodies specified in `bodyIDs`.
        out = []
        for tmp in self._cache:
            if not (tmp.rb_a in bodyIDs or tmp.rb_b in bodyIDs):
                continue
            out.append(self._cache[tmp])
        return RetVal(True, None, tuple(out))

    def getAllConstraints(self):
        """
        Return all constraints in the local cache.

        ..note:: this method only consults the local cache. Depending on your
                 circumstances you may want to to call ``updateLocalCache``
                 first.

        :return: list of all ``ConstraintMeta`` instances in local cache.
        :rtype: tuple
        """
        return RetVal(True, None, tuple(self._cache.values()))
{'fraction_non_alphanumeric': 0.08252596361608698, 'fraction_numerical': 0.002230431449083432, 'mean_word_length': 2.169427877181356, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2075207', 'n_tokens_mistral': 3811, 'n_tokens_neox': 3683, 'n_words': 1519}