vc2_conformance.constraint_table: A simple constraints model

The vc2_conformance.constraint_table module describes a constraint system which is used to describe restrictions imposed by VC-2 levels. See vc2_conformance.level_constraints.LEVEL_CONSTRAINTS for the actual level constraints table.

Tutorial

Constraint tables enumerate allowed combinations of values as a list of dictionaries containing ValueSet objects. Each dictionary describes a valid combination of values. In the contrived running example below we’ll define valid food-color combinations (rather than VC-2 codec options):

>>> real_foods = [
...     {"type": ValueSet("tomato"), "color": ValueSet("red")},
...     {"type": ValueSet("apple"), "color": ValueSet("red", "green")},
...     {"type": ValueSet("beetroot"), "color": ValueSet("purple")},
... ]

We can check dictionaries of values against this permitted list of combinations using is_allowed_combination():

>>> # Allowed combinations
>>> is_allowed_combination(real_foods, {"type": "tomato", "color": "red"})
True
>>> is_allowed_combination(real_foods, {"type": "apple", "color": "red"})
True
>>> is_allowed_combination(real_foods, {"type": "apple", "color": "green"})
True

>>> # Purple apples? I don't think so...
>>> is_allowed_combination(real_foods, {"type": "apple", "color": "purple"})
False

But we don’t have to check a complete set of values. For example, we can check if a particular color is valid for any foodstuff:

>>> is_allowed_combination(real_foods, {"color": "red"})
True
>>> is_allowed_combination(real_foods, {"color": "yellow"})
False

This behaviour allows us to detect the first non-constraint-satisfying value when values are obtained sequentially (as they are for a VC-2 bitstream). The bitstream validator (vc2_conformance.decoder) uses this functionality to check bitstream values conform to the constraints imposed by a specified VC-2 level.

Given an incomplete set of values, we can use allowed_values_for() to discover what values are permissible for values we’ve not yet assigned. For example:

>>> # If we have an apple, what colors can it be?
>>> allowed_values_for(real_foods, "color", {"type": "apple"})
ValueSet('red', 'green')
>>> # If we have something red, what might it be?
>>> allowed_values_for(real_foods, "type", {"color": "red"})
ValueSet('apple', 'tomato')

This functionality is used by the test case generators and encoder (vc2_conformance.test_cases and vc2_conformance.encoder) to discover combinations of bitstream features which satisfy particular level requirements.

ValueSet

class ValueSet(*values_and_ranges)

Represents a set of allowed values. May consist of anything from a single value, a range of values or a combination of several of these.

__init__(*values_and_ranges)

Create a ValueSet containing the specified set of values:

>>> no_values = ValueSet()
>>> 100 in no_values
False

>>> single_value = ValueSet(100)
>>> 100 in single_value
True
>>> 200 in single_value
False

>>> range_of_values = ValueSet((10, 20))
>>> 9 in range_of_values
False
>>> 10 in range_of_values
True
>>> 11 in range_of_values
True
>>> 20 in range_of_values  # NB: Range is inclusive
True
>>> 21 in range_of_values
False

>>> many_values = ValueSet(100, 200, (300, 400))
>>> 100 in many_values
True
>>> 200 in many_values
True
>>> 300 in many_values
True
>>> 350 in many_values
True
>>> 500 in many_values
False

>>> non_numeric = ValueSet("foo", "bar", "baz")
>>> "foo" in non_numeric
True
>>> "nope" in non_numeric
False
Parameters
*values_and_rangesvalue, or (lower_value, upper_value)

Sets the initial set of values and (inclusive) ranges to be matched

add_value(value)

Add a single value to the set.

add_range(lower_bound, upper_bound)

Add the range of values between the two inclusive bounds to the set.

__contains__(value)

Test if a value is a member of this set. For example:

>>> value_set = ValueSet(1, 2, 3)
>>> 1 in value_set
True
>>> 100 in value_set
False
is_disjoint(other)

Test if this ValueSet is disjoint from another – i.e. they share no common values.

__eq__(other)

Return self==value.

__add__(other)

Combine two ValueSet objects into a single object containing the union of both of their values.

For example:

