# -*- coding:utf-8 -*-
from __future__ import unicode_literals
from django.core.management.base import BaseCommand

from toolcontrol.utils import check_for_service

from loaners.models import Employee
from main.models import Company
from resources.models import Tool

class Command(BaseCommand):
    help = 'Sends a mail to every employee that has a tool that needs service'

    def handle(self, *args, **options):
        # First, message the admins of the tools that need service
        for company in Company.objects.all():
            tools = Tool.objects.filter(company=company)
            tools_to_service = []

            for tool in tools:
                if check_for_service(tool):
                    tools_to_service.append(tool)

            subject = 'Daglig serviceopdatering'
            message = ('Daglig serviceopdatering. Følgende værktøj mangler ' +
                       'service:\n')

            if not tools_to_service:
                message += 'Intet!'
            else:
                for tool in tools_to_service:
                    message += '%s (%s)\n' % (tool.name, tool.get_location())

                for admin in Employee.objects.filter(company=company,
                                                     is_admin=True):
                    admin.send_message(subject, message)

        # Send SMS to all loaners who have tools that need service
        for employee in Employee.objects.filter(company__isnull=False):
            tools_to_service = []
            send_message = False

            for tool in employee.tools.all():
                if check_for_service(tool):
                    tools_to_service.append(tool)

            subject = 'Daglig serviceopdatering'
            message = 'Daglig serviceopdatering. '

            if tools_to_service:
                message += ('Følgende værktøj i din besiddelse mangler ' +
                            'service:\n')
                for tool in tools_to_service:
                    message += '%s\n' % tool.name
                send_message = True
                tools_to_service = []

            for tool in Tool.objects.filter(building_site__foreman=employee,
                                            employee__isnull=True):
                if check_for_service(tool):
                    tools_to_service.append(tool)

            if tools_to_service:
                message += ('Følgende værktøj på en af dine byggepladser ' +
                            'mangler service:\n')
                for tool in tools_to_service:
                    message += '%s (%s)\n' % (tool.name, tool.building_site)
                send_message = True

            if send_message:
                employee.send_message(subject, message)


import sys
p={}

with open("contacts_long.txt","r") as contacts:
    for line in contacts:
        line=line.strip().split()
        p[line[0]]=line[1],line[2]

names=sys.stdin.readlines()
for name in names:
    name=name.strip()
    print("Name: {}".format(name))
    try:
        print("Phone: {}".format(p[name][0]))
        print("Email: {}".format(p[name][1]))
    except:

          print("No such contact")

CA116-CA117 Programming 1 & 2/2017-02-21/ca117/nolanm45/contacts_long_41.py


import os
import time
import warnings
import requests
import traceback
from IPython.core import magic_arguments
from IPython.core.magics import ExecutionMagics
from IPython.core.magic import cell_magic, magics_class
from os.path import expanduser

def notify_self(message):

    #A file called ~/.slack_webhook_jupyterhub is in every user's home folder
    #where the slack webhook is stored.
    homedir = expanduser("~")
    f = open(homedir + "/.slack_webhook_jupyterhub",'r')
    slack_webhook_url = f.readlines()
    slack_webhook_url = slack_webhook_url[0].replace("\n","")

    if slack_webhook_url is not None:
        r = requests.post(slack_webhook_url, json={"text": message})
        return r.text, message
    else:
        slack_token = os.getenv("SLACK_TOKEN")
        slack_id = os.getenv("SLACK_ID")
        if slack_token is not None and slack_id is not None:
            warnings.warn("Slack tokens are a legacy feature and may be deprecated at any time: "
                          "Consider moving to webhooks (see https://api.slack.com/messaging/webhooks) "
                          "for more details")
            parameters = {
                "token": slack_token,
                "channel": "@" + slack_id,
                "text": message
            }
            r = requests.post("https://slack.com/api/chat.postMessage", params=parameters)
            return r.text, message
        else:
            raise ValueError("Either $SLACK_WEBHOOK_URL must be set "
                             "(see https://api.slack.com/messaging/webhooks) "
                             "or both $SLACK_TOKEN and $SLACK_ID must be set "
                             "(see https://api.slack.com/custom-integrations/legacy-tokens)")

class Monitor:
    def __init__(self, msg, time=False,
            send_full_traceback=False, send_on_start=False,
            start_prefix="Started", end_prefix="Finished",
            err_prefix="Error while"):
        self.msg = msg
        self.time = time
        self.send_on_start = send_on_start
        self.send_full_traceback = send_full_traceback
        self.start_prefix = start_prefix
        self.end_prefix = end_prefix
        self.err_prefix = err_prefix

    @staticmethod
    def construct_time_mess(elapsed):
        day = elapsed // (24 * 3600)
        elapsed = elapsed % (24 * 3600)
        hour = elapsed // 3600
        elapsed %= 3600
        minutes = elapsed // 60
        elapsed %= 60
        seconds = round(elapsed, 1)
        time_mess = ""
        if day > 0:
            time_mess += " {} days".format(day)
        if hour > 0:
            time_mess += " {} hours ".format(hour)
        if minutes > 0:
            time_mess += " {} minutes".format(minutes)
        if seconds > 0:
            time_mess += " {} seconds".format(seconds)
        return time_mess

    def __enter__(self):
        self._start = time.time()
        if self.send_on_start: notify_self("{} {}".format(self.start_prefix, self.msg))
        return self

    def __exit__(self, exception_type, exception_value, tb):
        if exception_value is None:
            if self.time:
                elapsed = time.time() - self._start
                msg = "{} {} in {}".format(self.end_prefix, self.msg, self.construct_time_mess(elapsed))
            else: msg = "{} {}".format(self.end_prefix, self.msg)
            notify_self(msg)
        else:
            if self.send_full_traceback:
                trace = ''.join(traceback.format_exception(exception_type, exception_value, tb)).strip()
            else:
                trace = "{!r}".format(exception_value)
            msg = "{} {}'\n```\n{}\n```".format(self.err_prefix, self.msg, trace)
            notify_self(msg)
            raise exception_value.with_traceback(tb)

@magics_class
class MessengerMagics(ExecutionMagics):

    def __init__(self, shell):
        super().__init__(shell)

    @cell_magic
    @magic_arguments.magic_arguments()
    @magic_arguments.argument("message", type=str)
    @magic_arguments.argument("--time", "-t", action="store_true")
    def notify(self, line="", cell=None):
        args = magic_arguments.parse_argstring(self.notify, line)
        mess = args.message.replace("\"", "")
        with Monitor(mess, time=args.time):
            self.shell.ex(cell)


# 후보 인원 수: 2
# 투표 내용 : 1 1 2 2 2
# [2, 3]
# 기호 : 1, 득표 수 :2
# 기호 : 2, 득표 수 :3
# 최다 득표자: 기호2

# countingVotes : 각 후보들의 득표 수를 list로 반환해 줍니다.
def countingVotes(numbers, votes):
    earns = dict()
    for i in votes:
        if i in earns :
            earns[i] = earns[i] + 1
        else:
            earns[i] = 1
    print(earns)
    list1 = sorted(earns.keys())
    list2 = [earns[i] for i in list1]
    print(list1,list2,sep='\n')
    return list2

# printMaximum : 최다 득표자의 정보를 출력해 줍니다.
def printMaximum(earns):
    maxre = getMaxInfo(earns)
    print(f'최다 득표자 : 기호 {maxre}번')

# getMaxInfo : 최다 득표자의 기호 번호를 반환 해주는 함수입니다.
def getMaxInfo(earns):
    result = 0
    for i in range(1,len(earns)):
        if earns[result] < earns[i]:
            result = i
    return result+1

number = 2
votes = [1,1,3,2,2,2,3,4,5,5,2]
earns = countingVotes(number,votes)
print(earns)
for i in range(len(earns)):
    print(f'기호 {i+1}번 : {earns[i]}표')
printMaximum(earns)


import argparse
import sys

OPTIONS = None

def parse_args(args=None):
    global OPTIONS
    if args is None:
        args = sys.argv[1:]

    parser = argparse.ArgumentParser()
    parser.add_argument("-c", "--config")
    parser.add_argument("-d", "--debug", action="store_true", default=False)

    OPTIONS = parser.parse_args(args)


import unittest

from validate_test import TestStateValidation

def create_suite():
    test_suite = unittest.TestSuite()
    test_suite.addTest(unittest.makeSuite(TestStateValidation))
    return test_suite

if __name__ == '__main__':
    suite = create_suite()

    runner = unittest.TextTestRunner()
    runner.run(suite)


import os
import sys

from PyQt5 import QtCore, QtQml, QtWidgets
from sortfilterproxymodel import SortFilterProxyModel
from mymodel import MyModel, MyItem

osname = os.name.lower()
sysplatform = sys.platform.lower()
windows = os.name.lower() == "nt" and sysplatform.startswith("win")

# PyQt class name, QML URI, major version, minor version, QML type name
QtQml.qmlRegisterType(MyModel, 'MyModel', 1, 0, 'MyModel')
QtQml.qmlRegisterType(MyItem, 'MyItem', 1, 0, 'MyItem')
QtQml.qmlRegisterType(SortFilterProxyModel, 'SortFilterProxyModel', 1, 0, 'SortFilterProxyModel')

app = QtWidgets.QApplication(sys.argv)

# Create the QML engine
engine = QtQml.QQmlEngine(app)
engine.quit.connect(app.quit)

# Load the main.qml file and create the toplevel component
component = QtQml.QQmlComponent(engine)
currentFilePath = os.path.dirname(os.path.abspath(__file__))
mainFilepath = os.path.join(currentFilePath, "main.qml")
if windows:
    mainFilepath = mainFilepath.replace('\\', '/')
qmlFile = QtCore.QUrl("file:///" + mainFilepath)
component.loadUrl(qmlFile)
if component.status() != QtQml.QQmlComponent.Ready:
    for error in component.errors():
        print(error.toString())
    sys.exit(-1)

topLevelItem = component.create()
if not topLevelItem:
    for error in component.errors():
        print(error.toString())
    sys.exit(-1)

# Now run the main loop until the user closes the application
topLevelItem.show()
sys.exit(app.exec_())


#temporal_data_transform.py

import pandas as pd
import numpy as np
import datetime
from dateutil.rrule import rrule, MONTHLY
from dateutil.relativedelta import relativedelta, MO
import csv

#each row: add column value reading in from infoDict

dfData = pd.read_excel("/Users/agupta21/Downloads/MSE246/externaldata.xlsx")
infoDict = {}

for index, row in dfData.iterrows():
        #in timestamp format
        infoDict[row['Date']] = [row['P/E'],row['Unemployment'],row['Fed_Funds_Rate'],row['GDP_prev_q']]

#need to integrate these four values into the row in addition to the 0-1 label

df = pd.read_excel("/Users/agupta21/Downloads/MSE246/dataMoreFeatures.xlsx")

