<commit_before>"""
utils
=====

Low-level functionality NOT intended for users to EVER use.

"""

import json
import os.path
import sys
import threading
import re
import datetime

### incase people are using threading, we lock file reads
lock = threading.Lock()

### general file setup tools ###

def load_json_dict(filename, *args):
    """Checks if file exists. Returns {} if something fails."""
    data = {}
    if os.path.exists(filename):
        lock.acquire()
        with open(filename, "r") as f:
            try:
                data = json.load(f)
                if not isinstance(data, dict):
                    data = {}
            except:
                data = {}  # TODO: issue a warning and bubble it up
        lock.release()
        if args:
            d = dict()
            for key in args:
                if key in data:
                    d[key] = data[key]
            return d
            # TODO: replace with below if we drop Python 2.6 compatibility
            # return {key: data[key] for key in args if key in data}
    return data

def save_json_dict(filename, json_dict):
    """Save json to file. Error if path DNE, not a dict, or invalid json."""
    if isinstance(json_dict, dict):
        # this will raise a TypeError if something goes wrong
        json_string = json.dumps(json_dict, indent=4)
        lock.acquire()
        with open(filename, "w") as f:
            f.write(json_string)
        lock.release()
    else:
        raise TypeError("json_dict was not a dictionary. not saving.")

def ensure_file_exists(filename):
    """Given a valid filename, make sure it exists (will create if DNE)."""
    if not os.path.exists(filename):
        head, tail = os.path.split(filename)
        ensure_dir_exists(head)
        with open(filename, 'w') as f:
            pass  # just create the file

def ensure_dir_exists(directory):
    """Given a valid directory path, make sure it exists."""
    if dir:
        if not os.path.isdir(directory):
            os.makedirs(directory)

### Custom JSON encoders ###
class NotEncodable(Exception):
    pass

class _plotlyJSONEncoder(json.JSONEncoder):
    def numpyJSONEncoder(self, obj):
        try:
            import numpy
        except:
            raise NotEncodable

        if type(obj).__module__.split('.')[0] == numpy.__name__:
            l = obj.tolist()
            try:
                return self.datetimeJSONEncoder(l)
            except NotEncodable:
                return l
        else:
            raise NotEncodable

    def datetimeJSONEncoder(self, obj):
        """
        if datetime or iterable of datetimes,
        convert to a string that plotly understands
        format as %Y-%m-%d %H:%M:%S.%f,
                  %Y-%m-%d %H:%M:%S, or
                  %Y-%m-%d
        depending on what non-zero resolution was provided
        """
        try:
            import pandas
            pandas_importable = True
        except:
            pandas_importable = False

        if pandas_importable and obj is pandas.NaT:
            return None

        if isinstance(obj, (datetime.datetime, datetime.date)):
            if obj.microsecond != 0:
                return obj.strftime('%Y-%m-%d %H:%M:%S.%f')
            elif obj.second != 0 or obj.minute != 0 or obj.hour != 0:
                return obj.strftime('%Y-%m-%d %H:%M:%S')
            else:
                return obj.strftime('%Y-%m-%d')
        elif isinstance(obj[0], (datetime.datetime, datetime.date)):
            return [o.strftime(
                    '%Y-%m-%d %H:%M:%S.%f') if o.microsecond != 0 else
                    o.strftime('%Y-%m-%d %H:%M:%S') if o.second != 0 or o.minute != 0 or o.hour != 0 else
                    o.strftime('%Y-%m-%d')
                    for o in obj]
        else:
            raise NotEncodable

    def pandasJSONEncoder(self, obj):
        try:
            import pandas
        except:
            raise NotEncodable

        if isinstance(obj, pandas.Series):
            serializable_list = []
            for li in list(obj):
                try:
                    json.dumps(li)
                    serializable_list.append(li)
                except TypeError:
                    serializable_list.append(self.default(li))

            return serializable_list
        elif isinstance(obj, pandas.Index):
            return obj.tolist()
        elif obj is pandas.NaT:
            return None
        else:
            raise NotEncodable

    def sageJSONEncoder(self, obj):
        try:
            from sage.all import RR, ZZ
        except:
            raise NotEncodable

        if obj in RR:
            return float(obj)
        elif obj in ZZ:
            return int(obj)
        else:
            raise NotEncodable

    def builtinJSONEncoder(self, obj):
        try:
            return obj.to_plotly_json()
        except AttributeError:
            raise NotEncodable

    def default(self, obj):
        encoders = (self.builtinJSONEncoder,
                    self.datetimeJSONEncoder,
                    self.numpyJSONEncoder,
                    self.pandasJSONEncoder,
                    self.sageJSONEncoder)
        for encoder in encoders:
            try:
                return encoder(obj)
            except NotEncodable:
                pass

        raise TypeError(repr(obj) + " is not JSON serializable")

### unicode stuff ###
def decode_unicode(coll):
    if isinstance(coll, list):
        for no, entry in enumerate(coll):
            if isinstance(entry, (dict, list)):
                coll[no] = decode_unicode(entry)
            else:
                if isinstance(entry, str):
                    try:
                        coll[no] = str(entry)
                    except UnicodeEncodeError:
                        pass
    elif isinstance(coll, dict):
        keys, vals = list(coll.keys()), list(coll.values())
        for key, val in zip(keys, vals):
            if isinstance(val, (dict, list)):
                coll[key] = decode_unicode(val)
            elif isinstance(val, str):
                try:
                    coll[key] = str(val)
                except UnicodeEncodeError:
                    pass
            coll[str(key)] = coll.pop(key)
    return coll

### docstring templating ###
def template_doc(**names):
    def _decorator(func):
        if sys.version[:3] != '3.2':
            if func.__doc__ is not None:
                func.__doc__ = func.__doc__.format(**names)
        return func
    return _decorator

def get_first_duplicate(items):
    seen = set()
    for item in items:
        if item not in seen:
            seen.add(item)
        else:
            return item
    return None

### source key
def is_source_key(key):
    src_regex = re.compile(r'.+src$')
    if src_regex.match(key) is not None:
        return True
    else:
        return False
<commit_msg>import just once, and at the top<commit_after>"""
utils
=====

Low-level functionality NOT intended for users to EVER use.

"""

import json
import os.path
import sys
import threading
import re
import datetime

try:
    import numpy
    _numpy_imported = True
except ImportError:
    _numpy_imported = False

try:
    import pandas
    _pandas_imported = True
except ImportError:
    _pandas_imported = False

try:
    from sage.all import RR, ZZ
    _sage_imported = True
except ImportError:
    _sage_imported = False

### incase people are using threading, we lock file reads
lock = threading.Lock()

### general file setup tools ###

def load_json_dict(filename, *args):
    """Checks if file exists. Returns {} if something fails."""
    data = {}
    if os.path.exists(filename):
        lock.acquire()
        with open(filename, "r") as f:
            try:
                data = json.load(f)
                if not isinstance(data, dict):
                    data = {}
            except:
                data = {}  # TODO: issue a warning and bubble it up
        lock.release()
        if args:
            d = dict()
            for key in args:
                if key in data:
                    d[key] = data[key]
            return d
            # TODO: replace with below if we drop Python 2.6 compatibility
            # return {key: data[key] for key in args if key in data}
    return data

def save_json_dict(filename, json_dict):
    """Save json to file. Error if path DNE, not a dict, or invalid json."""
    if isinstance(json_dict, dict):
        # this will raise a TypeError if something goes wrong
        json_string = json.dumps(json_dict, indent=4)
        lock.acquire()
        with open(filename, "w") as f:
            f.write(json_string)
        lock.release()
    else:
        raise TypeError("json_dict was not a dictionary. not saving.")

def ensure_file_exists(filename):
    """Given a valid filename, make sure it exists (will create if DNE)."""
    if not os.path.exists(filename):
        head, tail = os.path.split(filename)
        ensure_dir_exists(head)
        with open(filename, 'w') as f:
            pass  # just create the file

def ensure_dir_exists(directory):
    """Given a valid directory path, make sure it exists."""
    if dir:
        if not os.path.isdir(directory):
            os.makedirs(directory)

### Custom JSON encoders ###
class NotEncodable(Exception):
    pass

class _plotlyJSONEncoder(json.JSONEncoder):
    def numpyJSONEncoder(self, obj):
        if not _numpy_imported:
            raise NotEncodable

        if type(obj).__module__.split('.')[0] == numpy.__name__:
            l = obj.tolist()
            try:
                return self.datetimeJSONEncoder(l)
            except NotEncodable:
                return l
        else:
            raise NotEncodable

    def datetimeJSONEncoder(self, obj):
        """
        if datetime or iterable of datetimes,
        convert to a string that plotly understands
        format as %Y-%m-%d %H:%M:%S.%f,
                  %Y-%m-%d %H:%M:%S, or
                  %Y-%m-%d
        depending on what non-zero resolution was provided
        """

        if _pandas_imported and obj is pandas.NaT:
            return None

        if isinstance(obj, (datetime.datetime, datetime.date)):
            if obj.microsecond != 0:
                return obj.strftime('%Y-%m-%d %H:%M:%S.%f')
            elif obj.second != 0 or obj.minute != 0 or obj.hour != 0:
                return obj.strftime('%Y-%m-%d %H:%M:%S')
            else:
                return obj.strftime('%Y-%m-%d')
        elif isinstance(obj[0], (datetime.datetime, datetime.date)):
            return [o.strftime(
                    '%Y-%m-%d %H:%M:%S.%f') if o.microsecond != 0 else
                    o.strftime('%Y-%m-%d %H:%M:%S') if o.second != 0 or o.minute != 0 or o.hour != 0 else
                    o.strftime('%Y-%m-%d')
                    for o in obj]
        else:
            raise NotEncodable

    def pandasJSONEncoder(self, obj):
        if not _pandas_imported:
            raise NotEncodable

        if isinstance(obj, pandas.Series):
            serializable_list = []
            for li in list(obj):
                try:
                    json.dumps(li)
                    serializable_list.append(li)
                except TypeError:
                    serializable_list.append(self.default(li))

            return serializable_list
        elif isinstance(obj, pandas.Index):
            return obj.tolist()
        elif obj is pandas.NaT:
            return None
        else:
            raise NotEncodable

    def sageJSONEncoder(self, obj):
        if not _sage_imported:
            raise NotEncodable

        if obj in RR:
            return float(obj)
        elif obj in ZZ:
            return int(obj)
        else:
            raise NotEncodable

    def builtinJSONEncoder(self, obj):
        try:
            return obj.to_plotly_json()
        except AttributeError:
            raise NotEncodable

    def default(self, obj):
        encoders = (self.builtinJSONEncoder,
                    self.datetimeJSONEncoder,
                    self.numpyJSONEncoder,
                    self.pandasJSONEncoder,
                    self.sageJSONEncoder)
        for encoder in encoders:
            try:
                return encoder(obj)
            except NotEncodable:
                pass

        raise TypeError(repr(obj) + " is not JSON serializable")