>>> a = ValueSet(123)
>>> b = ValueSet((10, 20))
>>> a + b
ValueSet(123, (10, 20))
__iter__()

Iterate over the values and (lower_bound, upper_bound) tuples in this value set in no particular order.

iter_values()

Iterate over the values (including the enumerated values of ranges) in this value set in no particular order.

__str__()

Produce a human-readable description of the permitted values.

For example:

>>> print(ValueSet())
{<no values>}
>>> print(ValueSet(1, 2, 3, (10, 20)))
{1, 2, 3, 10-20}
class AnyValue

Like ValueSet but represents a ‘wildcard’ set of values which contains all possible values.

Constraint tables

A ‘constraint table’ is defined as a list of ‘allowed combination’ dictionaries.

An ‘allowed combination’ dictionary defines a ValueSet for every permitted key.

For example, the following is a constraint table containing three allowed combination dictionaries:

>>> real_foods = [
...     {"type": ValueSet("tomato"), "color": ValueSet("red")},
...     {"type": ValueSet("apple"), "color": ValueSet("red", "green")},
...     {"type": ValueSet("beetroot"), "color": ValueSet("purple")},
... ]

A set of values satisfies a constraint table if there is at least one allowed combination dictionary which contains the specified combination of values. For example, the following two dictionaries satisfy the constraint table:

{"type": "apple", "color": "red"}

{"color": "red"}

The first satisfies the constraint table because the combination of values given appears in the second entry of the constraint table.

The second satisfies the constraint table because, even though it does not define a value for every key, the key it does define is included in both the first and second entries.

Meanwhile, the following dictionaries do not satisfy the constraint table:

{"type": "apple", "color": "purple"}

{"type": "beetroot", "color": "purple", "pickleable": True}

The first of these contains values which, in isolation, would be permitted by the second and third entries of the table but which are not present together in any table entries. Consequently, this value does not satisfy the table.

The second contains a ‘pickleable’ key which is not present in any of the allowed combinations in constraint table and so does not satisfy the table.

The functions below may be used to interrogate a constraint table.

filter_constraint_table(constraint_table, values)

Return the subset of constraint_table entries which match all of the values in values. That is, with the entries whose constraints are not met by the provided values removed.

is_allowed_combination(constraint_table, values)

Check to see if the values dictionary holds an allowed combination of values according to the provided constraint table.

Note

A candidate containing only a subset of the keys listed in the constraint table is allowed if the fields it does define are a permitted combination.

Parameters
constraint_table: [{key: :py:class:`ValueSet`, …}, …]
values{key: value}
allowed_values_for(constraint_table, key, values={}, any_value=AnyValue())

Return a ValueSet which matches all allowed values for the specified key, given the existing values defined in values.

Parameters
constraint_table[{key: ValueSet, …}, …]
keykey
values{key: value, …}

Optional. The values already chosen. (Default: assume nothing chosen).

any_valueValueSet

Optional. If provided and AnyValue is allowed, this value will be substituted instead. This may be useful when AnyValue is being used as a short-hand for a more concrete set of values.

CSV format

A Constraint table can be read from CSV files using the following function:

read_constraints_from_csv(csv_filename)

Reads a table of constraints from a CSV file.

The CSV file should be arranged with each row describing a particular value to be constrained and each column defining an allowed combination of values.

Empty rows and rows containing only ‘#’ prefixed values will be skipped.

The first column will be treated as the keys being constrained, remaining columns should contain allowed combinations of values. Each of these values will be converted into a ValueSet as follows:

  • Values which contain integers will be converted to int

  • Values which contain ‘TRUE’ or ‘FALSE’ will be converted to bool

  • Values containing a pair of integers separated by a - will be treated as an incusive range.

  • Several comma-separated instances of the above will be combined into a single ValueSet. (Cells containing comma-separated values will need to be enclosed in double quotes (") in the CSV).

  • The value ‘any’ will be substituted for AnyValue.

  • Empty cells will be converted into empty ValueSets.

  • Cells which contain only a pair of quotes (e.g. ", i.e. ditto) will be assigned the same value as the column to their left. (This is encoded using four double quotes ("""") in CSV format).

The read constraint table will be returned as a list of dictionaries (one per column) as expected by the functions in vc2_conformance.constraint_table.