with open('/Users/agupta21/Downloads/MSE246/data_time_series3.csv','w') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['LoanNum','Program','BorrName','BorrStreet', 'BorrCity', 'BorrState','BorrZip','CDC_Name',
    'CDC_Street','CDC_City','CDC_State','CDC_Zip','ThirdPartyLender_Name','ThirdPartyLender_City','ThirdPartyLender_State',
    'ThirdPartyDummy','ThirdPartyDollars','GrossApproval','LoanSum','ApprovalDate',	'ApprovalFiscalYear','DeliveryMethod',
    'subpgmdesc', 'InitialInterestRate','TermInMonths','NaicsCode',	'NaicsDescription', 'ProjectCounty','ProjectState',
    'BusinessType', 'LoanStatusCat','LoanStatus', 'ChargeOffDate', 'GrossChargeOffAmount','unemp_comp','biz_net_income',
    'adj_gross_income','net_cap_gain','total_tax_liab','2018-2010','2010-2000', '2000-1990','T-t0(years)','Cur_T', 'P/E_t','unemp_t','interest_rate_t',
    'GDPchange_t','value'])
    count = 0
    for index, row in df.iterrows():
        count+=1
        if pd.isnull(row["2018-2010"]):
            continue
        if count < 10000000:
            print(count)
            startDate = row['ApprovalDate']
            startDate = startDate.replace(day=1) #make it the first of the month
            lastMonth = 0 #FINAL VALUE
            if pd.isnull(row['ChargeOffDate']): #case where there is no charge off
                termLen = int(row['TermInMonths']/12) #iterate from start thru end
            else: #case where there is a charge off
                chargeOffDate = row['ChargeOffDate']
                chargeOffDate = chargeOffDate.replace(day=1)
                #print(chargeOffDate,startDate)
                #termLen = len([dt for dt in rrule(YEARLY, dtstart=startDate, until=chargeOffDate)])
                termLen = relativedelta(chargeOffDate, startDate).years
                #print(termLen)
                lastMonth = 1
            #MANUALLY ADD t-t0 from data 2 later on!!
            for i in range(termLen):
                #add one month
                originfo = row.tolist() #all the information
                if i ==0:
                    concat_list_0 = [count] + originfo + [i, startDate] + infoDict[startDate] + [0]
                    writer.writerow(concat_list_0)
                originfo = row.tolist()
                t_t0 = i+1
                startDate = startDate + relativedelta(months=12)
                if startDate in infoDict:
                    features = infoDict[startDate]
                else:
                    break
                if i < termLen-1:
                    list2 = [t_t0,startDate, features[0],features[1],features[2],features[3],0]
                else: #equals it
                    list2 = [t_t0,startDate, features[0],features[1],features[2],features[3],lastMonth]
                concat_list = [count]+ originfo + list2
                writer.writerow(concat_list)


import rclpy
from rclpy.node import Node

from std_msgs.msg import String

class EasySubscriber(Node):

    def __init__(self):
        super().__init__('easy_subscriber')
        self.subscription = self.create_subscription(String, 'words', self.listener_callback, 10)
        self.subscription
        self.i = 0

    def listener_callback(self, msg):
        self.get_logger().info('I heard: "%s"' % msg.data)

def main(args=None):
    rclpy.init(args=args)

    easy_subscriber = EasySubscriber()

    rclpy.spin(easy_subscriber)

    easy_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()


from flask_restful import Resource
from flask_jwt_extended import jwt_required
import random
from flask import request

from app.models import Book, Borrow
from app.helpers.parsers import add_book_parser, get_parser, edit_book_parser
from app.helpers.decorators import admin_required
from app.helpers.helper_functions import search_book

class AddBook(Resource):
    """
    Contains all the methods to add book, list all books
    """
    @jwt_required
    @admin_required
    def post(self):
        """Post method to allow addition of book"""
        add_book_args = add_book_parser.parse_args()
        book_title = add_book_args['book_title'].strip().title()
        authors = add_book_args['authors'].strip().title()
        year = add_book_args['year'].strip()
        edition = add_book_args['edition']
        city_published = add_book_args['city_published'].strip().title()
        book_isnb = add_book_args['book_isnb'].strip()
        publisher = add_book_args['publisher'].strip().title()
        copies = add_book_args['copies']
        available_by_isnb = Book.query.filter_by(book_isnb=book_isnb).first()
        available_by_author_title = Book.query.filter_by(book_title=book_title, authors=authors).first()
        if available_by_isnb:
            return {"message": "This book's id is {}. Please update it.".format(available_by_isnb.book_id)}, 409
        if available_by_author_title:
            return {"message": "This book's is {}. Please update it.".format(available_by_author_title.book_id)}, 409
        new_book = Book(book_id=random.randint(1111, 9999), book_title=book_title, authors=authors,
                        edition=edition, city_published=city_published, book_isnb=book_isnb,
                        publisher=publisher, year=year, copies=copies)
        new_book.save_book()
        return {"message": "The book was added successfully.", "book_added": new_book.book_serializer()}, 201

    def get(self):
        """Get method to get all books"""
        get_books_args = get_parser.parse_args()
        page = get_books_args['page']
        limit = get_books_args['limit']
        q = request.args.get('q')
        books = Book.query.paginate(page=page, per_page=limit, error_out=False)
        all_books = books.items
        num_results = books.total
        total_pages = books.pages
        current_page = books.page
        if q:
            return search_book(q)
        # if str(page) > str(total_pages):
        #     return {"message": "Page you try to access is not found"}, 404
        if current_page == 1:
            has_prev_page = None
        has_next_page = books.has_next
        has_prev_page = books.has_prev
        prev_num = books.prev_num
        next_num = books.next_num
        if not all_books:
            return {"message": "Books not found"}, 404
        results = [book.book_serializer() for book in all_books]
        return {
            "Total results": num_results,
            "Total Pages": total_pages,
            "Current page": current_page,
            "All books": results,
            "Previous page": prev_num,
            "Next page": next_num
               }, 200

class SingleBook(Resource):

    """
    Contains all activities of a single book, including editing, getting and removing a book.
    """
    @jwt_required
    @admin_required
    def put(self, book_id):
        """Put method to edit already existing book. Book title, isnb and authors is not edited."""
        edit_book_args = edit_book_parser.parse_args()
        year = edit_book_args['year'].strip()
        edition = edit_book_args['edition']
        publisher = edit_book_args['publisher'].strip().title()
        city_published = edit_book_args['city_published'].strip().title()
        copies = edit_book_args['copies']
        get_book = Book.query.filter_by(book_id=book_id).first()
        if not get_book:
            return {"The book is not found"}, 404
        get_book.year = year
        get_book.edition = edition
        get_book.publisher = publisher
        get_book.city_published = city_published
        get_book.copies = copies
        get_book.update_book()
        edited_book = get_book.book_serializer()
        return {"message": "The book was edited successfully.", "details": edited_book}, 200

    @jwt_required
    @admin_required
    def delete(self, book_id):
        """Delete method to delete a single book"""
        get_book_id = Book.query.filter_by(book_id=book_id).first()
        is_borrowed = Borrow.query.filter_by(book_id=book_id).first()
        if get_book_id:
            if is_borrowed:
                return {"message": "This book is currently borrowed and cannot be deleted."}, 403
            get_book_id.delete_book()
            return {"message": "The book was deleted successfully."}, 200
        return {"error": "Book not found."}, 404

    def get(self, book_id):
        """Get method for a single book"""
        book = Book.query.filter_by(book_id=book_id).first()
        if book:
            result = book.book_serializer()
            return {"book_details": result}, 200
        return {"error": "Book not found."}, 404


import datetime
import minimalmodbus
import time

BYTEORDER_LITTLE_SWAP = 3

ADDR_VOLTAGE_A = 0
ADDR_VOLTAGE_B = 2
ADDR_VOLTAGE_C = 4
# ADDR_VOLTAGE_AVERAGE = 6
ADDR_CURRENT_A = 10
ADDR_CURRENT_B = 12
ADDR_CURRENT_C = 14
ADDR_ACTIVE_POWER_A = 26
ADDR_ACTIVE_POWER_B = 28
ADDR_ACTIVE_POWER_C = 30
ADDR_TOTAL_ACTIVE_POWER = 32
ADDR_REACTIVE_POWER_A = 34
ADDR_REACTIVE_POWER_B = 36
ADDR_REACTIVE_POWER_C = 38
ADDR_TOTAL_REACTIVE_POWER = 40
ADDR_APPARENT_POWER_A = 42
ADDR_APPARENT_POWER_B = 44
ADDR_APPARENT_POWER_C = 46
ADDR_TOTAL_APPARENT_POWER = 48
ADDR_POWER_FACTOR_A = 50
ADDR_POWER_FACTOR_B = 52
ADDR_POWER_FACTOR_C = 54
ADDR_TOTAL_POWER_FACTOR = 56
ADDR_FREQUENCY = 58
ADDR_TOTAL_ACTIVE_ELECTRIC_ENERGY = 80

