def validate_math(math):
    # Check that all connected variables are inside this component
    is_valid = True
    for e in math.errors.all():
        e.delete()

    # Compare list of local variables in this component with the ones used in the mathml
    available_variables = [
        x[0] for x in math.component.variables.values_list('name')
    ]
    referenced_variables = [
        x.split("</ci>")[0] for x in math.math_ml.split("<ci>")[1:]
    ]

    missing = set(referenced_variables) - set(available_variables)

    for m in missing:
        n = "'{n}' ".format(math.name) if math.name != "" else ""
        err = ItemError(
            hints=
            "Maths {n}in component '{c}' references a variable '{v}' which is not inside the component."
            .format(n=n, c=math.component.name, v=m),
            spec="14.1.3",
            fields=["variables"])
        err.save()
        math.errors.add(err)
        is_valid = False

    # TODO Validate elements in the cellml string
    # TODO Validate that units are consistent
    # TODO Warn if multipliers are inconsistent in the units

    return is_valid
def validate_equivalent_variable(component, variable):
    is_valid = True
    # Check that equivalent variables are not in the same component
    for ev in variable.equivalent_variables.filter(component=component):
        err = ItemError(
            hints=
            "Variable <i>{v1}</i> and equivalent variable <i>{v2}</i> are both in the same component "
            "<i>{c}</i>".format(v1=variable.name, v2=ev.name,
                                c=component.name),
            spec="17.1.2",
            fields=['component']  # TODO not sure what this should be
        )
        err.save()
        component.errors.add(err)
        is_valid = False

    # Check that the variables have a valid parent component
    for ev in variable.equivalent_variables.filter(component__isnull=True):
        err = ItemError(
            hints=
            "Variable <i>{ev}</i> is equivalent to variable <i>{v}</i> in component <i>{c}</i> but "
            "does not have a parent component".format(
                ev=ev.name,
                v=variable.name,
                c=component.name,
            ),
            spec="17.?",
            fields=['component']  # TODO not sure what this should be
        )
        err.save()
        ev.errors.add(err)
        component.errors.add(err)
        is_valid = False

    return is_valid
def validate_component_locally(component):
    is_valid = True
    for e in component.errors.all():
        e.delete()

    is_valid, hints = is_cellml_identifier(component.name)
    if not is_valid:
        err = ItemError(hints="Invalid component name <i>{n}</i>: {h}".format(
            n=component.name, h=hints),
                        spec='10.1.1',
                        fields=['name'])
        err.save()
        component.errors.add(err)

    # Check component's variables for duplicate names
    duplicates = component.variables.values('name').annotate(
        name_count=Count('name')).filter(name_count__gt=1)
    for d in duplicates:
        err = ItemError(
            hints=
            "Variable name <i>{n}</i> is duplicated {x} times in component <i>{m}</i>"
            .format(n=d['name'], x=d['name_count'], m=component.name),
            spec='11.1.1.1',
            fields=['variables'])
        err.save()
        component.errors.add(
            err
        )  # It's an error of the *component* not of the variable itself ...
        is_valid = False

    return is_valid
def validate_unit(unit):
    is_valid = True
    for e in unit.errors.all():
        e.delete()
    unit.error_tree = None

    # 9.1.1 Check that the pointers are valid
    if not unit.child_cu:
        is_valid = False
        err = ItemError(
            hints="Unit in units <i>{p}</i> points to a blank unit".format(
                p=unit.parent_cu.name),
            spec='9.1.1',
            fields=['child_cu'])
        err.save()
        unit.parent_cu.errors.add(err)

    error_tree, error_count = draw_error_tree(unit)
    error_count += unit.errors.count()
    unit.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    unit.is_valid = is_valid
    unit.save()

    return is_valid
def validate_connections(model):
    is_valid = True
    for component in model.all_components.all():
        for variable in component.variables.filter(
                equivalent_variables__isnull=False):
            is_valid = validate_equivalent_variable(component, variable)

    cycle_list = model_cyclic_variables_found(model)
    loop_count = len(cycle_list)
    if loop_count > 0:
        loops = " loops" if loop_count > 1 else " loop"
        des = ''.join(cycle_list)
        err = ItemError(hints="Cyclic variables exist, " + str(loop_count) +
                        loops + " found (Component,Variable): <br>" + des,
                        spec="19.10.5")
        err.save()
        model.errors.add(err)
        is_valid = False
    else:
        # Check order uniqueness in resets
        total_done_list = []
        for component in model.all_components.all():
            for variable in component.variables.filter(
                    equivalent_variables__isnull=False).exclude(
                        id__in=total_done_list):
                local_done_list = []
                reset_map = {}
                local_done_list, reset_map = fetch_connected_resets(
                    variable, local_done_list, reset_map)

                total_done_list.extend(local_done_list)

                for order, resets in reset_map.items():
                    if len(resets) > 1:
                        des = "Non-unique reset order of " + order + " found within equivalent variable set: "
                        for r in resets:
                            des += "<br>  - variable <i>" + r.variable.name + "</i> in component <i>" + \
                                   r.variable.component.name + "</i> has reset with order " + order
                        err = ItemError(hints=des, spec='12.1.1.2')
                        err.save()
                        model.errors.add(err)
                        is_valid = False

    return is_valid
