from typing import Callable
from dataclasses import dataclass
import pandas as pd
from hvac import Quantity
from hvac.fluids import HumidAir
from hvac.climate import ClimateData
from hvac.climate.sun.solar_time import time_from_decimal_hour
from ..core import (
    ExteriorBuildingElement,
    InteriorBuildingElement,
    InternalHeatGain,
    OnOffSchedule,
    TemperatureSchedule,
    ConstructionAssembly,
    ThermalNetwork,
    ThermalStorageNode,
    ZoneAirNode,
    ThermalNetworkSolver
)
from . import VentilationZone

Q_ = Quantity

@dataclass
class InternalThermalMass:
    A: Quantity
    R: Quantity
    C: Quantity

class Ventilation:

    def __init__(self):
        self.space: Space | None = None
        self.vz: VentilationZone | None = None

        self.n_min: float = 0.0
        self.V_open: float = 0.0
        self.V_ATD_d: float = 0.0
        self.V_sup: float = 0.0
        self.V_trf: float = 0.0
        self.V_exh: float = 0.0
        self.V_comb: float = 0.0

        self.T_sup: TemperatureSchedule | None = None
        self.T_trf: TemperatureSchedule | None = None
        self.Tdb_ext: TemperatureSchedule | None = None
        self.Twb_ext: TemperatureSchedule | None = None

    @classmethod
    def create(
        cls,
        space: 'Space',
        ventilation_zone: 'VentilationZone',
        n_min: Quantity = Q_(0.5, '1 / hr'),
        V_open: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_ATD_d: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_sup: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_trf: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_exh: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_comb: Quantity = Q_(0.0, 'm ** 3 / hr'),
        T_sup: TemperatureSchedule | None = None,
        T_trf: TemperatureSchedule | None = None,
    ) -> 'Ventilation':
        obj = cls()
        obj.space = space
        obj.vz = ventilation_zone
        obj.vz.add_space(space)

        obj.n_min = n_min.to('1 / hr').m
        obj.V_open = V_open.to('m ** 3 / hr').m
        obj.V_ATD_d = V_ATD_d.to('m ** 3 / hr').m
        obj.V_sup = V_sup.to('m ** 3 / hr').m
        obj.V_trf = V_trf.to('m ** 3 / hr').m
        obj.V_exh = V_exh.to('m ** 3 / hr').m
        obj.V_comb = V_comb.to('m ** 3 / hr').m

        obj.T_sup = T_sup or space.climate_data.Tdb_ext
        obj.T_trf = T_trf
        obj.Tdb_ext = space.climate_data.Tdb_ext
        obj.Twb_ext = space.climate_data.Twb_ext

        return obj

    @property
    def V_leak_ATD(self) -> float:
        """
        External airflow rate into the space through leakages and ATDs.
        (EN 12831-1 eq. 19: the leakage airflow rate into the ventilation zone
        is divided among the spaces according to their envelope surface area
        and the airflow rate through ATDs into the ventilation zone is divided
        among the spaces according to their design airflow rate through ATDs).
        """
        try:
            return (
                self.vz.V_leak * (self.space.envelope_area.to('m ** 2').m / self.vz.A_env) +
                self.vz.V_ATD * (self.V_ATD_d / self.vz.V_ATD_d)
            )
        except ZeroDivisionError:
            try:
                return self.vz.V_leak * (self.space.envelope_area.to('m ** 2').m / self.vz.A_env)
            except ZeroDivisionError:
                return 0.0

    @property
    def V_env(self) -> float:
        """
        External airflow rate into the space through the envelope.
        (EN 12831-1 eq. 18)
        """
        try:
            V_env = (
                (self.vz.V_inf_add / self.vz.V_env)
                * min(self.vz.V_env, self.V_leak_ATD * self.vz.f_dir)
            )
            V_env += (
                (self.vz.V_env - self.vz.V_inf_add)
                / self.vz.V_env * self.V_leak_ATD
            )
        except ZeroDivisionError:
            return float('nan')
        else:
            return V_env

    @property
    def V_tech(self) -> float:
        """
        Technical airflow rate into the space.
        (EN 12831-1 eq. 23)
        """
        return max(self.V_sup + self.V_trf, self.V_exh + self.V_comb)

    @property
    def V_min(self) -> float:
        """
        Minimum required airflow rate of the space that needs to be ensured in
        order to maintain an appropriate level of air hygiene.
        (EN 12831-1 eq. 33)
        """
        return self.n_min * self.space.volume.to('m ** 3').m

    @property
    def V_outdoor(self) -> Quantity:
        """
        Volume flow rate of outdoor air that enters the space.
        """
        V_outside = max(self.V_env + self.V_open, self.V_min - self.V_tech)
        return Q_(V_outside, 'm ** 3 / hr')

    def Q_ven_sen(self, t: float, T_int: float) -> float:
        """
        Sensible infiltration/ventilation load of the space.
        (EN 12831-1 eq. 17)
        """
        VT_inf = (
            max(self.V_env + self.V_open, self.V_min - self.V_tech)
            * (self.Tdb_ext(t) - T_int)
        )
        VT_sup = self.V_sup * (self.T_sup(t) - T_int)
        if self.T_trf:
            VT_trf = self.V_trf * (self.T_trf(t) - T_int)
        else:
            VT_trf = 0.0
        Q_ven = 0.34 * (VT_inf + VT_sup + VT_trf)  # see EN 12831-1 B.2.8.
        return Q_ven

    def Q_ven_lat(self, t: float) -> float:
        """
        Latent infiltration/ventilation load of the space
        (Principles of Heating, Ventilation, and Air Conditioning in
        Buildings, Chapter 10.5).
        """
        h_o = HumidAir(
            Tdb=Q_(self.Tdb_ext(t), 'degC'),
            Twb=Q_(self.Twb_ext(t), 'degC')
        ).h.to('J / kg').m
        h_x = HumidAir(
            Tdb=Q_(self.Tdb_ext(t), 'degC'),
            RH=self.space.RH_int
        ).h.to('J / kg').m
        rho_o = HumidAir(
            Tdb=Q_(self.Tdb_ext(t), 'degC'),
            Twb=Q_(self.Twb_ext(t), 'degC')
        ).rho.to('kg / m ** 3').m
        V_inf = max(self.V_env + self.V_open, self.V_min - self.V_tech) / 3600.0  # m³/s
        Q_ven_lat = rho_o * V_inf * (h_o - h_x)
        return Q_ven_lat

