<commit_before>
class Rubiks_Side():
    """ One side of a Rubik's cube """

    def __init__(self, color):
        self._face = []
        for i in range(3):
            self._face.append([color, color, color])

    def __repr__(self):
        return '\n'.join(' '.join(i) for i in self._face)

    def row(self, row):
        return self._face[row]

    def col(self, col):
        return [self._face[0][col], self._face[1][col], self._face[2][col]]

class Rubiks_Cube():
    """ A Rubik's cube """

    def __init__(self):
        self._sides = [Rubiks_Side('r'),
                       Rubiks_Side('o'),
                       Rubiks_Side('g'),
                       Rubiks_Side('b'),
                       Rubiks_Side('w'),
                       Rubiks_Side('y')]

    def rotate(self, side=0, direction='cw'):
        face = side
        # pick the 'face' to rotate if rotating a middle section
        if side >= 6:
            face = (face - 6) * 2

        # ignoring the faces parallel to the rotating section
        ignore = [face, face - 1 if face % 2 else face + 1]
        ignore.sort()
        sides_to_rotate = list(range(6))
        sides_to_rotate = sides_to_rotate[0:ignore[0]] + sides_to_rotate[ignore[1]+1:]
        sides_to_rotate = [[i] for i in sides_to_rotate]

        rows_cols = []
        reverse = []
        # for each of the 3 orthogonal rotations, determine whether we need to reverse
        # a row/column when it is moved, and whether it is a row or a column that should
        # be moved
        if face in (0, 1):
            reverse = [True, False, False, True]
            rows_cols = ['c', 'c', 'r', 'r']
        elif face in (2, 3):
            reverse = [False, True, False, True]
            rows_cols = ['c', 'c', 'c', 'c']
        elif face in (4, 5):
            reverse = [False, False, False, False]
            rows_cols = ['r', 'r', 'r', 'r']

        # determine which row/column is moved
        if side in (0, 3):
            rows_cols = [j + ('0' if i % 2 else '2') for i, j in enumerate(rows_cols)]
        elif side in (1, 2):
            rows_cols = [j + ('2' if i % 2 else '0') for i, j in enumerate(rows_cols)]
        elif side == 4:
            rows_cols = [i + '0' for i in rows_cols]
        elif side == 5:
            rows_cols = [i + '2' for i in rows_cols]
        elif side in (6, 7, 8):
            rows_cols = [i + '1' for i in rows_cols]

        sides_to_rotate = list(zip(sides_to_rotate, rows_cols, reverse))

a = Rubiks_Cube()
a.rotate()
a.rotate(1)
<commit_msg>added __repr__ for Rubiks_Cube<commit_after>
class Rubiks_Side():
    """ One side of a Rubik's cube """

    def __init__(self, color):
        self._face = []
        for i in range(3):
            self._face.append([color, color, color])

    def __repr__(self):
        return '\n'.join(' '.join(i) for i in self._face)

    def row(self, row):
        return self._face[row]

    def col(self, col):
        return [self._face[0][col], self._face[1][col], self._face[2][col]]

class Rubiks_Cube():
    """ A Rubik's cube """

    def __init__(self):
        self._sides = [Rubiks_Side('r'),
                       Rubiks_Side('o'),
                       Rubiks_Side('g'),
                       Rubiks_Side('b'),
                       Rubiks_Side('w'),
                       Rubiks_Side('y')]

    def __repr__(self):
        def split_repr(side):
            return side.__repr__().split('\n')
        repr = []
        blank = split_repr(Rubiks_Side(' '))
        red = split_repr(self._sides[0])
        orange = split_repr(self._sides[1])
        green = split_repr(self._sides[2])
        blue = split_repr(self._sides[3])
        white = split_repr(self._sides[4])
        yellow = split_repr(self._sides[5])
        repr.extend([blank[i] + ' ' + yellow[i] for i in range(len(blank))])
        repr.extend([blank[i] + ' ' + orange[i] for i in range(len(blank))])
        repr.extend([green[i] + ' ' + white[i] + ' ' + blue[i] for i in range(len(green))])
        repr.extend([blank[i] + ' ' + red[i] for i in range(len(blank))])

        return '\n'.join(repr)

    def rotate(self, side=0, direction='cw'):
        face = side
        # pick the 'face' to rotate if rotating a middle section
        if side >= 6:
            face = (face - 6) * 2

        # ignoring the faces parallel to the rotating section
        ignore = [face, face - 1 if face % 2 else face + 1]
        ignore.sort()
        sides_to_rotate = list(range(6))
        sides_to_rotate = sides_to_rotate[0:ignore[0]] + sides_to_rotate[ignore[1]+1:]
        sides_to_rotate = [[i] for i in sides_to_rotate]

        rows_cols = []
        reverse = []
        # for each of the 3 orthogonal rotations, determine whether we need to reverse
        # a row/column when it is moved, and whether it is a row or a column that should
        # be moved
        if face in (0, 1):
            reverse = [True, False, False, True]
            rows_cols = ['c', 'c', 'r', 'r']
        elif face in (2, 3):
            reverse = [False, True, False, True]
            rows_cols = ['c', 'c', 'c', 'c']
        elif face in (4, 5):
            reverse = [False, False, False, False]
            rows_cols = ['r', 'r', 'r', 'r']

        # determine which row/column is moved
        if side in (0, 3):
            rows_cols = [j + ('0' if i % 2 else '2') for i, j in enumerate(rows_cols)]
        elif side in (1, 2):
            rows_cols = [j + ('2' if i % 2 else '0') for i, j in enumerate(rows_cols)]
        elif side == 4:
            rows_cols = [i + '0' for i in rows_cols]
        elif side == 5:
            rows_cols = [i + '2' for i in rows_cols]
        elif side in (6, 7, 8):
            rows_cols = [i + '1' for i in rows_cols]

        sides_to_rotate = list(zip(sides_to_rotate, rows_cols, reverse))

a = Rubiks_Cube()
print(a)
<|endoftext|>


<commit_before># Scrapy settings for Fourmi project
#
# For simplicity, this file contains only the most important settings by
# default. All the other settings are documented here:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
#

BOT_NAME = 'FourmiCrawler'

SPIDER_MODULES = ['FourmiCrawler']
NEWSPIDER_MODULE = 'FourmiCrawler'
ITEM_PIPELINES = {
    "FourmiCrawler.pipelines.RemoveNonePipeline": 100,
    'FourmiCrawler.pipelines.AttributeSelectionPipeline': 200,
    'FourmiCrawler.pipelines.DuplicatePipeline': 300,
}
FEED_URI = 'results.json'
FEED_FORMAT = 'jsonlines'

# Crawl responsibly by identifying yourself (and your website) on the
# user-agent

# [todo] - Check for repercussions on spoofing the user agent

USER_AGENT = 'Fourmi'
# USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36'
<commit_msg>Fourmi as our USER_AGENT<commit_after># Scrapy settings for Fourmi project
#
# For simplicity, this file contains only the most important settings by
# default. All the other settings are documented here:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
#

BOT_NAME = 'FourmiCrawler'

SPIDER_MODULES = ['FourmiCrawler']
NEWSPIDER_MODULE = 'FourmiCrawler'
ITEM_PIPELINES = {
    "FourmiCrawler.pipelines.RemoveNonePipeline": 100,
    'FourmiCrawler.pipelines.AttributeSelectionPipeline': 200,
    'FourmiCrawler.pipelines.DuplicatePipeline': 300,
}
FEED_URI = 'results.json'
FEED_FORMAT = 'jsonlines'

# Crawl responsibly by identifying yourself (and your website) on the
# user-agent

USER_AGENT = 'Fourmi'
<|endoftext|>


<commit_before>#!/usr/bin/env python

"""
The Cython debugger

The current directory should contain a directory named 'cython_debug', or a
path to the cython project directory should be given (the parent directory of
cython_debug).

Additional gdb args can be provided only if a path to the project directory is
given.
"""

import os
import sys
import glob
import tempfile
import textwrap
import subprocess
import optparse
import logging

logger = logging.getLogger(__name__)

def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
    if not no_import:
        pattern = os.path.join(path_to_debug_info,
                               'cython_debug',
                               'cython_debug_info_*')
        debug_files = glob.glob(pattern)

        if not debug_files:
            sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
                                   usage, os.path.abspath(path_to_debug_info)))

    fd, tempfilename = tempfile.mkstemp()
    f = os.fdopen(fd, 'w')
    try:
        f.write(prefix_code)
        f.write(textwrap.dedent('''\
            # This is a gdb command file
            # See https://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html

            set breakpoint pending on
            set print pretty on

            python
            # Activate virtualenv, if we were launched from one
            import os
            virtualenv = os.getenv('VIRTUAL_ENV')
            if virtualenv:
                path_to_activate_this_py = os.path.join(virtualenv, 'bin', 'activate_this.py')
                print("gdb command file: Activating virtualenv: %s; path_to_activate_this_py: %s" % (
                    virtualenv, path_to_activate_this_py))
                with open(path_to_activate_this_py) as f:
                    exec(f.read(), dict(__file__=path_to_activate_this_py))

            from Cython.Debugger import libcython, libpython
            end
            '''))

        if no_import:
            # don't do this, this overrides file command in .gdbinit
            # f.write("file %s\n" % sys.executable)
            pass
        else:
            path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
            interpreter_file = open(path)
            try:
                interpreter = interpreter_file.read()
            finally:
                interpreter_file.close()
            f.write("file %s\n" % interpreter)
            f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
            f.write(textwrap.dedent('''\
                python
                import sys
                try:
                    gdb.lookup_type('PyModuleObject')
                except RuntimeError:
                    sys.stderr.write(
                        'Python was not compiled with debug symbols (or it was '
                        'stripped). Some functionality may not work (properly).\\n')
                end

                source .cygdbinit
            '''))
    finally:
        f.close()

    return tempfilename

usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"

