Modeling

This page provides an overview of the SDK’s declarative modeling API. The functionality covered here is exported by the opvious.modeling module, which we recommend importing qualified:

import opvious.modeling as om

Definitions

Each Model can be roughly thought of as a container for definitions. Its specification is generated by combining all its attributes which contain Definition instances.

Note

Attributes starting with _ are ignored when detecting a model’s definitions.

There are 6 categories of definitions, all described below.

Dimensions

class opvious.modeling.Dimension(*, label: Label | None = None, name: Name | None = None, is_numeric: bool = False)

Bases: Definition, ScalarSpace

An abstract collection of values

Parameters:
  • label – Dimension label override. By default the label is derived from the attribute’s name

  • name – The dimension’s name. By default the name will be derived from the dimension’s label

  • is_numeric – Whether the dimension will only contain integers. This enables arithmetic operations on this dimension’s quantifiers

Dimensions are Quantifiable and as such can be quantified over using cross(). As a convenience, iterating on a dimension also yields a suitable quantifier. This allows creating simple constraints directly:

class ProductModel(Model):
    products = Dimension()
    count = Variable.natural(products)

    @constraint
    def at_least_one_of_each(self):
        for p in self.products:  # Note the iteration here
            yield self.count(p) >= 1

Parameters and variables

class opvious.modeling.Parameter(image: Image, *quantifiables: Quantifiable, name: Name | None = None, label: Label | None = None, qualifiers: Sequence[Label] | None = None)

Bases: Tensor

An optimization input parameter

Parameters:
  • image – Target Image

  • quantifiables – Quantification

  • label – Optional label, if unset it will be derived from the parameter’s attribute name

  • name – Optional name, if unset it will be derived from the labe

  • qualifiers – Optional list of labels used to name this parameter’s quantifiables

Consider instantiating parameters via one of the various Tensor convenience class methods, for example:

p1 = Parameter.continuous()  # Real-valued parameter
p2 = Parameter.natural() # # Parameter with values in {0, 1...}

Each parameter will be expected to have a matching input when solving this model on data.

class opvious.modeling.Variable(image: Image, *quantifiables: Quantifiable, name: Name | None = None, label: Label | None = None, qualifiers: Sequence[Label] | None = None)

Bases: Tensor

An optimization output variable

Parameters:
  • image – Target Image

  • quantifiables – Quantification

  • label – Optional label, if unset it will be derived from the parameter’s attribute name

  • name – Optional name, if unset it will be derived from the labe

  • qualifiers – Optional list of labels used to name this parameter’s quantifiables

Similar to parameters, consider instantiating variables via one of the various Tensor convenience class methods, for example:

v1 = Variable.unit()  # Variable with value within [0, 1]
v2 = Variable.non_negative() # # Variable with value at least 0

Each variable will have its results available in feasible solve solutions.

Constraints

class opvious.modeling.Constraint(body: Callable[[], Quantified[Predicate]], label: Label | None = None, qualifiers: Sequence[Label] | None = None)

Bases: Definition

Optimization constraint

Constraints are best created directly from Model methods via the constraint() decorator.

class ProductModel:
    products = Dimension()
    count = Variable.natural(products)

    @constraint
    def at_least_one(self):
        yield total(self.count(p) for p in self.products) >= 1
opvious.modeling.constraint(method: ConstraintMethod) Constraint
opvious.modeling.constraint(*, label: Label | None = None, qualifiers: Sequence[Label] | None = None, disabled: bool = False) Callable[[ConstraintMethod], Constraint | None]
opvious.modeling.constraint(new: Callable[[...], Any]) Callable[[ConstraintMethod], Constraint]

Decorator promoting a Model method to a Constraint

Parameters:
  • label – Constraint label override. By default the label is derived from the method’s name.

  • qualifiers – Optional list of labels used to qualify the constraint’s quantifiers. This is useful to override the name of the colums in solution dataframes.

  • disabled – Disable the constraint

The decorated method should accept only a self argument and return a quantified Predicate.

As a convenience, this decorator can be used with and without arguments:

class MyModel(Model):
    # ...

    @constraint
    def ensure_something(self):
        # ...

    @constraint(label="ensuresEverything")
    def ensure_something_else(self):
        # ...

Objectives

class opvious.modeling.Objective(body: Callable[[], Expression], sense: ObjectiveSense, label: Label | None = None)

Bases: Definition

Optimization objective

