<commit_before>"""Contains all the units for the spec."""
from batesian.units import Units
import inspect
import json
import os
import re
import subprocess
import urllib
import yaml

def get_json_schema_object_fields(obj, enforce_title=False):
    # Algorithm:
    # f.e. property => add field info (if field is object then recurse)
    if obj.get("type") != "object":
        raise Exception(
            "get_json_schema_object_fields: Object %s isn't an object." % obj
        )
    if enforce_title and not obj.get("title"):
        raise Exception(
            "get_json_schema_object_fields: Nested object %s doesn't have a title." % obj
        )

    required_keys = obj.get("required")
    if not required_keys:
        required_keys = []

    fields = {
        "title": obj.get("title"),
        "rows": []
    }
    tables = [fields]

    props = obj.get("properties")
    parents = obj.get("allOf")
    if not props and not parents:
        raise Exception(
            "Object %s has no properties or parents." % obj
        )
    if not props:  # parents only
        return [{
            "title": obj["title"],
            "parent": parents[0]["$ref"],
            "no-table": True
        }]

    for key_name in sorted(props):
        value_type = None
        required = key_name in required_keys
        desc = props[key_name].get("description", "")

        if props[key_name]["type"] == "object":
            if props[key_name].get("additionalProperties"):
                # not "really" an object, just a KV store
                value_type = (
                    "{string: %s}" %
                    props[key_name]["additionalProperties"]["type"]
                )
            else:
                nested_object = get_json_schema_object_fields(
                    props[key_name],
                    enforce_title=True
                )
                value_type = "{%s}" % nested_object[0]["title"]

                if not nested_object[0].get("no-table"):
                    tables += nested_object
        elif props[key_name]["type"] == "array":
            # if the items of the array are objects then recurse
            if props[key_name]["items"]["type"] == "object":
                nested_object = get_json_schema_object_fields(
                    props[key_name]["items"],
                    enforce_title=True
                )
                value_type = "[%s]" % nested_object[0]["title"]
                tables += nested_object
            else:
                value_type = "[%s]" % props[key_name]["items"]["type"]
        else:
            value_type = props[key_name]["type"]
            if props[key_name].get("enum"):
                if len(props[key_name].get("enum")) > 1:
                    value_type = "enum"
                    desc += (
                        " One of: %s" % json.dumps(props[key_name]["enum"])
                    )
                else:
                    desc += (
                        " Must be '%s'." % props[key_name]["enum"][0]
                    )

        fields["rows"].append({
            "key": key_name,
            "type": value_type,
            "required": required,
            "desc": desc,
            "req_str": "**Required.** " if required else ""
        })
    return tables

class MatrixUnits(Units):

    def _load_swagger_meta(self, api, group_name):
        endpoints = []
        for path in api["paths"]:
            for method in api["paths"][path]:
                single_api = api["paths"][path][method]
                endpoint = {
                    "title": single_api.get("summary", ""),
                    "desc": single_api.get("description", single_api.get("summary", "")),
                    "method": method.upper(),
                    "path": api.get("basePath", "") + path,
                    "requires_auth": "security" in single_api,
                    "rate_limited": 429 in single_api.get("responses", {}),
                    "req_params": [],
                    "res_tables": [],
                    "example": {
                        "req": "",
                        "res": ""
                    }
                }
                self.log(".o.O.o. Endpoint: %s %s" % (method, path))
                for param in single_api.get("parameters", []):
                    # description
                    desc = param.get("description", "")
                    if param.get("required"):
                        desc = "**Required.** " + desc

                    # assign value expected for this param
                    val_type = param.get("type") # integer/string
                    refType = Units.prop(param, "schema/$ref/") # Error,Event
                    schemaFmt = Units.prop(param, "schema/format") # bytes e.g. uploads
                    if not val_type and refType:
                        val_type = refType  # TODO: Resolve to human-readable.
                    if not val_type and schemaFmt:
                        val_type = schemaFmt
                    if val_type:
                        endpoint["req_params"].append({
                            "key": param["name"],
                            "loc": param["in"],
                            "type": val_type,
                            "desc": desc
                        })
                        continue
                    # If we're here, either the param has no value or it is an
                    # object which we haven't $reffed (so probably just a json
                    # object with some keys; we'll add entries f.e one)
                    if "schema" not in param:
                        raise Exception(
                            "API endpoint group=%s path=%s method=%s param=%s"+
                            " has no valid parameter value." % (
                                group_name, path, method, param
                            )
                        )
                    if Units.prop(param, "schema/type") != "object":
                        raise Exception(
                            ("API endpoint group=%s path=%s method=%s defines a"+
                            " param with a schema which isn't an object. Array?")
                            % (group_name, path, method)
                        )
                    # loop top-level json keys
                    json_body = Units.prop(param, "schema/properties")
                    for key in json_body:
                        endpoint["req_params"].append({
                            "key": key,
                            "loc": "JSON body",
                            "type": json_body[key]["type"],
                            "desc": json_body[key]["description"]
                        })
                # endfor[param]
                # group params by location to ease templating
                endpoint["req_param_by_loc"] = {
                    #   path: [...], query: [...], body: [...]
                }
                for p in endpoint["req_params"]:
                    if p["loc"] not in endpoint["req_param_by_loc"]:
                        endpoint["req_param_by_loc"][p["loc"]] = []
                    endpoint["req_param_by_loc"][p["loc"]].append(p)

                # add example response if it has one
                res = single_api["responses"][200]  # get the 200 OK response
                endpoint["example"]["res"] = res.get("examples", {}).get(
                    "application/json", ""
                )
                # form example request if it has one. It "has one" if all params
                # have either "x-example" or a "schema" with an "example".
                params_missing_examples = [
                    p for p in single_api.get("parameters", []) if (
                        "x-example" not in p and
                        not Units.prop(p, "schema/example")
                    )
                ]
                if len(params_missing_examples) == 0:
                    path_template = api.get("basePath", "") + path
                    qps = {}
                    body = ""
                    for param in single_api.get("parameters", []):
                        if param["in"] == "path":
                            path_template = path_template.replace(
                                "{%s}" % param["name"], urllib.quote(
                                    param["x-example"]
                                )
                            )
                        elif param["in"] == "body":
                            body = param["schema"]["example"]
                        elif param["in"] == "query":
                            qps[param["name"]] = param["x-example"]
                    query_string = "" if len(qps) == 0 else "?"+urllib.urlencode(qps)
                    endpoint["example"]["req"] = "%s %s%s\n%s" % (
                        method.upper(), path_template, query_string, body
                    )
                else:
                    self.log(
                        "The following parameters are missing examples :( \n %s" %
                        [ p["name"] for p in params_missing_examples ]
                    )

                # add response params if this API has any.
                res_type = Units.prop(res, "schema/type")
                if res_type and res_type not in ["object", "array"]:
                    # response is a raw string or something like that
                    endpoint["res_tables"].append({
                        "title": None,
                        "rows": [{
                            "key": res["schema"].get("name", ""),
                            "type": res_type,
                            "desc": res.get("description", "")
                        }]
                    })
                elif res_type and Units.prop(res, "schema/properties"):  # object
                    res_tables = get_json_schema_object_fields(res["schema"])
                    for table in res_tables:
                        if "no-table" not in table:
                            endpoint["res_tables"].append(table)

                endpoints.append(endpoint)

        return {
            "base": api.get("basePath"),
            "group": group_name,
            "endpoints": endpoints,
        }

    def load_swagger_apis(self):
        path = "../api/client-server/v1"
        apis = {}
        for filename in os.listdir(path):
            if not filename.endswith(".yaml"):
                continue
            self.log("Reading swagger API: %s" % filename)
            with open(os.path.join(path, filename), "r") as f:
                # strip .yaml
                group_name = filename[:-5]
                api = yaml.load(f.read())
                api["__meta"] = self._load_swagger_meta(api, group_name)
                apis[group_name] = api
        return apis

    def load_common_event_fields(self):
        path = "../event-schemas/schema/v1/core"
        event_types = {}
        with open(path, "r") as f:
            core_json = json.loads(f.read())
            for event_type in core_json["definitions"]:
                if "event" not in event_type:
                    continue  # filter ImageInfo and co
                event_info = core_json["definitions"][event_type]
                table = {
                    "title": event_info["title"],
                    "desc": event_info["description"],
                    "rows": []
                }
                for prop in sorted(event_info["properties"]):
                    row = {
                        "key": prop,
                        "type": event_info["properties"][prop]["type"],
                        "desc": event_info["properties"][prop].get("description","")
                    }
                    table["rows"].append(row)
                event_types[event_type] = table
        return event_types

    def load_event_examples(self):
        path = "../event-schemas/examples/v1"
        examples = {}
        for filename in os.listdir(path):
            if not filename.startswith("m."):
                continue
            with open(os.path.join(path, filename), "r") as f:
                examples[filename] = json.loads(f.read())
                if filename == "m.room.message#m.text":
                    examples["m.room.message"] = examples[filename]
        return examples

    def load_event_schemas(self):
        path = "../event-schemas/schema/v1"
        schemata = {}

        for filename in os.listdir(path):
            if not filename.startswith("m."):
                continue
            self.log("Reading %s" % os.path.join(path, filename))
            with open(os.path.join(path, filename), "r") as f:
                json_schema = json.loads(f.read())
                schema = {
                    "typeof": None,
                    "typeof_info": "",
                    "type": None,
                    "title": None,
                    "desc": None,
                    "msgtype": None,
                    "content_fields": [
                    # {
                    #   title: "<title> key"
                    #   rows: [
                    #       { key: <key_name>, type: <string>,
                    #         desc: <desc>, required: <bool> }
                    #   ]
                    # }
                    ]
                }

                # add typeof
                base_defs = {
                    "core#/definitions/room_event": "Message Event",
                    "core#/definitions/state_event": "State Event"
                }
                if type(json_schema.get("allOf")) == list:
                    schema["typeof"] = base_defs.get(
                        json_schema["allOf"][0].get("$ref")
                    )
                elif json_schema.get("title"):
                    schema["typeof"] = json_schema["title"]

                # add type
                schema["type"] = Units.prop(
                    json_schema, "properties/type/enum"
                )[0]

                # add summary and desc
                schema["title"] = json_schema.get("title")
                schema["desc"] = json_schema.get("description", "")

                # walk the object for field info
                schema["content_fields"] = get_json_schema_object_fields(
                    Units.prop(json_schema, "properties/content")
                )

                # grab msgtype if it is the right kind of event
                msgtype = Units.prop(
                    json_schema, "properties/content/properties/msgtype/enum"
                )
                if msgtype:
                    schema["msgtype"] = msgtype[0]  # enum prop

                # link to msgtypes for m.room.message
                if schema["type"] == "m.room.message" and not msgtype:
                    schema["desc"] += (
                        " For more information on ``msgtypes``, see "+
                        "`m.room.message msgtypes`_."
                    )

                # Assign state key info if it has some
                if schema["typeof"] == "State Event":
                    skey_desc = Units.prop(
                        json_schema, "properties/state_key/description"
                    )
                    if not skey_desc:
                        raise Exception("Missing description for state_key")
                    schema["typeof_info"] = "``state_key``: %s" % skey_desc

                schemata[filename] = schema
        return schemata

    def load_spec_meta(self):
        path = "../CHANGELOG.rst"
        title_part = None
        version = None
        changelog_lines = []
        with open(path, "r") as f:
            prev_line = None
            for line in f.readlines():
                if line.strip().startswith(".. "):
                    continue  # comment
                if prev_line is None:
                    prev_line = line
                    continue
                if not title_part:
                    # find the title underline (at least 3 =)
                    if re.match("^[=]{3,}$", line.strip()):
                        title_part = prev_line
                        continue
                    prev_line = line
                else:  # have title, get body (stop on next title or EOF)
                    if re.match("^[=]{3,}$", line.strip()):
                        # we added the title in the previous iteration, pop it
                        # then bail out.
                        changelog_lines.pop()
                        break
                    changelog_lines.append(line)

        # parse out version from title
        for word in title_part.split():
            if re.match("^v[0-9\.]+$", word):
                version = word[1:]  # strip the 'v'

        self.log("Version: %s Title part: %s Changelog lines: %s" % (
            version, title_part, len(changelog_lines)
        ))
        if not version or len(changelog_lines) == 0:
            raise Exception("Failed to parse CHANGELOG.rst")

        return {
            "version": version,
            "changelog": "".join(changelog_lines)
        }

    def load_git_version(self):
        null = open(os.devnull, 'w')
        cwd = os.path.dirname(os.path.abspath(__file__))
        try:
            git_branch = subprocess.check_output(
                ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
                stderr=null,
                cwd=cwd,
            ).strip()
        except subprocess.CalledProcessError:
            git_branch = ""
        try:
            git_tag = subprocess.check_output(
                ['git', 'describe', '--exact-match'],
                stderr=null,
                cwd=cwd,
            ).strip()
            git_tag = "tag=" + git_tag
        except subprocess.CalledProcessError:
            git_tag = ""
        try:
            git_commit = subprocess.check_output(
                ['git', 'rev-parse', '--short', 'HEAD'],
                stderr=null,
                cwd=cwd,
            ).strip()
        except subprocess.CalledProcessError:
            git_commit = ""
        try:
            dirty_string = "-this_is_a_dirty_checkout"
            is_dirty = subprocess.check_output(
                ['git', 'describe', '--dirty=' + dirty_string, "--all"],
                stderr=null,
                cwd=cwd,
            ).strip().endswith(dirty_string)
            git_dirty = "dirty" if is_dirty else ""
        except subprocess.CalledProcessError:
            git_dirty = ""

        git_version = "Unknown"
        if git_branch or git_tag or git_commit or git_dirty:
            git_version = ",".join(
                s for s in
                (git_branch, git_tag, git_commit, git_dirty,)
                if s
            ).encode("ascii")
        return {
            "string": git_version,
            "revision": git_commit
        }