def validate_compoundunit(cu):
    for e in cu.errors.all():
        e.delete()

    # Built-in units are valid
    if cu.is_standard:
        return True

    cu.error_tree = None

    # Checking the name
    is_valid, hints = is_cellml_identifier(cu.name)
    if not is_valid:
        err = ItemError(
            hints="Invalid compound_units name <i>{n}</i>: {h}".format(
                n=cu.name, h=hints),
            spec='8.1.1',
            fields=['name'])
        err.save()
        cu.errors.add(err)

    if CompoundUnit.objects.filter(name=cu.name, is_standard=True).count() > 0:
        err = ItemError(
            hints=
            "The name cannot be the same as a built-in units name, <i>{}</i>".
            format(cu.name),
            spec="8.1.3",
            fields=['name'])
        err.save()
        cu.errors.add(err)
        is_valid = False

    # Also need to check the unit elements of this compound unit here as they can't be accessed individually
    for u in cu.product_of.all():
        is_valid = validate_unit(u) and is_valid

    error_tree, error_count = draw_error_tree(cu)
    error_count += cu.errors.count()
    cu.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    cu.is_valid = is_valid
    cu.save()

    return is_valid
def validate_cellmodel_locally(model):
    is_valid = True
    for e in model.errors.all():
        e.delete()
    model.error_tree = None

    # Check model name
    is_valid, hints = is_cellml_identifier(model.name)
    if not is_valid:
        err = ItemError(hints="Invalid model name <i>{n}</i>: {h}".format(
            n=model.name, h=hints),
                        spec='4.2.1',
                        fields=['name'])
        err.save()
        model.errors.add(err)
        is_valid = False

    # Check model's components for duplicate names
    duplicates = model.all_components.values('name').annotate(
        name_count=Count('name')).filter(name_count__gt=1)
    for d in duplicates:
        err = ItemError(
            hints=
            "Component name <i>{n}</i> is duplicated {x} times in model <i>{m}</i>"
            .format(n=d['name'], x=d['name_count'], m=model.name),
            spec='10.1.1',
        )
        err.save()
        model.errors.add(
            err
        )  # It's an error of the *model* not of the component itself ...
        is_valid = False

    # Check model units
    duplicates = model.compoundunits.values('name').annotate(
        name_count=Count('name')).filter(name_count__gt=1)
    for d in duplicates:
        err = ItemError(
            hints=
            "Units name <i>{n}</i> is duplicated {x} times in model <i>{m}</i>"
            .format(n=d['name'], x=d['name_count'], m=model.name),
            spec='8.1.2',
        )
        err.save()
        model.errors.add(
            err
        )  # It's an error of the *model* not of the compoundunit itself ...
        is_valid = False

    # Check that the set of compound units used by the variables exists in the model
    required = model.all_components.values_list(
        'name', 'variables__name', 'variables__compoundunit__name')
    available = model.compoundunits.values_list('name').distinct()
    missing = [(c, v, u) for c, v, u in required
               if (u, ) not in available and u is not None and (
                   u, ) not in CompoundUnit.objects.filter(
                       is_standard=True).values_list('name')]

    for c, v, u in missing:
        err = ItemError(
            hints=
            "Variable <i>{v}</i> in component <i>{c}</i> has units <i>{u}</i> which do not exist in this "
            "model, and are not built-in.".format(c=c, u=u, v=v),
            spec="11.1.1.2")
        err.save()
        model.errors.add(err)
        is_valid = False

    return is_valid