### unicode stuff ###
def decode_unicode(coll):
    if isinstance(coll, list):
        for no, entry in enumerate(coll):
            if isinstance(entry, (dict, list)):
                coll[no] = decode_unicode(entry)
            else:
                if isinstance(entry, str):
                    try:
                        coll[no] = str(entry)
                    except UnicodeEncodeError:
                        pass
    elif isinstance(coll, dict):
        keys, vals = list(coll.keys()), list(coll.values())
        for key, val in zip(keys, vals):
            if isinstance(val, (dict, list)):
                coll[key] = decode_unicode(val)
            elif isinstance(val, str):
                try:
                    coll[key] = str(val)
                except UnicodeEncodeError:
                    pass
            coll[str(key)] = coll.pop(key)
    return coll

### docstring templating ###
def template_doc(**names):
    def _decorator(func):
        if sys.version[:3] != '3.2':
            if func.__doc__ is not None:
                func.__doc__ = func.__doc__.format(**names)
        return func
    return _decorator

def get_first_duplicate(items):
    seen = set()
    for item in items:
        if item not in seen:
            seen.add(item)
        else:
            return item
    return None

### source key
def is_source_key(key):
    src_regex = re.compile(r'.+src$')
    if src_regex.match(key) is not None:
        return True
    else:
        return False
<|endoftext|>


<commit_before>import random
import itertools
from serialiser import *
WORKER_NODES = 50
QUEUES = 3

class DemoBatchSystem(GenericBatchSystem):
    """
    This is an example implementation of how a batch system is "read" and what is expected of it
    by qtop in order to run.
    """

    @staticmethod
    def get_mnemonic():
        return "demo"

    def __init__(self, scheduler_output_filenames, config):
        """
        config corresponds to the QTOPCONF_YAML file distributed with qtop.
        Custom QTOPCONF files can be created and placed either
        in USERPATH/QTOPCONF_YAML or in SYSTEMCONFDIR/QTOPCONF_YAML
        """
        self.scheduler_output_filenames = scheduler_output_filenames
        self.config = config
        self.jobs = list()  # needed just for the demo to work
        self.total_nps = 0

    def get_worker_nodes(self):
        """
        Possible node states are:
        "-": free
        "b": busy
        "d": down/offline
        and generally the first letter of the word describing the state is used.
        """

        worker_nodes = list()
        iter_names = itertools.cycle(['dn', 'pc', 'wn'])
        iter_domains = itertools.cycle(['foo.com', 'bar.com', 'baz.com'])
        for i in range(WORKER_NODES):
            worker_node = dict()
            worker_node["domainname"] = "".join([
                next(iter_names),
                str(i),
                next(iter_domains)])
            worker_node["state"] = random.choice(["-", "b", "d"])
            # worker_node["gpus"] = random.choice([0, 2, 4, 8, 16, 24, 32])  # currently not displayed
            worker_node["np"] = random.choice([8, 16, 24, 32])
            self.total_nps += worker_node["np"]

            # pick a series of random core/random job nr pairs
            worker_node["core_job_map"] = dict()
            for i in range(random.randint(0, worker_node["np"])):
                if worker_node["state"] == "d": break
                random_job_id = "j" + str(random.randint(0, 500))
                random_core = random.choice(range(worker_node["np"]))
                worker_node["core_job_map"][random_core] = random_job_id
                self.jobs.append(random_job_id)
            worker_nodes.append(worker_node)

        return worker_nodes

    def get_jobs_info(self):
        """
        These 4 lists have to be of the same length (TODO: maybe make a tuple out of them or consider an alternative structure?)
        """
        job_ids = self.jobs
        # These are used in the upper and lower parts of qtop in the statistics part
        job_states = [random.choice("Q R C E W".split()) for _ in job_ids]
        usernames = [random.choice("alice023 alibs lhc154 fotis Atlassm".split()) for _ in job_ids]
        queue_names = [random.choice("Urgent Foobar Priori".split()) for _ in job_ids]
        return job_ids, usernames, job_states, queue_names

    def get_queues_info(self):
        total_running_jobs = 110  # these are reported directly by qstat in PBS; if not, they are calculated.
        total_queued_jobs = 100

        qstatq_list = list()
        queues = "Urgent Foobar Priori".split()
        for i in range(QUEUES):
            qstatq = dict()
            qstatq['run'] = random.randint(0, 15)
            qstatq['queued'] = str(random.randint(0, 15))
            # qstatq['queue_name'] = random.choice("Urgent Foobar Priori".split())
            qstatq['queue_name'] = queues.pop()
            qstatq['state'] = random.choice("Q R C E W".split())
            qstatq['lm'] = random.randint(0, 100)
            qstatq_list.append(qstatq)

        return total_running_jobs, total_queued_jobs, qstatq_list
<commit_msg>r+q jobs in usage totals are now reporting the actual numbers of the jobs shown<commit_after>import random
import itertools
from serialiser import *
WORKER_NODES = 50
QUEUES = 3

class DemoBatchSystem(GenericBatchSystem):
    """
    This is an example implementation of how a batch system is "read" and what is expected of it
    by qtop in order to run.
    """

    @staticmethod
    def get_mnemonic():
        return "demo"

    def __init__(self, scheduler_output_filenames, config):
        """
        config corresponds to the QTOPCONF_YAML file distributed with qtop.
        Custom QTOPCONF files can be created and placed either
        in USERPATH/QTOPCONF_YAML or in SYSTEMCONFDIR/QTOPCONF_YAML
        """
        self.scheduler_output_filenames = scheduler_output_filenames
        self.config = config
        self.jobs = list()  # needed just for the demo to work
        self.running_jobs_nr = 0
        self.queued_jobs_nr = 0
        self.total_nps = 0

    def get_worker_nodes(self):
        """
        Possible node states are:
        "-": free
        "b": busy
        "d": down/offline
        and generally the first letter of the word describing the state is used.
        """

        worker_nodes = list()
        iter_names = itertools.cycle(['dn', 'pc', 'wn'])
        iter_domains = itertools.cycle(['foo.com', 'bar.com', 'baz.com'])
        for i in range(WORKER_NODES):
            worker_node = dict()
            worker_node["domainname"] = "".join([
                next(iter_names),
                str(i),
                next(iter_domains)])
            worker_node["state"] = random.choice(["-", "b", "d"])
            # worker_node["gpus"] = random.choice([0, 2, 4, 8, 16, 24, 32])  # currently not displayed
            worker_node["np"] = random.choice([8, 16, 24, 32])
            self.total_nps += worker_node["np"]

            # pick a series of random core/random job nr pairs
            worker_node["core_job_map"] = dict()
            for i in range(random.randint(0, worker_node["np"])):
                if worker_node["state"] == "d": break
                random_job_id = "j" + str(random.randint(0, 500))
                random_core = random.choice(range(worker_node["np"]))
                worker_node["core_job_map"][random_core] = random_job_id
                self.jobs.append(random_job_id)
            worker_nodes.append(worker_node)

        return worker_nodes

    def get_jobs_info(self):
        """
        These 4 lists have to be of the same length (TODO: maybe make a tuple out of them or consider an alternative structure?)
        """
        job_ids = self.jobs
        # These are used in the upper and lower parts of qtop in the statistics part
        job_states = [random.choice("Q R C E W".split()) for _ in job_ids]
        usernames = [random.choice("alice023 alibs lhc154 fotis Atlassm".split()) for _ in job_ids]
        queue_names = [random.choice("Urgent Foobar Priori".split()) for _ in job_ids]
        self.running_jobs_nr = job_states.count('R')
        self.queued_jobs_nr = job_states.count('Q')
        return job_ids, usernames, job_states, queue_names

    def get_queues_info(self):
        total_running_jobs = self.running_jobs_nr  # these are reported directly by qstat in PBS; if not, they are calculated.
        total_queued_jobs = self.queued_jobs_nr

        qstatq_list = list()
        queues = "Urgent Foobar Priori".split()
        for i in range(QUEUES):
            qstatq = dict()
            qstatq['run'] = random.randint(0, 15)
            qstatq['queued'] = str(random.randint(0, 15))
            qstatq['queue_name'] = queues.pop()
            qstatq['state'] = random.choice("Q R C E W".split())
            qstatq['lm'] = random.randint(0, 100)
            qstatq_list.append(qstatq)

        return total_running_jobs, total_queued_jobs, qstatq_list
<|endoftext|>


<commit_before>from plugin import Plugin
from config import cmd_help