<commit_msg>Include patternProperties as normal properties (they basically are just patterns instead of keys; we could probably annotate this more nicely in the future)<commit_after>"""Contains all the units for the spec."""
from batesian.units import Units
import inspect
import json
import os
import re
import subprocess
import urllib
import yaml

def get_json_schema_object_fields(obj, enforce_title=False):
    # Algorithm:
    # f.e. property => add field info (if field is object then recurse)
    if obj.get("type") != "object":
        raise Exception(
            "get_json_schema_object_fields: Object %s isn't an object." % obj
        )
    if enforce_title and not obj.get("title"):
        raise Exception(
            "get_json_schema_object_fields: Nested object %s doesn't have a title." % obj
        )

    required_keys = obj.get("required")
    if not required_keys:
        required_keys = []

    fields = {
        "title": obj.get("title"),
        "rows": []
    }
    tables = [fields]

    props = obj.get("properties", obj.get("patternProperties"))
    parents = obj.get("allOf")
    if not props and not parents:
        raise Exception(
            "Object %s has no properties or parents." % obj
        )
    if not props:  # parents only
        return [{
            "title": obj["title"],
            "parent": parents[0]["$ref"],
            "no-table": True
        }]

    for key_name in sorted(props):
        value_type = None
        required = key_name in required_keys
        desc = props[key_name].get("description", "")

        if props[key_name]["type"] == "object":
            if props[key_name].get("additionalProperties"):
                # not "really" an object, just a KV store
                value_type = (
                    "{string: %s}" %
                    props[key_name]["additionalProperties"]["type"]
                )
            else:
                nested_object = get_json_schema_object_fields(
                    props[key_name],
                    enforce_title=True
                )
                value_type = "{%s}" % nested_object[0]["title"]

                if not nested_object[0].get("no-table"):
                    tables += nested_object
        elif props[key_name]["type"] == "array":
            # if the items of the array are objects then recurse
            if props[key_name]["items"]["type"] == "object":
                nested_object = get_json_schema_object_fields(
                    props[key_name]["items"],
                    enforce_title=True
                )
                value_type = "[%s]" % nested_object[0]["title"]
                tables += nested_object
            else:
                value_type = "[%s]" % props[key_name]["items"]["type"]
        else:
            value_type = props[key_name]["type"]
            if props[key_name].get("enum"):
                if len(props[key_name].get("enum")) > 1:
                    value_type = "enum"
                    desc += (
                        " One of: %s" % json.dumps(props[key_name]["enum"])
                    )
                else:
                    desc += (
                        " Must be '%s'." % props[key_name]["enum"][0]
                    )

        fields["rows"].append({
            "key": key_name,
            "type": value_type,
            "required": required,
            "desc": desc,
            "req_str": "**Required.** " if required else ""
        })
    return tables