class Electricity:
    """docstring for Electricity"""
    def __init__(self, device, address, offset, debug):
        instrument = minimalmodbus.Instrument(device, address)
        instrument.serial.baudrate = 9600
        instrument.serial.bytesize = 8
        instrument.serial.parity = minimalmodbus.serial.PARITY_NONE
        instrument.serial.stopbits = 1
        instrument.serial.timeout=1
        instrument.debug = debug

        # read values
        self.timestamp = time.time()
        self.address = address
        self.voltage_a = instrument.read_float(offset + ADDR_VOLTAGE_A, byteorder=BYTEORDER_LITTLE_SWAP)
        self.voltage_b = instrument.read_float(offset + ADDR_VOLTAGE_B, byteorder=BYTEORDER_LITTLE_SWAP)
        self.voltage_c = instrument.read_float(offset + ADDR_VOLTAGE_C, byteorder=BYTEORDER_LITTLE_SWAP)
        self.current_a = instrument.read_float(offset + ADDR_CURRENT_A, byteorder=BYTEORDER_LITTLE_SWAP)
        self.current_b = instrument.read_float(offset + ADDR_CURRENT_B, byteorder=BYTEORDER_LITTLE_SWAP)
        self.current_c = instrument.read_float(offset + ADDR_CURRENT_C, byteorder=BYTEORDER_LITTLE_SWAP)
        self.total_active_power = instrument.read_float(offset + ADDR_TOTAL_ACTIVE_POWER, byteorder=BYTEORDER_LITTLE_SWAP)
        self.active_power_a = instrument.read_float(offset + ADDR_ACTIVE_POWER_A, byteorder=BYTEORDER_LITTLE_SWAP)
        self.active_power_b = instrument.read_float(offset + ADDR_ACTIVE_POWER_B, byteorder=BYTEORDER_LITTLE_SWAP)
        self.active_power_c = instrument.read_float(offset + ADDR_ACTIVE_POWER_C, byteorder=BYTEORDER_LITTLE_SWAP)
        self.total_reactive_power = instrument.read_float(offset + ADDR_TOTAL_REACTIVE_POWER, byteorder=BYTEORDER_LITTLE_SWAP)
        self.reactive_power_a = instrument.read_float(offset + ADDR_REACTIVE_POWER_A, byteorder=BYTEORDER_LITTLE_SWAP)
        self.reactive_power_b = instrument.read_float(offset + ADDR_REACTIVE_POWER_B, byteorder=BYTEORDER_LITTLE_SWAP)
        self.reactive_power_c = instrument.read_float(offset + ADDR_REACTIVE_POWER_C, byteorder=BYTEORDER_LITTLE_SWAP)
        self.total_apparent_power = instrument.read_float(offset + ADDR_TOTAL_APPARENT_POWER, byteorder=BYTEORDER_LITTLE_SWAP)
        self.apparent_power_a = instrument.read_float(offset + ADDR_APPARENT_POWER_A, byteorder=BYTEORDER_LITTLE_SWAP)
        self.apparent_power_b = instrument.read_float(offset + ADDR_APPARENT_POWER_B, byteorder=BYTEORDER_LITTLE_SWAP)
        self.apparent_power_c = instrument.read_float(offset + ADDR_APPARENT_POWER_C, byteorder=BYTEORDER_LITTLE_SWAP)
        self.total_power_factor = instrument.read_float(offset + ADDR_TOTAL_POWER_FACTOR, byteorder=BYTEORDER_LITTLE_SWAP)
        self.power_factor_a = instrument.read_float(offset + ADDR_POWER_FACTOR_A, byteorder=BYTEORDER_LITTLE_SWAP)
        self.power_factor_b = instrument.read_float(offset + ADDR_POWER_FACTOR_B, byteorder=BYTEORDER_LITTLE_SWAP)
        self.power_factor_c = instrument.read_float(offset + ADDR_POWER_FACTOR_C, byteorder=BYTEORDER_LITTLE_SWAP)
        self.frequency = instrument.read_float(offset + ADDR_FREQUENCY, byteorder=BYTEORDER_LITTLE_SWAP)
        self.total_active_electric_energy = instrument.read_float(offset + ADDR_TOTAL_ACTIVE_ELECTRIC_ENERGY, byteorder=BYTEORDER_LITTLE_SWAP)

    def json(self):
        data = {
            'addr': self.address,
            'voltage_A': self.voltage_a,
            'voltage_B': self.voltage_b,
            'voltage_C': self.voltage_c,
            'current_A': self.current_a,
            'current_B': self.current_b,
            'current_C': self.current_c,
            'total_active_power': self.total_active_power,
            'active_power_A': self.active_power_a,
            'active_power_B': self.active_power_b,
            'active_power_C': self.active_power_c,
            'total_reactive_power': self.total_reactive_power,
            'reactive_power_A': self.reactive_power_a,
            'reactive_power_B': self.reactive_power_b,
            'reactive_power_C': self.reactive_power_c,
            'total_apparent_power': self.total_apparent_power,
            'apparent_power_A': self.apparent_power_a,
            'apparent_power_B': self.apparent_power_b,
            'apparent_power_C': self.apparent_power_c,
            'total_power_factor': self.total_power_factor,
            'power_factor_A': self.power_factor_a,
            'power_factor_B': self.power_factor_b,
            'power_factor_C': self.power_factor_c,
            'frequency': self.frequency,
            'total_active_energy': self.total_active_electric_energy
        }
        return data

    def str(self):
        return str(self.address) + ': ' + datetime.datetime.fromtimestamp(self.timestamp).strftime('%Y-%m-%d.%H:%M:%S') + ' ' + str(self.voltage_a) + ' ' + str(self.voltage_b) + ' ' + str(self.voltage_c) + ' ' + str(self.current_a) + ' ' + str(self.current_b) + ' ' + str(self.current_c) + ' ' + str(self.total_active_power) + ' ' + str(self.active_power_a) + ' ' + str(self.active_power_b) + ' ' + str(self.active_power_c) + ' ' + str(self.total_reactive_power) + ' ' + str(self.reactive_power_a) + ' ' + str(self.reactive_power_b) + ' ' + str(self.reactive_power_c) + ' ' + str(self.total_apparent_power) + ' ' + str(self.apparent_power_a) + ' ' + str(self.apparent_power_b) + ' ' + str(self.apparent_power_c) + ' ' + str(self.total_power_factor) + ' ' + str(self.power_factor_a) + ' ' + str(self.power_factor_b) + ' ' + str(self.power_factor_c) + ' ' + str(self.frequency) + ' ' + str(self.total_active_electric_energy)


"""
Define the database schema and some helpers.

N.B. Schema defaults only applied once object commited.
"""
from __future__ import absolute_import, print_function
import datetime

import sqlalchemy as sqla
import sqlalchemy.orm as sqla_orm
import sqlalchemy.ext.declarative

import cog.exc
import cog.tbl
import cogdb

# TODO: System hierarchy mapped to single table. Fair bit of overlap here.
# Example
# SystemBase --> SystemFort -> SystemPrep
#           \--> SystemUM  --> UMControl
#                         \--> UMExpand --> UMOppose
# TODO: Maybe make Merit -> FortMerit, UMMerit

LEN_CMD = 15  # Max length of a subclass of cog.actions
LEN_DID = 30
LEN_NAME = 100
LEN_FACTION = 10
LEN_SHEET = 15
Base = sqlalchemy.ext.declarative.declarative_base()

class EFaction(object):
    """ Switch between the two fed factions. """
    hudson = 'hudson'
    winters = 'winters'

class ESheetType(object):
    """ Type of sheet. """
    cattle = 'SheetCattle'
    undermine = 'SheetUM'

class EUMType(object):
    """ Type of undermine system. """
    control = 'control'
    expand = 'expanding'
    oppose = 'opposing'

class Admin(Base):
    """
    Table that lists admins. Essentially just a boolean.
    All admins are equal, except for removing other admins, then seniority is considered by date.
    This shouldn't be a problem practically.
    """
    __tablename__ = 'admins'

    id = sqla.Column(sqla.String(LEN_DID), primary_key=True)
    date = sqla.Column(sqla.DateTime, default=datetime.datetime.utcnow)  # All dates UTC

    def remove(self, session, other):
        """
        Remove an existing admin.
        """
        if self.date > other.date:
            raise cog.exc.InvalidPerms("You are not the senior admin. Refusing.")
        session.delete(other)
        session.commit()

    def __repr__(self):
        keys = ['id', 'date']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "Admin({})".format(', '.join(kwargs))

    def __str__(self):
        return repr(self)

    def __eq__(self, other):
        return self.id == other.id

class ChannelPerm(Base):
    """
    A channel permission to restrict cmd to listed channels.
    """
    __tablename__ = 'perms_channel'

    cmd = sqla.Column(sqla.String(LEN_CMD), primary_key=True)
    server = sqla.Column(sqla.String(30), primary_key=True)
    channel = sqla.Column(sqla.String(40), primary_key=True)

    def __repr__(self):
        keys = ['cmd', 'server', 'channel']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "ChannelPerm({})".format(', '.join(kwargs))

    def __str__(self):
        return repr(self)

    def __eq__(self, other):
        return isinstance(self, ChannelPerm) and isinstance(other, ChannelPerm) and (
            str(self) == str(other))

class RolePerm(Base):
    """
    A role permission to restrict cmd to listed roles.
    """
    __tablename__ = 'perms_role'

    cmd = sqla.Column(sqla.String(LEN_CMD), primary_key=True)
    server = sqla.Column(sqla.String(30), primary_key=True)
    role = sqla.Column(sqla.String(40), primary_key=True)

    def __repr__(self):
        keys = ['cmd', 'server', 'role']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "RolePerm({})".format(', '.join(kwargs))

    def __str__(self):
        return repr(self)

    def __eq__(self, other):
        return isinstance(self, RolePerm) and isinstance(other, RolePerm) and (
            str(self) == str(other))

class FortOrder(Base):
    """
    Simply store a list of Control systems in the order they should be forted.
    """
    __tablename__ = 'fort_order'

    order = sqla.Column(sqla.Integer, unique=True)
    system_name = sqla.Column(sqla.String(LEN_NAME), primary_key=True)

    def __repr__(self):
        keys = ['order', 'system_name']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "FortOrder({})".format(', '.join(kwargs))

    def __str__(self):
        return repr(self)

    def __eq__(self, other):
        return isinstance(self, FortOrder) and isinstance(other, FortOrder) and (
            str(self) == str(other))

class DUser(Base):
    """
    Table to store discord users and their permanent preferences.
    """
    __tablename__ = 'discord_users'

    id = sqla.Column(sqla.String(LEN_DID), primary_key=True)  # Discord id
    display_name = sqla.Column(sqla.String(LEN_NAME))
    pref_name = sqla.Column(sqla.String(LEN_NAME), unique=True, nullable=False)  # pref_name == display_name until change
    pref_cry = sqla.Column(sqla.String(LEN_NAME), default='')
    faction = sqla.Column(sqla.String(LEN_FACTION), default=EFaction.hudson)

    def __repr__(self):
        keys = ['id', 'display_name', 'pref_name', 'pref_cry', 'faction']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "DUser({})".format(', '.join(kwargs))

    def __str__(self):
        return repr(self)

    def __eq__(self, other):
        return isinstance(other, DUser) and self.id == other.id

    def switch_faction(self, new_faction=None):
        """ Switch current user faction """
        if not new_faction:
            new_faction = EFaction.hudson if self.faction == EFaction.winters else EFaction.winters
        self.faction = new_faction

    @property
    def mention(self):
        """ Mention this user in a response. """
        return "<@" + self.id + ">"

    def sheets(self, session):
        """ Return all sheets found. """
        return session.query(SheetRow).filter_by(name=self.pref_name).all()

    def get_sheet(self, session, sheet_type, *, faction=None):
        """
        Get a sheet belonging to a certain type. See ESheetType.

        Returns a SheetRow subclass. None if not set.
        """
        if not faction:
            faction = self.faction

        for sheet in self.sheets(session):
            if sheet.type == sheet_type and sheet.faction == faction:
                return sheet

        return None

    def cattle(self, session):
        """ Get users current cattle sheet. """
        return self.get_sheet(session, ESheetType.cattle)

    def undermine(self, session):
        """ Get users current undermining sheet. """
        return self.get_sheet(session, ESheetType.undermine)

class SheetRow(Base):
    """
    Track all infomration about the user in a row of the cattle sheet.
    """
    __tablename__ = 'sheet_users'

    id = sqla.Column(sqla.Integer, primary_key=True)
    type = sqla.Column(sqla.String(LEN_SHEET))  # See ESheetType
    faction = sqla.Column(sqla.String(LEN_FACTION), default=EFaction.hudson)
    name = sqla.Column(sqla.String(LEN_NAME))
    cry = sqla.Column(sqla.String(LEN_NAME), default='')
    row = sqla.Column(sqla.Integer)

    __table_args__ = (
        sqla.UniqueConstraint('name', 'type', 'faction'),
    )
    __mapper_args__ = {
        'polymorphic_identity': 'base_row',
        'polymorphic_on': type
    }

    def __repr__(self):
        keys = ['name', 'type', 'faction', 'row', 'cry']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "{}({})".format(self.__class__.__name__, ', '.join(kwargs))

    def __str__(self):
        return "id={!r}, {!r}".format(self.id, self)

    def __eq__(self, other):
        return isinstance(other, SheetRow) and (self.name, self.type, self.faction) == (
            other.name, other.type, other.faction)

    def duser(self, session):
        return session.query(DUser).filter_by(pref_name=self.name).one()

class SheetCattle(SheetRow):
    """ Track user in Hudson cattle sheet. """
    __mapper_args__ = {
        'polymorphic_identity': ESheetType.cattle,
    }

    def merit_summary(self):
        """ Summarize user merits. """
        total = 0
        for drop in self.merits:
            total += drop.amount

        return 'Dropped {}'.format(total)