class Help(Plugin):
    is_global = True

    async def on_message(self, message, pfx):

        if message.content.startswith(pfx + cmd_help):
                    cmd_name = 'Help'
                    await self.client.send_typing(message.channel)
                    await self.client.send_message(message.channel, '\nHelp: `' + pfx + cmd_help + '`' +
                                              '\nOverwatch: `' + pfx + cmd_overwatch + '`' +
                                              '\nLeague of Legends: `' + pfx + cmd_league + '`' +
                                              '\nBlade and Soul: `' + pfx + cmd_bns + '`' +
                                              '\n - Detailed Attack Stats: `' + pfx + cmd_bns + 'att' + '`' +
                                              '\n - Detailed Defense Stats: `' + pfx + cmd_bns + 'def' + '`' +
                                              '\nUrban Dictionary: `' + pfx + cmd_ud + '`' +
                                              '\nWeather: `' + pfx + cmd_weather + '`' +
                                              '\nHearthstone: `' + pfx + cmd_hearthstone + '`' +
                                              '\nPokemon: `' + pfx + cmd_pokemon + '`' +
                                              '\nJoke: `' + pfx + cmd_joke + '`')
                    #print('CMD [' + cmd_name + '] > ' + initiator_data)<commit_msg>import config global to help.py<commit_after>from plugin import Plugin
from config import *

class Help(Plugin):
    is_global = True

    async def on_message(self, message, pfx):

        if message.content.startswith(pfx + cmd_help):
                    cmd_name = 'Help'
                    await self.client.send_typing(message.channel)
                    await self.client.send_message(message.channel, '\nHelp: `' + pfx + cmd_help + '`' +
                                              '\nOverwatch: `' + pfx + cmd_overwatch + '`' +
                                              '\nLeague of Legends: `' + pfx + cmd_league + '`' +
                                              '\nBlade and Soul: `' + pfx + cmd_bns + '`' +
                                              '\n - Detailed Attack Stats: `' + pfx + cmd_bns + 'att' + '`' +
                                              '\n - Detailed Defense Stats: `' + pfx + cmd_bns + 'def' + '`' +
                                              '\nUrban Dictionary: `' + pfx + cmd_ud + '`' +
                                              '\nWeather: `' + pfx + cmd_weather + '`' +
                                              '\nHearthstone: `' + pfx + cmd_hearthstone + '`' +
                                              '\nPokemon: `' + pfx + cmd_pokemon + '`' +
                                              '\nJoke: `' + pfx + cmd_joke + '`')
                    #print('CMD [' + cmd_name + '] > ' + initiator_data)<|endoftext|>


<commit_before>import asyncio
import datetime
import json
import schedule
from random import choice
from plugin_manager import BasePlugin
from utils import Command, respond

class MOTD(BasePlugin):
    name = "motd"
    default_config = {
        "motd_file": "config/motds.json",
        "motd_channel": "CHANNEL ID HERE"
    }

    def activate(self):
        try:
            with open(self.plugin_config.motd_file, "r") as f:
                self.motds = json.load(f)
                schedule.every().day.at("00:00").do(self._display_motd)
                asyncio.ensure_future(self._run_motd())
        except json.decoder.JSONDecodeError:
            self.logger.exception(f"Could not decode {self.plugin_config.motd_file}! ", exc_info=True)

    async def _run_motd(self):
        while True:
            schedule.run_pending()
            await asyncio.sleep(60)

    def _display_motd(self):
        today = datetime.date.today()
        month = today.strftime("%B")
        day = today.day
        weekday = today.strftime("%A")
        holiday_lines = self._get_holiday(month, day, weekday)
        chan = self.client.get_channel(self.plugin_config.motd_channel)
        if holiday_lines:
            asyncio.ensure_future(self.client.send_message(chan, choice(holiday_lines)))
        else:
            lines = []
            lines += self.motds.get("Any", {}).get("Any", [])
            lines += self.motds.get("Any", {}).get(day, [])
            lines += self.motds.get("Any", {}).get(weekday, [])
            lines += self.motds.get(month, {}).get("Any", [])
            lines += self.motds.get(month, {}).get(day, [])
            lines += self.motds.get(month, {}).get(weekday, [])
            asyncio.ensure_future(self.client.send_message(chan, choice(lines)))

    def _get_holiday(self, month, day, weekday):
        holidays = self.motds["holidays"]
        if f"{month}/{day}" in holidays:
            return self.motds[month][day]
        elif f"{month}/{weekday}" in holidays:
            return self.motds[month][weekday]
        elif f"{month}/Any" in holidays:
            return self.motds[month]["Any"]
        elif f"Any/{day}" in holidays:
            return self.motds["Any"][day]
        elif f"Any/{weekday}" in holidays:
            return self.motds["Any"][weekday]
        else:
            return

    @Command("testmotd",
             doc="Used for testing MOTD lines.",
             syntax="(month/Any) (day/weekday/Any)")
    async def _testmotd(self, data):
        args = data.clean_content.split()[1:]
        month = args[0]
        day = args[1]
        weekday = args[2]
        holiday_lines = self._get_holiday(month, day, weekday)
        if holiday_lines:
            await respond(self.client, data, choice(holiday_lines))
        else:
            lines = []
            lines += self.motds.get("Any", {}).get("Any", [])
            lines += self.motds.get("Any", {}).get(day, [])
            lines += self.motds.get("Any", {}).get(weekday, [])
            lines += self.motds.get(month, {}).get("Any", [])
            lines += self.motds.get(month, {}).get(day, [])
            lines += self.motds.get(month, {}).get(weekday, [])
            await respond(self.client, data, "\n".join(lines))

    @Command("displaymotd",
             doc="Display a MOTD based on the current date.")
    async def _displaymotdcmd(self, data):
        today = datetime.date.today()
        month = today.strftime("%B")
        day = today.day
        weekday = today.strftime("%A")
        holiday_lines = self._get_holiday(month, day, weekday)
        chan = self.client.get_channel(self.plugin_config.motd_channel)
        if holiday_lines:
            asyncio.ensure_future(self.client.send_message(chan, choice(holiday_lines)))
        else:
            lines = []
            lines += self.motds.get("Any", {}).get("Any", [])
            lines += self.motds.get("Any", {}).get(day, [])
            lines += self.motds.get("Any", {}).get(weekday, [])
            lines += self.motds.get(month, {}).get("Any", [])
            lines += self.motds.get(month, {}).get(day, [])
            lines += self.motds.get(month, {}).get(weekday, [])
            asyncio.ensure_future(self.client.send_message(chan, choice(lines)))<commit_msg>Fix some errors in MOTD<commit_after>import asyncio
import datetime
import json
import schedule
from random import choice
from plugin_manager import BasePlugin
from utils import Command, respond

class MOTD(BasePlugin):
    name = "motd"
    default_config = {
        "motd_file": "config/motds.json",
        "motd_channel": "CHANNEL ID HERE"
    }

    def activate(self):
        try:
            with open(self.plugin_config.motd_file, "r") as f:
                self.motds = json.load(f)
                schedule.every().day.at("00:00").do(self._display_motd)
                asyncio.ensure_future(self._run_motd())
        except json.decoder.JSONDecodeError:
            self.logger.exception(f"Could not decode {self.plugin_config.motd_file}! ", exc_info=True)

    async def _run_motd(self):
        while True:
            schedule.run_pending()
            await asyncio.sleep(60)

    def _display_motd(self):
        today = datetime.date.today()
        month = today.strftime("%B")
        day = str(today.day)
        weekday = today.strftime("%A")
        holiday_lines = self._get_holiday(month, day, weekday)
        chan = self.client.get_channel(self.plugin_config.motd_channel)
        if holiday_lines:
            asyncio.ensure_future(self.client.send_message(chan, choice(holiday_lines)))
        else:
            lines = []
            lines += self.motds.get("Any", {}).get("Any", [])
            lines += self.motds.get("Any", {}).get(day, [])
            lines += self.motds.get("Any", {}).get(weekday, [])
            lines += self.motds.get(month, {}).get("Any", [])
            lines += self.motds.get(month, {}).get(day, [])
            lines += self.motds.get(month, {}).get(weekday, [])
            asyncio.ensure_future(self.client.send_message(chan, choice(lines)))

    def _get_holiday(self, month, day, weekday):
        holidays = self.motds["holidays"]
        if f"{month}/{day}" in holidays:
            return self.motds[month][day]
        elif f"{month}/{weekday}" in holidays:
            return self.motds[month][weekday]
        elif f"{month}/Any" in holidays:
            return self.motds[month]["Any"]
        elif f"Any/{day}" in holidays:
            return self.motds["Any"][day]
        elif f"Any/{weekday}" in holidays:
            return self.motds["Any"][weekday]
        else:
            return

    @Command("testmotd",
             doc="Used for testing MOTD lines.",
             syntax="(month/Any) (day/weekday/Any)")
    async def _testmotd(self, data):
        args = data.clean_content.split()[1:]
        month = args[0]
        day = args[1]
        weekday = args[2]
        holiday_lines = self._get_holiday(month, day, weekday)
        if holiday_lines:
            await respond(self.client, data, choice(holiday_lines))
        else:
            lines = []
            lines += self.motds.get("Any", {}).get("Any", [])
            lines += self.motds.get("Any", {}).get(day, [])
            lines += self.motds.get("Any", {}).get(weekday, [])
            lines += self.motds.get(month, {}).get("Any", [])
            lines += self.motds.get(month, {}).get(day, [])
            lines += self.motds.get(month, {}).get(weekday, [])
            await respond(self.client, data, "\n".join(lines))

    @Command("displaymotd",
             doc="Display a MOTD based on the current date.")
    async def _displaymotdcmd(self, data):
        today = datetime.date.today()
        month = today.strftime("%B")
        day = str(today.day)
        weekday = today.strftime("%A")
        holiday_lines = self._get_holiday(month, day, weekday)
        chan = self.client.get_channel(self.plugin_config.motd_channel)
        if holiday_lines:
            asyncio.ensure_future(self.client.send_message(chan, choice(holiday_lines)))
        else:
            lines = []
            lines += self.motds.get("Any", {}).get("Any", [])
            lines += self.motds.get("Any", {}).get(day, [])
            lines += self.motds.get("Any", {}).get(weekday, [])
            lines += self.motds.get(month, {}).get("Any", [])
            lines += self.motds.get(month, {}).get(day, [])
            lines += self.motds.get(month, {}).get(weekday, [])
            asyncio.ensure_future(self.client.send_message(chan, choice(lines)))<|endoftext|>