def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
    """
    Start the Cython debugger. This tells gdb to import the Cython and Python
    extensions (libcython.py and libpython.py) and it enables gdb's pending
    breakpoints.

    path_to_debug_info is the path to the Cython build directory
    gdb_argv is the list of options to gdb
    no_import tells cygdb whether it should import debug information
    """
    parser = optparse.OptionParser(usage=usage)
    parser.add_option("--gdb-executable",
        dest="gdb", default='gdb',
        help="gdb executable to use [default: gdb]")
    parser.add_option("--verbose", "-v",
        dest="verbosity", action="count", default=0,
        help="Verbose mode. Multiple -v options increase the verbosity")

    (options, args) = parser.parse_args()
    if path_to_debug_info is None:
        if len(args) > 1:
            path_to_debug_info = args[0]
        else:
            path_to_debug_info = os.curdir

    if gdb_argv is None:
        gdb_argv = args[1:]

    if path_to_debug_info == '--':
        no_import = True

    logging_level = logging.WARN
    if options.verbosity == 1:
        logging_level = logging.INFO
    if options.verbosity == 2:
        logging_level = logging.DEBUG
    logging.basicConfig(level=logging_level)

    logger.info("verbosity = %r", options.verbosity)
    logger.debug("options = %r; args = %r", options, args)
    logger.debug("Done parsing command-line options. path_to_debug_info = %r, gdb_argv = %r",
        path_to_debug_info, gdb_argv)

    tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
    logger.info("Launching %s with command file: %s and gdb_argv: %s",
        options.gdb, tempfilename, gdb_argv)
    logger.debug('Command file (%s) contains: """\n%s"""', tempfilename, open(tempfilename).read())
    logger.info("Spawning %s...", options.gdb)
    p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
    logger.info("Spawned %s (pid %d)", options.gdb, p.pid)
    while True:
        try:
            logger.debug("Waiting for gdb (pid %d) to exit...", p.pid)
            ret = p.wait()
            logger.debug("Wait for gdb (pid %d) to exit is done. Returned: %r", p.pid, ret)
        except KeyboardInterrupt:
            pass
        else:
            break
    logger.debug("Removing temp command file: %s", tempfilename)
    os.remove(tempfilename)
    logger.debug("Removed temp command file: %s", tempfilename)
<commit_msg>Allow passing more than two -v's to cygdb.<commit_after>#!/usr/bin/env python

"""
The Cython debugger

The current directory should contain a directory named 'cython_debug', or a
path to the cython project directory should be given (the parent directory of
cython_debug).

Additional gdb args can be provided only if a path to the project directory is
given.
"""

import os
import sys
import glob
import tempfile
import textwrap
import subprocess
import optparse
import logging

logger = logging.getLogger(__name__)

def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
    if not no_import:
        pattern = os.path.join(path_to_debug_info,
                               'cython_debug',
                               'cython_debug_info_*')
        debug_files = glob.glob(pattern)

        if not debug_files:
            sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
                                   usage, os.path.abspath(path_to_debug_info)))

    fd, tempfilename = tempfile.mkstemp()
    f = os.fdopen(fd, 'w')
    try:
        f.write(prefix_code)
        f.write(textwrap.dedent('''\
            # This is a gdb command file
            # See https://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html

            set breakpoint pending on
            set print pretty on

            python
            # Activate virtualenv, if we were launched from one
            import os
            virtualenv = os.getenv('VIRTUAL_ENV')
            if virtualenv:
                path_to_activate_this_py = os.path.join(virtualenv, 'bin', 'activate_this.py')
                print("gdb command file: Activating virtualenv: %s; path_to_activate_this_py: %s" % (
                    virtualenv, path_to_activate_this_py))
                with open(path_to_activate_this_py) as f:
                    exec(f.read(), dict(__file__=path_to_activate_this_py))

            from Cython.Debugger import libcython, libpython
            end
            '''))

        if no_import:
            # don't do this, this overrides file command in .gdbinit
            # f.write("file %s\n" % sys.executable)
            pass
        else:
            path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
            interpreter_file = open(path)
            try:
                interpreter = interpreter_file.read()
            finally:
                interpreter_file.close()
            f.write("file %s\n" % interpreter)
            f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
            f.write(textwrap.dedent('''\
                python
                import sys
                try:
                    gdb.lookup_type('PyModuleObject')
                except RuntimeError:
                    sys.stderr.write(
                        'Python was not compiled with debug symbols (or it was '
                        'stripped). Some functionality may not work (properly).\\n')
                end

                source .cygdbinit
            '''))
    finally:
        f.close()

    return tempfilename

usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"

def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
    """
    Start the Cython debugger. This tells gdb to import the Cython and Python
    extensions (libcython.py and libpython.py) and it enables gdb's pending
    breakpoints.

    path_to_debug_info is the path to the Cython build directory
    gdb_argv is the list of options to gdb
    no_import tells cygdb whether it should import debug information
    """
    parser = optparse.OptionParser(usage=usage)
    parser.add_option("--gdb-executable",
        dest="gdb", default='gdb',
        help="gdb executable to use [default: gdb]")
    parser.add_option("--verbose", "-v",
        dest="verbosity", action="count", default=0,
        help="Verbose mode. Multiple -v options increase the verbosity")

    (options, args) = parser.parse_args()
    if path_to_debug_info is None:
        if len(args) > 1:
            path_to_debug_info = args[0]
        else:
            path_to_debug_info = os.curdir

    if gdb_argv is None:
        gdb_argv = args[1:]

    if path_to_debug_info == '--':
        no_import = True

    logging_level = logging.WARN
    if options.verbosity == 1:
        logging_level = logging.INFO
    if options.verbosity >= 2:
        logging_level = logging.DEBUG
    logging.basicConfig(level=logging_level)

    logger.info("verbosity = %r", options.verbosity)
    logger.debug("options = %r; args = %r", options, args)
    logger.debug("Done parsing command-line options. path_to_debug_info = %r, gdb_argv = %r",
        path_to_debug_info, gdb_argv)

    tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
    logger.info("Launching %s with command file: %s and gdb_argv: %s",
        options.gdb, tempfilename, gdb_argv)
    logger.debug('Command file (%s) contains: """\n%s"""', tempfilename, open(tempfilename).read())
    logger.info("Spawning %s...", options.gdb)
    p = subprocess.Popen([options.gdb, '-command', tempfilename] + gdb_argv)
    logger.info("Spawned %s (pid %d)", options.gdb, p.pid)
    while True:
        try:
            logger.debug("Waiting for gdb (pid %d) to exit...", p.pid)
            ret = p.wait()
            logger.debug("Wait for gdb (pid %d) to exit is done. Returned: %r", p.pid, ret)
        except KeyboardInterrupt:
            pass
        else:
            break
    logger.debug("Removing temp command file: %s", tempfilename)
    os.remove(tempfilename)
    logger.debug("Removed temp command file: %s", tempfilename)
<|endoftext|>


<commit_before>"""Creates Homewizard sensor entities."""
from __future__ import annotations

import logging
from typing import Final