class Space:

    def __init__(self):
        self.ID: str = ''
        self.height: Quantity | None = None
        self.width: Quantity | None = None
        self.length: Quantity | None = None
        self.ventilation_zone: VentilationZone | None = None
        self.ventilation: Ventilation | None = None
        self.climate_data: ClimateData | None = None
        self.T_int_fun: Callable[[float], float] | None = None
        self.cooling_schedule: OnOffSchedule | None = None
        self.RH_int: Quantity | None = None
        self.ext_building_elements: dict[str, ExteriorBuildingElement] = {}
        self.int_building_elements: dict[str, InteriorBuildingElement] = {}
        self.int_heat_gains: dict[str, InternalHeatGain] = {}
        self.int_thermal_mass: InternalThermalMass | None = None
        self.dt_hr: float = 1.0
        self.n_cycles: int = 6
        self._hbm: HeatBalanceMethod | None = None
        self.Q_input_fun: Callable[[float, float | None], float] | None = None

    @classmethod
    def create(
        cls,
        ID: str,
        height: Quantity,
        width: Quantity,
        length: Quantity,
        climate_data: ClimateData,
        T_int_fun: Callable[[float], float],
        RH_int: Quantity = Q_(50, 'pct'),
        cooling_schedule: OnOffSchedule | None = None
    ) -> 'Space':
        """
        Create a space.

        Parameters
        ----------
        ID:
            Name of the space.
        height:
            Height of the space (vertical dimension).
        width:
            Width of the space (1st horizontal dimension).
        length:
            Length of the space (2nd horizontal dimension).
        climate_data:
            Instance of `ClimateData`, containing the relevant climatic data
            for the "design day".
        T_int_fun:
            Function that takes the time t in seconds from 00:00:00 and returns
            the setpoint space air temperature at that time. Can also be
            an instance of class `TemperatureSchedule`.
        RH_int:
            Design value for the relative humidity in the space (used to
            calculate the latent ventilation load).
        cooling_schedule:
            Function that takes the time t in seconds from 00:00:00 and returns
            a boolean to indicate if the cooling system is ON (True) or OFF
            (False). Can also be an instance of class `OnOffSchedule`.
        """
        space = cls()
        space.ID = ID
        space.height = height
        space.width = width
        space.length = length
        space.climate_data = climate_data
        space.T_int_fun = T_int_fun
        space.RH_int = RH_int
        # default internal thermal mass
        space.int_thermal_mass = InternalThermalMass(
            A=2 * space.floor_area,
            R=Q_(1 / 25 + 0.1, 'm ** 2 * K / W'),
            C=Q_(200, 'kJ / (m ** 2 * K)')
        )
        space.cooling_schedule = cooling_schedule
        return space

    def add_ext_building_element(
        self,
        ID: str,
        azimuth: Quantity,
        tilt: Quantity,
        width: Quantity,
        height: Quantity,
        construction_assembly: ConstructionAssembly,
        F_rad: float = 0.46,
        surface_absorptance: Quantity | None = None,
        surface_color: str = 'dark-colored'
    ) -> ExteriorBuildingElement:
        """
        Add an exterior building element to the space (exterior wall, roof)

        Parameters
        ----------
        ID:
            Name for the building element.
        azimuth:
            Azimuth angle of the building element.
        tilt:
            Tilt angle of the building element.
        width:
            The width of the building element.
        height:
            The height or length of the building element.
        construction_assembly:
            The construction assembly that describes the construction of
            the building element.
        F_rad:
            Fraction of conductive heat flow that is transferred by radiation to
            the interior thermal mass of the space. Default is 0.46 (see ASHRAE
            Fundamentals 2017, Ch. 18, Table 14).
        surface_absorptance: default None
            The solar absorptivity of the exterior surface (see ASHRAE
            Fundamentals 2017, Ch. 18, Table 15).
        surface_color: ['dark-colored', 'light-colored']
            Alternative for setting `surface_absorptance` explicitly.
        """
        ebe = ExteriorBuildingElement.create(
            ID=ID,
            azimuth=azimuth,
            tilt=tilt,
            width=width,
            height=height,
            climate_data=self.climate_data,
            construction_assembly=construction_assembly,
            T_int_fun=self.T_int_fun,
            F_rad=F_rad,
            surface_absorptance=surface_absorptance,
            surface_color=surface_color
        )
        self.ext_building_elements[ebe.ID] = ebe
        return ebe

    def add_int_building_element(
        self,
        ID: str,
        width: Quantity,
        height: Quantity,
        construction_assembly: Quantity,
        T_adj_fun: Callable[[float], float],
        F_rad: float = 0.46
    ) -> InteriorBuildingElement:
        """
        Add interior building element to the space.

        Parameters
        ----------
        ID:
            Name for the building element.
        width:
            The width of the building element.
        height:
            The height or length of the building element.
        construction_assembly:
            The construction assembly that describes the construction of
            the building element.
        T_adj_fun:
            Function that takes the time t in seconds from 00:00:00 and returns
            the temperature of the adjacent space at that time. Can also be
            an instance of class `TemperatureSchedule`.
        F_rad:
            Fraction of conductive heat flow that is transferred by radiation to
            the interior thermal mass of the space. Default is 0.46 (see ASHRAE
            Fundamentals 2017, Ch. 18, Table 14).
        """
        ibe = InteriorBuildingElement.create(
            ID=ID,
            width=width,
            height=height,
            construction_assembly=construction_assembly,
            T_adj_fun=T_adj_fun,
            F_rad=F_rad
        )
        self.int_building_elements[ibe.ID] = ibe
        return ibe

    def add_internal_heat_gain(self, ihg: InternalHeatGain) -> None:
        """
        Add internal heat gain to the space (see classes `EquipmentHeatGain`,
        `LightingHeatGain`, and `PeopleHeatGain` in subpackage
        `core.internal_heat_gains`).
        """
        self.int_heat_gains[ihg.ID] = ihg

    def add_internal_thermal_mass(
        self,
        A: Quantity,
        R: Quantity,
        C: Quantity
    ) -> None:
        """
        Add thermal mass to the space interior.

        Parameters
        ----------
        A:
            Surface area of the internal thermal mass
        R:
            Thermal unit resistance between thermal mass and space air. It is
            the sum of convective heat transfer resistance and that of any floor
            surfaces such as carpeting. The convective heat transfer coefficient
            for the interior of buildings is 25 W/(m².K). The thermal resistance
            of carpeting ranges from about 0.1 to 0.4 m².K/W [Principles of
            Heating, Ventilation and Air Conditioning in Buildings, p. 276].
        C:
            The capacitance of the internal thermal mass. This is an aggregate
            value of capacitance that takes into account storage in the floor,
            ceiling, partitions, and furnishings. It is in the range of 100 to
            300 kJ/(m².K) of floor area [Principles of Heating, Ventilation and
            Air Conditioning in Buildings, p. 276].
        """
        self.int_thermal_mass = InternalThermalMass(A, R, C)

    def add_ventilation(
        self,
        ventilation_zone: VentilationZone,
        n_min: Quantity = Q_(0.5, '1 / hr'),
        V_open: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_ATD_d: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_sup: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_trf: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_exh: Quantity = Q_(0.0, 'm ** 3 / hr'),
        V_comb: Quantity = Q_(0.0, 'm ** 3 / hr'),
        T_sup: TemperatureSchedule | None = None,
        T_trf: TemperatureSchedule | None = None
    ) -> None:
        """
        Add ventilation to the space.

        Parameters
        ----------
        ventilation_zone:
            The ventilation zone the space belongs to.
        n_min:
            Minimum air change rate required for the space for reasons of air
            quality/hygiene and comfort (NBN EN 12831-1, B.2.10 - Table B.7).
            Default value applies to permanent dwelling areas (living rooms,
            offices) and a ceiling height less than 3 m.
        V_open: Quantity, optional
            External air volume flow rate into the space through large openings
            (NBN EN 12831-1, Annex G).
        V_ATD_d:
            Design air volume flow rate of the ATDs in the room
            (NBN EN 12831-1, B.2.12). Only relevant if ATDs are used for
            ventilation.
        V_sup:
            Supply air volume flow rate from the ventilation system into the
            space.
        V_trf:
            Transfer air volume flow rate into the space from adjacent spaces.
        V_exh:
            Exhaust ventilation air volume flow rate from the space.
        V_comb:
            Air volume flow rate exhausted from the space that has not been
            included in the exhaust air volume flow of the ventilation system
            (typically, but not necessarily, combustion air if an open flue
            heater is present in the heated space).
        T_sup:
            Temperature of the ventilation supply air after passing heat recovery
            (see NBN EN 12831-1 §6.3.3.7) or after passive pre-cooling. Any
            temperature fall by active pre-cooling, which requires power from a
            cooling machine, shall not be taken into account (ventilation load is
            not a space load in that case).
        T_trf:
            Temperature of the transfer air volume flow into the space from
            another space. In case the room height of the other space is less
            than 4 m, it is equal to the internal design temperature of the other
            space; otherwise, it is equal to mean air temperature of the other
            space (see NBN EN 12831-1 §6.3.8.3).
        """
        self.ventilation = Ventilation.create(
            space=self,
            ventilation_zone=ventilation_zone,
            n_min=n_min,
            V_open=V_open,
            V_ATD_d=V_ATD_d,
            V_sup=V_sup,
            V_trf=V_trf,
            V_exh=V_exh,
            V_comb=V_comb,
            T_sup=T_sup,
            T_trf=T_trf
        )

    @property
    def envelope_area(self) -> Quantity:
        """
        Returns the area of the exterior building elements that surround the
        space.
        """
        if self.ext_building_elements:
            A_env = sum(
                ebe.area_gross
                for ebe in self.ext_building_elements.values()
            )
        else:
            A_env = Q_(0.0, 'm ** 2')
        return A_env

    @property
    def floor_area(self) -> Quantity:
        """
        Returns the floor area of the space.
        """
        return self.width * self.length

    @property
    def volume(self) -> Quantity:
        """
        Returns the space volume.
        """
        return self.floor_area * self.height

    def get_heat_gains(self, unit: str = 'W') -> pd.DataFrame:
        """
        Returns the heat gains into the space. The default unit is Watts ('W').
        """
        if self._hbm is None:
            self._hbm = HeatBalanceMethod(self, self.dt_hr, self.n_cycles)
        df = self._hbm.get_heat_gains(unit)
        return df

    def get_thermal_storage_heat_flows(
        self,
        T_unit: str = 'degC',
        Q_unit: str = 'W'
    ) -> pd.DataFrame:
        """
        Returns the thermal mass temperature, the heat flow into the thermal
        mass, the heat flow out of the thermal mass, and the heat being stored
        in the thermal mass.
        """
        if self._hbm is None:
            self._hbm = HeatBalanceMethod(self, self.dt_hr, self.n_cycles)
        df = self._hbm.get_thermal_storage_heat_flows(T_unit, Q_unit)
        return df

    def get_space_air_temperatures(
        self,
        unit: str = 'degC'
    ) -> pd.DataFrame:
        """
        Returns the space air temperature.
        """
        if self._hbm is None:
            self._hbm = HeatBalanceMethod(self, self.dt_hr, self.n_cycles)
        df = self._hbm.get_space_air_temperatures(unit)
        return df

class ThermalNetworkBuilder:
    """Builds the thermal network of the space."""

    @classmethod
    def build(cls, space: Space) -> tuple[ThermalNetwork, ThermalNetwork]:
        # thermal network when cooling ON (space air temperature known)
        nodes_cooling_on = []
        int_surf_node_indices = []
        for ebe in space.ext_building_elements.values():
            cls._modify_int_surf_node(ebe)
            nodes_cooling_on.extend(ebe.thermal_network.nodes)
            int_surf_node_indices.append(len(nodes_cooling_on) - 1)
        tsn = cls._create_thermal_storage_node(space)
        nodes_cooling_on.append(tsn)
        tnw_cooling_on = ThermalNetwork(nodes_cooling_on, int_surf_node_indices)
        # thermal network when cooling is OFF (space air temperature unknown)
        nodes_cooling_off = [node for node in nodes_cooling_on]
        zan = cls._create_zone_air_node(space)
        nodes_cooling_off.insert(-1, zan)
        tnw_cooling_off = ThermalNetwork(nodes_cooling_off, int_surf_node_indices)
        return tnw_cooling_on, tnw_cooling_off

    @staticmethod
    def _modify_int_surf_node(ebe) -> None:
        int_surf_node = ebe.thermal_network.nodes[-1]
        R_surf_film = int_surf_node.R[-1]
        R_rad = R_surf_film / ebe.F_rad
        R_conv = R_surf_film / (1.0 - ebe.F_rad)
        int_surf_node.R = [int_surf_node.R[0], R_conv, R_rad]

    @classmethod
    def _create_thermal_storage_node(cls, space: Space) -> ThermalStorageNode:
        int_surf_nodes = [
            ebe.thermal_network.nodes[-1]
            for ebe in space.ext_building_elements.values()
        ]
        R = [Q_(isn.R_unit[-1], 'm ** 2 * K / W') for isn in int_surf_nodes]
        R.append(space.int_thermal_mass.R)
        int_thermal_mass_node = ThermalStorageNode(
            ID='internal thermal mass',
            R_unit_lst=R,
            C=space.int_thermal_mass.C,
            A=space.int_thermal_mass.A,
            T_input=space.T_int_fun,
            Q_input=cls._get_tsn_Q_input_fun(space),
            windows=cls._get_windows(space),
            int_building_elements=list(space.int_building_elements.values()),
            ext_doors=cls._get_ext_doors(space),
            int_doors=cls._get_int_doors(space)
        )
        return int_thermal_mass_node

    @classmethod
    def _get_tsn_Q_input_fun(cls, space: Space) -> Callable[[float], float]:
        windows = cls._get_windows(space)
        ext_doors = cls._get_ext_doors(space)
        int_doors = cls._get_int_doors(space)

        def _Q_input(t: float, T_int: float | None = None) -> float:
            """
            Get the radiative heat gain at time t in seconds from 00:00:00 from
            windows, interior building elements, doors and internal heat gains
            (people, equipment, lightning).
            """
            T_int = T_int if T_int is not None else space.T_int_fun(t)
            Q_rad_wnd = sum(
                wnd.get_conductive_heat_gain(t, T_int)['rad']
                for wnd in windows
            )
            Q_rad_wnd += sum(
                wnd.get_solar_heat_gain(t)['rad']
                for wnd in windows
            )
            Q_rad_ibe = sum(
                ibe.get_conductive_heat_gain(t, T_int)['rad']
                for ibe in space.int_building_elements.values()
            )
            Q_rad_idr = sum(
                door.get_conductive_heat_gain(t, T_int)['rad']
                for door in int_doors
            )
            Q_rad_edr = sum(
                door.get_conductive_heat_gain(t, T_int)['rad']
                for door in ext_doors
            )
            Q_rad_ihg = sum(
                int_heat_gain.Q_sen(t)['rad']
                for int_heat_gain in space.int_heat_gains.values()
            )
            Q_rad = Q_rad_wnd + Q_rad_ibe + Q_rad_idr + Q_rad_edr + Q_rad_ihg
            return Q_rad

        return _Q_input

    @classmethod
    def _create_zone_air_node(cls, space: Space) -> ZoneAirNode:
        int_surf_nodes = [
            ebe.thermal_network.nodes[-1]
            for ebe in space.ext_building_elements.values()
        ]
        R = [Q_(isn.R_unit[-1], 'm ** 2 * K / W') for isn in int_surf_nodes]
        R.append(space.int_thermal_mass.R)
        zan = ZoneAirNode(
            ID='zone air node',
            R_unit_lst=R,
            Q_input=space.Q_input_fun,
            windows=cls._get_windows(space),
            int_building_elements=list(space.int_building_elements.values()),
            ext_doors=cls._get_ext_doors(space),
            int_doors=cls._get_int_doors(space)
        )
        return zan

    @staticmethod
    def _get_windows(space: Space):
        windows = [
            window
            for ebe in space.ext_building_elements.values()
            for window in ebe.windows.values() if ebe.windows
        ]
        return windows

    @staticmethod
    def _get_ext_doors(space: Space):
        ext_doors = [
            door
            for ebe in space.ext_building_elements.values()
            for door in ebe.doors.values() if ebe.doors
        ]
        return ext_doors

    @staticmethod
    def _get_int_doors(space: Space):
        int_doors = [
            door
            for ibe in space.int_building_elements.values()
            for door in ibe.doors.values() if ibe.doors
        ]
        return int_doors

