From 240005977d6500621fde8d7d4f671f623a92bab2 Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:07:49 +0100
Subject: [PATCH 1/9] add: dev-0.3.1
---
.github/workflows/ci.yml | 52 +++
README.md | 61 ++--
pyproject.toml | 67 ++++
qaekwy/__init__.py | 34 +-
qaekwy/__main__.py | 12 +-
qaekwy/__metadata__.py | 2 +-
qaekwy/api/model.py | 158 ++++-----
qaekwy/core/engine.py | 23 +-
qaekwy/core/model/constraint/abs.py | 4 +-
qaekwy/core/model/constraint/acos.py | 4 +-
qaekwy/core/model/constraint/asin.py | 4 +-
qaekwy/core/model/constraint/atan.py | 4 +-
qaekwy/core/model/constraint/cos.py | 4 +-
qaekwy/core/model/constraint/distinct.py | 4 +-
qaekwy/core/model/constraint/divide.py | 4 +-
qaekwy/core/model/constraint/element.py | 4 +-
qaekwy/core/model/constraint/exponential.py | 4 +-
qaekwy/core/model/constraint/if_then_else.py | 5 +-
qaekwy/core/model/constraint/logarithm.py | 4 +-
qaekwy/core/model/constraint/maximum.py | 4 +-
qaekwy/core/model/constraint/member.py | 4 +-
qaekwy/core/model/constraint/minimum.py | 4 +-
qaekwy/core/model/constraint/modulo.py | 4 +-
qaekwy/core/model/constraint/multiply.py | 4 +-
qaekwy/core/model/constraint/nroot.py | 4 +-
qaekwy/core/model/constraint/power.py | 4 +-
qaekwy/core/model/constraint/relational.py | 4 +-
qaekwy/core/model/constraint/sin.py | 4 +-
qaekwy/core/model/constraint/sort.py | 4 +-
qaekwy/core/model/constraint/tan.py | 4 +-
qaekwy/core/model/function.py | 2 +-
qaekwy/core/model/modeller.py | 227 ++++++-------
qaekwy/core/model/relation.py | 52 ---
qaekwy/core/model/specific.py | 4 +-
qaekwy/core/model/variable/boolean.py | 18 +-
qaekwy/core/model/variable/float.py | 12 +-
qaekwy/core/model/variable/integer.py | 17 +-
qaekwy/core/model/variable/variable.py | 131 +++-----
qaekwy/core/response.py | 18 +-
qaekwy/core/solution.py | 2 +-
setup.py | 87 -----
tests/api/test_model.py | 316 ++++++++++++++++++
tests/{ => core}/model/constraint/test_abs.py | 0
.../{ => core}/model/constraint/test_acos.py | 0
.../{ => core}/model/constraint/test_asin.py | 0
.../{ => core}/model/constraint/test_atan.py | 0
tests/{ => core}/model/constraint/test_cos.py | 0
tests/core/model/constraint/test_distinct.py | 218 ++++++++++++
.../model/constraint/test_divide.py | 0
.../model/constraint/test_element.py | 0
.../model/constraint/test_exponential.py | 0
.../model/constraint/test_if_then_else.py | 0
.../model/constraint/test_logarithm.py | 0
.../model/constraint/test_maximum.py | 0
.../model/constraint/test_member.py | 0
.../model/constraint/test_minimum.py | 0
.../model/constraint/test_modulo.py | 0
.../model/constraint/test_multiply.py | 0
.../{ => core}/model/constraint/test_nroot.py | 0
.../{ => core}/model/constraint/test_power.py | 0
.../model/constraint/test_relational.py | 0
tests/{ => core}/model/constraint/test_sin.py | 0
.../{ => core}/model/constraint/test_sort.py | 0
tests/{ => core}/model/constraint/test_tan.py | 0
tests/core/model/test_cutoff.py | 164 +++++++++
tests/core/model/test_function.py | 62 ++++
tests/core/model/test_modeller.py | 243 ++++++++++++++
tests/core/model/variable/test_boolean.py | 138 ++++++++
.../model/variable/test_expression.py | 0
tests/core/model/variable/test_float.py | 180 ++++++++++
tests/core/model/variable/test_integer.py | 291 ++++++++++++++++
tests/{ => core}/test_explanation.py | 0
tests/core/test_response.py | 105 ++++++
tests/core/test_solution.py | 170 ++++++++++
tests/model/constraint/test_distinct.py | 151 ---------
tests/model/test_modeller.py | 105 ------
tests/model/variable/test_integer.py | 102 ------
tests/test_solution.py | 65 ----
78 files changed, 2339 insertions(+), 1039 deletions(-)
create mode 100644 .github/workflows/ci.yml
create mode 100644 pyproject.toml
delete mode 100644 qaekwy/core/model/relation.py
delete mode 100644 setup.py
create mode 100644 tests/api/test_model.py
rename tests/{ => core}/model/constraint/test_abs.py (100%)
rename tests/{ => core}/model/constraint/test_acos.py (100%)
rename tests/{ => core}/model/constraint/test_asin.py (100%)
rename tests/{ => core}/model/constraint/test_atan.py (100%)
rename tests/{ => core}/model/constraint/test_cos.py (100%)
create mode 100644 tests/core/model/constraint/test_distinct.py
rename tests/{ => core}/model/constraint/test_divide.py (100%)
rename tests/{ => core}/model/constraint/test_element.py (100%)
rename tests/{ => core}/model/constraint/test_exponential.py (100%)
rename tests/{ => core}/model/constraint/test_if_then_else.py (100%)
rename tests/{ => core}/model/constraint/test_logarithm.py (100%)
rename tests/{ => core}/model/constraint/test_maximum.py (100%)
rename tests/{ => core}/model/constraint/test_member.py (100%)
rename tests/{ => core}/model/constraint/test_minimum.py (100%)
rename tests/{ => core}/model/constraint/test_modulo.py (100%)
rename tests/{ => core}/model/constraint/test_multiply.py (100%)
rename tests/{ => core}/model/constraint/test_nroot.py (100%)
rename tests/{ => core}/model/constraint/test_power.py (100%)
rename tests/{ => core}/model/constraint/test_relational.py (100%)
rename tests/{ => core}/model/constraint/test_sin.py (100%)
rename tests/{ => core}/model/constraint/test_sort.py (100%)
rename tests/{ => core}/model/constraint/test_tan.py (100%)
create mode 100644 tests/core/model/test_cutoff.py
create mode 100644 tests/core/model/test_function.py
create mode 100644 tests/core/model/test_modeller.py
create mode 100644 tests/core/model/variable/test_boolean.py
rename tests/{ => core}/model/variable/test_expression.py (100%)
create mode 100644 tests/core/model/variable/test_float.py
create mode 100644 tests/core/model/variable/test_integer.py
rename tests/{ => core}/test_explanation.py (100%)
create mode 100644 tests/core/test_response.py
create mode 100644 tests/core/test_solution.py
delete mode 100644 tests/model/constraint/test_distinct.py
delete mode 100644 tests/model/test_modeller.py
delete mode 100644 tests/model/variable/test_integer.py
delete mode 100644 tests/test_solution.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..6eb27d0
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,52 @@
+name: CI
+
+on:
+ pull_request:
+ branches:
+ - main
+ push:
+ branches:
+ - "dev-[0-9]*-[0-9]*-[0-9]*"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ cache: pip
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install ".[dev]"
+ pip install requests types-requests
+
+ - name: Lint with Black
+ run: black --check qaekwy
+
+ - name: Lint with Pylint
+ run: pylint qaekwy
+
+ - name: Type check with MyPy
+ run: mypy qaekwy
+
+ - name: Test with Pytest + coverage
+ run: |
+ python -m pytest --cov=qaekwy --cov-report=xml --cov-fail-under=80 tests
+
+# - name: Upload coverage to Codecov
+# uses: codecov/codecov-action@v4
+# with:
+# files: coverage.xml
+# fail_ci_if_error: true
diff --git a/README.md b/README.md
index 4406d70..bfb0a2f 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@ Qaekwy is a Python library designed for modeling and solving combinatorial optim
It provides a clean, Pythonic interface for defining variables, constraints, and objectives, enabling a natural *define-and-solve* workflow. Qaekwy manages the interaction with the solver engine, allowing users to focus entirely on expressing the structure of their problems.
-Perfect for:
+#### Perfect for
-* 🎓 **Learning**: Students can *quickly model* and solve problems.
-* 👩🏫 **Teaching**: Instructors can *demo core CSP concepts* with **minimal setup**.
-* 🌟 **Discovering**: Researchers can *explore* strategies, heuristics, and models.
+* 🎓 **Learning** — Model real problems in minutes
+* 👩🏫 **Teaching** — Demonstrate CSP concepts with no setup
+* 🔬 **Research & Prototyping** — Explore models, heuristics, and ideas fast
## 🚀 Quick Start
@@ -26,11 +26,11 @@ Perfect for:
### Installation
-Install the production-ready client via PyPI:
-
-`pip install qaekwy`
+```shell
+pip install qaekwy
+```
-### Your First Model
+### 🌱 Your First Model
```python
import qaekwy as qw
@@ -42,12 +42,9 @@ y = m.integer_variable("y", (-10, 10))
z = m.integer_variable("z", (-10, 10))
m.constraint(x + 2*y + 3*z <= 15)
-m.constraint(x + y >= 5)
-
-m.minimize(z)
+m.maximize(x)
-solution = m.solve_one()
-solution.pretty_print()
+m.solve_one().pretty_print()
```
*Output*:
@@ -56,14 +53,12 @@ solution.pretty_print()
----------------------------------------
Solution:
----------------------------------------
-x: 6
-y: 8
-z: -3
+x: -3
+y: 2
+z: 4
----------------------------------------
```
-*That's it.*
-
## Capabilities
@@ -84,11 +79,14 @@ Transparent handling of model serialization and execution on the Qaekwy Cloud So
Visit the [Qaekwy Documentation](https://docs.qaekwy.io/) for guides, teaching resources, and detailed examples.
----
+## Examples
+
+### 🔢 Constraint Programming -- Sudoku
-### 🧩 Constraint Programming Example: Sudoku
+Here is a complete example solving a [Sudoku](https://en.wikipedia.org/wiki/Sudoku) grid:
-Here is a complete example solving a Sudoku grid:
+> The objective is to fill a 9 × 9 grid with digits so that each column, each row, and each
+> of the nine 3 × 3 subgrids that compose the grid contains all of the digits from 1 to 9.
```python
import qaekwy as qw
@@ -108,7 +106,7 @@ my_problem = [
[0, 2, 0, 0, 0, 0, 5, 7, 0]
]
-# Initialize the model container
+# Instantiate the model
m = qw.Model()
# Create a 9x9 matrix of integer variables
@@ -122,7 +120,7 @@ for i in range(9):
# Ensure all variables in column 'i' are unique
m.constraint_distinct(grid.col(i))
-# Iterate in steps of 3 (0, 3, 6) to find the top-left corner of each block
+# Iterate over 3x3 blocks
for i in range(0, 9, 3):
for j in range(0, 9, 3):
# Extract the 3x3 block and enforce uniqueness
@@ -161,7 +159,7 @@ grid: (9 x 9 matrix)
----------------------------------------
```
-### 🎒 Optimization Example: Knapsack Problem
+### 🎒 Optimization -- Knapsack Problem
Here is a complete example solving a basic resource allocation problem ([The Knapsack Problem](https://en.wikipedia.org/wiki/Knapsack_problem)):
@@ -207,18 +205,18 @@ print(f"Max Value: {solution.total_value}")
# Output: Max Value: 9
```
-#### 💡 Core Concepts
+## 💡 Core Concepts
-##### The Model
+### The Model
The `qw.Model` acts as the container for your variables and constraints. It also manages the interaction with
the underlying solver engine.
-##### The Variables
+#### The Variables
Here are examples of variable creation in the model:
@@ -230,7 +228,7 @@ capacity = m.integer_variable("capacity", domain=(0, 100))
grid = m.integer_matrix("grid", rows=9, cols=9, domain=(1, 9))
```
-##### The Constraints
+#### The Constraints
Constraints are logical assertions that must be true in any valid solution.
@@ -239,7 +237,7 @@ Constraints are logical assertions that must be true in any valid solution.
m.constraint(x * 2 < qw.math.power(y, 2) + 5)
```
-#### Modeling Capabilities
+### Modeling Capabilities
Qaekwy supports:
@@ -278,9 +276,9 @@ m.constraint(sum(mat.col(0)) > arr[2])
- `solve_one()` — find one feasible or optimal solution
- `solve()` — returns a list of solutions
-- minimize(...) / maximize(...) — Set one or more objectives on variables
+- `minimize(...)` / `maximize(...)` — Set one or more objectives on variables
- Searchers such as DFS, Branch-and-Bound, etc.
-- Cloud-based Solver instance (please, refer to [Terms & Conditions](https://docs.qaekwy.io/docs/terms-and-conditions/))
+- Cloud-based Solver instance (*please, refer to [Terms & Conditions](https://docs.qaekwy.io/docs/terms-and-conditions/)*)
#### Integration
@@ -291,7 +289,6 @@ The model is then sent to the Qaekwy Cloud Engine through REST API.
----
## License
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..3ddce64
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,67 @@
+[build-system]
+requires = ["setuptools>=61.0", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "qaekwy"
+dynamic = ["version"]
+description = "Qaekwy, a modern, open-source Python framework for declarative constraint programming and combinatorial optimization"
+readme = "README.md"
+requires-python = ">=3.9"
+license = "EUPL-1.2"
+authors = [
+ {name = "Alexis LE GOADEC", email = "alex@qaekwy.io"},
+]
+keywords = [
+ "optimization", "constraint programming", "combinatorial optimization",
+ "operations research", "constraint satisfaction", "constraint solver",
+ "solver", "CSP", "optimization library", "optimization framework",
+ "mathematical optimization", "discrete optimization", "optimization modeling",
+ "constraint modeling", "declarative programming", "modeling language",
+ "DSL", "define-and-solve", "scheduling", "routing", "planning",
+ "resource allocation", "assignment", "decision support", "open source", "Python"
+]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Education",
+ "Intended Audience :: Science/Research",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Topic :: Scientific/Engineering",
+ "Topic :: Scientific/Engineering :: Mathematics",
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+]
+dependencies = [
+ "requests"
+]
+
+[project.optional-dependencies]
+types = [
+ "types-requests",
+]
+dev = [
+ "pytest>=7.0",
+ "pytest-cov",
+ "black",
+ "mypy",
+ "build",
+ "twine",
+]
+
+[project.urls]
+Homepage = "https://qaekwy.io"
+Documentation = "https://docs.qaekwy.io"
+"Issue Tracker" = "https://github.com/alex-87/qaekwy-python/issues"
+Source = "https://github.com/alex-87/qaekwy-python"
+
+[tool.setuptools.dynamic]
+version = {attr = "qaekwy.__metadata__.__version__"}
+
+[tool.setuptools.packages.find]
+where = ["."]
+include = ["qaekwy*"]
+
+[tool.pylint]
+disable = ["R0801", "R0917", "R0913", "R0902", "R0903", "C0301", "R0911", "R0912"]
diff --git a/qaekwy/__init__.py b/qaekwy/__init__.py
index 22cf204..dcbefab 100644
--- a/qaekwy/__init__.py
+++ b/qaekwy/__init__.py
@@ -1,29 +1,15 @@
"""QAekwy API Module Initialization."""
-from qaekwy.api.model import Model
-from qaekwy.api.exceptions import SolverError
-from qaekwy.core.model.variable.branch import (
- BranchBooleanVal,
- BranchBooleanVar,
- BranchIntegerVal,
- BranchIntegerVar,
- BranchFloatVal,
- BranchFloatVar,
-)
-from qaekwy.core.model.cutoff import (
- Cutoff,
- CutoffConstant,
- CutoffFibonacci,
- CutoffLinear,
- CutoffLuby,
- CutoffGeometric,
- CutoffRandom,
- MetaCutoffAppender,
- MetaCutoffMerger,
- MetaCutoffRepeater,
-)
-
-import qaekwy.core.model.function as math
+from .api.exceptions import SolverError
+from .api.model import Model
+from .core.model import function as math
+from .core.model.cutoff import (Cutoff, CutoffConstant, CutoffFibonacci,
+ CutoffGeometric, CutoffLinear, CutoffLuby,
+ CutoffRandom, MetaCutoffAppender,
+ MetaCutoffMerger, MetaCutoffRepeater)
+from .core.model.variable.branch import (BranchBooleanVal, BranchBooleanVar,
+ BranchFloatVal, BranchFloatVar,
+ BranchIntegerVal, BranchIntegerVar)
__all__ = [
"Model",
diff --git a/qaekwy/__main__.py b/qaekwy/__main__.py
index b498e02..aa2407f 100644
--- a/qaekwy/__main__.py
+++ b/qaekwy/__main__.py
@@ -4,14 +4,8 @@
import argparse
-from qaekwy.__metadata__ import (
- __author__,
- __copyright__,
- __license__,
- __license_url__,
- __software__,
- __version__,
-)
+from .__metadata__ import (__author__, __copyright__, __license__,
+ __license_url__, __software__, __version__)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Qaekwy Python Library")
@@ -29,5 +23,5 @@
print(f"Licensed under the {__license__}")
print(f"You may obtain a copy of the License at {__license_url__}")
print(
- "You are free to use, modify, and redistribute this work under the terms of this licence."
+ "You are free to use, modify, and redistribute this work under the terms of this license."
)
diff --git a/qaekwy/__metadata__.py b/qaekwy/__metadata__.py
index 17e20b7..66f9975 100644
--- a/qaekwy/__metadata__.py
+++ b/qaekwy/__metadata__.py
@@ -8,4 +8,4 @@
__license_url__ = "https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12"
__software__ = "Qaekwy"
__author_email__ = "alex@qaekwy.io"
-__version__ = "0.3.0"
+__version__ = "0.3.1"
diff --git a/qaekwy/api/model.py b/qaekwy/api/model.py
index 26c3cf1..2867479 100644
--- a/qaekwy/api/model.py
+++ b/qaekwy/api/model.py
@@ -14,70 +14,55 @@
as well as for solving the model and retrieving solutions.
"""
-from typing import Optional
-
-from qaekwy.api.exceptions import SolverError
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.constraint.multiply import ConstraintMultiply
-from qaekwy.core.solution import Solution
-
-from qaekwy.core.model.modeller import Modeller
-from qaekwy.core.response import SolutionResponse
-
-from qaekwy.core.model import DIRECTENGINE_API_ENDPOINT
-
-from qaekwy.core.model.variable.variable import (
- MatrixVariable,
- Variable,
- ArrayVariable,
- Expression,
- VariableType,
- VectorExpression,
-)
-from qaekwy.core.model.variable.integer import (
- IntegerVariable,
- IntegerExpressionVariable,
-)
-from qaekwy.core.model.variable.integer import IntegerVariableArray
-from qaekwy.core.model.variable.branch import BranchIntegerVal, BranchIntegerVar
-from qaekwy.core.model.variable.float import FloatVariable, FloatExpressionVariable
-from qaekwy.core.model.variable.float import FloatVariableArray
-from qaekwy.core.model.variable.branch import BranchFloatVal, BranchFloatVar
-from qaekwy.core.model.variable.boolean import BooleanVariable
-from qaekwy.core.model.variable.boolean import BooleanVariableArray
-from qaekwy.core.model.variable.branch import BranchBooleanVal, BranchBooleanVar
-
-from qaekwy.core.model.constraint.abs import ConstraintAbs
-from qaekwy.core.model.constraint.acos import ConstraintACos
-from qaekwy.core.model.constraint.asin import ConstraintASin
-from qaekwy.core.model.constraint.atan import ConstraintATan
-from qaekwy.core.model.constraint.cos import ConstraintCos
-from qaekwy.core.model.constraint.distinct import (
- ConstraintDistinctArray,
- ConstraintDistinctRow,
- ConstraintDistinctCol,
- ConstraintDistinctSlice,
-)
-from qaekwy.core.model.constraint.divide import ConstraintDivide
-from qaekwy.core.model.constraint.element import ConstraintElement
-from qaekwy.core.model.constraint.exponential import ConstraintExponential
-from qaekwy.core.model.constraint.sin import ConstraintSin
-from qaekwy.core.model.constraint.tan import ConstraintTan
-from qaekwy.core.model.constraint.logarithm import ConstraintLogarithm
-from qaekwy.core.model.constraint.maximum import ConstraintMaximum
-from qaekwy.core.model.constraint.minimum import ConstraintMinimum
-from qaekwy.core.model.constraint.member import ConstraintMember
-from qaekwy.core.model.constraint.modulo import ConstraintModulo
-from qaekwy.core.model.constraint.nroot import ConstraintNRoot
-from qaekwy.core.model.constraint.power import ConstraintPower
-from qaekwy.core.model.constraint.if_then_else import ConstraintIfThenElse
-from qaekwy.core.model.constraint.sort import ConstraintSorted, ConstraintReverseSorted
-
-from qaekwy.core.model.specific import SpecificMaximum, SpecificMinimum
-
-from qaekwy.core.model.searcher import SearcherType
-from qaekwy.core.model.cutoff import Cutoff
-from qaekwy.core.engine import DirectEngine
+from typing import Optional, Union
+
+from ..core.engine import DirectEngine
+from ..core.model import DIRECTENGINE_API_ENDPOINT
+from ..core.model.constraint.abs import ConstraintAbs
+from ..core.model.constraint.abstract_constraint import AbstractConstraint
+from ..core.model.constraint.acos import ConstraintACos
+from ..core.model.constraint.asin import ConstraintASin
+from ..core.model.constraint.atan import ConstraintATan
+from ..core.model.constraint.cos import ConstraintCos
+from ..core.model.constraint.distinct import (ConstraintDistinctArray,
+ ConstraintDistinctCol,
+ ConstraintDistinctRow,
+ ConstraintDistinctSlice)
+from ..core.model.constraint.divide import ConstraintDivide
+from ..core.model.constraint.element import ConstraintElement
+from ..core.model.constraint.exponential import ConstraintExponential
+from ..core.model.constraint.if_then_else import ConstraintIfThenElse
+from ..core.model.constraint.logarithm import ConstraintLogarithm
+from ..core.model.constraint.maximum import ConstraintMaximum
+from ..core.model.constraint.member import ConstraintMember
+from ..core.model.constraint.minimum import ConstraintMinimum
+from ..core.model.constraint.modulo import ConstraintModulo
+from ..core.model.constraint.multiply import ConstraintMultiply
+from ..core.model.constraint.nroot import ConstraintNRoot
+from ..core.model.constraint.power import ConstraintPower
+from ..core.model.constraint.sin import ConstraintSin
+from ..core.model.constraint.sort import (ConstraintReverseSorted,
+ ConstraintSorted)
+from ..core.model.constraint.tan import ConstraintTan
+from ..core.model.cutoff import Cutoff
+from ..core.model.modeller import Modeller
+from ..core.model.searcher import SearcherType
+from ..core.model.specific import SpecificMaximum, SpecificMinimum
+from ..core.model.variable.boolean import BooleanVariable, BooleanVariableArray, BooleanVariableMatrix
+from ..core.model.variable.branch import (BranchBooleanVal, BranchBooleanVar,
+ BranchFloatVal, BranchFloatVar,
+ BranchIntegerVal, BranchIntegerVar)
+from ..core.model.variable.float import (FloatExpressionVariable,
+ FloatVariable, FloatVariableArray, FloatVariableMatrix)
+from ..core.model.variable.integer import (IntegerExpressionVariable,
+ IntegerVariable,
+ IntegerVariableArray, IntegerVariableMatrix)
+from ..core.model.variable.variable import (ArrayVariable, Expression,
+ MatrixVariable, Variable,
+ VariableType, VectorExpression)
+from ..core.response import SolutionResponse
+from ..core.solution import Solution
+from .exceptions import SolverError
class Model: # pylint: disable=too-many-public-methods
@@ -109,7 +94,7 @@ def integer_variable(
expression: Optional[str] = None,
branch_val: BranchIntegerVal = BranchIntegerVal.VAL_RND,
branch_order: Optional[int] = -1,
- ) -> IntegerVariable | IntegerExpressionVariable:
+ ) -> Union[IntegerVariable, IntegerExpressionVariable]:
"""
Creates an integer variable or an integer expression variable.
@@ -195,7 +180,7 @@ def integer_matrix(
branch_var: BranchIntegerVar = BranchIntegerVar.VAR_RND,
branch_val: BranchIntegerVal = BranchIntegerVal.VAL_RND,
branch_order: Optional[int] = -1,
- ) -> MatrixVariable:
+ ) -> IntegerVariableMatrix:
"""
Creates a matrix of integer variables.
@@ -210,10 +195,10 @@ def integer_matrix(
branch_order (int, optional): The branching order.
Returns:
- MatrixVariable: The created variable matrix.
+ IntegerVariableMatrix: The created variable matrix.
"""
l, h = domain
- v: MatrixVariable = MatrixVariable(
+ v: IntegerVariableMatrix = IntegerVariableMatrix(
var_name=name,
rows=rows,
cols=cols,
@@ -234,7 +219,7 @@ def float_variable(
expression: Optional[str] = None,
branch_val: BranchFloatVal = BranchFloatVal.VAL_RND,
branch_order: int = -1,
- ) -> FloatVariable | FloatExpressionVariable:
+ ) -> Union[FloatVariable, FloatExpressionVariable]:
"""
Creates a float variable or a float expression variable.
@@ -310,11 +295,10 @@ def float_matrix(
rows: int,
cols: int,
domain: tuple[float, float],
- specific_domain: Optional[list[float]] = None,
branch_var: BranchFloatVar = BranchFloatVar.VAR_RND,
branch_val: BranchFloatVal = BranchFloatVal.VAL_RND,
branch_order: Optional[int] = -1,
- ) -> MatrixVariable:
+ ) -> FloatVariableMatrix:
"""
Creates a matrix of float variables.
@@ -323,23 +307,20 @@ def float_matrix(
rows (int): The number of rows in the matrix.
cols (int): The number of columns in the matrix.
domain (tuple[float, float]): A tuple representing the (low, high) domain of the variables.
- specific_domain (list[float], optional): A specific domain for the variables.
branch_var (BranchFloatVar, optional): The brancher variable strategy.
branch_val (BranchFloatVal, optional): The brancher value strategy.
branch_order (int, optional): The branching order.
Returns:
- MatrixVariable: The created variable matrix.
+ FloatVariableMatrix: The created variable matrix.
"""
l, h = domain
- v: MatrixVariable = MatrixVariable(
+ v: FloatVariableMatrix = FloatVariableMatrix(
var_name=name,
rows=rows,
cols=cols,
- var_type=VariableType.FLOAT_ARRAY,
domain_low=l,
domain_high=h,
- specific_domain=specific_domain,
branch_var=branch_var,
branch_val=branch_val,
branch_order=branch_order,
@@ -406,12 +387,10 @@ def boolean_matrix(
name: str,
rows: int,
cols: int,
- domain: tuple[bool, bool],
- specific_domain: Optional[list[bool]] = None,
branch_var: BranchBooleanVar = BranchBooleanVar.VAR_RND,
branch_val: BranchBooleanVal = BranchBooleanVal.VAL_RND,
branch_order: Optional[int] = -1,
- ) -> MatrixVariable:
+ ) -> BooleanVariableMatrix:
"""
Creates a matrix of boolean variables.
@@ -419,24 +398,17 @@ def boolean_matrix(
name (str): The name of the variable matrix.
rows (int): The number of rows in the matrix.
cols (int): The number of columns in the matrix.
- domain (tuple[boolean, boolean]): A tuple representing the (low, high) domain of the variables.
- specific_domain (list[boolean], optional): A specific domain for the variables.
branch_var (BranchBooleanVar, optional): The brancher variable strategy.
branch_val (BranchBooleanVal, optional): The brancher value strategy.
branch_order (int, optional): The branching order.
Returns:
- MatrixVariable: The created variable matrix.
+ BooleanVariableMatrix: The created variable matrix.
"""
- l, h = domain
- v: MatrixVariable = MatrixVariable(
+ v: BooleanVariableMatrix = BooleanVariableMatrix(
var_name=name,
rows=rows,
cols=cols,
- var_type=VariableType.BOOLEAN_ARRAY,
- domain_low=l,
- domain_high=h,
- specific_domain=specific_domain,
branch_var=branch_var,
branch_val=branch_val,
branch_order=branch_order,
@@ -509,7 +481,7 @@ def constraint_cos(self, var_1: Variable, var_2: Variable) -> None:
constraint = ConstraintCos(var_1, var_2)
self._modeller.add_constraint(constraint)
- def constraint_distinct(self, var: ArrayVariable | VectorExpression) -> None:
+ def constraint_distinct(self, var: Union[ArrayVariable, VectorExpression]) -> None:
"""
Add a distinct constraint.
@@ -808,8 +780,8 @@ def solve(
self,
searcher: str = "dfs",
solution_limit: int = 1,
- cutoff: Cutoff | None = None,
- ) -> list[Solution] | None:
+ cutoff: Union[Cutoff, None] = None,
+ ) -> Union[list[Solution], None]:
"""
Solves the model.
@@ -852,8 +824,8 @@ def solve(
return solution_response.get_solutions()
def solve_one(
- self, searcher: str = "dfs", cutoff: Cutoff | None = None
- ) -> Solution | None:
+ self, searcher: str = "dfs", cutoff: Union[Cutoff, None] = None
+ ) -> Union[Solution, None]:
"""
Solves the model and returns one solution.
diff --git a/qaekwy/core/engine.py b/qaekwy/core/engine.py
index 2d40413..3d684bf 100644
--- a/qaekwy/core/engine.py
+++ b/qaekwy/core/engine.py
@@ -78,23 +78,12 @@
import requests
-from qaekwy.core.model import DIRECTENGINE_API_ENDPOINT
-from qaekwy.core.model.modeller import Modeller
-from qaekwy.core.response import (
- AbstractResponse,
- ClusterStatusResponse,
- EchoResponse,
- ExplanationResponse,
- ModelJSonResponse,
- SolutionResponse,
- StatusResponse,
- VersionResponse,
-)
-
-from qaekwy.__metadata__ import (
- __software__,
- __version__,
-)
+from ..__metadata__ import __software__, __version__
+from .model import DIRECTENGINE_API_ENDPOINT
+from .model.modeller import Modeller
+from .response import (AbstractResponse, ClusterStatusResponse, EchoResponse,
+ ExplanationResponse, ModelJSonResponse,
+ SolutionResponse, StatusResponse, VersionResponse)
class AbstractAction(ABC):
diff --git a/qaekwy/core/model/constraint/abs.py b/qaekwy/core/model/constraint/abs.py
index a47145f..ecaaf1d 100644
--- a/qaekwy/core/model/constraint/abs.py
+++ b/qaekwy/core/model/constraint/abs.py
@@ -2,8 +2,8 @@
This module defines the ConstraintAbs class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintAbs(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/acos.py b/qaekwy/core/model/constraint/acos.py
index 4553b43..88c7a70 100644
--- a/qaekwy/core/model/constraint/acos.py
+++ b/qaekwy/core/model/constraint/acos.py
@@ -2,8 +2,8 @@
This module defines the ConstraintACos class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintACos(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/asin.py b/qaekwy/core/model/constraint/asin.py
index 337ecd6..495a0ef 100644
--- a/qaekwy/core/model/constraint/asin.py
+++ b/qaekwy/core/model/constraint/asin.py
@@ -2,8 +2,8 @@
This module defines the ConstraintASin class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintASin(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/atan.py b/qaekwy/core/model/constraint/atan.py
index 6f6e189..6dc2cb4 100644
--- a/qaekwy/core/model/constraint/atan.py
+++ b/qaekwy/core/model/constraint/atan.py
@@ -2,8 +2,8 @@
This module defines the ConstraintATan class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintATan(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/cos.py b/qaekwy/core/model/constraint/cos.py
index a339c23..f233081 100644
--- a/qaekwy/core/model/constraint/cos.py
+++ b/qaekwy/core/model/constraint/cos.py
@@ -2,8 +2,8 @@
This module defines the ConstraintCos class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintCos(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/distinct.py b/qaekwy/core/model/constraint/distinct.py
index 3007068..937ca87 100644
--- a/qaekwy/core/model/constraint/distinct.py
+++ b/qaekwy/core/model/constraint/distinct.py
@@ -2,8 +2,8 @@
This module defines constraints for enforcing distinctness in arrays.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import ArrayVariable, MatrixVariable
+from ..variable.variable import ArrayVariable, MatrixVariable
+from .abstract_constraint import AbstractConstraint
class ConstraintDistinctArray(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/divide.py b/qaekwy/core/model/constraint/divide.py
index f54785c..9f0e058 100644
--- a/qaekwy/core/model/constraint/divide.py
+++ b/qaekwy/core/model/constraint/divide.py
@@ -2,8 +2,8 @@
This module defines the ConstraintDivide class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintDivide(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/element.py b/qaekwy/core/model/constraint/element.py
index 8118e01..92fe25e 100644
--- a/qaekwy/core/model/constraint/element.py
+++ b/qaekwy/core/model/constraint/element.py
@@ -2,8 +2,8 @@
This module defines the ConstraintElement class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import ArrayVariable, Variable
+from ..variable.variable import ArrayVariable, Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintElement(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/exponential.py b/qaekwy/core/model/constraint/exponential.py
index cd41732..0736ed0 100644
--- a/qaekwy/core/model/constraint/exponential.py
+++ b/qaekwy/core/model/constraint/exponential.py
@@ -2,8 +2,8 @@
This module defines the ConstraintExponential class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintExponential(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/if_then_else.py b/qaekwy/core/model/constraint/if_then_else.py
index ff9e034..e71912c 100644
--- a/qaekwy/core/model/constraint/if_then_else.py
+++ b/qaekwy/core/model/constraint/if_then_else.py
@@ -3,8 +3,9 @@
"""
from typing import Optional
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Expression, VariableType
+
+from ..variable.variable import Expression, VariableType
+from .abstract_constraint import AbstractConstraint
class ConstraintIfThenElse(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/logarithm.py b/qaekwy/core/model/constraint/logarithm.py
index 652f56a..0b40b0f 100644
--- a/qaekwy/core/model/constraint/logarithm.py
+++ b/qaekwy/core/model/constraint/logarithm.py
@@ -2,8 +2,8 @@
This module defines the ConstraintLogarithm class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintLogarithm(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/maximum.py b/qaekwy/core/model/constraint/maximum.py
index 9b2c894..3beb5a9 100644
--- a/qaekwy/core/model/constraint/maximum.py
+++ b/qaekwy/core/model/constraint/maximum.py
@@ -2,8 +2,8 @@
This module defines the ConstraintMaximum class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintMaximum(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/member.py b/qaekwy/core/model/constraint/member.py
index faa0953..e65d54e 100644
--- a/qaekwy/core/model/constraint/member.py
+++ b/qaekwy/core/model/constraint/member.py
@@ -2,8 +2,8 @@
This module defines the ConstraintMember class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import ArrayVariable, Variable
+from ..variable.variable import ArrayVariable, Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintMember(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/minimum.py b/qaekwy/core/model/constraint/minimum.py
index d5bac20..9cbcb7e 100644
--- a/qaekwy/core/model/constraint/minimum.py
+++ b/qaekwy/core/model/constraint/minimum.py
@@ -2,8 +2,8 @@
This module defines the ConstraintMinimum class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintMinimum(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/modulo.py b/qaekwy/core/model/constraint/modulo.py
index acf51b3..f876d74 100644
--- a/qaekwy/core/model/constraint/modulo.py
+++ b/qaekwy/core/model/constraint/modulo.py
@@ -2,8 +2,8 @@
This module defines the ConstraintModulo class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintModulo(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/multiply.py b/qaekwy/core/model/constraint/multiply.py
index d4d8cf1..0ae0694 100644
--- a/qaekwy/core/model/constraint/multiply.py
+++ b/qaekwy/core/model/constraint/multiply.py
@@ -2,8 +2,8 @@
This module defines the ConstraintMultiply class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintMultiply(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/nroot.py b/qaekwy/core/model/constraint/nroot.py
index 5c34d60..e054d77 100644
--- a/qaekwy/core/model/constraint/nroot.py
+++ b/qaekwy/core/model/constraint/nroot.py
@@ -2,8 +2,8 @@
This module defines the ConstraintNRoot class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintNRoot(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/power.py b/qaekwy/core/model/constraint/power.py
index 2dc6ef5..bd386f2 100644
--- a/qaekwy/core/model/constraint/power.py
+++ b/qaekwy/core/model/constraint/power.py
@@ -2,8 +2,8 @@
This module defines the ConstraintPower class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintPower(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/relational.py b/qaekwy/core/model/constraint/relational.py
index 3108304..4a7644b 100644
--- a/qaekwy/core/model/constraint/relational.py
+++ b/qaekwy/core/model/constraint/relational.py
@@ -2,8 +2,8 @@
This module defines the RelationalExpression class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Expression, VariableType
+from ..variable.variable import Expression, VariableType
+from .abstract_constraint import AbstractConstraint
class RelationalExpression(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/sin.py b/qaekwy/core/model/constraint/sin.py
index 221bdc0..3451ed0 100644
--- a/qaekwy/core/model/constraint/sin.py
+++ b/qaekwy/core/model/constraint/sin.py
@@ -2,8 +2,8 @@
This module defines the ConstraintSin class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintSin(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/sort.py b/qaekwy/core/model/constraint/sort.py
index bfb28f4..d67ee9b 100644
--- a/qaekwy/core/model/constraint/sort.py
+++ b/qaekwy/core/model/constraint/sort.py
@@ -2,8 +2,8 @@
This module defines constraints for sorting arrays.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import ArrayVariable
+from ..variable.variable import ArrayVariable
+from .abstract_constraint import AbstractConstraint
class ConstraintSorted(AbstractConstraint):
diff --git a/qaekwy/core/model/constraint/tan.py b/qaekwy/core/model/constraint/tan.py
index a7ec2ea..4281664 100644
--- a/qaekwy/core/model/constraint/tan.py
+++ b/qaekwy/core/model/constraint/tan.py
@@ -2,8 +2,8 @@
This module defines the ConstraintTan class.
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from ..variable.variable import Variable
+from .abstract_constraint import AbstractConstraint
class ConstraintTan(AbstractConstraint):
diff --git a/qaekwy/core/model/function.py b/qaekwy/core/model/function.py
index 55e30ba..45d0e1f 100644
--- a/qaekwy/core/model/function.py
+++ b/qaekwy/core/model/function.py
@@ -23,7 +23,7 @@
"""
-from qaekwy.core.model.variable.variable import Expression, ExpressionArray
+from .variable.variable import Expression, ExpressionArray
def maximum(expr: ExpressionArray) -> Expression:
diff --git a/qaekwy/core/model/modeller.py b/qaekwy/core/model/modeller.py
index 5bc15e9..2d93d30 100644
--- a/qaekwy/core/model/modeller.py
+++ b/qaekwy/core/model/modeller.py
@@ -18,49 +18,44 @@
json_model = modeller.to_json()
"""
-from typing import Any, Optional, Union
-
-from qaekwy.core.exception.model_failure import ModelFailure
-
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.constraint.abs import ConstraintAbs
-from qaekwy.core.model.constraint.acos import ConstraintACos
-from qaekwy.core.model.constraint.asin import ConstraintASin
-from qaekwy.core.model.constraint.atan import ConstraintATan
-from qaekwy.core.model.constraint.cos import ConstraintCos
-from qaekwy.core.model.constraint.distinct import (
- ConstraintDistinctArray,
- ConstraintDistinctCol,
- ConstraintDistinctRow,
- ConstraintDistinctSlice,
-)
-from qaekwy.core.model.constraint.divide import ConstraintDivide
-from qaekwy.core.model.constraint.element import ConstraintElement
-from qaekwy.core.model.constraint.exponential import ConstraintExponential
-from qaekwy.core.model.constraint.logarithm import ConstraintLogarithm
-from qaekwy.core.model.constraint.maximum import ConstraintMaximum
-from qaekwy.core.model.constraint.minimum import ConstraintMinimum
-from qaekwy.core.model.constraint.modulo import ConstraintModulo
-from qaekwy.core.model.constraint.member import ConstraintMember
-from qaekwy.core.model.constraint.multiply import ConstraintMultiply
-from qaekwy.core.model.constraint.nroot import ConstraintNRoot
-from qaekwy.core.model.constraint.power import ConstraintPower
-from qaekwy.core.model.constraint.relational import RelationalExpression
-from qaekwy.core.model.constraint.sin import ConstraintSin
-from qaekwy.core.model.constraint.sort import ConstraintSorted, ConstraintReverseSorted
-from qaekwy.core.model.constraint.tan import ConstraintTan
-from qaekwy.core.model.constraint.if_then_else import ConstraintIfThenElse
-
-from qaekwy.core.model.cutoff import Cutoff
-from qaekwy.core.model.searcher import SearcherType
-from qaekwy.core.model.specific import SpecificMaximum, SpecificMinimum
-from qaekwy.core.model.variable.variable import (
- ArrayVariable,
- Expression,
- MatrixVariable,
- Variable,
- VariableType,
-)
+from typing import Any, Callable, Dict, Optional, Union
+
+from ..exception.model_failure import ModelFailure
+from .constraint.abs import ConstraintAbs
+from .constraint.abstract_constraint import AbstractConstraint
+from .constraint.acos import ConstraintACos
+from .constraint.asin import ConstraintASin
+from .constraint.atan import ConstraintATan
+from .constraint.cos import ConstraintCos
+from .constraint.distinct import (ConstraintDistinctArray,
+ ConstraintDistinctCol, ConstraintDistinctRow,
+ ConstraintDistinctSlice)
+from .constraint.divide import ConstraintDivide
+from .constraint.element import ConstraintElement
+from .constraint.exponential import ConstraintExponential
+from .constraint.if_then_else import ConstraintIfThenElse
+from .constraint.logarithm import ConstraintLogarithm
+from .constraint.maximum import ConstraintMaximum
+from .constraint.member import ConstraintMember
+from .constraint.minimum import ConstraintMinimum
+from .constraint.modulo import ConstraintModulo
+from .constraint.multiply import ConstraintMultiply
+from .constraint.nroot import ConstraintNRoot
+from .constraint.power import ConstraintPower
+from .constraint.relational import RelationalExpression
+from .constraint.sin import ConstraintSin
+from .constraint.sort import ConstraintReverseSorted, ConstraintSorted
+from .constraint.tan import ConstraintTan
+from .cutoff import Cutoff
+from .searcher import SearcherType
+from .specific import SpecificMaximum, SpecificMinimum
+from .variable.variable import (ArrayVariable, Expression, MatrixVariable,
+ Variable, VariableType)
+
+ConstraintFactory = Callable[
+ [dict, list[Union[Variable, ArrayVariable, MatrixVariable]]],
+ AbstractConstraint,
+]
class Modeller:
@@ -68,7 +63,7 @@ class Modeller:
Constructs and configures optimization models.
Attributes:
- variable_list (list[Union[Variable, ArrayVariable]]): Collection of model variables.
+ variable_list (list[Union[Variable, ArrayVariable, MatrixVariable]]): Collection of model variables.
constraint_list (list[Union[AbstractConstraint]]): Collection of model constraints.
objective_list (list[Union[SpecificMinimum, SpecificMaximum]]): Optimization objectives (minimize or maximize).
searcher (SearcherType): Strategy for searching solution space.
@@ -77,11 +72,40 @@ class Modeller:
solution_limit (int): Maximum number of solutions to return.
"""
+ CONSTRAINT_REGISTRY: Dict[str, ConstraintFactory] = {
+ "abs": ConstraintAbs.from_json,
+ "acos": ConstraintACos.from_json,
+ "asin": ConstraintASin.from_json,
+ "atan": ConstraintATan.from_json,
+ "cos": ConstraintCos.from_json,
+ "distinct_array": ConstraintDistinctArray.from_json,
+ "distinct_col": ConstraintDistinctCol.from_json,
+ "distinct_row": ConstraintDistinctRow.from_json,
+ "distinct_slice": ConstraintDistinctSlice.from_json,
+ "divide": ConstraintDivide.from_json,
+ "element": ConstraintElement.from_json,
+ "exponential": ConstraintExponential.from_json,
+ "logarithm": ConstraintLogarithm.from_json,
+ "maximum": ConstraintMaximum.from_json,
+ "minimum": ConstraintMinimum.from_json,
+ "modulo": ConstraintModulo.from_json,
+ "member": ConstraintMember.from_json,
+ "multiply": ConstraintMultiply.from_json,
+ "nroot": ConstraintNRoot.from_json,
+ "power": ConstraintPower.from_json,
+ "rel": lambda data, _: RelationalExpression.from_json(data),
+ "sin": ConstraintSin.from_json,
+ "sorted": ConstraintSorted.from_json,
+ "rsorted": ConstraintReverseSorted.from_json,
+ "tan": ConstraintTan.from_json,
+ "if_then_else": lambda data, _: ConstraintIfThenElse.from_json(data),
+ }
+
def __init__(self) -> None:
"""
Initializes an empty Modeller instance.
"""
- self.variable_list: list[Union[Variable, ArrayVariable]] = []
+ self.variable_list: list[Union[Variable, ArrayVariable, MatrixVariable]] = []
self.constraint_list: list[Union[AbstractConstraint]] = []
self.objective_list: list[Union[SpecificMinimum, SpecificMaximum]] = []
self.searcher: Optional[SearcherType] = None
@@ -89,12 +113,14 @@ def __init__(self) -> None:
self.callback_url: Optional[str] = None
self.solution_limit: int = 1
- def add_variable(self, variable: Union[Variable, ArrayVariable]) -> "Modeller":
+ def add_variable(
+ self, variable: Union[Variable, ArrayVariable, MatrixVariable]
+ ) -> "Modeller":
"""
Adds a variable or array of variables to the model.
Args:
- variable: A single Variable or ArrayVariable instance.
+ variable: A single Variable, ArrayVariable or MatrixVariable instance.
Returns:
self: Enables method chaining.
@@ -150,7 +176,7 @@ def set_searcher(self, searcher: SearcherType) -> "Modeller":
self.searcher = searcher
return self
- def set_cutoff(self, cutoff: Cutoff | None) -> "Modeller":
+ def set_cutoff(self, cutoff: Union[Cutoff, None]) -> "Modeller":
"""
Sets a cutoff condition to terminate optimization early.
@@ -229,72 +255,20 @@ def to_json(self, serialization: bool = False) -> dict:
@staticmethod
def _constraints_factory(
- constraint_data: dict, variable_list: list[Union[Variable, ArrayVariable]]
+ constraint_data: dict,
+ variable_list: list[Union[Variable, ArrayVariable, MatrixVariable]],
) -> AbstractConstraint:
"""
Factory method to create a Constraint instance from JSON data.
+ """
+ constraint_type: str = str(constraint_data.get("type"))
- Args:
- constraint_data (dict): A dictionary containing constraint information.
- variable_list (list[Union[Variable, ArrayVariable]]): A list of Variable instances.
+ try:
+ factory = Modeller.CONSTRAINT_REGISTRY[constraint_type]
+ except KeyError as exc:
+ raise ValueError(f"Unknown constraint type: {constraint_type}") from exc
- Returns:
- AbstractConstraint: An AbstractConstraint instance.
- """
- if constraint_data.get("type") == "abs":
- return ConstraintAbs.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "acos":
- return ConstraintACos.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "asin":
- return ConstraintASin.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "atan":
- return ConstraintATan.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "cos":
- return ConstraintCos.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "distinct_array":
- return ConstraintDistinctArray.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "distinct_col":
- return ConstraintDistinctCol.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "distinct_row":
- return ConstraintDistinctRow.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "distinct_slice":
- return ConstraintDistinctSlice.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "divide":
- return ConstraintDivide.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "element":
- return ConstraintElement.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "exponential":
- return ConstraintExponential.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "logarithm":
- return ConstraintLogarithm.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "maximum":
- return ConstraintMaximum.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "minimum":
- return ConstraintMinimum.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "modulo":
- return ConstraintModulo.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "member":
- return ConstraintMember.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "multiply":
- return ConstraintMultiply.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "nroot":
- return ConstraintNRoot.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "power":
- return ConstraintPower.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "rel":
- return RelationalExpression.from_json(constraint_data)
- if constraint_data.get("type") == "sin":
- return ConstraintSin.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "sorted":
- return ConstraintSorted.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "rsorted":
- return ConstraintReverseSorted.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "tan":
- return ConstraintTan.from_json(constraint_data, variable_list)
- if constraint_data.get("type") == "if_then_else":
- return ConstraintIfThenElse.from_json(constraint_data)
-
- raise ValueError(f"Unknown constraint type: {constraint_data.get('type')}")
+ return factory(constraint_data, variable_list)
@staticmethod
def from_json(json_data: dict) -> "Modeller":
@@ -308,19 +282,32 @@ def from_json(json_data: dict) -> "Modeller":
Modeller: A Modeller instance.
"""
modeller = Modeller()
+
+ variable_list: list = []
+ array_variable_list: list = []
+ matrix_variable_list: list = []
+ for v in json_data.get("var", []):
+ var_type = VariableType.from_json(v["type"])
+ if var_type in [
+ VariableType.INTEGER_ARRAY,
+ VariableType.FLOAT_ARRAY,
+ VariableType.BOOLEAN_ARRAY,
+ ]:
+ if v.get("subtype", "") == "matrix":
+ matrix_variable_list.append(MatrixVariable.from_json(v))
+ else:
+ array_variable_list.append(ArrayVariable.from_json(v))
+ elif var_type in [
+ VariableType.INTEGER,
+ VariableType.FLOAT,
+ VariableType.BOOLEAN,
+ ]:
+ variable_list.append(Variable.from_json(v))
+
modeller.variable_list = (
- [Variable.from_json(v) for v in json_data.get("var", []) if v is not None]
- + [
- ArrayVariable.from_json(v)
- for v in json_data.get("var", [])
- if v is not None
- ]
- + [
- MatrixVariable.from_json(v)
- for v in json_data.get("var", [])
- if v is not None
- ]
+ variable_list + array_variable_list + matrix_variable_list
)
+
modeller.constraint_list = []
for c in json_data.get("constraint", []):
constraint = Modeller._constraints_factory(c, modeller.variable_list)
diff --git a/qaekwy/core/model/relation.py b/qaekwy/core/model/relation.py
deleted file mode 100644
index f61bf3a..0000000
--- a/qaekwy/core/model/relation.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""RelationType Module
-
-This module defines the RelationType enum, which represents different
-types of relational comparisons.
-
-Enums:
- RelationType: Represents different types of relational comparisons.
-
-"""
-
-from enum import Enum
-
-
-class RelationType(Enum):
- """
- Represents different types of relational comparisons.
-
- The RelationType enum defines symbolic representations of common relational comparisons
- used in constraint-based modelling, such as greater than, greater than or equal to,
- equal to, not equal to, less than or equal to, and less than.
-
- Enum Members:
- GT (str): Greater than.
- GE (str): Greater than or equal to.
- EQ (str): Equal to.
- NE (str): Not equal to.
- LE (str): Less than or equal to.
- LT (str): Less than.
-
- Example:
- relation = RelationType.GE # Represents "greater than or equal to"
- """
-
- GT = "GT"
- GE = "GE"
- EQ = "EQ"
- NE = "NE"
- LE = "LE"
- LT = "LT"
-
- @staticmethod
- def from_json(json_data: str) -> "RelationType":
- """
- Creates a RelationType instance from a string.
-
- Args:
- json_data (str): The string representation of the relation type.
-
- Returns:
- RelationType: An instance of the RelationType enum.
- """
- return RelationType(json_data)
diff --git a/qaekwy/core/model/specific.py b/qaekwy/core/model/specific.py
index 3b3562e..92865ee 100644
--- a/qaekwy/core/model/specific.py
+++ b/qaekwy/core/model/specific.py
@@ -9,8 +9,8 @@
"""
-from qaekwy.core.model.constraint.abstract_constraint import AbstractConstraint
-from qaekwy.core.model.variable.variable import Variable
+from .constraint.abstract_constraint import AbstractConstraint
+from .variable.variable import Variable
class SpecificMinimum(AbstractConstraint):
diff --git a/qaekwy/core/model/variable/boolean.py b/qaekwy/core/model/variable/boolean.py
index d4dc4ff..65d7033 100644
--- a/qaekwy/core/model/variable/boolean.py
+++ b/qaekwy/core/model/variable/boolean.py
@@ -10,20 +10,10 @@
"""
from typing import Optional
-from qaekwy.core.model.variable.branch import (
- BranchBooleanVal,
- BranchBooleanVar,
- BranchVal,
- BranchVar,
-)
-from qaekwy.core.model.variable.variable import (
- ArrayVariable,
- Expression,
- ExpressionVariable,
- MatrixVariable,
- Variable,
- VariableType,
-)
+
+from .branch import BranchBooleanVal, BranchBooleanVar, BranchVal, BranchVar
+from .variable import (ArrayVariable, Expression, ExpressionVariable,
+ MatrixVariable, Variable, VariableType)
class BooleanVariable(Variable):
diff --git a/qaekwy/core/model/variable/float.py b/qaekwy/core/model/variable/float.py
index beea975..9f2c020 100644
--- a/qaekwy/core/model/variable/float.py
+++ b/qaekwy/core/model/variable/float.py
@@ -11,15 +11,9 @@
from typing import Optional
-from qaekwy.core.model.variable.branch import BranchFloatVal, BranchFloatVar, BranchVal
-from qaekwy.core.model.variable.variable import (
- ArrayVariable,
- Expression,
- ExpressionVariable,
- MatrixVariable,
- Variable,
- VariableType,
-)
+from .branch import BranchFloatVal, BranchFloatVar, BranchVal
+from .variable import (ArrayVariable, Expression, ExpressionVariable,
+ MatrixVariable, Variable, VariableType)
class FloatVariable(Variable):
diff --git a/qaekwy/core/model/variable/integer.py b/qaekwy/core/model/variable/integer.py
index d86cefc..0f166d6 100644
--- a/qaekwy/core/model/variable/integer.py
+++ b/qaekwy/core/model/variable/integer.py
@@ -12,20 +12,9 @@
from typing import Optional
-from qaekwy.core.model.variable.branch import (
- BranchIntegerVal,
- BranchIntegerVar,
- BranchVal,
- BranchVar,
-)
-from qaekwy.core.model.variable.variable import (
- ArrayVariable,
- Expression,
- ExpressionVariable,
- MatrixVariable,
- Variable,
- VariableType,
-)
+from .branch import BranchIntegerVal, BranchIntegerVar, BranchVal, BranchVar
+from .variable import (ArrayVariable, Expression, ExpressionVariable,
+ MatrixVariable, Variable, VariableType)
class IntegerVariable(Variable):
diff --git a/qaekwy/core/model/variable/variable.py b/qaekwy/core/model/variable/variable.py
index 1552cba..29a4f64 100644
--- a/qaekwy/core/model/variable/variable.py
+++ b/qaekwy/core/model/variable/variable.py
@@ -19,16 +19,9 @@
from enum import Enum
from typing import Optional, Union
-from qaekwy.core.model.variable.branch import (
- BranchBooleanVal,
- BranchBooleanVar,
- BranchFloatVal,
- BranchFloatVar,
- BranchIntegerVal,
- BranchIntegerVar,
- BranchVal,
- BranchVar,
-)
+from .branch import (BranchBooleanVal, BranchBooleanVar, BranchFloatVal,
+ BranchFloatVar, BranchIntegerVal, BranchIntegerVar,
+ BranchVal, BranchVar)
class VariableType(Enum):
@@ -163,23 +156,10 @@ class ExpressionArray:
array_name (str): The name of the array.
Methods:
- col(table_width: int, column: int) -> Expression:
- Creates an Expression for accessing a column in the array-like structure.
-
- row(table_width: int, row: int) -> Expression:
- Creates an Expression for accessing a row in the array-like structure.
-
- slice() -> Expression:
- Creates an Expression for accessing a slice in the array-like structure.
__getitem__(pos: int) -> Expression:
Overloaded method to create an Expression for accessing a specific
position in the array.
-
- Example:
- col_expression =
- my_array.col(table_width=4, column=2) # Creates an expression for column access.
-
"""
def __init__(self, array_name: str) -> None:
@@ -309,16 +289,6 @@ def from_json(json_data: dict) -> "ArrayVariable":
ArrayVariable: An instance of the ArrayVariable class.
"""
var_type = VariableType.from_json(json_data["type"])
- if (
- var_type
- not in [
- VariableType.INTEGER_ARRAY,
- VariableType.FLOAT_ARRAY,
- VariableType.BOOLEAN_ARRAY,
- ]
- or json_data.get("subtype", "") == "matrix"
- ):
- return None
branch_var_enum: Union[
type[BranchIntegerVar], type[BranchFloatVar], type[BranchBooleanVar]
@@ -405,7 +375,6 @@ def sum(self) -> Expression:
def __str__(self):
if self.kind == "row":
return f"{self.matrix.var_name}[{self.matrix.rows}][{self.matrix.cols}][r][{self.params['row']}]"
-
if self.kind == "col":
return f"{self.matrix.var_name}[{self.matrix.rows}][{self.matrix.cols}][c][{self.params['col']}]"
if self.kind == "slice":
@@ -413,19 +382,8 @@ def __str__(self):
return "VectorExpression()"
- def to_json(self):
- """
- Converts the vector expression to a JSON representation.
- """
- return {
- "type": "vector",
- "matrix": self.matrix.var_name,
- "kind": self.kind,
- **self.params,
- }
-
-class MatrixVariable(ArrayVariable):
+class MatrixVariable:
"""
Represents a matrix-type variable.
@@ -463,27 +421,28 @@ def __init__(
branch_val: BranchVal = BranchIntegerVal.VAL_RND,
branch_order: Optional[int] = -1,
) -> None:
- length = rows * cols
- # Check if var_name is already in the form 'MATRIX_{rows}_{cols}_{var_name}'
- expected_prefix = f"MATRIX${rows}${cols}$"
- if var_name.startswith(expected_prefix):
- raise ValueError(
- f"var_name should not start with '{expected_prefix}'. Please provide a base variable name."
- )
-
- typed_var_name: str = f"MATRIX${rows}${cols}${var_name}"
- super().__init__(
- typed_var_name,
- length,
- var_type,
- domain_low,
- domain_high,
- specific_domain,
- branch_var,
- branch_val,
- branch_order,
- )
+ typed_var_name: str = var_name
+ if not var_name.startswith(f"MATRIX${rows}${cols}$"):
+ if var_name.startswith("MATRIX$"):
+ parts = var_name.split("$", 3)
+ part_col, part_row = int(parts[1]), int(parts[2])
+ if rows != part_row or cols != part_col:
+ raise ValueError(
+ f"var_name '{var_name}' should not start with 'MATRIX$'. Please provide a base variable name."
+ )
+ else:
+ typed_var_name = f"MATRIX${rows}${cols}${var_name}"
+
+ self.var_name = typed_var_name
+ self.var_type = var_type
+ self.length = rows * cols
+ self.domain_low = domain_low
+ self.domain_high = domain_high
+ self.specific_domain = specific_domain
+ self.branch_var = branch_var
+ self.branch_val = branch_val
+ self.branching_order = branch_order
self.rows = rows
self.cols = cols
@@ -497,16 +456,6 @@ def __getitem__(self, col: int) -> Expression:
f"{self.matrix_var.var_name}[{self.row * (self.matrix_var.cols) + col}]"
)
- class _MatrixCol:
- def __init__(self, matrix_var: "MatrixVariable", col: int):
- self.matrix_var = matrix_var
- self.col = col
-
- def __getitem__(self, row: int) -> Expression:
- return Expression(
- f"{self.matrix_var.var_name}[{row * (self.matrix_var.cols) + self.col}]"
- )
-
def __getitem__(self, row: int) -> "_MatrixRow":
return MatrixVariable._MatrixRow(self, row)
@@ -548,7 +497,24 @@ def to_json(self):
Returns:
dict: A JSON representation of the matrix variable.
"""
- data_json = super().to_json()
+ data_json = {
+ "name": self.var_name,
+ "type": self.var_type.value,
+ "length": self.length,
+ "brancher_variable": self.branch_var.value,
+ "brancher_value": self.branch_val.value,
+ "branching_order": self.branching_order,
+ }
+
+ if self.domain_low is not None:
+ data_json["domlow"] = self.domain_low
+
+ if self.domain_high is not None:
+ data_json["domup"] = self.domain_high
+
+ if self.specific_domain is not None:
+ data_json["specific_domain"] = self.specific_domain
+
data_json["rows"] = self.rows
data_json["cols"] = self.cols
data_json["subtype"] = "matrix"
@@ -566,16 +532,6 @@ def from_json(json_data) -> "MatrixVariable":
MatrixVariable: An instance of the MatrixVariable class.
"""
var_type = VariableType.from_json(json_data["type"])
- if (
- var_type
- not in [
- VariableType.INTEGER_ARRAY,
- VariableType.FLOAT_ARRAY,
- VariableType.BOOLEAN_ARRAY,
- ]
- or json_data.get("subtype", "") != "matrix"
- ):
- return None
branch_var_enum: Union[
type[BranchIntegerVar], type[BranchFloatVar], type[BranchBooleanVar]
@@ -605,6 +561,7 @@ def from_json(json_data) -> "MatrixVariable":
return MatrixVariable(
var_name=json_data["name"],
+ var_type=var_type,
rows=json_data["rows"],
cols=json_data["cols"],
domain_low=json_data.get("domlow"),
diff --git a/qaekwy/core/response.py b/qaekwy/core/response.py
index 5a6a40a..6f5a7a3 100644
--- a/qaekwy/core/response.py
+++ b/qaekwy/core/response.py
@@ -44,8 +44,8 @@
from abc import ABC
from typing import Any, Optional
-from qaekwy.core.explanation import Explanation
-from qaekwy.core.solution import Solution
+from .explanation import Explanation
+from .solution import Solution
class NodeStatus:
@@ -348,7 +348,7 @@ def get_app(self) -> str:
Returns:
str: The name of the application.
"""
- return self.response_content["app"]
+ return str(self.response_content["app"])
def get_author(self) -> str:
"""
@@ -357,7 +357,7 @@ def get_author(self) -> str:
Returns:
str: The author of the application.
"""
- return self.response_content["author"]
+ return str(self.response_content["author"])
def get_version(self) -> str:
"""
@@ -366,7 +366,7 @@ def get_version(self) -> str:
Returns:
str: The version of the application.
"""
- return self.response_content["version"]
+ return str(self.response_content["version"])
def get_version_major(self) -> int:
"""
@@ -375,7 +375,7 @@ def get_version_major(self) -> int:
Returns:
int: The major version number of the application.
"""
- return self.response_content["version_major"]
+ return int(self.response_content["version_major"])
def get_version_minor(self) -> int:
"""
@@ -384,7 +384,7 @@ def get_version_minor(self) -> int:
Returns:
int: The minor version number of the application.
"""
- return self.response_content["version_minor"]
+ return int(self.response_content["version_minor"])
def get_version_build(self) -> int:
"""
@@ -393,7 +393,7 @@ def get_version_build(self) -> int:
Returns:
int: The build version number of the application.
"""
- return self.response_content["version_build"]
+ return int(self.response_content["version_build"])
def get_release(self) -> str:
"""
@@ -402,7 +402,7 @@ def get_release(self) -> str:
Returns:
str: The release information of the application.
"""
- return self.response_content["version_release"]
+ return str(self.response_content["version_release"])
class ClusterStatusResponse(AbstractResponse):
diff --git a/qaekwy/core/solution.py b/qaekwy/core/solution.py
index 67b4a78..11df6f3 100644
--- a/qaekwy/core/solution.py
+++ b/qaekwy/core/solution.py
@@ -36,7 +36,7 @@ class Solution(dict):
z_value = solution.z # z_value is None
"""
- def __init__( # pylint: disable=too-many-locals
+ def __init__( # pylint: disable=too-many-locals
self, solution_json_content: list[dict]
) -> None:
self.solution_json_content = solution_json_content
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 37d2a6b..0000000
--- a/setup.py
+++ /dev/null
@@ -1,87 +0,0 @@
-""" Installation """
-
-import ast
-from pathlib import Path
-from setuptools import setup
-
-
-metadata_file = Path("qaekwy/__metadata__.py")
-
-metadata = {}
-with open(metadata_file, "r", encoding="utf-8") as f:
- tree = ast.parse(f.read(), filename=str(metadata_file))
- for node in tree.body:
- if isinstance(node, ast.Assign) and len(node.targets) == 1:
- target = node.targets[0]
- if isinstance(target, ast.Name) and isinstance(node.value, ast.Constant):
- metadata[target.id] = node.value.value
-
-with open("README.md", "r", encoding="UTF-8") as fh:
- long_description = fh.read()
-
-
-setup(
- name="qaekwy",
- version=metadata["__version__"],
- license=metadata["__license__"],
- author=metadata["__author__"],
- author_email=metadata["__author_email__"],
- keywords=[
- "optimization",
- "constraint programming",
- "combinatorial optimization",
- "operations research",
- "constraint satisfaction",
- "constraint solver",
- "solver",
- "CSP",
- "optimization library",
- "optimization framework",
- "mathematical optimization",
- "discrete optimization",
- "optimization modeling",
- "constraint modeling",
- "declarative programming",
- "modeling language",
- "DSL",
- "define-and-solve",
- "scheduling",
- "routing",
- "planning",
- "resource allocation",
- "assignment",
- "decision support",
- "open source",
- "Python",
- ],
- description="Qaekwy, a modern, open-source Python framework for declarative constraint programming and combinatorial optimization",
- long_description_content_type = "text/markdown",
- long_description=long_description,
- url="https://qaekwy.io",
- classifiers=[
- "Development Status :: 4 - Beta",
- "Intended Audience :: Developers",
- "Intended Audience :: Education",
- "Intended Audience :: Science/Research",
- "Programming Language :: Python",
- "Programming Language :: Python :: 3",
- "Topic :: Scientific/Engineering",
- "Topic :: Scientific/Engineering :: Mathematics",
- "Topic :: Scientific/Engineering :: Artificial Intelligence",
- "Topic :: Software Development :: Libraries :: Python Modules",
- ],
- packages=[
- "qaekwy",
- ],
- project_urls={
- 'Homepage': 'https://qaekwy.io',
- 'Documentation': 'https://docs.qaekwy.io',
- 'Issue Tracker': 'https://github.com/alex-87/qaekwy-python/issues',
- 'Source': 'https://github.com/alex-87/qaekwy-python',
- },
- python_requires=">=3.9",
- install_requires=[
- "requests",
- "types_requests",
- ],
-)
diff --git a/tests/api/test_model.py b/tests/api/test_model.py
new file mode 100644
index 0000000..17f1cf4
--- /dev/null
+++ b/tests/api/test_model.py
@@ -0,0 +1,316 @@
+# pylint: skip-file
+
+import unittest
+from unittest.mock import MagicMock, patch
+
+from qaekwy import Model
+from qaekwy import SolverError
+
+from qaekwy.core.model.cutoff import Cutoff
+from qaekwy.core.model.searcher import SearcherType
+from qaekwy.core.response import SolutionResponse
+from qaekwy.core.solution import Solution
+
+from qaekwy.core.model.variable.integer import IntegerVariable
+from qaekwy.core.model.variable.integer import IntegerVariableArray
+from qaekwy.core.model.variable.variable import VectorExpression
+from qaekwy.core.model.variable.variable import MatrixVariable
+
+
+
+
+class TestModelVariables(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+
+ def test_integer_variable(self):
+ var = self.model.integer_variable(
+ name="x",
+ domain=(0, 10)
+ )
+
+ self.assertIsInstance(var, IntegerVariable)
+ self.model._modeller.add_variable.assert_called_once_with(var)
+
+ def test_integer_array(self):
+ arr = self.model.integer_array(
+ name="x",
+ length=5,
+ domain=(0, 9)
+ )
+
+ self.assertIsInstance(arr, IntegerVariableArray)
+ self.model._modeller.add_variable.assert_called_once_with(arr)
+
+class TestConstraintDistinct(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+
+ def test_constraint_distinct_array(self):
+ array = MagicMock(spec=IntegerVariableArray)
+
+ self.model.constraint_distinct(array)
+
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_distinct_row(self):
+ matrix = MagicMock(spec=MatrixVariable)
+ matrix.cols = 4
+
+ vec = MagicMock(spec=VectorExpression)
+ vec.matrix = matrix
+ vec.kind = "row"
+ vec.params = {"row": 1}
+
+ self.model.constraint_distinct(vec)
+
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_distinct_col(self):
+ matrix = MagicMock(spec=MatrixVariable)
+ matrix.rows = 3
+
+ vec = MagicMock(spec=VectorExpression)
+ vec.matrix = matrix
+ vec.kind = "col"
+ vec.params = {"col": 2}
+
+ self.model.constraint_distinct(vec)
+
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_distinct_slice(self):
+ matrix = MagicMock(spec=MatrixVariable)
+ matrix.cols = 5
+
+ vec = MagicMock(spec=VectorExpression)
+ vec.matrix = matrix
+ vec.kind = "slice"
+ vec.params = {
+ "row_start": 0,
+ "col_start": 0,
+ "row_end": 2,
+ "col_end": 2,
+ }
+
+ self.model.constraint_distinct(vec)
+
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_distinct_invalid_type(self):
+ with self.assertRaises(TypeError):
+ self.model.constraint_distinct(object())
+
+
+class TestSolve(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+ self.model._engine = MagicMock()
+
+ def test_invalid_searcher(self):
+ with self.assertRaises(ValueError):
+ self.model.solve(searcher="invalid")
+
+ def test_invalid_solution_limit(self):
+ with self.assertRaises(ValueError):
+ self.model.solve(solution_limit=0)
+
+ def test_solver_error(self):
+ response = MagicMock(spec=SolutionResponse)
+ response.is_status_ok.return_value = False
+ response.get_status.return_value = "ERROR"
+ response.get_message.return_value = "Failure"
+ response.get_content.return_value = {}
+
+ self.model._engine.model.return_value = response
+
+ with self.assertRaises(SolverError):
+ self.model.solve()
+
+ def test_solve_success(self):
+ sol = MagicMock(spec=Solution)
+
+ response = MagicMock(spec=SolutionResponse)
+ response.is_status_ok.return_value = True
+ response.get_solutions.return_value = [sol]
+
+ self.model._engine.model.return_value = response
+
+ solutions = self.model.solve()
+
+ self.assertEqual(solutions, [sol])
+
+ def test_solve_one(self):
+ sol = MagicMock(spec=Solution)
+
+ self.model.solve = MagicMock(return_value=[sol])
+
+ result = self.model.solve_one()
+
+ self.assertEqual(result, sol)
+
+class TestModelExpressionVariables(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+
+ def test_integer_expression_variable(self):
+ v = self.model.integer_variable(
+ name="x",
+ expression="y + 1"
+ )
+
+ self.model._modeller.add_variable.assert_called_once_with(v)
+ self.assertEqual(v.var_name, "x")
+
+ def test_float_expression_variable(self):
+ v = self.model.float_variable(
+ name="f",
+ domain=None,
+ expression="x * 0.5"
+ )
+
+ self.model._modeller.add_variable.assert_called_once_with(v)
+ self.assertEqual(v.var_name, "f")
+
+class TestModelBooleanVariables(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+
+ def test_boolean_variable(self):
+ v = self.model.boolean_variable("b")
+
+ self.model._modeller.add_variable.assert_called_once_with(v)
+
+ def test_boolean_array(self):
+ arr = self.model.boolean_array("b", length=3)
+
+ self.model._modeller.add_variable.assert_called_once_with(arr)
+
+class TestModelFloatCollections(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+
+ def test_float_array(self):
+ arr = self.model.float_array("f", length=4, domain=(0.0, 1.0))
+ self.model._modeller.add_variable.assert_called_once_with(arr)
+
+ def test_float_matrix(self):
+ mat = self.model.float_matrix("m", rows=2, cols=2, domain=(0.0, 10.0))
+ self.model._modeller.add_variable.assert_called_once_with(mat)
+
+class TestModelObjectives(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+
+ def test_minimize(self):
+ var = MagicMock()
+ self.model.minimize(var)
+
+ self.model._modeller.add_objective.assert_called_once()
+
+ def test_maximize(self):
+ var = MagicMock()
+ self.model.maximize(var)
+
+ self.model._modeller.add_objective.assert_called_once()
+
+class TestModelConstraints(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+ self.v1 = MagicMock()
+ self.v2 = MagicMock()
+ self.v3 = MagicMock()
+
+ def test_constraint_abs(self):
+ self.model.constraint_abs(self.v1, self.v2)
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_multiply(self):
+ self.model.constraint_multiply(self.v1, self.v2, self.v3)
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_divide(self):
+ self.model.constraint_divide(self.v1, self.v2, self.v3)
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_power(self):
+ self.model.constraint_power(self.v1, 2, self.v3)
+ self.model._modeller.add_constraint.assert_called_once()
+
+ def test_constraint_if_then_else(self):
+ cond = MagicMock()
+ then_c = MagicMock()
+
+ self.model.constraint_if_then_else(cond, then_c)
+
+ self.model._modeller.add_constraint.assert_called_once()
+
+class TestModelSearcherConfiguration(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+ self.model._modeller = MagicMock()
+ self.model._engine = MagicMock()
+
+ def test_searcher_set_correctly(self):
+ response = MagicMock()
+ response.is_status_ok.return_value = True
+ response.get_solutions.return_value = []
+
+ self.model._engine.model.return_value = response
+
+ self.model.solve(searcher="dfs")
+
+ self.model._modeller.set_searcher.assert_called_once_with(
+ searcher=SearcherType.DFS
+ )
+
+ def test_cutoff_passed(self):
+ cutoff = MagicMock(spec=Cutoff)
+
+ response = MagicMock()
+ response.is_status_ok.return_value = True
+ response.get_solutions.return_value = []
+
+ self.model._engine.model.return_value = response
+
+ self.model.solve(cutoff=cutoff)
+
+ self.model._modeller.set_cutoff.assert_called_once_with(cutoff=cutoff)
+
+class TestSolveOneEdgeCases(unittest.TestCase):
+
+ def setUp(self):
+ self.model = Model()
+
+ def test_solve_one_no_solution(self):
+ self.model.solve = MagicMock(return_value=[])
+
+ result = self.model.solve_one()
+
+ self.assertIsNone(result)
+
+class TestModelJsonExtended(unittest.TestCase):
+
+ def test_to_json_delegation(self):
+ model = Model()
+ model._modeller = MagicMock()
+ model._modeller.to_json.return_value = {"k": "v"}
+
+ self.assertEqual(model.to_json(), {"k": "v"})
diff --git a/tests/model/constraint/test_abs.py b/tests/core/model/constraint/test_abs.py
similarity index 100%
rename from tests/model/constraint/test_abs.py
rename to tests/core/model/constraint/test_abs.py
diff --git a/tests/model/constraint/test_acos.py b/tests/core/model/constraint/test_acos.py
similarity index 100%
rename from tests/model/constraint/test_acos.py
rename to tests/core/model/constraint/test_acos.py
diff --git a/tests/model/constraint/test_asin.py b/tests/core/model/constraint/test_asin.py
similarity index 100%
rename from tests/model/constraint/test_asin.py
rename to tests/core/model/constraint/test_asin.py
diff --git a/tests/model/constraint/test_atan.py b/tests/core/model/constraint/test_atan.py
similarity index 100%
rename from tests/model/constraint/test_atan.py
rename to tests/core/model/constraint/test_atan.py
diff --git a/tests/model/constraint/test_cos.py b/tests/core/model/constraint/test_cos.py
similarity index 100%
rename from tests/model/constraint/test_cos.py
rename to tests/core/model/constraint/test_cos.py
diff --git a/tests/core/model/constraint/test_distinct.py b/tests/core/model/constraint/test_distinct.py
new file mode 100644
index 0000000..16328d2
--- /dev/null
+++ b/tests/core/model/constraint/test_distinct.py
@@ -0,0 +1,218 @@
+# pylint: skip-file
+import unittest
+
+# Import the classes under test
+# Adjust the import path if needed for your project structure
+from qaekwy.core.model.constraint.distinct import (
+ ConstraintDistinctArray,
+ ConstraintDistinctRow,
+ ConstraintDistinctCol,
+ ConstraintDistinctSlice,
+)
+
+
+# ---------------------------------------------------------------------------
+# Minimal mock classes to avoid dependency on the full variable implementation
+# ---------------------------------------------------------------------------
+
+class MockArrayVariable:
+ def __init__(self, var_name):
+ self.var_name = var_name
+
+
+class MockMatrixVariable:
+ def __init__(self, var_name):
+ self.var_name = var_name
+
+
+# ---------------------------------------------------------------------------
+# Test cases
+# ---------------------------------------------------------------------------
+
+class TestConstraintDistinctArray(unittest.TestCase):
+ def setUp(self):
+ self.var = MockArrayVariable("x")
+
+ def test_to_json(self):
+ constraint = ConstraintDistinctArray(self.var, "c1")
+
+ expected = {
+ "name": "c1",
+ "type": "distinct",
+ "v1": "x",
+ "selection": "standard",
+ }
+
+ self.assertEqual(constraint.to_json(), expected)
+
+ def test_from_json(self):
+ json_data = {
+ "name": "c1",
+ "type": "distinct",
+ "v1": "x",
+ "selection": "standard",
+ }
+
+ constraint = ConstraintDistinctArray.from_json(json_data, [self.var])
+
+ self.assertIsInstance(constraint, ConstraintDistinctArray)
+ self.assertEqual(constraint.var_1, self.var)
+ self.assertEqual(constraint.constraint_name, "c1")
+
+ def test_from_json_variable_not_found(self):
+ json_data = {"v1": "missing"}
+
+ with self.assertRaises(ValueError):
+ ConstraintDistinctArray.from_json(json_data, [])
+
+
+class TestConstraintDistinctRow(unittest.TestCase):
+ def setUp(self):
+ self.var = MockMatrixVariable("m")
+
+ def test_to_json(self):
+ constraint = ConstraintDistinctRow(self.var, size=4, idx=1, constraint_name="row1")
+
+ expected = {
+ "name": "row1",
+ "type": "distinct",
+ "v1": "m",
+ "selection": "row",
+ "size": 4,
+ "index": 1,
+ }
+
+ self.assertEqual(constraint.to_json(), expected)
+
+ def test_from_json(self):
+ json_data = {
+ "name": "row1",
+ "v1": "m",
+ "selection": "row",
+ "size": 4,
+ "index": 1,
+ }
+
+ constraint = ConstraintDistinctRow.from_json(json_data, [self.var])
+
+ self.assertIsInstance(constraint, ConstraintDistinctRow)
+ self.assertEqual(constraint.var_1, self.var)
+ self.assertEqual(constraint.size, 4)
+ self.assertEqual(constraint.idx, 1)
+ self.assertEqual(constraint.constraint_name, "row1")
+
+ def test_from_json_variable_not_found(self):
+ json_data = {"v1": "missing", "size": 3, "index": 0}
+
+ with self.assertRaises(ValueError):
+ ConstraintDistinctRow.from_json(json_data, [])
+
+
+class TestConstraintDistinctCol(unittest.TestCase):
+ def setUp(self):
+ self.var = MockMatrixVariable("m")
+
+ def test_to_json(self):
+ constraint = ConstraintDistinctCol(self.var, size=5, idx=2, constraint_name="col1")
+
+ expected = {
+ "name": "col1",
+ "type": "distinct",
+ "v1": "m",
+ "selection": "col",
+ "size": 5,
+ "index": 2,
+ }
+
+ self.assertEqual(constraint.to_json(), expected)
+
+ def test_from_json(self):
+ json_data = {
+ "name": "col1",
+ "v1": "m",
+ "selection": "col",
+ "size": 5,
+ "index": 2,
+ }
+
+ constraint = ConstraintDistinctCol.from_json(json_data, [self.var])
+
+ self.assertIsInstance(constraint, ConstraintDistinctCol)
+ self.assertEqual(constraint.size, 5)
+ self.assertEqual(constraint.idx, 2)
+ self.assertEqual(constraint.constraint_name, "col1")
+
+ def test_from_json_variable_not_found(self):
+ json_data = {"v1": "missing", "size": 3, "index": 0}
+
+ with self.assertRaises(ValueError):
+ ConstraintDistinctCol.from_json(json_data, [])
+
+
+class TestConstraintDistinctSlice(unittest.TestCase):
+ def setUp(self):
+ self.var = MockMatrixVariable("m")
+
+ def test_to_json(self):
+ constraint = ConstraintDistinctSlice(
+ self.var,
+ size=6,
+ offset_start_x=0,
+ offset_start_y=1,
+ offset_end_x=2,
+ offset_end_y=3,
+ constraint_name="slice1",
+ )
+
+ expected = {
+ "name": "slice1",
+ "type": "distinct",
+ "v1": "m",
+ "selection": "slice",
+ "size": 6,
+ "offset_start_x": 0,
+ "offset_start_y": 1,
+ "offset_end_x": 2,
+ "offset_end_y": 3,
+ }
+
+ self.assertEqual(constraint.to_json(), expected)
+
+ def test_from_json(self):
+ json_data = {
+ "name": "slice1",
+ "v1": "m",
+ "selection": "slice",
+ "size": 6,
+ "offset_start_x": 0,
+ "offset_start_y": 1,
+ "offset_end_x": 2,
+ "offset_end_y": 3,
+ }
+
+ constraint = ConstraintDistinctSlice.from_json(json_data, [self.var])
+
+ self.assertIsInstance(constraint, ConstraintDistinctSlice)
+ self.assertEqual(constraint.size, 6)
+ self.assertEqual(constraint.offset_start_x, 0)
+ self.assertEqual(constraint.offset_start_y, 1)
+ self.assertEqual(constraint.offset_end_x, 2)
+ self.assertEqual(constraint.offset_end_y, 3)
+ self.assertEqual(constraint.constraint_name, "slice1")
+
+ def test_from_json_variable_not_found(self):
+ json_data = {
+ "v1": "missing",
+ "size": 4,
+ "offset_start_x": 0,
+ "offset_start_y": 0,
+ "offset_end_x": 1,
+ "offset_end_y": 1,
+ }
+
+ with self.assertRaises(ValueError):
+ ConstraintDistinctSlice.from_json(json_data, [])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/model/constraint/test_divide.py b/tests/core/model/constraint/test_divide.py
similarity index 100%
rename from tests/model/constraint/test_divide.py
rename to tests/core/model/constraint/test_divide.py
diff --git a/tests/model/constraint/test_element.py b/tests/core/model/constraint/test_element.py
similarity index 100%
rename from tests/model/constraint/test_element.py
rename to tests/core/model/constraint/test_element.py
diff --git a/tests/model/constraint/test_exponential.py b/tests/core/model/constraint/test_exponential.py
similarity index 100%
rename from tests/model/constraint/test_exponential.py
rename to tests/core/model/constraint/test_exponential.py
diff --git a/tests/model/constraint/test_if_then_else.py b/tests/core/model/constraint/test_if_then_else.py
similarity index 100%
rename from tests/model/constraint/test_if_then_else.py
rename to tests/core/model/constraint/test_if_then_else.py
diff --git a/tests/model/constraint/test_logarithm.py b/tests/core/model/constraint/test_logarithm.py
similarity index 100%
rename from tests/model/constraint/test_logarithm.py
rename to tests/core/model/constraint/test_logarithm.py
diff --git a/tests/model/constraint/test_maximum.py b/tests/core/model/constraint/test_maximum.py
similarity index 100%
rename from tests/model/constraint/test_maximum.py
rename to tests/core/model/constraint/test_maximum.py
diff --git a/tests/model/constraint/test_member.py b/tests/core/model/constraint/test_member.py
similarity index 100%
rename from tests/model/constraint/test_member.py
rename to tests/core/model/constraint/test_member.py
diff --git a/tests/model/constraint/test_minimum.py b/tests/core/model/constraint/test_minimum.py
similarity index 100%
rename from tests/model/constraint/test_minimum.py
rename to tests/core/model/constraint/test_minimum.py
diff --git a/tests/model/constraint/test_modulo.py b/tests/core/model/constraint/test_modulo.py
similarity index 100%
rename from tests/model/constraint/test_modulo.py
rename to tests/core/model/constraint/test_modulo.py
diff --git a/tests/model/constraint/test_multiply.py b/tests/core/model/constraint/test_multiply.py
similarity index 100%
rename from tests/model/constraint/test_multiply.py
rename to tests/core/model/constraint/test_multiply.py
diff --git a/tests/model/constraint/test_nroot.py b/tests/core/model/constraint/test_nroot.py
similarity index 100%
rename from tests/model/constraint/test_nroot.py
rename to tests/core/model/constraint/test_nroot.py
diff --git a/tests/model/constraint/test_power.py b/tests/core/model/constraint/test_power.py
similarity index 100%
rename from tests/model/constraint/test_power.py
rename to tests/core/model/constraint/test_power.py
diff --git a/tests/model/constraint/test_relational.py b/tests/core/model/constraint/test_relational.py
similarity index 100%
rename from tests/model/constraint/test_relational.py
rename to tests/core/model/constraint/test_relational.py
diff --git a/tests/model/constraint/test_sin.py b/tests/core/model/constraint/test_sin.py
similarity index 100%
rename from tests/model/constraint/test_sin.py
rename to tests/core/model/constraint/test_sin.py
diff --git a/tests/model/constraint/test_sort.py b/tests/core/model/constraint/test_sort.py
similarity index 100%
rename from tests/model/constraint/test_sort.py
rename to tests/core/model/constraint/test_sort.py
diff --git a/tests/model/constraint/test_tan.py b/tests/core/model/constraint/test_tan.py
similarity index 100%
rename from tests/model/constraint/test_tan.py
rename to tests/core/model/constraint/test_tan.py
diff --git a/tests/core/model/test_cutoff.py b/tests/core/model/test_cutoff.py
new file mode 100644
index 0000000..25230b2
--- /dev/null
+++ b/tests/core/model/test_cutoff.py
@@ -0,0 +1,164 @@
+# pylint: skip-file
+
+import unittest
+
+
+from qaekwy.core.model.cutoff import (
+ Cutoff,
+ CutoffConstant,
+ CutoffFibonacci,
+ CutoffGeometric,
+ CutoffLuby,
+ CutoffLinear,
+ CutoffRandom,
+ MetaCutoffAppender,
+ MetaCutoffMerger,
+ MetaCutoffRepeater,
+)
+
+
+
+class TestCutoff(unittest.TestCase):
+
+ def test_cutoff_constant(self):
+ c = CutoffConstant(42)
+
+ assert c.constant_value == 42
+ assert c.is_meta() is False
+ assert c.to_json() == {"name": "constant", "value": 42}
+
+ restored = CutoffConstant.from_json(c.to_json())
+ assert restored.constant_value == 42
+
+
+ def test_cutoff_fibonacci(self):
+ c = CutoffFibonacci()
+
+ assert c.is_meta() is False
+ assert c.to_json() == {"name": "fibonacci"}
+
+ restored = CutoffFibonacci.from_json({})
+ assert isinstance(restored, CutoffFibonacci)
+
+
+ def test_cutoff_geometric(self):
+ c = CutoffGeometric(base=1.2, scale=3)
+
+ assert c.is_meta() is False
+ assert c.base == 1.2
+ assert c.scale == 3
+
+ data = c.to_json()
+ restored = CutoffGeometric.from_json(data)
+
+ assert restored.base == 1.2
+ assert restored.scale == 3
+
+
+ def test_cutoff_luby(self):
+ c = CutoffLuby(scale=5)
+
+ assert c.is_meta() is False
+ assert c.scale == 5
+
+ data = c.to_json()
+ restored = CutoffLuby.from_json(data)
+
+ assert restored.scale == 5
+
+
+ def test_cutoff_linear(self):
+ c = CutoffLinear(scale=7)
+
+ assert c.is_meta() is False
+ assert c.scale == 7
+
+ data = c.to_json()
+ restored = CutoffLinear.from_json(data)
+
+ assert restored.scale == 7
+
+
+ def test_cutoff_random(self):
+ c = CutoffRandom(seed=123, minimum=10, maximum=50, round_value=5)
+
+ assert c.is_meta() is False
+ assert c.seed == 123
+ assert c.minimum == 10
+ assert c.maximum == 50
+ assert c.round_value == 5
+
+ data = c.to_json()
+ restored = CutoffRandom.from_json(data)
+
+ assert restored.seed == 123
+ assert restored.minimum == 10
+ assert restored.maximum == 50
+ assert restored.round_value == 5
+
+ def test_meta_cutoff_appender(self):
+ first = CutoffConstant(10)
+ second = CutoffLinear(3)
+
+ meta = MetaCutoffAppender(
+ first_cutoff=first,
+ number_from_first=2,
+ second_cutoff=second,
+ )
+
+ assert meta.is_meta() is True
+ assert meta.number_from_first == 2
+
+ data = meta.to_json()
+ restored = MetaCutoffAppender.from_json(data)
+
+ assert isinstance(restored.first_cutoff, CutoffConstant)
+ assert isinstance(restored.second_cutoff, CutoffLinear)
+ assert restored.number_from_first == 2
+
+
+ def test_meta_cutoff_merger(self):
+ first = CutoffConstant(5)
+ second = CutoffFibonacci()
+
+ meta = MetaCutoffMerger(first, second)
+
+ assert meta.is_meta() is True
+
+ data = meta.to_json()
+ restored = MetaCutoffMerger.from_json(data)
+
+ assert isinstance(restored.first_cutoff, CutoffConstant)
+ assert isinstance(restored.second_cutoff, CutoffFibonacci)
+
+
+ def test_meta_cutoff_repeater(self):
+ sub = CutoffGeometric(base=2.0, scale=3)
+ meta = MetaCutoffRepeater(sub_cutoff=sub, repeat=4)
+
+ assert meta.is_meta() is True
+ assert meta.repeat == 4
+
+ data = meta.to_json()
+ restored = MetaCutoffRepeater.from_json(data)
+
+ assert isinstance(restored.sub_cutoff, CutoffGeometric)
+ assert restored.repeat == 4
+
+ def test_nested_meta_cutoffs_roundtrip(self):
+ base = CutoffConstant(10)
+ repeated = MetaCutoffRepeater(base, repeat=3)
+ merged = MetaCutoffMerger(repeated, CutoffLinear(2))
+
+ data = merged.to_json()
+ restored = Cutoff.from_json(data)
+
+ assert isinstance(restored, MetaCutoffMerger)
+ assert isinstance(restored.first_cutoff, MetaCutoffRepeater)
+ assert isinstance(restored.first_cutoff.sub_cutoff, CutoffConstant)
+ assert restored.first_cutoff.repeat == 3
+ assert isinstance(restored.second_cutoff, CutoffLinear)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/core/model/test_function.py b/tests/core/model/test_function.py
new file mode 100644
index 0000000..41c056d
--- /dev/null
+++ b/tests/core/model/test_function.py
@@ -0,0 +1,62 @@
+# pylint: skip-file
+
+import unittest
+
+import qaekwy as qw
+from qaekwy.core.model.variable.variable import Expression, ExpressionArray
+
+class TestMathExpressions(unittest.TestCase):
+
+ def setUp(self):
+ self.expr_a = Expression("x")
+ self.expr_b = Expression("y")
+ self.array = ExpressionArray([self.expr_a, self.expr_b])
+
+ def test_maximum(self):
+ result = qw.math.maximum(self.array)
+ self.assertEqual(result, "max(x,y)")
+
+ def test_minimum(self):
+ result = qw.math.minimum(self.array)
+ self.assertEqual(result, "min(x,y)")
+
+ def test_sum_of(self):
+ result = qw.math.sum_of(self.array)
+ self.assertEqual(result, "sum(x,y)")
+
+ def test_absolute(self):
+ result = qw.math.absolute(self.expr_a)
+ self.assertEqual(result, "abs(x)")
+
+ def test_power(self):
+ result = qw.math.power(self.expr_a, 3)
+ self.assertEqual(result, "(pow(x, 3))")
+
+ def test_nroot(self):
+ result = qw.math.nroot(self.expr_a, 2)
+ self.assertEqual(result, "nroot(x, 2)")
+
+ def test_sqr(self):
+ result = qw.math.sqr(self.expr_a)
+ self.assertEqual(result, "sqr(x)")
+
+ def test_sqrt(self):
+ result = qw.math.sqrt(self.expr_a)
+ self.assertEqual(result, "sqrt(x)")
+
+ def test_trigonometry(self):
+ self.assertEqual(qw.math.sin(self.expr_a), "sin(x)")
+ self.assertEqual(qw.math.cos(self.expr_a), "cos(x)")
+ self.assertEqual(qw.math.tan(self.expr_a), "tan(x)")
+
+ def test_inverse_trigonometry(self):
+ self.assertEqual(qw.math.asin(self.expr_a), "asin(x)")
+ self.assertEqual(qw.math.acos(self.expr_a), "acos(x)")
+ self.assertEqual(qw.math.atan(self.expr_a), "atan(x)")
+
+ def test_log_and_exp(self):
+ self.assertEqual(qw.math.log(self.expr_a), "log(x)")
+ self.assertEqual(qw.math.exp(self.expr_a), "exp(x)")
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/core/model/test_modeller.py b/tests/core/model/test_modeller.py
new file mode 100644
index 0000000..7d2237e
--- /dev/null
+++ b/tests/core/model/test_modeller.py
@@ -0,0 +1,243 @@
+# pylint: skip-file
+
+import unittest
+from unittest.mock import MagicMock
+
+from qaekwy.core.model.constraint.abs import ConstraintAbs
+from qaekwy.core.model.modeller import Modeller
+from qaekwy.core.model.specific import SpecificMinimum
+from qaekwy.core.model.searcher import SearcherType
+from qaekwy.core.model.cutoff import CutoffFibonacci
+from qaekwy.core.model.variable.integer import IntegerVariable
+from qaekwy.core.model.cutoff import Cutoff
+from qaekwy.core.model.variable.variable import Expression
+from qaekwy.core.exception.model_failure import ModelFailure
+
+
+class TestModeller(unittest.TestCase):
+
+ def setUp(self):
+ self.modeller = Modeller()
+ self.var1 = IntegerVariable("var1", 0, 10)
+ self.var2 = IntegerVariable("var2", 0, 10)
+ self.constraint = ConstraintAbs(
+ var_1=self.var1, var_2=self.var2, constraint_name="abs"
+ )
+ self.objective = SpecificMinimum(self.var1)
+ self.searcher = SearcherType.DFS
+ self.cutoff = CutoffFibonacci()
+ self.callback_url = "https://example.com/callback"
+
+ def test_add_variable(self):
+ self.modeller.add_variable(self.var1).add_variable(self.var2)
+ self.assertEqual(self.modeller.variable_list, [self.var1, self.var2])
+
+ def test_add_constraint(self):
+ self.modeller.add_constraint(self.constraint)
+ self.assertEqual(self.modeller.constraint_list, [self.constraint])
+
+ def test_add_objective(self):
+ self.modeller.add_objective(self.objective)
+ self.assertEqual(self.modeller.objective_list, [self.objective])
+
+ def test_set_searcher(self):
+ self.modeller.set_searcher(self.searcher)
+ self.assertEqual(self.modeller.searcher, self.searcher)
+
+ def test_set_cutoff(self):
+ self.modeller.set_cutoff(self.cutoff)
+ self.assertEqual(self.modeller.cutoff, self.cutoff)
+
+ def test_set_callback_url(self):
+ self.modeller.set_callback_url(self.callback_url)
+ self.assertEqual(self.modeller.callback_url, self.callback_url)
+
+ def test_to_json(self):
+ self.modeller.add_variable(self.var1).add_variable(self.var2).add_constraint(
+ self.constraint
+ ).add_objective(self.objective)
+ self.modeller.set_searcher(self.searcher).set_cutoff(
+ self.cutoff
+ ).set_callback_url(self.callback_url)
+
+ expected_json = {
+ "callback_url": "https://example.com/callback",
+ "constraint": [{"name": "abs", "type": "abs", "v1": "var1", "v2": "var2"}],
+ "cutoff": {"name": "fibonacci"},
+ "solution_limit": 1,
+ "specific": [{"type": "minimize", "var": "var1"}],
+ "var": [
+ {
+ "brancher_value": "VAL_RND",
+ "branching_order": -1,
+ "domlow": 0,
+ "domup": 10,
+ "name": "var1",
+ "type": "integer",
+ },
+ {
+ "brancher_value": "VAL_RND",
+ "branching_order": -1,
+ "domlow": 0,
+ "domup": 10,
+ "name": "var2",
+ "type": "integer",
+ },
+ ],
+ }
+
+ print(
+ self.modeller.from_json(
+ self.modeller.from_json(expected_json).to_json(serialization=True)
+ ).to_json(serialization=True)
+ )
+
+ self.assertDictEqual(
+ self.modeller.to_json(serialization=True),
+ self.modeller.from_json(expected_json).to_json(serialization=True),
+ self.modeller.from_json(
+ self.modeller.from_json(expected_json).to_json(serialization=True)
+ ).to_json(serialization=True),
+ )
+
+ self.assertDictEqual(
+ self.modeller.to_json(serialization=True),
+ expected_json,
+ )
+
+class TestModellerBasic(unittest.TestCase):
+
+ def test_add_variable(self):
+ m = Modeller()
+ v = MagicMock()
+
+ result = m.add_variable(v)
+
+ self.assertIs(result, m)
+ self.assertIn(v, m.variable_list)
+
+ def test_add_constraint_expression_wrapped(self):
+ m = Modeller()
+ expr = MagicMock(spec=Expression)
+
+ m.add_constraint(expr)
+
+ self.assertEqual(len(m.constraint_list), 1)
+ self.assertNotEqual(m.constraint_list[0], expr)
+
+class TestModellerToJson(unittest.TestCase):
+
+ def setUp(self):
+ self.m = Modeller()
+ self.m.variable_list = []
+ self.m.constraint_list = []
+ self.m.objective_list = []
+
+ def test_to_json_no_searcher_raises(self):
+ with self.assertRaises(ModelFailure):
+ self.m.to_json()
+
+ def test_to_json_serialization_skips_searcher(self):
+ json_data = self.m.to_json(serialization=True)
+ self.assertNotIn("searcher", json_data)
+
+ def test_to_json_with_searcher(self):
+ self.m.set_searcher(SearcherType.DFS)
+
+ json_data = self.m.to_json()
+ self.assertEqual(json_data["searcher"], SearcherType.DFS.value)
+
+ def test_to_json_meta_cutoff(self):
+ cutoff = MagicMock(spec=Cutoff)
+ cutoff.is_meta.return_value = True
+ cutoff.to_json.return_value = {"t": 1}
+
+ self.m.set_searcher(SearcherType.DFS)
+ self.m.set_cutoff(cutoff)
+
+ json_data = self.m.to_json()
+ self.assertIn("meta_cutoff", json_data)
+
+ def test_to_json_callback_url(self):
+ self.m.set_searcher(SearcherType.DFS)
+ self.m.set_callback_url("https://callback")
+
+ json_data = self.m.to_json()
+ self.assertEqual(json_data["callback_url"], "https://callback")
+
+class TestConstraintFactory(unittest.TestCase):
+
+ def test_unknown_constraint_type(self):
+ with self.assertRaises(ValueError):
+ Modeller._constraints_factory(
+ {"type": "unknown"},
+ []
+ )
+
+class TestModellerFromJsonExtras(unittest.TestCase):
+
+ def test_solution_limit_default(self):
+ m = Modeller.from_json({})
+ self.assertEqual(m.solution_limit, 1)
+
+ def test_solution_limit_custom(self):
+ m = Modeller.from_json({"solution_limit": 5})
+ self.assertEqual(m.solution_limit, 5)
+
+ def test_cutoff_parsing(self):
+ with unittest.mock.patch(
+ "qaekwy.core.model.cutoff.Cutoff.from_json",
+ return_value="cutoff"
+ ):
+ m = Modeller.from_json({"cutoff": {"x": 1}})
+
+ self.assertEqual(m.cutoff, "cutoff")
+
+
+class TestModellerFromJsonVariables(unittest.TestCase):
+
+ def test_scalar_variable(self):
+ json_data = {
+ "var": [{"type": "integer", "name": "x", "brancher_value": "VAL_RND"}],
+ }
+
+ m = Modeller.from_json(json_data)
+ self.assertEqual(len(m.variable_list), 1)
+
+ def test_array_variable(self):
+ json_data = {
+ "var": [
+ {
+ "type": "integer_array",
+ "name": "a",
+ "length": 5,
+ "brancher_variable": "VAR_RND",
+ "brancher_value": "VAL_RND"
+ }
+ ],
+ }
+
+ m = Modeller.from_json(json_data)
+ self.assertEqual(len(m.variable_list), 1)
+
+ def test_matrix_variable(self):
+ json_data = {
+ "var": [
+ {
+ "type": "integer_array",
+ "subtype": "matrix",
+ "name": "m",
+ "cols": 2,
+ "rows": 3,
+ "brancher_variable": "VAR_RND",
+ "brancher_value": "VAL_RND"
+ }
+ ],
+ }
+
+ m = Modeller.from_json(json_data)
+ self.assertEqual(len(m.variable_list), 1)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/core/model/variable/test_boolean.py b/tests/core/model/variable/test_boolean.py
new file mode 100644
index 0000000..05bb66a
--- /dev/null
+++ b/tests/core/model/variable/test_boolean.py
@@ -0,0 +1,138 @@
+# pylint: skip-file
+
+import unittest
+
+from qaekwy.core.model.variable.boolean import (
+ BooleanVariable,
+ BooleanExpressionVariable,
+ BooleanVariableArray,
+ BooleanVariableMatrix,
+)
+
+from qaekwy.core.model.variable.variable import (
+ Expression,
+ VariableType,
+)
+
+from qaekwy.core.model.variable.branch import (
+ BranchBooleanVal,
+ BranchBooleanVar,
+)
+
+
+class TestBooleanVariable(unittest.TestCase):
+
+ def test_boolean_variable_basic_init(self):
+ v = BooleanVariable("b")
+
+ self.assertEqual(v.var_name, "b")
+ self.assertEqual(v.var_type, VariableType.BOOLEAN)
+ self.assertEqual(v.domain_low, 0)
+ self.assertEqual(v.domain_high, 1)
+ self.assertEqual(v.branch_val, BranchBooleanVal.VAL_RND)
+
+ def test_boolean_variable_to_json_and_from_json_roundtrip(self):
+ v = BooleanVariable(
+ "b1",
+ branch_val=BranchBooleanVal.VAL_RND,
+ branch_order=5,
+ )
+
+ data = v.to_json()
+ restored = BooleanVariable.from_json(data)
+
+ self.assertEqual(restored.var_name, v.var_name)
+ self.assertEqual(restored.var_type, VariableType.BOOLEAN)
+ self.assertEqual(restored.domain_low, 0)
+ self.assertEqual(restored.domain_high, 1)
+ self.assertEqual(restored.branch_val, BranchBooleanVal.VAL_RND)
+ self.assertEqual(restored.branching_order, 5)
+
+ def test_boolean_expression_variable_basic_init(self):
+ expr = "x > 5"
+ v = BooleanExpressionVariable("be", expression=expr)
+
+ self.assertEqual(v.var_name, "be")
+ self.assertEqual(v.var_type, VariableType.BOOLEAN)
+ self.assertEqual(str(v.expression), "x > 5")
+
+ def test_boolean_expression_variable_from_json(self):
+ json_data = {
+ "name": "be_json",
+ "expr": "y == 1",
+ "type": VariableType.BOOLEAN.value,
+ "brancher_value": BranchBooleanVal.VAL_RND.value,
+ "branching_order": 2,
+ }
+
+ v = BooleanExpressionVariable.from_json(json_data)
+
+ self.assertEqual(v.var_name, "be_json")
+ self.assertEqual(str(v.expression), "y == 1")
+ self.assertEqual(v.branch_val, BranchBooleanVal.VAL_RND)
+ self.assertEqual(v.branching_order, 2)
+
+ def test_boolean_variable_array_basic_init(self):
+ arr = BooleanVariableArray("bool_arr", length=10)
+
+ self.assertEqual(arr.var_name, "bool_arr")
+ self.assertEqual(arr.length, 10)
+ self.assertEqual(arr.var_type, VariableType.BOOLEAN_ARRAY)
+ self.assertEqual(arr.domain_low, 0)
+ self.assertEqual(arr.domain_high, 1)
+ self.assertEqual(arr.branch_var, BranchBooleanVar.VAR_RND)
+
+ def test_boolean_variable_array_to_json_and_from_json_roundtrip(self):
+ arr = BooleanVariableArray(
+ "arr_json",
+ length=3,
+ branch_var=BranchBooleanVar.VAR_RND,
+ branch_val=BranchBooleanVal.VAL_RND,
+ branch_order=1,
+ )
+
+ data = arr.to_json()
+ restored = BooleanVariableArray.from_json(data)
+
+ self.assertEqual(restored.var_name, arr.var_name)
+ self.assertEqual(restored.length, 3)
+ self.assertEqual(restored.domain_low, 0)
+ self.assertEqual(restored.domain_high, 1)
+ self.assertEqual(restored.branch_var, BranchBooleanVar.VAR_RND)
+ self.assertEqual(restored.branch_val, BranchBooleanVal.VAL_RND)
+ self.assertEqual(restored.branching_order, 1)
+
+ def test_boolean_variable_matrix_basic_init(self):
+ m = BooleanVariableMatrix("M", rows=4, cols=5)
+
+ self.assertEqual(m.rows, 4)
+ self.assertEqual(m.cols, 5)
+ self.assertEqual(m.length, 20)
+ self.assertEqual(m.var_type, VariableType.BOOLEAN_ARRAY)
+ self.assertEqual(m.domain_low, 0)
+ self.assertEqual(m.domain_high, 1)
+
+ def test_boolean_variable_matrix_to_json_and_from_json_roundtrip(self):
+ m = BooleanVariableMatrix(
+ "mat_json",
+ rows=2,
+ cols=2,
+ branch_var=BranchBooleanVar.VAR_RND,
+ branch_val=BranchBooleanVal.VAL_RND,
+ branch_order=4,
+ )
+
+ data = m.to_json()
+ restored = BooleanVariableMatrix.from_json(data)
+
+ self.assertEqual(restored.var_name, m.var_name)
+ self.assertEqual(restored.rows, 2)
+ self.assertEqual(restored.cols, 2)
+ self.assertEqual(restored.length, 4)
+ self.assertEqual(restored.branch_var, BranchBooleanVar.VAR_RND)
+ self.assertEqual(restored.branch_val, BranchBooleanVal.VAL_RND)
+ self.assertEqual(restored.branching_order, 4)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/model/variable/test_expression.py b/tests/core/model/variable/test_expression.py
similarity index 100%
rename from tests/model/variable/test_expression.py
rename to tests/core/model/variable/test_expression.py
diff --git a/tests/core/model/variable/test_float.py b/tests/core/model/variable/test_float.py
new file mode 100644
index 0000000..0bb4ac8
--- /dev/null
+++ b/tests/core/model/variable/test_float.py
@@ -0,0 +1,180 @@
+# pylint: skip-file
+
+import unittest
+
+from qaekwy.core.model.variable.float import (
+ FloatVariable,
+ FloatExpressionVariable,
+ FloatVariableArray,
+ FloatVariableMatrix,
+ Expression,
+ VariableType,
+)
+from qaekwy.core.model.variable.branch import (
+ BranchFloatVal,
+ BranchFloatVar,
+)
+
+
+class TestFloatVariable(unittest.TestCase):
+
+ def test_float_variable_basic_init(self):
+ v = FloatVariable("x")
+
+ assert v.var_name == "x"
+ assert v.var_type == VariableType.FLOAT
+ assert v.domain_low is None
+ assert v.domain_high is None
+ assert v.branch_val == BranchFloatVal.VAL_RND
+
+
+ def test_float_variable_with_domain(self):
+ v = FloatVariable("x", domain_low=0.0, domain_high=1.0)
+
+ assert v.domain_low == 0.0
+ assert v.domain_high == 1.0
+
+
+ def test_float_variable_to_json_and_from_json_roundtrip(self):
+ v = FloatVariable(
+ "x",
+ domain_low=-1.5,
+ domain_high=2.5,
+ branch_val=BranchFloatVal.VAL_RND,
+ branch_order=3,
+ )
+
+ data = v.to_json()
+ restored = FloatVariable.from_json(data)
+
+ assert restored.var_name == v.var_name
+ assert restored.var_type == VariableType.FLOAT
+ assert restored.domain_low == -1.5
+ assert restored.domain_high == 2.5
+ assert restored.branch_val == BranchFloatVal.VAL_RND
+ assert restored.branching_order == 3
+
+
+ def test_float_expression_variable_basic_init(self):
+ expr = Expression("x + 1.5")
+ v = FloatExpressionVariable("y", expression=expr.expr)
+
+ assert v.var_name == "y"
+ assert v.var_type == VariableType.FLOAT
+ assert str(v.expression) == "x + 1.5"
+
+
+ def test_float_expression_variable_from_json(self):
+ json_data = {
+ "name": "z",
+ "expr": "x * 0.5",
+ "type": VariableType.FLOAT.value,
+ "brancher_value": BranchFloatVal.VAL_RND.value,
+ "branching_order": 2,
+ }
+
+ v = FloatExpressionVariable.from_json(json_data)
+
+ assert v.var_name == "z"
+ assert v.var_type == VariableType.FLOAT
+ assert str(v.expression) == "x * 0.5"
+ assert v.branch_val == BranchFloatVal.VAL_RND
+ assert v.branching_order == 2
+
+
+ def test_float_variable_array_basic_init(self):
+ arr = FloatVariableArray("arr", length=5)
+
+ assert arr.var_name == "arr"
+ assert arr.length == 5
+ assert arr.var_type == VariableType.FLOAT_ARRAY
+ assert arr.domain_low is None
+ assert arr.domain_high is None
+ assert arr.branch_var == BranchFloatVar.VAR_RND
+
+
+ def test_float_variable_array_with_domain(self):
+ arr = FloatVariableArray(
+ "arr",
+ length=3,
+ domain_low=0.0,
+ domain_high=10.0,
+ )
+
+ assert arr.domain_low == 0.0
+ assert arr.domain_high == 10.0
+
+
+ def test_float_variable_array_to_json_and_from_json_roundtrip(self):
+ arr = FloatVariableArray(
+ "arr",
+ length=4,
+ domain_low=-2.0,
+ domain_high=2.0,
+ branch_var=BranchFloatVar.VAR_RND,
+ branch_val=BranchFloatVal.VAL_RND,
+ branch_order=1,
+ )
+
+ data = arr.to_json()
+ restored = FloatVariableArray.from_json(data)
+
+ assert restored.var_name == arr.var_name
+ assert restored.length == 4
+ assert restored.var_type == VariableType.FLOAT_ARRAY
+ assert restored.domain_low == -2.0
+ assert restored.domain_high == 2.0
+ assert restored.branch_var == BranchFloatVar.VAR_RND
+ assert restored.branch_val == BranchFloatVal.VAL_RND
+ assert restored.branching_order == 1
+
+ def test_float_variable_matrix_basic_init(self):
+ m = FloatVariableMatrix("M", rows=2, cols=3)
+
+ assert m.rows == 2
+ assert m.cols == 3
+ assert m.length == 6
+ assert m.var_type == VariableType.FLOAT_ARRAY
+ assert m.branch_var == BranchFloatVar.VAR_RND
+
+
+ def test_float_variable_matrix_with_domain(self):
+ m = FloatVariableMatrix(
+ "M",
+ rows=3,
+ cols=3,
+ domain_low=0.0,
+ domain_high=1.0,
+ )
+
+ assert m.domain_low == 0.0
+ assert m.domain_high == 1.0
+
+
+ def test_float_variable_matrix_to_json_and_from_json_roundtrip(self):
+ m = FloatVariableMatrix(
+ "M",
+ rows=2,
+ cols=2,
+ domain_low=-1.0,
+ domain_high=1.0,
+ branch_var=BranchFloatVar.VAR_RND,
+ branch_val=BranchFloatVal.VAL_RND,
+ branch_order=4,
+ )
+
+ data = m.to_json()
+ restored = FloatVariableMatrix.from_json(data)
+
+ assert restored.var_name == m.var_name
+ assert restored.rows == 2
+ assert restored.cols == 2
+ assert restored.length == 4
+ assert restored.domain_low == -1.0
+ assert restored.domain_high == 1.0
+ assert restored.branch_var == BranchFloatVar.VAR_RND
+ assert restored.branch_val == BranchFloatVal.VAL_RND
+ assert restored.branching_order == 4
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/core/model/variable/test_integer.py b/tests/core/model/variable/test_integer.py
new file mode 100644
index 0000000..ac25473
--- /dev/null
+++ b/tests/core/model/variable/test_integer.py
@@ -0,0 +1,291 @@
+# pylint: skip-file
+
+import unittest
+
+from qaekwy.core.model.variable.branch import BranchIntegerVal, BranchIntegerVar
+from qaekwy.core.model.variable.integer import IntegerVariable, IntegerVariableMatrix
+from qaekwy.core.model.variable.variable import Expression, VariableType, VectorExpression
+
+
+class TestExpression(unittest.TestCase):
+ def test_arithmetic_operations(self):
+ expr = Expression("x")
+ expr_add = expr + 2
+ self.assertEqual(str(expr_add), "(x + 2)")
+
+ expr_sub = expr - 3
+ self.assertEqual(str(expr_sub), "(x - 3)")
+
+ expr_mul = expr * 4
+ self.assertEqual(str(expr_mul), "x * 4")
+
+ expr_div = expr / 5
+ self.assertEqual(str(expr_div), "((x) / (5))")
+
+ expr_mod = expr % 6
+ self.assertEqual(str(expr_mod), "((x) % (6))")
+
+
+class TestVariable(unittest.TestCase):
+ def test_variable_to_json(self):
+ var = IntegerVariable("x", domain_low=0, domain_high=10)
+
+ var_json = var.to_json()
+ self.assertEqual(var_json["name"], "x")
+ self.assertEqual(var_json["type"], "integer")
+ self.assertEqual(var_json["brancher_value"], "VAL_RND")
+ self.assertEqual(var_json["domlow"], 0)
+ self.assertEqual(var_json["domup"], 10)
+
+
+class TestSpecificDomainVariable(unittest.TestCase):
+ def test_variable_to_json(self):
+ var = IntegerVariable("x", specific_domain=[2, 4, 6])
+
+ var_json = var.to_json()
+ self.assertEqual(var_json["name"], "x")
+ self.assertEqual(var_json["type"], "integer")
+ self.assertEqual(var_json["brancher_value"], "VAL_RND")
+ self.assertEqual(var_json["specific_domain"], [2, 4, 6])
+
+
+class TestExprVariable(unittest.TestCase):
+ def test_variable_to_json(self):
+ var = IntegerVariable("x")
+ var_expr = var + 2
+ var.expression = var_expr
+
+ var_json = var.to_json()
+ self.assertEqual(var_json["name"], "x")
+ self.assertEqual(var_json["type"], "integer")
+ self.assertEqual(var_json["brancher_value"], "VAL_RND")
+ self.assertEqual(var_json["expr"], "(x + 2)")
+
+
+class TestIntegerVariable(unittest.TestCase):
+ def test_integer_variable_to_json(self):
+ int_var = IntegerVariable("y", domain_low=1, domain_high=5)
+ int_var_json = int_var.to_json()
+ self.assertEqual(int_var_json["name"], "y")
+ self.assertEqual(int_var_json["type"], "integer")
+ self.assertEqual(int_var_json["brancher_value"], "VAL_RND")
+ self.assertEqual(int_var_json["domlow"], 1)
+ self.assertEqual(int_var_json["domup"], 5)
+
+ def test_from_json(self):
+ json_data = {
+ "name": "i",
+ "type": "integer",
+ "brancher_value": "VAL_MAX",
+ "specific_domain": [1, 2, 3],
+ }
+ i = IntegerVariable.from_json(json_data)
+ self.assertEqual(i.var_name, "i")
+ self.assertEqual(i.specific_domain, [1, 2, 3])
+ self.assertEqual(i.branch_val, BranchIntegerVal.VAL_MAX)
+
+ json_data = {
+ "name": "j",
+ "type": "integer",
+ "brancher_value": "VAL_MAX",
+ "domlow": 0,
+ "domup": 100,
+ }
+ i = IntegerVariable.from_json(json_data)
+ self.assertEqual(i.var_name, "j")
+ self.assertEqual(i.domain_low, 0)
+ self.assertEqual(i.domain_high, 100)
+ self.assertEqual(i.branch_val, BranchIntegerVal.VAL_MAX)
+
+
+class TestIntegerMatrix(unittest.TestCase):
+
+ def test_matrix_variable_basic_init(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=3)
+
+ assert m.rows == 2
+ assert m.cols == 3
+ assert m.length == 6
+ assert m.var_type == VariableType.INTEGER_ARRAY
+ assert m.var_name == "MATRIX$2$3$A"
+
+ def test_matrix_variable_domain_fields(self):
+ m = IntegerVariableMatrix(
+ "B",
+ rows=2,
+ cols=2,
+ domain_low=0,
+ domain_high=10,
+ specific_domain=[1, 3, 5],
+ )
+
+ assert m.domain_low == 0
+ assert m.domain_high == 10
+ assert m.specific_domain == [1, 3, 5]
+
+ def test_matrix_item_access_returns_expression(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=3)
+
+ expr = m[1][2]
+ assert isinstance(expr, Expression)
+ assert str(expr) == "MATRIX$2$3$A[5]" # 1 * 3 + 2
+
+
+ def test_row_vector_expression_creation(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=3)
+
+ v = m.row(1)
+ assert isinstance(v, VectorExpression)
+ assert v.kind == "row"
+ assert v.params["row"] == 1
+
+
+ def test_col_vector_expression_creation(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=3)
+
+ v = m.col(2)
+ assert isinstance(v, VectorExpression)
+ assert v.kind == "col"
+ assert v.params["col"] == 2
+
+
+ def test_slice_vector_expression_creation(self):
+ m = IntegerVariableMatrix("A", rows=3, cols=3)
+
+ v = m.slice(0, 1, 1, 2)
+
+ assert v.kind == "slice"
+ assert v.params["row_start"] == 0
+ assert v.params["col_start"] == 1
+ # slice() adds +1 internally
+ assert v.params["row_end"] == 2
+ assert v.params["col_end"] == 3
+
+ def test_iter_row_vector_expression(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=3)
+
+ v = m.row(0)
+ items = list(v)
+
+ assert len(items) == 3
+ assert [str(e) for e in items] == [
+ "MATRIX$2$3$A[0]",
+ "MATRIX$2$3$A[1]",
+ "MATRIX$2$3$A[2]",
+ ]
+
+
+ def test_iter_col_vector_expression(self):
+ m = IntegerVariableMatrix("A", rows=3, cols=2)
+
+ v = m.col(1)
+ items = list(v)
+
+ assert len(items) == 3
+ assert [str(e) for e in items] == [
+ "MATRIX$3$2$A[1]",
+ "MATRIX$3$2$A[3]",
+ "MATRIX$3$2$A[5]",
+ ]
+
+
+ def test_iter_slice_vector_expression(self):
+ m = IntegerVariableMatrix("A", rows=3, cols=3)
+
+ v = m.slice(0, 0, 1, 1)
+ items = list(v)
+
+ assert len(items) == 4
+ assert [str(e) for e in items] == [
+ "MATRIX$3$3$A[0]",
+ "MATRIX$3$3$A[1]",
+ "MATRIX$3$3$A[3]",
+ "MATRIX$3$3$A[4]",
+ ]
+
+
+ def test_vector_expression_str_row(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=3)
+
+ v = m.row(1)
+ assert str(v) == "MATRIX$2$3$A[2][3][r][1]"
+
+
+ def test_vector_expression_str_col(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=3)
+
+ v = m.col(0)
+ assert str(v) == "MATRIX$2$3$A[2][3][c][0]"
+
+
+ def test_vector_expression_str_slice(self):
+ m = IntegerVariableMatrix("A", rows=3, cols=3)
+
+ v = m.slice(0, 1, 1, 2)
+ assert str(v) == "MATRIX$3$3$A[3][3][s][0][1][2][3]"
+
+
+ def test_vector_expression_sum_method(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=2)
+
+ v = m.row(0)
+ s = v.sum()
+
+ assert isinstance(s, Expression)
+ assert str(s) == f"sum({v})"
+
+
+ def test_vector_expression_radd_with_zero(self):
+ m = IntegerVariableMatrix("A", rows=2, cols=2)
+
+ v = m.col(0)
+ result = sum([v]) # triggers __radd__ with 0
+
+ assert isinstance(result, Expression)
+ assert str(result) == f"sum({v})"
+
+
+ def test_matrix_variable_to_json(self):
+ m = IntegerVariableMatrix(
+ "A",
+ rows=2,
+ cols=3,
+ domain_low=0,
+ domain_high=5,
+ branch_var=BranchIntegerVar.VAR_RND,
+ branch_val=BranchIntegerVal.VAL_RND,
+ branch_order=7,
+ )
+
+ data = m.to_json()
+
+ assert data["name"] == m.var_name
+ assert data["rows"] == 2
+ assert data["cols"] == 3
+ assert data["length"] == 6
+ assert data["subtype"] == "matrix"
+ assert data["domlow"] == 0
+ assert data["domup"] == 5
+ assert data["branching_order"] == 7
+
+
+ def test_matrix_variable_from_json_roundtrip(self):
+ m = IntegerVariableMatrix(
+ "A",
+ rows=2,
+ cols=2,
+ domain_low=1,
+ domain_high=9,
+ )
+
+ data = m.to_json()
+ restored = IntegerVariableMatrix.from_json(data)
+
+ assert restored.var_name == m.var_name
+ assert restored.rows == 2
+ assert restored.cols == 2
+ assert restored.domain_low == 1
+ assert restored.domain_high == 9
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/test_explanation.py b/tests/core/test_explanation.py
similarity index 100%
rename from tests/test_explanation.py
rename to tests/core/test_explanation.py
diff --git a/tests/core/test_response.py b/tests/core/test_response.py
new file mode 100644
index 0000000..aab581f
--- /dev/null
+++ b/tests/core/test_response.py
@@ -0,0 +1,105 @@
+# pylint: skip-file
+
+import unittest
+import json
+# from unittest.mock import MagicMock
+
+from qaekwy.core.response import (
+ AbstractResponse, EchoResponse, StatusResponse,
+ SolutionResponse, ExplanationResponse, VersionResponse,
+ ClusterStatusResponse
+)
+
+class TestResponseClasses(unittest.TestCase):
+
+ def test_abstract_response_default_ok(self):
+ resp = AbstractResponse({"content": "data"})
+ self.assertEqual(resp.get_status(), "Ok")
+ self.assertTrue(resp.is_status_ok())
+ self.assertEqual(resp.get_message(), "")
+
+ def test_abstract_response_custom_status(self):
+ resp = AbstractResponse({"status": "Error", "message": "Failed"})
+ self.assertEqual(resp.get_status(), "Error")
+ self.assertFalse(resp.is_status_ok())
+ self.assertEqual(resp.get_message(), "Failed")
+
+ def test_echo_response(self):
+ content = "hello world"
+ resp = EchoResponse(content)
+ self.assertEqual(resp.get_status(), "")
+ self.assertTrue(resp.is_status_ok())
+ self.assertEqual(resp.get_message(), content)
+ self.assertEqual(resp.get_content(), content)
+
+ def test_status_response_fields(self):
+ data = {
+ "type": "info",
+ "code": 200,
+ "busy_node": True,
+ "current_solution_found": 5
+ }
+ resp = StatusResponse(data)
+ self.assertEqual(resp.get_type(), "info")
+ self.assertEqual(resp.get_code(), 200)
+ self.assertTrue(resp.is_busy())
+ self.assertEqual(resp.get_number_of_solution_found(), 5)
+
+ def test_status_response_defaults(self):
+ resp = StatusResponse({})
+ self.assertEqual(resp.get_type(), "")
+ self.assertEqual(resp.get_code(), -1)
+ self.assertFalse(resp.is_busy())
+ self.assertEqual(resp.get_number_of_solution_found(), -1)
+
+ def test_version_response(self):
+ data = {
+ "app": "OptiEngine",
+ "author": "DevTeam",
+ "version": "1.2.3",
+ "version_major": 1,
+ "version_minor": 2,
+ "version_build": 3,
+ "version_release": "stable"
+ }
+ resp = VersionResponse(data)
+ self.assertEqual(resp.get_app(), "OptiEngine")
+ self.assertEqual(resp.get_version_major(), 1)
+ self.assertEqual(resp.get_release(), "stable")
+
+ def test_cluster_status_parsing(self):
+ node_data = [
+ {
+ "identifier": "node_01",
+ "url": "http://node1",
+ "enabled": True,
+ "message": "Healthy",
+ "busy_node": False,
+ "current_solution_found": 10,
+ "failure": False,
+ "awake": True
+ }
+ ]
+ resp = ClusterStatusResponse(json.dumps(node_data))
+ nodes = resp.get_node_status_list()
+
+ self.assertIsNotNone(nodes)
+ self.assertEqual(len(nodes), 1)
+ self.assertEqual(nodes[0].identifier, "node_01")
+ self.assertEqual(nodes[0].number_of_solutions, 10)
+ self.assertTrue(nodes[0].is_enabled)
+
+ def test_cluster_status_empty(self):
+ resp = ClusterStatusResponse(None)
+ self.assertIsNone(resp.get_node_status_list())
+
+ def test_solution_response_error_status(self):
+ resp = SolutionResponse({"status": "Error", "content": []})
+ self.assertIsNone(resp.get_solutions())
+
+ def test_explanation_response_error_status(self):
+ resp = ExplanationResponse({"status": "Error", "content": {}})
+ self.assertIsNone(resp.get_explanation())
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/core/test_solution.py b/tests/core/test_solution.py
new file mode 100644
index 0000000..1bef5fa
--- /dev/null
+++ b/tests/core/test_solution.py
@@ -0,0 +1,170 @@
+# pylint: skip-file
+
+import unittest
+
+from io import StringIO
+from contextlib import redirect_stdout
+
+from qaekwy.core.solution import Solution
+
+class TestSolutionScalars(unittest.TestCase):
+
+ def test_scalar_assigned(self):
+ content = [
+ {"name": "x", "assigned": True, "value": 5},
+ {"name": "y", "assigned": True, "value": 10},
+ ]
+
+ sol = Solution(content)
+
+ self.assertEqual(sol["x"], 5)
+ self.assertEqual(sol["y"], 10)
+
+ self.assertEqual(sol.x, 5)
+ self.assertEqual(sol.y, 10)
+
+ def test_scalar_unassigned(self):
+ content = [
+ {"name": "x", "assigned": False, "value": None},
+ ]
+
+ sol = Solution(content)
+
+ self.assertIsNone(sol["x"])
+ self.assertIsNone(sol.x)
+
+class TestSolutionArrays(unittest.TestCase):
+
+ def test_array_with_positions(self):
+ content = [
+ {"name": "a", "assigned": True, "value": 1, "position": 0},
+ {"name": "a", "assigned": True, "value": 3, "position": 2},
+ {"name": "a", "assigned": False, "value": None, "position": 1},
+ ]
+
+ sol = Solution(content)
+
+ self.assertEqual(sol["a"], [1, None, 3])
+ self.assertEqual(sol.a, [1, None, 3])
+
+ def test_sparse_array_positions(self):
+ content = [
+ {"name": "b", "assigned": True, "value": 7, "position": 3},
+ ]
+
+ sol = Solution(content)
+
+ self.assertEqual(sol["b"], [None, None, None, 7])
+
+class TestSolutionMatrices(unittest.TestCase):
+
+ def test_matrix_reconstruction(self):
+ content = [
+ {"name": "MATRIX$2$3$m", "assigned": True, "value": 1, "position": 0},
+ {"name": "MATRIX$2$3$m", "assigned": True, "value": 2, "position": 1},
+ {"name": "MATRIX$2$3$m", "assigned": True, "value": 3, "position": 2},
+ {"name": "MATRIX$2$3$m", "assigned": True, "value": 4, "position": 3},
+ {"name": "MATRIX$2$3$m", "assigned": True, "value": 5, "position": 4},
+ {"name": "MATRIX$2$3$m", "assigned": True, "value": 6, "position": 5},
+ ]
+
+ sol = Solution(content)
+
+ expected = [
+ [1, 2, 3],
+ [4, 5, 6],
+ ]
+
+ self.assertIn("m", sol)
+ self.assertNotIn("MATRIX$2$3$m", sol)
+
+ self.assertEqual(sol["m"], expected)
+ self.assertEqual(sol.m, expected)
+
+class TestSolutionMixed(unittest.TestCase):
+
+ def test_mixed_solution(self):
+ content = [
+ {"name": "x", "assigned": True, "value": 9},
+ {"name": "arr", "assigned": True, "value": 1, "position": 0},
+ {"name": "arr", "assigned": True, "value": 2, "position": 1},
+ {"name": "MATRIX$1$2$m", "assigned": True, "value": 4, "position": 0},
+ {"name": "MATRIX$1$2$m", "assigned": True, "value": 5, "position": 1},
+ ]
+
+ sol = Solution(content)
+
+ self.assertEqual(sol.x, 9)
+ self.assertEqual(sol.arr, [1, 2])
+ self.assertEqual(sol.m, [[4, 5]])
+
+class TestSolutionRepr(unittest.TestCase):
+
+ def test_repr(self):
+ content = [{"name": "x", "assigned": True, "value": 1}]
+ sol = Solution(content)
+
+ rep = repr(sol)
+
+ self.assertTrue(rep.startswith("Solution("))
+ self.assertIn("'x': 1", rep)
+
+class TestSolutionPrettyPrint(unittest.TestCase):
+
+ def test_pretty_print_scalar(self):
+ content = [{"name": "x", "assigned": True, "value": 5}]
+ sol = Solution(content)
+
+ buf = StringIO()
+ with redirect_stdout(buf):
+ sol.pretty_print()
+
+ output = buf.getvalue()
+
+ self.assertIn("Solution:", output)
+ self.assertIn("x", output)
+ self.assertIn("5", output)
+
+ def test_pretty_print_array(self):
+ content = [
+ {"name": "a", "assigned": True, "value": 1, "position": 0},
+ {"name": "a", "assigned": False, "value": None, "position": 1},
+ ]
+ sol = Solution(content)
+
+ buf = StringIO()
+ with redirect_stdout(buf):
+ sol.pretty_print()
+
+ output = buf.getvalue()
+
+ self.assertIn("[1, -]", output)
+
+ def test_pretty_print_matrix(self):
+ content = [
+ {"name": "MATRIX$2$2$m", "assigned": True, "value": 1, "position": 0},
+ {"name": "MATRIX$2$2$m", "assigned": True, "value": 2, "position": 1},
+ {"name": "MATRIX$2$2$m", "assigned": True, "value": 3, "position": 2},
+ {"name": "MATRIX$2$2$m", "assigned": True, "value": 4, "position": 3},
+ ]
+
+ sol = Solution(content)
+
+ buf = StringIO()
+ with redirect_stdout(buf):
+ sol.pretty_print()
+
+ output = buf.getvalue()
+
+ self.assertIn("(2 x 2 matrix)", output)
+ self.assertIn("1 2", output)
+ self.assertIn("3 4", output)
+
+ def test_pretty_print_empty(self):
+ sol = Solution([])
+
+ buf = StringIO()
+ with redirect_stdout(buf):
+ sol.pretty_print()
+
+ self.assertIn("Empty solution", buf.getvalue())
diff --git a/tests/model/constraint/test_distinct.py b/tests/model/constraint/test_distinct.py
deleted file mode 100644
index 21e9c59..0000000
--- a/tests/model/constraint/test_distinct.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# pylint: skip-file
-
-
-import unittest
-
-from qaekwy.core.model.variable.integer import IntegerVariableArray
-from qaekwy.core.model.constraint.distinct import (
- ConstraintDistinctArray,
- ConstraintDistinctCol,
- ConstraintDistinctRow,
- ConstraintDistinctSlice,
-)
-
-
-class TestConstraintDistinctArray(unittest.TestCase):
-
- def setUp(self):
- self.array_var = IntegerVariableArray("array_var", 10, 0, 30)
-
- def test_constraint_array_creation(self):
- constraint = ConstraintDistinctArray(
- self.array_var, "distinct_array_constraint"
- )
- self.assertEqual(constraint.var_1, self.array_var)
- self.assertEqual(constraint.constraint_name, "distinct_array_constraint")
-
- def test_constraint_array_to_json(self):
- constraint = ConstraintDistinctArray(
- self.array_var, "distinct_array_constraint"
- )
- expected_json = {
- "name": "distinct_array_constraint",
- "type": "distinct",
- "v1": "array_var",
- "selection": "standard",
- }
-
- self.assertDictEqual(constraint.to_json(), expected_json)
-
-
-class TestConstraintDistinctRow(unittest.TestCase):
-
- def setUp(self):
- self.array_var = IntegerVariableArray("array_var", 10, 0, 30)
-
- def test_constraint_row_creation(self):
- constraint = ConstraintDistinctRow(
- self.array_var, size=3, idx=1, constraint_name="distinct_row_constraint"
- )
- self.assertEqual(constraint.var_1, self.array_var)
- self.assertEqual(constraint.size, 3)
- self.assertEqual(constraint.idx, 1)
- self.assertEqual(constraint.constraint_name, "distinct_row_constraint")
-
- def test_constraint_row_to_json(self):
- constraint = ConstraintDistinctRow(
- self.array_var, size=3, idx=1, constraint_name="distinct_row_constraint"
- )
- expected_json = {
- "name": "distinct_row_constraint",
- "type": "distinct",
- "v1": "array_var",
- "selection": "row",
- "size": 3,
- "index": 1,
- }
- self.assertDictEqual(constraint.to_json(), expected_json)
-
-
-class TestConstraintDistinctCol(unittest.TestCase):
-
- def setUp(self):
- self.array_var = IntegerVariableArray("array_var", 10, 0, 30)
-
- def test_constraint_column_creation(self):
- constraint = ConstraintDistinctCol(
- self.array_var, size=3, idx=0, constraint_name="distinct_col_constraint"
- )
- self.assertEqual(constraint.var_1, self.array_var)
- self.assertEqual(constraint.size, 3)
- self.assertEqual(constraint.idx, 0)
- self.assertEqual(constraint.constraint_name, "distinct_col_constraint")
-
- def test_constraint_column_to_json(self):
- constraint = ConstraintDistinctCol(
- self.array_var, size=3, idx=0, constraint_name="distinct_col_constraint"
- )
- expected_json = {
- "name": "distinct_col_constraint",
- "type": "distinct",
- "v1": "array_var",
- "selection": "col",
- "size": 3,
- "index": 0,
- }
- self.assertDictEqual(constraint.to_json(), expected_json)
-
-
-class TestConstraintDistinctSlice(unittest.TestCase):
-
- def setUp(self):
- self.array_var = IntegerVariableArray("array_var", 10, 0, 30)
-
- def test_constraint_slice_creation(self):
- constraint = ConstraintDistinctSlice(
- self.array_var,
- size=6,
- offset_start_x=1,
- offset_start_y=1,
- offset_end_x=3,
- offset_end_y=2,
- constraint_name="distinct_slice_constraint",
- )
- self.assertEqual(constraint.var_1, self.array_var)
- self.assertEqual(constraint.size, 6)
- self.assertEqual(constraint.offset_start_x, 1)
- self.assertEqual(constraint.offset_start_y, 1)
- self.assertEqual(constraint.offset_end_x, 3)
- self.assertEqual(constraint.offset_end_y, 2)
- self.assertEqual(constraint.constraint_name, "distinct_slice_constraint")
-
- def test_constraint_slice_to_json(self):
- constraint = ConstraintDistinctSlice(
- self.array_var,
- size=6,
- offset_start_x=1,
- offset_start_y=1,
- offset_end_x=3,
- offset_end_y=2,
- constraint_name="distinct_slice_constraint",
- )
- expected_json = {
- "name": "distinct_slice_constraint",
- "type": "distinct",
- "v1": "array_var",
- "selection": "slice",
- "size": 6,
- "offset_start_x": 1,
- "offset_start_y": 1,
- "offset_end_x": 3,
- "offset_end_y": 2,
- }
- self.assertDictEqual(
- constraint.to_json(),
- constraint.from_json(expected_json, [self.array_var]).to_json(),
- expected_json,
- )
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/model/test_modeller.py b/tests/model/test_modeller.py
deleted file mode 100644
index 5065c84..0000000
--- a/tests/model/test_modeller.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# pylint: skip-file
-
-import unittest
-from qaekwy.core.model.constraint.abs import ConstraintAbs
-from qaekwy.core.model.modeller import Modeller
-from qaekwy.core.model.specific import SpecificMinimum
-from qaekwy.core.model.searcher import SearcherType
-from qaekwy.core.model.cutoff import CutoffFibonacci
-from qaekwy.core.model.variable.integer import IntegerVariable
-
-
-class TestModeller(unittest.TestCase):
-
- def setUp(self):
- self.modeller = Modeller()
- self.var1 = IntegerVariable("var1", 0, 10)
- self.var2 = IntegerVariable("var2", 0, 10)
- self.constraint = ConstraintAbs(
- var_1=self.var1, var_2=self.var2, constraint_name="abs"
- )
- self.objective = SpecificMinimum(self.var1)
- self.searcher = SearcherType.DFS
- self.cutoff = CutoffFibonacci()
- self.callback_url = "https://example.com/callback"
-
- def test_add_variable(self):
- self.modeller.add_variable(self.var1).add_variable(self.var2)
- self.assertEqual(self.modeller.variable_list, [self.var1, self.var2])
-
- def test_add_constraint(self):
- self.modeller.add_constraint(self.constraint)
- self.assertEqual(self.modeller.constraint_list, [self.constraint])
-
- def test_add_objective(self):
- self.modeller.add_objective(self.objective)
- self.assertEqual(self.modeller.objective_list, [self.objective])
-
- def test_set_searcher(self):
- self.modeller.set_searcher(self.searcher)
- self.assertEqual(self.modeller.searcher, self.searcher)
-
- def test_set_cutoff(self):
- self.modeller.set_cutoff(self.cutoff)
- self.assertEqual(self.modeller.cutoff, self.cutoff)
-
- def test_set_callback_url(self):
- self.modeller.set_callback_url(self.callback_url)
- self.assertEqual(self.modeller.callback_url, self.callback_url)
-
- def test_to_json(self):
- self.modeller.add_variable(self.var1).add_variable(self.var2).add_constraint(
- self.constraint
- ).add_objective(self.objective)
- self.modeller.set_searcher(self.searcher).set_cutoff(
- self.cutoff
- ).set_callback_url(self.callback_url)
-
- expected_json = {
- "callback_url": "https://example.com/callback",
- "constraint": [{"name": "abs", "type": "abs", "v1": "var1", "v2": "var2"}],
- "cutoff": {"name": "fibonacci"},
- "solution_limit": 1,
- "specific": [{"type": "minimize", "var": "var1"}],
- "var": [
- {
- "brancher_value": "VAL_RND",
- "branching_order": -1,
- "domlow": 0,
- "domup": 10,
- "name": "var1",
- "type": "integer",
- },
- {
- "brancher_value": "VAL_RND",
- "branching_order": -1,
- "domlow": 0,
- "domup": 10,
- "name": "var2",
- "type": "integer",
- },
- ],
- }
-
- print(
- self.modeller.from_json(
- self.modeller.from_json(expected_json).to_json(serialization=True)
- ).to_json(serialization=True)
- )
-
- self.assertDictEqual(
- self.modeller.to_json(serialization=True),
- self.modeller.from_json(expected_json).to_json(serialization=True),
- self.modeller.from_json(
- self.modeller.from_json(expected_json).to_json(serialization=True)
- ).to_json(serialization=True),
- )
-
- self.assertDictEqual(
- self.modeller.to_json(serialization=True),
- expected_json,
- )
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/model/variable/test_integer.py b/tests/model/variable/test_integer.py
deleted file mode 100644
index 2c55475..0000000
--- a/tests/model/variable/test_integer.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# pylint: skip-file
-
-import unittest
-
-from qaekwy.core.model.variable.branch import BranchIntegerVal
-from qaekwy.core.model.variable.integer import IntegerVariable
-from qaekwy.core.model.variable.variable import Expression
-
-
-class TestExpression(unittest.TestCase):
- def test_arithmetic_operations(self):
- expr = Expression("x")
- expr_add = expr + 2
- self.assertEqual(str(expr_add), "(x + 2)")
-
- expr_sub = expr - 3
- self.assertEqual(str(expr_sub), "(x - 3)")
-
- expr_mul = expr * 4
- self.assertEqual(str(expr_mul), "x * 4")
-
- expr_div = expr / 5
- self.assertEqual(str(expr_div), "((x) / (5))")
-
- expr_mod = expr % 6
- self.assertEqual(str(expr_mod), "((x) % (6))")
-
-
-class TestVariable(unittest.TestCase):
- def test_variable_to_json(self):
- var = IntegerVariable("x", domain_low=0, domain_high=10)
-
- var_json = var.to_json()
- self.assertEqual(var_json["name"], "x")
- self.assertEqual(var_json["type"], "integer")
- self.assertEqual(var_json["brancher_value"], "VAL_RND")
- self.assertEqual(var_json["domlow"], 0)
- self.assertEqual(var_json["domup"], 10)
-
-
-class TestSpecificDomainVariable(unittest.TestCase):
- def test_variable_to_json(self):
- var = IntegerVariable("x", specific_domain=[2, 4, 6])
-
- var_json = var.to_json()
- self.assertEqual(var_json["name"], "x")
- self.assertEqual(var_json["type"], "integer")
- self.assertEqual(var_json["brancher_value"], "VAL_RND")
- self.assertEqual(var_json["specific_domain"], [2, 4, 6])
-
-
-class TestExprVariable(unittest.TestCase):
- def test_variable_to_json(self):
- var = IntegerVariable("x")
- var_expr = var + 2
- var.expression = var_expr
-
- var_json = var.to_json()
- self.assertEqual(var_json["name"], "x")
- self.assertEqual(var_json["type"], "integer")
- self.assertEqual(var_json["brancher_value"], "VAL_RND")
- self.assertEqual(var_json["expr"], "(x + 2)")
-
-
-class TestIntegerVariable(unittest.TestCase):
- def test_integer_variable_to_json(self):
- int_var = IntegerVariable("y", domain_low=1, domain_high=5)
- int_var_json = int_var.to_json()
- self.assertEqual(int_var_json["name"], "y")
- self.assertEqual(int_var_json["type"], "integer")
- self.assertEqual(int_var_json["brancher_value"], "VAL_RND")
- self.assertEqual(int_var_json["domlow"], 1)
- self.assertEqual(int_var_json["domup"], 5)
-
- def test_from_json(self):
- json_data = {
- "name": "i",
- "type": "integer",
- "brancher_value": "VAL_MAX",
- "specific_domain": [1, 2, 3],
- }
- i = IntegerVariable.from_json(json_data)
- self.assertEqual(i.var_name, "i")
- self.assertEqual(i.specific_domain, [1, 2, 3])
- self.assertEqual(i.branch_val, BranchIntegerVal.VAL_MAX)
-
- json_data = {
- "name": "j",
- "type": "integer",
- "brancher_value": "VAL_MAX",
- "domlow": 0,
- "domup": 100,
- }
- i = IntegerVariable.from_json(json_data)
- self.assertEqual(i.var_name, "j")
- self.assertEqual(i.domain_low, 0)
- self.assertEqual(i.domain_high, 100)
- self.assertEqual(i.branch_val, BranchIntegerVal.VAL_MAX)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/test_solution.py b/tests/test_solution.py
deleted file mode 100644
index 81f7371..0000000
--- a/tests/test_solution.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# pylint: skip-file
-
-import unittest
-
-from qaekwy.core.solution import Solution
-
-
-class SolutionTest(unittest.TestCase):
-
- def test_init(self):
- solution_json_content = [
- {"name": "x", "assigned": True, "value": 5},
- {"name": "y", "assigned": True, "value": 10},
- {"name": "z", "assigned": False, "value": None},
- ]
- solution = Solution(solution_json_content)
-
- self.assertEqual(solution["x"], 5)
- self.assertEqual(solution["y"], 10)
- self.assertEqual(solution["z"], None)
-
- self.assertTrue(hasattr(solution, "x"))
- self.assertTrue(hasattr(solution, "y"))
- self.assertTrue(hasattr(solution, "z"))
-
- self.assertEqual(solution.x, 5)
- self.assertEqual(solution.y, 10)
- self.assertEqual(solution.z, None)
-
- def test_positional_assignment(self):
- solution_json_content = [
- {"name": "x", "assigned": True, "value": 5, "position": 1},
- {"name": "y", "assigned": True, "value": 10, "position": 0},
- {"name": "z", "assigned": False, "value": None},
- ]
- solution = Solution(solution_json_content)
-
- self.assertEqual(solution["x"], [None, 5])
- self.assertEqual(solution["y"][0], 10)
- self.assertEqual(solution["z"], None)
-
- def test_missing_variable(self):
- solution_json_content = [
- {"name": "x", "assigned": True, "value": 5},
- {"name": "y", "assigned": True, "value": 10},
- ]
- solution = Solution(solution_json_content)
-
- with self.assertRaises(KeyError):
- solution["z"]
-
- def test_invalid_position(self):
- solution_json_content = [
- {"name": "x", "assigned": True, "value": 5, "position": 1},
- {"name": "y", "assigned": True, "value": 10, "position": 0},
- {"name": "z", "assigned": False, "value": None},
- ]
- solution = Solution(solution_json_content)
-
- with self.assertRaises(IndexError):
- solution["x"][2]
-
-
-if __name__ == "__main__":
- unittest.main()
From bbbc24fa9fa69bf607951745c083056376250dd5 Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:16:26 +0100
Subject: [PATCH 2/9] dev-0-3-1 README.md
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index bfb0a2f..eeb69c2 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ z = m.integer_variable("z", (-10, 10))
m.constraint(x + 2*y + 3*z <= 15)
m.maximize(x)
-m.solve_one().pretty_print()
+m.solve_one(searcher="bab").pretty_print()
```
*Output*:
@@ -53,9 +53,9 @@ m.solve_one().pretty_print()
----------------------------------------
Solution:
----------------------------------------
-x: -3
+x: 10
y: 2
-z: 4
+z: -4
----------------------------------------
```
From 7d5a9f435b67fa091ca9b44d44da91ca3a72e009 Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:20:11 +0100
Subject: [PATCH 3/9] add: coverage report
---
.github/workflows/ci.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6eb27d0..5b16cdd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -45,8 +45,8 @@ jobs:
run: |
python -m pytest --cov=qaekwy --cov-report=xml --cov-fail-under=80 tests
-# - name: Upload coverage to Codecov
-# uses: codecov/codecov-action@v4
-# with:
-# files: coverage.xml
-# fail_ci_if_error: true
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v4
+ with:
+ files: coverage.xml
+ fail_ci_if_error: true
From 4f1dc9a809d3b4ff556019188e7762527a27ce8f Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:22:17 +0100
Subject: [PATCH 4/9] fix: code linting
---
qaekwy/__init__.py | 27 ++++++++----
qaekwy/__main__.py | 10 ++++-
qaekwy/api/model.py | 57 ++++++++++++++++++--------
qaekwy/core/engine.py | 13 ++++--
qaekwy/core/model/modeller.py | 18 +++++---
qaekwy/core/model/variable/boolean.py | 10 ++++-
qaekwy/core/model/variable/float.py | 10 ++++-
qaekwy/core/model/variable/integer.py | 10 ++++-
qaekwy/core/model/variable/variable.py | 13 ++++--
9 files changed, 124 insertions(+), 44 deletions(-)
diff --git a/qaekwy/__init__.py b/qaekwy/__init__.py
index dcbefab..3104fd1 100644
--- a/qaekwy/__init__.py
+++ b/qaekwy/__init__.py
@@ -3,13 +3,26 @@
from .api.exceptions import SolverError
from .api.model import Model
from .core.model import function as math
-from .core.model.cutoff import (Cutoff, CutoffConstant, CutoffFibonacci,
- CutoffGeometric, CutoffLinear, CutoffLuby,
- CutoffRandom, MetaCutoffAppender,
- MetaCutoffMerger, MetaCutoffRepeater)
-from .core.model.variable.branch import (BranchBooleanVal, BranchBooleanVar,
- BranchFloatVal, BranchFloatVar,
- BranchIntegerVal, BranchIntegerVar)
+from .core.model.cutoff import (
+ Cutoff,
+ CutoffConstant,
+ CutoffFibonacci,
+ CutoffGeometric,
+ CutoffLinear,
+ CutoffLuby,
+ CutoffRandom,
+ MetaCutoffAppender,
+ MetaCutoffMerger,
+ MetaCutoffRepeater,
+)
+from .core.model.variable.branch import (
+ BranchBooleanVal,
+ BranchBooleanVar,
+ BranchFloatVal,
+ BranchFloatVar,
+ BranchIntegerVal,
+ BranchIntegerVar,
+)
__all__ = [
"Model",
diff --git a/qaekwy/__main__.py b/qaekwy/__main__.py
index aa2407f..d76f9f7 100644
--- a/qaekwy/__main__.py
+++ b/qaekwy/__main__.py
@@ -4,8 +4,14 @@
import argparse
-from .__metadata__ import (__author__, __copyright__, __license__,
- __license_url__, __software__, __version__)
+from .__metadata__ import (
+ __author__,
+ __copyright__,
+ __license__,
+ __license_url__,
+ __software__,
+ __version__,
+)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Qaekwy Python Library")
diff --git a/qaekwy/api/model.py b/qaekwy/api/model.py
index 2867479..19d09e5 100644
--- a/qaekwy/api/model.py
+++ b/qaekwy/api/model.py
@@ -24,10 +24,12 @@
from ..core.model.constraint.asin import ConstraintASin
from ..core.model.constraint.atan import ConstraintATan
from ..core.model.constraint.cos import ConstraintCos
-from ..core.model.constraint.distinct import (ConstraintDistinctArray,
- ConstraintDistinctCol,
- ConstraintDistinctRow,
- ConstraintDistinctSlice)
+from ..core.model.constraint.distinct import (
+ ConstraintDistinctArray,
+ ConstraintDistinctCol,
+ ConstraintDistinctRow,
+ ConstraintDistinctSlice,
+)
from ..core.model.constraint.divide import ConstraintDivide
from ..core.model.constraint.element import ConstraintElement
from ..core.model.constraint.exponential import ConstraintExponential
@@ -41,25 +43,44 @@
from ..core.model.constraint.nroot import ConstraintNRoot
from ..core.model.constraint.power import ConstraintPower
from ..core.model.constraint.sin import ConstraintSin
-from ..core.model.constraint.sort import (ConstraintReverseSorted,
- ConstraintSorted)
+from ..core.model.constraint.sort import ConstraintReverseSorted, ConstraintSorted
from ..core.model.constraint.tan import ConstraintTan
from ..core.model.cutoff import Cutoff
from ..core.model.modeller import Modeller
from ..core.model.searcher import SearcherType
from ..core.model.specific import SpecificMaximum, SpecificMinimum
-from ..core.model.variable.boolean import BooleanVariable, BooleanVariableArray, BooleanVariableMatrix
-from ..core.model.variable.branch import (BranchBooleanVal, BranchBooleanVar,
- BranchFloatVal, BranchFloatVar,
- BranchIntegerVal, BranchIntegerVar)
-from ..core.model.variable.float import (FloatExpressionVariable,
- FloatVariable, FloatVariableArray, FloatVariableMatrix)
-from ..core.model.variable.integer import (IntegerExpressionVariable,
- IntegerVariable,
- IntegerVariableArray, IntegerVariableMatrix)
-from ..core.model.variable.variable import (ArrayVariable, Expression,
- MatrixVariable, Variable,
- VariableType, VectorExpression)
+from ..core.model.variable.boolean import (
+ BooleanVariable,
+ BooleanVariableArray,
+ BooleanVariableMatrix,
+)
+from ..core.model.variable.branch import (
+ BranchBooleanVal,
+ BranchBooleanVar,
+ BranchFloatVal,
+ BranchFloatVar,
+ BranchIntegerVal,
+ BranchIntegerVar,
+)
+from ..core.model.variable.float import (
+ FloatExpressionVariable,
+ FloatVariable,
+ FloatVariableArray,
+ FloatVariableMatrix,
+)
+from ..core.model.variable.integer import (
+ IntegerExpressionVariable,
+ IntegerVariable,
+ IntegerVariableArray,
+ IntegerVariableMatrix,
+)
+from ..core.model.variable.variable import (
+ ArrayVariable,
+ Expression,
+ Variable,
+ VariableType,
+ VectorExpression,
+)
from ..core.response import SolutionResponse
from ..core.solution import Solution
from .exceptions import SolverError
diff --git a/qaekwy/core/engine.py b/qaekwy/core/engine.py
index 3d684bf..e6ad5e6 100644
--- a/qaekwy/core/engine.py
+++ b/qaekwy/core/engine.py
@@ -81,9 +81,16 @@
from ..__metadata__ import __software__, __version__
from .model import DIRECTENGINE_API_ENDPOINT
from .model.modeller import Modeller
-from .response import (AbstractResponse, ClusterStatusResponse, EchoResponse,
- ExplanationResponse, ModelJSonResponse,
- SolutionResponse, StatusResponse, VersionResponse)
+from .response import (
+ AbstractResponse,
+ ClusterStatusResponse,
+ EchoResponse,
+ ExplanationResponse,
+ ModelJSonResponse,
+ SolutionResponse,
+ StatusResponse,
+ VersionResponse,
+)
class AbstractAction(ABC):
diff --git a/qaekwy/core/model/modeller.py b/qaekwy/core/model/modeller.py
index 2d93d30..67b56f5 100644
--- a/qaekwy/core/model/modeller.py
+++ b/qaekwy/core/model/modeller.py
@@ -27,9 +27,12 @@
from .constraint.asin import ConstraintASin
from .constraint.atan import ConstraintATan
from .constraint.cos import ConstraintCos
-from .constraint.distinct import (ConstraintDistinctArray,
- ConstraintDistinctCol, ConstraintDistinctRow,
- ConstraintDistinctSlice)
+from .constraint.distinct import (
+ ConstraintDistinctArray,
+ ConstraintDistinctCol,
+ ConstraintDistinctRow,
+ ConstraintDistinctSlice,
+)
from .constraint.divide import ConstraintDivide
from .constraint.element import ConstraintElement
from .constraint.exponential import ConstraintExponential
@@ -49,8 +52,13 @@
from .cutoff import Cutoff
from .searcher import SearcherType
from .specific import SpecificMaximum, SpecificMinimum
-from .variable.variable import (ArrayVariable, Expression, MatrixVariable,
- Variable, VariableType)
+from .variable.variable import (
+ ArrayVariable,
+ Expression,
+ MatrixVariable,
+ Variable,
+ VariableType,
+)
ConstraintFactory = Callable[
[dict, list[Union[Variable, ArrayVariable, MatrixVariable]]],
diff --git a/qaekwy/core/model/variable/boolean.py b/qaekwy/core/model/variable/boolean.py
index 65d7033..75c3cf0 100644
--- a/qaekwy/core/model/variable/boolean.py
+++ b/qaekwy/core/model/variable/boolean.py
@@ -12,8 +12,14 @@
from typing import Optional
from .branch import BranchBooleanVal, BranchBooleanVar, BranchVal, BranchVar
-from .variable import (ArrayVariable, Expression, ExpressionVariable,
- MatrixVariable, Variable, VariableType)
+from .variable import (
+ ArrayVariable,
+ Expression,
+ ExpressionVariable,
+ MatrixVariable,
+ Variable,
+ VariableType,
+)
class BooleanVariable(Variable):
diff --git a/qaekwy/core/model/variable/float.py b/qaekwy/core/model/variable/float.py
index 9f2c020..c25c36c 100644
--- a/qaekwy/core/model/variable/float.py
+++ b/qaekwy/core/model/variable/float.py
@@ -12,8 +12,14 @@
from typing import Optional
from .branch import BranchFloatVal, BranchFloatVar, BranchVal
-from .variable import (ArrayVariable, Expression, ExpressionVariable,
- MatrixVariable, Variable, VariableType)
+from .variable import (
+ ArrayVariable,
+ Expression,
+ ExpressionVariable,
+ MatrixVariable,
+ Variable,
+ VariableType,
+)
class FloatVariable(Variable):
diff --git a/qaekwy/core/model/variable/integer.py b/qaekwy/core/model/variable/integer.py
index 0f166d6..e439678 100644
--- a/qaekwy/core/model/variable/integer.py
+++ b/qaekwy/core/model/variable/integer.py
@@ -13,8 +13,14 @@
from typing import Optional
from .branch import BranchIntegerVal, BranchIntegerVar, BranchVal, BranchVar
-from .variable import (ArrayVariable, Expression, ExpressionVariable,
- MatrixVariable, Variable, VariableType)
+from .variable import (
+ ArrayVariable,
+ Expression,
+ ExpressionVariable,
+ MatrixVariable,
+ Variable,
+ VariableType,
+)
class IntegerVariable(Variable):
diff --git a/qaekwy/core/model/variable/variable.py b/qaekwy/core/model/variable/variable.py
index 29a4f64..8440e19 100644
--- a/qaekwy/core/model/variable/variable.py
+++ b/qaekwy/core/model/variable/variable.py
@@ -19,9 +19,16 @@
from enum import Enum
from typing import Optional, Union
-from .branch import (BranchBooleanVal, BranchBooleanVar, BranchFloatVal,
- BranchFloatVar, BranchIntegerVal, BranchIntegerVar,
- BranchVal, BranchVar)
+from .branch import (
+ BranchBooleanVal,
+ BranchBooleanVar,
+ BranchFloatVal,
+ BranchFloatVar,
+ BranchIntegerVal,
+ BranchIntegerVar,
+ BranchVal,
+ BranchVar,
+)
class VariableType(Enum):
From aecc8bd7dc76c85bed4d2b4a4caf346ec7fbdf8a Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:24:24 +0100
Subject: [PATCH 5/9] add: dev deps
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index 3ddce64..d382602 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,6 +46,7 @@ dev = [
"pytest-cov",
"black",
"mypy",
+ "pylint",
"build",
"twine",
]
From ccb316b6ae3d6578c5cafdfcd57efc858ded768f Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:27:01 +0100
Subject: [PATCH 6/9] fix: ci coverage rendering from xml to json
---
.github/workflows/ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5b16cdd..70bdb53 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -43,10 +43,10 @@ jobs:
- name: Test with Pytest + coverage
run: |
- python -m pytest --cov=qaekwy --cov-report=xml --cov-fail-under=80 tests
+ python -m pytest --cov=qaekwy --cov-report=json --cov-fail-under=80 tests
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
- files: coverage.xml
+ files: coverage.json
fail_ci_if_error: true
From 2794da272a6ec78814f95de9b3f4f40f7664c65c Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:38:19 +0100
Subject: [PATCH 7/9] add: coverage badge
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index eeb69c2..8d7d101 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,8 @@
*A modern, open-source Python framework for declarative constraint programming and combinatorial optimization*.
- 
+  
+
## Overview
From 56b66da9ef890b69d4f40cc93e7355916e10522a Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 18:53:33 +0100
Subject: [PATCH 8/9] fix: CI
---
.github/workflows/ci.yml | 8 +-------
README.md | 4 +---
2 files changed, 2 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 70bdb53..bf35e36 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -43,10 +43,4 @@ jobs:
- name: Test with Pytest + coverage
run: |
- python -m pytest --cov=qaekwy --cov-report=json --cov-fail-under=80 tests
-
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@v4
- with:
- files: coverage.json
- fail_ci_if_error: true
+ python -m pytest --cov=qaekwy --cov-fail-under=80 tests
diff --git a/README.md b/README.md
index 8d7d101..007ab2f 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,7 @@
*A modern, open-source Python framework for declarative constraint programming and combinatorial optimization*.
-  
-
-
+ 
## Overview
Qaekwy is a Python library designed for modeling and solving combinatorial optimization and constraint satisfaction problems.
From bb3f7dd5682ef9bccc756d22fd289eec76109b44 Mon Sep 17 00:00:00 2001
From: alex-87
Date: Sat, 10 Jan 2026 19:04:50 +0100
Subject: [PATCH 9/9] fix: README
---
README.md | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 007ab2f..5d35438 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,9 @@ It provides a clean, Pythonic interface for defining variables, constraints, and
* 👩🏫 **Teaching** — Demonstrate CSP concepts with no setup
* 🔬 **Research & Prototyping** — Explore models, heuristics, and ideas fast
+## 📚 Documentation
+
+Visit the [Qaekwy Documentation](https://docs.qaekwy.io/) for guides, teaching resources, and detailed examples.
## 🚀 Quick Start
@@ -73,11 +76,6 @@ Configure solver behavior using explicit search strategies such as Depth-First S
Transparent handling of model serialization and execution on the Qaekwy Cloud Solver instance.
-## 📚 Documentation
-
-Visit the [Qaekwy Documentation](https://docs.qaekwy.io/) for guides, teaching resources, and detailed examples.
-
-
## Examples
### 🔢 Constraint Programming -- Sudoku