Objectives are best created directly from Model methods via the objective() decorator.

class ProductModel:
    products = Dimension()
    count = Variable.natural(products)

    @objective
    def minimize_total_count(self):
        return total(self.count(p) for p in self.products)
opvious.modeling.objective(method: ObjectiveMethod) Objective
opvious.modeling.objective(*, sense: ObjectiveSense | None = None, label: Label | None = None, disabled: bool = False) Callable[[ObjectiveMethod], Objective]
opvious.modeling.objective(new: Callable[[...], Any]) Callable[[ObjectiveMethod], Objective]

Decorator promoting a method to an Objective

Parameters:
  • sense – Optimization direction. This may be omitted if the method name starts with min or max, in which case the appropriate sense will be inferred.

  • label – Objective label override. By default the label is derived from the method’s name.

  • disabled – Disable the objective

The decorated method should accept only a self argument and return an Expression, which will become the objective’s optimization target.

As a convenience, this decorator can be used with and without arguments:

class ProductModel(Model):
    products = Dimension()
    cost = Parameter.non_negative(products)
    count = Variable.natural(products)

    @objective
    def minimize_cost(self):
        return total(
            self.count(p) * self.cost(p)
            for p in self.products
        )

    @objective(sense="max")
    def optimize_count(self):
        return total(self.count(p) for p in self.products)

Aliases

opvious.modeling.alias(name: Name | None, *quantifiables: Quantifiable, quantifier_names: Iterable[Name] | None = None) Callable[[F], F]

Decorator promoting a Model method to a named alias

Parameters:
  • name – The alias’ name. If None, no alias will be added.

  • quantifiables – The alias’ quantification. If omitted, it will be inferred from the alias’ calls.

  • quantifier_names – Optional names to use for the alias’ quantifiers

The method can return a (potentially quantified) expression or a quantification and may accept any number of expression arguments. This is useful to make the generated specification more readable by extracting commonly used sub-expressions or sub-spaces.

Finally, the decorated function may be wrapped as a property if doesn’t have any non-self arguments.

class ProductModel(Model):
    products = Dimension()
    count = Variable.natural(products)

    @property
    @alias("t")
    def total_product_count(self):
        return total(self.count(p) for p in self.products)

Terms

Expressions

class opvious.modeling.Expression

Base expression

Expressions are typically created via Parameter or Variable instances. They can be combined using any of the following operations:

  • Addition: x + y

  • Substraction: x - y

  • Multiplication: x * y

  • Modulo: x % y

  • Integer division: x // y

  • Floating division: x / y

  • Power: x ** y

Literal numbers will automatically be promoted to expressions when used in any of the operations above. They may also be manually wrapped with literal().

Other types of expressions may be created by one of the functions below:

See also Predicate for the list of supported comparison operators.

Quantifiers

class opvious.modeling.Quantifier(identifier: QuantifierIdentifier)

An expression used to index a quantifiable space

Quantifiers are generated by using cross() and its convenience alternatives (for example iterating on a Dimension). You should not need to instantiate them directly - this class is only exposed for typing purposes.

Predicates

class opvious.modeling.Predicate

A predicate on expressions

Instances of this class are generated by using comparison operators on Expression instances. The example below shows the three types of predicates supported as constraints:

class ProductModel(Model):
    products = Dimension()
    cost = Parameter(products)
    count = Variable(products)

    @constraint
    def total_cost_is_at_most_100(self):
        total_cost = total(
            self.cost(p) * self.count(p)
            for p in self.products
        )
        yield total_cost <= 100  # LEQ predicate

    @constraint
    def each_count_is_at_least_10(self):
        for p in self.products:
            yield self.count(p) >= 10  # GEQ predicate

    @constraint
    def exactly_10_of_expensive_items(self):
        for p in self.products:
            if self.cost(p) > 50:  # See below
                yield self.count(p) == 10  # EQ predicate

Additional types of predicates may be generated on expressions which do not include variables and used as conditionals within a quantified expression. For example the exactly_10_of_expensive_items constraint above uses a greater than predicate to filter the set of products where the constraint applies.

Fragments

class opvious.modeling.ModelFragment

Reusable model sub-component

Model fragments are useful to group related definitions together and expose them in a reusable way. See the API reference for the list of available fragments.

property default_definition: Label | None

Optional label that will be used as the fragment’s default.

The matching definition’s final label will be shortened to the fragment’s prefix.