class HeatBalanceMethod:

    def __init__(
        self,
        space: Space,
        dt_hr: float = 1.0,
        n_cycles: int = 6
    ):
        self.space = space
        tnw_cooling_on, tnw_cooling_off = ThermalNetworkBuilder.build(space)
        self._thermal_network = ThermalNetworkSolver.solve(
            thermal_networks=(tnw_cooling_on, tnw_cooling_off),
            cooling_schedule=space.cooling_schedule,
            dt_hr=dt_hr,
            n_cycles=n_cycles
        )
        self._dt = dt_hr * 3600

    def _calculate_conv_ext_build_elem_gain(self, k: int, T_nodes: list[float]) -> float:
        """
        Calculate heat flow from exterior building elements to space air at
        time index k
        """
        Q_env = 0.0
        if self.space.cooling_schedule is None:
            # cooling always ON
            T_int = self.space.T_int_fun(k * self._dt)
        else:
            # cooling may be ON or OFF depending on the cooling schedule
            T_int = T_nodes[-2]
        for i in self._thermal_network.int_surf_node_indices:
            T_isn = T_nodes[i]
            R_conv = self._thermal_network.nodes[i].R[1]
            Q_env += (T_isn - T_int) / R_conv
        return Q_env

    def _calculate_conv_therm_mass_gain(self, k: int, T_nodes: list[float]) -> float:
        """
        Calculate heat flow from interior thermal mass to space air at time
        index k
        """
        T_itm = T_nodes[-1]
        if self.space.cooling_schedule is None:
            T_int = self.space.T_int_fun(k * self._dt)
        else:
            T_int = T_nodes[-2]
        R_conv = self._thermal_network.nodes[-1].R[-1]
        Q = (T_itm - T_int) / R_conv
        return Q

    def _calculate_conv_window_gain(self, k: int, T_nodes: list[float]) -> float:
        """
        Calculate heat flow from windows to space air at time index k.
        """
        windows = [
            window
            for ebe in self.space.ext_building_elements.values()
            for window in ebe.windows.values() if ebe.windows
        ]
        Q_conv_sol = sum(
            window.get_solar_heat_gain(k * self._dt)['conv']
            for window in windows
        )
        if self.space.cooling_schedule is None:
            T_int = self.space.T_int_fun(k * self._dt)
        else:
            T_int = T_nodes[-2]
        Q_conv_cond = sum(
            window.get_conductive_heat_gain(k * self._dt, T_int)['conv']
            for window in windows
        )
        Q = Q_conv_sol + Q_conv_cond
        return Q

    def _calculate_conv_int_build_elem_gain(self, k: int, T_nodes: list[float]) -> float:
        """
        Calculate heat flow from interior building elements to space air at
        time index k
        """
        if self.space.cooling_schedule is None:
            T_int = self.space.T_int_fun(k * self._dt)
        else:
            T_int = T_nodes[-2]
        Q = sum(
            ibe.get_conductive_heat_gain(k * self._dt, T_int)['conv']
            for ibe in self.space.int_building_elements.values()
        )
        return Q

    def _calculate_conv_int_door_gain(self, k: int, T_nodes: list[float]) -> float:
        """
        Calculate heat flow from interior doors to space air at time index k.
        """
        doors = [
            door
            for ibe in self.space.int_building_elements.values()
            for door in ibe.doors.values() if ibe.doors
        ]
        if self.space.cooling_schedule is None:
            T_int = self.space.T_int_fun(k * self._dt)
        else:
            T_int = T_nodes[-2]
        Q = sum(
            door.get_conductive_heat_gain(k * self._dt, T_int)['conv']
            for door in doors
        )
        return Q

    def _calculate_conv_ext_door_gain(self, k: int, T_nodes: list[float]) -> float:
        """
        Calculate heat flow from exterior doors to space air at time index k.
        """
        doors = [
            door
            for ebe in self.space.ext_building_elements.values()
            for door in ebe.doors.values() if ebe.doors
        ]
        if self.space.cooling_schedule is None:
            T_int = self.space.T_int_fun(k * self._dt)
        else:
            T_int = T_nodes[-2]
        Q = sum(
            door.get_conductive_heat_gain(k * self._dt, T_int)['conv']
            for door in doors
        )
        return Q

    def _calculate_conv_int_heat_gain(self, k: int) -> float:
        """
        Calculate heat flow from internal heat gains to space air at time
        index k.
        """
        Q = sum(
            ihg.Q_sen(k * self._dt)['conv']
            for ihg in self.space.int_heat_gains.values()
        )
        return Q

    def _calculate_lat_int_heat_gain(self, k: int) -> float:
        """
        Calculate latent internal heat gains to space air at time index k.
        """
        Q = sum(
            ihg.Q_lat(k * self._dt)
            for ihg in self.space.int_heat_gains.values()
        )
        return Q

    def _calculate_sen_vent_gain(self, k: int, T_nodes: list[float]) -> float:
        """
        Calculate sensible heat gain to space air at time index k.
        """
        if self.space.ventilation is not None:
            if self.space.cooling_schedule is None:
                T_int = self.space.T_int_fun(k * self._dt)
            else:
                T_int = T_nodes[-2]
            Q = self.space.ventilation.Q_ven_sen(k * self._dt, T_int)
            return Q
        return 0.0

    def _calculate_lat_vent_gain(self, k: int) -> float:
        if self.space.ventilation is not None:
            Q = self.space.ventilation.Q_ven_lat(k * self._dt)
            return Q
        return 0.0

    def _calculate_thermal_storage_heat_flows(
        self,
        k: int,
        T_nodes: list[float]
    ) -> tuple[float, ...]:
        """
        Calculate heat flow into internal thermal mass, heat stored in internal
        thermal mass and heat flow out from internal thermal mass at time index
        k.
        """
        # heat flow from exterior building elements into internal thermal mass
        # at time index k
        Q_rad_ext = 0.0
        T_itm = T_nodes[-1]
        for i in self._thermal_network.int_surf_node_indices:
            T_isn = T_nodes[i]
            R_rad = self._thermal_network.nodes[i].R[-1]
            Q_rad_ext += (T_isn - T_itm) / R_rad
        # heat flow from windows, doors and interior building elements into
        # internal thermal mass at time index k
        if self.space.cooling_schedule is None:
            T_int = self.space.T_int_fun(k * self._dt)
        else:
            T_int = T_nodes[-2]
        Q_rad_other = self._thermal_network.nodes[-1].Q_input(k * self._dt, T_int)
        Q_in = Q_rad_ext + Q_rad_other
        # heat flow from internal thermal mass to space air at time index k
        T_int = self.space.T_int_fun(k * self._dt)
        R_itm = self._thermal_network.nodes[-1].R[-1]
        Q_out = (T_itm - T_int) / R_itm
        # heat stored in internal thermal mass at time index k
        Q_sto = Q_in - Q_out
        return T_itm, Q_in, Q_out, Q_sto

    def get_heat_gains(self, unit: str = 'W') -> pd.DataFrame:
        """
        Get Pandas `DataFrame` object with the heat gains to the space air and
        the cooling load of the space at each time moment of the design day
        in the measuring unit asked (default unit is Watts, 'W').
        """
        Q_gains = {
            'time': [],
            'Q_conv_ext': [],
            'Q_conv_int': [],
            'Q_conv_itm': [],
            'Q_conv_wnd': [],
            'Q_conv_ihg': [],
            'Q_sen_vent': [],
            'Q_sen_load': [],
            'Q_lat_ihg': [],
            'Q_lat_vent': [],
            'Q_lat_load': []
        }
        # noinspection PyProtectedMember
        for k, T_nodes in enumerate(self._thermal_network._T_node_table):
            Q_gains['time'].append(time_from_decimal_hour(k * self._dt / 3600))
            # heat flow from exterior building elements to space air at time
            # index k
            Q_gains['Q_conv_ext'].append(
                Q_(
                    self._calculate_conv_ext_build_elem_gain(k, T_nodes) +
                    self._calculate_conv_ext_door_gain(k, T_nodes),
                    'W'
                ).to(unit).m
            )
            # heat flow from interior building elements to space air at time
            # index k
            Q_gains['Q_conv_int'].append(
                Q_(
                    self._calculate_conv_int_build_elem_gain(k, T_nodes) +
                    self._calculate_conv_int_door_gain(k, T_nodes),
                    'W'
                ).to(unit).m
            )
            # heat flow from internal thermal mass to space air at time
            # index k
            Q_gains['Q_conv_itm'].append(
                Q_(
                    self._calculate_conv_therm_mass_gain(k, T_nodes),
                    'W'
                ).to(unit).m
            )
            # heat flow from windows to space air at time index k
            Q_gains['Q_conv_wnd'].append(
                Q_(
                    self._calculate_conv_window_gain(k, T_nodes),
                    'W'
                ).to(unit).m
            )
            # heat flow from internal heat gains to space air at time index k
            Q_gains['Q_conv_ihg'].append(
                Q_(
                    self._calculate_conv_int_heat_gain(k),
                    'W'
                ).to(unit).m
            )
            # heat flow from ventilation to space air at time index k
            Q_gains['Q_sen_vent'].append(
                Q_(
                    self._calculate_sen_vent_gain(k, T_nodes),
                    'W'
                ).to(unit).m
            )
            # sensible cooling load of space air at time index k
            Q_gains['Q_sen_load'].append(
                Q_gains['Q_conv_ext'][-1] +
                Q_gains['Q_conv_int'][-1] +
                Q_gains['Q_conv_itm'][-1] +
                Q_gains['Q_conv_wnd'][-1] +
                Q_gains['Q_conv_ihg'][-1] +
                Q_gains['Q_sen_vent'][-1]
            )
            # latent heat transfer from internal heat gains to space air at
            # time index k
            Q_gains['Q_lat_ihg'].append(
                Q_(
                    self._calculate_lat_int_heat_gain(k),
                    'W'
                ).to(unit).m
            )
            # latent heat transfer from ventilation to space air at time index
            # k
            Q_gains['Q_lat_vent'].append(
                Q_(
                    self._calculate_lat_vent_gain(k),
                    'W'
                ).to(unit).m
            )
            # latent cooling load of space air at time index k
            Q_gains['Q_lat_load'].append(
                Q_gains['Q_lat_ihg'][-1] +
                Q_gains['Q_lat_vent'][-1]
            )
        Q_gains = pd.DataFrame(Q_gains)
        Q_gains['Q_tot_load'] = Q_gains['Q_sen_load'] + Q_gains['Q_lat_load']
        return Q_gains

    def get_thermal_storage_heat_flows(
        self,
        T_unit: str = 'degC',
        Q_unit: str = 'W'
    ) -> pd.DataFrame:
        Q_therm_storage = {
            'time': [],
            'T_itm': [],
            'Q_in': [],
            'Q_out': [],
            'Q_sto': []
        }
        # noinspection PyProtectedMember
        for k, T_nodes in enumerate(self._thermal_network._T_node_table):
            tup = self._calculate_thermal_storage_heat_flows(k, T_nodes)
            Q_therm_storage['time'].append(
                time_from_decimal_hour(k * self._dt / 3600)
            )
            Q_therm_storage['T_itm'].append(
                Q_(tup[0], 'degC').to(T_unit).m
            )
            Q_therm_storage['Q_in'].append(
                Q_(tup[1], 'W').to(Q_unit).m
            )
            Q_therm_storage['Q_out'].append(
                Q_(tup[2], 'W').to(Q_unit).m
            )
            Q_therm_storage['Q_sto'].append(
                Q_(tup[3], 'W').to(Q_unit).m
            )
        Q_therm_storage = pd.DataFrame(Q_therm_storage)
        return Q_therm_storage

    def get_space_air_temperatures(
        self,
        unit: str = 'degC'
    ) -> pd.DataFrame:
        d = {
            'time': [],
            'T_int': []
        }
        # noinspection PyProtectedMember
        for k, T_nodes in enumerate(self._thermal_network._T_node_table):
            t = k * self._dt
            if self.space.cooling_schedule is None:
                T_int = self.space.T_int_fun(t)
            else:
                T_int = T_nodes[-2]
            d['time'].append(time_from_decimal_hour(t / 3600))
            d['T_int'].append(Q_(T_int, 'degC').to(unit).m)
        df = pd.DataFrame(d)
        return df