class MatrixUnits(Units):

    def _load_swagger_meta(self, api, group_name):
        endpoints = []
        for path in api["paths"]:
            for method in api["paths"][path]:
                single_api = api["paths"][path][method]
                endpoint = {
                    "title": single_api.get("summary", ""),
                    "desc": single_api.get("description", single_api.get("summary", "")),
                    "method": method.upper(),
                    "path": api.get("basePath", "") + path,
                    "requires_auth": "security" in single_api,
                    "rate_limited": 429 in single_api.get("responses", {}),
                    "req_params": [],
                    "res_tables": [],
                    "example": {
                        "req": "",
                        "res": ""
                    }
                }
                self.log(".o.O.o. Endpoint: %s %s" % (method, path))
                for param in single_api.get("parameters", []):
                    # description
                    desc = param.get("description", "")
                    if param.get("required"):
                        desc = "**Required.** " + desc

                    # assign value expected for this param
                    val_type = param.get("type") # integer/string
                    refType = Units.prop(param, "schema/$ref/") # Error,Event
                    schemaFmt = Units.prop(param, "schema/format") # bytes e.g. uploads
                    if not val_type and refType:
                        val_type = refType  # TODO: Resolve to human-readable.
                    if not val_type and schemaFmt:
                        val_type = schemaFmt
                    if val_type:
                        endpoint["req_params"].append({
                            "key": param["name"],
                            "loc": param["in"],
                            "type": val_type,
                            "desc": desc
                        })
                        continue
                    # If we're here, either the param has no value or it is an
                    # object which we haven't $reffed (so probably just a json
                    # object with some keys; we'll add entries f.e one)
                    if "schema" not in param:
                        raise Exception(
                            "API endpoint group=%s path=%s method=%s param=%s"+
                            " has no valid parameter value." % (
                                group_name, path, method, param
                            )
                        )
                    if Units.prop(param, "schema/type") != "object":
                        raise Exception(
                            ("API endpoint group=%s path=%s method=%s defines a"+
                            " param with a schema which isn't an object. Array?")
                            % (group_name, path, method)
                        )
                    # loop top-level json keys
                    json_body = Units.prop(param, "schema/properties")
                    for key in json_body:
                        endpoint["req_params"].append({
                            "key": key,
                            "loc": "JSON body",
                            "type": json_body[key]["type"],
                            "desc": json_body[key]["description"]
                        })
                # endfor[param]
                # group params by location to ease templating
                endpoint["req_param_by_loc"] = {
                    #   path: [...], query: [...], body: [...]
                }
                for p in endpoint["req_params"]:
                    if p["loc"] not in endpoint["req_param_by_loc"]:
                        endpoint["req_param_by_loc"][p["loc"]] = []
                    endpoint["req_param_by_loc"][p["loc"]].append(p)

                # add example response if it has one
                res = single_api["responses"][200]  # get the 200 OK response
                endpoint["example"]["res"] = res.get("examples", {}).get(
                    "application/json", ""
                )
                # form example request if it has one. It "has one" if all params
                # have either "x-example" or a "schema" with an "example".
                params_missing_examples = [
                    p for p in single_api.get("parameters", []) if (
                        "x-example" not in p and
                        not Units.prop(p, "schema/example")
                    )
                ]
                if len(params_missing_examples) == 0:
                    path_template = api.get("basePath", "") + path
                    qps = {}
                    body = ""
                    for param in single_api.get("parameters", []):
                        if param["in"] == "path":
                            path_template = path_template.replace(
                                "{%s}" % param["name"], urllib.quote(
                                    param["x-example"]
                                )
                            )
                        elif param["in"] == "body":
                            body = param["schema"]["example"]
                        elif param["in"] == "query":
                            qps[param["name"]] = param["x-example"]
                    query_string = "" if len(qps) == 0 else "?"+urllib.urlencode(qps)
                    endpoint["example"]["req"] = "%s %s%s\n%s" % (
                        method.upper(), path_template, query_string, body
                    )
                else:
                    self.log(
                        "The following parameters are missing examples :( \n %s" %
                        [ p["name"] for p in params_missing_examples ]
                    )

                # add response params if this API has any.
                res_type = Units.prop(res, "schema/type")
                if res_type and res_type not in ["object", "array"]:
                    # response is a raw string or something like that
                    endpoint["res_tables"].append({
                        "title": None,
                        "rows": [{
                            "key": res["schema"].get("name", ""),
                            "type": res_type,
                            "desc": res.get("description", "")
                        }]
                    })
                elif res_type and Units.prop(res, "schema/properties"):  # object
                    res_tables = get_json_schema_object_fields(res["schema"])
                    for table in res_tables:
                        if "no-table" not in table:
                            endpoint["res_tables"].append(table)

                endpoints.append(endpoint)

        return {
            "base": api.get("basePath"),
            "group": group_name,
            "endpoints": endpoints,
        }

    def load_swagger_apis(self):
        path = "../api/client-server/v1"
        apis = {}
        for filename in os.listdir(path):
            if not filename.endswith(".yaml"):
                continue
            self.log("Reading swagger API: %s" % filename)
            with open(os.path.join(path, filename), "r") as f:
                # strip .yaml
                group_name = filename[:-5]
                api = yaml.load(f.read())
                api["__meta"] = self._load_swagger_meta(api, group_name)
                apis[group_name] = api
        return apis

    def load_common_event_fields(self):
        path = "../event-schemas/schema/v1/core"
        event_types = {}
        with open(path, "r") as f:
            core_json = json.loads(f.read())
            for event_type in core_json["definitions"]:
                if "event" not in event_type:
                    continue  # filter ImageInfo and co
                event_info = core_json["definitions"][event_type]
                table = {
                    "title": event_info["title"],
                    "desc": event_info["description"],
                    "rows": []
                }
                for prop in sorted(event_info["properties"]):
                    row = {
                        "key": prop,
                        "type": event_info["properties"][prop]["type"],
                        "desc": event_info["properties"][prop].get("description","")
                    }
                    table["rows"].append(row)
                event_types[event_type] = table
        return event_types

    def load_event_examples(self):
        path = "../event-schemas/examples/v1"
        examples = {}
        for filename in os.listdir(path):
            if not filename.startswith("m."):
                continue
            with open(os.path.join(path, filename), "r") as f:
                examples[filename] = json.loads(f.read())
                if filename == "m.room.message#m.text":
                    examples["m.room.message"] = examples[filename]
        return examples

    def load_event_schemas(self):
        path = "../event-schemas/schema/v1"
        schemata = {}

        for filename in os.listdir(path):
            if not filename.startswith("m."):
                continue
            self.log("Reading %s" % os.path.join(path, filename))
            with open(os.path.join(path, filename), "r") as f:
                json_schema = json.loads(f.read())
                schema = {
                    "typeof": None,
                    "typeof_info": "",
                    "type": None,
                    "title": None,
                    "desc": None,
                    "msgtype": None,
                    "content_fields": [
                    # {
                    #   title: "<title> key"
                    #   rows: [
                    #       { key: <key_name>, type: <string>,
                    #         desc: <desc>, required: <bool> }
                    #   ]
                    # }
                    ]
                }

                # add typeof
                base_defs = {
                    "core#/definitions/room_event": "Message Event",
                    "core#/definitions/state_event": "State Event"
                }
                if type(json_schema.get("allOf")) == list:
                    schema["typeof"] = base_defs.get(
                        json_schema["allOf"][0].get("$ref")
                    )
                elif json_schema.get("title"):
                    schema["typeof"] = json_schema["title"]

                # add type
                schema["type"] = Units.prop(
                    json_schema, "properties/type/enum"
                )[0]

                # add summary and desc
                schema["title"] = json_schema.get("title")
                schema["desc"] = json_schema.get("description", "")

                # walk the object for field info
                schema["content_fields"] = get_json_schema_object_fields(
                    Units.prop(json_schema, "properties/content")
                )

                # grab msgtype if it is the right kind of event
                msgtype = Units.prop(
                    json_schema, "properties/content/properties/msgtype/enum"
                )
                if msgtype:
                    schema["msgtype"] = msgtype[0]  # enum prop

                # link to msgtypes for m.room.message
                if schema["type"] == "m.room.message" and not msgtype:
                    schema["desc"] += (
                        " For more information on ``msgtypes``, see "+
                        "`m.room.message msgtypes`_."
                    )

                # Assign state key info if it has some
                if schema["typeof"] == "State Event":
                    skey_desc = Units.prop(
                        json_schema, "properties/state_key/description"
                    )
                    if not skey_desc:
                        raise Exception("Missing description for state_key")
                    schema["typeof_info"] = "``state_key``: %s" % skey_desc

                schemata[filename] = schema
        return schemata

    def load_spec_meta(self):
        path = "../CHANGELOG.rst"
        title_part = None
        version = None
        changelog_lines = []
        with open(path, "r") as f:
            prev_line = None
            for line in f.readlines():
                if line.strip().startswith(".. "):
                    continue  # comment
                if prev_line is None:
                    prev_line = line
                    continue
                if not title_part:
                    # find the title underline (at least 3 =)
                    if re.match("^[=]{3,}$", line.strip()):
                        title_part = prev_line
                        continue
                    prev_line = line
                else:  # have title, get body (stop on next title or EOF)
                    if re.match("^[=]{3,}$", line.strip()):
                        # we added the title in the previous iteration, pop it
                        # then bail out.
                        changelog_lines.pop()
                        break
                    changelog_lines.append(line)

        # parse out version from title
        for word in title_part.split():
            if re.match("^v[0-9\.]+$", word):
                version = word[1:]  # strip the 'v'

        self.log("Version: %s Title part: %s Changelog lines: %s" % (
            version, title_part, len(changelog_lines)
        ))
        if not version or len(changelog_lines) == 0:
            raise Exception("Failed to parse CHANGELOG.rst")

        return {
            "version": version,
            "changelog": "".join(changelog_lines)
        }

    def load_git_version(self):
        null = open(os.devnull, 'w')
        cwd = os.path.dirname(os.path.abspath(__file__))
        try:
            git_branch = subprocess.check_output(
                ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
                stderr=null,
                cwd=cwd,
            ).strip()
        except subprocess.CalledProcessError:
            git_branch = ""
        try:
            git_tag = subprocess.check_output(
                ['git', 'describe', '--exact-match'],
                stderr=null,
                cwd=cwd,
            ).strip()
            git_tag = "tag=" + git_tag
        except subprocess.CalledProcessError:
            git_tag = ""
        try:
            git_commit = subprocess.check_output(
                ['git', 'rev-parse', '--short', 'HEAD'],
                stderr=null,
                cwd=cwd,
            ).strip()
        except subprocess.CalledProcessError:
            git_commit = ""
        try:
            dirty_string = "-this_is_a_dirty_checkout"
            is_dirty = subprocess.check_output(
                ['git', 'describe', '--dirty=' + dirty_string, "--all"],
                stderr=null,
                cwd=cwd,
            ).strip().endswith(dirty_string)
            git_dirty = "dirty" if is_dirty else ""
        except subprocess.CalledProcessError:
            git_dirty = ""

        git_version = "Unknown"
        if git_branch or git_tag or git_commit or git_dirty:
            git_version = ",".join(
                s for s in
                (git_branch, git_tag, git_commit, git_dirty,)
                if s
            ).encode("ascii")
        return {
            "string": git_version,
            "revision": git_commit
        }