class SheetUM(SheetRow):
    """ Track user in Hudson undermining sheet. """
    __mapper_args__ = {
        'polymorphic_identity': ESheetType.undermine,
    }

    def merit_summary(self):
        """ Summarize user merits. """
        held = 0
        redeemed = 0
        for hold in self.merits:
            held += hold.held
            redeemed += hold.redeemed

        return 'Holding {}, Redeemed {}'.format(held, redeemed)

class Drop(Base):
    """
    Every drop made by a user creates a fort entry here.
    User maintains a sub collection of these for easy access.
    """
    __tablename__ = 'merits'

    id = sqla.Column(sqla.Integer, primary_key=True)
    amount = sqla.Column(sqla.Integer)
    system_id = sqla.Column(sqla.Integer, sqla.ForeignKey('systems.id'), nullable=False)
    user_id = sqla.Column(sqla.Integer, sqla.ForeignKey('sheet_users.id'), nullable=False)

    def __repr__(self):
        keys = ['system_id', 'user_id', 'amount']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "Drop({})".format(', '.join(kwargs))

    def __str__(self):
        system = ''
        if getattr(self, 'system'):
            system = "system={!r}, ".format(self.system.name)

        suser = ''
        if getattr(self, 'user'):
            suser = "user={!r}, ".format(self.user.name)

        return "id={!r}, {}{}{!r}".format(self.id, system, suser, self)

    def __eq__(self, other):
        return isinstance(other, Drop) and (self.user_id, self.system_id) == (
            other.user_id, other.system_id)

    def __lt__(self, other):
        return self.amount < other.amount

class Hold(Base):
    """
    Represents a user's held and redeemed merits within an undermining system.
    """
    __tablename__ = 'um_merits'

    id = sqla.Column(sqla.Integer, primary_key=True)
    system_id = sqla.Column(sqla.Integer, sqla.ForeignKey('um_systems.id'), nullable=False)
    user_id = sqla.Column(sqla.Integer, sqla.ForeignKey('sheet_users.id'), nullable=False)
    held = sqla.Column(sqla.Integer)
    redeemed = sqla.Column(sqla.Integer)

    def __repr__(self):
        keys = ['system_id', 'user_id', 'held', 'redeemed']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "Hold({})".format(', '.join(kwargs))

    def __str__(self):
        system = ''
        if getattr(self, 'system', None):
            system = "system={!r}, ".format(self.system.name)

        suser = ''
        if getattr(self, 'user', None):
            suser = "user={!r}, ".format(self.user.name)

        return "id={!r}, {}{}{!r}".format(self.id, system, suser, self)

    def __eq__(self, other):
        return isinstance(other, Hold) and (self.user_id, self.system_id) == (
            other.user_id, other.system_id)

    def __lt__(self, other):
        return self.held + self.redeemed < other.held + other.redeemed

class KOS(Base):
    """
    Represents a the kos list.
    """
    __tablename__ = 'kos'

    id = sqla.Column(sqla.Integer, primary_key=True)
    cmdr = sqla.Column(sqla.String(100), unique=True, nullable=False)
    faction = sqla.Column(sqla.String(100), nullable=False)
    danger = sqla.Column(sqla.Integer)
    is_friendly = sqla.Column(sqla.Boolean)

    def __repr__(self):
        keys = ['cmdr', 'faction', 'danger', 'is_friendly']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "KOS({})".format(', '.join(kwargs))

    def __str__(self):
        return "id={!r}, {!r}".format(self.id, self)

    def __eq__(self, other):
        return isinstance(other, KOS) and (self.cmdr) == (other.cmdr)

    def __hash__(self):
        return hash((self.id, self.cmdr))

    @property
    def friendly(self):
        return 'FRIENDLY' if self.is_friendly else 'KILL'

class System(Base):
    """
    Represent a single system for fortification.
    Object can be flushed and queried from the database.

    data: List to be unpacked: ump, trigger, status, notes):
    Data tuple is to be used to make a table, with header

    args:
        id: Set by the database, unique id.
        name: Name of the system. (string)
        fort_status: Current reported status from galmap/users. (int)
        trigger: Total trigger of merits required. (int)
        undermine: Percentage of undermining of the system. (float)
        notes: Any notes attached to the system. (string)
        sheet_col: The name of the column in the excel. (string)
        sheet_order: Order systems should be ordered. (int)
    """
    __tablename__ = 'systems'

    header = ['Type', 'System', 'Missing', 'Merits (Fort%/UM%)', 'Notes']

    id = sqla.Column(sqla.Integer, primary_key=True)
    name = sqla.Column(sqla.String(LEN_NAME), unique=True)
    fort_status = sqla.Column(sqla.Integer)
    trigger = sqla.Column(sqla.Integer)
    um_status = sqla.Column(sqla.Integer, default=0)
    undermine = sqla.Column(sqla.Float, default=0.0)
    fort_override = sqla.Column(sqla.Float, default=0.0)
    distance = sqla.Column(sqla.Float)
    notes = sqla.Column(sqla.String(LEN_NAME), default='')
    sheet_col = sqla.Column(sqla.String(5))
    sheet_order = sqla.Column(sqla.Integer)
    type = sqla.Column(sqla.String(5))

    __mapper_args__ = {
        'polymorphic_identity': 'fort',
        'polymorphic_on': type
    }

    def __repr__(self):
        keys = ['name', 'fort_status', 'trigger', 'um_status',
                'undermine', 'distance', 'notes', 'sheet_col', 'sheet_order']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "System({})".format(', '.join(kwargs))

    def __str__(self):
        return "id={!r}, cmdr_merits={!r}, {!r}".format(self.id, self.cmdr_merits, self)

    def __eq__(self, other):
        return isinstance(other, System) and self.name == other.name

    def __lt__(self, other):
        """ Order systems by remaining supplies needed. """
        if isinstance(other, self.__class__):
            return self.missing < other.missing

    @property
    def cmdr_merits(self):
        """ Total merits dropped by cmdrs """
        total = 0
        for drop in self.merits:
            total += drop.amount
        return total

    @property
    def ump(self):
        """ Return the undermine percentage, stored as decimal. """
        return '{:.1f}'.format(self.undermine * 100)

    @property
    def current_status(self):
        """ Simply return max fort status reported. """
        # FIXME: Hack until import sheet included.
        supplies = max(self.fort_status, self.cmdr_merits)
        if self.fort_override > supplies / self.trigger:
            return int(self.fort_override * self.trigger)

        return supplies

    @property
    def skip(self):
        """ The system should be skipped. """
        notes = self.notes.lower()
        return 'leave' in notes or 'skip' in notes

    @property
    def is_fortified(self):
        """ The remaining supplies to fortify """
        # FIXME: Hack until import sheet included.
        if self.fort_override >= 1.0:
            return True

        return self.current_status >= self.trigger

    @property
    def is_undermined(self):
        """ The system has been undermined """
        return self.undermine >= 1.00

    @property
    def missing(self):
        """ The remaining supplies to fortify """
        return max(0, self.trigger - self.current_status)

    @property
    def completion(self):
        """ The fort completion percentage """
        try:
            comp_cent = self.current_status / self.trigger * 100
        except ZeroDivisionError:
            comp_cent = 0

        return '{:.1f}'.format(comp_cent)

    @property
    def table_row(self):
        """
        Return a tuple of important data to be formatted for table output.
        Each element should be mapped to separate column.
        See header.
        """
        status = '{:>4}/{} ({}%/{}%)'.format(self.current_status, self.trigger,
                                             self.completion, self.ump)

        return (self.type.capitalize(), self.name,
                '{:>4}'.format(self.missing), status, self.notes)

    def set_status(self, new_status):
        """
        Update the fort_status and um_status of this System based on new_status.
        Format of new_status: fort_status[:um_status]

        Raises: ValueError
        """
        for val, attr in zip(new_status.split(':'), ['fort_status', 'um_status']):
            new_val = int(val)
            if new_val < 0:
                raise cog.exc.InvalidCommandArgs('New fort/um status must be in range: [0, \u221E]')

            setattr(self, attr, int(val))

    def display(self, *, miss=None):
        """
        Return a useful short representation of System.

        Kwargs:
            missing: A trinary:
                - None, show missing only if < 1500 left
                - True, display missing
                - False, do not display missing
        """
        umd = ''
        if self.um_status > 0:
            umd = ', {} :Undermin{}:'.format(
                self.um_status, 'ed' if self.is_undermined else 'ing')
        elif self.is_undermined:
            umd = ', :Undermined:'

        msg = '**{}** {:>4}/{} :Fortif{}:{}'.format(
            self.name, self.current_status, self.trigger,
            'ied' if self.is_fortified else 'ying', umd)

        if miss or miss is not False and (self.missing and self.missing < 1500):
            msg += ' ({} left)'.format(self.missing)

        if self.notes:
            msg += ' ' + self.notes

        return msg

    def display_details(self):
        """ Return a highly detailed system display. """
        miss = ' ({} left)'.format(self.missing) if self.missing else ''
        lines = [
            ['Completion', '{}%{}'.format(self.completion, miss)],
            ['CMDR Merits', '{}/{}'.format(self.cmdr_merits, self.trigger)],
            ['Fort Status', '{}/{}'.format(self.fort_status, self.trigger)],
            ['UM Status', '{} ({:.2f}%)'.format(self.um_status, self.undermine * 100)],
            ['Notes', self.notes],
        ]

        return '**{}**\n'.format(self.name) + cog.tbl.wrap_markdown(cog.tbl.format_table(lines))

class PrepSystem(System):
    """
    A prep system that must be fortified for expansion.
    """
    __mapper_args__ = {
        'polymorphic_identity': 'prep',
    }

    @property
    def is_fortified(self):
        """ Prep systems never get finished. """
        return False

    def display(self, *, miss=None):
        """
        Return a useful short representation of PrepSystem.
        """
        return 'Prep: ' + super().display(miss=miss)

