eqc-models 0.11.0__tar.gz → 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eqc_models-0.11.0/eqc_models.egg-info → eqc_models-0.12.0}/PKG-INFO +2 -3
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/conf.py +1 -1
- eqc_models-0.12.0/eqc_models/assignment/__init__.py +6 -0
- eqc_models-0.12.0/eqc_models/assignment/resource.py +165 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/polyeval.c +4219 -3442
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/quadratic.py +2 -2
- eqc_models-0.12.0/eqc_models/base/results.py +166 -0
- eqc_models-0.12.0/eqc_models/graph/__init__.py +9 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/graph/base.py +8 -4
- eqc_models-0.12.0/eqc_models/graph/shortestpath.py +157 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/classifierbase.py +31 -5
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/classifierqboost.py +14 -1
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/classifierqsvm.py +223 -19
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/clustering.py +5 -5
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/clusteringbase.py +1 -1
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/decomposition.py +39 -16
- eqc_models-0.12.0/eqc_models/process/base.py +18 -0
- eqc_models-0.12.0/eqc_models/process/mpc.py +17 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/solvers/__init__.py +1 -5
- eqc_models-0.12.0/eqc_models/solvers/eqcdirect.py +71 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/solvers/qciclient.py +6 -3
- {eqc_models-0.11.0 → eqc_models-0.12.0/eqc_models.egg-info}/PKG-INFO +2 -3
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models.egg-info/SOURCES.txt +59 -3
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models.egg-info/requires.txt +1 -3
- {eqc_models-0.11.0 → eqc_models-0.12.0}/pyproject.toml +5 -2
- eqc_models-0.12.0/scripts/crew_assignment_example.py +51 -0
- eqc_models-0.12.0/scripts/qboost_iris_dirac3_direct.py +103 -0
- eqc_models-0.12.0/scripts/qsvm_dual_iris_dirac3.py +101 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/qsvm_iris_dirac3.py +2 -5
- eqc_models-0.12.0/scripts/test_shortestpath.py +38 -0
- eqc_models-0.12.0/test/testshortestpath.py +55 -0
- eqc_models-0.12.0/test_suite/README.txt +22 -0
- eqc_models-0.12.0/test_suite/run_tests.py +293 -0
- eqc_models-0.12.0/test_suite/test_cases/c6h6_graph_clustering/c6h6_graph_clustering.py +60 -0
- eqc_models-0.12.0/test_suite/test_cases/clustering/clustering.py +49 -0
- eqc_models-0.12.0/test_suite/test_cases/clustering/data/X.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/cvqboost_iris/cvqboost_iris.py +63 -0
- eqc_models-0.12.0/test_suite/test_cases/cvqboost_iris/data/X_test.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/cvqboost_iris/data/X_train.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/cvqboost_iris/data/y_test.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/cvqboost_iris/data/y_train.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/karate_graph_clustering/karate_graph_clustering.py +44 -0
- eqc_models-0.12.0/test_suite/test_cases/pca_iris/pca_iris.py +59 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1MJC/data/C_1MJC.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1MJC/data/J_1MJC.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1MJC/protein_design_1MJC.py +52 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1NXB/data/C_1NXB.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1NXB/data/J_1NXB.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1NXB/protein_design_1NXB.py +52 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1POH/data/C_1POH.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1POH/data/J_1POH.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/protein_design_1POH/protein_design_1POH.py +52 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_dual_iris/data/X_test.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_dual_iris/data/X_train.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_dual_iris/data/y_test.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_dual_iris/data/y_train.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_dual_iris/qsvm_dual_iris.py +64 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_primal_iris/data/X_test.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_primal_iris/data/X_train.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_primal_iris/data/y_test.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_primal_iris/data/y_train.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/qsvm_primal_iris/qsvm_primal_iris.py +57 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_100/data/C_8000000_100.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_100/data/J_8000000_100.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_100/synthetic_cls_100.py +52 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_300/data/C_8000000_300.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_300/data/J_8000000_300.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_300/synthetic_cls_300.py +52 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_500/data/C_8000000_500.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_500/data/J_8000000_500.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_500/synthetic_cls_500.py +52 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_700/data/C_8000000_700.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_700/data/J_8000000_700.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_700/synthetic_cls_700.py +52 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_900/data/C_8000000_900.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_900/data/J_8000000_900.npy +0 -0
- eqc_models-0.12.0/test_suite/test_cases/synthetic_cls_900/synthetic_cls_900.py +52 -0
- eqc_models-0.12.0/test_suite/test_suite_config.json +212 -0
- eqc_models-0.12.0/test_suite/test_utils.py +34 -0
- eqc_models-0.11.0/eqc_models/assignment/__init__.py +0 -5
- eqc_models-0.11.0/eqc_models/base/results.py +0 -94
- eqc_models-0.11.0/eqc_models/graph/__init__.py +0 -6
- eqc_models-0.11.0/eqc_models/sequence/scheduling.py +0 -29
- eqc_models-0.11.0/scripts/duality_example.py +0 -73
- {eqc_models-0.11.0 → eqc_models-0.12.0}/.gitlab-ci.yml +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/LICENSE.txt +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/MANIFEST.in +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/README.md +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/compile_extensions.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/Makefile +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/basic.css +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/css/badge_only.css +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/css/theme.css +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/custom.css +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/file.png +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/minus.png +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/plus.png +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/pygments.css +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/build/html/_static/white_logo.png +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/make.bat +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/_static/custom.css +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/_static/white_logo.png +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/dependencies.rst +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/eqc_models.rst +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/index.rst +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/modules.rst +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/docs/source/usage.rst +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/algorithms/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/algorithms/base.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/algorithms/penaltymultiplier.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/allocation/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/allocation/allocation.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/allocation/portbase.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/allocation/portmomentum.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/assignment/qap.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/assignment/setpartition.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/base.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/constraints.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/operators.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/polyeval.pyx +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/base/polynomial.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/combinatorics/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/combinatorics/setcover.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/combinatorics/setpartition.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/decoding.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/graph/hypergraph.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/graph/maxcut.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/graph/maxkcut.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/graph/partition.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/cvqboost_hamiltonian.pyx +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/cvqboost_hamiltonian_c_func.c +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/cvqboost_hamiltonian_c_func.h +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/forecast.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/forecastbase.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/regressor.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/regressorbase.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/ml/reservoir.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/sequence/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/sequence/tsp.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/utilities/__init__.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/utilities/fileio.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/utilities/polynomial.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models/utilities/qplib.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models.egg-info/dependency_links.txt +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/eqc_models.egg-info/top_level.txt +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/binary_job_example.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/c6h6_graph_clustering.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/clustering.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/continuous_job_example.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/graph_clustering.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/graph_partitioning.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/hamiltonian_to_polynomial.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/hypergraph.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/integer_job_example.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/karate_graph_clustering.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/lin_reg_dirac3.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/mackey_glass_cell_production_series.csv +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/pca_iris_dirac3.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/port_opt_dirac3.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/qboost_iris_dirac3.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/qplib_benchmark_config.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/qplib_reader.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/qplib_runner.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/reservoir_forecast.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/results_example.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/rundoctests.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/scripts/utils.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/setup.cfg +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/doctest_base.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testallocationmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testconstraint.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testcvqboost.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testeqcdirectsolver.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testgraphpartitionmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testhypergraphmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testmaxcutmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testpolynomialmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testqapmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testqciclientsolver.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testquadraticmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testsetcovermodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testsetpartitionmodel.py +0 -0
- {eqc_models-0.11.0 → eqc_models-0.12.0}/test/testtsp.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eqc-models
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Optimization and ML modeling package targeting EQC devices
|
|
5
5
|
Author: Quantum Computing Inc.
|
|
6
6
|
Author-email: support@quantumcomputinginc.com
|
|
@@ -16,8 +16,7 @@ Requires-Dist: pandas>=2.1.0
|
|
|
16
16
|
Requires-Dist: scikit-learn>=1.2.1
|
|
17
17
|
Requires-Dist: qci-client<5,>=4.3.0
|
|
18
18
|
Requires-Dist: emucore-direct==1.0.6
|
|
19
|
-
|
|
20
|
-
Requires-Dist: eqc-direct<2,>=1.3.0; extra == "direct"
|
|
19
|
+
Requires-Dist: eqc-direct<3,>=2.0.1
|
|
21
20
|
Provides-Extra: dev
|
|
22
21
|
Requires-Dist: pytest<8,>=7.1.0; extra == "dev"
|
|
23
22
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
project = 'eqc-models'
|
|
11
11
|
# sphinx adds a period after copyright
|
|
12
|
-
copyright = '
|
|
12
|
+
copyright = '2025 Quantum Computing Inc. All rights reserved'
|
|
13
13
|
author = 'Quantum Computing Inc.'
|
|
14
14
|
|
|
15
15
|
import importlib.metadata as metadata
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from typing import (Dict, List)
|
|
2
|
+
import numpy as np
|
|
3
|
+
from eqc_models.base.quadratic import ConstrainedQuadraticModel
|
|
4
|
+
from eqc_models.base.constraints import InequalitiesMixin
|
|
5
|
+
|
|
6
|
+
class ResourceAssignmentModel(InequalitiesMixin, ConstrainedQuadraticModel):
|
|
7
|
+
"""
|
|
8
|
+
Resource assignment model
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
------------
|
|
12
|
+
|
|
13
|
+
resources : List
|
|
14
|
+
tasks : List
|
|
15
|
+
|
|
16
|
+
>>> # name is not a required attribute of the resources or tasks
|
|
17
|
+
>>> crews = [{"name": "Maintenance Crew 1", "skills": ["A", "F"], "capacity": 5, "cost": 4},
|
|
18
|
+
... {"name": "Baggage Crew 1", "skills": ["B"], "capacity": 4, "cost": 1},
|
|
19
|
+
... {"name": "Maintenance Crew 2", "skills": ["A", "F"], "capacity": 5, "cost": 2}]
|
|
20
|
+
>>> tasks = [{"name": "Refuel", "skill_need": "F", "load": 3},
|
|
21
|
+
... {"name": "Baggage", "skill_need": "B", "load": 1}]
|
|
22
|
+
>>> model = ResourceAssignmentModel(crews, tasks)
|
|
23
|
+
>>> assignments = model.createAssignmentVars()
|
|
24
|
+
>>> assignments
|
|
25
|
+
[{'resource': 0, 'task': 0}, {'resource': 1, 'task': 1}, {'resource': 2, 'task': 0}]
|
|
26
|
+
>>> A, b, senses = model.constrainAssignments(assignments)
|
|
27
|
+
>>> A
|
|
28
|
+
array([[3., 0., 0.],
|
|
29
|
+
[0., 1., 0.],
|
|
30
|
+
[0., 0., 3.],
|
|
31
|
+
[3., 0., 3.],
|
|
32
|
+
[0., 3., 0.]], dtype=float32)
|
|
33
|
+
>>> b
|
|
34
|
+
array([5., 4., 5., 3., 3.], dtype=float32)
|
|
35
|
+
>>> senses
|
|
36
|
+
['LE', 'LE', 'LE', 'EQ', 'EQ']
|
|
37
|
+
>>> A, b = model.constraints
|
|
38
|
+
>>> A
|
|
39
|
+
array([[3., 0., 0., 1., 0., 0.],
|
|
40
|
+
[0., 1., 0., 0., 1., 0.],
|
|
41
|
+
[0., 0., 3., 0., 0., 1.],
|
|
42
|
+
[3., 0., 3., 0., 0., 0.],
|
|
43
|
+
[0., 3., 0., 0., 0., 0.]])
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, resources, tasks):
|
|
48
|
+
self.resources = resources
|
|
49
|
+
self.checkTasks(tasks)
|
|
50
|
+
self.tasks = tasks
|
|
51
|
+
self.assignments = assignments = self.createAssignmentVars()
|
|
52
|
+
n = len(assignments) + len(resources)
|
|
53
|
+
self.variables = [f"a{i}" for i in range(len(assignments))]
|
|
54
|
+
self.upper_bound = np.ones((n,))
|
|
55
|
+
self.upper_bound[-len(resources):] = [resource["capacity"] for resource in resources]
|
|
56
|
+
A, b, senses = self.constrainAssignments(assignments)
|
|
57
|
+
J = np.zeros((n, n))
|
|
58
|
+
C = np.zeros((n,), dtype=np.float32)
|
|
59
|
+
# objective is to minimize cost of assignments
|
|
60
|
+
for j, assignment in enumerate(assignments):
|
|
61
|
+
C[j] = resources[assignment["resource"]]["cost"] * tasks[assignment["task"]]["load"]
|
|
62
|
+
super(ResourceAssignmentModel, self).__init__(C, J, A, b)
|
|
63
|
+
self.senses = senses
|
|
64
|
+
# always use a machine slack
|
|
65
|
+
self.machine_slacks = 1
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def checkTasks(cls, tasks):
|
|
69
|
+
for task in tasks:
|
|
70
|
+
if "skill_need" not in task:
|
|
71
|
+
raise ValueError("All tasks must have the skill_need attribute")
|
|
72
|
+
if "load" not in task:
|
|
73
|
+
raise ValueError("All tasks must have the load attribute")
|
|
74
|
+
|
|
75
|
+
def createAssignmentVars(self):
|
|
76
|
+
""" Examine all combinatins of possible crew-task assignments """
|
|
77
|
+
|
|
78
|
+
assign_vars = []
|
|
79
|
+
resources = self.resources
|
|
80
|
+
tasks = self.tasks
|
|
81
|
+
for i, resource in enumerate(resources):
|
|
82
|
+
skills = resource["skills"]
|
|
83
|
+
for j, task in enumerate(tasks):
|
|
84
|
+
if task["skill_need"] in skills:
|
|
85
|
+
assign_vars.append({"resource": i, "task": j})
|
|
86
|
+
return assign_vars
|
|
87
|
+
|
|
88
|
+
def constrainAssignments(self, assignments : List) -> List:
|
|
89
|
+
"""
|
|
90
|
+
Examine the assignments to determine the necessary constraints to
|
|
91
|
+
ensure feasibility of solution.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
# A is sized using the number of crews and the number of assignment variables plus slacks
|
|
95
|
+
m1 = len(self.resources)
|
|
96
|
+
m2 = len(self.tasks)
|
|
97
|
+
n1 = len(assignments)
|
|
98
|
+
m = m1 + m2
|
|
99
|
+
n = n1
|
|
100
|
+
A = np.zeros((m, n), dtype=np.float32)
|
|
101
|
+
b = np.zeros((m,), dtype=np.float32)
|
|
102
|
+
for i, resource in enumerate(self.resources):
|
|
103
|
+
b[i] = resource["capacity"]
|
|
104
|
+
for k, assignment in enumerate(assignments):
|
|
105
|
+
if assignment["resource"] == i:
|
|
106
|
+
A[i, k] = self.tasks[assignment["task"]]["load"]
|
|
107
|
+
assignment_coeff = np.max(A)
|
|
108
|
+
for i, task in enumerate(self.tasks):
|
|
109
|
+
b[m1+i] = assignment_coeff
|
|
110
|
+
for k, assignment in enumerate(assignments):
|
|
111
|
+
if assignment["task"] == i:
|
|
112
|
+
A[m1+i, k] = assignment_coeff
|
|
113
|
+
senses = ["LE" for resource in self.resources] + ["EQ" for task in self.tasks]
|
|
114
|
+
return A, b, senses
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def sum_constraint(self) -> int:
|
|
118
|
+
""" This value is a suggestion which should be used with a machine slack """
|
|
119
|
+
|
|
120
|
+
sc = 0
|
|
121
|
+
sc += sum([resource["capacity"] for resource in self.resources])
|
|
122
|
+
sc += len(self.tasks)
|
|
123
|
+
return sc
|
|
124
|
+
|
|
125
|
+
def decode(self, solution : np.array) -> List[Dict]:
|
|
126
|
+
"""
|
|
127
|
+
Convert the binary solution into a list of tasks
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
# ensure solution is array
|
|
132
|
+
solution = np.array(solution)
|
|
133
|
+
resource_assignments = [[] for resource in self.resources]
|
|
134
|
+
vals = [val for val in set(solution) if val <= 1.0]
|
|
135
|
+
# check if there are fractional values less than 1
|
|
136
|
+
if solution[~np.logical_or(solution==0, solution>=1)].size>0:
|
|
137
|
+
# iterate over the values and assign tasks by largest value for tasks
|
|
138
|
+
# not assigned already
|
|
139
|
+
remaining_tasks = list(range(len(self.tasks)))
|
|
140
|
+
fltr = self.upper_bound==1
|
|
141
|
+
while len(remaining_tasks) > 0 and solution[fltr].shape[0]>0:
|
|
142
|
+
largest = np.max(solution[fltr])
|
|
143
|
+
indices, = np.where(np.logical_and(fltr, solution == largest))
|
|
144
|
+
for idx in indices:
|
|
145
|
+
assignment = self.assignments[idx]
|
|
146
|
+
if assignment["task"] in remaining_tasks:
|
|
147
|
+
task = self.tasks[assignment["task"]]
|
|
148
|
+
resource_assignments[assignment["resource"]].append(task)
|
|
149
|
+
del remaining_tasks[remaining_tasks.index(assignment["task"])]
|
|
150
|
+
break
|
|
151
|
+
fltr = np.logical_and(fltr, solution < largest)
|
|
152
|
+
else:
|
|
153
|
+
# Use the restriction that a task cannot be assigned more than once
|
|
154
|
+
for j, task in enumerate(self.tasks):
|
|
155
|
+
highest = 0
|
|
156
|
+
best_resource = None
|
|
157
|
+
for a, assignment in zip(solution, self.assignments):
|
|
158
|
+
if assignment["task"] == j:
|
|
159
|
+
if a > highest:
|
|
160
|
+
highest = a
|
|
161
|
+
best_resource = assignment["resource"]
|
|
162
|
+
assert best_resource is not None, f"solution had no positive assignment values for {task}"
|
|
163
|
+
resource_assignments[best_resource].append(task)
|
|
164
|
+
|
|
165
|
+
return resource_assignments
|