from homeassistant.components.sensor import (
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    DEVICE_CLASS_ENERGY,
    DEVICE_CLASS_GAS,
    DEVICE_CLASS_POWER,
    ENERGY_KILO_WATT_HOUR,
    PERCENTAGE,
    POWER_WATT,
    VOLUME_CUBIC_METERS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, DeviceResponseEntry
from .coordinator import HWEnergyDeviceUpdateCoordinator

_LOGGER = logging.getLogger(__name__)

SENSORS: Final[tuple[SensorEntityDescription, ...]] = (
    SensorEntityDescription(
        key="smr_version",
        name="DSMR Version",
        icon="mdi:counter",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    SensorEntityDescription(
        key="meter_model",
        name="Smart Meter Model",
        icon="mdi:gauge",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    SensorEntityDescription(
        key="wifi_ssid",
        name="Wifi SSID",
        icon="mdi:wifi",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    SensorEntityDescription(
        key="wifi_strength",
        name="Wifi Strength",
        icon="mdi:wifi",
        native_unit_of_measurement=PERCENTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
    ),
    SensorEntityDescription(
        key="total_power_import_t1_kwh",
        name="Total Power Import T1",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=DEVICE_CLASS_ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="total_power_import_t2_kwh",
        name="Total Power Import T2",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=DEVICE_CLASS_ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="total_power_export_t1_kwh",
        name="Total Power Export T1",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=DEVICE_CLASS_ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="total_power_export_t2_kwh",
        name="Total Power Export T2",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=DEVICE_CLASS_ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="active_power_w",
        name="Active Power",
        native_unit_of_measurement=POWER_WATT,
        device_class=DEVICE_CLASS_POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="active_power_l1_w",
        name="Active Power L1",
        native_unit_of_measurement=POWER_WATT,
        device_class=DEVICE_CLASS_POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="active_power_l2_w",
        name="Active Power L2",
        native_unit_of_measurement=POWER_WATT,
        device_class=DEVICE_CLASS_POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="active_power_l3_w",
        name="Active Power L3",
        native_unit_of_measurement=POWER_WATT,
        device_class=DEVICE_CLASS_POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="total_gas_m3",
        name="Total Gas",
        native_unit_of_measurement=VOLUME_CUBIC_METERS,
        device_class=DEVICE_CLASS_GAS,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
)

async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """Initialize sensors."""
    coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

    entities = []
    if coordinator.api.data is not None:
        for description in SENSORS:
            if (
                description.key in coordinator.api.data.available_datapoints
                and getattr(coordinator.api.data, description.key) is not None
            ):
                entities.append(HWEnergySensor(coordinator, entry, description))
    async_add_entities(entities)

class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorEntity):
    """Representation of a HomeWizard Sensor."""

    def __init__(
        self,
        coordinator: HWEnergyDeviceUpdateCoordinator,
        entry: ConfigEntry,
        description: SensorEntityDescription,
    ) -> None:
        """Initialize Sensor Domain."""

        super().__init__(coordinator)
        self.entity_description = description
        self.entry = entry

        # Config attributes.
        self._attr_name = f"{entry.title} {description.name}"
        self.data_type = description.key
        self._attr_unique_id = f"{entry.unique_id}_{description.key}"

        # Special case for export, not everyone has solarpanels
        # The chance that 'export' is non-zero when you have solar panels is nil
        if self.data_type in [
            "total_power_export_t1_kwh",
            "total_power_export_t2_kwh",
        ]:
            if self.data["data"][self.data_type] == 0:
                self._attr_entity_registry_enabled_default = False

    @property
    def device_info(self) -> DeviceInfo:
        """Return device information."""
        return {
            "name": self.entry.title,
            "manufacturer": "HomeWizard",
            "sw_version": self.data["device"].firmware_version,
            "model": self.data["device"].product_type,
            "identifiers": {(DOMAIN, self.data["device"].serial)},
        }

    @property
    def data(self) -> DeviceResponseEntry:
        """Return data object from DataUpdateCoordinator."""
        return self.coordinator.data

    @property
    def native_value(self) -> StateType:
        """Return state of meter."""
        return self.data["data"][self.data_type]

    @property
    def available(self) -> bool:
        """Return availability of meter."""
        return super().available and self.data_type in self.data["data"]
<commit_msg>Use SensorDeviceClass in homewizard (#69266)<commit_after>"""Creates Homewizard sensor entities."""
from __future__ import annotations

import logging
from typing import Final

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    ENERGY_KILO_WATT_HOUR,
    PERCENTAGE,
    POWER_WATT,
    VOLUME_CUBIC_METERS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, DeviceResponseEntry
from .coordinator import HWEnergyDeviceUpdateCoordinator

_LOGGER = logging.getLogger(__name__)

SENSORS: Final[tuple[SensorEntityDescription, ...]] = (
    SensorEntityDescription(
        key="smr_version",
        name="DSMR Version",
        icon="mdi:counter",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    SensorEntityDescription(
        key="meter_model",
        name="Smart Meter Model",
        icon="mdi:gauge",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    SensorEntityDescription(
        key="wifi_ssid",
        name="Wifi SSID",
        icon="mdi:wifi",
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    SensorEntityDescription(
        key="wifi_strength",
        name="Wifi Strength",
        icon="mdi:wifi",
        native_unit_of_measurement=PERCENTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        entity_category=EntityCategory.DIAGNOSTIC,
        entity_registry_enabled_default=False,
    ),
    SensorEntityDescription(
        key="total_power_import_t1_kwh",
        name="Total Power Import T1",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="total_power_import_t2_kwh",
        name="Total Power Import T2",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="total_power_export_t1_kwh",
        name="Total Power Export T1",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="total_power_export_t2_kwh",
        name="Total Power Export T2",
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    SensorEntityDescription(
        key="active_power_w",
        name="Active Power",
        native_unit_of_measurement=POWER_WATT,
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="active_power_l1_w",
        name="Active Power L1",
        native_unit_of_measurement=POWER_WATT,
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="active_power_l2_w",
        name="Active Power L2",
        native_unit_of_measurement=POWER_WATT,
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="active_power_l3_w",
        name="Active Power L3",
        native_unit_of_measurement=POWER_WATT,
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    SensorEntityDescription(
        key="total_gas_m3",
        name="Total Gas",
        native_unit_of_measurement=VOLUME_CUBIC_METERS,
        device_class=SensorDeviceClass.GAS,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
)

async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """Initialize sensors."""
    coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

    entities = []
    if coordinator.api.data is not None:
        for description in SENSORS:
            if (
                description.key in coordinator.api.data.available_datapoints
                and getattr(coordinator.api.data, description.key) is not None
            ):
                entities.append(HWEnergySensor(coordinator, entry, description))
    async_add_entities(entities)

class HWEnergySensor(CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], SensorEntity):
    """Representation of a HomeWizard Sensor."""

    def __init__(
        self,
        coordinator: HWEnergyDeviceUpdateCoordinator,
        entry: ConfigEntry,
        description: SensorEntityDescription,
    ) -> None:
        """Initialize Sensor Domain."""

        super().__init__(coordinator)
        self.entity_description = description
        self.entry = entry

        # Config attributes.
        self._attr_name = f"{entry.title} {description.name}"
        self.data_type = description.key
        self._attr_unique_id = f"{entry.unique_id}_{description.key}"

        # Special case for export, not everyone has solarpanels
        # The chance that 'export' is non-zero when you have solar panels is nil
        if self.data_type in [
            "total_power_export_t1_kwh",
            "total_power_export_t2_kwh",
        ]:
            if self.data["data"][self.data_type] == 0:
                self._attr_entity_registry_enabled_default = False

    @property
    def device_info(self) -> DeviceInfo:
        """Return device information."""
        return {
            "name": self.entry.title,
            "manufacturer": "HomeWizard",
            "sw_version": self.data["device"].firmware_version,
            "model": self.data["device"].product_type,
            "identifiers": {(DOMAIN, self.data["device"].serial)},
        }

    @property
    def data(self) -> DeviceResponseEntry:
        """Return data object from DataUpdateCoordinator."""
        return self.coordinator.data

    @property
    def native_value(self) -> StateType:
        """Return state of meter."""
        return self.data["data"][self.data_type]

    @property
    def available(self) -> bool:
        """Return availability of meter."""
        return super().available and self.data_type in self.data["data"]
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# doorman.py
# Created by Hexapetalous on Jan 11, 2016.
#
# This is a part of C0004, a new version of Tenzor's entrance guard.
# This is the whole.
#
# Copyright (c) 2016 Hexapetalous. All rights reserved.
# This code is licensed under MIT License.
#
import logging
import requests
import sys
import redis
import time
import json
import thread

# For info: function used to open the door is around line #292.
class Doorman(object):
    # REDIS PART
    redis_server = None

    WELCOMED_EXPIRE = 60 * 60 * 24 * 2
    FORBIDDEN_EXPIRE = 60 * 2
    WELCOMED_VALUE = 'welcome'
    FORBIDDEN_VALUE = 'get out'
    LOG_LIST_NAME = 'logs'

    def redis_init(self):
        self.redis_server = redis.StrictRedis()
        return

    def redis_add_people(self, name, welcomed):
        if welcomed:
            self.redis_add_welcomed_people(name)
        else:
            self.redis_add_forbidden_people(name)
        return

    def redis_add_welcomed_people(self, name):
        self.redis_server.setex(name, self.WELCOMED_EXPIRE, self.WELCOMED_VALUE)
        self.logger.debug('Added \"%s\" to welcomed people set.' % name)
        return

    def redis_add_forbidden_people(self, name):
        self.redis_server.setex(name, self.FORBIDDEN_EXPIRE,
                                self.FORBIDDEN_VALUE)
        self.logger.debug('Added \"%s\" to forbidden people set.' % name)
        return

    def redis_check_people(self, name):
        if not self.redis_server.exists(name):
            return None
        if self.redis_server.get(name) == self.WELCOMED_VALUE:
            return True
        else:
            return False

    def redis_get_old_records(self):
        old = []
        for name in self.redis_server.keys():
            if self.redis_server.ttl(name) < 60 * 60:
                old.append(name)
        return old

    def redis_delete_people(self, name):
        self.redis_server.delete(name)

    def redis_save_log(self, log):
        self.redis_server.rpush(self.LOG_LIST_NAME, log)

    def redis_get_all_logs(self):
        res = []
        while self.redis_server.llen(self.LOG_LIST_NAME) > 0:
            res.append(self.redis_server.lpop(self.LOG_LIST_NAME))
        return res

    def redis_get_log_count(self):
        return self.redis_server.llen(self.LOG_LIST_NAME)

    # VALIDATING LOG PART
    validating_log_id = 0

    def validating_log_create(self, name, passed):
        log = {'log_id': self.validating_log_id,
               'card_no': name,
               'time': str(time.time())}
        self.validating_log_id += 1
        if passed:
            log['status'] = 'PASS_CACHE_HIT'
        else:
            log['status'] = 'PASS_CACHE_FORBIDDEN'
        return log

    # WEB PART
    API_URL = 'https://op.tiaozhan.com/doorman/Home/Doorman'
    API_KEY = 'Tiaozhan-Work'

    # This is a new version of web API.
    # For some stupid man who changed the logic of the server.
    def web_validate_people_20160227(self, name):
        url = self.API_URL + '/validate'
        headers = {'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'VALIDATE_CARD_NO'}
        params = {'card_no': name}
        try:
            r = requests.get(url, headers=headers, params=params)
            if r.status_code != requests.codes.ok:
                r.raise_for_status()
            try:
                result = r.json()
                if result['status'] == 0:
                    return True
                else:
                    return False
            except ValueError:
                self.logger.error('Can not resolve data from server.')
                self.logger.error(r.text)
            except KeyError:
                self.logger.error(
                        'Do not have necessary key in data from server.')
                self.logger.error(r.text)
        except requests.exceptions.RequestException:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
        return None

    def web_validate_people_classic(self, name):
        url = self.API_URL + '/validate'
        headers = {'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'VALIDATE_CARD_NO'}
        params = {'card_no': name}
        try:
            r = requests.get(url, headers=headers, params=params)
            if r.status_code != requests.codes.ok:
                r.raise_for_status()
            try:
                result = r.json()
                # FOR TEST
                # result['valid_card_no'] = ['Someone not in cache']
                # END TEST
                if name in result['valid_card_no']:
                    return True
                elif name in result['invalid_card_no']:
                    return False
                else:
                    self.logger.error(
                            'Did not find result in data from server.')
                    self.logger.error(r.text)
            except ValueError:
                self.logger.error('Can not resolve data from server.')
                self.logger.error(r.text)
            except KeyError:
                self.logger.error(
                        'Do not have necessary key in data from server.')
                self.logger.error(r.text)
        except requests.exceptions.RequestException:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
        return None

    def web_validate_old_records_20160227(self, names):
        valid = []
        for name in names:
            if self.web_validate_people_20160227(name):
                valid.append(name)
        return valid

    # Don't do anything with exceptions here.
    # TODO: Maybe I should handle some errors here. The error is so bother me!
    def web_validate_old_records_classical(self, names):
        url = self.API_URL + '/validate'
        headers = {'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'VALIDATE_CARD_NO'}
        params = {'card_no': ','.join(names)}
        r = requests.get(url, headers=headers, params=params)
        if r.status_code != requests.codes.ok:
            return None
        return r.json()['valid_card_no']

    def web_post_log(self):
        try:
            logs = self.redis_get_all_logs()
        except redis.RedisError:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
            return
        url = self.API_URL + '/log'
        headers = {'content-type': 'application/json',
                   'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'POST_LOG'}
        data = {'count': len(logs), 'log': logs}
        try:
            r = requests.post(url, headers=headers, data=json.dumps(data))
            if r.status_code != requests.codes.ok:
                r.raise_for_status()
            res = None
            try:
                res = r.json()
            except ValueError:
                self.logger.error('Can not resolve data from server.')
                self.logger.error(r.text)
            try:
                if res['status'] != 0:
                    self.logger.error(
                            'Server returned status %d.' % res['status'])
                    self.logger.error('Succeed logs: %s' % res['success_log'])
                    for log in logs:
                        if log['log_id'] not in res['success_log']:
                            self.logger.error('Failed log: %s' % log)
                    # If a log is not accepted by server, it's probably that it
                    # contains some errors and will never be accepted.
                    # So I just write it in log and ignore it, return success.
                    return True
                else:
                    self.logger.info('Log posted successful.')
                    return True
            except KeyError:
                exception, msg, traceback = sys.exc_info()
                self.logger.error('%s: %s' % (exception, msg))
                return False
        except requests.exceptions.RequestException:
            exception, msg, traceback = sys.exc_info()
            self.logger.error('%s: %s' % (exception, msg))
            return False

    def web_send_heart_beat(self):
        url = self.API_URL + '/sys'
        headers = {'X-Doorman': self.API_KEY, 'X-Doorman-Action': 'HEART_BEAT'}
        data = {'client_time': str(time.time()),
                'client_redis_status': 'OK',
                'client_log_queue': self.redis_get_log_count()}
        try:
            r = requests.put(url, headers=headers, data=data)
            result = None
            try:
                result = r.json()
            except ValueError:
                exception, msg, traceback = sys.exc_info()
                self.logger.info('%s: %s' % (exception, msg))
            try:
                if result['status'] != 0:
                    self.logger.error(
                            'Server returned status: %d' % result['status'])
                    self.logger.error('Server returned: %s' % r.text)
                # Send a little earlier than expected doesn't hurt~
                # And the net is slow, the daemon needs to sleep.
                self.next_heart_beat = result['next_heart_beat'] - 2
                # The server's time fix logic is unbelievable.
                # TODO: Fix it.
                # self.delta_time += result['time_fix']
            except KeyError:
                exception, msg, traceback = sys.exc_info()
                self.logger.info('%s: %s' % (exception, msg))
        except requests.exceptions.RequestException:
            exception, msg, traceback = sys.exc_info()
            self.logger.info('%s: %s' % (exception, msg))
        # If something goes wrong, `next_heart_beat` will not be changed and
        # thus next heart beat will be sent immediately until one is succeed.
        return

    # LOG PART
    logger = None

    def log_logger_init(self, level):
        logger = logging.getLogger('Doorman logger')
        log_format = logging.Formatter(
                '%(asctime)s [%(levelname)s]%(funcName)s %(message)s',
                '%Y-%m-%d %H:%M:%S')
        file_handle = logging.StreamHandler()
        file_handle.setFormatter(log_format)
        file_handle.setLevel(level)
        logger.addHandler(file_handle)
        self.logger = logger
        self.logger.info('Added logger.')
        return

    # MAIN PART
    def __init__(self):
        super(Doorman, self).__init__()
        self.redis_init()
        self.log_logger_init(logging.WARNING)
        # Multi-thread working
        self.logger.info('Making multi-threads...')
        thread.start_new_thread(self.heart_beat_daemon, ())
        thread.start_new_thread(self.expire_update_daemon, ())

    def main_loop(self):
        try:
            while True:
                name = raw_input('Your name, please:')
                if self.main_validate(name):
                    self.main_open_door()
                if self.redis_get_log_count() > 0:
                    # The server logic has been changed and I don't know what it
                    #  is.
                    # TODO: Fix it.
                    # self.web_post_log()
                    pass
        except KeyboardInterrupt:
            self.logger.info('Keyboard interrupted.')
            self.logger.info('Exit.')
            return

    def main_validate(self, name):
        try:
            res = self.redis_check_people(name)
        except redis.RedisError:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
            return None
        if res is not None:
            log = self.validating_log_create(name, res)
            try:
                self.redis_save_log(log)
            except redis.RedisError:
                exception, message, traceback = sys.exc_info()
                self.logger.error('%s %s' % (exception, message))
            return res
        else:
            res = self.web_validate_people_20160227(name)
            if res is None:
                return None
            # Try to write to cache first.
            try:
                self.redis_add_people(name, res)
            except redis.RedisError:
                exception, message, traceback = sys.exc_info()
                self.logger.error('%s %s' % (exception, message))
            return res

    @staticmethod
    def main_open_door():
        print 'The door is opened.'
        return

    # HEART BEAT PART
    # I still considered that the system time is difficult to change and change
    # correctly. So the program will keep another timer based on system's.
    delta_time = 0
    next_heart_beat = time.time()

    def heart_beat_daemon(self):
        while True:
            now = time.time() + self.delta_time
            if now >= self.next_heart_beat:
                self.web_send_heart_beat()
            time.sleep(1)

    # EXPIRE UPDATE PART
    def expire_update_daemon(self):
        while True:
            # time.sleep(60 * 60)
            # noinspection PyBroadException
            try:
                self.logger.info('Asking expire...')
                old_records = self.redis_get_old_records()
                if not old_records:
                    continue
                still_valid = self.web_validate_old_records_20160227(old_records)
                if still_valid is None:
                    continue
                for name in old_records:
                    if name in still_valid:
                        self.redis_add_welcomed_people(name)
                    else:
                        self.redis_delete_people(name)
                        # It is almost impossible that this guy came to here in
                        # 2 minutes after this action. So it's needless to add
                        # it to forbidden.
                self.logger.info('Asked for expire.')
            except:
                exception, message, traceback = sys.exc_info()
                self.logger.error('%s %s' % (exception, message))

if __name__ == '__main__':
    d = Doorman()
    d.main_loop()
<commit_msg>Setting up.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# doorman.py
# Created by Hexapetalous on Jan 11, 2016.
#
# This is a part of C0004, a new version of Tenzor's entrance guard.
# This is the whole.
#
# Copyright (c) 2016 Hexapetalous. All rights reserved.
# This code is licensed under MIT License.
#
import logging
import requests
import sys
import redis
import time
import json
import thread
import GPIO.RPi as GPIO

# For info: function used to open the door is around line #292.
class Doorman(object):
    # REDIS PART
    redis_server = None

    WELCOMED_EXPIRE = 60 * 60 * 24 * 2
    FORBIDDEN_EXPIRE = 60 * 2
    WELCOMED_VALUE = 'welcome'
    FORBIDDEN_VALUE = 'get out'
    LOG_LIST_NAME = 'logs'

    def redis_init(self):
        self.redis_server = redis.StrictRedis()
        return

    def redis_add_people(self, name, welcomed):
        if welcomed:
            self.redis_add_welcomed_people(name)
        else:
            self.redis_add_forbidden_people(name)
        return

    def redis_add_welcomed_people(self, name):
        self.redis_server.setex(name, self.WELCOMED_EXPIRE, self.WELCOMED_VALUE)
        self.logger.debug('Added \"%s\" to welcomed people set.' % name)
        return

    def redis_add_forbidden_people(self, name):
        self.redis_server.setex(name, self.FORBIDDEN_EXPIRE,
                                self.FORBIDDEN_VALUE)
        self.logger.debug('Added \"%s\" to forbidden people set.' % name)
        return

    def redis_check_people(self, name):
        if not self.redis_server.exists(name):
            return None
        if self.redis_server.get(name) == self.WELCOMED_VALUE:
            return True
        else:
            return False

    def redis_get_old_records(self):
        old = []
        for name in self.redis_server.keys():
            if self.redis_server.ttl(name) < 60 * 60:
                old.append(name)
        return old

    def redis_delete_people(self, name):
        self.redis_server.delete(name)

    def redis_save_log(self, log):
        self.redis_server.rpush(self.LOG_LIST_NAME, log)

    def redis_get_all_logs(self):
        res = []
        while self.redis_server.llen(self.LOG_LIST_NAME) > 0:
            res.append(self.redis_server.lpop(self.LOG_LIST_NAME))
        return res

    def redis_get_log_count(self):
        return self.redis_server.llen(self.LOG_LIST_NAME)

    # VALIDATING LOG PART
    validating_log_id = 0

    def validating_log_create(self, name, passed):
        log = {'log_id': self.validating_log_id,
               'card_no': name,
               'time': str(time.time())}
        self.validating_log_id += 1
        if passed:
            log['status'] = 'PASS_CACHE_HIT'
        else:
            log['status'] = 'PASS_CACHE_FORBIDDEN'
        return log

    # WEB PART
    API_URL = 'https://op.tiaozhan.com/doorman/Home/Doorman'
    API_KEY = 'Tiaozhan-Work'

    # This is a new version of web API.
    # For some stupid man who changed the logic of the server.
    def web_validate_people_20160227(self, name):
        url = self.API_URL + '/validate'
        headers = {'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'VALIDATE_CARD_NO'}
        params = {'card_no': name}
        try:
            r = requests.get(url, headers=headers, params=params)
            if r.status_code != requests.codes.ok:
                r.raise_for_status()
            try:
                result = r.json()
                if result['status'] == 0:
                    return True
                else:
                    return False
            except ValueError:
                self.logger.error('Can not resolve data from server.')
                self.logger.error(r.text)
            except KeyError:
                self.logger.error(
                        'Do not have necessary key in data from server.')
                self.logger.error(r.text)
        except requests.exceptions.RequestException:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
        return None

    def web_validate_people_classic(self, name):
        url = self.API_URL + '/validate'
        headers = {'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'VALIDATE_CARD_NO'}
        params = {'card_no': name}
        try:
            r = requests.get(url, headers=headers, params=params)
            if r.status_code != requests.codes.ok:
                r.raise_for_status()
            try:
                result = r.json()
                # FOR TEST
                # result['valid_card_no'] = ['Someone not in cache']
                # END TEST
                if name in result['valid_card_no']:
                    return True
                elif name in result['invalid_card_no']:
                    return False
                else:
                    self.logger.error(
                            'Did not find result in data from server.')
                    self.logger.error(r.text)
            except ValueError:
                self.logger.error('Can not resolve data from server.')
                self.logger.error(r.text)
            except KeyError:
                self.logger.error(
                        'Do not have necessary key in data from server.')
                self.logger.error(r.text)
        except requests.exceptions.RequestException:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
        return None

    def web_validate_old_records_20160227(self, names):
        valid = []
        for name in names:
            if self.web_validate_people_20160227(name):
                valid.append(name)
        return valid

    # Don't do anything with exceptions here.
    # TODO: Maybe I should handle some errors here. The error is so bother me!
    def web_validate_old_records_classical(self, names):
        url = self.API_URL + '/validate'
        headers = {'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'VALIDATE_CARD_NO'}
        params = {'card_no': ','.join(names)}
        r = requests.get(url, headers=headers, params=params)
        if r.status_code != requests.codes.ok:
            return None
        return r.json()['valid_card_no']

    def web_post_log(self):
        try:
            logs = self.redis_get_all_logs()
        except redis.RedisError:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
            return
        url = self.API_URL + '/log'
        headers = {'content-type': 'application/json',
                   'X-Doorman': self.API_KEY,
                   'X-Doorman-Action': 'POST_LOG'}
        data = {'count': len(logs), 'log': logs}
        try:
            r = requests.post(url, headers=headers, data=json.dumps(data))
            if r.status_code != requests.codes.ok:
                r.raise_for_status()
            res = None
            try:
                res = r.json()
            except ValueError:
                self.logger.error('Can not resolve data from server.')
                self.logger.error(r.text)
            try:
                if res['status'] != 0:
                    self.logger.error(
                            'Server returned status %d.' % res['status'])
                    self.logger.error('Succeed logs: %s' % res['success_log'])
                    for log in logs:
                        if log['log_id'] not in res['success_log']:
                            self.logger.error('Failed log: %s' % log)
                    # If a log is not accepted by server, it's probably that it
                    # contains some errors and will never be accepted.
                    # So I just write it in log and ignore it, return success.
                    return True
                else:
                    self.logger.info('Log posted successful.')
                    return True
            except KeyError:
                exception, msg, traceback = sys.exc_info()
                self.logger.error('%s: %s' % (exception, msg))
                return False
        except requests.exceptions.RequestException:
            exception, msg, traceback = sys.exc_info()
            self.logger.error('%s: %s' % (exception, msg))
            return False

    def web_send_heart_beat(self):
        url = self.API_URL + '/sys'
        headers = {'X-Doorman': self.API_KEY, 'X-Doorman-Action': 'HEART_BEAT'}
        data = {'client_time': str(time.time()),
                'client_redis_status': 'OK',
                'client_log_queue': self.redis_get_log_count()}
        try:
            r = requests.put(url, headers=headers, data=data)
            result = None
            try:
                result = r.json()
            except ValueError:
                exception, msg, traceback = sys.exc_info()
                self.logger.info('%s: %s' % (exception, msg))
            try:
                if result['status'] != 0:
                    self.logger.error(
                            'Server returned status: %d' % result['status'])
                    self.logger.error('Server returned: %s' % r.text)
                # Send a little earlier than expected doesn't hurt~
                # And the net is slow, the daemon needs to sleep.
                self.next_heart_beat = result['next_heart_beat'] - 2
                # The server's time fix logic is unbelievable.
                # TODO: Fix it.
                # self.delta_time += result['time_fix']
            except KeyError:
                exception, msg, traceback = sys.exc_info()
                self.logger.info('%s: %s' % (exception, msg))
        except requests.exceptions.RequestException:
            exception, msg, traceback = sys.exc_info()
            self.logger.info('%s: %s' % (exception, msg))
        # If something goes wrong, `next_heart_beat` will not be changed and
        # thus next heart beat will be sent immediately until one is succeed.
        return

    # LOG PART
    logger = None

    def log_logger_init(self, level):
        logger = logging.getLogger('Doorman logger')
        log_format = logging.Formatter(
                '%(asctime)s [%(levelname)s]%(funcName)s %(message)s',
                '%Y-%m-%d %H:%M:%S')
        file_handle = logging.StreamHandler()
        file_handle.setFormatter(log_format)
        file_handle.setLevel(level)
        logger.addHandler(file_handle)
        self.logger = logger
        self.logger.info('Added logger.')
        return

    # MAIN PART
    def __init__(self):
        super(Doorman, self).__init__()
        self.redis_init()
        self.log_logger_init(logging.WARNING)
        # Multi-thread working
        self.logger.info('Making multi-threads...')
        thread.start_new_thread(self.heart_beat_daemon, ())
        thread.start_new_thread(self.expire_update_daemon, ())
        # Set up RPi
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(25, GPIO.OUT)

    def main_loop(self):
        try:
            while True:
                name = raw_input('Your name, please:')
                if self.main_validate(name):
                    self.main_open_door()
                if self.redis_get_log_count() > 0:
                    # The server logic has been changed and I don't know what it
                    #  is.
                    # TODO: Fix it.
                    # self.web_post_log()
                    pass
        except KeyboardInterrupt:
            self.logger.info('Keyboard interrupted.')
            self.logger.info('Exit.')
            return

    def main_validate(self, name):
        try:
            res = self.redis_check_people(name)
        except redis.RedisError:
            exception, message, traceback = sys.exc_info()
            self.logger.error('%s %s' % (exception, message))
            return None
        if res is not None:
            log = self.validating_log_create(name, res)
            try:
                self.redis_save_log(log)
            except redis.RedisError:
                exception, message, traceback = sys.exc_info()
                self.logger.error('%s %s' % (exception, message))
            return res
        else:
            res = self.web_validate_people_20160227(name)
            if res is None:
                return None
            # Try to write to cache first.
            try:
                self.redis_add_people(name, res)
            except redis.RedisError:
                exception, message, traceback = sys.exc_info()
                self.logger.error('%s %s' % (exception, message))
            return res

    @staticmethod
    def main_open_door():
        print 'The door is opened.'
        GPIO.output(25,GPIO.HIGH)
        time.sleep(1)
        GPIO.output(25,GPIO.LOW)
        return

    # HEART BEAT PART
    # I still considered that the system time is difficult to change and change
    # correctly. So the program will keep another timer based on system's.
    delta_time = 0
    next_heart_beat = time.time()

    def heart_beat_daemon(self):
        while True:
            now = time.time() + self.delta_time
            if now >= self.next_heart_beat:
                self.web_send_heart_beat()
            time.sleep(1)

    # EXPIRE UPDATE PART
    def expire_update_daemon(self):
        while True:
            # time.sleep(60 * 60)
            # noinspection PyBroadException
            try:
                self.logger.info('Asking expire...')
                old_records = self.redis_get_old_records()
                if not old_records:
                    continue
                still_valid = self.web_validate_old_records_20160227(old_records)
                if still_valid is None:
                    continue
                for name in old_records:
                    if name in still_valid:
                        self.redis_add_welcomed_people(name)
                    else:
                        self.redis_delete_people(name)
                        # It is almost impossible that this guy came to here in
                        # 2 minutes after this action. So it's needless to add
                        # it to forbidden.
                self.logger.info('Asked for expire.')
            except:
                exception, message, traceback = sys.exc_info()
                self.logger.error('%s %s' % (exception, message))

if __name__ == '__main__':
    d = Doorman()
    d.main_loop()
<|endoftext|>


<commit_before># Copyright (c) 2012, James Hensman and Ricardo Andrade
# Licensed under the BSD 3-clause license (see LICENSE.txt)

from kernpart import kernpart
import numpy as np
from GPy.util.linalg import mdot, pdinv

class coregionalise(kernpart):
    """
    Kernel for Intrisec Corregionalization Models
    """
    def __init__(self,Nout,R=1, W=None, kappa=None):
        self.D = 1
        self.name = 'coregion'
        self.Nout = Nout
        self.R = R
        if W is None:
            self.W = np.ones((self.Nout,self.R))
        else:
            assert W.shape==(self.Nout,self.R)
            self.W = W
        if kappa is None:
            kappa = np.ones(self.Nout)
        else:
            assert kappa.shape==(self.Nout,)
        self.kappa = kappa
        self.Nparam = self.Nout*(self.R + 1)
        self._set_params(np.hstack([self.W.flatten(),self.kappa]))

    def _get_params(self):
        return np.hstack([self.W.flatten(),self.kappa])

    def _set_params(self,x):
        assert x.size == self.Nparam
        self.kappa = x[-self.Nout:]
        self.W = x[:-self.Nout].reshape(self.Nout,self.R)
        self.B = np.dot(self.W,self.W.T) + np.diag(self.kappa)

    def _get_param_names(self):

        return sum([['W%i_%i'%(i,j) for j in range(self.R)] for i in range(self.Nout)],[]) + ['kappa_%i'%i for i in range(self.Nout)]

    def K(self,index,index2,target):
        index = np.asarray(index,dtype=np.int)
        if index2 is None:
            index2 = index
        else:
            index2 = np.asarray(index2,dtype=np.int)
        ii,jj = np.meshgrid(index2,index)
        target += self.B[ii,jj]

    def Kdiag(self,index,target):
        target += np.diag(self.B)[np.asarray(index,dtype=np.int).flatten()]

    def dK_dtheta(self,partial,index,index2,target):
        index = np.asarray(index,dtype=np.int)
        if index2 is None:
            index2 = index
        else:
            index2 = np.asarray(index2,dtype=np.int)
        ii,jj = np.meshgrid(index2,index)
        PK = np.zeros((self.R,self.R))
        partial_small = np.zeros_like(self.B)
        for i in range(self.Nout):
            for j in range(self.Nout):
                partial_small[i,j] = np.sum(partial[(ii==i)*(jj==j)])
        dkappa = np.diag(partial_small)

        dW = 2.*(self.W[:,None,:]*partial_small[:,:,None]).sum(0)

        target += np.hstack([dW.flatten(),dkappa])

    def dKdiag_dtheta(self,partial,index,target):
        index = np.asarray(index,dtype=np.int).flatten()
        partial_small = np.zeros(self.Nout)
        for i in range(self.Nout):
            partial_small[i] += np.sum(partial[index==i])
        dW = 2.*self.W*partial_small[:,None]
        dkappa = partial_small
        target += np.hstack([dW.flatten(),dkappa])

    def dK_dX(self,partial,X,X2,target):
        pass

    def dKdiag_dthetai_(self,partial,index,target):
        index = np.asarray(index,dtype=np.int)
        index2 = index
        ii,jj = np.meshgrid(index2,index)
        PK = np.zeros((self.R,self.R))
        partial_small = np.zeros_like(self.B)
        for i in range(self.Nout):
            for j in range(self.Nout):
                partial_small[j,i] = np.sum(partial[np.diag((ii==i)*(jj==j))])
        #print partial_small
        dkappa = np.diag(partial_small)

        ##target += (((X2[:, None, :] * self.variances)) * partial[:,:, None]).sum(0)
        partial_small = np.diag(np.diag(partial_small))
        #dW = 2.*(self.W[:,None,:]*partial_small[:,:,None]).sum(0)
        dW = 2.

        target += np.hstack([dW.flatten(),dkappa])

<commit_msg>more messing around with coregionalize<commit_after># Copyright (c) 2012, James Hensman and Ricardo Andrade
# Licensed under the BSD 3-clause license (see LICENSE.txt)

from kernpart import kernpart
import numpy as np
from GPy.util.linalg import mdot, pdinv

class coregionalise(kernpart):
    """
    Kernel for Intrisec Corregionalization Models
    """
    def __init__(self,Nout,R=1, W=None, kappa=None):
        self.D = 1
        self.name = 'coregion'
        self.Nout = Nout
        self.R = R
        if W is None:
            self.W = np.ones((self.Nout,self.R))
        else:
            assert W.shape==(self.Nout,self.R)
            self.W = W
        if kappa is None:
            kappa = np.ones(self.Nout)
        else:
            assert kappa.shape==(self.Nout,)
        self.kappa = kappa
        self.Nparam = self.Nout*(self.R + 1)
        self._set_params(np.hstack([self.W.flatten(),self.kappa]))

    def _get_params(self):
        return np.hstack([self.W.flatten(),self.kappa])

    def _set_params(self,x):
        assert x.size == self.Nparam
        self.kappa = x[-self.Nout:]
        self.W = x[:-self.Nout].reshape(self.Nout,self.R)
        self.B = np.dot(self.W,self.W.T) + np.diag(self.kappa)

    def _get_param_names(self):

        return sum([['W%i_%i'%(i,j) for j in range(self.R)] for i in range(self.Nout)],[]) + ['kappa_%i'%i for i in range(self.Nout)]

    def K(self,index,index2,target):
        index = np.asarray(index,dtype=np.int)
        if index2 is None:
            index2 = index
        else:
            index2 = np.asarray(index2,dtype=np.int)
        ii,jj = np.meshgrid(index2,index)
        target += self.B[ii,jj]

    def Kdiag(self,index,target):
        target += np.diag(self.B)[np.asarray(index,dtype=np.int).flatten()]

    def dK_dtheta(self,partial,index,index2,target):
        index = np.asarray(index,dtype=np.int)
        if index2 is None:
            index2 = index
        else:
            index2 = np.asarray(index2,dtype=np.int)
        ii,jj = np.meshgrid(index2,index)
        PK = np.zeros((self.R,self.R))
        partial_small = np.zeros_like(self.B)
        for i in range(self.Nout):
            for j in range(self.Nout):
                partial_small[i,j] = np.sum(partial[(ii==i)*(jj==j)])
        dkappa = np.diag(partial_small)

        dW = 2.*(self.W[:,None,:]*partial_small[:,:,None]).sum(0)

        target += np.hstack([dW.flatten(),dkappa])

    def dKdiag_dtheta_foo(self,partial,index,target):
        index = np.asarray(index,dtype=np.int).flatten()
        partial_small = np.zeros(self.Nout)
        for i in range(self.Nout):
            partial_small[i] += np.sum(partial[index==i])
        dW = 2.*self.W*partial_small[:,None]
        dkappa = partial_small
        target += np.hstack([dW.flatten(),dkappa])

    def dK_dX(self,partial,X,X2,target):
        pass

    def dKdiag_dtheta(self,partial,index,target):
        self.dK_dtheta(np.diag(partial),index,index,target)

<|endoftext|>


<commit_before>from collections import namedtuple
import itertools
import functools
import operator
import ctypes

import numpy as np

from numba import _helperlib
from numba.core import config

Extent = namedtuple("Extent", ["begin", "end"])

attempt_nocopy_reshape = ctypes.CFUNCTYPE(
    ctypes.c_int,
    ctypes.c_long,  # nd
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # dims
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # strides
    ctypes.c_long,  # newnd
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # newdims
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # newstrides
    ctypes.c_long,  # itemsize
    ctypes.c_int,  # is_f_order
)(_helperlib.c_helpers['attempt_nocopy_reshape'])

class Dim(object):
    """A single dimension of the array

    Attributes
    ----------
    start:
        start offset
    stop:
        stop offset
    size:
        number of items
    stride:
        item stride
    """
    __slots__ = 'start', 'stop', 'size', 'stride', 'single'

    def __init__(self, start, stop, size, stride, single):
        self.start = start
        self.stop = stop
        self.size = size
        self.stride = stride
        self.single = single
        assert not single or size == 1

    def __getitem__(self, item):
        if isinstance(item, slice):
            start, stop, step = item.indices(self.size)
            stride = step * self.stride
            start = self.start + start * abs(self.stride)
            stop = self.start + stop * abs(self.stride)
            if stride == 0:
                size = 1
            else:
                size = _compute_size(start, stop, stride)
            ret = Dim(
                start=start,
                stop=stop,
                size=size,
                stride=stride,
                single=False
            )
            return ret
        else:
            sliced = self[item:item + 1] if item != -1 else self[-1:]
            if sliced.size != 1:
                raise IndexError
            return Dim(
                start=sliced.start,
                stop=sliced.stop,
                size=sliced.size,
                stride=sliced.stride,
                single=True,
            )

    def get_offset(self, idx):
        return self.start + idx * self.stride

    def __repr__(self):
        strfmt = "Dim(start=%s, stop=%s, size=%s, stride=%s)"
        return strfmt % (self.start, self.stop, self.size, self.stride)

    def normalize(self, base):
        return Dim(start=self.start - base, stop=self.stop - base,
                   size=self.size, stride=self.stride, single=self.single)

    def copy(self, start=None, stop=None, size=None, stride=None, single=None):
        if start is None:
            start = self.start
        if stop is None:
            stop = self.stop
        if size is None:
            size = self.size
        if stride is None:
            stride = self.stride
        if single is None:
            single = self.single
        return Dim(start, stop, size, stride, single)

    def is_contiguous(self, itemsize):
        return self.stride == itemsize

def compute_index(indices, dims):
    return sum(d.get_offset(i) for i, d in zip(indices, dims))

class Element(object):
    is_array = False

    def __init__(self, extent):
        self.extent = extent

    def iter_contiguous_extent(self):
        yield self.extent

class Array(object):
    """A dummy numpy array-like object.  Consider it an array without the
    actual data, but offset from the base data pointer.

    Attributes
    ----------
    dims: tuple of Dim
        describing each dimension of the array

    ndim: int
        number of dimension

    shape: tuple of int
        size of each dimension

    strides: tuple of int
        stride of each dimension

    itemsize: int
        itemsize

    extent: (start, end)
        start and end offset containing the memory region
    """
    is_array = True

    @classmethod
    def from_desc(cls, offset, shape, strides, itemsize):
        dims = []
        for ashape, astride in zip(shape, strides):
            dim = Dim(offset, offset + ashape * astride, ashape, astride,
                      single=False)
            dims.append(dim)
            offset = 0  # offset only applies to first dimension
        return cls(dims, itemsize)

    def __init__(self, dims, itemsize):
        self.dims = tuple(dims)
        self.ndim = len(self.dims)
        self.shape = tuple(dim.size for dim in self.dims)
        self.strides = tuple(dim.stride for dim in self.dims)
        self.itemsize = itemsize
        self.size = functools.reduce(operator.mul, self.shape, 1)
        self.extent = self._compute_extent()
        if config.NPY_RELAXED_STRIDES_CHECKING:
            self.flags = self._relaxed_compute_layout()
        else:
            self.flags = self._compute_layout()

    def _relaxed_compute_layout(self):
        # The logic here is based on that in _UpdateContiguousFlags from
        # numpy/core/src/multiarray/flagsobject.c in NumPy v1.19.1 (commit
        # 13661ac70).
        # https://github.com/numpy/numpy/blob/maintenance/1.19.x/numpy/core/src/multiarray/flagsobject.c#L123-L191

        # Records have no dims, and we can treat them as contiguous
        if not self.dims:
            return {'C_CONTIGUOUS': True, 'F_CONTIGUOUS': True}

        # If this is a broadcast array then it is not contiguous
        if any([dim.stride == 0 for dim in self.dims]):
            return {'C_CONTIGUOUS': False, 'F_CONTIGUOUS': False}

        flags = {'C_CONTIGUOUS': True, 'F_CONTIGUOUS': True}

        # Check C contiguity
        sd = self.itemsize
        for dim in reversed(self.dims):
            if dim.size == 0:
                # Contiguous by definition
                return {'C_CONTIGUOUS': True, 'F_CONTIGUOUS': True}
            if dim.size != 1:
                if dim.stride != sd:
                    flags['C_CONTIGUOUS'] = False
                sd *= dim.size

        # Check F contiguity
        sd = self.itemsize
        for dim in self.dims:
            if dim.size != 1:
                if dim.stride != sd:
                    flags['F_CONTIGUOUS'] = False
                    return flags
                sd *= dim.size

        return flags

    def _compute_layout(self):
        flags = {}

        if not self.dims:
            # Records have no dims, and we can treat them as contiguous
            flags['F_CONTIGUOUS'] = True
            flags['C_CONTIGUOUS'] = True
            return flags

        leftmost = self.dims[0].is_contiguous(self.itemsize)
        rightmost = self.dims[-1].is_contiguous(self.itemsize)

        def is_contig(traverse):
            last = next(traverse)
            for dim in traverse:
                if last.size != 0 and last.size * last.stride != dim.stride:
                    return False
                last = dim
            return True

        flags['F_CONTIGUOUS'] = leftmost and is_contig(iter(self.dims))
        flags['C_CONTIGUOUS'] = rightmost and is_contig(reversed(self.dims))
        return flags

    def _compute_extent(self):
        firstidx = [0] * self.ndim
        lastidx = [s - 1 for s in self.shape]
        start = compute_index(firstidx, self.dims)
        stop = compute_index(lastidx, self.dims) + self.itemsize
        stop = max(stop, start)   # ensure positive extent
        return Extent(start, stop)

    def __repr__(self):
        return '<Array dims=%s itemsize=%s>' % (self.dims, self.itemsize)

    def __getitem__(self, item):
        if not isinstance(item, tuple):
            item = [item]
        else:
            item = list(item)

        nitem = len(item)
        ndim = len(self.dims)
        if nitem > ndim:
            raise IndexError("%d extra indices given" % (nitem - ndim,))

        # Add empty slices for missing indices
        while len(item) < ndim:
            item.append(slice(None, None))

        dims = [dim.__getitem__(it) for dim, it in zip(self.dims, item)]
        newshape = [d.size for d in dims if not d.single]

        arr = Array(dims, self.itemsize)
        if newshape:
            return arr.reshape(*newshape)[0]
        else:
            return Element(arr.extent)

    @property
    def is_c_contig(self):
        return self.flags['C_CONTIGUOUS']

    @property
    def is_f_contig(self):
        return self.flags['F_CONTIGUOUS']

    def iter_contiguous_extent(self):
        """ Generates extents
        """
        if self.is_c_contig or self.is_f_contig:
            yield self.extent
        else:
            if self.dims[0].stride < self.dims[-1].stride:
                innerdim = self.dims[0]
                outerdims = self.dims[1:]
                outershape = self.shape[1:]
            else:
                innerdim = self.dims[-1]
                outerdims = self.dims[:-1]
                outershape = self.shape[:-1]

            if innerdim.is_contiguous(self.itemsize):
                oslen = [range(s) for s in outershape]
                for indices in itertools.product(*oslen):
                    base = compute_index(indices, outerdims)
                    yield base + innerdim.start, base + innerdim.stop
            else:
                oslen = [range(s) for s in self.shape]
                for indices in itertools.product(*oslen):
                    offset = compute_index(indices, self.dims)
                    yield offset, offset + self.itemsize

    def reshape(self, *newdims, **kws):
        oldnd = self.ndim
        newnd = len(newdims)

        if newdims == self.shape:
            return self, None

        order = kws.pop('order', 'C')
        if kws:
            raise TypeError('unknown keyword arguments %s' % kws.keys())
        if order not in 'CFA':
            raise ValueError('order not C|F|A')

        # check for exactly one instance of -1 in newdims
        unknownidx = -1
        knownsize = 1
        for i, dim in enumerate(newdims):
            if dim < 0:
                if unknownidx == -1:
                    unknownidx = i
                else:
                    raise ValueError("can only specify one unknown dimension")
            else:
                knownsize *= dim

        newsize = functools.reduce(operator.mul, newdims, 1)

        if order == 'A':
            order = 'F' if self.is_f_contig else 'C'

        if newsize != self.size:
            raise ValueError("reshape changes the size of the array")

        if self.is_c_contig or self.is_f_contig:
            if order == 'C':
                newstrides = list(iter_strides_c_contig(self, newdims))
            elif order == 'F':
                newstrides = list(iter_strides_f_contig(self, newdims))
            else:
                raise AssertionError("unreachable")
        else:
            newstrides = np.empty(newnd, np.ctypeslib.c_intp)

            # need to keep these around in variables, not temporaries, so they
            # don't get GC'ed before we call into the C code
            olddims = np.array(self.shape, dtype=np.ctypeslib.c_intp)
            oldstrides = np.array(self.strides, dtype=np.ctypeslib.c_intp)
            newdims = np.array(newdims, dtype=np.ctypeslib.c_intp)

            if not attempt_nocopy_reshape(
                oldnd,
                olddims,
                oldstrides,
                newnd,
                newdims,
                newstrides,
                self.itemsize,
                order == 'F',
            ):
                raise NotImplementedError('reshape would require copy')

        ret = self.from_desc(self.extent.begin, shape=newdims,
                             strides=newstrides, itemsize=self.itemsize)

        return ret, list(self.iter_contiguous_extent())

    def squeeze(self, axis=None):
        newshape, newstrides = [], []
        if axis is None:
            for length, stride in zip(self.shape, self.strides):
                if length != 1:
                    newshape.append(length)
                    newstrides.append(stride)
        else:
            if not isinstance(axis, tuple):
                axis = (axis,)
            for ax in axis:
                if self.shape[ax] != 1:
                    raise ValueError(
                        "cannot select an axis to squeeze out which has size not equal "
                        "to one"
                    )
            for i, (length, stride) in enumerate(zip(self.shape, self.strides)):
                if i not in axis:
                    newshape.append(length)
                    newstrides.append(stride)
        newarr = self.from_desc(
            self.extent.begin,
            shape=newshape,
            strides=newstrides,
            itemsize=self.itemsize,
        )
        return newarr, list(self.iter_contiguous_extent())

    def ravel(self, order='C'):
        if order not in 'CFA':
            raise ValueError('order not C|F|A')

        if self.ndim <= 1:
            return self

        elif (order in 'CA' and self.is_c_contig or
                          order in 'FA' and self.is_f_contig):
            newshape = (self.size,)
            newstrides = (self.itemsize,)
            arr = self.from_desc(self.extent.begin, newshape, newstrides,
                                 self.itemsize)
            return arr, list(self.iter_contiguous_extent())

        else:
            raise NotImplementedError("ravel on non-contiguous array")

def iter_strides_f_contig(arr, shape=None):
    """yields the f-contiguous strides
    """
    shape = arr.shape if shape is None else shape
    itemsize = arr.itemsize
    yield itemsize
    sum = 1
    for s in shape[:-1]:
        sum *= s
        yield sum * itemsize

def iter_strides_c_contig(arr, shape=None):
    """yields the c-contiguous strides
    """
    shape = arr.shape if shape is None else shape
    itemsize = arr.itemsize

    def gen():
        yield itemsize
        sum = 1
        for s in reversed(shape[1:]):
            sum *= s
            yield sum * itemsize

    for i in reversed(list(gen())):
        yield i

def is_element_indexing(item, ndim):
    if isinstance(item, slice):
        return False

    elif isinstance(item, tuple):
        if len(item) == ndim:
            if not any(isinstance(it, slice) for it in item):
                return True

    else:
        return True

    return False

def _compute_size(start, stop, step):
    """Algorithm adapted from cpython rangeobject.c
    """
    if step > 0:
        lo = start
        hi = stop
    else:
        lo = stop
        hi = start
        step = -step
    if lo >= hi:
        return 0
    return (hi - lo - 1) // step + 1
<commit_msg>add inference of unknown dimension value<commit_after>from collections import namedtuple
import itertools
import functools
import operator
import ctypes

import numpy as np

from numba import _helperlib
from numba.core import config

Extent = namedtuple("Extent", ["begin", "end"])

attempt_nocopy_reshape = ctypes.CFUNCTYPE(
    ctypes.c_int,
    ctypes.c_long,  # nd
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # dims
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # strides
    ctypes.c_long,  # newnd
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # newdims
    np.ctypeslib.ndpointer(np.ctypeslib.c_intp, ndim=1),  # newstrides
    ctypes.c_long,  # itemsize
    ctypes.c_int,  # is_f_order
)(_helperlib.c_helpers['attempt_nocopy_reshape'])

class Dim(object):
    """A single dimension of the array

    Attributes
    ----------
    start:
        start offset
    stop:
        stop offset
    size:
        number of items
    stride:
        item stride
    """
    __slots__ = 'start', 'stop', 'size', 'stride', 'single'

    def __init__(self, start, stop, size, stride, single):
        self.start = start
        self.stop = stop
        self.size = size
        self.stride = stride
        self.single = single
        assert not single or size == 1

    def __getitem__(self, item):
        if isinstance(item, slice):
            start, stop, step = item.indices(self.size)
            stride = step * self.stride
            start = self.start + start * abs(self.stride)
            stop = self.start + stop * abs(self.stride)
            if stride == 0:
                size = 1
            else:
                size = _compute_size(start, stop, stride)
            ret = Dim(
                start=start,
                stop=stop,
                size=size,
                stride=stride,
                single=False
            )
            return ret
        else:
            sliced = self[item:item + 1] if item != -1 else self[-1:]
            if sliced.size != 1:
                raise IndexError
            return Dim(
                start=sliced.start,
                stop=sliced.stop,
                size=sliced.size,
                stride=sliced.stride,
                single=True,
            )

    def get_offset(self, idx):
        return self.start + idx * self.stride

    def __repr__(self):
        strfmt = "Dim(start=%s, stop=%s, size=%s, stride=%s)"
        return strfmt % (self.start, self.stop, self.size, self.stride)

    def normalize(self, base):
        return Dim(start=self.start - base, stop=self.stop - base,
                   size=self.size, stride=self.stride, single=self.single)

    def copy(self, start=None, stop=None, size=None, stride=None, single=None):
        if start is None:
            start = self.start
        if stop is None:
            stop = self.stop
        if size is None:
            size = self.size
        if stride is None:
            stride = self.stride
        if single is None:
            single = self.single
        return Dim(start, stop, size, stride, single)

    def is_contiguous(self, itemsize):
        return self.stride == itemsize

def compute_index(indices, dims):
    return sum(d.get_offset(i) for i, d in zip(indices, dims))

class Element(object):
    is_array = False

    def __init__(self, extent):
        self.extent = extent

    def iter_contiguous_extent(self):
        yield self.extent

class Array(object):
    """A dummy numpy array-like object.  Consider it an array without the
    actual data, but offset from the base data pointer.

    Attributes
    ----------
    dims: tuple of Dim
        describing each dimension of the array

    ndim: int
        number of dimension

    shape: tuple of int
        size of each dimension

    strides: tuple of int
        stride of each dimension

    itemsize: int
        itemsize

    extent: (start, end)
        start and end offset containing the memory region
    """
    is_array = True

    @classmethod
    def from_desc(cls, offset, shape, strides, itemsize):
        dims = []
        for ashape, astride in zip(shape, strides):
            dim = Dim(offset, offset + ashape * astride, ashape, astride,
                      single=False)
            dims.append(dim)
            offset = 0  # offset only applies to first dimension
        return cls(dims, itemsize)

    def __init__(self, dims, itemsize):
        self.dims = tuple(dims)
        self.ndim = len(self.dims)
        self.shape = tuple(dim.size for dim in self.dims)
        self.strides = tuple(dim.stride for dim in self.dims)
        self.itemsize = itemsize
        self.size = functools.reduce(operator.mul, self.shape, 1)
        self.extent = self._compute_extent()
        if config.NPY_RELAXED_STRIDES_CHECKING:
            self.flags = self._relaxed_compute_layout()
        else:
            self.flags = self._compute_layout()

    def _relaxed_compute_layout(self):
        # The logic here is based on that in _UpdateContiguousFlags from
        # numpy/core/src/multiarray/flagsobject.c in NumPy v1.19.1 (commit
        # 13661ac70).
        # https://github.com/numpy/numpy/blob/maintenance/1.19.x/numpy/core/src/multiarray/flagsobject.c#L123-L191

        # Records have no dims, and we can treat them as contiguous
        if not self.dims:
            return {'C_CONTIGUOUS': True, 'F_CONTIGUOUS': True}

        # If this is a broadcast array then it is not contiguous
        if any([dim.stride == 0 for dim in self.dims]):
            return {'C_CONTIGUOUS': False, 'F_CONTIGUOUS': False}

        flags = {'C_CONTIGUOUS': True, 'F_CONTIGUOUS': True}

        # Check C contiguity
        sd = self.itemsize
        for dim in reversed(self.dims):
            if dim.size == 0:
                # Contiguous by definition
                return {'C_CONTIGUOUS': True, 'F_CONTIGUOUS': True}
            if dim.size != 1:
                if dim.stride != sd:
                    flags['C_CONTIGUOUS'] = False
                sd *= dim.size

        # Check F contiguity
        sd = self.itemsize
        for dim in self.dims:
            if dim.size != 1:
                if dim.stride != sd:
                    flags['F_CONTIGUOUS'] = False
                    return flags
                sd *= dim.size

        return flags

    def _compute_layout(self):
        flags = {}

        if not self.dims:
            # Records have no dims, and we can treat them as contiguous
            flags['F_CONTIGUOUS'] = True
            flags['C_CONTIGUOUS'] = True
            return flags

        leftmost = self.dims[0].is_contiguous(self.itemsize)
        rightmost = self.dims[-1].is_contiguous(self.itemsize)

        def is_contig(traverse):
            last = next(traverse)
            for dim in traverse:
                if last.size != 0 and last.size * last.stride != dim.stride:
                    return False
                last = dim
            return True

        flags['F_CONTIGUOUS'] = leftmost and is_contig(iter(self.dims))
        flags['C_CONTIGUOUS'] = rightmost and is_contig(reversed(self.dims))
        return flags

    def _compute_extent(self):
        firstidx = [0] * self.ndim
        lastidx = [s - 1 for s in self.shape]
        start = compute_index(firstidx, self.dims)
        stop = compute_index(lastidx, self.dims) + self.itemsize
        stop = max(stop, start)   # ensure positive extent
        return Extent(start, stop)

    def __repr__(self):
        return '<Array dims=%s itemsize=%s>' % (self.dims, self.itemsize)

    def __getitem__(self, item):
        if not isinstance(item, tuple):
            item = [item]
        else:
            item = list(item)

        nitem = len(item)
        ndim = len(self.dims)
        if nitem > ndim:
            raise IndexError("%d extra indices given" % (nitem - ndim,))

        # Add empty slices for missing indices
        while len(item) < ndim:
            item.append(slice(None, None))

        dims = [dim.__getitem__(it) for dim, it in zip(self.dims, item)]
        newshape = [d.size for d in dims if not d.single]

        arr = Array(dims, self.itemsize)
        if newshape:
            return arr.reshape(*newshape)[0]
        else:
            return Element(arr.extent)

    @property
    def is_c_contig(self):
        return self.flags['C_CONTIGUOUS']

    @property
    def is_f_contig(self):
        return self.flags['F_CONTIGUOUS']

    def iter_contiguous_extent(self):
        """ Generates extents
        """
        if self.is_c_contig or self.is_f_contig:
            yield self.extent
        else:
            if self.dims[0].stride < self.dims[-1].stride:
                innerdim = self.dims[0]
                outerdims = self.dims[1:]
                outershape = self.shape[1:]
            else:
                innerdim = self.dims[-1]
                outerdims = self.dims[:-1]
                outershape = self.shape[:-1]

            if innerdim.is_contiguous(self.itemsize):
                oslen = [range(s) for s in outershape]
                for indices in itertools.product(*oslen):
                    base = compute_index(indices, outerdims)
                    yield base + innerdim.start, base + innerdim.stop
            else:
                oslen = [range(s) for s in self.shape]
                for indices in itertools.product(*oslen):
                    offset = compute_index(indices, self.dims)
                    yield offset, offset + self.itemsize

    def reshape(self, *newdims, **kws):
        oldnd = self.ndim
        newnd = len(newdims)

        if newdims == self.shape:
            return self, None

        order = kws.pop('order', 'C')
        if kws:
            raise TypeError('unknown keyword arguments %s' % kws.keys())
        if order not in 'CFA':
            raise ValueError('order not C|F|A')

        # check for exactly one instance of -1 in newdims
        unknownidx = -1
        knownsize = 1
        for i, dim in enumerate(newdims):
            if dim < 0:
                if unknownidx == -1:
                    unknownidx = i
                else:
                    raise ValueError("can only specify one unknown dimension")
            else:
                knownsize *= dim

        # compute the missing dimension
        if unknownidx >= 0:
            if knownsize == 0 or self.size % knownsize != 0:
                raise ValueError("cannot infer valid shape for unknown dimension")
            else:
                newdims = newdims[0:unknownidx] \
                        + tuple((self.size // knownsize,)) \
                        + newdims[unknownidx + 1:]

        newsize = functools.reduce(operator.mul, newdims, 1)

        if order == 'A':
            order = 'F' if self.is_f_contig else 'C'

        if newsize != self.size:
            raise ValueError("reshape changes the size of the array")

        if self.is_c_contig or self.is_f_contig:
            if order == 'C':
                newstrides = list(iter_strides_c_contig(self, newdims))
            elif order == 'F':
                newstrides = list(iter_strides_f_contig(self, newdims))
            else:
                raise AssertionError("unreachable")
        else:
            newstrides = np.empty(newnd, np.ctypeslib.c_intp)

            # need to keep these around in variables, not temporaries, so they
            # don't get GC'ed before we call into the C code
            olddims = np.array(self.shape, dtype=np.ctypeslib.c_intp)
            oldstrides = np.array(self.strides, dtype=np.ctypeslib.c_intp)
            newdims = np.array(newdims, dtype=np.ctypeslib.c_intp)

            if not attempt_nocopy_reshape(
                oldnd,
                olddims,
                oldstrides,
                newnd,
                newdims,
                newstrides,
                self.itemsize,
                order == 'F',
            ):
                raise NotImplementedError('reshape would require copy')

        ret = self.from_desc(self.extent.begin, shape=newdims,
                             strides=newstrides, itemsize=self.itemsize)

        return ret, list(self.iter_contiguous_extent())

    def squeeze(self, axis=None):
        newshape, newstrides = [], []
        if axis is None:
            for length, stride in zip(self.shape, self.strides):
                if length != 1:
                    newshape.append(length)
                    newstrides.append(stride)
        else:
            if not isinstance(axis, tuple):
                axis = (axis,)
            for ax in axis:
                if self.shape[ax] != 1:
                    raise ValueError(
                        "cannot select an axis to squeeze out which has size not equal "
                        "to one"
                    )
            for i, (length, stride) in enumerate(zip(self.shape, self.strides)):
                if i not in axis:
                    newshape.append(length)
                    newstrides.append(stride)
        newarr = self.from_desc(
            self.extent.begin,
            shape=newshape,
            strides=newstrides,
            itemsize=self.itemsize,
        )
        return newarr, list(self.iter_contiguous_extent())

    def ravel(self, order='C'):
        if order not in 'CFA':
            raise ValueError('order not C|F|A')

        if self.ndim <= 1:
            return self

        elif (order in 'CA' and self.is_c_contig or
                          order in 'FA' and self.is_f_contig):
            newshape = (self.size,)
            newstrides = (self.itemsize,)
            arr = self.from_desc(self.extent.begin, newshape, newstrides,
                                 self.itemsize)
            return arr, list(self.iter_contiguous_extent())

        else:
            raise NotImplementedError("ravel on non-contiguous array")

def iter_strides_f_contig(arr, shape=None):
    """yields the f-contiguous strides
    """
    shape = arr.shape if shape is None else shape
    itemsize = arr.itemsize
    yield itemsize
    sum = 1
    for s in shape[:-1]:
        sum *= s
        yield sum * itemsize

def iter_strides_c_contig(arr, shape=None):
    """yields the c-contiguous strides
    """
    shape = arr.shape if shape is None else shape
    itemsize = arr.itemsize

    def gen():
        yield itemsize
        sum = 1
        for s in reversed(shape[1:]):
            sum *= s
            yield sum * itemsize

    for i in reversed(list(gen())):
        yield i

def is_element_indexing(item, ndim):
    if isinstance(item, slice):
        return False

    elif isinstance(item, tuple):
        if len(item) == ndim:
            if not any(isinstance(it, slice) for it in item):
                return True

    else:
        return True

    return False

def _compute_size(start, stop, step):
    """Algorithm adapted from cpython rangeobject.c
    """
    if step > 0:
        lo = start
        hi = stop
    else:
        lo = stop
        hi = start
        step = -step
    if lo >= hi:
        return 0
    return (hi - lo - 1) // step + 1
<|endoftext|>