<commit_before>from plugin import Plugin, PluginDocument

from database import TableBase, Column, Text
from database import engine

class PanoDocument(TableBase, PluginDocument):

    __tablename__ = 'pano'

    def __init__(self, owner, title='Untitled', text=''):
        super().__init__()
        self.owner = owner
        self.title = title
        self.text = text
        self.description = 'not support description'
    text = Column(Text())

class Pano(Plugin):
    def __init__(self):
        super().__init__()
        self.documents.set_document_type('pano')
        self.documents.set_document_table(PanoDocument)
        self.name = 'biobrick_manager'
        try:
            PanoDocument.__table__.create(engine)
        except:
            pass

    def process(self, request):
        if request['action'] == 'new':
            doc = PanoDocument(self.user)
            self.documents.create(doc)
            return dict(id=doc.id)
        elif request['action'] == 'save':
            request['id'] = int(request['id'])
            doc = self.documents.get(request['id'])
            if not (doc and doc.owner == self.user):
                raise ValueError('Document %d does not exists.' % request['id'])
            doc.text = request['data']
            self.documents.update(doc)
            return {}
        elif request['action'] == 'list':
            docs = self.documents.list(self.user)
            return dict(ids=list(map(lambda x:x.id, docs)))
        elif request['action'] == 'load':
            request['id'] = int(request['id'])
            doc = self.documents.get(request['id'])
            if not (doc and doc.owner == self.user):
                raise ValueError('Document %d does not exists.' % request['id'])
            return dict(data=doc.text)
        elif request['action'] == 'delete':
            request['id'] = int(request['id'])
            doc = self.documents.get(request['id'])
            if not (doc and doc.owner == self.user):
                raise ValueError('Document %d does not exists.' % request['id'])
            self.documents.delete(doc)
            return {}
        else:
            raise ValueError('Unknown action: %s' % request['action'])

__plugin__ = Pano()
<commit_msg>bug-driven development<commit_after>from plugin import Plugin, PluginDocument

from database import TableBase, Column, Text
from database import engine

class PanoDocument(TableBase, PluginDocument):

    __tablename__ = 'pano'

    def __init__(self, owner, title='Untitled', text=''):
        super().__init__()
        self.owner = owner
        self.title = title
        self.text = text
        self.description = 'not support description'
    text = Column(Text())

class Pano(Plugin):
    def __init__(self):
        super().__init__()
        self.documents.set_document_type('pano')
        self.documents.set_document_table(PanoDocument)
        self.name = 'biobrick_manager'
        try:
            PanoDocument.__table__.create(engine)
        except:
            pass

    def process(self, request):
        if request['action'] == 'new':
            doc = PanoDocument(self.user.id)
            self.documents.create(doc)
            return dict(id=doc.id)
        elif request['action'] == 'save':
            request['id'] = int(request['id'])
            doc = self.documents.get(request['id'])
            if not (doc and doc.owner == self.user):
                raise ValueError('Document %d does not exists.' % request['id'])
            doc.text = request['data']
            self.documents.update(doc)
            return {}
        elif request['action'] == 'list':
            docs = self.documents.list(self.user)
            return dict(ids=list(map(lambda x:x.id, docs)))
        elif request['action'] == 'load':
            request['id'] = int(request['id'])
            doc = self.documents.get(request['id'])
            if not (doc and doc.owner == self.user):
                raise ValueError('Document %d does not exists.' % request['id'])
            return dict(data=doc.text)
        elif request['action'] == 'delete':
            request['id'] = int(request['id'])
            doc = self.documents.get(request['id'])
            if not (doc and doc.owner == self.user):
                raise ValueError('Document %d does not exists.' % request['id'])
            self.documents.delete(doc)
            return {}
        else:
            raise ValueError('Unknown action: %s' % request['action'])

__plugin__ = Pano()
<|endoftext|>


<commit_before>'''
Compatibility module for Python 2.7 and > 3.3
=============================================
'''

__all__ = ('PY2', 'string_types', 'queue', 'iterkeys',
           'itervalues', 'iteritems')

import sys
try:
    import queue
except ImportError:
    import Queue as queue

#: True if Python 2 intepreter is used
PY2 = sys.version_info[0] == 2

#: String types that can be used for checking if a object is a string
string_types = None
text_type = None
if PY2:
    string_types = basestring
    text_type = unicode
else:
    string_types = text_type = str

if PY2:
    iterkeys = lambda d: d.iterkeys()
    itervalues = lambda d: d.itervalues()
    iteritems = lambda d: d.iteritems()
else:
    iterkeys = lambda d: iter(d.keys())
    itervalues = lambda d: iter(d.values())
    iteritems = lambda d: iter(d.items())

if PY2:
    xrange = xrange
else:
    xrange = range
<commit_msg>add xrange to compat.__all__<commit_after>'''
Compatibility module for Python 2.7 and > 3.3
=============================================
'''

__all__ = ('PY2', 'string_types', 'queue', 'iterkeys',
           'itervalues', 'iteritems', 'xrange')

import sys
try:
    import queue
except ImportError:
    import Queue as queue

#: True if Python 2 intepreter is used
PY2 = sys.version_info[0] == 2

#: String types that can be used for checking if a object is a string
string_types = None
text_type = None
if PY2:
    string_types = basestring
    text_type = unicode
else:
    string_types = text_type = str

if PY2:
    iterkeys = lambda d: d.iterkeys()
    itervalues = lambda d: d.itervalues()
    iteritems = lambda d: d.iteritems()
else:
    iterkeys = lambda d: iter(d.keys())
    itervalues = lambda d: iter(d.values())
    iteritems = lambda d: iter(d.items())

if PY2:
    xrange = xrange
else:
    xrange = range
<|endoftext|>


<commit_before>import abc
import api_user
import music_objects

class State(abc.ABC):  # states hold info on where we are in the program
    @abc.abstractmethod
    def show(self):  # displays relevant info from the current state
        pass

# ------------------------------------------------------------

class ExpandableState(State):  # ExpandableStates have lists that we can interact with
    def __init__(self, content):  # content is either a dict MusicObjects or a MusicObject
        self.content = content

    @abc.abstractclassmethod
    def get_option(self, num):  # return the appropriate MusicObject by matching  an inputted number to a list entry
        pass

    @abc.abstractclassmethod
    def length(self):  # get the length of the list of options
        pass

    @abc.abstractmethod
    def show(self):
        pass

# ------------------------------------------------------------

class NoChangeState(State):  # NoChangeStates are states that have no affect on our position in the program
    def __init__(self, last_state):  # last_state is the state that will be later restored
        self.last_state = last_state

    @abc.abstractmethod
    def show(self):
        pass

# ------------------------------------------------------------

class SearchResultsState(ExpandableState):
    def __init__(self, search_results):  # search_results is a dict of MusicObjects from Mobileclient.search
        super().__init__(search_results)

    def length(self):  # return number of search_results
        return sum([len(self.content[key]) for key in self.content])

    def show(self):  # print search results
        i = 1
        for key in sorted(self.content):  # albums, artists, songs
            print('%s:' % key.capitalize())
            for item in self.content[key]:
                print('%d: %s' % (i, item.to_string()))
                i += 1

    def get_option(self, num):  # return the num-th search result
        i = 1
        for key in sorted(self.content):
            for item in self.content[key]:
                if i is num:
                    return item
                else:
                    i += 1

# ------------------------------------------------------------

class ShowObjectState(ExpandableState):
    def __init__(self, music_obj):  # music_obj is a MusicObject
        super().__init__(music_obj)

    def length(self):  # get the length of all whatever list is being shown
        return self.content.length()

    def show(self):  # display the MusicObject
        self.content.show()

    def get_option(self, num):  # return the num-th entry in music_object's combined sublists
        i = 1
        if i is num:
            return self.content  # first entry is the MusicObject itself
        else:
            i += 1
            for key in sorted(self.content.contents):
                for item in self.content.contents[key]:
                    if i is num:
                        # artist IDs start with A, albums with B, and songs with T
                        # we need to create a new MusicObject because item only holds the name + id
                        if item['id'].startswith('A'):
                            return music_objects.Artist(api_user.API.get_artist_info(item['id']))
                        elif item['id'].startswith('B'):
                            return music_objects.Album(api_user.API.get_album_info(item['id']))
                        elif item['id'].startswith('T'):
                            return music_objects.Song(api_user.API.get_track_info(item['id']))
                    else:
                        i += 1

# ------------------------------------------------------------

class HelpState(NoChangeState):
    def __init__(self, last_state):
        super().__init__(last_state)

    def show(self):
        print('Commands:')
        print('h/help: Show this help message')
        print('s/search search-term: Search for search-term')
        print('q/quit: Exit pmcli')
        print('p/play 123: Play item number 123')
        print('i/info 123: Show info on item number 123')

# ------------------------------------------------------------

class InvalidInputState(NoChangeState):
    def __init__(self, last_state):
        super().__init__(last_state)

    def show(self):
        print('Invalid input: enter h for help')
<commit_msg>changed help text order<commit_after>import abc
import api_user
import music_objects

class State(abc.ABC):  # states hold info on where we are in the program
    @abc.abstractmethod
    def show(self):  # displays relevant info from the current state
        pass

# ------------------------------------------------------------

class ExpandableState(State):  # ExpandableStates have lists that we can interact with
    def __init__(self, content):  # content is either a dict MusicObjects or a MusicObject
        self.content = content

    @abc.abstractclassmethod
    def get_option(self, num):  # return the appropriate MusicObject by matching  an inputted number to a list entry
        pass

    @abc.abstractclassmethod
    def length(self):  # get the length of the list of options
        pass

    @abc.abstractmethod
    def show(self):
        pass

# ------------------------------------------------------------