<|endoftext|>


<commit_before>from chainer import __version__ as chainer_version
from chainer import as_variable
from chainer import configuration
import chainer.functions as F

class EmbedIDFunction(
        F.connection.embed_id.EmbedIDFunction):

    def __init__(self, ignore_label=None, fixed_weight=False):
        super(EmbedIDFunction, self).__init__(ignore_label)
        self.fixed_weight = fixed_weight

    def backward(self, inputs, grad_outputs):
        if self.fixed_weight:
            """W does not require its gradient"""
            return None, None
        return super(EmbedIDFunction).backward(inputs, grad_outputs)

if int(chainer_version[0]) > 2:
    def embed_id(x, W, ignore_label=None, fixed_weight=False):
        if fixed_weight:
            return EmbedIDFunction(ignore_label, fixed_weight) \
                .apply((x, W))[0]
        else:
            return F.connection.embed_id.EmbedIDFunction(ignore_label) \
                .apply((x, W))[0]
else:
    def embed_id(x, W, ignore_label=None, fixed_weight=False):
        if fixed_weight:
            return EmbedIDFunction(ignore_label, fixed_weight)(x, W)
        else:
            return F.connection.embed_id.EmbedIDFunction(ignore_label)(x, W)

def dropout(x, ratio=.5):
    if configuration.config.train and ratio > .0:
        return F.noise.dropout.Dropout(ratio).apply((x,))[0]
    return as_variable(x)
<commit_msg>implement roll function.<commit_after>from chainer import __version__ as chainer_version
from chainer import as_variable
from chainer import configuration
from chainer import cuda
from chainer import function_node
import chainer.functions as F
# from chainer.utils import type_check

class EmbedIDFunction(
        F.connection.embed_id.EmbedIDFunction):

    def __init__(self, ignore_label=None, fixed_weight=False):
        super(EmbedIDFunction, self).__init__(ignore_label)
        self.fixed_weight = fixed_weight

    def backward(self, inputs, grad_outputs):
        if self.fixed_weight:
            """W does not require its gradient"""
            return None, None
        return super(EmbedIDFunction).backward(inputs, grad_outputs)

if int(chainer_version[0]) > 2:
    def embed_id(x, W, ignore_label=None, fixed_weight=False):
        if fixed_weight:
            return EmbedIDFunction(ignore_label, fixed_weight) \
                .apply((x, W))[0]
        else:
            return F.connection.embed_id.EmbedIDFunction(ignore_label) \
                .apply((x, W))[0]
else:
    def embed_id(x, W, ignore_label=None, fixed_weight=False):
        if fixed_weight:
            return EmbedIDFunction(ignore_label, fixed_weight)(x, W)
        else:
            return F.connection.embed_id.EmbedIDFunction(ignore_label)(x, W)

def dropout(x, ratio=.5):
    if configuration.config.train and ratio > .0:
        return F.noise.dropout.Dropout(ratio).apply((x,))[0]
    return as_variable(x)

class Roll(function_node.FunctionNode):

    """Roll array elements along a given axis."""

    def __init__(self, shift, axis=None):
        # @TODO: type check
        self.shift = shift
        self.axis = axis

    def check_type_forward(self, in_types):
        # @TODO: type check
        pass

    def forward(self, inputs):
        self.retain_inputs(())
        xp = cuda.get_array_module(*inputs)
        return xp.roll(inputs[0], self.shift, self.axis),

    def backward(self, indexes, gy):
        if isinstance(self.shift, (list, tuple)):
            shift = [-e for e in self.shift]
        else:
            shift = -self.shift

        return Roll(shift, self.axis).apply(gy)

def roll(x, shift, axis=None):
    return Roll(shift, axis).apply((x,))[0]
<|endoftext|>


<commit_before>from django.db import models
from django.db.models import signals
# from validatedfile.fields import ValidatedFileField
from django.contrib.auth.models import User
from django.utils.text import slugify

# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver

import sys, os, time, yaml, errno
from uuid import uuid1, uuid4

from taggit.managers import TaggableManager
from host.settings import MEDIA_ROOT

from validatedfile.fields import ValidatedFileField
validatedfile_rules = [(
    [ValidatedFileField],
    [],
    {
        "content_types": ["content_types", {"default": []}],
        "max_upload_size": ["max_upload_size", {"default": 0}],
        "mime_lookup_length": ["mime_lookup_length", {"default": 4096}],
    }
)]

from south.modelsinspector import add_introspection_rules
add_introspection_rules(validatedfile_rules, ["^validatedfile\.fields\.ValidatedFileField"])

def mkdir_p(path, perms=None):
    try:
        if perms:
            os.makedirs(path, perms)
        else:
            os.makedirs(path)
    except OSError as exc: # Python >2.5
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else: raise

def path_for_game_file():
    def path_generator(instance, filename):
        ext = filename.split('.')[-1]
        basename = filename.split('.')[0]
        # set path.
        if instance.game_group:
            library_dir = slugify(instance.game_group.name) if instance.game_group else "ungrouped"
            path = "uploads/{library_dir}/games/".format(library_dir=library_dir)
        else:
            path = "uploads/unclaimed/" # dump problematic files where we can see them.

        # set permissions on path.
        mkdir_p(MEDIA_ROOT + path, 0o775)

        # since this is going into the group directory, along with Library files, make sure the filename is unique.
        filename = "{0}_{1}.{2}".format(basename, int(time.time()), ext)

        # return the whole path to the file
        return os.path.join(path, filename)
    return path_generator