# Asyncio Python 3.7+ package comparision with Javascript async
# by default operates in single thread and CPU core
# and schedules tasks as coroutines to run in an event loop

import asyncio
#import requests incompatible because does not return awaitable tasks
# pyenv exec pip install aiohttp
import aiohttp
from aiohttp import ClientSession

# generator based coroutines removed since 3.10
# @asyncio.coroutine
# def worker(name, timeout_secs):
#   yield asyncio.sleep(timeout_secs)

async def worker(name: str, timeout_secs: int) -> str | Exception:
    if (timeout_secs < 0): raise Exception("bad job")
    print(name, " start job")
    # do something else in event loop while working for timeout secs
    await asyncio.sleep(timeout_secs)
    print(name, " end job")
    return "good job"

async def fetch_html(url: str, session: ClientSession, **kwargs) -> str:
    """GET request wrapper to fetch page HTML.

    kwargs are passed to `session.request()`.
    """

    resp = await session.request(method="GET", url=url, **kwargs)
    resp.raise_for_status()
    print("Got response [%s] for URL: %s", resp.status, url)
    html = await resp.text()
    return html

async def main() -> None:
    results = await asyncio.gather(
        worker("Fast", 1),
        worker("Avg", 2.5),
        worker("Slow", 4),
        worker("Fail", -1),
        return_exceptions=True)
    print(results)
    try:
        results2 = await asyncio.gather(
        worker("Fail", -1),
        worker("Good", 1),
        return_exceptions=False)
        print(results2)
    except Exception as e:
        print("caught exception ", e)
    async with ClientSession() as session:
        result3 = await fetch_html("http://ip.jsontest.com/", session)
        print(result3)

# possible to get default event loop instance but not usually needed
#loop = asyncio.get_event_loop()
#try:
#    loop.run_until_complete(main())
#finally:
#    loop.close()

if __name__ == "__main__":
    import time
    s = time.perf_counter()
    asyncio.run(main(), debug=True)
    elapsed = time.perf_counter() - s
    print(f"{__file__} executed in {elapsed:0.2f} seconds.")


"""
Exam task 2 Binary Tree
"""
from collections import deque
from queue import Queue

class BinatyTree:
    """
    Binary tree class with basic functional
    Version with tree as node
    with data, left and right children
    """

    def __init__(self, data) -> None:
        """
        Init root with default left and right as None
        """
        self.data = data
        self.left_child = None
        self.right_child = None

    def get_root(self):
        """
        Return root data
        """
        return self.data

    def set_root(self, value) -> None:
        """
        Set root data as value
        """
        self.data = value

    def add_left(self, item) -> None:
        """ Add the lefy child to the root.
            Left child of the new node is old tree's right child
        """
        new_subtree = BinatyTree(item)
        new_subtree.left_child = self.get_left()
        self.left_child = new_subtree

    def add_right(self, item) -> None:
        """ Add the right child to the root.
            Right child of the new node is old tree's right child
        """
        new_subtree = BinatyTree(item)
        new_subtree.right_child = self.get_right()
        self.right_child = new_subtree

    def get_right(self):
        """
        Return right child of the tree
        """
        return self.right_child

    def get_left(self):
        """
        Return left child of the tree
        """
        return self.left_child

    def is_leaf(self):
        """
        Check if self Node is leaf
        """
        return self.get_left() is None and self.get_right() is None

    def inorder(self) -> list:
        """
        Return list with tree vertices
        data visited in inorder travelsal
        """
        result = []

        def recurse(root):
            """
            Helper function to add nodes
            """
            if root.left_child is not None:
                recurse(root.left_child)
            result.append(root.data)
            if root.right_child is not None:
                recurse(root.right_child)
        recurse(self)
        return result

    def right_most(self) -> list:
        """
        Return list with rightmost nodes for
        each level (starts from the root)
        """
        # Set initial values
        node_queue = Queue()
        node_queue.put(self)
        rightmost = []
        while not node_queue.empty():
            # get number of elements on current level
            num_node_on_level = node_queue.qsize()
            # iterate through items on one level
            for i in range(num_node_on_level):
                current = node_queue.get()
                # it is the most right item
                if i == num_node_on_level - 1:
                    rightmost.append(current.data)
                # add children nodes to the queue
                if current.left_child is not None:
                    node_queue.put(current.left_child)
                if current.right_child is not None:
                    node_queue.put(current.right_child)
        return rightmost

    def leaf_paths(self) -> list:
        """
        Return list with leaf path to the root
        """
        # init starting values
        child_parent_dict = {}
        paths = []
        nodes_stack = deque()
        nodes_stack.append(self)

        while nodes_stack:
            current = nodes_stack.pop()
            # if current is a leaf find its root path
            if current.is_leaf():
                curr_value = current.data
                paths.append([curr_value])
                while curr_value in child_parent_dict:
                    curr_value = child_parent_dict[curr_value]
                    paths[-1].append(curr_value)
            else:
                # Append children nodes to the stack
                # add child-parent pairs to the dictionary.
                if current.right_child is not None:
                    nodes_stack.append(current.get_right())
                    child_parent_dict[current.right_child.data] = current.data
                if current.left_child is not None:
                    nodes_stack.append(current.left_child)
                    child_parent_dict[current.left_child.data] = current.data
        return paths


import numpy as np
import sys

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.batchnorm = nn.BatchNorm2d(3, affine=False)
        self.pad2 = nn.ConstantPad2d(2, 0)
        self.pad1 = nn.ConstantPad2d(1, 0)

        self.conv1 = nn.Conv2d(3, 32, 3)
        self.conv2 = nn.Conv2d(32, 64, 3)
        self.conv3 = nn.Conv2d(64, 32, 3)
        self.conv4 = nn.Conv2d(32, 16, 3)
        self.conv5 = nn.Conv2d(16, 1, 5)

    def forward(self, x):
        x = self.batchnorm(x.float())
        x = self.pad1(F.relu(self.conv1(x)))
        x = self.pad1(F.relu(self.conv2(x)))
        x = self.pad1(F.relu(self.conv3(x)))
        x = self.pad1(F.relu(self.conv4(x)))
        x = self.pad2(F.relu(self.conv5(x)))

        x = x.view(-1, 225)

        return x

def toTripleBoard(board, side):
    curBoard = np.zeros(shape=(3, 15, 15), dtype=np.float)
    blackBoard = np.zeros(shape=(15, 15), dtype=np.float)
    whiteBoard = np.zeros(shape=(15, 15), dtype=np.float)

    if side == 1:
        blackTurnBoard = np.ones(shape=(15, 15), dtype=np.float)
    else:
        blackTurnBoard = -np.ones(shape=(15, 15), dtype=np.float)
    curBoard[0,:] = blackTurnBoard
    for i in range(15):
        for j in range(15):
            if board[i, j] == 1:
	            blackBoard[i, j] = 1
            if board[i, j] == -1:
                whiteBoard[i, j] = -1
    curBoard[1,:] = blackBoard
    curBoard[2,:] = whiteBoard
    return curBoard

def isGg(board, color):
    if color == 0:
        size = 4
    else:
        size = -4
    for i in range(15):
        for j in range(10):
            if board[i, j] + board[i, j + 1] + board[i, j + 2] + board[i, j + 3] + board[i, j + 4] == size:
                for k in range(5):
                    if board[i, j + k] == 0:
                        return i, j + k

    for i in range(10):
        for j in range(15):
            if board[i, j] + board[i + 1, j] + board[i + 2, j] + board[i + 3, j] + board[i + 4, j] == size:
                for k in range(5):
                    if board[i + k, j] == 0:
                        return i + k, j

    for i in range(10):
        for j in range(10):
            if board[i, j] + board[i + 1, j + 1] + board[i + 2, j + 2] + board[i + 3, j + 3] + board[i + 4, j + 4] == size:
                for k in range(5):
                    if board[i + k, j + k] == 0:
                        return i + k, j + k
    for i in range(5, 15):
        for j in range(10):
            if board[i, j] + board[i - 1, j + 1] + board[i - 2, j + 2] + board[i - 3, j + 3] + board[i - 4, j + 4] == size:
                for k in range(5):
                    if board[i - k, j + k] == 0:
                        return i - k, j + k
    return (-1, -1)

def toTurn(turn):
    letter = ord(turn[0]) - ord('a')
    num = int(turn[1:]) - 1
    return letter, num

def to_move(pos):
    return idx2chr[pos[0]] + str(pos[1] + 1)

def to_pos(move):
    return chr2idx[move[0]], int(move[1:]) - 1

if __name__ == "__main__":
	device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
	torch.backends.cudnn.benchmark = True
	idx2chr = 'abcdefghjklmnop'
	chr2idx = {letter: pos for pos, letter in enumerate(idx2chr)}
	net = Net()
	net = net.to(device)
	net.load_state_dict(torch.load("finalNN.txt"))

	while True:
		gameStr = sys.stdin.readline()#input()#
		if not gameStr:
			break
		gameStr = gameStr.strip().split()
		curBoard = np.zeros(shape=(15, 15), dtype=np.float)

		for i in range(len(gameStr)):
		    x, y = to_pos(gameStr[i])
		    if i % 2 == 0:
		        curBoard[x, y] = 1
		    else:
		        curBoard[x, y] = -1

		color = len(gameStr) % 2

		board = toTripleBoard(curBoard, color)
		with torch.no_grad():
		    outputs = net(torch.unsqueeze(torch.from_numpy(board), 0))
		    _, netTurn = torch.max(outputs, 1)
		    netTurn = int(netTurn)
		    turnX, turnY = netTurn // 15, netTurn % 15
		    while curBoard[turnX, turnY] != 0:
		        outputs[netTurn] = 0
		        _, netTurn = torch.max(outputs, 1)
		        netTurn = int(netTurn)
		        turnX, turnY = netTurn // 15, netTurn % 15

		ggTest = isGg(curBoard, color)
		if ggTest != (-1, -1):
		    turnX, turnY = ggTest

		myTurn = to_move((turnX, turnY))
		sys.stdout.write(myTurn + '\n')
		sys.stdout.flush()


#!/usr/bin/env python
# coding: utf-8

# In[ ]:

# Author : Amir Shokri
# github link : https://github.com/amirshnll/Wine
# dataset link : http://archive.ics.uci.edu/ml/datasets/Wine
# email : amirsh.nll@gmail.com

# In[8]:

import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn import metrics

# In[9]:

col_names = ['class', 'Alcohol', 'Malic acid', 'Ash', 'Alcalinity of ash', 'Magnesium', 'Total phenols', 'Flavanoids', ' Nonflavanoid phenols','Proanthocyanins','Color intensity','Hue','OD280/OD315 of diluted wines','Proline']
wine =pd.read_csv("wine.csv",header=None, names=col_names)

# In[10]:

wine.head()

# In[11]:

inputs =wine.drop('class',axis='columns')
target =wine['class']
target

# In[13]:

input_train,input_test,target_train,target_test=train_test_split(inputs,target,test_size=0.3,random_state=1)

# In[14]:

clf =DecisionTreeClassifier()
clf =clf.fit(input_train,target_train)
y_pred = clf.predict(input_test)

# In[15]:

print ("Accuracy:",metrics.accuracy_score(target_test,y_pred))

# In[ ]:


import unittest
from util.tests.reusable import compare_speed
from solution.batch2.problem30 import *