class SystemUM(Base):
    """
    A control system we intend on undermining.
    """
    __tablename__ = 'um_systems'

    id = sqla.Column(sqla.Integer, primary_key=True)
    name = sqla.Column(sqla.String(LEN_NAME), unique=True)
    type = sqla.Column(sqla.String(15))  # EUMType
    sheet_col = sqla.Column(sqla.String(5))
    goal = sqla.Column(sqla.Integer)
    security = sqla.Column(sqla.String(LEN_NAME))
    notes = sqla.Column(sqla.String(LEN_NAME))
    close_control = sqla.Column(sqla.String(LEN_NAME))
    progress_us = sqla.Column(sqla.Integer)
    progress_them = sqla.Column(sqla.Float)
    map_offset = sqla.Column(sqla.Integer, default=0)
    exp_trigger = sqla.Column(sqla.Integer, default=0)

    __mapper_args__ = {
        'polymorphic_identity': 'base_system',
        'polymorphic_on': type
    }

    @staticmethod
    def factory(kwargs):
        """ Simple factory to make undermining systems. """
        cls = kwargs.pop('cls')
        return cls(**kwargs)

    def __repr__(self):
        keys = ['name', 'type', 'sheet_col', 'goal', 'security', 'notes',
                'progress_us', 'progress_them', 'close_control', 'map_offset']
        kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys]

        return "SystemUM({})".format(', '.join(kwargs))

    def __str__(self):
        """
        Show additional computed properties.
        """
        return "id={!r}, cmdr_merits={!r}, {!r}".format(self.id, self.cmdr_merits, self)

    def __eq__(self, other):
        return isinstance(other, SystemUM) and self.name == other.name

    @property
    def completion(self):
        """ The completion percentage formatted as a string """
        try:
            comp_cent = (self.goal - self.missing) / self.goal * 100
        except ZeroDivisionError:
            comp_cent = 0

        completion = '{:.0f}%'.format(comp_cent)

        return completion

    @property
    def cmdr_merits(self):
        """ Total merits held and redeemed by cmdrs """
        total = 0
        for hold in self.merits:
            total += hold.held + hold.redeemed
        return total

    @property
    def missing(self):
        """ The remaining supplies to fortify """
        return self.goal - max(self.cmdr_merits + self.map_offset, self.progress_us)

    @property
    def descriptor(self):
        """ Descriptive prefix for string. """
        return self.type.capitalize()

    @property
    def is_undermined(self):
        """
        Return true only if the system is undermined.
        """
        return self.missing <= 0

    def display(self):
        """
        Format a simple summary for users.
        """
        lines = [
            [self.descriptor, '[{}] {}'.format(self.security[0].upper(), self.name)],
            [self.completion, 'Merits {} {}'.format('Missing' if self.missing > 0 else 'Leading',
                                                    str(abs(self.missing)))],
            ['Our Progress ' + str(self.progress_us),
             'Enemy Progress {:.0f}%'.format(self.progress_them * 100)],
            ['Nearest Hudson', self.close_control],
        ]

        return cog.tbl.wrap_markdown(cog.tbl.format_table(lines))

    def set_status(self, new_status):
        """
        Update the fort_status and um_status of this System based on new_status.
        Format of new_status: fort_status[:um_status]

        Raises: ValueError
        """
        vals = new_status.split(':')
        if len(vals) == 2:
            new_them = float(vals[1]) / 100
            if new_them < 0:
                raise cog.exc.InvalidCommandArgs('New "progress them" must be a % in range: [0, \u221E]')
            self.progress_them = new_them

        new_us = int(vals[0])
        if new_us < 0:
            raise cog.exc.InvalidCommandArgs('New "progress us" must be a number merits in range: [0, \u221E]')
        self.progress_us = new_us

class UMControl(SystemUM):
    """ Undermine an enemy control system. """
    __mapper_args__ = {
        'polymorphic_identity': EUMType.control,
    }

class UMExpand(SystemUM):
    """ An expansion we want. """
    __mapper_args__ = {
        'polymorphic_identity': EUMType.expand,
    }

    @property
    def is_undermined(self):
        """
        Expansions are never finished until tick.
        """
        return False

    @property
    def completion(self):
        """ The completion percentage formatted as a string """
        try:
            comp_cent = max(self.progress_us,
                            self.cmdr_merits + self.map_offset) * 100 / self.exp_trigger
        except ZeroDivisionError:
            comp_cent = 0

        comp_cent -= self.progress_them * 100
        prefix = 'Leading by' if comp_cent >= 0 else 'Behind by'
        completion = '{} {:.0f}%'.format(prefix, abs(comp_cent))

        return completion

class UMOppose(UMExpand):
    """ We want to oppose the expansion. """
    __mapper_args__ = {
        'polymorphic_identity': EUMType.oppose,
    }

    @property
    def descriptor(self):
        """ Descriptive prefix for string. """
        suffix = 'expansion'
        if self.notes != '':
            suffix = self.notes.split()[0]
        return 'Opposing ' + suffix

def kwargs_um_system(cells, sheet_col):
    """
    Return keyword args parsed from cell frame.

    Format !D1:E11:
        1: Title | Title
        2: Exp Trigger/Opp. Tigger | % safety margin  -> If cells blank, not expansion system.
        3: Leading by xx% OR behind by xx% (
        4: Estimated Goal (integer)
        5: CMDR Merits (Total merits)
        6: Missing Merits
        7: Security Level | Notes
        8: Closest Control (string)
        9: System Name (string)
        10: Our Progress (integer) | Type String (Ignore)
        11: Enemy Progress (percentage) | Type String (Ignore)
        12: Skip
        13: Map Offset (Map Value - Cmdr Merits)
    """
    try:
        main_col, sec_col = cells[0], cells[1]

        if main_col[8] == '' or 'template' in main_col[8].lower():
            raise cog.exc.SheetParsingError

        if main_col[0].startswith('Exp'):
            cls = UMExpand
        elif main_col[0] != '':
            cls = UMOppose
        else:
            cls = UMControl

        # Cell is not guaranteed to exist in list
        try:
            map_offset = parse_int(main_col[12])
        except IndexError:
            map_offset = 0

        return {
            'exp_trigger': parse_int(main_col[1]),
            'goal': parse_int(main_col[3]),  # FIXME: May come down as float. This would truncate.
            'security': main_col[6].strip().replace('Sec: ', ''),
            'notes': sec_col[6].strip(),
            'close_control': main_col[7].strip(),
            'name': main_col[8].strip(),
            'progress_us': parse_int(main_col[9]),
            'progress_them': parse_float(main_col[10]),
            'map_offset': map_offset,
            'sheet_col': sheet_col,
            'cls': cls,
        }
    except (IndexError, TypeError):
        raise cog.exc.SheetParsingError

def kwargs_fort_system(lines, order, column):
    """
    Simple adapter that parses the data and puts it into kwargs to
    be used when initializing the System object.

    lines: A list of the following
        0   - undermine % (comes as float 0.0 - 1.0)
        1   - completion % (comes as float 0.0 - 1.0)
        2   - fortification trigger
        3   - missing merits
        4   - merits dropped by commanders
        5   - status updated manually (defaults to '', map to 0)
        6   - undermine updated manually (defaults to '', map to 0)
        7   - distance from hq (float, always set)
        8   - notes (defaults '')
        9   - system name
    order: The order of this data set relative others.
    column: The column string this data belongs in.
    """
    try:
        if lines[9] == '':
            raise cog.exc.SheetParsingError

        return {
            'undermine': parse_float(lines[0]),
            'fort_override': parse_float(lines[1]),
            'trigger': parse_int(lines[2]),
            'fort_status': parse_int(lines[5]),
            'um_status': parse_int(lines[6]),
            'distance': parse_float(lines[7]),
            'notes': lines[8].strip(),
            'name': lines[9].strip(),
            'sheet_col': column,
            'sheet_order': order,
        }
    except (IndexError, TypeError):
        raise cog.exc.SheetParsingError

def parse_int(word):
    """ Parse into int, on failure return 0 """
    try:
        return int(word)
    except ValueError:
        return 0

def parse_float(word):
    """ Parse into float, on failure return 0.0 """
    try:
        return float(word)
    except ValueError:
        return 0.0

def empty_tables(session, *, perm=False):
    """
    Drop all tables.
    """
    classes = [Drop, Hold, System, SystemUM, SheetRow]
    if perm:
        classes += [DUser]

    for cls in classes:
        for matched in session.query(cls):
            session.delete(matched)
    session.commit()

def recreate_tables():
    """
    Recreate all tables in the database, mainly for schema changes and testing.
    """
    Base.metadata.drop_all(cogdb.engine)
    Base.metadata.create_all(cogdb.engine)

# Relationships
# TODO: Is there a better way?
#   Now using two one way selects, feels icky.
#   I cannot enforce this key constraint as both sides are inserted independently
#   So instead of a key I make two one way joins and cast the key.
# DUser.sheets = sqla_orm.relationship("SheetRow",
                                    # primaryjoin="DUser.pref_name == foreign(SheetRow.name)",
                                    # cascade_backrefs=False)
# SheetRow.duser = sqla_orm.relationship("DUser",
                                    # primaryjoin="SheetRow.name == foreign(DUser.pref_name)",
                                    # cascade_backrefs=False)

# Fortification relations
Drop.user = sqla_orm.relationship('SheetCattle', uselist=False, back_populates='merits',
                                  lazy='select')
SheetCattle.merits = sqla_orm.relationship('Drop',
                                           cascade='all, delete, delete-orphan',
                                           back_populates='user',
                                           lazy='select')
Drop.system = sqla_orm.relationship('System', uselist=False, back_populates='merits',
                                    lazy='select')
System.merits = sqla_orm.relationship('Drop',
                                      cascade='all, delete, delete-orphan',
                                      back_populates='system',
                                      lazy='select')

# Undermining relations
Hold.user = sqla_orm.relationship('SheetUM', uselist=False, back_populates='merits',
                                  lazy='select')
SheetUM.merits = sqla_orm.relationship('Hold',
                                       cascade='all, delete, delete-orphan',
                                       back_populates='user',
                                       lazy='select')
Hold.system = sqla_orm.relationship('SystemUM', uselist=False, back_populates='merits',
                                    lazy='select')
SystemUM.merits = sqla_orm.relationship('Hold',
                                        cascade='all, delete, delete-orphan',
                                        back_populates='system',
                                        lazy='select')

if cogdb.TEST_DB:
    recreate_tables()
else:
    Base.metadata.create_all(cogdb.engine)

def main():  # pragma: no cover
    """
    This continues to exist only as a sanity test for schema and relations.
    """
    Base.metadata.drop_all(cogdb.engine)
    Base.metadata.create_all(cogdb.engine)
    session = cogdb.Session()

    dusers = (
        DUser(id='197221', pref_name='GearsandCogs'),
        DUser(id='299221', pref_name='rjwhite'),
        DUser(id='293211', pref_name='vampyregtx'),
    )
    session.add_all(dusers)
    session.commit()

    sheets = (
        SheetCattle(name='GearsandCogs', row=15),
        SheetCattle(name='rjwhite', row=16),
        SheetCattle(name='vampyregtx', row=17),
        SheetUM(name='vampyregtx', row=22),
        SheetCattle(name='vampyregtx', faction=EFaction.winters, row=22),
        SheetUM(name='vampyregtx', faction=EFaction.winters, row=22),
    )

    session.add_all(sheets)
    session.commit()

    systems = (
        System(name='Frey', sheet_col='F', sheet_order=1, fort_status=0,
               trigger=7400, undermine=0),
        System(name='Adeo', sheet_col='G', sheet_order=2, fort_status=0,
               trigger=5400, undermine=0),
        System(name='Sol', sheet_col='H', sheet_order=3, fort_status=0,
               trigger=6000, undermine=0),
    )
    session.add_all(systems)
    session.commit()

    drops = (
        Drop(user_id=sheets[0].id, system_id=systems[0].id, amount=700),
        Drop(user_id=sheets[1].id, system_id=systems[0].id, amount=700),
        Drop(user_id=sheets[0].id, system_id=systems[2].id, amount=1400),
        Drop(user_id=sheets[2].id, system_id=systems[1].id, amount=2100),
        Drop(user_id=sheets[2].id, system_id=systems[0].id, amount=300),
    )
    session.add_all(drops)
    session.commit()

    def mprint(*args):
        """ Padded print. """
        args = [str(x) for x in args]
        print(*args)

    pad = ' ' * 3

    print('DiscordUsers----------')
    for user in session.query(DUser):
        mprint(user)
        mprint(pad, user.sheets)
        mprint(user.cattle)

    print('SheetUsers----------')
    for user in session.query(SheetRow):
        mprint(user)
        mprint(pad, user.duser)

    print('Systems----------')
    for sys in session.query(System):
        mprint(sys)
        mprint(pad, sys.merits)
        mprint(sorted(sys.merits))

    print('Drops----------')
    for drop in session.query(Drop):
        mprint(drop)
        mprint(pad, drop.user)
        mprint(pad, drop.system)

