eqc-models 0.11.1__tar.gz → 0.13.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.13.0/.gitignore +6 -0
- {eqc_models-0.11.1/eqc_models.egg-info → eqc_models-0.13.0}/PKG-INFO +7 -9
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/base.py +11 -0
- eqc_models-0.13.0/eqc_models/base/binaries.py +33 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/constraints.py +39 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/polynomial.py +21 -2
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/quadratic.py +2 -2
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/results.py +81 -1
- eqc_models-0.13.0/eqc_models/graph/__init__.py +11 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/graph/base.py +8 -4
- eqc_models-0.13.0/eqc_models/graph/rcshortestpath.py +81 -0
- eqc_models-0.13.0/eqc_models/graph/shortestpath.py +190 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/classifierbase.py +30 -5
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/classifierqboost.py +14 -1
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/classifierqsvm.py +28 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/clustering.py +5 -5
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/clusteringbase.py +1 -2
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/decomposition.py +0 -1
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/regressorbase.py +0 -1
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/process/base.py +6 -1
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/process/mpc.py +1 -1
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/solvers/__init__.py +10 -6
- eqc_models-0.13.0/eqc_models/solvers/eqcdirect.py +76 -0
- eqc_models-0.13.0/eqc_models/solvers/mip.py +115 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/solvers/qciclient.py +11 -22
- eqc_models-0.13.0/eqc_models/solvers/responselog.py +47 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0/eqc_models.egg-info}/PKG-INFO +7 -9
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models.egg-info/SOURCES.txt +61 -2
- eqc_models-0.13.0/eqc_models.egg-info/requires.txt +12 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/pyproject.toml +14 -12
- eqc_models-0.13.0/scripts/binary_w_continuous_solver_example.py +46 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/integer_job_example.py +1 -1
- eqc_models-0.13.0/scripts/mip_example.py +47 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/port_opt_dirac3.py +0 -1
- eqc_models-0.13.0/scripts/qboost_iris_dirac3_direct.py +103 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/qplib_benchmark_config.py +1 -1
- eqc_models-0.13.0/scripts/test_shortestpath.py +65 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testpolynomialmodel.py +6 -4
- eqc_models-0.13.0/test/testshortestpath.py +55 -0
- eqc_models-0.13.0/test_suite/README.txt +22 -0
- eqc_models-0.13.0/test_suite/run_tests.py +293 -0
- eqc_models-0.13.0/test_suite/test_cases/c6h6_graph_clustering/c6h6_graph_clustering.py +60 -0
- eqc_models-0.13.0/test_suite/test_cases/clustering/clustering.py +49 -0
- eqc_models-0.13.0/test_suite/test_cases/clustering/data/X.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/cvqboost_iris/cvqboost_iris.py +63 -0
- eqc_models-0.13.0/test_suite/test_cases/cvqboost_iris/data/X_test.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/cvqboost_iris/data/X_train.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/cvqboost_iris/data/y_test.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/cvqboost_iris/data/y_train.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/karate_graph_clustering/karate_graph_clustering.py +44 -0
- eqc_models-0.13.0/test_suite/test_cases/pca_iris/pca_iris.py +59 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1MJC/data/C_1MJC.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1MJC/data/J_1MJC.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1MJC/protein_design_1MJC.py +52 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1NXB/data/C_1NXB.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1NXB/data/J_1NXB.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1NXB/protein_design_1NXB.py +52 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1POH/data/C_1POH.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1POH/data/J_1POH.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/protein_design_1POH/protein_design_1POH.py +52 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_dual_iris/data/X_test.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_dual_iris/data/X_train.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_dual_iris/data/y_test.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_dual_iris/data/y_train.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_dual_iris/qsvm_dual_iris.py +64 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_primal_iris/data/X_test.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_primal_iris/data/X_train.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_primal_iris/data/y_test.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_primal_iris/data/y_train.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/qsvm_primal_iris/qsvm_primal_iris.py +57 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_100/data/C_8000000_100.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_100/data/J_8000000_100.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_100/synthetic_cls_100.py +52 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_300/data/C_8000000_300.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_300/data/J_8000000_300.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_300/synthetic_cls_300.py +52 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_500/data/C_8000000_500.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_500/data/J_8000000_500.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_500/synthetic_cls_500.py +52 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_700/data/C_8000000_700.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_700/data/J_8000000_700.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_700/synthetic_cls_700.py +52 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_900/data/C_8000000_900.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_900/data/J_8000000_900.npy +0 -0
- eqc_models-0.13.0/test_suite/test_cases/synthetic_cls_900/synthetic_cls_900.py +52 -0
- eqc_models-0.13.0/test_suite/test_suite_config.json +212 -0
- eqc_models-0.13.0/test_suite/test_utils.py +34 -0
- eqc_models-0.11.1/eqc_models/base/polyeval.c +0 -11363
- eqc_models-0.11.1/eqc_models/graph/__init__.py +0 -6
- eqc_models-0.11.1/eqc_models.egg-info/requires.txt +0 -14
- {eqc_models-0.11.1 → eqc_models-0.13.0}/.gitlab-ci.yml +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/LICENSE.txt +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/MANIFEST.in +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/README.md +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/compile_extensions.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/Makefile +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/basic.css +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/css/badge_only.css +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/css/theme.css +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/custom.css +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/file.png +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/minus.png +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/plus.png +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/pygments.css +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/build/html/_static/white_logo.png +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/make.bat +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/_static/custom.css +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/_static/white_logo.png +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/conf.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/dependencies.rst +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/eqc_models.rst +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/index.rst +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/modules.rst +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/docs/source/usage.rst +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/algorithms/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/algorithms/base.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/algorithms/penaltymultiplier.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/allocation/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/allocation/allocation.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/allocation/portbase.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/allocation/portmomentum.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/assignment/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/assignment/qap.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/assignment/resource.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/assignment/setpartition.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/operators.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/base/polyeval.pyx +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/combinatorics/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/combinatorics/setcover.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/combinatorics/setpartition.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/decoding.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/graph/hypergraph.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/graph/maxcut.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/graph/maxkcut.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/graph/partition.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/cvqboost_hamiltonian.pyx +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/cvqboost_hamiltonian_c_func.c +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/cvqboost_hamiltonian_c_func.h +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/forecast.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/forecastbase.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/regressor.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/ml/reservoir.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/sequence/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/sequence/tsp.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/utilities/__init__.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/utilities/fileio.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/utilities/polynomial.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models/utilities/qplib.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models.egg-info/dependency_links.txt +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/eqc_models.egg-info/top_level.txt +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/binary_job_example.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/c6h6_graph_clustering.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/clustering.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/continuous_job_example.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/crew_assignment_example.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/graph_clustering.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/graph_partitioning.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/hamiltonian_to_polynomial.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/hypergraph.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/karate_graph_clustering.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/lin_reg_dirac3.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/mackey_glass_cell_production_series.csv +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/pca_iris_dirac3.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/qboost_iris_dirac3.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/qplib_reader.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/qplib_runner.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/qsvm_dual_iris_dirac3.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/qsvm_iris_dirac3.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/reservoir_forecast.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/results_example.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/rundoctests.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/scripts/utils.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/setup.cfg +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/doctest_base.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testallocationmodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testconstraint.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testcvqboost.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testeqcdirectsolver.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testgraphpartitionmodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testhypergraphmodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testmaxcutmodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testqapmodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testqciclientsolver.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testquadraticmodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testsetcovermodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testsetpartitionmodel.py +0 -0
- {eqc_models-0.11.1 → eqc_models-0.13.0}/test/testtsp.py +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eqc-models
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Optimization and ML modeling package targeting EQC devices
|
|
5
|
-
Author: Quantum Computing Inc.
|
|
6
|
-
Author-email: support@quantumcomputinginc.com
|
|
5
|
+
Author-email: "Quantum Computing Inc." <support@quantumcomputinginc.com>
|
|
7
6
|
Project-URL: Homepage, https://quantumcomputinginc.com
|
|
8
7
|
Project-URL: Documentation, https://quantumcomputinginc.com/learn/support/software-packages/
|
|
9
8
|
Project-URL: Issues, https://support.quantumcomputinginc.com/
|
|
@@ -12,12 +11,11 @@ Description-Content-Type: text/markdown
|
|
|
12
11
|
License-File: LICENSE.txt
|
|
13
12
|
Requires-Dist: numpy<2,>=1.22.1
|
|
14
13
|
Requires-Dist: networkx<3,>=2.6.3
|
|
15
|
-
Requires-Dist: pandas
|
|
16
|
-
Requires-Dist: scikit-learn
|
|
17
|
-
Requires-Dist: qci-client<5
|
|
18
|
-
Requires-Dist: emucore-direct==1.0.
|
|
19
|
-
|
|
20
|
-
Requires-Dist: eqc-direct<2,>=1.3.0; extra == "direct"
|
|
14
|
+
Requires-Dist: pandas<3,>=2.1.0
|
|
15
|
+
Requires-Dist: scikit-learn<2,>=1.2.1
|
|
16
|
+
Requires-Dist: qci-client<6,>=5
|
|
17
|
+
Requires-Dist: emucore-direct==1.0.7
|
|
18
|
+
Requires-Dist: eqc-direct==2.0.2
|
|
21
19
|
Provides-Extra: dev
|
|
22
20
|
Requires-Dist: pytest<8,>=7.1.0; extra == "dev"
|
|
23
21
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
@@ -41,6 +41,7 @@ class EqcModel:
|
|
|
41
41
|
_upper_bound = None
|
|
42
42
|
_H = None
|
|
43
43
|
_machine_slacks = 0
|
|
44
|
+
_is_discrete = None
|
|
44
45
|
|
|
45
46
|
def decode(self, solution : np.ndarray, from_encoding : str=None) -> np.ndarray:
|
|
46
47
|
"""
|
|
@@ -117,6 +118,16 @@ class EqcModel:
|
|
|
117
118
|
warn("The domains property is deprecated in favor of naming it upper_bound", DeprecationWarning)
|
|
118
119
|
self._upper_bound = value
|
|
119
120
|
|
|
121
|
+
@property
|
|
122
|
+
def is_discrete(self) -> List:
|
|
123
|
+
""" An array of boolean values indicating if variable should be restricted to a discrete domain """
|
|
124
|
+
|
|
125
|
+
return self._is_discrete
|
|
126
|
+
|
|
127
|
+
@is_discrete.setter
|
|
128
|
+
def is_discrete(self, value : List):
|
|
129
|
+
self._is_discrete = value
|
|
130
|
+
|
|
120
131
|
@property
|
|
121
132
|
def n(self) -> int:
|
|
122
133
|
""" Return the number of variables """
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
def make_binary_penalty(var_idx, slack_idx, max_degree=2, penalty_multiplier=None):
|
|
3
|
+
"""
|
|
4
|
+
Make a penalty expression enforcing binary values
|
|
5
|
+
|
|
6
|
+
The expression is formed from the conditions
|
|
7
|
+
|
|
8
|
+
$$
|
|
9
|
+
x + w = 1
|
|
10
|
+
$$
|
|
11
|
+
|
|
12
|
+
$$
|
|
13
|
+
xw = 0
|
|
14
|
+
$$
|
|
15
|
+
|
|
16
|
+
$$
|
|
17
|
+
w^2 + 2xw + x^2 - 2w - 2x + 1 + xw = 0
|
|
18
|
+
$$
|
|
19
|
+
|
|
20
|
+
$$
|
|
21
|
+
3xw + w^2 + x^2 - 2w - 2x + 1 = 0
|
|
22
|
+
$$
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
indices = [(0, var_idx), (0, slack_idx), (var_idx, slack_idx), (var_idx, var_idx), (slack_idx, slack_idx)]
|
|
27
|
+
coefficients = [-2, -2, 3, 1, 1]
|
|
28
|
+
offset = 1
|
|
29
|
+
if penalty_multiplier is not None:
|
|
30
|
+
coefficients = [penalty_multiplier * c for c in coefficients]
|
|
31
|
+
offset *- penalty_multiplier
|
|
32
|
+
return coefficients, indices, offset
|
|
33
|
+
|
|
@@ -82,6 +82,45 @@ class ConstraintsMixIn:
|
|
|
82
82
|
def constraints(self, value: Tuple[np.ndarray, np.ndarray]):
|
|
83
83
|
self.lhs, self.rhs = value
|
|
84
84
|
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _stackLHS(*args):
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
>>> m1 = np.array([1, 1, 1])
|
|
90
|
+
>>> m2 = np.array([[2.0, 2.0]])
|
|
91
|
+
>>> m3 = np.array([[0, 3, 3, 1], [1, 0, 1, 0]])
|
|
92
|
+
>>> ConstraintsMixIn._stackLHS(m1, m2, m3)
|
|
93
|
+
array([[1., 1., 1., 0.],
|
|
94
|
+
[2., 2., 0., 0.],
|
|
95
|
+
[0., 3., 3., 1.],
|
|
96
|
+
[1., 0., 1., 0.]])
|
|
97
|
+
|
|
98
|
+
"""
|
|
99
|
+
n = 0
|
|
100
|
+
m = 0
|
|
101
|
+
dtype = np.int32
|
|
102
|
+
def update_dtype(matrix):
|
|
103
|
+
type_rank = ["int8", "int16", "int32", "int64", "float8", "float16", "float32", "float64"]
|
|
104
|
+
try:
|
|
105
|
+
idx = type_rank.index(str(matrix.dtype))
|
|
106
|
+
except ValueError:
|
|
107
|
+
raise TypeError(f"matrix type {matrix.dtype} not supported")
|
|
108
|
+
new_args = []
|
|
109
|
+
for mat in args:
|
|
110
|
+
if len(mat.shape) == 1:
|
|
111
|
+
mat = mat.reshape((1, mat.size))
|
|
112
|
+
if mat.shape[1] > n:
|
|
113
|
+
n = mat.shape[1]
|
|
114
|
+
m += mat.shape[0]
|
|
115
|
+
dtype = update_dtype(mat)
|
|
116
|
+
new_args.append(mat)
|
|
117
|
+
new_lhs = np.zeros((m, n), dtype=dtype)
|
|
118
|
+
idx = 0
|
|
119
|
+
for mat in new_args:
|
|
120
|
+
new_lhs[idx:idx+mat.shape[0],:mat.shape[1]] = mat
|
|
121
|
+
idx += mat.shape[0]
|
|
122
|
+
return new_lhs
|
|
123
|
+
|
|
85
124
|
@property
|
|
86
125
|
def offset(self) -> float:
|
|
87
126
|
""" Calculate the offset due to the conversion of constraints to penalties """
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# (C) Quantum Computing Inc., 2024.
|
|
2
2
|
from typing import Tuple, Union, List
|
|
3
|
+
import logging
|
|
3
4
|
import numpy as np
|
|
4
5
|
from eqc_models.base.base import EqcModel
|
|
5
6
|
from eqc_models.base.operators import Polynomial, QUBO, OperatorNotAvailableError
|
|
6
7
|
from eqc_models.base.constraints import ConstraintsMixIn
|
|
7
8
|
|
|
9
|
+
log = logging.getLogger(name=__name__)
|
|
10
|
+
|
|
8
11
|
class PolynomialMixin:
|
|
9
12
|
"""This class provides an instance method and property that
|
|
10
13
|
manage polynomial models.
|
|
@@ -46,7 +49,12 @@ class PolynomialMixin:
|
|
|
46
49
|
|
|
47
50
|
"""
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
coefficients, indices = self.H
|
|
53
|
+
log.debug("Coefficients (%d): %s", len(coefficients), coefficients)
|
|
54
|
+
log.debug("Indices (%d): %s", len(indices), indices)
|
|
55
|
+
polynomial = self.polynomial
|
|
56
|
+
log.debug("Polynomial: %s", polynomial)
|
|
57
|
+
value = polynomial.evaluate(np.array(solution))
|
|
50
58
|
|
|
51
59
|
return value
|
|
52
60
|
|
|
@@ -107,8 +115,19 @@ class PolynomialModel(PolynomialMixin, EqcModel):
|
|
|
107
115
|
"""
|
|
108
116
|
|
|
109
117
|
def __init__(self, coefficients : Union[List, np.ndarray], indices : Union[List, np.ndarray]) -> None:
|
|
118
|
+
# ensure that the coefficients are ordered and not duplicated
|
|
119
|
+
new_coefficients = {}
|
|
120
|
+
for idx, v in zip(indices, coefficients):
|
|
121
|
+
idx = tuple(idx)
|
|
122
|
+
if idx in new_coefficients:
|
|
123
|
+
new_coefficients[idx] += v
|
|
124
|
+
else:
|
|
125
|
+
new_coefficients[idx] = v
|
|
126
|
+
new_indices = [idx for idx in new_coefficients]
|
|
127
|
+
new_indices.sort()
|
|
128
|
+
coefficients = [new_coefficients[idx] for idx in new_indices]
|
|
110
129
|
self.coefficients = coefficients
|
|
111
|
-
self.indices =
|
|
130
|
+
self.indices = new_indices
|
|
112
131
|
|
|
113
132
|
@property
|
|
114
133
|
def polynomial(self) -> Polynomial:
|
|
@@ -245,6 +245,6 @@ class ConstrainedQuadraticModel(ConstraintsMixIn, QuadraticModel):
|
|
|
245
245
|
return self.lhs, self.rhs
|
|
246
246
|
|
|
247
247
|
def evaluateObjective(self, solution: np.ndarray) -> float:
|
|
248
|
-
J = self.quad_objective
|
|
249
|
-
C = self.linear_objective
|
|
248
|
+
J = np.array(self.quad_objective)
|
|
249
|
+
C = np.array(self.linear_objective)
|
|
250
250
|
return np.squeeze(C.T @ solution + solution.T@J@solution)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
+
from typing import Dict
|
|
2
3
|
import warnings
|
|
3
4
|
import numpy as np
|
|
4
5
|
|
|
@@ -41,6 +42,14 @@ class SolutionResults:
|
|
|
41
42
|
device : str
|
|
42
43
|
String that represents the device used to solve the model.
|
|
43
44
|
|
|
45
|
+
raw_solutions : np.ndarray
|
|
46
|
+
Numpy array of the solutions as returned by the solver device.
|
|
47
|
+
|
|
48
|
+
calibration_time : float
|
|
49
|
+
Total time spend during job exectution where the device performed calibration.
|
|
50
|
+
The calibration is not directly affected by the job submission and the time
|
|
51
|
+
is not included in run_time.
|
|
52
|
+
|
|
44
53
|
time_units : str
|
|
45
54
|
String indicator of the unit of time reported in the metrics. Only
|
|
46
55
|
ns is supported at this time.
|
|
@@ -56,6 +65,7 @@ class SolutionResults:
|
|
|
56
65
|
postprocessing_time : np.ndarray
|
|
57
66
|
penalties : np.ndarray = None
|
|
58
67
|
device : str = None
|
|
68
|
+
raw_solutions : np.ndarray = None
|
|
59
69
|
time_units : str = "ns"
|
|
60
70
|
|
|
61
71
|
@property
|
|
@@ -95,7 +105,22 @@ class SolutionResults:
|
|
|
95
105
|
|
|
96
106
|
@classmethod
|
|
97
107
|
def from_cloud_response(cls, model, response, solver):
|
|
98
|
-
"""
|
|
108
|
+
"""
|
|
109
|
+
Fill in the details from the cloud
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
------------
|
|
113
|
+
|
|
114
|
+
model : eqc_models.base.EqcModel
|
|
115
|
+
EqcModel object describing the problem solved in response
|
|
116
|
+
|
|
117
|
+
response : Dict
|
|
118
|
+
Dictionary of the repsonse from the solver device.
|
|
119
|
+
|
|
120
|
+
solver : eqc_models.base.ModelSolver
|
|
121
|
+
ModelSolver object which is used to obtain job metrics.
|
|
122
|
+
|
|
123
|
+
"""
|
|
99
124
|
|
|
100
125
|
solutions = np.array(response["results"]["solutions"])
|
|
101
126
|
if model.machine_slacks > 0:
|
|
@@ -164,3 +189,58 @@ class SolutionResults:
|
|
|
164
189
|
device=device_type, time_units="ns")
|
|
165
190
|
|
|
166
191
|
return results
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def from_eqcdirect_response(cls, model, response, solver):
|
|
195
|
+
"""
|
|
196
|
+
Fill in details from the response dictionary and possibly the solver device.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
------------
|
|
200
|
+
|
|
201
|
+
model : eqc_models.base.EqcModel
|
|
202
|
+
EqcModel object describing the problem solved in response
|
|
203
|
+
|
|
204
|
+
response : Dict
|
|
205
|
+
Dictionary of the repsonse from the solver device.
|
|
206
|
+
|
|
207
|
+
solver : eqc_models.base.ModelSolver
|
|
208
|
+
ModelSolver object which is used to obtain device information.
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
solutions = np.array(response["solution"])
|
|
212
|
+
if model.machine_slacks > 0:
|
|
213
|
+
solutions = solutions[:,:-model.machine_slacks]
|
|
214
|
+
energies = np.array(response["energy"])
|
|
215
|
+
# interrogate to determine the device type
|
|
216
|
+
info_dict = solver.client.system_info()
|
|
217
|
+
device_type = info_dict["device_type"]
|
|
218
|
+
if hasattr(model, "evaluateObjective"):
|
|
219
|
+
objectives = np.zeros((solutions.shape[0],), dtype=np.float32)
|
|
220
|
+
for i in range(solutions.shape[0]):
|
|
221
|
+
try:
|
|
222
|
+
objective = model.evaluateObjective(solutions[i])
|
|
223
|
+
except NotImplementedError:
|
|
224
|
+
warnings.warn(f"Cannot set objective value in results for {model.__class__}")
|
|
225
|
+
objectives = None
|
|
226
|
+
break
|
|
227
|
+
objectives[i] = objective
|
|
228
|
+
else:
|
|
229
|
+
objectives = None
|
|
230
|
+
if hasattr(model, "evaluatePenalties"):
|
|
231
|
+
penalties = np.zeros((solutions.shape[0],), dtype=np.float32)
|
|
232
|
+
for i in range(solutions.shape[0]):
|
|
233
|
+
penalties[i] = model.evaluatePenalties(solutions[i]) + model.offset
|
|
234
|
+
else:
|
|
235
|
+
penalties = None
|
|
236
|
+
counts = np.ones(solutions.shape[0])
|
|
237
|
+
runtime = response["runtime"]
|
|
238
|
+
post = response["postprocessing_time"]
|
|
239
|
+
pre = response["preprocessing_time"]
|
|
240
|
+
results = SolutionResults(solutions, energies, counts, objectives,
|
|
241
|
+
runtime, pre, post, penalties=penalties,
|
|
242
|
+
device=device_type, time_units="s")
|
|
243
|
+
|
|
244
|
+
return results
|
|
245
|
+
|
|
246
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# (C) Quantum Computing Inc., 2024.
|
|
2
|
+
|
|
3
|
+
from .base import EdgeMixin, EdgeModel, GraphModel, NodeModel
|
|
4
|
+
from .maxcut import MaxCutModel
|
|
5
|
+
from .partition import GraphPartitionModel
|
|
6
|
+
from .shortestpath import ShortestPathModel
|
|
7
|
+
from .rcshortestpath import RCShortestPathModel
|
|
8
|
+
|
|
9
|
+
__all__ = ["MaxCutModel", "GraphPartitionModel",
|
|
10
|
+
"EdgeMixin", "EdgeModel", "GraphModel",
|
|
11
|
+
"NodeModel"]
|
|
@@ -7,7 +7,7 @@ class GraphModel(QuadraticModel):
|
|
|
7
7
|
""" """
|
|
8
8
|
def __init__(self, G : nx.Graph):
|
|
9
9
|
self.G = G
|
|
10
|
-
super().__init__(*self.costFunction())
|
|
10
|
+
super(GraphModel, self).__init__(*self.costFunction())
|
|
11
11
|
|
|
12
12
|
@property
|
|
13
13
|
def linear_objective(self):
|
|
@@ -63,12 +63,16 @@ class TwoPartitionModel(NodeModel):
|
|
|
63
63
|
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
|
-
class
|
|
67
|
-
""" Create a model where the variables are edge-based """
|
|
66
|
+
class EdgeMixin:
|
|
68
67
|
|
|
69
68
|
@property
|
|
70
69
|
def variables(self) -> List[str]:
|
|
71
70
|
""" Provide a variable name to index lookup; order enforced by sorting the list before returning """
|
|
72
|
-
names = [
|
|
71
|
+
names = [(u, v) for u, v in self.G.edges]
|
|
73
72
|
names.sort()
|
|
74
73
|
return names
|
|
74
|
+
|
|
75
|
+
class EdgeModel(EdgeMixin, GraphModel):
|
|
76
|
+
""" Create a model where the variables are edge-based """
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import (Any, Dict, Tuple, List)
|
|
3
|
+
import networkx as nx
|
|
4
|
+
import numpy as np
|
|
5
|
+
from eqc_models.graph.shortestpath import ShortestPathModel
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(name=__name__)
|
|
8
|
+
|
|
9
|
+
class RCShortestPathModel(ShortestPathModel):
|
|
10
|
+
"""
|
|
11
|
+
Model for resource constrained shortest path problems.
|
|
12
|
+
Use a shortest path base model to implement the routing
|
|
13
|
+
constraints and objective function. Add resource constraints.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, G : nx.DiGraph, s : Any, t : Any, T : int, resource_key : str="resource"):
|
|
18
|
+
if T <= 0:
|
|
19
|
+
raise ValueError("T must be positive")
|
|
20
|
+
elif round(T, 0) != T:
|
|
21
|
+
raise ValueError("T must be integer-valued")
|
|
22
|
+
self.T = T
|
|
23
|
+
self.resource_key = resource_key
|
|
24
|
+
# determine shortest path by weight
|
|
25
|
+
nx_path = nx.shortest_path(G, s, t)
|
|
26
|
+
path_length = len(nx_path)
|
|
27
|
+
self.resource_mult = resource_mult = 2 * path_length / self.T
|
|
28
|
+
super(RCShortestPathModel, self).__init__(G, s, t)
|
|
29
|
+
upper_bound = np.ones(len(self.variables))
|
|
30
|
+
upper_bound[-1] = np.ceil(resource_mult * T)
|
|
31
|
+
self.upper_bound = upper_bound
|
|
32
|
+
is_discrete = [True for i in range(upper_bound.shape[0])]
|
|
33
|
+
is_discrete[-1] = False
|
|
34
|
+
self.is_discrete = is_discrete
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def variables(self):
|
|
38
|
+
variables = super(RCShortestPathModel, self).variables
|
|
39
|
+
return variables + ["resource_slack"]
|
|
40
|
+
|
|
41
|
+
def buildConstraints(self) -> Tuple[np.ndarray,np.ndarray]:
|
|
42
|
+
lhs, rhs = super(RCShortestPathModel, self).buildConstraints()
|
|
43
|
+
# add a single constraint
|
|
44
|
+
G = self.G
|
|
45
|
+
n = len(self.variables)
|
|
46
|
+
resource_lhs = np.zeros((1, n), dtype=np.float32)
|
|
47
|
+
resource_mult = self.resource_mult
|
|
48
|
+
log.debug("Resource multiplier %f", resource_mult)
|
|
49
|
+
for i in range(len(G.edges)):
|
|
50
|
+
(u, v) = self.variables[i]
|
|
51
|
+
# find the time to traverse the arc
|
|
52
|
+
resource_cost = G.edges[(u, v)][self.resource_key]
|
|
53
|
+
resource_cost *= resource_mult
|
|
54
|
+
log.debug(f"Adding resource %s for edge %s", resource_cost, (u, v))
|
|
55
|
+
resource_lhs[0, i] = resource_cost
|
|
56
|
+
resource_lhs[0, -1] = 1
|
|
57
|
+
lhs = self._stackLHS(lhs, resource_lhs)
|
|
58
|
+
rhs = np.hstack([rhs, [self.T*resource_mult]])
|
|
59
|
+
log.debug("LHS shape %s RHS shape %s", lhs.shape, rhs.shape)
|
|
60
|
+
return lhs, rhs
|
|
61
|
+
|
|
62
|
+
def pathCost(self, path):
|
|
63
|
+
""" sum the cost of all legs in the path """
|
|
64
|
+
assert self.s in path
|
|
65
|
+
G = self.G
|
|
66
|
+
node = self.s
|
|
67
|
+
max_len = len(self.G.nodes) - 1
|
|
68
|
+
path_len = 0
|
|
69
|
+
path_cost = 0
|
|
70
|
+
path_resources = 0
|
|
71
|
+
while node != t:
|
|
72
|
+
edge = (node, path[node])
|
|
73
|
+
if edge not in G.edges:
|
|
74
|
+
raise ValueError(f"Edge {edge} not found")
|
|
75
|
+
path_len += 1
|
|
76
|
+
path_cost += G.edges[edge]["weight"]
|
|
77
|
+
path_resources += G.edges[edge][self.resource_key]
|
|
78
|
+
if path_len > max_len:
|
|
79
|
+
raise ValueError("Invalid path. Describes a cycle.")
|
|
80
|
+
return path_cost, path_resources
|
|
81
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
MIP Shortest Path implementation
|
|
3
|
+
|
|
4
|
+
Given a graph $G$ and nodes $s$ and $t$, find the shortest path
|
|
5
|
+
by edge weight between $s$ and $t$.
|
|
6
|
+
|
|
7
|
+
$$
|
|
8
|
+
\min sum_ij w_ij x_ij
|
|
9
|
+
$$
|
|
10
|
+
subject to
|
|
11
|
+
$$
|
|
12
|
+
\sum_{(u,v)\in E} x_{u,v} - \sum{(v,u)\in E} x_{v,u} = 0 \forall u\in N\\{s,t}
|
|
13
|
+
$$
|
|
14
|
+
and
|
|
15
|
+
$$
|
|
16
|
+
\sum_{(s,v)\in E} x_{s,v} = 1
|
|
17
|
+
$$
|
|
18
|
+
and
|
|
19
|
+
$$
|
|
20
|
+
\sum_{(u,t)\in E} x_{u,t} = 1
|
|
21
|
+
$$
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Any, Dict, Tuple
|
|
26
|
+
import logging
|
|
27
|
+
import numpy as np
|
|
28
|
+
import networkx as nx
|
|
29
|
+
from eqc_models.graph import EdgeMixin
|
|
30
|
+
from eqc_models.base.quadratic import ConstrainedQuadraticModel
|
|
31
|
+
|
|
32
|
+
log = logging.getLogger(name=__name__)
|
|
33
|
+
|
|
34
|
+
class ShortestPathModel(EdgeMixin, ConstrainedQuadraticModel):
|
|
35
|
+
"""
|
|
36
|
+
ShortestPathModel describes the MIP formulation for the
|
|
37
|
+
shortest path problem.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
-------------
|
|
41
|
+
|
|
42
|
+
G : nx.DiGraph
|
|
43
|
+
A directed graph which is assumed to be connected. A graph
|
|
44
|
+
with disconnected subgraphs may reveal a solution if $s$ and $t$
|
|
45
|
+
are in the same subgraph, but testing for the existence of a path
|
|
46
|
+
between s and t using this model is not recommended. This is
|
|
47
|
+
due to the difficulty posed by selecting a penalty multiplier
|
|
48
|
+
large enough to enforce the panalties, which DNE in the infeasible
|
|
49
|
+
case.
|
|
50
|
+
s : Any
|
|
51
|
+
This is the label for the start node.
|
|
52
|
+
t : Any
|
|
53
|
+
This is the label for the end node.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, G: nx.DiGraph, s : Any, t : Any):
|
|
59
|
+
self.G = G
|
|
60
|
+
self.s = s
|
|
61
|
+
self.t = t
|
|
62
|
+
self.lhs, self.rhs = self.buildConstraints()
|
|
63
|
+
C, J = self.buildObjective()
|
|
64
|
+
super(ShortestPathModel, self).__init__(C, J, self.lhs, self.rhs)
|
|
65
|
+
self.upper_bound = np.ones(self.lhs.shape[1])
|
|
66
|
+
self.is_discrete = [True for i in range(self.lhs.shape[1])]
|
|
67
|
+
self.machine_slacks = 0
|
|
68
|
+
|
|
69
|
+
def buildConstraints(self) -> Tuple[np.ndarray,np.ndarray]:
|
|
70
|
+
"""
|
|
71
|
+
Constraints:
|
|
72
|
+
$$
|
|
73
|
+
sum_j x[i,l] - sum_j x[j,l] = c for all l
|
|
74
|
+
$$
|
|
75
|
+
$c$ is -1, 1 or 0 for $i=t$, $s$ or all others
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
log.debug("Building constraints to find path from %s to %s", self.s, self.t)
|
|
79
|
+
variables = self.variables
|
|
80
|
+
nodes = [n for n in self.G.nodes]
|
|
81
|
+
m = len(nodes)
|
|
82
|
+
n = len(variables)
|
|
83
|
+
_cons = np.zeros((m, n), dtype=np.int8)
|
|
84
|
+
_rhs = np.zeros((m, 1), dtype=np.int8)
|
|
85
|
+
for node_index, k in enumerate(nodes):
|
|
86
|
+
if k == self.s:
|
|
87
|
+
_rhs[node_index, 0] = 1
|
|
88
|
+
elif k == self.t:
|
|
89
|
+
_rhs[node_index, 0] = -1
|
|
90
|
+
for l in range(len(self.G.edges)): # don't enumerate the edges because the order could change
|
|
91
|
+
(i, j) = self.variables[l]
|
|
92
|
+
if i == j:
|
|
93
|
+
# self loops are not allowed
|
|
94
|
+
raise ValueError("Self loops are not allowed in ShortestPathModel")
|
|
95
|
+
# # ignore these edges because we can't go back to s or leave t
|
|
96
|
+
elif j == self.s:
|
|
97
|
+
continue
|
|
98
|
+
elif i == self.t:
|
|
99
|
+
continue
|
|
100
|
+
i_index = nodes.index(i)
|
|
101
|
+
j_index = nodes.index(j)
|
|
102
|
+
_cons[i_index, l] = 1
|
|
103
|
+
_cons[j_index, l] = -1
|
|
104
|
+
log.debug("LHS shape %s RHS shape %s", _cons.shape, _rhs.shape)
|
|
105
|
+
log.debug("checksum %f min %f", np.sum(_cons), np.min(_cons))
|
|
106
|
+
assert np.sum(_rhs) == 0
|
|
107
|
+
return _cons, np.squeeze(_rhs)
|
|
108
|
+
|
|
109
|
+
def buildObjective(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
110
|
+
r"""
|
|
111
|
+
Objective:
|
|
112
|
+
$\min sum_ij w_ij x_ij$
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
variables = self.variables
|
|
116
|
+
G = self.G
|
|
117
|
+
nodes = G.nodes
|
|
118
|
+
m, n = len(nodes), len(variables)
|
|
119
|
+
_obj = [0 for i in range(n)]
|
|
120
|
+
for index, name in enumerate(variables):
|
|
121
|
+
if type(name) == type((1,2)):
|
|
122
|
+
i, j = name
|
|
123
|
+
_obj[index] = v = G.get_edge_data(i, j)["weight"]
|
|
124
|
+
assert not np.isnan(v), f"Got a NaN at {i, j}"
|
|
125
|
+
J = np.zeros((n, n))
|
|
126
|
+
return np.array(_obj), J
|
|
127
|
+
|
|
128
|
+
def decode(self, solution : np.ndarray) -> Dict:
|
|
129
|
+
"""
|
|
130
|
+
Convert a solution to this model into a path, which is
|
|
131
|
+
a dictionary with each edge described by key, value pairs.
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
variables = self.variables[:len(self.G.edges)]
|
|
135
|
+
# log.debug("Using variables %s", variables)
|
|
136
|
+
|
|
137
|
+
lhs, rhs = self.constraints
|
|
138
|
+
log.debug("LHS shape %s RHS shape %s", lhs.shape, rhs.shape)
|
|
139
|
+
upper_thresh = max(solution[:len(variables)])
|
|
140
|
+
lower_thresh = 0
|
|
141
|
+
got_path = None
|
|
142
|
+
while upper_thresh - lower_thresh > 1e-6:
|
|
143
|
+
log.debug("Lower Value: %f Upper Value %f", lower_thresh, upper_thresh)
|
|
144
|
+
thresh = (lower_thresh + upper_thresh) / 2
|
|
145
|
+
nx_path = None
|
|
146
|
+
G = nx.DiGraph()
|
|
147
|
+
for (i, j), value in zip(variables, solution):
|
|
148
|
+
if value > thresh:
|
|
149
|
+
G.add_edge(i, j)
|
|
150
|
+
edges = [e for e in G.edges]
|
|
151
|
+
log.debug("Resulting edge count %s", len(edges))
|
|
152
|
+
self.alt_g = G
|
|
153
|
+
path = {}
|
|
154
|
+
try:
|
|
155
|
+
nx_path = nx.shortest_path(G, self.s, self.t)
|
|
156
|
+
lower_thresh = thresh
|
|
157
|
+
got_path = nx_path
|
|
158
|
+
log.debug("Got path size %d", len(got_path))
|
|
159
|
+
except (nx.exception.NodeNotFound, nx.NetworkXAlgorithmError) as err:
|
|
160
|
+
upper_thresh = thresh
|
|
161
|
+
if got_path is None:
|
|
162
|
+
raise RuntimeError(f"Solution does not describe path from {self.s} to {self.t}")
|
|
163
|
+
# path = {}
|
|
164
|
+
# log.debug("Translating path to dictionary")
|
|
165
|
+
# for i, v in enumerate(got_path):
|
|
166
|
+
# path[got_path[i-1]] = v
|
|
167
|
+
# log.debug("Updated path %s", path)
|
|
168
|
+
# if self.t in path:
|
|
169
|
+
# log.debug("Removing %s from path keys.", self.t)
|
|
170
|
+
# del path[self.t]
|
|
171
|
+
return got_path
|
|
172
|
+
|
|
173
|
+
def pathCost(self, path):
|
|
174
|
+
""" sum the cost of all legs in the path """
|
|
175
|
+
assert self.s in path
|
|
176
|
+
G = self.G
|
|
177
|
+
node = self.s
|
|
178
|
+
max_len = len(self.G.nodes) - 1
|
|
179
|
+
path_len = 0
|
|
180
|
+
path_cost = 0
|
|
181
|
+
while node != t:
|
|
182
|
+
edge = (node, path[node])
|
|
183
|
+
if edge not in G.edges:
|
|
184
|
+
raise ValueError(f"Edge {edge} not found")
|
|
185
|
+
path_len += 1
|
|
186
|
+
path_cost += G.edges[edge]["weight"]
|
|
187
|
+
if path_len > max_len:
|
|
188
|
+
raise ValueError("Invalid path. Describes a cycle.")
|
|
189
|
+
return path_cost
|
|
190
|
+
|