class DigitFifthPowers(unittest.TestCase):
    def test_all_but_upper(self):
        expected = [
            [153, 370, 371, 407],
            [1634, 8208, 9474],
            [4150, 4151, 54748, 92727, 93084, 194_979]
        ]
        for n in range(3, 6):
            self.assertListEqual(expected[n - 3], digit_nth_powers_brute(n))
            self.assertListEqual(expected[n - 3], digit_nth_powers_builtin(n))

    def test_digit_nth_powers_speed(self):
        n = 6
        expected = [548_834]
        solutions = {
            "Brute": [digit_nth_powers_brute, n],
            "Built-in": [digit_nth_powers_builtin, n]
        }
        results = compare_speed(solutions)
        self.assertTrue(all(expected == actual for actual in results.values()))

if __name__ == '__main__':
    unittest.main()


from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import logging

_logger = logging.getLogger(__name__)

class StudentTranscript(models.TransientModel):
    _name = "student.transcript"
    _description = "Student Transcript"
    from_date = fields.Date(
        'From Date', required=True, default=lambda self: fields.Date.today())
    to_date = fields.Date(
        'To Date', required=True, default=lambda self: fields.Date.today())
    course_id = fields.Many2one('op.course', 'Course', required=True, default=lambda self: self._get_default_course())
    batch_id = fields.Many2one(
        'op.batch', 'Term', required=True, default=lambda self: self._get_default_batch())
    semester_id = fields.Many2one('pm.semester', 'Semester')
    final = fields.Boolean('Final Transcript')

    def _get_default_batch(self):
        student_id = self.env.context.get('active_id', False)
        student_course = self.env['op.student.course'].search([('student_id', '=', student_id),
                                                               ('p_active', '=', True)], limit=1)
        return student_course.batch_id.id

    def _get_default_course(self):
        student_id = self.env.context.get('active_id', False)
        student_course = self.env['op.student.course'].search([('student_id', '=', student_id),
                                                               ('p_active', '=', True)], limit=1)
        return student_course.course_id.id

    def print_report(self):
        student_id = self.env.context.get('active_id', False)
        order = self.semester_id.semester_order
        is_internship = self.semester_id.is_internship
        if self.semester_id:
            print("self.semester_id.start_date")
            semester_start_date = self.semester_id.start_date.strftime('%d/%m/%Y')
            semester_end_date = self.semester_id.end_date.strftime('%d/%m/%Y')
        else:
            semester_start_date = ''
            semester_end_date = ''

        res = {
            "student_id": student_id,
            "semester_order": order,
            "semester_start_date": semester_start_date,
            "semester_end_date": semester_end_date,
            "course_id": self.course_id.id,
            "batch_id": self.batch_id.id,
            "semester_id": self.semester_id.id,
            "is_final": self.final
        }
        if self.final:
            print("check validation")
            internship_semester_count = self.env['pm.semester'].search_count([('batch_id', '=', self.batch_id.id),
                                                                              ('is_internship', '=', True)])

            normal_semester_count = self.env['pm.semester'].search_count([('batch_id', '=', self.batch_id.id),
                                                                          ('is_internship', '=', False)])

            student_internship_count = self.env['op.placement.offer'].search_count([('student_id', '=', student_id),
                                                                                ('batch_id', '=', self.batch_id.id),
                                                                                ('p_completed', '=', True)])

            mark_sheets_count = self.env['pm.semester.marksheet.register'].search_count(
                [('batch_id', '=', self.batch_id.id),
                 ('semester_id.is_internship', '=', False),
                 ('state', '=', 'validated')])

            _logger.info("---------------*****************************---------------")
            _logger.info('internship_semester_count %s', internship_semester_count)
            _logger.info('normal_semester_count %s', normal_semester_count)
            _logger.info('student_internship_count %s', student_internship_count)
            _logger.info('mark_sheets_count %s', mark_sheets_count)

            # if internship_semester_count != student_internship_count:
            #     raise ValidationError(
            #         "Internship hasn't finished yet. Please completed before generating transcript")

            if normal_semester_count != mark_sheets_count:
                raise ValidationError("Please validate semester result before generating transcript")

            return self.env.ref(
                'pm_students.action_get_report_transcript') \
                .report_action(self, data=res)

        elif is_internship:
            print('internshpip')
            count = self.env['op.placement.offer'].search_count(
                [('student_id', '=', student_id), ('semester_id', '=', self.semester_id.id),
                 ('p_completed', '=', True)])
            if count <= 0:
                raise ValidationError("Internship hasn't finised yet. Please completed before generating transcript")
            else:
                return self.env.ref(
                    'pm_students.action_get_report_transcript') \
                    .report_action(self, data=res)
        else:
            print("check validation")
            count = self.env['pm.semester.marksheet.register'].search_count(
                [('semester_id', '=', self.semester_id.id), ('state', '=', 'validated')])
            if count <= 0:
                raise ValidationError("Please validate semester result before generating transcript")
            else:
                return self.env.ref(
                    'pm_students.action_get_report_transcript') \
                    .report_action(self, data=res)


from django.shortcuts import render, redirect
from .models import Diary
from datetime import datetime
import pytz
import json

diary_dict = {}
response ={}
def index(request):
    diary_dict = Diary.objects.all().values()
    if 'user_login' in request.session:
        response['user_name'] = request.session['user_login']
        response['user_npm'] = request.session['kode_identitas']
        response['author'] = "Arga Ghulam Ahmad"
        response['diary_dict'] = convert_queryset_into_json(diary_dict)
        response['button_logout_session'] = True
        return render(request, 'to_do_list.html', response)
    else:
        html = 'lab_9/session/login.html'
        return render(request, html, response)

def add_activity(request):
    try:
        if request.method == 'POST':
            date = datetime.strptime(request.POST['date'], '%Y-%m-%dT%H:%M')
            Diary.objects.create(date=date.replace(tzinfo=pytz.UTC), activity=request.POST['activity'])
            return redirect('/lab-3/')
    except ValueError:
        pass
    finally:
        return redirect('/lab-3/')

def convert_queryset_into_json(queryset):
    ret_val = []
    for data in queryset:
        ret_val.append(data)
    return ret_val


import glob
import numpy as np
from parsebook import Book
import bookNBC

def getData(loveBooksPath, horrorBooksPath):
    loveBooks = []
    horrorBooks = []

    lovefiles = glob.glob(loveBooksPath)
    for file in lovefiles:
        book = Book(file,"love")
        loveBooks.append(book)

    horrorfiles = glob.glob(horrorBooksPath)
    for file in horrorfiles:
        book = Book(file,"horror")
        horrorBooks.append(book)

    words = ["rose", "man", "like", "miss", "love", "smiles","around","hell", "something", "horror", "blood", "saw"]

    loveValues = Book.get_counts(loveBooks, words)
    horrorValues = Book.get_counts(horrorBooks, words)

    numattributes=len(words)

    loveData = np.insert(loveValues, numattributes, values=0, axis=1)
    horrorData = np.insert(horrorValues, numattributes, values=1, axis=1)

    data = []
    data = np.concatenate((loveData, horrorData), axis=0)

    return numattributes, data


"""
Python script to scrape a web page for all email addresses
"""

from bs4 import BeautifulSoup
import requests
import requests.exceptions
from urllib.parse import urlsplit
import re

url1 = "http://www.rit.edu/gccis/computingsecurity/people-categories/faculty"
url2 = "https://www.rit.edu/its/about/staff"

# a set of crawled emails
emails = set()

# get url's content
print("Processing %s" % url2)
response = requests.get(url2)

# extract all email addresses and add them into the resulting set

# regex for normally formatted emails. ex: ahadsheriff@gmail.com
email_normal = set(re.findall(r"[a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+", response.text, re.I))
emails.update(email_normal)