if __name__ == "__main__":  # pragma: no cover
    main()


# ABC084D
import bisect

def Get_Sieve_of_Eratosthenes(N):
    prime_list = [2]
    limit = int(N ** 0.5)
    numeric_data = [i for i in range(3, N, 2)]
    while True:
        prime = numeric_data[0]
        if prime >= limit:
            return prime_list + numeric_data
        prime_list.append(prime)
        numeric_data = [x for x in numeric_data if x % prime != 0]

def Get_2017_like_number(N, prime_list):
    ans_list = []
    for p in prime_list:
        if (p+1)//2 in prime_list:
            ans_list.append(p)
    return ans_list

def main():
    N = 10 ** 5
    prime_list = Get_Sieve_of_Eratosthenes(N)
    like_number_list = Get_2017_like_number(N, prime_list)

    # 入力
    Q = int(input())
    Query = [list(map(int, input().split())) for _ in range(Q)]

    # 二分探索で見つける
    for q in Query:
        l = q[0]
        r = q[1]
        print(bisect.bisect_right(like_number_list, r) - bisect.bisect_left(like_number_list, l))

if __name__ == "__main__":
    main()

# 解説の解き方
# 素数判定リストの作成(True / False)
# 2017_like_number の個数リストの作成
# 累積和で獲得

N = 10**5
prime_list = [True] * (N+1)        # True が素数
count_list = [0] * (N+1)
for i in range(2, N+1, 1):
    if prime_list[i]:
        for j in range(i+i, N+1, i):
            prime_list[j] = False