def validate_reset(reset):
    is_valid = True
    for e in reset.errors.all():
        e.delete()

    if reset.order == '' or reset.order is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not have an order set".format(
                r=reset.name),
            spec="12.1.2",
            fields=["order"])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.test_value is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a test_value".format(
                r=reset.name),
            spec="12",  # TODO find correct code for resets
            fields=['test_value'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.reset_value is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a reset_value".format(
                r=reset.name),
            spec="12",  # TODO find correct code for resets
            fields=['reset_value'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.component is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not have a component".format(
                r=reset.name),
            spec="10.1.2.2",
            fields=['component'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.variable is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a variable".format(
                r=reset.name),
            spec="12.1.1",
            fields=['variable'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.test_variable is None:
        err = ItemError(
            hints="Reset <i>{r}</i> does not reference a test_variable".format(
                r=reset.name),
            spec="12",  # TODO find correct code for resets
            fields=['test_variable'])
        err.save()
        reset.errors.add(err)
        is_valid = False

    if reset.component is not None:
        if reset.variable is not None and reset.component != reset.variable.component:
            err = ItemError(
                hints=
                "Reset <i>{r}</i> in component <i>{c}</i> refers to a variable <i>{v}</i> which is in a "
                "different component, <i>{vc}</i>".format(
                    r=reset.name,
                    c=reset.component.name,
                    v=reset.variable.name,
                    vc=reset.variable.component.name),
                spec="12",  # TODO find correct code for resets
                fields=['variable'])
            err.save()
            reset.errors.add(err)
            is_valid = False

        if reset.test_variable is not None and reset.component != reset.test_variable.component:
            err = ItemError(
                hints=
                "Reset <i>{r}</i> in component <i>{c}</i> refers to a test_variable <i>{v}</i> in a different "
                "component, <i>{vc}</i>".format(
                    r=reset.name,
                    c=reset.component.name,
                    v=reset.test_variable.name,
                    vc=reset.test_variable.component.name),
                spec="12",  # TODO find correct code for resets
                fields=['test_variable'])
            err.save()
            reset.errors.add(err)
            is_valid = False

    error_tree, error_count = draw_error_tree(reset)
    error_count += reset.errors.count()
    reset.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    reset.is_valid = is_valid
    reset.save()

    return is_valid
def validate_variable(variable):
    # Variables are all local validation - no need for a duplicate
    for e in variable.errors.all():
        e.delete()

    try:
        initial_component_id = variable.initial_value_variable.component.id
    except Exception as e:
        initial_component_id = None
    try:
        initial_unit_id = variable.initial_value_variable.compoundunit.id
    except Exception as e:
        initial_unit_id = None

    # Validate name
    is_valid, hints = is_cellml_identifier(variable.name)
    if not is_valid:
        err = ItemError(hints="Invalid variable name <i>{n}</i>: {h}".format(
            n=variable.name, h=hints),
                        spec='11.1.1.1',
                        fields=["name"])
        err.save()
        variable.errors.add(err)

    # Check that variable has units
    if variable.compoundunit is None:
        err = ItemError(
            hints="Variable <i>{}</i> does not have any units.".format(
                variable.name),
            spec="11.1.1.2",
            fields=["compoundunit"])
        err.save()
        variable.errors.add(err)
        is_valid = False
    # Check that the units of the initialising variable are the same as for this variable
    elif initial_unit_id is not None:
        if initial_unit_id != variable.compoundunit.id:
            err = ItemError(
                hints=
                "Variable has units of <i>{u}</i> but is initialised by variable <i>{vi}</i> "
                "which has units of <i>{ui}</i>.".format(
                    u=variable.compoundunit.name,
                    vi=variable.initial_value_variable.name,
                    ui=variable.initial_value_variable.compoundunit.name),
                spec="11?",
                fields=["initial_value_variable"])
            err.save()
            variable.errors.add(err)
            is_valid = False

    # Check that the variable has access to the initialising variable
    if initial_component_id is not None:
        if initial_component_id != variable.component.id:
            err = ItemError(
                hints=
                "Variable <i>{v}</i> in component <i>{c}</i> is initialised by variable <i>{vi}</i> "
                "which is in another component <i>{ci}</i>.".format(
                    v=variable.name,
                    c=variable.component.name,
                    vi=variable.initial_value_variable.name,
                    ci=variable.initial_value_variable.component.name),
                spec="11.1.2.1",
                fields=['initial_value_variable', 'component'])
            err.save()
            variable.errors.add(err)
            is_valid = False

    # Check that there is only one method of initialisation
    if variable.initial_value_variable is not None and variable.initial_value_constant is not None:
        err = ItemError(
            hints=
            "Variable <i>{v}</i> in component <i>{c}</i> is initialised by variable <i>{vi}</i> "
            "as well as by the constant value <i>{ci}</i>. There can be only one."
            .format(v=variable.name,
                    c=variable.component.name,
                    vi=variable.initial_value_variable.name,
                    ci=variable.initial_value_constant),
            spec="11.1.2.1",
            fields=['initial_value_variable', 'initial_value_constant'])
        err.save()
        variable.errors.add(err)
        is_valid = False

    # Check that directly related resets have unique orders
    duplicated_orders = [
        x[0] for x in variable.reset_variables.values_list('order')
    ]
    unique_orders = list(set(duplicated_orders))
    for x in unique_orders:
        duplicated_orders.remove(x)

    for rv in variable.reset_variables.filter(order__in=duplicated_orders):
        err = ItemError(
            hints=
            "Variable <i>{v}</i> in component <i>{c}</i> contains more than one reset with order of "
            "<i>{o}</i>.".format(v=variable.name,
                                 c=variable.component.name,
                                 o=rv.order),
            spec="??",  # TODO find spec reference for resets
            fields=['reset_variables']  # TODO not sure what this should be
        )
        err.save()
        variable.errors.add(err)
        is_valid = False

    error_tree, error_count = draw_error_tree(variable)
    error_count += variable.errors.count()
    variable.error_tree = {'tree_html': error_tree, 'error_count': error_count}
    variable.is_valid = is_valid
    variable.save()

    return is_valid