# regex for emails formatted with a [dot]. ex: ahadsheriff@gmail[dot]com
email_dot = set(re.findall(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\[dot\][A-Za-z]{2,4}", response.text, re.I))
emails.update(email_dot)

# regex for emails formatted with [at] and [dot]. ex: ahadsheriff[at]gmail[dot]com
email_at_dot = set(re.findall(r"[A-Za-z0-9._%+-]+\[at\][A-Za-z0-9.-]+\[dot\][A-Za-z]{2,4}", response.text, re.I))
emails.update(email_at_dot)

print(emails)


import numpy as np
import cv2

# 단일 채널 생성 및 초기화
m1 = np.full((3, 6), 10, np.uint8)
m2 = np.full((3, 6), 50, np.uint8)
m_mask = np.zeros(m1.shape, np.uint8)       # 마스크 생성
m_mask[:, 3:] = 1                           # 관심영역을 지정한 후, 1을 할당

m_add1 = cv2.add(m1, m2)                    # 행렬 덧셈
m_add2 = cv2.add(m1, m2, mask=m_mask)       # 관심영역만 덧셈, 관심영역이 아닌 부분은 0

# 행렬 나눗셈 수행
m_div1 = cv2.divide(m1, m2)

# 소수 부분 보존 위한 형변환
m1 = m1.astype(np.float32)
m2 = np.float32(m2)
m_div2 = cv2.divide(m1, m2)

titles = ['m1', 'm2', 'm_mask', 'm_add1', 'm_add2', 'm_div1', 'm_div2']
for title in titles:
    print("[%s] = \n%s \n" % (title, eval(title)))


import logging

from scrim_bot.core.role import Role, RoleIsInvalid

logger = logging.getLogger(__name__)

class Player:
    """
    Represents a player taking part in inhouse games
    """

    _id: int
    _name: str
    roles: list[Role]
    elo: float
    summoner_name: str

    def __init__(self, _id: int, name: str, roles: list[str] = None, elo: float = None, summoner_name: str = None):
        self._id = _id
        self._name = name
        self.roles = []
        self.elo = elo
        self.summoner_name = summoner_name

        if roles is not None:
            self.addRoles(roles)

    def __eq__(self, other):
        if isinstance(other, Player):
            return self.id == other.id
        return False

    def __name__(self, other):
        return not self.__eq__(other)

    def addRole(self, role: str):
        validated_role = Role.validateRole(role)
        if validated_role == Role.INVALID:
            logger.error(f"Role {role} is invalid")
            raise RoleIsInvalid(role)
        if validated_role in self.roles:
            logger.error(f"Role {validated_role.name} is already registered")
            return
        logger.info(f"Role {validated_role.name} has been registered")
        self.roles.append(validated_role)

    def addRoles(self, role_list: list[str]):
        for role in role_list:
            self.addRole(role)

    def removeRole(self, role: str):
        validated_role = Role.validateRole(role)
        if validated_role == Role.INVALID:
            logger.error(f"Role {role} is invalid")
            raise RoleIsInvalid(role)
        if validated_role not in self.roles:
            logger.error(f"Role {validated_role.name} is not registered")
            return
        logger.info(f"Role {validated_role} has been removed")
        self.roles.remove(validated_role)

    def removeRoles(self, role_list: list[str]):
        for role in role_list:
            self.removeRole(role)

    def getRoles(self):
        return [role.name for role in self.roles]

    @property
    def id(self):
        return self._id

    @property
    def name(self):
        return self._name

    # Encoding Player() object for MongoDB queries
    def encode_player(self):
        return {"_id": self._id, "name": self._name, "roles": self.getRoles(), "elo": self.elo,
                "summoner_name": self.summoner_name}


"""Utility script to be used to cleanup the notebooks before git commit

This a mix from @minrk's various gists.

"""

import time
import sys
import os
import io
try:
    from queue import Empty
except:
    # Python 2 backward compat
    from Queue import Empty

try:
    from ipyparallel import Client
except ImportError:
    # Backward compat prior to IPython / Jupyter split
    from IPython.parallel import Client
try:
    import nbformat
except ImportError:
    # Backward compat prior to IPython / Jupyter split
    from IPython import nbformat
try:
    from jupyter_client.manager import KernelManager
except ImportError:
    # Backward compat prior to IPython / Jupyter split
    from IPython.kernel import KernelManager

def remove_outputs(nb):
    """Remove the outputs from a notebook"""
    if hasattr(nb, 'worksheets'):
        # nobody uses more than 1 worksheet
        ws = nb.worksheets[0]
    else:
        # no more worksheet level in new format
        ws = nb
    for cell in ws.cells:
        if cell.cell_type == 'code':
            cell.outputs = []
            if 'prompt_number' in cell:
                del cell['prompt_number']
            if 'execution_count' in cell:
                # notebook format v4
                cell['execution_count'] = None

def remove_signature(nb):
    """Remove the signature from a notebook"""
    if 'signature' in nb.metadata:
        del nb.metadata['signature']

def run_cell(kc, cell, timeout=300):
    if not hasattr(cell, 'input'):
        return [], False
    kc.execute(cell.input)
    # wait for finish, maximum 5min by default
    reply = kc.get_shell_msg(timeout=timeout)['content']
    if reply['status'] == 'error':
        failed = True
        print("\nFAILURE:")
        print(cell.input)
        print('-----')
        print("raised:")
        print('\n'.join(reply['traceback']))
    else:
        failed = False

    # Collect the outputs of the cell execution
    outs = []
    while True:
        try:
            msg = kc.get_iopub_msg(timeout=0.2)
        except Empty:
            break
        msg_type = msg['msg_type']
        if msg_type in ('status', 'pyin', 'execute_input'):
            continue
        elif msg_type == 'clear_output':
            outs = []
            continue

        content = msg['content']

        # IPython 3 writes pyerr/pyout in the notebook format but uses
        # error/execute_result in the message spec. This does the translation
        # needed for tests to pass with IPython 3
        notebook3_format_conversions = {
            'error': 'pyerr',
            'execute_result': 'pyout'
        }
        msg_type = notebook3_format_conversions.get(msg_type, msg_type)
        out = nbformat.NotebookNode(output_type=msg_type)

        if 'execution_count' in content:
            cell['prompt_number'] = content['execution_count']
            out.prompt_number = content['execution_count']

        if msg_type == 'stream':
            out.stream = content['name']
            # in msgspec 5, this is name, text
            # in msgspec 4, this is name, data
            if 'text' in content:
                out.text = content['text']
            else:
                out.text = content['data']
        elif msg_type in ('display_data', 'pyout'):
            for mime, data in content['data'].items():
                attr = mime.split('/')[-1].lower()
                # this gets most right, but fix svg+html, plain
                attr = attr.replace('+xml', '').replace('plain', 'text')
                setattr(out, attr, data)
        elif msg_type == 'pyerr':
            out.ename = content['ename']
            out.evalue = content['evalue']
            out.traceback = content['traceback']
        else:
            print("unhandled iopub msg: %s" % msg_type)

        outs.append(out)

    # Special handling of ipcluster restarts
    if '!ipcluster stop' in cell.input:
        # wait some time for cluster commands to complete
        for i in range(10):
            try:
                if len(Client()) == 0:
                    break
            except OSError:
                pass
            sys.stdout.write("@")
            sys.stdout.flush()
            time.sleep(5)
    if '!ipcluster start' in cell.input:
        # wait some time for cluster commands to complete
        for i in range(10):
            try:
                if len(Client()) > 0:
                    break
            except OSError:
                pass
            sys.stdout.write("#")
            sys.stdout.flush()
            time.sleep(5)
    return outs, failed

def run_notebook(nb):
    km = KernelManager()
    km.start_kernel(stderr=open(os.devnull, 'w'))

    kc = km.client()
    kc.start_channels()
    try:
        kc.wait_for_ready()
    except AttributeError:
        # IPython < 3
        kc.kernel_info()
        while True:
            msg = kc.get_shell_msg(block=True, timeout=30)
            if msg['msg_type'] == 'kernel_info_reply':
                break

        # Flush IOPub channel
        while True:
            try:
                msg = kc.get_iopub_msg(block=True, timeout=0.2)
            except Empty:
                break

    # simple ping:
    kc.execute("pass")
    kc.get_shell_msg()

    cells = 0
    failures = 0
    if hasattr(nb, 'worksheets'):
        # nobody uses more than 1 worksheet
        ws = nb.worksheets[0]
    else:
        # no more worksheet level in new format
        ws = nb

    for cell in ws.cells:
        if cell.cell_type != 'code':
            continue

        outputs, failed = run_cell(kc, cell)
        cell.outputs = outputs
        cell['prompt_number'] = cells
        failures += failed
        cells += 1
        sys.stdout.write('.')
        sys.stdout.flush()

    print()
    print("ran %3i cells" % cells)
    if failures:
        print("    %3i cells raised exceptions" % failures)
    kc.stop_channels()
    km.shutdown_kernel()
    del km

def process_notebook_file(fname, action='clean', output_fname=None):
    print("Performing '{}' on: {}".format(action, fname))
    orig_wd = os.getcwd()
    # XXX: Ugly hack to preserve backward compat for now
    cmd = ('ipython nbconvert --to notebook --nbformat 3'
           ' --output="%s" "%s"') % (fname, fname)
    os.system(cmd)
    with io.open(fname, 'r') as f:
        nb = nbformat.read(f, nbformat.NO_CONVERT)

    if action == 'check':
        os.chdir(os.path.dirname(fname))
        run_notebook(nb)
        remove_outputs(nb)
        remove_signature(nb)
    elif action == 'render':
        os.chdir(os.path.dirname(fname))
        run_notebook(nb)
    else:
        # Clean by default
        remove_outputs(nb)
        remove_signature(nb)

    os.chdir(orig_wd)
    if output_fname is None:
        output_fname = fname
    with io.open(output_fname, 'w') as f:
        nb = nbformat.write(nb, f, nbformat.NO_CONVERT)

if __name__ == '__main__':
    # TODO: use argparse instead
    args = sys.argv[1:]
    targets = [t for t in args if not t.startswith('--')]
    action = 'check' if '--check' in args else 'clean'
    action = 'render' if '--render' in args else action

    rendered_folder = os.path.join(os.path.dirname(__file__),
                                   'rendered_notebooks')
    if not os.path.exists(rendered_folder):
        os.makedirs(rendered_folder)
    if not targets:
        targets = [os.path.join(os.path.dirname(__file__), 'notebooks')]

    for target in targets:
        if os.path.isdir(target):
            fnames = [os.path.abspath(os.path.join(target, f))
                      for f in sorted(os.listdir(target))
                      if f.endswith('.ipynb')]
        else:
            fnames = [target]
        for fname in fnames:
            if action == 'render':
                output_fname = os.path.join(rendered_folder,
                                            os.path.basename(fname))
            else:
                output_fname = fname
            process_notebook_file(fname, action=action,
                                  output_fname=output_fname)


# Write an action  print, To debug: print("Debug messages", file=sys.stderr)
import sys
import math

def available_neighboors(grid, r, c):

    res = []

    for cor in ((r+1, c),(r-1, c), (r, c+1), (r, c-1)):
        if grid[cor[0]][cor[1]] != '#':
            res.append((cor[0],cor[1]))

    return res

def calculate_next_direction(r, c, nr, nc):
    if nr - r > 0:
        return 'DOWN'
    elif nr - r < 0 :
        return 'UP'
    elif nc - c > 0:
        return 'RIGHT'
    else:
        return 'LEFT'

def bfs(initial_coordinates, grid, goal):

    dic_parents = {}
    memory = set()
    queue = []
    queue.append(initial_coordinates)
    memory.add(initial_coordinates)

    while len(queue) > 0:

        r,c = queue.pop(0)
        for rn, cn in available_neighboors(grid, r, c):

            if (rn, cn) not in memory:
                dic_parents[(rn, cn)] = (r, c)

                if grid[rn][cn]==goal:
                    while (rn, cn) in dic_parents:
                        (rnew, cnew) = dic_parents[(rn, cn)]
                        if (rnew, cnew) == initial_coordinates:
                            return rn,cn
                        else:
                            (rn,cn) = (rnew,cnew)

                memory.add((rn, cn))
                queue.append((rn, cn))

    print("BFS did not found goal", file=sys.stderr)
    return (-1,-1)

# H: number of rows. # W: number of columns.
# a: number of rounds between the time the alarm countdown is activated and the time the alarm goes off.
H, W, a = [int(i) for i in input().split()]
goals = (s for s in ['C','T'])
goal = '?'

# game loop
while True:

    # kr: row where Kirk is located.
    # kc: column where Kirk is located.
    kr, kc = [int(i) for i in input().split()]
    grid = list([input() for _ in range(H)])

    nr, nc = bfs((kr,kc), grid, goal)
    if (nr, nc) ==(-1,-1):
        goal = next(goals)
        nr, nc = bfs((kr,kc), grid, goal)

    print(f'goal : {goal}', file=sys.stderr)
    print(f'Kirk : {kr},{kc}', file=sys.stderr)
    print(f'Go to :{nr}{nc}', file=sys.stderr)

    direction = calculate_next_direction(kr,kc, nr,nc)
    print(direction)


from invenio.ext.sqlalchemy import db

domain = 'BBMRI'
# display_name = 'Biobanking and BioMolecular Resources Research Infrastructure'
display_name = 'Biomedical Research'
table_name = 'BBMRI'
image = 'domain-bbmri.png'
kind = 'project'
domaindesc = 'Biomedical research data.'

fields = [
    {
        'name': 'study_id',
        'col_type': db.String(256),
        'display_text': 'Study ID',
        'description': 'The unique ID or acronym for the study',
        'required': False
    },
    {
        'name': 'study_name',
        'col_type': db.String(256),
        'display_text': 'Study name',
        'description': 'The name of the study in English',
        'required': False
    },
    {
        'name': 'study_description',
        'col_type': db.String(256),
        'display_text': 'Study Description',
        'description': 'A description of the study aim',
        'required': False
    },
    {
        'name': 'principal_investigator',
        'col_type': db.String(256),
        'display_text': 'Principal Investigator',
        'description': 'The name of the person responsible for the study or the principal investigator',
        'required': False
    },
    {
        'name': 'study_design',
        'col_type': db.String(256),
        'display_text': 'Study design',
        'data_provide': 'select',
        'cardinality': 'n',
        'description': 'The type of study. Can be one or several of the following values.',
        'data_source': ['Case-control', 'Cohort', 'Cross-sectional', 'Longitudinal',
                        'Twin-study', 'Quality control', 'Population-based', 'Other'],
        'required': False
    },
    {
        'name': 'disease',
        'col_type': db.String(256),
        'display_text': 'Disease',
        'description': 'The disease of main interest in the sample collection, if any. Can be several values MIABIS-21',
        'required': False
    },
    {
        'name': 'categories_of_data_collected',
        'col_type': db.String(256),
        'display_text': 'Categories of data collected',
        'description': 'The type of data collected in the study, and if biological samples are part of the study. '
                       'Can be one or several of the following values: '
                       'Biological samples, Register data, Survey data, '
                       'Physiological measurements, Imaging data, Medical records, Other',
        'data_provide': 'select',
        'cardinality': 'n',
        'data_source': ['Biological samples', 'Register data', 'Survey data',
                        'Physiological measurements', 'Imaging data',
                        'Medical records', 'Other'],
        'required': False
    },
    {
        'name': 'planned_sampled_individuals',
        'col_type': db.String(256),
        'display_text': 'Planned sampled individuals',
        'description': 'Number of individuals with biological samples planned for the study',
        'required': False
    },
    {
        'name': 'planned_total_individuals',
        'col_type': db.String(256),
        'display_text': 'Planned total individuals',
        'description': 'Total number of individuals planned for the study with or without biological samples',
        'required': False
    },
    {
        'name': 'sex',
        'col_type': db.String(256),
        'display_text': 'Sex',
        'description': 'The sex of the study participants. Can be several of the following values: Female, Male, Other',
        'data_provide': 'select',
        'cardinality': 'n',
        'data_source': ['Female', 'Male', 'Other'],
        'required': False
    },
    {
        'name': 'age_interval',
        'col_type': db.String(256),
        'display_text': 'Age interval',
        'description': 'Age interval of youngest to oldest study participant, for example 40-80',
        'required': False
    },
    {
        'name': 'material_type',
        'col_type': db.String(256),
        'display_text': 'Material type',
        'description': 'The nature of the biological samples that are included in the study, if any. '
                       'Can be one or several of the following values: '
                       'Whole blood, Plasma, Serum, Urine, Saliva, CSF, DNA, RNA, Tissue, Faeces, Other',
        'data_provide': 'select',
        'cardinality': 'n',
        'data_source': ['Whole blood', 'Plasma', 'Serum', 'Urine', 'Saliva',
                        'CSF', 'DNA', 'RNA', 'Tissue', 'Faeces', 'Other'],
        'required': False
    },
]


"""Training script for End-to-end visuomotor controllers."""

import argparse
import os
from stat import ST_CTIME
import shutil
import re
import json
import pprint

import tensorflow as tf

from data.geeco_gym import pickplace_input_fn
from models.e2evmc.estimator import e2evmc_model_fn, goal_e2evmc_model_fn
from models.e2evmc.params import create_e2evmc_config
from models.e2evmc.utils import save_model_config, load_model_config
from utils.runscript import save_run_command

# ---------- command line arguments ----------

ARGPARSER = argparse.ArgumentParser(description='Train E2E VMC.')
# --- directory parameters
ARGPARSER.add_argument(
    '--dataset_dir', type=str, default='../data/gym-pick-pad2-cube2-v4',
    help='The path to the dataset (needs to conform with gym_provider).')
ARGPARSER.add_argument(
    '--split_name', type=str, default='default',
    help='The name of the data split to be used.')
ARGPARSER.add_argument(
    '--model_dir', type=str, default='../tmp/models/geeco-f',
    help='The directory where the model will be stored.')
# --- model parameters
ARGPARSER.add_argument(
    '--observation_format', type=str, default='rgb',
    help='Observation data to be used (sets img_channels): rgb | rgbd.')
ARGPARSER.add_argument(
    '--control_mode', type=str, default='cartesian',
    help='Control mode of the robot: cartesian | velocity.')
ARGPARSER.add_argument(
    '--goal_condition', type=str, default='none',
    help='Conditioning mode of the reflex. Options are: none | target. \
      none = no goal provided, unconditional reflex \
      target = target image provided, conditional reflex')
ARGPARSER.add_argument(
    '--window_size', type=int, default=4,
    help='The number of frames to process before making prediction.')
ARGPARSER.add_argument(
    '--dim_h_lstm', type=int, default=128,
    help='Hidden state dimension of the LSTM.')
ARGPARSER.add_argument(
    '--dim_h_fc', type=int, default=128,
    help='Output dimension of the LSTM (before decoding heads).')
ARGPARSER.add_argument(
    '--dim_s_obs', type=int, default=256,
    help='Output dimension of the observation encoding.')
ARGPARSER.add_argument(
    '--dim_s_dyn', type=int, default=256,
    help='Output dimension of the dynamics encoding.')
ARGPARSER.add_argument(
    '--dim_s_diff', type=int, default=256,
    help='Output dimension of the target difference encoding.')
ARGPARSER.add_argument(
    '--proc_obs', type=str, default='sequence',
    help='The processing type of the frame buffer: sequence | dynimg')
ARGPARSER.add_argument(
    '--proc_tgt', type=str, default='constant',
    help='The processing type of the target frame: constant | residual | dyndiff')
ARGPARSER.add_argument(
    '--l2_regularizer', type=float, default=0.0,
    help='The weight of the L2 weight regularizer. Zero disables weight regularization.')
ARGPARSER.add_argument(
    '--lambda_aux', type=float, default=1.0,
    help='The weight of the auxiliary pose prediction losses. Zero disables them.')
# --- data parameters
ARGPARSER.add_argument(
    '--data_encoding', type=str, default='v4',
    help='Version of the data encoding. Available: v1 | v2 | v3 | v4')
# --- training parameters
ARGPARSER.add_argument(
    '--lr', type=float, default=1e-4,
    help='The learning rate of the ADAM solver.')
ARGPARSER.add_argument(
    '--train_epochs', type=int, default=10,
    help='The number of epochs to train.')
# --- snapshot management
ARGPARSER.add_argument(
    '--ckpt_steps', type=int, default=10000,
    help='Number of steps between checkpoint saves.')
ARGPARSER.add_argument(
    '--num_last_ckpt', type=int, default=2,
    help='Number of last snapshots to keep.')
ARGPARSER.add_argument(
    '--num_best_ckpt', type=int, default=5,
    help='Number of best performing snapshots to keep.')
# --- memory management
ARGPARSER.add_argument(
    '--batch_size', type=int, default=32,
    help='The number of data points per batch. Each data points contains \
      `window_size` number of annotated frames as well as a tensor containing \
      the target commands.')
ARGPARSER.add_argument(
    '--memcap', type=float, default=0.8,
    help='Maximum fraction of memory to allocate per GPU.')
ARGPARSER.add_argument(
    '--num_threads', type=int, default=4,
    help='How many parallel threads to run for data fetching.')
ARGPARSER.add_argument(
    '--prefetch_size', type=int, default=4,
    help='How many batches to prefetch.')
ARGPARSER.add_argument(
    '--shuffle_buffer', type=int, default=64,
    help='Number of shuffled examples to draw minibatch from.')
# --- logging
ARGPARSER.add_argument(
    '--log_steps', type=int, default=1000,
    help='Global steps between log output.')
ARGPARSER.add_argument(
    '--debug', default=False, action='store_true',
    help="Enables debugging mode for more verbose logging and tensorboard \
      output.")
ARGPARSER.add_argument(
    '--initial_eval', default=False, action='store_true',
    help="Runs an evaluation before the first training iteration.")

# ---------- constants ----------

_OBSERVATION_FORMAT_TO_CHANNELS = {
    'rgb' : 3,
    'rgbd' : 4,
}

_GOAL_CONDITION_TO_MODEL = {  # cond -> (model_fn, model_scope)
    'none' : (e2evmc_model_fn, 'VMC'),
    'target' : (goal_e2evmc_model_fn, 'GoalVMC'),
}

# ---------- helper_fn ----------

# TODO: refactor into general training utils
def _export_snapshot(model_dir, eval_results, num_best_ckpt):
  """
  Manages `num_best_ckpt` snapshots of the best performing models according to
  eval_results['loss'] in <model_dir>/snapshots/
  """
  # --- directory setup
  snapshots_dir = os.path.join(model_dir, 'snapshots')
  os.makedirs(snapshots_dir, exist_ok=True)
  # --- snapshot index
  snapshot_index_file = os.path.join(snapshots_dir, 'snapshot_index.json')
  if os.path.exists(snapshot_index_file):  # load snapshot index
    with open(snapshot_index_file, 'r') as fp:
      snapshot_index = json.load(fp)
  else:  # create new snapshot index
    snapshot_index = {}  # <snapshot_name> -> {step, loss, dir}
  print(">>> Current snapshot index contains %d entries." % len(snapshot_index))  # DEBUG
  # --- get latest runcmd, config, step and loss
  ckpt_name = os.path.basename(tf.train.latest_checkpoint(model_dir))
  step = int(re.search(r'\d+', ckpt_name).group(0))
  loss = float(eval_results['loss'])
  runcmd_files = [fn for fn in os.listdir(model_dir) if fn.endswith('runcmd.json')]
  runcmd_files = [os.path.join(model_dir, fn) for fn in runcmd_files]
  runcmd_files = [(fn, os.stat(fn)[ST_CTIME]) for fn in runcmd_files]
  runcmd_files.sort(key=lambda t: t[1])  # sort by creation date asc
  runcmd_path = runcmd_files[-1][0]  # get latest ending in _runcmd.json
  config_files = [fn for fn in os.listdir(model_dir) if fn.endswith('config.json')]
  config_files = [os.path.join(model_dir, fn) for fn in config_files]
  config_files = [(fn, os.stat(fn)[ST_CTIME]) for fn in config_files]
  config_files.sort(key=lambda t: t[1])  # sort by creation date asc
  config_path = config_files[-1][0]  # get latest ending in _config.json
  # --- export current checkpoint to <model_dir>/snapshots/<ckpt_name>
  ckpt_dir = os.path.join(snapshots_dir, ckpt_name)
  os.makedirs(ckpt_dir, exist_ok=True)
  for cfg_fn in [runcmd_path, config_path]:  # copy configs
    shutil.copy(src=cfg_fn, dst=ckpt_dir)
  for ckpt_fn in [fn for fn in os.listdir(model_dir) if fn.startswith(ckpt_name)]:  # checkpoint files
    shutil.copy(src=os.path.join(model_dir, ckpt_fn), dst=ckpt_dir)
  with open(os.path.join(ckpt_dir, 'checkpoint'), 'w') as fp:  # create checkpoint header
    fp.write("model_checkpoint_path: \"%s\"\n" % ckpt_name)
  print(">>> Exported current checkpoint (step=%d; loss=%.06f) to %s." % (step, loss, ckpt_dir))  # DEBUG
  pprint.pprint(os.listdir(ckpt_dir))  # DEBUG
  # --- update index
  snapshot_index[ckpt_name] = {
      'step' : step,
      'loss' : loss,
      'dir' : ckpt_dir,
  }
  # --- gc worst snapshot, if save slots are exceeded
  if len(snapshot_index) > num_best_ckpt:
    ckpt_by_loss = list(snapshot_index.items())
    ckpt_by_loss.sort(key=lambda t: t[1]['loss'])
    worst_ckpt, _ = ckpt_by_loss[-1]  # get worst checkpoint name
    worst_ckpt_dir = snapshot_index[worst_ckpt]['dir']
    shutil.rmtree(worst_ckpt_dir)
    worst_info = snapshot_index.pop(worst_ckpt)
    print(">>> Removed worst snapshot (step=%d; loss=%.06f): %s" % \
      (worst_info['step'], worst_info['loss'], worst_info['dir']))  # DEBUG
  # --- save snapshot index
  pprint.pprint(snapshot_index)  # DEBUG
  with open(snapshot_index_file, 'w') as fp:
    json.dump(snapshot_index, fp, indent=2, sort_keys=True)
  print(">>> Saved snapshot index: %s" % snapshot_index_file)
  return ckpt_dir

# ---------- main ----------

def main(args):
  """Executes the model training."""
  # --- directory setup
  os.makedirs(name=args.model_dir, exist_ok=True)
  # --- run command
  run_cmd_path = save_run_command(argparser=ARGPARSER, run_dir=args.model_dir)
  # --- run config
  gpu_options = tf.GPUOptions(
      allow_growth=True,
      per_process_gpu_memory_fraction=args.memcap)
  sess_config = tf.ConfigProto(gpu_options=gpu_options)
  run_config = tf.estimator.RunConfig(
      session_config=sess_config,
      save_checkpoints_steps=args.ckpt_steps,
      keep_checkpoint_max=args.num_last_ckpt)
  # --- model config, TODO: refactor config CLI interface, loading and merging into model utils
  config_name = 'e2evmc_config'  # TODO: add config name CLI parameter
  config_fn = "%s.json" % (config_name, )
  config_path = os.path.join(args.model_dir, config_fn)
  if os.path.exists(config_path):  # load model config from previous run
    custom_params = load_model_config(args.model_dir, config_name)
    e2evmc_config = create_e2evmc_config(custom_params)
    print(">>> Loaded existing model config from %s" % (config_path, ))
  else:  # create new model config from CLI parameters
    custom_params = {
        'img_channels' : _OBSERVATION_FORMAT_TO_CHANNELS[args.observation_format],
        'control_mode' : args.control_mode,
        'window_size' : args.window_size,
        'dim_h_lstm' : args.dim_h_lstm,
        'dim_h_fc' : args.dim_h_fc,
        'dim_s_obs' : args.dim_s_obs,
        'dim_s_dyn' : args.dim_s_dyn,
        'dim_s_diff' : args.dim_s_diff,
        'proc_obs' : args.proc_obs,
        'proc_tgt' : args.proc_tgt,
        'l2_regularizer' : args.l2_regularizer,
        'lambda_aux' : args.lambda_aux,
        'batch_size' : args.batch_size,
        'lr' : args.lr,
    }
    e2evmc_config = create_e2evmc_config(custom_params)
    save_model_config(e2evmc_config._asdict(), args.model_dir, config_name)
    print(">>> Saved model config to %s" % (config_path, ))
  # --- estimator setup
  estimator_params = {
      'e2evmc_config' : e2evmc_config,
      'log_steps' : args.log_steps,
      'debug' : args.debug,
  }
  model_fn, model_scope = _GOAL_CONDITION_TO_MODEL[args.goal_condition]
  estimator = tf.estimator.Estimator(
      model_fn=model_fn,
      model_dir=args.model_dir,
      config=run_config,
      params=estimator_params)
  # --- data pipeline
  input_fn = lambda estimator_mode: pickplace_input_fn(
      dataset_dir=args.dataset_dir,
      split_name=args.split_name,
      mode=estimator_mode,
      encoding=args.data_encoding,
      window_size=e2evmc_config.window_size,
      fetch_target=(args.goal_condition == 'target'),
      shuffle_buffer=args.shuffle_buffer,
      batch_size=args.batch_size,
      num_epochs=1,
      num_threads=args.num_threads,
      prefetch_size=args.prefetch_size,
      seed=None)
  train_input = lambda: input_fn(estimator_mode='train')
  eval_input = lambda: input_fn(estimator_mode='eval')

  # --- extended init
  if args.initial_eval:
    eval_results = estimator.evaluate(input_fn=eval_input)
    # _export_snapshot(args.model_dir, eval_results, args.num_best_ckpt)

  # --- main training loop
  for epoch_id in range(args.train_epochs):
    estimator.train(input_fn=train_input)
    eval_results = estimator.evaluate(input_fn=eval_input)
    _export_snapshot(args.model_dir, eval_results, args.num_best_ckpt)

if __name__ == '__main__':
  print(">>> Training E2E VMC.")
  PARSED_ARGS, UNPARSED_ARGS = ARGPARSER.parse_known_args()
  print(">>> PARSED ARGV:")
  pprint.pprint(PARSED_ARGS)
  print(">>> UNPARSED ARGV:")
  pprint.pprint(UNPARSED_ARGS)
  tf.logging.set_verbosity(tf.logging.INFO)
  main(PARSED_ARGS)


import os
import json
import requests
from bs4 import BeautifulSoup
from datetime import datetime as dt
import pandas as pd
from __common__ import user_choice

def __load_json__(file):
    """
    """
    try:
        with open(file) as rf:
            data = json.load(rf)
        return data
    except:
        return {}

def __get_links__(url, base_url):
    """
    """
    r = requests.get(url)
    soup = BeautifulSoup(r.text)
    link_dict = {}
    ignore = ['[To Parent Directory]', 'DUPLICATE']
    for link in soup.findAll('a'):
        if link.text in ignore :
            pass
        else:
            link_dict[link.text] = base_url+link.get('href')
    return link_dict

def __gen_file_name__(data_keys, resource_type):
    """
    """
    if resource_type == 'Archive':
        i = -1
    else:
        i = -2
    name = set(map(lambda x: '-'.join(x.split('_')[:i]).lower(),
                   data_keys))
    name = list(name)
    if len(name) > 1:
        print("More than one file type detected!")
        return None
    else:
        if resource_type == 'Archive':
            name = '-'.join([name[0],resource_type.lower()])
        else:
            name = name[0]
        return name + '.csv'

def __gen_tracker_df__(data_files, data_keys, resource_type):
    """
    """
    if resource_type == 'Archive':
        timestamp = pd.to_datetime(list(map(lambda x:
                                   x.split('_')[-1][:-4],
                                   data_keys)))
        version = resource_type
    else:
        timestamp = pd.to_datetime(list(map(lambda x:
                                    x.split('_')[-2],
                                    data_keys)))
        version = list(map(lambda x: 'V'+x.split('_')[-1][:-4],
                                      data_keys))
    data_dict = {
        'TIMESTAMP': timestamp,
        'VERSION': version,
        'DOWNLOADED': False,
        'DOWNLOAD_DATE': pd.to_datetime('19000101'),
        'URL': list(data_files.values())
    }
    return pd.DataFrame(data_dict)

def __merge_trackers__(old_df, new_df):
    """
    Add anything from new_df that is not in old_df already
    """
    merge_cols = ['TIMESTAMP', 'VERSION']
    old_temp = old_df.copy()[merge_cols]
    new_temp = new_df.copy()[merge_cols]
    merge_df = pd.merge(old_temp, new_temp, on=merge_cols,
                        how='outer', indicator=True)
    # rows no longer in CURRENT NEM reports
    old_rows = merge_df[merge_df["_merge"]=="left_only"]
    old_rows = old_rows[merge_cols].merge(old_df, on=merge_cols)
      # keep only old rows that have been downloaded
      # else they need to extracted from ARCHIVE or older records
    old_rows = old_rows[old_rows.DOWNLOADED]
    # for common rows, must keep old_df records
    com_rows = merge_df[merge_df["_merge"]=="both"]
    com_rows = com_rows[merge_cols].merge(old_df, on=merge_cols)
    # for new rows, use latest records
    new_rows = merge_df[merge_df["_merge"]=="right_only"]
    new_rows = new_rows[merge_cols].merge(new_df, on=merge_cols)
    return pd.concat([old_rows, com_rows, new_rows],
                     ignore_index=True)

class NEM_tracker:
    """
    Class object for tracking various resources on NEM website.
    Can potentially be extended to other websites too.
    """

    def __init__(self, data_dir, base_url="http://nemweb.com.au"):
        """
        """
        self.data_dir = data_dir
        self.base_url = base_url
        self.resource_path = os.path.join(data_dir, 'resources.json')
        self.resources = __load_json__(self.resource_path)
        self.selected_resource = None
        self.tracker_dir = os.path.join(data_dir, 'resource_trackers')
        if os.path.isdir(self.tracker_dir):
            pass
        else:
            os.makedirs(self.tracker_dir)

    def resources_report(self):
        """
        """
        for k, v in self.resources.items():
            print('\n%s\nLast update: %s'%(k, v['last_update']))

    def update_resource(self, resource):
        """
        """
        url = self.base_url + resource
        data_files = __get_links__(url, self.base_url)
        data_keys = list(data_files.keys())
        resource_type = resource.split('/')[2]
        file_name = __gen_file_name__(data_keys, resource_type)
        self.resources[resource] = {
            'url': url,
            'type': resource_type,
            'tracker_file': file_name,
            'last_update': dt.now().strftime('%Y-%m-%d-%H:%M:%S')
        }
        with open(self.resource_path, 'w') as wf:
            json.dump(self.resources, wf)
        new_df = __gen_tracker_df__(data_files, data_keys,
                                    resource_type)
        file_path = os.path.join(self.tracker_dir, file_name)
        if file_name in os.listdir(self.tracker_dir):
            old_df = pd.read_csv(file_path, parse_dates=[
                'TIMESTAMP', 'DOWNLOAD_DATE'
            ])
            tracker_df = __merge_trackers__(old_df, new_df)
        else:
            tracker_df = new_df
        tracker_df.to_csv(file_path, index=False)

    def add_resources(self, resources):
        """
        For adding new resources.
        Resources must be specified as the relative URL
        path from the domain to the directory where all
        relevant data files are stored.
        E.g. /Reports/Current/Next_Day_Intermittent_DS/
        """
        if type(resources) is not list:
            resources = [resources]
        else:
            pass
        for resource in resources:
            self.update_resource(resource)

    def bulk_update(self):
        """
        For updating existing resources.
        """
        for resource in self.resources.keys():
            self.update_resource(resource)

    def select_resource(self, resource=None):
        """
        Select a resource for further processing.
        """
        d = {}
        if resource is None:
            res_list = self.resources.keys()
            name = user_choice(res_list)
        else:
            name = resource
        d['name'] = name
        tracker_file = self.resources[name]['tracker_file']
        d['tracker_path'] = os.path.join(self.tracker_dir,
                                         tracker_file)
        d['resource_dir'] = os.path.join(self.data_dir,
                                         tracker_file[:-4])
        self.selected_resource = d


import numpy as np
import torch
import torch.nn.functional as F
import torch.nn as nn
import os
import cv2 as cv

def rgb2gray(rgb):
    '''
    Transforms frame into a grayscale

    Args:
        rgb (numpy array) : numpy array grayscaled
    '''

    r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2]
    gray = 0.2989 * r + 0.5870 * g + 0.1140 * b

    return gray

def transform_frame(frame, n):
    '''
    Transform the size of the frame. Note that the frame should be shaped M*M*3 (square)

    Args:
        frame (numpy array) : shaped M*M*3
        n (int)             : new size of the frame (n*n*3)

    Returns:
        frame (torch tensor) : shaped n*n*3

    '''
    shape = frame.shape
    assert len(shape) == 3
    assert shape[0] == shape[1] and shape[2] == 3

    color0 = frame[:,:,0]
    color1 = frame[:,:,1]
    color2 = frame[:,:,2]
    current_frame = np.array([color0, color1, color2])
    batch = torch.from_numpy(current_frame).unsqueeze(0)
    batch = F.interpolate(batch.float(), (n, n))
    small_frame = batch.squeeze(0)
    #print(small_frame.permute(1,2,0).shape)
    return small_frame.permute(1,2,0)
    #return -small_frame[0,:,:].unsqueeze(dim = -1)

def discount_rewards(r, gamma):
    '''
    Discounted sum of the rewards : r_t + gamma * r_{t+1} + gamma^2 * r_{t+2} ...

    Args:
        r (list)      : List of rewards returned by the environment
        gamma (float) : discount factor

    Return:
        dr (float) : discounted sum of the rewards
    '''
    discounted_r = torch.zeros_like(r)
    running_add = 0
    for t in reversed(range(0, r.size(-1))):
        running_add = running_add * gamma + r[t]
        discounted_r[t] = running_add
    return discounted_r

class PrintLayer(nn.Module):
    '''
    Print layer shapes inside a sequential structure
    '''
    def __init__(self):
        super(PrintLayer, self).__init__()

    def forward(self, x):
        # Do your print / debug stuff here
        print(x.shape)
        return x

def listdir_nohidden(path):
    '''
    Extension to os.listdir to ignore files starting with '.' or '_'
    '''
    for f in os.listdir(path):
        if not (f.startswith('.') or f.startswith('_')):
            yield f

def render_video(path):
    '''
    Renders a video provided as a path.

    Args:
        path (numpy array, compressed format) : array of frames
    '''
    # TODO: support for compressed format
    video = np.load(path)

    for frame in video:
        cv.imshsow("", frame)
        cv.waitKey(33)