Esempio n. 1
0
 def test_det_dedupe(self):
     l = [0, 1, 2, 0, 1, 0, 7, 1]
     expected = [0, 1, 2, 7]
     self.assertEqual(det_dedupe(l), expected)
     self.assertEqual(det_dedupe([]), [])
     with self.assertRaises(TypeError):
         det_dedupe([[]])
Esempio n. 2
0
    def start(self, *args):
        """ Collapse return into a list of related model instances

        Args:
            *args (:obj:`list` of :obj:`core.Model`): related model instances

        Returns:
            :obj:`list` of :obj:`core.Model`: related model instances
        """
        return det_dedupe(arg for arg in args if not isinstance(arg, lark.lexer.Token))
Esempio n. 3
0
def get_subclasses(cls, immediate_only=False):
    """ Reproducibly get subclasses of a class, with duplicates removed

    Args:
        cls (:obj:`type`): class
        immediate_only (:obj:`bool`, optional): if true, only return direct subclasses

    Returns:
        :obj:`list` of `type`: list of subclasses, with duplicates removed
    """
    subclasses = list(cls.__subclasses__())

    if not immediate_only:
        for sub_cls in subclasses.copy():
            subclasses.extend(get_subclasses(sub_cls, immediate_only=False))

    return det_dedupe(subclasses)
Esempio n. 4
0
def get_obj_units(obj):
    """ Get units used in a model object and related objects

    Args:
        obj (:obj:`core.Model`): model object

    Returns:
        :obj:`list` of :obj:`pint.unit._Unit`: units used in model object
    """
    units = []
    for o in itertools.chain([obj], obj.get_related()):
        for attr in o.Meta.attributes.values():
            if isinstance(attr, (UnitAttribute, QuantityAttribute)):
                unit = getattr(o, attr.name)
                if unit:
                    units.append(unit)

    # return units
    return det_dedupe(units)
Esempio n. 5
0
    def set_up_ode_submodel(self):
        """ Set up an ODE submodel, including its ODE solver """

        # disable locking temporarily
        # self.get_solver_lock()

        # find species in reactions modeled by this submodel
        ode_species = []
        for idx, rxn in enumerate(self.reactions):
            for species_coefficient in rxn.participants:
                species_id = species_coefficient.species.gen_id()
                ode_species.append(species_id)
        self.ode_species_ids = det_dedupe(ode_species)

        # this is optimal, but costs O(|self.reactions| * |rxn.participants|)
        tmp_coeffs_and_rate_laws = {
            species_id: []
            for species_id in self.ode_species_ids
        }
        for idx, rxn in enumerate(self.reactions):
            rate_law_id = rxn.rate_laws[0].id
            dynamic_rate_law = self.dynamic_model.dynamic_rate_laws[
                rate_law_id]
            for species_coefficient in rxn.participants:
                species_id = species_coefficient.species.gen_id()
                tmp_coeffs_and_rate_laws[species_id].append(
                    (species_coefficient.coefficient, dynamic_rate_law))

        # todo: use linear algebra to compute species derivatives, & calc each RL once
        # replace rate_of_change_expressions with a stoichiometric matrix S
        # then, in compute_population_change_rates() compute a vector of reaction rates R and get
        # rates of change of species from R * S
        self.rate_of_change_expressions = []
        for species_id in self.ode_species_ids:
            self.rate_of_change_expressions.append(
                tmp_coeffs_and_rate_laws[species_id])
