## What is a model ?

In Qaekwy, a model represents a formalized abstraction of a real-world problem that encapsulates the problem’s variables, constraints, objectives, and relationships in a structured manner. It serves as a mathematical or logical representation of the problem that allows you to define the problem’s key components and the rules governing their interactions.

Models in Qaekwy provide a way to express complex problem-solving scenarios in a concise and systematic manner, making it easier to reason about the problem and find optimal or feasible solutions.

By defining variables to represent entities of interest, constraints to capture the limitations or requirements, and objectives to quantify goals, a model serves as the foundation for utilizing Qaekwy’s constraint-based solving capabilities to derive solutions that satisfy the specified conditions and criteria.

## The Modeller

In Qaekwy, any problem model is represented using the `Modeller`

class, which serves as a foundational
framework for constructing and solving constraint-based problems. This class provides the structure to
welcome variables, constraints, objectives, and other components of a problem model.

You can instantiate a `Modeller`

using the following code:

```
from qaekwy.model.modeller import Modeller
my_model = Modeller()
```

It offers a standardized way to encapsulate the problem’s complexity, enabling concise and organized
modelling. Additionally, the `Modeller`

class can be easily extended through object-oriented programming
(OOP) principles. This means that you can create derived classes by inheriting from `Modeller`

allowing you to build specialized models with tailored functionalities while still leveraging the
core capabilities provided by Qaekwy’s modelling framework.

If you prefer the OOP approach, you can derivate the `Modeller`

using the following code:

```
from qaekwy.model.modeller import Modeller
class MyGenericProblem(Modeller):
def __init__(self):
super().__init__()
...
```

## Variables

Variables represent the entities or quantities that need to be manipulated, optimized, or constrained to solve a specific problem. These variables encapsulate the values that the solver will determine or evaluate during the solution process.

**Variables can come in three main forms: arrays, non-arrays and expression.**

Both array or non-array types of variables can be assigned domains, which define the range of values that the variables can take. By defining variables and their domains, you provide the solver with the foundation to explore potential solutions and satisfy the constraints and objectives of the problem.

### Non-Array Variables

These are individual variables that represent single entities or values. For instance, a non-array variable might represent the number of items to be produced in a manufacturing process or the cost of a specific resource. These variables are used when the problem involves optimizing or making decisions about discrete values.

You can instantiate a new variable that can only take one of the values of `[2, 4, 6, 8]`

for your model,
(here an `IntegerVariable`

) using the following code:

```
from qaekwy.model.variable.integer import IntegerVariable
my_var = IntegerVariable(var_name="my_var", specific_domain=[2, 4, 6, 8])
```

Or if you prefer the OOP approache you can proceed this way to create, for example, a variable that
can take a value within the `[1, 9]`

integers interval:

```
from qaekwy.model.variable.integer import IntegerVariable
class SudokuCase(IntegerVariable):
def __init__(self, position_x, position_y):
super().__init__(var_name=f"{position_x}_{position_y}", domain_low=1, domain_high=9))
...
for x in range(0, 9):
for y in range(0, 9):
my_model.add_variable(SudokuCase(x, y))
```

### Array Variables

Array variables represent collections of related values. They are used when the problem requires managing multiple interconnected variables. An array variable can represent, for example, a sequence of activities in a scheduling problem or the distribution of resources across different time periods. Array variables are particularly useful for modelling scenarios with inherent structures or sequences.

You can instantiate a new variable for your model (here an `IntegerArrayVariable`

) using the following code:

```
from qaekwy.model.variable.integer import IntegerArrayVariable
my_array_variable = IntegerArrayVariable(var_name="my_array", length=5, domain_low=0, domain_high=10)
```

Or if you prefer the OOP approache, you can proceed this way:

```
from qaekwy.model.variable.integer import IntegerArrayVariable
class MySudokuGrid(IntegerArrayVariable):
def __init__(self):
super().__init__(var_name="grid", length=9*9, domain_low=1, domain_high=9)
my_custom_array_variable = MyCustomArrayVariable(var_name="custom_array", length=3, domain_low=-5, domain_high=5)
```

## Constraints

Constraints in a Qaekwy model define the rules, limitations, and relationships that the variables within the model must adhere to. They capture the conditions that solutions must satisfy to be considered valid. Constraints can represent various aspects of a problem, such as logical conditions, inequalities, equalities, and dependencies between variables. These constraints guide the solver in generating solutions that fulfill the specified requirements and objectives of the problem.

For example, in a scheduling problem, constraints might ensure that certain tasks are scheduled before others, that resource capacities are not exceeded, or that specific deadlines are met. In an optimization problem, constraints could enforce budget limits, production capacity, or other business-related considerations. Qaekwy provides a range of constraint types, allowing you to model diverse real-world scenarios accurately and systematically.

```
from qaekwy.model.constraint.relational import RelationalExpression
from qaekwy.model.variable.integer import IntegerVariable
# Creating IntegerVariable instances
x = IntegerVariable(var_name="x", domain_low=0, domain_high=10)
y = IntegerVariable(var_name="y", domain_low=0, domain_high=10)
# Defining a constraint using a relational expression
constraint = RelationalExpression(x + y <= 15)
# Adding the constraint to the model
my_model.add_constraint(constraint)
```

## Objectives

In a Qaekwy model, objectives define the goals or criteria that you want the solver to optimize during the
solution process. Objectives quantify the desired outcomes of the problem and guide the solver in finding solutions
that either *maximize* or *minimize* these outcomes based on the problem’s context.

Objectives can be expressed as mathematical expressions that involve the variables of the model. These expressions can represent quantities like costs, profits, time, distances, or any other measure that is relevant to the problem. By incorporating objectives into the model, you provide the solver with a clear sense of what constitutes an ideal solution.

For example, in an optimization problem, an objective might be to *minimize* production costs, *maximize* revenue, or
*minimize* travel time. In a scheduling problem, objectives could involve minimizing the completion time of tasks,
maximizing resource utilization, or minimizing delays.

To incorporate objectives into your Qaekwy model, you can utilize the `add_objective`

method provided by the `Modeller`

class.
This method allows you to add instances of `SpecificMinimum`

or `SpecificMaximum`

classes, which represent the objective to
*minimize* or *maximize* a specific variable, respectively.

**Caution: optimization is available with a SearcherType set to Branch-and-bound (BAB) ONLY !**

For instance, consider the following code:

```
from qaekwy.model.modeller import Modeller
from qaekwy.model.variable.integer import IntegerVariable
from qaekwy.model.specific import SpecificMaximum
# Create a Modeller instance
my_model = Modeller()
# Define an IntegerVariable named "profit"
profit = IntegerVariable(var_name="profit", domain_low=0, domain_high=100)
# ...
# Create an objective to maximize the "profit" variable
maximize_profit = SpecificMaximum(variable=profit)
# Add the objective to the model
my_model.add_objective(maximize_profit)
```

In this example, the `maximize_profit`

objective is added to the `my_model`

instance using the `add_objective`

method.
This process guides the solver to focus on optimizing the `profit`

variable while adhering to the defined constraints.