class NoChangeState(State):  # NoChangeStates are states that have no affect on our position in the program
    def __init__(self, last_state):  # last_state is the state that will be later restored
        self.last_state = last_state

    @abc.abstractmethod
    def show(self):
        pass

# ------------------------------------------------------------

class SearchResultsState(ExpandableState):
    def __init__(self, search_results):  # search_results is a dict of MusicObjects from Mobileclient.search
        super().__init__(search_results)

    def length(self):  # return number of search_results
        return sum([len(self.content[key]) for key in self.content])

    def show(self):  # print search results
        i = 1
        for key in sorted(self.content):  # albums, artists, songs
            print('%s:' % key.capitalize())
            for item in self.content[key]:
                print('%d: %s' % (i, item.to_string()))
                i += 1

    def get_option(self, num):  # return the num-th search result
        i = 1
        for key in sorted(self.content):
            for item in self.content[key]:
                if i is num:
                    return item
                else:
                    i += 1

# ------------------------------------------------------------

class ShowObjectState(ExpandableState):
    def __init__(self, music_obj):  # music_obj is a MusicObject
        super().__init__(music_obj)

    def length(self):  # get the length of all whatever list is being shown
        return self.content.length()

    def show(self):  # display the MusicObject
        self.content.show()

    def get_option(self, num):  # return the num-th entry in music_object's combined sublists
        i = 1
        if i is num:
            return self.content  # first entry is the MusicObject itself
        else:
            i += 1
            for key in sorted(self.content.contents):
                for item in self.content.contents[key]:
                    if i is num:
                        # artist IDs start with A, albums with B, and songs with T
                        # we need to create a new MusicObject because item only holds the name + id
                        if item['id'].startswith('A'):
                            return music_objects.Artist(api_user.API.get_artist_info(item['id']))
                        elif item['id'].startswith('B'):
                            return music_objects.Album(api_user.API.get_album_info(item['id']))
                        elif item['id'].startswith('T'):
                            return music_objects.Song(api_user.API.get_track_info(item['id']))
                    else:
                        i += 1

# ------------------------------------------------------------

class HelpState(NoChangeState):
    def __init__(self, last_state):
        super().__init__(last_state)

    def show(self):
        print('Commands:')
        print('s/search search-term: Search for search-term')
        print('i/info 123: Show info on item number 123')
        print('p/play 123: Play item number 123')
        print('q/quit: Exit pmcli')
        print('h/help: Show this help message')

# ------------------------------------------------------------

class InvalidInputState(NoChangeState):
    def __init__(self, last_state):
        super().__init__(last_state)

    def show(self):
        print('Invalid input: enter h for help')
<|endoftext|>


<commit_before>#!/usr/bin/python

"""
k-mer base library.
"""

import sys
import argparse
from Bio import Seq
from Bio import SeqIO

class kMer():
    """
    Handle the counting of k-mers in a fasta file.
    """
    __nucleotideToBinary = {
        'A': 0x00, 'a': 0x00,
        'C': 0x01, 'c': 0x01,
        'G': 0x02, 'g': 0x02,
        'T': 0x03, 't': 0x03
    }
    """ Conversion table form nucleotide to binary. """
    __binaryToNucleotide = {
        0x00: 'A',
        0x01: 'C',
        0x02: 'G',
        0x03: 'T'
    }
    """ Conversion table form binary to nucleotide. """

    def __init__(self):
        """
        Initialise the class instance.
        """
        self.total = 0
        self.nonZero = 0
    #__init__

    def __initialise(self, length):
        """
        Initialise the rest of the class instance variables.

        @arg length: Length of the k-mers.
        @type length: int
        """
        self.length = length
        self.number = 4 ** self.length
        self.count = self.number * [0]
        self.__bitMask = self.number - 0x01
    #__initialise

    def __add(self, binaryRepresentation):
        """
        Add a k-mer and keep track of the nonZero and total counters.

        @arg binaryRepresentation: Binary representation of a k-mer.
        @type binaryRepresentation: int
        """
        if not self.count[binaryRepresentation]:
            self.nonZero += 1
        self.count[binaryRepresentation] += 1
        self.total += 1
    #__add

    def __scanLine(self, sequence):
        """
        Count all occurrences of  k-mers in a DNA string.

        @arg sequence: A DNA sequence from a fasta file.
        @type sequence: string
        """
        if len(sequence) > self.length:
            binaryRepresentation = 0x00

            # Calculate the binary representation of a k-mer.
            for i in sequence[:self.length]:
                binaryRepresentation = ((binaryRepresentation << 2) |
                    self.__nucleotideToBinary[i])
            self.__add(binaryRepresentation)

            # Calculate the binary representation of the next k-mer.
            for i in sequence[self.length:]:
                binaryRepresentation = ((binaryRepresentation << 2) |
                    self.__nucleotideToBinary[i]) & self.__bitMask
                self.__add(binaryRepresentation)
            #for
        #if
    #__scanLine

    def analyse(self, handle, length):
        """
        Read a fasta file and count all k-mers in each line.

        @arg handle: An open file handle to a fasta file.
        @type handle: stream
        @arg length: Length of the k-mers.
        @type length: int
        """
        self.__initialise(length)

        for record in SeqIO.parse(handle, "fasta"):
            for sequence in str(record.seq).split('N'):
                self.__scanLine(sequence)
    #analyse

    def load(self, handle):
        """
        Load the k-mer table from a file.

        @arg handle: Open handle to a file.
        @type handle: stream
        """
        self.__initialise(int(handle.readline()[:-1]))
        self.total = (int(handle.readline()[:-1]))
        self.nonZero = (int(handle.readline()[:-1]))

        offset = 0
        line = handle.readline()
        while line:
            self.count[offset] = int(line[:-1])
            line = handle.readline()
            offset += 1
        #while
    #load

    def save(self, handle):
        """
        Save the k-mer table in a file.

        @arg handle: Writable open handle to a file.
        @type handle: stream
        """
        handle.write("%i\n" % self.length)
        handle.write("%i\n" % self.total)
        handle.write("%i\n" % self.nonZero)
        for i in self.count:
            handle.write("%i\n" % i)
    #save

    def merge(self, profile):
        """
        Add the counts of a (compatible) k-mer profile to this one.

        @arg profile: An other k-mer profile.
        @type profile: object(kMer)
        """
        self.total += profile.total
        self.nonZero = 0

        for i in range(self.number):
            self.count[i] += profile.count[i]
            if self.count[i]:
                self.nonZero += 1
        #for
    #merge

    def balance(self):
        """
        Add the counts of the reverse complement of a k-mer to the k-mer and
        vice versa.
        """
        for i in range(self.number):
            i_rc = self.DNAToBinary(Seq.reverse_complement(
                self.binaryToDNA(i)))

            if i <= i_rc:
                temp = self.count[i]
                self.count[i] += self.count[i_rc]
                self.count[i_rc] += temp
            #if
        #for
    #balance

    def split(self):
        """
        Split the profile into two lists, every position in the first list has
        its reverse complement in the same position in the second list and vice
        versa.

        @returns: The forward and reverse complement counts.
        @rtype: tuple(list[float], list[float])
        """
        forward = []
        reverse = []

        for i in range(self.number):
            i_rc = self.DNAToBinary(Seq.reverse_complement(
                self.binaryToDNA(i)))

            if i <= i_rc:
                forward.append(self.count[i])
                reverse.append(self.count[i_rc])
            #if
        #for

        return forward, reverse
    #balance

    def shrink(self, factor):
        """
        Shrink the profile, effectively reducing the value of k.

        Note that this operation may give slightly different values than
        indexing on a lower k directly.

        @arg factor: Shrinking factor.
        @type factor: int
        """
        if self.length < factor:
            raise ValueError(
                "Reduction factor should be smaller than k-mer size.")

        mergeSize = 4 ** factor
        self.nonZero = 0
        newCount = []
        for i in range(0, self.number, mergeSize):
            sub = sum(map(lambda x: self.count[x], range(i, i + mergeSize)))

            if sub:
                self.nonZero += 1
            newCount.append(sub)
        #for
        self.count = newCount
        self.length -= factor
        self.number -= mergeSize
        self.__bitMask >>= 2 * factor
    #shrink

    def DNAToBinary(self, sequence):
        """
        Convert a string of DNA to an integer.

        @arg sequence:
        @type sequence: string

        @returns: Binary representation of a DNA string.
        @rtype: int
        """
        result = 0x00

        for i in sequence:
            result <<= 2
            result |= self.__nucleotideToBinary[i]
        #for

        return result
    #DNAToBinary

    def binaryToDNA(self, number):
        """
        Convert an integer to a DNA string.

        @arg number: Binary representation of a DNA string.
        @type number: int

        @returns: A DNA string.
        @rtype: string
        """
        sequence = ""

        for i in range(self.length):
            sequence += self.__binaryToNucleotide[number & 0x03]
            number >>= 2
        #while

        return sequence[::-1]
    #binaryToDNA

    def complement(self, number):
        """
        Calculate the complement of an integer (note, not the reverse
        complement).

        @arg number: An integer.
        @type number: int

        @returns: The complement of {number}.
        @rtype: int
        """
        myNumber = number
        result = 0x00

        for i in range(self.length):
            result <<= 2
            result |= ~myNumber & 0x03
            myNumber <<= 2
        #for

        return result
    #complement

    def calculateRatios(self):
        """
        Calculate all relative frequencies of k-mers. If a division by 0
        occurs, the frequency will be set to -1.0.

        @returns: A matrix with relative frequencies.
        @rtype: float[][]
        """
        # Initialise the matrix.
        ratios = []
        for i in range(self.number):
            ratios.append(self.number * [0.0])

        # Fill the matrix.
        for i in range(self.number):
            for j in range(self.number):
                if self.count[j]:
                    ratios[i][j] = (float(self.count[i]) /
                        self.count[j]) / self.total
                else:
                    ratios[i][j] = -1.0
            #for

        return ratios
    #calculateRatios

    def calculateDiff(self):
        """
        Calculate all frequency differences of k-mers.

        @returns: A matrix with frequency differences.
        @rtype: float[][]
        """
        ratios = []
        for i in range(self.number):
            ratios.append(self.number * [0])

        # Fill the matrix.
        for i in range(self.number):
            for j in range(self.number):
                if self.count[j]:
                    ratios[i][j] = float(abs(self.count[i] -
                        self.count[j])) / self.total

        return ratios
    #calculateDiff

    def printCounts(self):
        """
        Print the k-mer counts.
        """
        for i in range(self.number):
            print self.binaryToDNA(i), self.count[i]
    #printCounts

    def printRatios(self, ratios):
        """
        Print a ratios matrix.

        @arg ratios: A matrix with relative frequencies.
        @type ratios: float[][]
        """
        # The header.
        print (self.length + 1) * ' ',
        for i in range(self.number):
            print "%s%s" % (self.binaryToDNA(i), 2 * ' '),
        print

        # The matrix.
        for i in range(self.number):
            print "%s" % self.binaryToDNA(i),
            for j in range(self.number):
                print ("%%.%if" % self.length) % ratios[i][j],
            print
        #for
    #printRatios