class Game(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    game_group = models.ForeignKey("GameGroup")
    game_spec = ValidatedFileField(
                        null = True,
                        blank = True,
                        upload_to = path_for_game_file(),
                        max_length = 250,
                        max_upload_size = 204800, # 200KB
                        content_types = ['application/x-yaml','text/yaml','text/plain','text/plain; charset=us-ascii', 'text/plain; charset=utf-8'])
    tags = TaggableManager(blank=True)

    def slug(self):
        return slugify(self.name)

    def __unicode__(self):
        return self.name

    def save(self, *args, **kwargs):
        # create Round objects as nec. set a 'parsed' flag to true when done.
        super(Game, self).save(*args, **kwargs)

        game_yaml_file_path = MEDIA_ROOT + str(self.game_spec)
        game_yaml_file = None
        try:
            game_yaml_file = open(game_yaml_file_path, 'r')
            game_yaml = yaml.load(game_yaml_file)

            # for a first stab at game YAML validation,
            # we try to create a Round object for each Round in the uploaded file.
            # if this fails, we pass back a validation error.
            rounds = game_yaml.get('rounds')
            for i, round in enumerate(rounds):
                rnd, created = Round.objects.get_or_create(game=self, nbr=(i+1), points=value)
                if created:
                    rnd.save()

            # if we've gotten this far, note it in the db.
            self.parsed = True
            super(Game, self).save(*args, **kwargs)

        except:
            e = sys.exc_info()[0]
            print( "Error: %s" % e )

        finally:
            if (game_yaml_file):
                game_yaml_file.close()

@receiver(pre_delete, sender=Game)
def game_delete(sender, instance, **kwargs):
    # Pass false so FileField doesn't save the model.
    instance.game_spec.delete(False)

class Round(models.Model):
    game = models.ForeignKey(Game)
    nbr = models.IntegerField()
    correct_answer = models.CharField(max_length=32)
    points = models.IntegerField()

class Struct:
    def __init__(self, **entries):
        self.__dict__.update(entries)

class GameReport(models.Model):
    payload = models.TextField(blank=True,null=True)
    student = models.ForeignKey(User, unique=False)
    game = models.ForeignKey(Game, unique=False)
    created_on = models.DateTimeField(auto_now_add=True)

    def _decode_payload(self):
        args = json.loads(self.payload)
        s = Struct(**args)
        return s
    decoded_payload = property(_decode_payload)

class GameGroup(models.Model):
    name = models.CharField(max_length=255)
    games = models.ManyToManyField(Game, blank=True, null=True)
    users = models.ManyToManyField(User, blank=True, null=True, through="Membership")
    library_files = models.ManyToManyField("LibraryFile", blank=True, null=True)

    def slug(self):
        return slugify(self.name)

    def __unicode__(self):
        return self.name

class Role(models.Model):
    name = models.CharField(max_length=64)

    def __unicode__(self):
        return self.name

class Membership(models.Model):
    user = models.ForeignKey(User)
    game_group = models.ForeignKey(GameGroup)
    role = models.ForeignKey(Role)

    def __unicode__(self):
        group_name = self.game_group.name
        role_name = self.role.name
        try:
            return "{}:{}".format(group_name, role_name)
        except:
            return "unknown"

    def games(self):
        return self.game_group.games

def path_for_media_type():
    def path_generator(instance, filename):
        ext = filename.split('.')[-1]
        # set path.
        if instance.game_group:
            library_dir = slugify(instance.game_group.name) if instance.game_group else "ungrouped"
            path = "uploads/{library_dir}/custom_{media_type}/".format(library_dir=library_dir, media_type=instance.media_type)
        else:
            path = "uploads/unclaimed/" # dump problematic files where we can see them.

        # return the whole path to the file
        return os.path.join(path, filename)
    return path_generator

class LibraryFile(models.Model):
    MEDIA_CHOICES = (
        ('js', 'js'),
    )
    media_type = models.CharField(max_length=64, choices=MEDIA_CHOICES)
    description = models.TextField(blank=True, null=True)
    game_group = models.ForeignKey(GameGroup)
    file = models.FileField(
                        null = True,
                        blank = True,
                        upload_to = path_for_media_type())

    def __unicode__(self):
        return self.file.name

@receiver(pre_delete, sender=LibraryFile)
def library_file_delete(sender, instance, **kwargs):
    # Pass false so FileField doesn't save the model.
    instance.file.delete(False)
<commit_msg>fix bug in Membership model __unicode__(), when group or role is undefined.<commit_after>from django.db import models
from django.db.models import signals
# from validatedfile.fields import ValidatedFileField
from django.contrib.auth.models import User
from django.utils.text import slugify

# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver

import sys, os, time, yaml, errno
from uuid import uuid1, uuid4

from taggit.managers import TaggableManager
from host.settings import MEDIA_ROOT

from validatedfile.fields import ValidatedFileField
validatedfile_rules = [(
    [ValidatedFileField],
    [],
    {
        "content_types": ["content_types", {"default": []}],
        "max_upload_size": ["max_upload_size", {"default": 0}],
        "mime_lookup_length": ["mime_lookup_length", {"default": 4096}],
    }
)]

from south.modelsinspector import add_introspection_rules
add_introspection_rules(validatedfile_rules, ["^validatedfile\.fields\.ValidatedFileField"])

def mkdir_p(path, perms=None):
    try:
        if perms:
            os.makedirs(path, perms)
        else:
            os.makedirs(path)
    except OSError as exc: # Python >2.5
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else: raise

def path_for_game_file():
    def path_generator(instance, filename):
        ext = filename.split('.')[-1]
        basename = filename.split('.')[0]
        # set path.
        if instance.game_group:
            library_dir = slugify(instance.game_group.name) if instance.game_group else "ungrouped"
            path = "uploads/{library_dir}/games/".format(library_dir=library_dir)
        else:
            path = "uploads/unclaimed/" # dump problematic files where we can see them.

        # set permissions on path.
        mkdir_p(MEDIA_ROOT + path, 0o775)

        # since this is going into the group directory, along with Library files, make sure the filename is unique.
        filename = "{0}_{1}.{2}".format(basename, int(time.time()), ext)

        # return the whole path to the file
        return os.path.join(path, filename)
    return path_generator

class Game(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    game_group = models.ForeignKey("GameGroup")
    game_spec = ValidatedFileField(
                        null = True,
                        blank = True,
                        upload_to = path_for_game_file(),
                        max_length = 250,
                        max_upload_size = 204800, # 200KB
                        content_types = ['application/x-yaml','text/yaml','text/plain','text/plain; charset=us-ascii', 'text/plain; charset=utf-8'])
    tags = TaggableManager(blank=True)

    def slug(self):
        return slugify(self.name)

    def __unicode__(self):
        return self.name

    def save(self, *args, **kwargs):
        # create Round objects as nec. set a 'parsed' flag to true when done.
        super(Game, self).save(*args, **kwargs)

        game_yaml_file_path = MEDIA_ROOT + str(self.game_spec)
        game_yaml_file = None
        try:
            game_yaml_file = open(game_yaml_file_path, 'r')
            game_yaml = yaml.load(game_yaml_file)

            # for a first stab at game YAML validation,
            # we try to create a Round object for each Round in the uploaded file.
            # if this fails, we pass back a validation error.
            rounds = game_yaml.get('rounds')
            for i, round in enumerate(rounds):
                rnd, created = Round.objects.get_or_create(game=self, nbr=(i+1), points=value)
                if created:
                    rnd.save()

            # if we've gotten this far, note it in the db.
            self.parsed = True
            super(Game, self).save(*args, **kwargs)

        except:
            e = sys.exc_info()[0]
            print( "Error: %s" % e )

        finally:
            if (game_yaml_file):
                game_yaml_file.close()

@receiver(pre_delete, sender=Game)
def game_delete(sender, instance, **kwargs):
    # Pass false so FileField doesn't save the model.
    instance.game_spec.delete(False)

class Round(models.Model):
    game = models.ForeignKey(Game)
    nbr = models.IntegerField()
    correct_answer = models.CharField(max_length=32)
    points = models.IntegerField()

class Struct:
    def __init__(self, **entries):
        self.__dict__.update(entries)

class GameReport(models.Model):
    payload = models.TextField(blank=True,null=True)
    student = models.ForeignKey(User, unique=False)
    game = models.ForeignKey(Game, unique=False)
    created_on = models.DateTimeField(auto_now_add=True)

    def _decode_payload(self):
        args = json.loads(self.payload)
        s = Struct(**args)
        return s
    decoded_payload = property(_decode_payload)

class GameGroup(models.Model):
    name = models.CharField(max_length=255)
    games = models.ManyToManyField(Game, blank=True, null=True)
    users = models.ManyToManyField(User, blank=True, null=True, through="Membership")
    library_files = models.ManyToManyField("LibraryFile", blank=True, null=True)

    def slug(self):
        return slugify(self.name)

    def __unicode__(self):
        return self.name

class Role(models.Model):
    name = models.CharField(max_length=64)

    def __unicode__(self):
        return self.name

class Membership(models.Model):
    user = models.ForeignKey(User)
    game_group = models.ForeignKey(GameGroup)
    role = models.ForeignKey(Role)

    def __unicode__(self):
        group_name = self.game_group.name
        role_name = self.role.name
        try:
            return "{0}:{1}".format(group_name, role_name)
        except:
            return "unknown"

    def games(self):
        return self.game_group.games

def path_for_media_type():
    def path_generator(instance, filename):
        ext = filename.split('.')[-1]
        # set path.
        if instance.game_group:
            library_dir = slugify(instance.game_group.name) if instance.game_group else "ungrouped"
            path = "uploads/{library_dir}/custom_{media_type}/".format(library_dir=library_dir, media_type=instance.media_type)
        else:
            path = "uploads/unclaimed/" # dump problematic files where we can see them.

        # return the whole path to the file
        return os.path.join(path, filename)
    return path_generator

class LibraryFile(models.Model):
    MEDIA_CHOICES = (
        ('js', 'js'),
    )
    media_type = models.CharField(max_length=64, choices=MEDIA_CHOICES)
    description = models.TextField(blank=True, null=True)
    game_group = models.ForeignKey(GameGroup)
    file = models.FileField(
                        null = True,
                        blank = True,
                        upload_to = path_for_media_type())

    def __unicode__(self):
        return self.file.name

@receiver(pre_delete, sender=LibraryFile)
def library_file_delete(sender, instance, **kwargs):
    # Pass false so FileField doesn't save the model.
    instance.file.delete(False)
<|endoftext|>


<commit_before>import MDP
import numpy as np
import math

class Planner(object):

    """
    Planner class handles the MDP and has additional functions to use the MDPs policy.
    Planner contains an Markov Decision Process object (MDP), a softmax parameter (tau), a future discount parameter (gamma), a value iteration convergence parameter (epsilon)
    and the probability that agents may die (deathprob)
    """

    def __init__(self, diagonal=True, MDP=MDP.MDP(), deathprob=0, tau=0.001, gamma=0.9999, epsilon=0.00001):
        """
        Build a Planner.

        All arguments are option. If no arguments are provided the object contains an empty MDP with prespecified parameters.

        ARGUMENTS:
        diagonal[boolean] determines if agent can travel diagonally
        MDP         MDP object
        deathprob   0<=deathprob<1 is the probability of targets disappearing (Using the last two positions or targets in Map class).
        tau         softmax parameter. Set by default to 0.001
        gamma       future discount parameter. Set by default to 0.9999
        epsilon     value iteration convergence limit. Set by default to 0.00001
        """
        self.MDP = MDP
        self.tau = tau
        self.gamma = gamma
        self.epsilon = epsilon
        self.diagonal = diagonal
        # Probability that a target will disappear.
        self.deathprob = deathprob

    def Integrate(self, Agent, Map, ExitType="OneLocation", exitstate=0):
        """
        Integrate builds the MDP using a Map and an Agent object.
        This function calls Planner methods that help build the MDP.
        It doesn't return anything. Instead. the object's MDP will change.
        ARGUMENTS:
        Agent     Agent object
        Map       Map or Map object
        """
        found = 0
        if ExitType == "OneLocation":
            found = 1
            # Agent can only exit from the map in exitstate
            self.MDP = MDP.MDP(range(self.GetDeepStateSize(Map)), Map.A, self.BuildDeepT(
                Agent, Map, [exitstate]), self.BuildDeepR(Agent, Map), self.diagonal, self.gamma)
        if ExitType == "Border":
            found = 1
            # Exit from any map border:
            self.MDP = MDP.MDP(range(self.GetDeepStateSize(Map)), Map.A, self.BuildDeepT(
                Agent, Map), self.BuildDeepR(Agent, Map), self.diagonal, self.gamma)
        if ExitType == "North":
            found = 1
            # Exit from any top location:
            self.MDP = MDP.MDP(range(self.GetDeepStateSize(Map)), Map.A, self.BuildDeepT(
                Agent, Map, False, [range(Map.x)]), self.BuildDeepR(Agent, Map), self.diagonal, self.gamma)
        if found == 0:
            print "ERROR: ExitType does not exit."

    def ComputePolicy(self, Softmax=True):
        """
        ComputePolicy runs value iteration on it's MDP and then builds an optimal policy.
        ARGUMENTS:
        Softmax.   If set to True (Default) the policy will be softmaxed (Using the Planner.tau parameter).
        """
        self.MDP.ValueIteration(self.epsilon)  # Do value iteration on MDP.
        self.MDP.BuildPolicy(self.tau, Softmax)  # Get softmax probabilities.

    def EvalRoute(self, StartingPoint, ActionSequence):
        """
        EvalRoute(StartingPoint,ActionSequence)
        returns the probability of the agent taking the ActionSequence from StartingPoint.
        If there are agents who need help and may die this function is optimistic and assumes no one dies (thus never sending the agent to deathstates)
        """
        # This function retrieves the optimistic state sequence where no
        # objects disappear.
        StateSequence = self.MDP.GetStates(StartingPoint, ActionSequence)
        p = 1  # probability taking the action sequence
        for i in range(len(ActionSequence)):
            p *= (self.MDP.policy[ActionSequence[i], StateSequence[i]])
        return p

    def ExitState(self, State):
        """
        ExitStates(State)
        returns true is State is the Map's death state
        """
        return (State == (self.GetDeepStateSize() - 1))

    def SimulatePathFor(self, StartingPoint, steps):
        """
        SimulatePathFor(StartingPoint,steps)
        simulates an agent's actions for the specified number of steps starting from the starting point
        """
        Actions = [-1] * steps
        State = StartingPoint
        for i in range(steps):
            [State, Actions[i]] = self.MDP.Run(State)
        return Actions

    def SimulatePathUntil(self, StartingPoint, StopStates, Limit, Simple=False):
        """
        Simulate path from StartingPoint until agent reaches a state in the StopStates list.
        Simulation ends after the agent has taken more steps than specified on Limit.
        """
        iterations = 0
        Actions = []
        StateSequence = [StartingPoint]
        State = StartingPoint
        while State not in StopStates:
            [State, NewAct] = self.MDP.Run(State, Simple)
            Actions.append(NewAct)
            StateSequence.append(State)
            iterations += 1
            if (iterations > Limit):
                return [Actions, StateSequence]
        return [Actions, StateSequence]

    def GetDeepStateSize(self, Map):
        """
        GetDeepStateSize(Map) returns the final state size the Planner will use given a Map.
        This function should only be used internally. To check a Map's size use the Map's methods.
        """
        # The map is multiplied into four maps
        # Map 0: Both objects
        # Map 1: Object 1 missing
        # Map 2: Object 2 missing
        # Map 3: No objects
        # Last 2 states are the death state and the exit state.
        return Map.GetWorldSize() * 4 + 2

    def BuildDeepR(self, Agent, Map):
        """
        Builds the MDPs reward functions.
        ARGUMENTS:

        Agent an Agent object.
        Map   a Map of Map object.
        """
        # Note that this function also builds the diagonal rewards,
        # MDP object will discard them if diagonal is off.

        # Build a reward function over the expanded transition matrix.
        # Reward function for actions when you move horizontally or diagonally.
        Rstraight = [0] * self.GetDeepStateSize(Map)
        # Code logic is similar to Planner.BuildRewardFunction.
        # First reward gets inserted in Levels 0 and 2. Second reward gets inserted in Levels 0 and 2.
        # Add costs first
        ActiveStateCosts = [-Agent.costs[Map.StateTypes[i]]
                            for i in range(len(Map.S))] * 4
        ActiveStateCosts.append(0)  # Death state has no cost yet.
        ActiveStateCosts.append(0)  # Exit state has no cost
        Rstraight = ActiveStateCosts
        # Create the diagonal reward matrix
        Rdiagonal = [i * math.sqrt(2) for i in Rstraight]
        # Add rewards
        RewardIndex = 0
        RewardObject = 0  # What object is this reward for?
        for i in range(len(Map.Locations)):
            if Map.Locations[i] != []:
                for j in Map.Locations[i]:
                    Rstraight[j] = Agent.rewards[RewardIndex]
                    Rdiagonal[j] = Agent.rewards[RewardIndex]
                    Rstraight[j + Map.GetWorldSize() * (2 - RewardObject)] = Agent.rewards[
                        RewardIndex]
                    Rdiagonal[j + Map.GetWorldSize() * (2 - RewardObject)] = Agent.rewards[
                        RewardIndex]
                    if i > 1:  # If this an agent-type reward
                        # If Object A is an agent then make the reward for
                        # saving A a cost on all states while A needs help.
                        if (RewardObject == 0):
                            Rstraight[0:Map.GetWorldSize()] = [
                                Rstraight[pos] - Agent.rewards[RewardIndex] for pos in range(Map.GetWorldSize())]
                            Rstraight[(Map.GetWorldSize() * 2):(Map.GetWorldSize() * 3)] = [Rstraight[pos] - Agent.rewards[
                                RewardIndex] for pos in range(Map.GetWorldSize() * 2, Map.GetWorldSize() * 3)]
                            Rdiagonal[0:Map.GetWorldSize()] = [
                                Rdiagonal[pos] - Agent.rewards[RewardIndex] for pos in range(Map.GetWorldSize())]
                            Rdiagonal[(Map.GetWorldSize() * 2):(Map.GetWorldSize() * 3)] = [Rdiagonal[pos] - Agent.rewards[
                                RewardIndex] for pos in range(Map.GetWorldSize() * 2, Map.GetWorldSize() * 3)]
                        else:
                            Rstraight[0:Map.GetWorldSize()] = [
                                Rstraight[pos] - Agent.rewards[RewardIndex] for pos in range(Map.GetWorldSize())]
                            Rstraight[(Map.GetWorldSize()):(Map.GetWorldSize() * 2)] = [Rstraight[pos] - Agent.rewards[
                                RewardIndex] for pos in range(Map.GetWorldSize(), Map.GetWorldSize() * 2)]
                            Rdiagonal[0:Map.GetWorldSize()] = [
                                Rdiagonal[pos] - Agent.rewards[RewardIndex] for pos in range(Map.GetWorldSize())]
                            Rdiagonal[(Map.GetWorldSize()):(Map.GetWorldSize() * 2)] = [Rdiagonal[pos] - Agent.rewards[
                                RewardIndex] for pos in range(Map.GetWorldSize(), Map.GetWorldSize() * 2)]

                    RewardObject += 1
                RewardIndex += 1
        # GIVE A SMALL REWARD FOR GETTING HOME. So MPD can converge in regions
        # where all other rewards were picked up.
        Rstraight[0] = 10
        Rdiagonal[0] = 10
        Rstraight[Map.GetWorldSize()] = 10
        Rdiagonal[Map.GetWorldSize()] = 10
        Rstraight[Map.GetWorldSize() * 2] = 10
        Rdiagonal[Map.GetWorldSize() * 2] = 10
        Rstraight[Map.GetWorldSize() * 3] = 10
        Rdiagonal[Map.GetWorldSize() * 3] = 10
        return [Rstraight, Rdiagonal]

    def BuildDeepT(self, Agent, Map, ExitStates=None):
        """
        Build an deep transition matrix using map information.
        This function takes a Map.T transition matrix and expands it
        so the agent can only pick up each object once.
        ExitStates list is used to build exits.
        """
        if (sum(map(len, Map.Locations)) != 2):
            print "Warning: Didn't find exactly two objects. Don't use Planner.BuildDeepT unless you have exactly two objects."
        WorldSize = Map.GetWorldSize()
        MapExitState = WorldSize * 4 + 1  # Position of exit state.
        # Create big world with death and exit states.
        T = np.zeros(
            (WorldSize * 4 + 2, Map.NumberOfActions(), WorldSize * 4 + 2))
        # Insert the Map's transition matrix into the big matrix.
        T[WorldSize * 0:WorldSize * 1, :, WorldSize * 0:WorldSize * 1] = Map.T
        T[WorldSize * 1:WorldSize * 2, :, WorldSize * 1:WorldSize * 2] = Map.T
        T[WorldSize * 2:WorldSize * 3, :, WorldSize * 2:WorldSize * 3] = Map.T
        T[WorldSize * 3:WorldSize * 4, :, WorldSize * 3:WorldSize * 4] = Map.T
        if (ExitStates == None):
            print "Warning in Planner.BuilDeepT(). No exit states in Map!"
        for exitstate in ExitStates:
            # Check if ExitState is top, left, right, or bottom.
            # Left
            if (exitstate in [loopvar * Map.x for loopvar in range(Map.y)]):
                for DepthLevel in range(4):
                    T[exitstate + WorldSize * DepthLevel, 0,
                        :] = np.zeros((T[exitstate + WorldSize * DepthLevel, 0, :].shape))
                    T[exitstate + WorldSize * DepthLevel, 0, MapExitState] = 1
            # Right
            if (exitstate in [loopvar * Map.x + Map.x - 1 for loopvar in range(Map.y)]):
                for DepthLevel in range(4):
                    T[exitstate + WorldSize * DepthLevel, 1,
                        :] = np.zeros((T[exitstate + WorldSize * DepthLevel, 1, :].shape))
                    T[exitstate + WorldSize * DepthLevel, 1, MapExitState] = 1
            if (exitstate in range(Map.x)):  # Top
                for DepthLevel in range(4):
                    T[exitstate + WorldSize * DepthLevel, 2,
                        :] = np.zeros((T[exitstate + WorldSize * DepthLevel, 2, :].shape))
                    T[exitstate + WorldSize * DepthLevel, 2, MapExitState] = 1
            if (exitstate in range(WorldSize - Map.x, WorldSize)):  # Bottom
                for DepthLevel in range(4):
                    T[exitstate + WorldSize * DepthLevel, 3,
                        :] = np.zeros((T[exitstate + WorldSize * DepthLevel, 3, :].shape))
                    T[exitstate + WorldSize * DepthLevel, 3, MapExitState] = 1
            T[exitstate, :, :] = np.zeros((T[exitstate, :, :].shape))
            T[exitstate + WorldSize, :,
                :] = np.zeros((T[exitstate, :, :].shape))
            T[exitstate + WorldSize * 2, :,
                :] = np.zeros((T[exitstate, :, :].shape))
            T[exitstate + WorldSize * 3, :,
                :] = np.zeros((T[exitstate, :, :].shape))
            T[exitstate, :, MapExitState] = [
                1] * len(T[exitstate, :, MapExitState])
            T[exitstate + WorldSize, :, MapExitState] = [1] * \
                len(T[exitstate, :, MapExitState])
            T[exitstate + WorldSize * 2, :, MapExitState] = [1] * \
                len(T[exitstate, :, MapExitState])
            T[exitstate + WorldSize * 3, :, MapExitState] = [1] * \
                len(T[exitstate, :, MapExitState])
        # Add how picking up objects takes you to different levels.
        # Wit this construction, the agent needs to take one action to pick up
        # the object that will not change it's location.
        Target = 1  # Which target are you inserting?
        for i in range(len(Map.Locations)):
            if Map.Locations[i] != []:
                for j in Map.Locations[i]:
                    if Target == 1:
                        # First delete all previous moves
                        T[j, :, :] = np.zeros((T[j, :, :].shape))
                        # Level 0 goes to level 1
                        T[j, :, j + WorldSize] = [1] * \
                            len(T[j, :, j + WorldSize])
                        # And level 2 takes you to level 3
                        T[j + WorldSize * 2, :,
                            :] = np.zeros((T[j + WorldSize * 2, :, :].shape))
                        T[j + WorldSize * 2, :, j + WorldSize * 3] = [1] * \
                            len(T[j + WorldSize * 2, :, j + WorldSize * 3]
                                )  # Level 2 goes to level 3
                        Target += 1
                        # If the target corresponds to an agent who needs help
                        if (i >= 2):
                            # Take all actions on that level and add the chance of the agent dying.
                            # Loop over subarrays and replace values.
                            for subloop1 in range(WorldSize):
                                for subloop2 in range(WorldSize * 4):
                                    for subloop3 in range(Map.NumberOfActions()):
                                        if(T[subloop1, subloop3, subloop2] == 1):
                                            T[subloop1, subloop3, subloop2] = 1 - \
                                                self.deathprob
                                        if(T[subloop1 + WorldSize * 2, subloop3, subloop2] == 1):
                                            T[subloop1 + WorldSize * 2, subloop3,
                                                subloop2] = 1 - self.deathprob
                            # Add chance of moving to death state in all.
                            T[WorldSize * 0:WorldSize * 1, :, WorldSize * 4] = np.ones(
                                (T[WorldSize * 0:WorldSize * 1, :, WorldSize * 4].shape)) * self.deathprob
                            T[WorldSize * 2:WorldSize * 3, :, WorldSize * 4] = np.ones(
                                (T[WorldSize * 2:WorldSize * 3, :, WorldSize * 4].shape)) * self.deathprob
                    else:
                        # First delete all previous moves
                        T[j, :, :] = np.zeros((T[j, :, :].shape))
                        # Level 0 goes to level 2
                        T[j, :, j + WorldSize * 2] = [1] * \
                            len(T[j, :, j + WorldSize * 2])
                        # And level 1 takes you to level 3
                        T[j + WorldSize, :,
                            :] = np.zeros((T[j + WorldSize, :, :].shape))
                        # Level 1 goes to level 3
                        T[j + WorldSize, :, j + WorldSize * 3] = [1] * \
                            len(T[j + WorldSize, :, j + WorldSize * 3])
                        # If the target corresponds to an agent who needs help
                        if (i >= 2):
                            # Take all actions on that level and add the change of the agent dying.
                            # Loop over subarrays and replace values.
                            for subloop1 in range(WorldSize):
                                for subloop2 in range(WorldSize * 4):
                                    for subloop3 in range(Map.NumberOfActions()):
                                        if(T[subloop1, subloop3, subloop2] == 1):
                                            T[subloop1, subloop3, subloop2] = 1 - \
                                                self.deathprob
                                        if(T[subloop1 + WorldSize, subloop3, subloop2] == 1):
                                            T[subloop1 + WorldSize, subloop3,
                                                subloop2] = 1 - self.deathprob
                            # Add chance of moving to death state in all.
                            T[WorldSize * 0:WorldSize * 1, :, WorldSize * 4] = np.ones(
                                (T[WorldSize * 0:WorldSize * 1, :, WorldSize * 4].shape)) * self.deathprob
                            T[WorldSize * 1:WorldSize * 2, :, WorldSize * 4] = np.ones(
                                (T[WorldSize * 1:WorldSize * 2, :, WorldSize * 4].shape)) * self.deathprob
        # Have death state send you to exit.
        T[WorldSize * 4, :, WorldSize * 4 +
            1] = np.ones((T[WorldSize * 4, :, WorldSize * 4 + 1].shape))
        # Make exit state absorbing.
        T[WorldSize * 4 + 1, :, WorldSize * 4 +
            1] = np.ones((T[WorldSize * 4 + 1, :, WorldSize * 4 + 1].shape))
        return T

    def UpdateDeathProb(self, deathprob):
        self.deathprob = deathprob

    def UpdateSoftMax(self, tau):
        self.tau = tau

    def UpdateConvergence(self, epsilon):
        self.epsilon = epsilon

    def UpdateDiscount(self, gamma):
        self.gamma = gamma

    def Display(self, Full=False):
        # Print class properties
        if Full:
            for (property, value) in vars(self).iteritems():
                print property, ': ', value
        else:
            for (property, value) in vars(self).iteritems():
                print property
<commit_msg>Breaking code<commit_after>import MDP
import numpy as np
import math

class Planner(object):

    """
    Planner class handles the MDP and has additional functions to use the MDPs policy.
    Planner contains an Markov Decision Process object (MDP), a softmax parameter (tau), a future discount parameter (gamma), a value iteration convergence parameter (epsilon)
    and the probability that agents may die (deathprob)
    """

    def __init__(self, diagonal=True, MDP=MDP.MDP(), distmat=None, tau=0.001, gamma=0.9999, epsilon=0.00001):
        """
        Build a Planner.

        All arguments are option. If no arguments are provided the object contains an empty MDP with prespecified parameters.

        Args:
        diagonal[boolean] determines if agent can travel diagonally
        MDP         MDP object
        distmat [matrix] Matrix where distmat[i,j] is the minimum cost for moving from state i to state j.
                    This matrix is not exhaustive on the state-space. Instead, it selects the starting and end state as well as states with rewards.
        tau         softmax parameter. Set by default to 0.001
        gamma       future discount parameter. Set by default to 0.9999
        epsilon     value iteration convergence limit. Set by default to 0.00001
        """
        self.MDP = MDP
        self.tau = tau
        self.gamma = gamma
        self.epsilon = epsilon
        self.diagonal = diagonal
        self.distmat = distmat

    def BuildCostMatrix(self, Agent, Map):
        """
        Build a matrix with minimal cost between goals.

        Args:
            Agent (agent object)
            Map (map object)
        """
        TargetNo = len(self.Map.Locations) + 2
        DistMat = np.zeros((TargetNo, TargetNo))
        TargetSpace = self.Map.Locations
        TargetSpace.append(self.Map.ExitState)
        for i in range(1, TargetNo):
            TempPolicy = self.PlanForGoal(i)

    def Integrate(self, Agent, Map, ExitType="OneLocation", exitstate=0):
        """
        Integrate builds the MDP using a Map and an Agent object.
        This function calls Planner methods that help build the MDP.
        It doesn't return anything. Instead. the object's MDP will change.
        ARGUMENTS:
        Agent     Agent object
        Map       Map or Map object
        """
        found = 0
        if ExitType == "OneLocation":
            found = 1
            # Agent can only exit from the map in exitstate
            self.MDP = MDP.MDP(range(self.GetDeepStateSize(Map)), Map.A, self.BuildDeepT(
                Agent, Map, [exitstate]), self.BuildDeepR(Agent, Map), self.diagonal, self.gamma)
        if ExitType == "Border":
            found = 1
            # Exit from any map border:
            self.MDP = MDP.MDP(range(self.GetDeepStateSize(Map)), Map.A, self.BuildDeepT(
                Agent, Map), self.BuildDeepR(Agent, Map), self.diagonal, self.gamma)
        if ExitType == "North":
            found = 1
            # Exit from any top location:
            self.MDP = MDP.MDP(range(self.GetDeepStateSize(Map)), Map.A, self.BuildDeepT(
                Agent, Map, False, [range(Map.x)]), self.BuildDeepR(Agent, Map), self.diagonal, self.gamma)
        if found == 0:
            print "ERROR: ExitType does not exit."

    def ComputePolicy(self, Softmax=True):
        """
        ComputePolicy runs value iteration on it's MDP and then builds an optimal policy.
        ARGUMENTS:
        Softmax.   If set to True (Default) the policy will be softmaxed (Using the Planner.tau parameter).
        """
        self.MDP.ValueIteration(self.epsilon)  # Do value iteration on MDP.
        self.MDP.BuildPolicy(self.tau, Softmax)  # Get softmax probabilities.

    def EvalRoute(self, StartingPoint, ActionSequence):
        """
        EvalRoute(StartingPoint,ActionSequence)
        returns the probability of the agent taking the ActionSequence from StartingPoint.
        If there are agents who need help and may die this function is optimistic and assumes no one dies (thus never sending the agent to deathstates)
        """
        # This function retrieves the optimistic state sequence where no
        # objects disappear.
        StateSequence = self.MDP.GetStates(StartingPoint, ActionSequence)
        p = 1  # probability taking the action sequence
        for i in range(len(ActionSequence)):
            p *= (self.MDP.policy[ActionSequence[i], StateSequence[i]])
        return p

    def SimulatePathFor(self, StartingPoint, steps):
        """
        SimulatePathFor(StartingPoint,steps)
        simulates an agent's actions for the specified number of steps starting from the starting point
        """
        Actions = [-1] * steps
        State = StartingPoint
        for i in range(steps):
            [State, Actions[i]] = self.MDP.Run(State)
        return Actions

    def SimulatePathUntil(self, StartingPoint, StopStates, Limit, Simple=False):
        """
        Simulate path from StartingPoint until agent reaches a state in the StopStates list.
        Simulation ends after the agent has taken more steps than specified on Limit.
        """
        iterations = 0
        Actions = []
        StateSequence = [StartingPoint]
        State = StartingPoint
        while State not in StopStates:
            [State, NewAct] = self.MDP.Run(State, Simple)
            Actions.append(NewAct)
            StateSequence.append(State)
            iterations += 1
            if (iterations > Limit):
                return [Actions, StateSequence]
        return [Actions, StateSequence]

    def BuildR(self, Agent, Map):
        """
        Build the cost function for an MDP using the Map and Agent objects.

        Args:
            Agent (Agent object)
            Map (Map object)

        Returns:
            R (matrix): Reward function as a matrix where R[A,S] is the reward for tkaing action A in state S
        """
        R = np.zeros((len(Map.A), len(Map.S)))

        Costs = [-Agent.costs[Map.StateTypes[i]] for i in range(len(Map.S))]

        # Add regular costs to first four actions
        for i in range(4):
            R[i, :] = Costs

        if len(Map.A) > 4:
            Costs = [i * np.sqrt(2) for i in Costs]
            for i in range(4, 8):
                R[i, :] = Costs
        return R

    def UpdateSoftMax(self, tau):
        """
        Update softmax value.

        Args:
        tau (float): Softmax value. Must be greater than 0
        """
        if tau < 0:
            print "ERROR: Cannot use this value to softmax"
            return None
        self.tau = tau

    def UpdateConvergence(self, epsilon):
        """
        Update MDPs convergence value.

        Args:
        epsilon (float): Convergence value for MDP's value iteration
        """
        if epsilon >= 1 or epsilon < 0:
            print "ERROR: Cannot use this value as future discount"
            return None
        self.epsilon = epsilon

    def UpdateDiscount(self, gamma):
        """
        Update MDPs future discount.

        Args:
        gamma (float): Future value. Must be between 0 and 1
        """
        if gamma >= 1 or gamma < 0:
            print "ERROR: Cannot use this value as future discount"
            return None
        self.gamma = gamma

    def Display(self, Full=False):
        """
        Print object attributes.

        .. Internal function::

           This function is for internal use only.

        Args:
            Full (bool): When set to False, function only prints attribute names. Otherwise, it also prints its values.

        Returns:
            standard output summary
        """
        # Print class properties
        if Full:
            for (property, value) in vars(self).iteritems():
                print property, ': ', value
        else:
            for (property, value) in vars(self).iteritems():
                print property
<|endoftext|>