for i in range(3, N+1, 2):
    if prime_list[i] and prime_list[(i+1)//2]:
        count_list[i] += 1

for i in range(3, N+1, 1):
    count_list[i] += count_list[i-1]

Q = int(input())
query = [list(map(int, input().split())) for _ in range(Q)]
for q in query:
    l, r = q
    print(count_list[r] - count_list[l-1])


# Implement a class to hold room information. This should have name and
# description attributes.
class Room:
  def __init__(self, name, description, nextDesc = '', roomInv = [], itemRequired = [], itemUsed = False, npc = []):
    self.name = name
    self.description = description
    self.roomInv = roomInv
    self.nextDesc = nextDesc
    self.itemRequired = itemRequired
    self.itemUsed = itemUsed
    self.npc = npc


class Solution:
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        # Hash dict
        d = {}
        for idx, val in enumerate(nums):
            # If the rest of sum is already in nums, return their position
            if target - val in d:
                return [d[target-val], idx]
            # Else, add current value in dictionary
            else:
                d[val] = idx
        return []


# -*- coding: utf-8 -*-

def bynumpy():
    '使用numpy使三阶多项式拟合正弦函数'
    import numpy as np
    import math

    # Create random input and output data
    x = np.linspace(-math.pi, math.pi, 2000)
    y = np.sin(x)

    # Randomly initialize weights
    a = np.random.randn()
    b = np.random.randn()
    c = np.random.randn()
    d = np.random.randn()

    learning_rate = 1e-6
    for t in range(2000):
        # Forward pass: compute predicted y
        # y = a + b x + c x^2 + d x^3
        y_pred = a + b * x + c * x ** 2 + d * x ** 3

        # Compute and print loss
        loss = np.square(y_pred - y).sum()
        if t % 100 == 99:
            print(t, loss)

        # Backprop to compute gradients of a, b, c, d with respect to loss
        grad_y_pred = 2.0 * (y_pred - y)
        grad_a = grad_y_pred.sum()
        grad_b = (grad_y_pred * x).sum()
        grad_c = (grad_y_pred * x ** 2).sum()
        grad_d = (grad_y_pred * x ** 3).sum()

        # Update weights
        a -= learning_rate * grad_a
        b -= learning_rate * grad_b
        c -= learning_rate * grad_c
        d -= learning_rate * grad_d

    print(f'Result: y = {a} + {b} x + {c} x^2 + {d} x^3')

def bytorch():
    import torch
    import math

    dtype = torch.float
    device = torch.device("cpu")
    # device = torch.device("cuda:0") # Uncomment this to run on GPU

    # Create random input and output data
    x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
    y = torch.sin(x)

    # Randomly initialize weights
    a = torch.randn((), device=device, dtype=dtype)
    b = torch.randn((), device=device, dtype=dtype)
    c = torch.randn((), device=device, dtype=dtype)
    d = torch.randn((), device=device, dtype=dtype)

    learning_rate = 1e-6
    for t in range(2000):
        # Forward pass: compute predicted y
        y_pred = a + b * x + c * x ** 2 + d * x ** 3

        # Compute and print loss
        loss = (y_pred - y).pow(2).sum().item()
        if t % 100 == 99:
            print(t, loss)

        # Backprop to compute gradients of a, b, c, d with respect to loss
        grad_y_pred = 2.0 * (y_pred - y)
        grad_a = grad_y_pred.sum()
        grad_b = (grad_y_pred * x).sum()
        grad_c = (grad_y_pred * x ** 2).sum()
        grad_d = (grad_y_pred * x ** 3).sum()

        # Update weights using gradient descent
        a -= learning_rate * grad_a
        b -= learning_rate * grad_b
        c -= learning_rate * grad_c
        d -= learning_rate * grad_d

    print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')

def bytorch_autograd():
    import torch
    import math

    dtype = torch.float
    device = torch.device("cpu")
    # device = torch.device("cuda:0")  # Uncomment this to run on GPU

    # Create Tensors to hold input and outputs.
    # By default, requires_grad=False, which indicates that we do not need to
    # compute gradients with respect to these Tensors during the backward pass.
    x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
    y = torch.sin(x)

    # Create random Tensors for weights. For a third order polynomial, we need
    # 4 weights: y = a + b x + c x^2 + d x^3
    # Setting requires_grad=True indicates that we want to compute gradients with
    # respect to these Tensors during the backward pass.
    a = torch.randn((), device=device, dtype=dtype, requires_grad=True)
    b = torch.randn((), device=device, dtype=dtype, requires_grad=True)
    c = torch.randn((), device=device, dtype=dtype, requires_grad=True)
    d = torch.randn((), device=device, dtype=dtype, requires_grad=True)

    learning_rate = 1e-6
    for t in range(2000):
        # Forward pass: compute predicted y using operations on Tensors.
        y_pred = a + b * x + c * x ** 2 + d * x ** 3

        # Compute and print loss using operations on Tensors.
        # Now loss is a Tensor of shape (1,)
        # loss.item() gets the scalar value held in the loss.
        loss = (y_pred - y).pow(2).sum()
        if t % 100 == 99:
            print(t, loss.item())

        # Use autograd to compute the backward pass. This call will compute the
        # gradient of loss with respect to all Tensors with requires_grad=True.
        # After this call a.grad, b.grad. c.grad and d.grad will be Tensors holding
        # the gradient of the loss with respect to a, b, c, d respectively.
        loss.backward()

        # Manually update weights using gradient descent. Wrap in torch.no_grad()
        # because weights have requires_grad=True, but we don't need to track this
        # in autograd.
        with torch.no_grad():
            a -= learning_rate * a.grad
            b -= learning_rate * b.grad
            c -= learning_rate * c.grad
            d -= learning_rate * d.grad

            # Manually zero the gradients after updating weights
            a.grad = None
            b.grad = None
            c.grad = None
            d.grad = None

    print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')

def bytorch_auto():
    import torch
    import math

    class LegendrePolynomial3(torch.autograd.Function):
        """
        We can implement our own custom autograd Functions by subclassing
        torch.autograd.Function and implementing the forward and backward passes
        which operate on Tensors.
        """

        @staticmethod
        def forward(ctx, input):
            """
            In the forward pass we receive a Tensor containing the input and return
            a Tensor containing the output. ctx is a context object that can be used
            to stash information for backward computation. You can cache arbitrary
            objects for use in the backward pass using the ctx.save_for_backward method.
            """
            ctx.save_for_backward(input)
            return 0.5 * (5 * input ** 3 - 3 * input)

        @staticmethod
        def backward(ctx, grad_output):
            """
            In the backward pass we receive a Tensor containing the gradient of the loss
            with respect to the output, and we need to compute the gradient of the loss
            with respect to the input.
            """
            input, = ctx.saved_tensors
            return grad_output * 1.5 * (5 * input ** 2 - 1)

    dtype = torch.float
    device = torch.device("cpu")
    # device = torch.device("cuda:0")  # Uncomment this to run on GPU

    # Create Tensors to hold input and outputs.
    # By default, requires_grad=False, which indicates that we do not need to
    # compute gradients with respect to these Tensors during the backward pass.
    x = torch.linspace(-math.pi, math.pi, 2000, device=device, dtype=dtype)
    y = torch.sin(x)

    # Create random Tensors for weights. For this example, we need
    # 4 weights: y = a + b * P3(c + d * x), these weights need to be initialized
    # not too far from the correct result to ensure convergence.
    # Setting requires_grad=True indicates that we want to compute gradients with
    # respect to these Tensors during the backward pass.
    a = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
    b = torch.full((), -1.0, device=device, dtype=dtype, requires_grad=True)
    c = torch.full((), 0.0, device=device, dtype=dtype, requires_grad=True)
    d = torch.full((), 0.3, device=device, dtype=dtype, requires_grad=True)

    learning_rate = 5e-6
    for t in range(2000):
        # To apply our Function, we use Function.apply method. We alias this as 'P3'.
        P3 = LegendrePolynomial3.apply

        # Forward pass: compute predicted y using operations; we compute
        # P3 using our custom autograd operation.
        y_pred = a + b * P3(c + d * x)

        # Compute and print loss
        loss = (y_pred - y).pow(2).sum()
        if t % 100 == 99:
            print(t, loss.item())

        # Use autograd to compute the backward pass.
        loss.backward()

        # Update weights using gradient descent
        with torch.no_grad():
            a -= learning_rate * a.grad
            b -= learning_rate * b.grad
            c -= learning_rate * c.grad
            d -= learning_rate * d.grad

            # Manually zero the gradients after updating weights
            a.grad = None
            b.grad = None
            c.grad = None
            d.grad = None

    print(f'Result: y = {a.item()} + {b.item()} * P3({c.item()} + {d.item()} x)')

if __name__ == "__main__":
    # import timeit
    # t1 = timeit.timeit("bynumpy()",setup="from __main__ import bynumpy",number=10)
    # t2 = timeit.timeit("bytorch()",setup="from __main__ import bytorch",number=10)
    # print(t1,t2)
    bytorch_autograd()
    # https://pytorch.org/tutorials/beginner/pytorch_with_examples.html


# Alpha-2 ISO country to continent
countriesAF = ['DZ', 'AO', 'SH', 'BJ', 'BW', 'BF', 'BI', 'CM', 'CV', 'CF', 'TD', 'KM', 'CG', 'CD', 'DJ', 'EG',
              'GQ', 'ER', 'ET', 'GA', 'GM', 'GH', 'GN', 'GW', 'CI', 'KE', 'LS', 'LR', 'LY', 'MG', 'MW', 'ML',
              'MR', 'MU', 'YT', 'MA', 'MZ', 'NA', 'NE', 'NG', 'ST', 'RE', 'RW', 'SN', 'SC', 'SL', 'SO', 'ZA',
              'SS', 'SD', 'SZ', 'TZ', 'TG', 'TN', 'UG', 'ZM', 'ZW', 'AN', 'EH']

countriesAT = ['AQ', 'GS', 'HM', 'TF']

countriesAS = ['AF', 'AM', 'AZ', 'BH', 'BD', 'BT', 'BN', 'KH', 'CN', 'CX', 'CC', 'IO', 'GE', 'HK', 'IN', 'ID',
              'IR', 'IQ', 'IL', 'JP', 'JO', 'KZ', 'KW', 'KG', 'LA', 'LB', 'MO', 'MY', 'MV', 'MN', 'MM', 'NP',
              'KP', 'OM', 'PK', 'PS', 'PH', 'QA', 'SA', 'SG', 'KR', 'LK', 'SY', 'TW', 'TJ', 'TH', 'TR', 'TM',
              'AE', 'UZ', 'VN', 'YE', 'TP']

countriesOC = ['AS', 'AU', 'NZ', 'CK', 'TL', 'FM', 'FJ', 'PF', 'GU', 'KI', 'MP', 'MH', 'UM', 'NR', 'NC', 'NU',
              'NF', 'PW', 'PG', 'WS', 'SB', 'TK', 'TO', 'TV', 'VU', 'WF', 'PN']

countriesEU = ['AL', 'AD', 'AT', 'BY', 'BE', 'BA', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FO', 'FI', 'FR', 'DE',
              'GI', 'GR', 'HU', 'IS', 'IE', 'IM', 'IT', 'XK', 'LV', 'LI', 'LT', 'LU', 'MK', 'MT', 'MD', 'MC',
              'ME', 'NL', 'NO', 'PL', 'PT', 'RO', 'RU', 'SM', 'RS', 'SK', 'SI', 'ES', 'SE', 'CH', 'UA', 'GB',
              'VA', 'FX', 'SJ', 'AX', 'YU', 'JE']

countriesNA = ['AI', 'AG', 'AW', 'BS', 'BB', 'BZ', 'BM', 'BQ', 'VG', 'CA', 'KY', 'CR', 'CU', 'CW', 'DM', 'DO',
              'SV', 'GL', 'GD', 'GP', 'GT', 'HT', 'HN', 'JM', 'MQ', 'MX', 'PM', 'MS', 'KN', 'NI', 'PA', 'PR',
              'SX', 'LC', 'VC', 'TT', 'TC', 'US', 'VI', 'BL']

countriesSA = ['AR', 'BO', 'BR', 'CL', 'CO', 'EC', 'FK', 'GF', 'GY', 'PY', 'PE', 'SR', 'UY', 'VE']

country2continent = dict().fromkeys(countriesAF, 'AF')
toAdd = dict().fromkeys(countriesAT, 'AT')
country2continent.update(toAdd)
toAdd = dict().fromkeys(countriesAS, 'AS')
country2continent.update(toAdd)
toAdd = dict().fromkeys(countriesOC, 'OC')
country2continent.update(toAdd)
toAdd = dict().fromkeys(countriesEU, 'EU')
country2continent.update(toAdd)
toAdd = dict().fromkeys(countriesNA, 'NA')
country2continent.update(toAdd)
toAdd = dict().fromkeys(countriesSA, 'SA')
country2continent.update(toAdd)
print(country2continent)
# print(len(country2continent))


"""
Cylindrical grids with azimuthal symmetry

.. codeauthor:: David Zwicker <david.zwicker@ds.mpg.de>

"""

import warnings
from typing import TYPE_CHECKING, Any, Dict, Generator, Sequence, Tuple, Union

import numpy as np

from ..tools.cache import cached_property
from ..tools.docstrings import fill_in_docstring
from .base import DimensionError, GridBase, _check_shape, discretize_interval
from .cartesian import CartesianGrid

if TYPE_CHECKING:
    from .boundaries.axes import Boundaries, BoundariesData  # @UnusedImport
    from .spherical import PolarSymGrid  # @UnusedImport

class CylindricalSymGrid(GridBase):  # lgtm [py/missing-equals]
    r""" 3-dimensional cylindrical grid assuming polar symmetry

    The polar symmetry implies that states only depend on the radial and axial
    coordinates :math:`r` and :math:`z`, respectively. These are discretized
    uniformly as

    .. math::
        :nowrap:

        \begin{align*}
            r_i &= \left(i + \frac12\right) \Delta r
            &&\quad \text{for} \quad i = 0, \ldots, N_r - 1
            &&\quad \text{with} \quad \Delta r = \frac{R}{N_r}
        \\
            z_j &= z_\mathrm{min} + \left(j + \frac12\right) \Delta z
            &&\quad \text{for} \quad j = 0, \ldots, N_z - 1
            &&\quad \text{with}
                \quad \Delta z = \frac{z_\mathrm{max} - z_\mathrm{min}}{N_z}
        \end{align*}

    where :math:`R` is the radius of the cylindrical grid,
    :math:`z_\mathrm{min}` and :math:`z_\mathrm{max}` denote the respective
    lower and upper bounds of the axial direction, so that
    :math:`z_\mathrm{max} - z_\mathrm{min}` is the total height.
    The two axes are discretized by :math:`N_r` and :math:`N_z` support points,
    respectively.

    Warning:
        The order of components in the vector and tensor fields defined on this grid
        is different than in ordinary math. While it is common to use
        :math:`(r, \phi, z)`, we here use the order :math:`(r, z, \phi)`. It might thus
        be best to access components by name instead of index, e.g., use
        :code:`field['z']` instead of :code:`field[1]`.
    """

    dim = 3  # dimension of the described space
    num_axes = 2  # number of independent axes
    axes = ["r", "z"]  # name of the actual axes
    axes_symmetric = ["phi"]
    coordinate_constraints = [0, 1]  # constraint Cartesian coordinates

    def __init__(
        self,
        radius: float,
        bounds_z: Tuple[float, float],
        shape: Tuple[int, int],
        periodic_z: bool = False,
    ):
        """
        Args:
            radius (float): The radius of the cylinder
            bounds_z (tuple): The lower and upper bound of the z-axis
            shape (tuple): The number of support points in r and z direction,
                respectively.
            periodic_z (bool): Determines whether the z-axis has periodic
                boundary conditions.
        """
        super().__init__()
        shape_list = _check_shape(shape)
        if len(shape_list) == 1:
            self._shape: Tuple[int, int] = (shape_list[0], shape_list[0])
        elif len(shape_list) == 2:
            self._shape = tuple(shape_list)  # type: ignore
        else:
            raise DimensionError("`shape` must be two integers")
        if len(bounds_z) != 2:
            raise ValueError(
                "Lower and upper value of the axial coordinate must be specified"
            )
        self._periodic_z: bool = bool(periodic_z)  # might cast from np.bool_
        self.periodic = [False, self._periodic_z]

        # radial discretization
        dr = radius / self.shape[0]
        rs = (np.arange(self.shape[0]) + 0.5) * dr
        assert np.isclose(rs[-1] + dr / 2, radius)

        # axial discretization
        zs, dz = discretize_interval(*bounds_z, self.shape[1])
        assert np.isclose(zs[-1] + dz / 2, bounds_z[1])

        self._axes_coords = (rs, zs)
        self._axes_bounds = ((0.0, radius), tuple(bounds_z))  # type: ignore
        self._discretization = np.array((dr, dz))

    @property
    def state(self) -> Dict[str, Any]:
        """state: the state of the grid"""
        radius = self.axes_bounds[0][1]
        return {
            "radius": radius,
            "bounds_z": self.axes_bounds[1],
            "shape": self.shape,
            "periodic_z": self._periodic_z,
        }

    @classmethod
    def from_state(cls, state: Dict[str, Any]) -> "CylindricalSymGrid":  # type: ignore
        """create a field from a stored `state`.

        Args:
            state (dict):
                The state from which the grid is reconstructed.
        """
        state_copy = state.copy()
        obj = cls(
            radius=state_copy.pop("radius"),
            bounds_z=state_copy.pop("bounds_z"),
            shape=state_copy.pop("shape"),
            periodic_z=state_copy.pop("periodic_z"),
        )
        if state_copy:
            raise ValueError(f"State items {state_copy.keys()} were not used")
        return obj

    @property
    def radius(self) -> float:
        """float: radius of the cylinder"""
        return self.axes_bounds[0][1]

    @property
    def length(self) -> float:
        """float: length of the cylinder"""
        return self.axes_bounds[1][1] - self.axes_bounds[1][0]

    @property
    def volume(self) -> float:
        """float: total volume of the grid"""
        return float(np.pi * self.radius ** 2 * self.length)

    def get_random_point(
        self,
        boundary_distance: float = 0,
        cartesian: bool = True,
        avoid_center: bool = False,
        rng: np.random.Generator = None,
    ) -> np.ndarray:
        """return a random point within the grid

        Note that these points will be uniformly distributed on the radial axis,
        which implies that they are not uniformly distributed in the volume.

        Args:
            boundary_distance (float): The minimal distance this point needs to
                have from all boundaries.
            cartesian (bool): Determines whether the point is returned in
                Cartesian coordinates or grid coordinates.
            avoid_center (bool): Determines whether the boundary distance
                should also be kept from the center, i.e., whether points close
                to the center are returned.
            rng (:class:`~numpy.random.Generator`):
                Random number generator (default: :func:`~numpy.random.default_rng()`)

        Returns:
            :class:`~numpy.ndarray`: The coordinates of the point
        """
        if rng is None:
            rng = np.random.default_rng()

        # handle the boundary distance
        r_min = boundary_distance if avoid_center else 0
        r_mag = self.radius - boundary_distance - r_min
        z_min, z_max = self.axes_bounds[1]

        if boundary_distance != 0:
            z_min += boundary_distance
            z_max -= boundary_distance
            if r_mag <= 0 or z_max <= z_min:
                raise RuntimeError("Random points would be too close to boundary")

        # create random point
        r = r_mag * rng.random() + r_min
        z = z_min + (z_max - z_min) * rng.random()
        point = np.array([r, z])
        if cartesian:
            return self.point_to_cartesian(point)
        else:
            return point

    def get_line_data(self, data: np.ndarray, extract: str = "auto") -> Dict[str, Any]:
        """return a line cut for the cylindrical grid

        Args:
            data (:class:`~numpy.ndarray`):
                The values at the grid points
            extract (str):
                Determines which cut is done through the grid. Possible choices
                are (default is `cut_axial`):

                * `cut_z` or `cut_axial`: values along the axial coordinate for
                  :math:`r=0`.
                * `project_z` or `project_axial`: average values for each axial
                  position (radial average).
                * `project_r` or `project_radial`: average values for each
                  radial position (axial average)
        Returns:
            A dictionary with information about the line cut, which is
            convenient for plotting.
        """
        if extract == "auto":
            extract = "cut_axial"

        if extract == "cut_z" or extract == "cut_axial":
            # do a cut along the z axis for r=0
            axis = 1
            data_y = data[..., 0, :]
            label_y = "Cut along z"

        elif extract == "project_z" or extract == "project_axial":
            # project on the axial coordinate (average radially)
            axis = 1
            data_y = (data.mean(axis=-2),)
            label_y = "Projection onto z"

        elif extract == "project_r" or extract == "project_radial":
            # project on the radial coordinate (average axially)
            axis = 0
            data_y = (data.mean(axis=-1),)
            label_y = "Projection onto r"

        else:
            raise ValueError(f"Unknown extraction method {extract}")

        return {
            "data_x": self.axes_coords[axis],
            "data_y": data_y,
            "extent_x": self.axes_bounds[axis],
            "label_x": self.axes[axis],
            "label_y": label_y,
        }

    def get_image_data(self, data: np.ndarray) -> Dict[str, Any]:
        """return a 2d-image of the data

        Args:
            data (:class:`~numpy.ndarray`): The values at the grid points

        Returns:
            A dictionary with information about the image, which is  convenient
            for plotting.
        """
        bounds_r, bounds_z = self.axes_bounds
        return {
            "data": np.vstack((data[::-1, :], data)),
            "x": self.axes_coords[0],
            "y": self.axes_coords[1],
            "extent": (-bounds_r[1], bounds_r[1], bounds_z[0], bounds_z[1]),
            "label_x": self.axes[0],
            "label_y": self.axes[1],
        }

    def contains_point(self, point: np.ndarray) -> np.ndarray:
        """check whether the point is contained in the grid

        Args:
            point (:class:`~numpy.ndarray`): Coordinates of the point
        """
        point = np.atleast_1d(point)
        assert point.shape[-1] == 3, f"Point must have 3 coordinates"

        in_radius = np.hypot(point[..., 0], point[..., 1]) <= self.radius
        bounds_z = self.axes_bounds[1]
        in_z = (bounds_z[0] <= point[..., 2]) & (point[..., 2] <= bounds_z[1])
        return in_radius & in_z  # type: ignore

    def iter_mirror_points(
        self, point: np.ndarray, with_self: bool = False, only_periodic: bool = True
    ) -> Generator:
        """generates all mirror points corresponding to `point`

        Args:
            point (:class:`~numpy.ndarray`): the point within the grid
            with_self (bool): whether to include the point itself
            only_periodic (bool): whether to only mirror along periodic axes

        Returns:
            A generator yielding the coordinates that correspond to mirrors
        """
        point = np.asanyarray(point, dtype=np.double)

        if with_self:
            yield point

        if not only_periodic or self._periodic_z:
            yield point - np.array([self.length, 0, 0])
            yield point + np.array([self.length, 0, 0])

    @cached_property()
    def cell_volume_data(self) -> Tuple[np.ndarray, float]:
        """:class:`~numpy.ndarray`: the volumes of all cells"""
        dr, dz = self.discretization
        rs = np.arange(self.shape[0] + 1) * dr
        areas = np.pi * rs ** 2
        r_vols = np.diff(areas).reshape(self.shape[0], 1)
        return (r_vols, dz)

    def point_to_cartesian(self, points: np.ndarray) -> np.ndarray:
        """convert coordinates of a point to Cartesian coordinates

        Args:
            points (:class:`~numpy.ndarray`):
                Points given in the coordinates of the grid

        Returns:
            :class:`~numpy.ndarray`: The Cartesian coordinates of the point
        """
        points = np.atleast_1d(points)
        if points.shape[-1] != self.num_axes:
            raise DimensionError(f"Array of shape {points.shape} cannot denote points")

        x = points[..., 0]
        y = np.zeros_like(x)
        z = points[..., 1]
        return np.stack((x, y, z), axis=-1)

    def point_from_cartesian(self, points: np.ndarray) -> np.ndarray:
        """convert points given in Cartesian coordinates to this grid

        This function returns points restricted to the x-z plane, i.e., the
        y-coordinate will be zero.

        Args:
            points (:class:`~numpy.ndarray`):
                Points given in Cartesian coordinates.

        Returns:
            :class:`~numpy.ndarray`: Points given in the coordinates of the grid
        """
        points = np.atleast_1d(points)
        assert points.shape[-1] == self.dim, f"Point must have {self.dim} coordinates"

        rs = np.hypot(points[..., 0], points[..., 1])
        zs = points[..., 2]
        return np.stack((rs, zs), axis=-1)

    def cell_to_point(self, cells: np.ndarray, cartesian: bool = True) -> np.ndarray:
        """convert cell coordinates to real coordinates

        This function returns points restricted to the x-z plane, i.e., the
        y-coordinate will be zero.

        Args:
            cells (:class:`~numpy.ndarray`):
                Indices of the cells whose center coordinates are requested.
                This can be float values to indicate positions relative to the
                cell center.
            cartesian (bool):
                Determines whether the point is returned in Cartesian
                coordinates or grid coordinates.

        Returns:
            :class:`~numpy.ndarray`: The center points of the respective cells
        """
        cells = np.atleast_1d(cells)

        if cells.size == 0:
            return np.zeros((0, self.dim))
        if cells.shape[-1] != self.num_axes:
            raise DimensionError(f"Array of shape {cells.shape} cannot denote cells")

        # convert from cells indices to grid coordinates
        points = (cells + 0.5) * self.discretization
        points[..., 1] += self.axes_bounds[1][0]
        if cartesian:
            return self.point_to_cartesian(points)
        else:
            return points  # type: ignore

    def point_to_cell(self, points: np.ndarray) -> np.ndarray:
        """Determine cell(s) corresponding to given point(s)

        This function respects periodic boundary conditions, but it does not
        throw an error when coordinates lie outside the bcs (for
        non-periodic axes).

        Args:
            points (:class:`~numpy.ndarray`): Real coordinates

        Returns:
            :class:`~numpy.ndarray`: The indices of the respective cells
        """
        points = self.point_from_cartesian(points)

        # convert from grid coordinates to cells indices
        points[..., 1] -= self.axes_bounds[1][0]
        points /= self.discretization
        return points.astype(np.intc)

    def difference_vector_real(self, p1: np.ndarray, p2: np.ndarray) -> np.ndarray:
        """return the vector pointing from p1 to p2.

        In case of periodic boundary conditions, the shortest vector is returned

        Args:
            p1 (:class:`~numpy.ndarray`): First point(s)
            p2 (:class:`~numpy.ndarray`): Second point(s)

        Returns:
            :class:`~numpy.ndarray`: The difference vectors between the points
            with periodic  boundary conditions applied.
        """
        diff = np.atleast_1d(p2) - np.atleast_1d(p1)
        if self._periodic_z:
            size = self.length
            diff[..., 1] = (diff[..., 1] + size / 2) % size - size / 2
        return diff  # type: ignore

    def polar_coordinates_real(
        self, origin: np.ndarray, *, ret_angle: bool = False
    ) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
        """return spherical coordinates associated with the grid

        Args:
            origin (:class:`~numpy.ndarray`): Coordinates of the origin at which the polar
                coordinate system is anchored. Note that this must be of the
                form `[0, 0, z_val]`, where only `z_val` can be chosen freely.
            ret_angle (bool): Determines whether the azimuthal angle is returned
                alongside the distance. If `False` only the distance to the
                origin is  returned for each support point of the grid.
                If `True`, the distance and angles are returned.
        """
        origin = np.array(origin, dtype=np.double, ndmin=1)
        if len(origin) != self.dim:
            raise DimensionError("Dimensions are not compatible")

        if origin[0] != 0 or origin[1] != 0:
            raise RuntimeError("Origin must lie on symmetry axis for cylindrical grid")

        # calculate the difference vector between all cells and the origin
        diff = self.difference_vector_real(np.array([0, origin[2]]), self.cell_coords)
        dist: np.ndarray = np.linalg.norm(diff, axis=-1)  # get distance

        if ret_angle:
            return dist, np.arctan2(diff[:, :, 0], diff[:, :, 1])
        else:
            return dist

    @fill_in_docstring
    def get_boundary_conditions(
        self, bc: "BoundariesData" = "natural", rank: int = 0
    ) -> "Boundaries":
        """constructs boundary conditions from a flexible data format

        Args:
            bc (str or list or tuple or dict):
                The boundary conditions applied to the field.
                {ARG_BOUNDARIES}
            rank (int):
                The tensorial rank of the value associated with the boundary
                conditions.

        Raises:
            ValueError: If the data given in `bc` cannot be read
            PeriodicityError: If the boundaries are not compatible with the
                periodic axes of the grid.
        """
        from .boundaries import Boundaries  # @Reimport

        # obtain boundary conditions
        return Boundaries.from_data(self, bc, rank=rank)

    def get_cartesian_grid(self, mode: str = "valid") -> CartesianGrid:
        """return a Cartesian grid for this Cylindrical one

        Args:
            mode (str):
                Determines how the grid is determined. Setting it to 'valid'
                only returns points that are fully resolved in the cylindrical
                grid, e.g., the cylinder is circumscribed. Conversely, 'full'
                returns all data, so the cylinder is inscribed.

        Returns:
            :class:`pde.grids.cartesian.CartesianGrid`: The requested grid
        """
        # Pick the grid instance
        if mode == "valid":
            bounds = self.radius / np.sqrt(self.dim)
        elif mode == "full":
            bounds = self.radius
        else:
            raise ValueError(f"Unsupported mode `{mode}`")

        # determine the Cartesian grid
        num = round(bounds / self.discretization[0])
        grid_bounds = [(-bounds, bounds), (-bounds, bounds), self.axes_bounds[1]]
        grid_shape = 2 * num, 2 * num, self.shape[1]
        return CartesianGrid(grid_bounds, grid_shape)

    def get_subgrid(
        self, indices: Sequence[int]
    ) -> Union["CartesianGrid", "PolarSymGrid"]:
        """return a subgrid of only the specified axes

        Args:
            indices (list):
                Indices indicating the axes that are retained in the subgrid

        Returns:
            :class:`~pde.grids.cartesian.CartesianGrid` or
            :class:`~pde.grids.spherical.PolarSymGrid`: The subgrid
        """
        if len(indices) != 1:
            raise ValueError(f"Can only get sub-grid for one axis.")

        if indices[0] == 0:
            # return a radial grid
            from .spherical import PolarSymGrid  # @Reimport

            return PolarSymGrid(self.radius, self.shape[0])

        elif indices[0] == 1:
            # return a Cartesian grid along the z-axis
            subgrid = CartesianGrid(
                bounds=[self.axes_bounds[1]],
                shape=self.shape[1],
                periodic=self.periodic[1],
            )
            subgrid.axes = [self.axes[1]]
            return subgrid

        else:
            raise ValueError(f"Cannot get sub-grid for index {indices[0]}")

class CylindricalGrid(CylindricalSymGrid):
    r"""3-dimensional cylindrical grid assuming polar symmetry

    .. deprecated:: 0.14 (2021-05-21)
        Use  :class:`~pde.grids.cylindrical.CylindricalSymGrid` instead.
    """

    deprecated: bool = True

    def __init__(self, *args, **kwargs):
        """class deprecated since 2021-05-21"""
        warnings.warn(
            "CylindricalGrid is a deprecated class. Use CylindricalSymGrid instead",
            DeprecationWarning,
        )
        super().__init__(*args, **kwargs)