#kMer
<commit_msg>Added functionality to split on anything but unambiguous nucleotides.<commit_after>#!/usr/bin/python

"""
k-mer base library.
"""

import sys
import argparse
import re
from Bio import Seq
from Bio import SeqIO

class kMer():
    """
    Handle the counting of k-mers in a fasta file.
    """
    __nucleotideToBinary = {
        'A': 0x00, 'a': 0x00,
        'C': 0x01, 'c': 0x01,
        'G': 0x02, 'g': 0x02,
        'T': 0x03, 't': 0x03
    }
    """ Conversion table form nucleotide to binary. """
    __binaryToNucleotide = {
        0x00: 'A',
        0x01: 'C',
        0x02: 'G',
        0x03: 'T'
    }
    """ Conversion table form binary to nucleotide. """

    def __init__(self):
        """
        Initialise the class instance.
        """
        self.total = 0
        self.nonZero = 0
    #__init__

    def __initialise(self, length):
        """
        Initialise the rest of the class instance variables.

        @arg length: Length of the k-mers.
        @type length: int
        """
        self.length = length
        self.number = 4 ** self.length
        self.count = self.number * [0]
        self.__bitMask = self.number - 0x01
    #__initialise

    def __add(self, binaryRepresentation):
        """
        Add a k-mer and keep track of the nonZero and total counters.

        @arg binaryRepresentation: Binary representation of a k-mer.
        @type binaryRepresentation: int
        """
        if not self.count[binaryRepresentation]:
            self.nonZero += 1
        self.count[binaryRepresentation] += 1
        self.total += 1
    #__add

    def __scanLine(self, sequence):
        """
        Count all occurrences of  k-mers in a DNA string.

        @arg sequence: A DNA sequence from a fasta file.
        @type sequence: string
        """
        if len(sequence) > self.length:
            binaryRepresentation = 0x00

            # Calculate the binary representation of a k-mer.
            for i in sequence[:self.length]:
                binaryRepresentation = ((binaryRepresentation << 2) |
                    self.__nucleotideToBinary[i])
            self.__add(binaryRepresentation)

            # Calculate the binary representation of the next k-mer.
            for i in sequence[self.length:]:
                binaryRepresentation = ((binaryRepresentation << 2) |
                    self.__nucleotideToBinary[i]) & self.__bitMask
                self.__add(binaryRepresentation)
            #for
        #if
    #__scanLine

    def analyse(self, handle, length):
        """
        Read a fasta file and count all k-mers in each line.

        @arg handle: An open file handle to a fasta file.
        @type handle: stream
        @arg length: Length of the k-mers.
        @type length: int
        """
        self.__initialise(length)
        S = re.compile("[^%s]" % ''.join(self.__nucleotideToBinary.keys()))

        for record in SeqIO.parse(handle, "fasta"):
            for sequence in S.split(str(record.seq)):
                self.__scanLine(sequence)
    #analyse

    def load(self, handle):
        """
        Load the k-mer table from a file.

        @arg handle: Open handle to a file.
        @type handle: stream
        """
        self.__initialise(int(handle.readline()[:-1]))
        self.total = (int(handle.readline()[:-1]))
        self.nonZero = (int(handle.readline()[:-1]))

        offset = 0
        line = handle.readline()
        while line:
            self.count[offset] = int(line[:-1])
            line = handle.readline()
            offset += 1
        #while
    #load

    def save(self, handle):
        """
        Save the k-mer table in a file.

        @arg handle: Writable open handle to a file.
        @type handle: stream
        """
        handle.write("%i\n" % self.length)
        handle.write("%i\n" % self.total)
        handle.write("%i\n" % self.nonZero)
        for i in self.count:
            handle.write("%i\n" % i)
    #save

    def merge(self, profile):
        """
        Add the counts of a (compatible) k-mer profile to this one.

        @arg profile: An other k-mer profile.
        @type profile: object(kMer)
        """
        self.total += profile.total
        self.nonZero = 0

        for i in range(self.number):
            self.count[i] += profile.count[i]
            if self.count[i]:
                self.nonZero += 1
        #for
    #merge

    def balance(self):
        """
        Add the counts of the reverse complement of a k-mer to the k-mer and
        vice versa.
        """
        for i in range(self.number):
            i_rc = self.DNAToBinary(Seq.reverse_complement(
                self.binaryToDNA(i)))

            if i <= i_rc:
                temp = self.count[i]
                self.count[i] += self.count[i_rc]
                self.count[i_rc] += temp
            #if
        #for
    #balance

    def split(self):
        """
        Split the profile into two lists, every position in the first list has
        its reverse complement in the same position in the second list and vice
        versa.

        @returns: The forward and reverse complement counts.
        @rtype: tuple(list[float], list[float])
        """
        forward = []
        reverse = []

        for i in range(self.number):
            i_rc = self.DNAToBinary(Seq.reverse_complement(
                self.binaryToDNA(i)))

            if i <= i_rc:
                forward.append(self.count[i])
                reverse.append(self.count[i_rc])
            #if
        #for

        return forward, reverse
    #balance

    def shrink(self, factor):
        """
        Shrink the profile, effectively reducing the value of k.

        Note that this operation may give slightly different values than
        indexing on a lower k directly.

        @arg factor: Shrinking factor.
        @type factor: int
        """
        if self.length < factor:
            raise ValueError(
                "Reduction factor should be smaller than k-mer size.")

        mergeSize = 4 ** factor
        self.nonZero = 0
        newCount = []
        for i in range(0, self.number, mergeSize):
            sub = sum(map(lambda x: self.count[x], range(i, i + mergeSize)))

            if sub:
                self.nonZero += 1
            newCount.append(sub)
        #for
        self.count = newCount
        self.length -= factor
        self.number -= mergeSize
        self.__bitMask >>= 2 * factor
    #shrink

    def DNAToBinary(self, sequence):
        """
        Convert a string of DNA to an integer.

        @arg sequence:
        @type sequence: string

        @returns: Binary representation of a DNA string.
        @rtype: int
        """
        result = 0x00

        for i in sequence:
            result <<= 2
            result |= self.__nucleotideToBinary[i]
        #for

        return result
    #DNAToBinary

    def binaryToDNA(self, number):
        """
        Convert an integer to a DNA string.

        @arg number: Binary representation of a DNA string.
        @type number: int

        @returns: A DNA string.
        @rtype: string
        """
        sequence = ""

        for i in range(self.length):
            sequence += self.__binaryToNucleotide[number & 0x03]
            number >>= 2
        #while

        return sequence[::-1]
    #binaryToDNA

    def complement(self, number):
        """
        Calculate the complement of an integer (note, not the reverse
        complement).

        @arg number: An integer.
        @type number: int

        @returns: The complement of {number}.
        @rtype: int
        """
        myNumber = number
        result = 0x00

        for i in range(self.length):
            result <<= 2
            result |= ~myNumber & 0x03
            myNumber <<= 2
        #for

        return result
    #complement

    def calculateRatios(self):
        """
        Calculate all relative frequencies of k-mers. If a division by 0
        occurs, the frequency will be set to -1.0.

        @returns: A matrix with relative frequencies.
        @rtype: float[][]
        """
        # Initialise the matrix.
        ratios = []
        for i in range(self.number):
            ratios.append(self.number * [0.0])

        # Fill the matrix.
        for i in range(self.number):
            for j in range(self.number):
                if self.count[j]:
                    ratios[i][j] = (float(self.count[i]) /
                        self.count[j]) / self.total
                else:
                    ratios[i][j] = -1.0
            #for

        return ratios
    #calculateRatios

    def calculateDiff(self):
        """
        Calculate all frequency differences of k-mers.

        @returns: A matrix with frequency differences.
        @rtype: float[][]
        """
        ratios = []
        for i in range(self.number):
            ratios.append(self.number * [0])

        # Fill the matrix.
        for i in range(self.number):
            for j in range(self.number):
                if self.count[j]:
                    ratios[i][j] = float(abs(self.count[i] -
                        self.count[j])) / self.total

        return ratios
    #calculateDiff

    def printCounts(self):
        """
        Print the k-mer counts.
        """
        for i in range(self.number):
            print self.binaryToDNA(i), self.count[i]
    #printCounts

    def printRatios(self, ratios):
        """
        Print a ratios matrix.

        @arg ratios: A matrix with relative frequencies.
        @type ratios: float[][]
        """
        # The header.
        print (self.length + 1) * ' ',
        for i in range(self.number):
            print "%s%s" % (self.binaryToDNA(i), 2 * ' '),
        print

        # The matrix.
        for i in range(self.number):
            print "%s" % self.binaryToDNA(i),
            for j in range(self.number):
                print ("%%.%if" % self.length) % ratios[i][j],
            print
        #for
    #printRatios
#kMer
<|endoftext|>