Esempio n. 6
0
def init_schema(filename, out_filename=None):
    """ Initialize an `ObjTables` schema from a tabular declarative specification in
    :obj:`filename`. :obj:`filename` can be a XLSX, CSV, or TSV file.

    Schemas (classes and attributes) should be defined using the following tabular format.
    Classes and their attributes can be defined in any order.

    .. table:: Format for specifying classes.
        :name: class_tabular_schema

        ==========================================  =========================  =================================================  ========
        Python                                      Tabular column             Tabular column values                              Optional
        ==========================================  =========================  =================================================  ========
        Class name                                  !Name                      Valid Python name
        Class                                       !Type                      ``Class``
        Superclass                                  !Parent                    Empty or the name of another class
        :obj:`obj_tables.Meta.table_format`         !Format                    ``row``, ``column``, ``multiple_cells``, ``cell``
        :obj:`obj_tables.Meta.verbose_name`         !Verbose name              String                                             Y
        :obj:`obj_tables.Meta.verbose_name_plural`  !Verbose name plural       String                                             Y
        :obj:`obj_tables.Meta.description`          !Description                                                                  Y
        ==========================================  =========================  =================================================  ========

    .. table:: Format for specifying attributes of classes.
        :name: attribute_tabular_schema

        ===========================================================  ====================  ==========================================  ========
        Python                                                       Tabular column        Tabular column values                       Optional
        ===========================================================  ====================  ==========================================  ========
        Name of instance of subclass of :obj:`obj_tables.Attribute`  !Name                 a-z, A-Z, 0-9, _, :, >, ., -, [, ], or ' '
        :obj:`obj_tables.Attribute`                                  !Type                 ``Attribute``
        Parent class                                                 !Parent               Name of the parent class
        Subclass of :obj:`obj_tables.Attribute`                      !Format               ``Boolean`, ``Float`, ``String``, etc.
        :obj:`obj_tables.Attribute.verbose_name`                     !Verbose name         String                                      Y
        :obj:`obj_tables.Attribute.verbose_name_plural`              !Verbose name plural  String                                      Y
        :obj:`obj_tables.Attribute.description`                      !Description          String                                      Y
        ===========================================================  ====================  ==========================================  ========

    Args:
        filename (:obj:`str`): path to
        out_filename (:obj:`str`, optional): path to save schema

    Returns:
        :obj:`tuple`:

            * :obj:`types.ModuleType`: module with classes
            * :obj:`str`: schema name

    Raises:
        :obj:`ValueError`: if schema specification is not in a supported format,
            an XLSX schema file does not contain a worksheet with the name ``!!_Schema`` which specifies the schema,
            the class inheritance structure is cyclic,
            or the schema specification is invalid (e.g., a class is defined multiple defined)
    """
    from obj_tables.io import WorkbookReader

    base, ext = os.path.splitext(filename)
    if ext in ['.xlsx']:
        sheet_name = '!!' + SCHEMA_SHEET_NAME
    elif ext in ['.csv', '.tsv']:
        if '*' in filename:
            sheet_name = '!!' + SCHEMA_SHEET_NAME
        else:
            sheet_name = ''
    else:
        raise ValueError('{} format is not supported.'.format(ext))

    wb = wc_utils.workbook.io.read(filename)
    if sheet_name not in wb:
        raise ValueError(
            'Schema file must contain a sheet with name "{}".'.format(
                sheet_name))
    ws = wb[sheet_name]

    name_col_name = '!Name'
    type_col_name = '!Type'
    parent_col_name = '!Parent'
    format_col_name = '!Format'
    verbose_name_col_name = '!Verbose name'
    verbose_name_plural_col_name = '!Verbose name plural'
    desc_col_name = '!Description'

    col_names = [
        name_col_name,
        type_col_name,
        parent_col_name,
        format_col_name,
        verbose_name_col_name,
        verbose_name_plural_col_name,
        desc_col_name,
    ]

    class_type = 'Class'
    attr_type = 'Attribute'

    rows = ws
    doc_metadata, model_metadata, _ = WorkbookReader.read_worksheet_metadata(
        sheet_name, rows)

    doc_schema_name = doc_metadata.get('schema', None)
    schema_schema_name = model_metadata.get('name', None)
    assert not doc_schema_name or not schema_schema_name or doc_schema_name == schema_schema_name, \
        "Schema names must be None or equal"
    schema_name = doc_schema_name or schema_schema_name
    module_name = schema_name or rand_schema_name()

    if model_metadata.get('type', None) != SCHEMA_TABLE_TYPE:
        raise ValueError(
            "The type of the schema must be '{}'.".format(SCHEMA_TABLE_TYPE))

    # parse model specifications
    header_row = rows[0]
    rows = rows[1:]

    if name_col_name not in header_row:
        raise ValueError('Schema must have column "{}"'.format(name_col_name))
    if type_col_name not in header_row:
        raise ValueError('Schema must have column "{}"'.format(type_col_name))
    if parent_col_name not in header_row:
        raise ValueError(
            'Schema must have column "{}"'.format(parent_col_name))
    if format_col_name not in header_row:
        raise ValueError(
            'Schema must have column "{}"'.format(format_col_name))
    extra_headers = set(header_row) - set(col_names)
    if extra_headers:
        raise ValueError('Schema has unrecognized columns:\n  {}'.format(
            '\n  '.join(natsorted(extra_headers, alg=ns.IGNORECASE))))

    cls_specs = {}
    explicit_model_names = []
    implicit_model_names = []
    for i_row, row_list in enumerate(rows):
        # ignore empty rows
        if all(cell in [None, ''] for cell in row_list):
            continue

        # ignore comment rows
        if len(row_list) == 1 and isinstance(
                row_list[0], str) and row_list[0].startswith(
                    '%/') and row_list[0].endswith('/%'):
            continue

        # convert cells to strings
        for i_cell, cell in enumerate(row_list):
            if cell is not None and not isinstance(cell, str):
                row_list[i_cell] = str(cell)

        row = {}
        for header, cell in zip(header_row, row_list):
            row[header] = cell

        if row[type_col_name] == class_type:
            cls_name = row[name_col_name]
            if not cls_name:
                raise ValueError(
                    'Class at row {} of the schema must have a name'.format(
                        i_row + 1))
            if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', cls_name):
                raise ValueError(
                    ("Invalid class name '{}' at row {} of the schema. "
                     "Class names must start with a letter or underscore, "
                     "and consist of letters, numbers, and underscores."
                     ).format(cls_name, i_row + 1))

            if cls_name in cls_specs:
                cls = cls_specs[cls_name]
                if cls['explictly_defined']:
                    raise ValueError(
                        'Class "{}" can only be defined once in the schema.'.
                        format(cls_name))
                cls['explictly_defined'] = True
            else:
                cls = cls_specs[cls_name] = {
                    'super_class': None,
                    'name': cls_name,
                    'attrs': {},
                    'attr_order': [],
                    'explictly_defined': True,
                }

            if row[parent_col_name]:
                cls['super_class'] = row[parent_col_name]

            if (row[format_col_name] or 'row') not in TableFormat.__members__:
                raise ValueError(
                    "Invalid class format '{}' at row {} of the schema".format(
                        row[format_col_name], i_row + 1))
            cls['tab_format'] = TableFormat[row[format_col_name] or 'row']

            def_verbose_name = cls_name
            cls['verbose_name'] = row.get(verbose_name_col_name,
                                          def_verbose_name) or def_verbose_name

            if row.get(verbose_name_col_name, None):
                def_plural_verbose_name = inflect.engine().plural(
                    row[verbose_name_col_name])
            else:
                def_plural_verbose_name = cls_name
            cls['verbose_name_plural'] = row.get(
                verbose_name_plural_col_name,
                def_plural_verbose_name) or def_plural_verbose_name

            cls['desc'] = row.get(desc_col_name, None) or None

            explicit_model_names.append(cls_name)

        elif row[type_col_name] == attr_type:
            cls_name = row[parent_col_name]
            if not cls_name:
                raise ValueError(
                    'Parent class of attribute at row {} must be defined'.
                    format(i_row + 1))
            if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', cls_name):
                raise ValueError((
                    "Parent class of attribute at row {} of the schema has an invalid name '{}'. "
                    "Class names must start with a letter or underscore, "
                    "and consist of letters, numbers, and underscores."
                ).format(i_row + 1, cls_name))

            if cls_name in cls_specs:
                cls = cls_specs[cls_name]
            else:
                cls = cls_specs[cls_name] = {
                    'explictly_defined': False,
                    'super_class': None,
                    'name': cls_name,
                    'attrs': {},
                    'attr_order': [],
                    'tab_format': TableFormat.row,
                    'verbose_name': cls_name,
                    'verbose_name_plural': cls_name,
                    'desc': None,
                }
                implicit_model_names.append(cls_name)

            attr_name = row[name_col_name]
            if not attr_name:
                raise ValueError(
                    'Attribute at row {} of the schema must have a name'.
                    format(i_row + 1))
            if not re.match(r'^[a-zA-Z_:>\.\- \[\]][a-zA-Z0-9_:>\.\- \[\]]*$',
                            attr_name):
                raise ValueError(
                    ("Invalid attribute name '{}' at row {} of the schema. "
                     "Attribute names must consist of alphanumeric "
                     "characters, underscores, colons, forward carets, "
                     "dots, dashes, square brackets, and spaces and "
                     "begin with a non-numeric character.").format(
                         attr_name, i_row + 1))
            attr_name = re.sub(r'[^a-zA-Z0-9_]', '_', attr_name)
            attr_name = stringcase.snakecase(attr_name)
            attr_name = re.sub(r'_+', '_', attr_name)

            if attr_name == 'Meta':
                raise ValueError(
                    '"{}" cannot have attribute with name "Meta" at row {} of the schema.'
                    .format(cls_name, i_row + 1)
                )  # pragma: no cover # unreachable because snake case is all lowercase
            if attr_name in cls['attrs']:
                raise ValueError(
                    'Attribute "{}" of "{}" can only be defined once.'.format(
                        row[name_col_name], cls_name))

            cls['attrs'][attr_name] = {
                'name': attr_name,
                'type': row[format_col_name],
                'desc': row.get(desc_col_name, None),
                'verbose_name': row.get(verbose_name_col_name,
                                        row[name_col_name])
            }
            cls['attr_order'].append(attr_name)

        else:
            if row[type_col_name]:
                raise ValueError(
                    'Type "{}" is not supported at row {} of the schema.'.
                    format(row[type_col_name], i_row + 1))
            else:
                raise ValueError(
                    'Type must be defined at row {} of the schema.'.format(
                        row[type_col_name], i_row + 1))

    # check that the inheritance graph is valid (i.e. acyclic)
    inheritance_graph = networkx.DiGraph()
    sub_classes = {'obj_tables.Model': []}
    for cls_name, cls_spec in cls_specs.items():
        if cls_spec['super_class']:
            if cls_spec['super_class'] not in cls_specs:
                raise ValueError(
                    'Superclass "{}" for class "{}" must be defined'.format(
                        cls_spec['super_class'], cls_name))

            inheritance_graph.add_edge(cls_spec['super_class'], cls_name)
            if cls_spec['super_class'] not in sub_classes:
                sub_classes[cls_spec['super_class']] = []
            sub_classes[cls_spec['super_class']].append(cls_name)
        else:
            inheritance_graph.add_edge('obj_tables.Model', cls_name)
            sub_classes['obj_tables.Model'].append(cls_name)
    if list(networkx.simple_cycles(inheritance_graph)):
        raise ValueError('The schema inheritance graph must be acyclic.')

    # create classes
    module = type(module_name, (types.ModuleType, ), {})

    all_attrs = get_attrs()
    classes_to_construct = list(sub_classes['obj_tables.Model'])
    while classes_to_construct:
        cls_name = classes_to_construct.pop()
        cls_spec = cls_specs[cls_name]
        # if not cls_spec['explictly_defined']:
        #     raise ValueError('Class "{}" is not defined in the schema'.format(cls_name))

        classes_to_construct.extend(sub_classes.get(cls_name, []))

        meta_attrs = {
            'table_format': cls_spec['tab_format'],
            'attribute_order': tuple(cls_spec['attr_order']),
            'description': cls_spec['desc'],
        }
        if cls_spec['verbose_name']:
            meta_attrs['verbose_name'] = cls_spec['verbose_name']
        if cls_spec['verbose_name_plural']:
            meta_attrs['verbose_name_plural'] = cls_spec['verbose_name_plural']

        attrs = {
            '__module__': module_name,
            '__doc__': cls_spec['desc'],
            'Meta': type('Meta', (Model.Meta, ), meta_attrs),
        }
        for attr_spec in cls_spec['attrs'].values():
            attr_type_spec, _, args = attr_spec['type'].partition('(')
            if attr_type_spec not in all_attrs:
                raise ValueError(
                    'Attribute "{}" is not defined in the schema'.format(
                        attr_type_spec))
            attr_type = all_attrs[attr_type_spec]
            attr_spec['python_type'] = attr_type_spec + 'Attribute'
            if args:
                attr_spec['python_args'] = args[0:-1]
                if attr_spec['verbose_name']:
                    attr_spec['python_args'] += ", verbose_name='{}'".format(
                        attr_spec['verbose_name'].replace("'", "\\'"))
            else:
                attr_spec['python_args'] = ''
                if attr_spec['verbose_name']:
                    attr_spec['python_args'] = "verbose_name='{}'".format(
                        attr_spec['verbose_name'].replace("'", "\\'"))

            if args:
                attr = eval('func(' + args, {}, {'func': attr_type})
            else:
                attr = attr_type()
            attr.verbose_name = attr_spec['verbose_name']
            attr.description = attr_spec['desc']
            attrs[attr_spec['name']] = attr

        if cls_spec['super_class'] is None or cls_spec[
                'super_class'] == 'obj_tables.Model':
            super_class = Model
        else:
            super_class = getattr(module, cls_spec['super_class'])

        cls = type(cls_spec['name'], (super_class, ), attrs)
        setattr(module, cls_spec['name'], cls)

    # optionally, generate a Python file
    if out_filename:
        with open(out_filename, 'w') as file:
            # print documentation
            file.write(
                '# Schema automatically generated at {:%Y-%m-%d %H:%M:%S}\n\n'.
                format(datetime.now()))

            # print import statements
            imported_modules = set(['obj_tables'])
            for cls_spec in cls_specs.values():
                for attr_spec in cls_spec['attrs'].values():
                    imported_modules.add(
                        'obj_tables.' +
                        attr_spec['python_type'].rpartition('.')[0])
            if 'obj_tables.' in imported_modules:
                imported_modules.remove('obj_tables.')
            for imported_module in imported_modules:
                file.write('import {}\n'.format(imported_module))

            # print definition of * import behavior
            file.write('\n')
            file.write('\n')
            file.write('__all__ = [\n')
            file.write(''.join("    '{}',\n".format(cls_name)
                               for cls_name in sorted(cls_specs.keys())))
            file.write(']\n')

            # print class definitions
            classes_to_define = list(sub_classes['obj_tables.Model'])
            while classes_to_define:
                cls_name = classes_to_define.pop(0)
                cls_spec = cls_specs[cls_name]
                classes_to_define.extend(sub_classes.get(cls_name, []))

                if cls_spec['super_class']:
                    super_class = cls_spec['super_class']
                else:
                    super_class = 'obj_tables.Model'

                file.write('\n')
                file.write('\n')
                file.write('class {}({}):\n'.format(cls_spec['name'],
                                                    super_class))
                if cls_spec['desc']:
                    file.write('    """ {} """\n\n'.format(cls_spec['desc']))
                for attr_name in cls_spec['attr_order']:
                    attr_spec = cls_spec['attrs'][attr_name]
                    file.write('    {} = obj_tables.{}({})\n'.format(
                        attr_spec['name'], attr_spec['python_type'],
                        attr_spec['python_args']))

                file.write('\n')
                file.write('    class Meta(obj_tables.Model.Meta):\n')
                file.write(
                    "        table_format = obj_tables.TableFormat.{}\n".
                    format(cls_spec['tab_format'].name))
                file.write("        attribute_order = (\n{}        )\n".format(
                    "".join("            '{}',\n".format(attr)
                            for attr in cls_spec['attr_order'])))
                if cls_spec['verbose_name']:
                    file.write("        verbose_name = '{}'\n".format(
                        cls_spec['verbose_name'].replace("'", "\\'")))
                if cls_spec['verbose_name_plural']:
                    file.write("        verbose_name_plural = '{}'\n".format(
                        cls_spec['verbose_name_plural'].replace("'", "\\'")))
                if cls_spec['desc']:
                    file.write("        description = '{}'\n".format(
                        cls_spec['desc'].replace("'", "\\'")))

    # get models in order of their definition
    model_names = det_dedupe(explicit_model_names + implicit_model_names)
    models = [getattr(module, model_name) for model_name in model_names]

    # return the created module and its name
    return (module, schema_name, models)