<commit_before>#!/usr/bin/python
import time, threading, json, logging, tornado.websocket, modules.calc, math, modules.utils, modules.log, socket, sys
from modules.server import ServerThread
from modules.location import Location
from modules.calc import direction_to_point
from modules.control_thread import StoppableThread

# Variables and constants
data = {'category': 'data', 'timestamp': 0, 'location': Location(0, 0),
        'heading': 0, 'speed': 0, 'wind_dir': 0, 'roll': 0, 'pitch': 0,
        'yaw': 0}

target_locations = []
boundary_locations = []
location_pointer = 0

# Specifies the default values
values = {'debug': False, 'port': 80, 'transmission_delay': 5, 'eval_delay': 5, 'current_desired_heading': 0,
          'direction': 0, 'absolute_wind_direction': 0, 'max_turn_rate_angle': 70, 'max_rudder_angle': 30}

## ----------------------------------------------------------

class DataThread(StoppableThread):

    """ Transmits the data object to the server thread
    """

    server_thread = None
    rudder_sock = None
    winch_sock = None

    def __init__(self, *args, **kwargs):
        super(DataThread, self).__init__(*args, **kwargs)

        global server_thread
        server_thread = ServerThread(name='Server', kwargs={'port': values['port'], 'target_locations': target_locations, 'boundary_locations': boundary_locations})
        server_thread.start()

        global rudder_sock
        try:
            rudder_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            rudder_sock.connect(("localhost", 9107))
        except socket.error:
            # Connection refused error
            logging.critical("Could not connect to servo socket")

        global winch_sock
        try:
            winch_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            winch_sock.connect(("localhost", 9108))
        except socket.error:
            # Connection refused error
            logging.critical("Could not connect to servo socket")

        # Set up logging to the web server console
        logging.getLogger().addHandler(modules.log.WebSocketLogger(self))

    def set_rudder_angle(self, angle):
        try:
            rudder_sock.send(str(angle).encode('utf-8'))
        except socket.error:
            # Broken Pipe Error
            logging.error('The rudder socket is broken!')

    def set_winch_angle(self, angle):
        try:
            winch_sock.send(str(angle).encode('utf-8'))
        except socket.error:
            # Broken Pipe Error
            logging.error('The winch socket is broken!')

    def send_data(self, data):

        # do not log any data here, doing so would create an infinite loop
        try:
            server_thread.send_data(data)
        except tornado.websocket.WebSocketClosedError:
            print('Could not send data because the socket is closed.')

    def run(self):

        logging.info('Starting the data thread!')

        # Connect to the GPS socket
        try:
            gps_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            gps_sock.connect(("localhost", 8907))
        except socket.error:
            # Connection refused error
            logging.critical("Could not connect to GPS socket")

        # Connect to the wind sensor socket
        try:
            wind_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            wind_sock.connect(("localhost", 8894))
        except socket.error:
            logging.critical("Could not connect to wind sensor socket")

        while True:

            if self.stopped():
                # Stop the server thread
                server_thread.stop()
                break

            # Query and update the GPS data
            try:
                gps_sock.send(str(0).encode('utf-8'))
                gps_parsed = json.loads(gps_sock.recv(1024).decode('utf-8').strip('\x00'))

                # Update the data object
                data.update(gps_parsed)

                # Add the location as an embeded data structure
                data['location'] = Location(gps_parsed['latitude'], gps_parsed['longitude'])

                data['heading'] = 180

            except (AttributeError, ValueError, socket.error) as e:
                logging.error('The GPS socket is broken or sent malformed data!')

            # Query and update the wind sensor data
            try:
                wind_sock.send(str(0).encode('utf-8'))
                wind_parsed = json.loads(wind_sock.recv(1024).decode('utf-8'))
                data['wind_dir'] = wind_parsed
            except (ValueError, socket.error) as e:
                # Broken pipe error
                logging.error('The wind sensor socket is broken!')

            # Send data to the server
            server_thread.send_data(modules.utils.getJSON(data))
            logging.debug('Data sent to the server %s' % json.dumps(json.loads(modules.utils.getJSON(data))))

            # Wait in the loop
            time.sleep(float(values['transmission_delay']))

## ----------------------------------------------------------

class LogicThread(StoppableThread):

    preferred_tack = 0 # -1 means left tack and 1 means right tack; 0 not on a tack
    preferred_gybe = 0 # -1 means left-of-wind gybe and 1 means right-of-wind gybe; 0 not on a gybe

    def run(self):
        logging.info("Beginning autonomous navigation routines....")
        logging.warn("The angle is: %d" % data['wind_dir'])

        while True:

            if self.stopped():
                break

            # Update direction
            values['direction'] = modules.calc.direction_to_point(data['location'], target_locations[0])
            values['absolute_wind_direction'] = data['wind_dir'] + data['heading']

            time.sleep(float(values['eval_delay']))
            logging.debug("Heading: %d, Direction: %d, Wind: %d, Absolute Wind Direction: %d, Current Desired Heading: %d, Preferred Tack: %d" % (data['heading'], values['direction'], data['wind_dir'], values['absolute_wind_direction'], values['current_desired_heading'], self.preferred_tack))

            # If it's sailable, go straight to it
            if self.sailable(target_locations[location_pointer]):
                values['current_desired_heading'] = values['direction']
                self.preferred_tack = 0
                self.preferred_gybe = 0

            # It's not sailable; if it's upwind, tack
            elif self.upwind(target.locations[location_pointer]):

                if self.preferred_tack == 0:  # If the target's upwind and you haven't chosen a tack, choose one
                    self.preferred_tack = (180 - ((data['heading'] - values['absolute_wind_direction']) % 360)) / math.fabs(180 - ((data['heading'] - values['absolute_wind_direction']) % 360))

                if self.preferred_tack == -1:  # If the boat is on a left-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] - 45 + 360) % 360

                elif self.preferred_tack == 1: # If the boat is on a right-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] + 45 + 360) % 360

                else:
                    logging.error("The preferred tack was %d" % self.preferred_tack)

            # Otherwise, gybe
            elif self.downwind(target.locations[location_pointer]):

                if self.preferred_gybe == 0:  # If the target's downwind and you haven't chosen a gybe, choose one
                    self.preferred_gybe = (180 - ((data['heading'] - values['absolute_wind_direction']) % 360)) / math.fabs(180 - ((data['heading'] - values['absolute_wind_direction']) % 360))

                if self.preferred_gybe == -1:  # If the boat is on a left-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] + 180 + 45 + 360) % 360

                elif self.preferred_gybe == 1: # If the boat is on a right-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] + 180 - 45 + 360) % 360

                else:
                    logging.error("The preferred gybe was %d" % self.preferred_gybe)

            else:
                logging.critical('Critical logic error!')

            self.turn_rudder()
            self.check_locations()
            logging.debug("Heading: %d, Direction: %d, Wind: %d, Absolute Wind Direction: %d, Current Desired Heading: %d, Preferred Tack: %d, Sailable: %r\n" % (data['heading'], values['direction'], data['wind_dir'], values['absolute_wind_direction'], values['current_desired_heading'], self.preferred_tack, self.sailable(target_locations[location_pointer])))

    # Checks to see if the target location is within a sailable region
    def sailable(self, target_location):
        angle_of_target_off_the_wind = (values['direction'] - values['absolute_wind_direction'] + 360) % 360

        if(math.fabs(angle_of_target_off_the_wind) < 45):
            return False

        if(math.fabs(angle_of_target_off_the_wind) > (360 - 45)):
            return False

        return True

    def upwind(self, target_location):
        angle_of_target_off_the_wind = (values['direction'] - values['absolute_wind_direction'] + 360) % 360
        return math.fabs(angle_of_target_off_the_wind) < 45

    def downwind(self, target_location):
        angle_of_target_off_the_wind = (values['direction'] - values['absolute_wind_direction'] + 360) % 360
        return math.fabs(angle_of_target_off_the_wind) > (360 - 45)

    def check_locations(self):
        global location_pointer
        logging.debug('Trying to sail to %s' % target_locations[location_pointer])

        if modules.calc.point_proximity(data['location'], target_locations[location_pointer]):
            logging.debug('Location %s has been reached! Now traveling to %s!' % (target_locations[location_pointer], target_locations[location_pointer + 1]))
            location_pointer += 1

    def turn_rudder(self):
        # Heading differential
        a = values['current_desired_heading'] - data['heading']
        if (a > 180):
            a -= 360

        # Cap the turn speed
        if (a > values['max_turn_rate_angle']):
            a = values['max_turn_rate_angle']

        # Cap the turn speed
        if (a < (-1 * values['max_turn_rate_angle'])):
            a = -1 * values['max_turn_rate_angle']

        rudder_angle = 90 + a * (values['max_rudder_angle'] / values['max_turn_rate_angle'])

        logging.debug('Set the rudder angle to: %f' % rudder_angle)
        self._kwargs['data_thread'].set_rudder_angle(rudder_angle)

    def turn_winch(self):
        # TODO implement winch turning algorithm
        self._kwargs['data_thread'].set_winch_angle(winch_angle)

## ----------------------------------------------------------

if __name__ == '__main__':
    try:
        threading.current_thread().setName('Main')

        # Sets up the program configuration
        modules.utils.setup_config(values)
        modules.utils.setup_locations(target_locations, boundary_locations)

        logging.info('Starting SailBOT!')

        data_thread = DataThread(name='Data')
        logic_thread = LogicThread(name='Logic', kwargs={'data_thread': data_thread})

        data_thread.start()
        time.sleep(0)
        logic_thread.start()

        while True:
            time.sleep(100)

    except KeyboardInterrupt:
        logging.critical('Program terminating!')
        # Stop the threads
        data_thread.stop()
        logic_thread.stop()

        # Join the threads into the main threads
        data_thread.join()
        logic_thread.join()

        # Terminate the program
        logging.critical('Program exited!')
        sys.exit()
            <commit_msg>Fixed GPS socket receive size<commit_after>#!/usr/bin/python
import time, threading, json, logging, tornado.websocket, modules.calc, math, modules.utils, modules.log, socket, sys
from modules.server import ServerThread
from modules.location import Location
from modules.calc import direction_to_point
from modules.control_thread import StoppableThread

# Variables and constants
data = {'category': 'data', 'timestamp': 0, 'location': Location(0, 0),
        'heading': 0, 'speed': 0, 'wind_dir': 0, 'roll': 0, 'pitch': 0,
        'yaw': 0}

target_locations = []
boundary_locations = []
location_pointer = 0

# Specifies the default values
values = {'debug': False, 'port': 80, 'transmission_delay': 5, 'eval_delay': 5, 'current_desired_heading': 0,
          'direction': 0, 'absolute_wind_direction': 0, 'max_turn_rate_angle': 70, 'max_rudder_angle': 30}

## ----------------------------------------------------------

class DataThread(StoppableThread):

    """ Transmits the data object to the server thread
    """

    server_thread = None
    rudder_sock = None
    winch_sock = None

    def __init__(self, *args, **kwargs):
        super(DataThread, self).__init__(*args, **kwargs)

        global server_thread
        server_thread = ServerThread(name='Server', kwargs={'port': values['port'], 'target_locations': target_locations, 'boundary_locations': boundary_locations})
        server_thread.start()

        global rudder_sock
        try:
            rudder_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            rudder_sock.connect(("localhost", 9107))
        except socket.error:
            # Connection refused error
            logging.critical("Could not connect to servo socket")

        global winch_sock
        try:
            winch_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            winch_sock.connect(("localhost", 9108))
        except socket.error:
            # Connection refused error
            logging.critical("Could not connect to servo socket")

        # Set up logging to the web server console
        logging.getLogger().addHandler(modules.log.WebSocketLogger(self))

    def set_rudder_angle(self, angle):
        try:
            rudder_sock.send(str(angle).encode('utf-8'))
        except socket.error:
            # Broken Pipe Error
            logging.error('The rudder socket is broken!')

    def set_winch_angle(self, angle):
        try:
            winch_sock.send(str(angle).encode('utf-8'))
        except socket.error:
            # Broken Pipe Error
            logging.error('The winch socket is broken!')

    def send_data(self, data):

        # do not log any data here, doing so would create an infinite loop
        try:
            server_thread.send_data(data)
        except tornado.websocket.WebSocketClosedError:
            print('Could not send data because the socket is closed.')

    def run(self):

        logging.info('Starting the data thread!')

        # Connect to the GPS socket
        try:
            gps_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            gps_sock.connect(("localhost", 8907))
        except socket.error:
            # Connection refused error
            logging.critical("Could not connect to GPS socket")

        # Connect to the wind sensor socket
        try:
            wind_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            wind_sock.connect(("localhost", 8894))
        except socket.error:
            logging.critical("Could not connect to wind sensor socket")

        while True:

            if self.stopped():
                # Stop the server thread
                server_thread.stop()
                break

            # Query and update the GPS data
            try:
                gps_sock.send(str(0).encode('utf-8'))
                gps_parsed = json.loads(gps_sock.recv(128).decode('utf-8').strip('\x00'))

                # Update the data object
                data.update(gps_parsed)

                # Add the location as an embeded data structure
                data['location'] = Location(gps_parsed['latitude'], gps_parsed['longitude'])

                data['heading'] = 180

            except (AttributeError, ValueError, socket.error) as e:
                logging.error('The GPS socket is broken or sent malformed data!')

            # Query and update the wind sensor data
            try:
                wind_sock.send(str(0).encode('utf-8'))
                wind_parsed = json.loads(wind_sock.recv(1024).decode('utf-8'))
                data['wind_dir'] = wind_parsed
            except (ValueError, socket.error) as e:
                # Broken pipe error
                logging.error('The wind sensor socket is broken!')

            # Send data to the server
            server_thread.send_data(modules.utils.getJSON(data))
            logging.debug('Data sent to the server %s' % json.dumps(json.loads(modules.utils.getJSON(data))))

            # Wait in the loop
            time.sleep(float(values['transmission_delay']))

## ----------------------------------------------------------

class LogicThread(StoppableThread):

    preferred_tack = 0 # -1 means left tack and 1 means right tack; 0 not on a tack
    preferred_gybe = 0 # -1 means left-of-wind gybe and 1 means right-of-wind gybe; 0 not on a gybe

    def run(self):
        logging.info("Beginning autonomous navigation routines....")
        logging.warn("The angle is: %d" % data['wind_dir'])

        while True:

            if self.stopped():
                break

            # Update direction
            values['direction'] = modules.calc.direction_to_point(data['location'], target_locations[0])
            values['absolute_wind_direction'] = data['wind_dir'] + data['heading']

            time.sleep(float(values['eval_delay']))
            logging.debug("Heading: %d, Direction: %d, Wind: %d, Absolute Wind Direction: %d, Current Desired Heading: %d, Preferred Tack: %d" % (data['heading'], values['direction'], data['wind_dir'], values['absolute_wind_direction'], values['current_desired_heading'], self.preferred_tack))

            # If it's sailable, go straight to it
            if self.sailable(target_locations[location_pointer]):
                values['current_desired_heading'] = values['direction']
                self.preferred_tack = 0
                self.preferred_gybe = 0

            # It's not sailable; if it's upwind, tack
            elif self.upwind(target.locations[location_pointer]):

                if self.preferred_tack == 0:  # If the target's upwind and you haven't chosen a tack, choose one
                    self.preferred_tack = (180 - ((data['heading'] - values['absolute_wind_direction']) % 360)) / math.fabs(180 - ((data['heading'] - values['absolute_wind_direction']) % 360))

                if self.preferred_tack == -1:  # If the boat is on a left-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] - 45 + 360) % 360

                elif self.preferred_tack == 1: # If the boat is on a right-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] + 45 + 360) % 360

                else:
                    logging.error("The preferred tack was %d" % self.preferred_tack)

            # Otherwise, gybe
            elif self.downwind(target.locations[location_pointer]):

                if self.preferred_gybe == 0:  # If the target's downwind and you haven't chosen a gybe, choose one
                    self.preferred_gybe = (180 - ((data['heading'] - values['absolute_wind_direction']) % 360)) / math.fabs(180 - ((data['heading'] - values['absolute_wind_direction']) % 360))

                if self.preferred_gybe == -1:  # If the boat is on a left-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] + 180 + 45 + 360) % 360

                elif self.preferred_gybe == 1: # If the boat is on a right-of-wind tack
                    values['current_desired_heading'] = (values['absolute_wind_direction'] + 180 - 45 + 360) % 360

                else:
                    logging.error("The preferred gybe was %d" % self.preferred_gybe)

            else:
                logging.critical('Critical logic error!')

            self.turn_rudder()
            self.check_locations()
            logging.debug("Heading: %d, Direction: %d, Wind: %d, Absolute Wind Direction: %d, Current Desired Heading: %d, Preferred Tack: %d, Sailable: %r\n" % (data['heading'], values['direction'], data['wind_dir'], values['absolute_wind_direction'], values['current_desired_heading'], self.preferred_tack, self.sailable(target_locations[location_pointer])))

    # Checks to see if the target location is within a sailable region
    def sailable(self, target_location):
        angle_of_target_off_the_wind = (values['direction'] - values['absolute_wind_direction'] + 360) % 360

        if(math.fabs(angle_of_target_off_the_wind) < 45):
            return False

        if(math.fabs(angle_of_target_off_the_wind) > (360 - 45)):
            return False

        return True

    def upwind(self, target_location):
        angle_of_target_off_the_wind = (values['direction'] - values['absolute_wind_direction'] + 360) % 360
        return math.fabs(angle_of_target_off_the_wind) < 45

    def downwind(self, target_location):
        angle_of_target_off_the_wind = (values['direction'] - values['absolute_wind_direction'] + 360) % 360
        return math.fabs(angle_of_target_off_the_wind) > (360 - 45)

    def check_locations(self):
        global location_pointer
        logging.debug('Trying to sail to %s' % target_locations[location_pointer])

        if modules.calc.point_proximity(data['location'], target_locations[location_pointer]):
            logging.debug('Location %s has been reached! Now traveling to %s!' % (target_locations[location_pointer], target_locations[location_pointer + 1]))
            location_pointer += 1

    def turn_rudder(self):
        # Heading differential
        a = values['current_desired_heading'] - data['heading']
        if (a > 180):
            a -= 360

        # Cap the turn speed
        if (a > values['max_turn_rate_angle']):
            a = values['max_turn_rate_angle']

        # Cap the turn speed
        if (a < (-1 * values['max_turn_rate_angle'])):
            a = -1 * values['max_turn_rate_angle']

        rudder_angle = 90 + a * (values['max_rudder_angle'] / values['max_turn_rate_angle'])

        logging.debug('Set the rudder angle to: %f' % rudder_angle)
        self._kwargs['data_thread'].set_rudder_angle(rudder_angle)

    def turn_winch(self):
        # TODO implement winch turning algorithm
        self._kwargs['data_thread'].set_winch_angle(winch_angle)

## ----------------------------------------------------------

if __name__ == '__main__':
    try:
        threading.current_thread().setName('Main')

        # Sets up the program configuration
        modules.utils.setup_config(values)
        modules.utils.setup_locations(target_locations, boundary_locations)

        logging.info('Starting SailBOT!')

        data_thread = DataThread(name='Data')
        logic_thread = LogicThread(name='Logic', kwargs={'data_thread': data_thread})

        data_thread.start()
        time.sleep(0)
        logic_thread.start()

        while True:
            time.sleep(100)

    except KeyboardInterrupt:
        logging.critical('Program terminating!')
        # Stop the threads
        data_thread.stop()
        logic_thread.stop()

        # Join the threads into the main threads
        data_thread.join()
        logic_thread.join()

        # Terminate the program
        logging.critical('Program exited!')
        sys.exit()
            <|endoftext|>